/* App shell: 4 resizable columns, keyboard shortcuts, copy-reference, status bar */

function Splitter({ orientation = "v", onDelta }) {
  const [drag, setDrag] = useState(false);
  function down(e) {
    e.preventDefault();
    let last = { x: e.clientX, y: e.clientY };
    setDrag(true);
    document.body.style.cursor = orientation === "v" ? "col-resize" : "row-resize";
    document.body.style.userSelect = "none";
    function mv(ev) {
      onDelta(ev.clientX - last.x, ev.clientY - last.y);
      last = { x: ev.clientX, y: ev.clientY };
    }
    function up() {
      setDrag(false);
      document.body.style.cursor = ""; document.body.style.userSelect = "";
      document.removeEventListener("mousemove", mv); document.removeEventListener("mouseup", up);
    }
    document.addEventListener("mousemove", mv); document.addEventListener("mouseup", up);
  }
  return <div className={"splitter" + (orientation === "h" ? " h" : "") + (drag ? " drag" : "")} onMouseDown={down} />;
}

function RightColumn({ width }) {
  const [topFrac, setTopFrac] = useState(0.52);
  const ref = useRef(null);
  function delta(dx, dy) {
    const h = ref.current ? ref.current.clientHeight : 600;
    setTopFrac((f) => Math.max(0.18, Math.min(0.82, (f * h + dy) / h)));
  }
  const agent = useMemo(() => agentSeed(), []);
  const shell = useMemo(() => shellSeed(), []);
  return (
    <div className="col right-col" style={{ width, flex: "0 0 " + width + "px" }}>
      <div ref={ref} style={{ flex: 1, minHeight: 0, display: "flex", flexDirection: "column" }}>
        <div style={{ flex: "0 0 " + (topFrac * 100) + "%", minHeight: 0, display: "flex" }}>
          <Terminal kind="agent" seed={agent} />
        </div>
        <Splitter orientation="h" onDelta={delta} />
        <div style={{ flex: 1, minHeight: 0, display: "flex" }}>
          <Terminal kind="shell" seed={shell} />
        </div>
      </div>
    </div>
  );
}

function clamp(v, lo, hi) { return Math.max(lo, Math.min(hi, v)); }
function ancestors(path) {
  const parts = path.split("/"); const out = [];
  for (let i = 1; i < parts.length; i++) out.push(parts.slice(0, i).join("/"));
  return out;
}
function initialOpenDirs(node, set) {
  if (node.type === "dir") {
    if (node.open && node.path) set.add(node.path);
    (node.children || []).forEach((c) => initialOpenDirs(c, set));
  }
  return set;
}

