// Terminal — main shell pane. CLI + rendered output lines.
// Commands dispatch from this component. Split-pane layout in shell.jsx.

function TermPane({ paneId, initial, onFocus, isFocused, prompts = true }) {
  const C = window.TERM_PALETTE;
  const T = window.TERM_CONTENT;
  const G = window.TERM_GAMES;
  const [lines, setLines] = React.useState(initial || []);
  const [input, setInput] = React.useState('');
  const [history, setHistory] = React.useState([]);
  const [histIdx, setHistIdx] = React.useState(-1);
  const [cwd, setCwd] = React.useState('~');
  const [mode, setMode] = React.useState(null); // {kind:'snake'|'hangman', state, cleanup}
  const scrollRef = React.useRef(null);
  const inputRef = React.useRef(null);

  const append = (...xs) => setLines((ls) => [...ls, ...xs]);
  const replaceBlock = (id, block) => setLines((ls) => ls.map((l) => l.type === 'block' && l.id === id ? block : l));
  const print = (x) => {
    if (x && x.type === 'update') replaceBlock(x.id, { type: 'block', id: x.id, color: x.color, lines: x.lines });
    else append(x);
  };

  React.useEffect(() => {
    if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
  }, [lines, mode]);

  React.useEffect(() => {
    if (isFocused && inputRef.current && !mode) inputRef.current.focus();
  }, [isFocused, mode]);

  // Listen for external command injections (from sidebar nav).
  React.useEffect(() => {
    const h = (e) => {
      if (!e.detail || e.detail.paneId !== paneId) return;
      run(e.detail.cmd);
    };
    window.addEventListener('mf-term-run', h);
    return () => window.removeEventListener('mf-term-run', h);
  });

  const run = (raw) => {
    const cmd = raw.trim();
    // echo prompt+cmd
    append({ type: 'prompt', cwd, cmd });
    if (!cmd) return;
    setHistory((h) => [...h, cmd]);
    setHistIdx(-1);

    const [head, ...rest] = cmd.split(/\s+/);
    const arg = rest.join(' ');

    const found = {
      help: () => append(...T.help),
      about: () => append(...T.about),
      projects: () => append(...T.projects),
      now: () => append(...T.now),
      uses: () => append(...T.uses),
      contact: () => append(...T.contact),
      banner: () => append(...T.banner),
      motd: () => append(...T.motd),
      whoami: () => append(mkLine(C.fg, 'shawn')),
      pwd: () => append(mkLine(C.fg, cwd.replace('~', '/home/shawn'))),
      date: () => append(mkLine(C.fg, new Date().toString())),
      clear: () => setLines([]),
      exit: () => append(mkLine(C.red, 'logout'), mkLine(C.dim, '(refresh to log back in)')),
      fortune: () => append(mkLine(C.yellow, T.fortunes[Math.floor(Math.random() * T.fortunes.length)])),
      cowsay: () => append(...G.cowsay(arg)),
      ls: () => append(...doLs(arg)),
      cat: () => append(...doCat(arg)),
      cd: () => doCd(arg),
      echo: () => append(mkLine(C.fg, arg)),
      sudo: () => append(
        mkLine(C.red, '[sudo] password for shawn: ' + '*'.repeat(8)),
        mkLine(C.yellow, 'Sorry, user shawn is not in the sudoers file. This incident will be reported.'),
        mkLine(C.dim, '(nice try.)'),
      ),
      rm: () => {
        if (rest.join(' ').includes('-rf') && rest.join(' ').includes('/')) {
          G.rmrfSteps().forEach((s, i) => {
            setTimeout(() => append(mkLine(i >= 4 && i <= 8 ? C.red : C.fg, s)), i * 220);
          });
        } else append(mkLine(C.red, `rm: refuse to do anything without arguments. that\'s the one thing i\'m good at.`));
      },
      sl: () => { G.sl(print, () => append(mkLine(C.dim, '# you should have typed `ls`.'))); },
      snake: () => {
        const s = G.makeSnake();
        setMode({ kind: 'snake', state: s });
      },
      hangman: () => {
        const h = G.makeHangman();
        setMode({ kind: 'hangman', state: h });
      },
      neofetch: () => append(mkLine(C.dim, '(you asked me to skip this.)')),
      '': () => {},
    }[head];

    if (found) found();
    else append(mkLine(C.red, `${head}: command not found`), mkLine(C.dim, `try \`help\``));
  };

  const mkLine = (color, text) => ({ type: 'line', parts: [{ type: 'span', color, text }] });

  const doLs = (path) => {
    const full = resolvePath(path || '.');
    const entries = T.fs[full === '/home/shawn' ? '/home/shawn' : full];
    if (!entries) return [mkLine(C.red, `ls: cannot access '${path}': No such file or directory`)];
    const showHidden = (path || '').includes('-a') || (path || '').startsWith('-');
    const stripped = (path || '').replace(/^-\w+\s*/, '').trim();
    const base = stripped || '.';
    const target = resolvePath(base);
    const list = T.fs[target === '/home/shawn' ? '/home/shawn' : target] || [];
    const visible = list.filter((n) => showHidden || !n.startsWith('.'));
    return [{
      type: 'line',
      parts: visible.map((n) => ({
        type: 'span',
        color: n.endsWith('/') ? C.blue : (n.startsWith('.') ? C.dim : C.fg),
        text: n + '  ',
      })),
    }];
  };

  const doCat = (name) => {
    if (!name) return [mkLine(C.red, 'cat: missing file operand')];
    const key = T.fileMap[name] || T.fileMap[name.replace(/^\.\//, '')];
    if (!key) return [mkLine(C.red, `cat: ${name}: No such file or directory`)];
    return T[key] || [mkLine(C.red, `cat: ${name}: I/O error`)];
  };

  const doCd = (path) => {
    if (!path || path === '~') { setCwd('~'); return; }
    if (path === '..') { setCwd('~'); return; }
    if (path === 'projects' || path === 'projects/' || path === '~/projects') { setCwd('~/projects'); return; }
    append(mkLine(C.red, `cd: ${path}: No such file or directory`));
  };

  const resolvePath = (p) => {
    if (!p || p === '.' || p === '~') return '/home/shawn';
    if (p === '/') return '/';
    if (p === 'projects' || p === 'projects/' || p === '~/projects') return '/home/shawn/projects';
    if (p === '/etc' || p === '/etc/') return '/etc';
    return p;
  };

  const onKey = (e) => {
    if (mode) return;
    if (e.key === 'Enter') { run(input); setInput(''); }
    else if (e.key === 'ArrowUp') {
      e.preventDefault();
      if (!history.length) return;
      const ni = histIdx < 0 ? history.length - 1 : Math.max(0, histIdx - 1);
      setHistIdx(ni); setInput(history[ni] || '');
    }
    else if (e.key === 'ArrowDown') {
      e.preventDefault();
      if (histIdx < 0) return;
      const ni = histIdx + 1;
      if (ni >= history.length) { setHistIdx(-1); setInput(''); }
      else { setHistIdx(ni); setInput(history[ni]); }
    }
    else if (e.key === 'Tab') {
      e.preventDefault();
      const cmds = ['help','about','projects','now','uses','contact','ls','cat','clear','banner','motd','fortune','cowsay','sl','snake','hangman','sudo','exit','whoami','pwd','date','echo','rm','cd','neofetch'];
      const m = cmds.filter((c) => c.startsWith(input.trim()));
      if (m.length === 1) setInput(m[0] + ' ');
      else if (m.length > 1) append({ type: 'line', parts: m.map((x) => ({ type: 'span', color: C.cyan, text: x + '  ' })) });
    }
    else if (e.key === 'l' && e.ctrlKey) { e.preventDefault(); setLines([]); }
    else if (e.key === 'c' && e.ctrlKey) { e.preventDefault(); append({ type: 'prompt', cwd, cmd: input + '^C' }); setInput(''); }
  };

  // mode loop
  React.useEffect(() => {
    if (!mode) return;
    if (mode.kind === 'snake') {
      const s = mode.state;
      const t = setInterval(() => { s.step(); setLines((ls) => [...ls.filter((l) => l.type !== 'gamepanel'), { type: 'gamepanel', lines: s.render(), color: C.green }]); }, 120);
      const k = (e) => {
        if (e.key === 'Escape') { clearInterval(t); window.removeEventListener('keydown', k); setMode(null); setLines((ls) => ls.filter((l) => l.type !== 'gamepanel')); append(mkLine(C.dim, '# snake closed.')); return; }
        if (e.key.startsWith('Arrow')) { e.preventDefault(); s.setDir(e.key); }
      };
      window.addEventListener('keydown', k);
      return () => { clearInterval(t); window.removeEventListener('keydown', k); };
    }
    if (mode.kind === 'hangman') {
      const h = mode.state;
      setLines((ls) => [...ls, { type: 'gamepanel', lines: h.render(), color: C.yellow }]);
      const k = (e) => {
        if (e.key === 'Escape' || (h.isDone() && e.key.length === 1)) {
          window.removeEventListener('keydown', k); setMode(null);
          setLines((ls) => ls.filter((l) => l.type !== 'gamepanel'));
          append(mkLine(C.dim, '# hangman closed.'));
          return;
        }
        if (e.key.length === 1) { h.guess(e.key); setLines((ls) => [...ls.filter((l) => l.type !== 'gamepanel'), { type: 'gamepanel', lines: h.render(), color: C.yellow }]); }
      };
      window.addEventListener('keydown', k);
      return () => window.removeEventListener('keydown', k);
    }
  }, [mode]);

  return (
    <div onMouseDown={() => onFocus && onFocus(paneId)} ref={scrollRef}
      style={{
        height: '100%', overflow: 'auto', padding: '10px 14px', background: C.bg,
        fontFamily: window.TERM_FONT, fontSize: 13, lineHeight: 1.5, color: C.fg,
        boxSizing: 'border-box',
        borderTop: isFocused ? `1px solid ${C.orange}` : '1px solid transparent',
      }}>
      {lines.map((l, i) => <RenderLine key={i} line={l} C={C} />)}
      {prompts && !mode && (
        <div style={{ display: 'flex', alignItems: 'center' }}>
          <InlinePrompt cwd={cwd} C={C} />
          <input ref={inputRef} value={input} onChange={(e) => setInput(e.target.value)} onKeyDown={onKey}
            spellCheck={false} autoComplete="off"
            style={{
              flex: 1, background: 'transparent', border: 'none', outline: 'none',
              color: C.fg, fontFamily: 'inherit', fontSize: 'inherit', caretColor: C.orange,
            }} />
        </div>
      )}
    </div>
  );
}

function InlinePrompt({ cwd, C }) {
  return (
    <div style={{ whiteSpace: 'pre' }}>
      <span style={{ color: C.user }}>shawn@mface</span>
      <span>:</span>
      <span style={{ color: C.path }}>{cwd}</span>
      <span style={{ color: C.orange }}>$ </span>
    </div>
  );
}

function RenderLine({ line, C }) {
  if (line.type === 'prompt') return (
    <div>
      <span style={{ color: C.user }}>shawn@mface</span>
      <span>:</span>
      <span style={{ color: C.path }}>{line.cwd}</span>
      <span style={{ color: C.orange }}>$ </span>
      <span style={{ color: C.fg }}>{line.cmd}</span>
    </div>
  );
  if (line.type === 'block' || line.type === 'gamepanel') {
    return (
      <pre style={{ margin: '4px 0', color: line.color || C.fg, fontFamily: 'inherit', whiteSpace: 'pre', lineHeight: 1.25 }}>
        {(line.lines || []).join('\n')}
      </pre>
    );
  }
  // default: 'line' with parts
  return (
    <div style={{ whiteSpace: 'pre-wrap' }}>
      {(line.parts || []).map((p, i) => {
        if (p.type === 'link') return (
          <a key={i} href={p.href} target="_blank" rel="noreferrer"
            style={{ color: p.color || C.cyan, textDecoration: 'none', borderBottom: `1px dotted ${p.color || C.cyan}`, fontWeight: p.bold ? 600 : 400 }}>{p.text}</a>
        );
        return <span key={i} style={{ color: p.color || C.fg, fontWeight: p.bold ? 600 : 400 }}>{p.text}</span>;
      })}
    </div>
  );
}

window.TermPane = TermPane;