function App() {
  const changeMap = useMemo(() => Object.fromEntries(PROJECT.changes.map((c) => [c.path, c.status])), []);
  const changeSet = useMemo(() => new Set(PROJECT.changes.map((c) => c.path)), []);

  const [tabs, setTabs] = useState([
    { path: "src/Http/Controller/UserController.php" },
    { path: "public/assets/app.js" },
    { path: "src/types/api.ts" },
  ]);
  const [active, setActive] = useState("src/Http/Controller/UserController.php");
  const [tabMode, setTabMode] = useState({ "src/Http/Controller/UserController.php": "diff" });
  const [openDirs, setOpenDirs] = useState(() => initialOpenDirs(PROJECT.tree, new Set()));
  const [cursor, setCursor] = useState({ path: "src/Http/Controller/UserController.php", line: 1, col: 1 });
  const [selection, setSelection] = useState(null);
  const [overlay, setOverlay] = useState(null);
  const [menu, setMenu] = useState(null);
  const [toasts, setToasts] = useState([]);
  const [splitFor, setSplitFor] = useState(null);
  const [staged, setStaged] = useState(() => new Set(["src/Service/PaymentService.php", "config/app.json"]));
  const [committed, setCommitted] = useState(() => new Set());
  const [commitMsg, setCommitMsg] = useState("");
  const [passPopup, setPassPopup] = useState(null);

  const [gitW, setGitW] = useState(232);
  const [treeW, setTreeW] = useState(244);
  const [rightW, setRightW] = useState(444);

  function toast(title, ref) {
    const id = lid();
    setToasts((t) => [...t, { id, title, ref }]);
    setTimeout(() => setToasts((t) => t.filter((x) => x.id !== id)), 2300);
  }
  async function copyText(text, label) {
    try { await navigator.clipboard.writeText(text); }
    catch (e) {
      const ta = document.createElement("textarea"); ta.value = text;
      ta.style.position = "fixed"; ta.style.opacity = "0"; document.body.appendChild(ta);
      ta.select(); try { document.execCommand("copy"); } catch (_) {} ta.remove();
    }
    toast(label || "Copied reference", text);
  }

  const toggleDir = useCallback((p) => {
    setOpenDirs((s) => { const n = new Set(s); n.has(p) ? n.delete(p) : n.add(p); return n; });
  }, []);

  function reveal(path) {
    setOpenDirs((s) => { const n = new Set(s); ancestors(path).forEach((a) => n.add(a)); return n; });
  }

  const stage = (p) => setStaged((s) => { const n = new Set(s); n.add(p); return n; });
  const unstage = (p) => setStaged((s) => { const n = new Set(s); n.delete(p); return n; });
  const stageAll = () => setStaged(new Set(PROJECT.changes.filter((c) => !committed.has(c.path)).map((c) => c.path)));
  const unstageAll = () => setStaged(new Set());
  function commit() {
    const list = PROJECT.changes.filter((c) => staged.has(c.path) && !committed.has(c.path));
    if (!list.length || !commitMsg.trim()) return;
    setCommitted((prev) => { const n = new Set(prev); list.forEach((c) => n.add(c.path)); return n; });
    setStaged(new Set());
    const msg = commitMsg.trim();
    setCommitMsg("");
    toast(`Committed ${list.length} file${list.length > 1 ? "s" : ""}`, msg.length > 34 ? msg.slice(0, 34) + "…" : msg);
  }

  function openFile(path, opts = {}) {
    const changed = !!PROJECT.diffs[path];
    setTabs((t) => t.some((x) => x.path === path) ? t : [...t, { path }]);
    setActive(path);
    setTabMode((m) => ({ ...m, [path]: opts.diff && changed ? "diff" : (m[path] || (changed ? "diff" : "code")) }));
    reveal(path);
    if (opts.line) {
      // show the current/updated file so line numbers map to search hits
      setSplitFor(null);
      setTabMode((m) => ({ ...m, [path]: changed ? "updated" : "code" }));
      setCursor({ path, line: opts.line, col: 1 });
      setSelection(null);
      setTimeout(() => {
        const row = document.querySelector('.editor .ln-row[data-line="' + opts.line + '"]');
        if (row) { const ed = row.closest(".editor"); const er = ed.getBoundingClientRect(), rr = row.getBoundingClientRect(); ed.scrollTop += (rr.top - er.top) - ed.clientHeight / 2; }
      }, 70);
    }
  }

  function closeTab(path) {
    setTabs((t) => {
      const ix = t.findIndex((x) => x.path === path);
      const next = t.filter((x) => x.path !== path);
      if (path === active) {
        const fallback = next[ix] || next[ix - 1] || next[next.length - 1];
        setActive(fallback ? fallback.path : null);
      }
      return next;
    });
  }

  // ---- context menus ----
  function openMenu(e, target) {
    e.preventDefault(); e.stopPropagation();
    const sparkSend = (ref) => ({ icon: Icon.spark({}), label: "Send reference to agent", onClick: () => { window.dispatchEvent(new CustomEvent("agentPaste", { detail: ref })); toast("Passed to agent", ref); } });
    if (target.kind === "editor") {
      const ref = target.sel ? `${target.path}:${target.sel.start}-${target.sel.end}` : `${target.path}:${target.line}`;
      const mx = e.clientX, my = e.clientY;
      setMenu({
        x: mx, y: my, note: ref,
        items: [
          { primary: true, icon: Icon.copy({}), label: "Copy reference", onClick: () => copyText(ref) },
          { icon: Icon.spark({}), label: "Pass on to Agent", onClick: () => setPassPopup({ x: mx, y: my, ref }) },
        ],
      });
    } else {
      const isDir = target.kind === "dir";
      const ref = isDir ? target.path + "/" : target.path;
      const name = target.path.split("/").pop();
      const items = [
        { primary: true, icon: Icon.copy({}), label: "Copy reference", onClick: () => copyText(ref) },
        sparkSend(ref),
        { icon: Icon.copy({}), label: isDir ? "Copy folder path" : "Copy file name", onClick: () => copyText(isDir ? target.path : name, "Copied") },
      ];
      if (!isDir) {
        items.push({ sep: true });
        if (target.kind === "git") {
          const isStaged = staged.has(target.path);
          items.push(isStaged
            ? { icon: Icon.minus({}), label: "Unstage changes", onClick: () => unstage(target.path) }
            : { icon: Icon.plus({}), label: "Stage changes", onClick: () => stage(target.path) });
          items.push({ icon: Icon.diff({}), label: "Open diff", onClick: () => openFile(target.path, { diff: true }) });
        }
        items.push({ icon: Icon.file({}), label: "Open file", onClick: () => openFile(target.path) });
        items.push({ icon: Icon.reveal({}), label: "Reveal in Explorer", onClick: () => reveal(target.path) });
      }
      setMenu({ x: e.clientX, y: e.clientY, note: ref, items });
    }
  }

  // ---- shortcuts ----
  useEffect(() => {
    function onKey(e) {
      const meta = e.metaKey || e.ctrlKey;
      if (meta && e.key.toLowerCase() === "f") { e.preventDefault(); setOverlay("search"); }
      else if (meta && e.key.toLowerCase() === "w") { e.preventDefault(); if (active) closeTab(active); }
      else if (e.key === "Escape") { if (splitFor) setSplitFor(null); else { setOverlay(null); setMenu(null); } }
    }
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [active, splitFor]);

  const MODE_LABEL = { original: "orig", updated: "upd", diff: "diff", code: "" };
  const MODE_WORD = { original: "Original", updated: "Updated", diff: "Diff" };
  const resolvedTabs = tabs.map((t) => {
    const changed = !!PROJECT.diffs[t.path];
    const m = tabMode[t.path] || (changed ? "diff" : "code");
    return { ...t, changed, modeLabel: splitFor === t.path ? "split" : MODE_LABEL[m] };
  });
  const mode = tabMode[active] || (PROJECT.diffs[active] ? "diff" : "code");
  const totals = PROJECT.changes.reduce((a, c) => ({ add: a.add + c.add, del: a.del + c.del }), { add: 0, del: 0 });
  const activeLang = active ? HL.langLabel(active) : "";

  const crumb = active ? active.split("/") : [];

  return (
    <div className="app">
      {/* title bar */}
      <div className="titlebar">
        <div className="traffic"><i className="r" /><i className="y" /><i className="g" /></div>
        <div className="tb-title">{Icon.spark({ style: { color: "var(--accent)" } })}<b>Helder</b><span style={{ color: "var(--fg-3)" }}>—</span><span style={{ color: "var(--fg-2)" }}>{PROJECT.name}</span></div>
        {active && (
          <div className="tb-crumb">
            {crumb.map((s, i) => (<React.Fragment key={i}>{i > 0 && <span className="seg"> › </span>}<span style={i === crumb.length - 1 ? { color: "var(--fg-1)" } : null}>{s}</span></React.Fragment>))}
          </div>
        )}
        <div className="tb-spacer" />
        <div className="tb-actions">
          <button className="tb-btn" onClick={() => setOverlay("search")}>{Icon.search({})} Search <kbd>⌘F</kbd></button>
        </div>
      </div>

      {/* workbench */}
      <div className="workbench">
        <div className="col" style={{ width: gitW, flex: "0 0 " + gitW + "px" }}>
          <GitPanel changes={PROJECT.changes} staged={staged} committed={committed}
            commitMsg={commitMsg} setCommitMsg={setCommitMsg}
            onStage={stage} onUnstage={unstage} onStageAll={stageAll} onUnstageAll={unstageAll} onCommit={commit}
            onOpen={openFile} onContext={openMenu} activePath={active} />
        </div>
        <Splitter onDelta={(dx) => setGitW((w) => clamp(w + dx, 160, 460))} />

        <div className="col" style={{ width: treeW, flex: "0 0 " + treeW + "px" }}>
          <FileTree tree={PROJECT.tree} openDirs={openDirs} toggleDir={toggleDir} onOpen={openFile}
            onContext={openMenu} activePath={active} changeMap={changeMap} committed={committed} />
        </div>
        <Splitter onDelta={(dx) => setTreeW((w) => clamp(w + dx, 160, 520))} />

        <div className="col editor-col">
          <Editor tabs={resolvedTabs} active={active} mode={mode}
            setMode={(m) => { setTabMode((mm) => ({ ...mm, [active]: m })); setSplitFor(null); }}
            onActivate={setActive} onClose={closeTab} onContext={openMenu}
            onSplit={(p) => setSplitFor(p)} splitOpen={splitFor === active}
            cursor={cursor} selection={selection} setCursor={setCursor} setSelection={setSelection} />
        </div>
        <Splitter onDelta={(dx) => setRightW((w) => clamp(w - dx, 280, 780))} />

        <RightColumn width={rightW} />
      </div>

      {/* status bar */}
      <div className="statusbar">
        <div className="sb accent">{Icon.branch({ width: 12, height: 12 })}<span style={{ color: "#0c1320" }}>{PROJECT.branch}</span></div>
        <div className="sb"><span className="a">+{totals.add}</span> <span className="d">−{totals.del}</span></div>
        <div className="sb spacer" />
        {active && <div className="sb">{selection && selection.path === active && selection.start !== selection.end ? `${selection.end - selection.start + 1} lines selected` : `Ln ${cursor.path === active ? cursor.line : 1}, Col ${cursor.path === active ? cursor.col : 1}`}</div>}
        {active && <div className="sb">Spaces: 4</div>}
        {active && <div className="sb">UTF-8</div>}
        {active && <div className="sb"><b>{activeLang}</b></div>}
        {active && PROJECT.diffs[active] && <div className="sb">{splitFor === active ? "Split" : (MODE_WORD[mode] || "")}</div>}
      </div>

      {/* overlays */}
      {splitFor && <SplitView path={splitFor} onClose={() => setSplitFor(null)} onContext={openMenu} />}
      {passPopup && <PassPopup x={passPopup.x} y={passPopup.y} refStr={passPopup.ref}
        onConfirm={(text) => {
          const line = (text && text.trim() ? text.trim() + " " : "") + passPopup.ref;
          window.dispatchEvent(new CustomEvent("agentPaste", { detail: line }));
          setPassPopup(null);
          toast("Passed to agent", passPopup.ref);
        }}
        onCancel={() => setPassPopup(null)} />}
      {overlay === "search" && <SearchModal onOpen={openFile} onOpenAt={(p, n) => openFile(p, { line: n })} onClose={() => setOverlay(null)} changeSet={changeSet} />}
      {menu && <ContextMenu menu={menu} onClose={() => setMenu(null)} />}
      <Toasts toasts={toasts} />
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
