// app.jsx — Correx TUI shell: layout, live state, keybind engine, theme + tweaks.
const { useState, useEffect, useRef, useCallback } = React;
const D = window.CORREX;
const { applyTheme, THEMES, ACCENTS } = window.CORREX_THEMES;

const DIR_ORDER = ["boxed", "soft", "bare"];
const ACC_ORDER = ["cyan", "amber", "green", "magenta", "neutral"];
const SPIN = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "direction": "boxed",
  "accent": "cyan",
  "fontSize": 13,
  "motion": true,
  "scanlines": false
}/*EDITMODE-END*/;

function Panel({ n, title, right, active, className, footer, bodyRef, children }) {
  return (
    <div className={"panel" + (active ? " active" : "") + (className ? " " + className : "")}>
      <div className="panel-h">
        {n && <span className="pn">{n}</span>}
        <span className="pt">{title}</span>
        {right && <span className="ph-r">{right}</span>}
      </div>
      <div className="panel-b" ref={bodyRef}>{children}</div>
      {footer}
    </div>
  );
}

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const rootRef = useRef(null);
  const evRef = useRef(null);

  const [overlay, setOverlay] = useState(null);        // 'cmd'|'events'|'context'|'graph'|'models'|'tools'|'approval'|null
  const [focusPanel, setFocusPanel] = useState("conv"); // 'conv'|'events'
  const [sessionState, setSessionState] = useState("awaiting_approval");
  const [activeModel, setActiveModel] = useState(D.SESSION.model);
  const [tools, setTools] = useState(D.TOOLS);
  const [autoApprove, setAutoApprove] = useState(false);
  const [approvalResolved, setApprovalResolved] = useState(false);

  const [msgs, setMsgs] = useState(D.CONVERSATION);
  const [events, setEvents] = useState(D.EVENTS);
  const [stages, setStages] = useState(D.STAGES);
  const [toast, setToast] = useState(null);

  const [streamTarget, setStreamTarget] = useState(D.CONVERSATION.length - 1);
  const [streamLen, setStreamLen] = useState(0);
  const [frame, setFrame] = useState(0);

  const [draft, setDraft] = useState("");
  const [promptFocus, setPromptFocus] = useState(false);
  const promptRef = useRef(null);
  const convRef = useRef(null);

  // theme application
  useEffect(() => { if (rootRef.current) applyTheme(rootRef.current, t.direction, t.accent); }, [t.direction, t.accent]);

  // spinner / clock frames
  useEffect(() => {
    if (!t.motion) return;
    const id = setInterval(() => setFrame((f) => f + 1), 110);
    return () => clearInterval(id);
  }, [t.motion]);

  // streaming reveal
  useEffect(() => {
    if (streamTarget == null) return;
    const full = (msgs[streamTarget] || {}).text || "";
    if (streamLen >= full.length) return;
    if (!t.motion) { setStreamLen(full.length); return; }
    const id = setTimeout(() => setStreamLen((n) => Math.min(n + 3, full.length)), 16);
    return () => clearTimeout(id);
  }, [streamTarget, streamLen, msgs, t.motion]);

  // autoscroll event panel
  useEffect(() => { if (evRef.current) evRef.current.scrollTop = evRef.current.scrollHeight; }, [events]);
  // autoscroll conversation
  useEffect(() => { if (convRef.current) convRef.current.scrollTop = convRef.current.scrollHeight; }, [msgs, streamLen]);

  const flash = (m) => { setToast(m); setTimeout(() => setToast(null), 2600); };
  const pushEvent = (e) => setEvents((arr) => [...arr, { id: "x" + arr.length + Math.random().toString(36).slice(2, 5), ...e }]);
  const addMsg = (m) => { setMsgs((arr) => { const ni = arr.length; setStreamTarget(ni); setStreamLen(0); return [...arr, m]; }); };
  const appendMsg = (m) => setMsgs((arr) => [...arr, m]);

  // user steering input → routes back through Correx
  const hm = () => new Date().toTimeString().slice(0, 5);
  const replyFor = (q) => {
    const s = q.toLowerCase();
    if (/test|spec|cover/.test(s)) return "Queued — I'll add SegmentStoreTest with an append→read round-trip and a dedup assertion (identical bytes ⇒ one object). Runs at the validation gate.";
    if (/perf|bench|fast|speed|throughput/.test(s)) return "Noted. I'll add a JMH harness for append throughput; the mmap read path stays zero-copy so reads aren't affected by compression.";
    if (/revert|undo|stop|cancel|rollback/.test(s)) return "Holding edits. Nothing destructive ran — the gated gradle resolve was the only external op. Awaiting your next instruction.";
    if (/compress|zstd|gzip|lz4|codec/.test(s)) return "Compression is pluggable behind a SegmentCodec — zstd for cold, raw for hot. I can swap lz4 for lower CPU at a worse ratio; say the word.";
    if (/index|offset|seek|lookup/.test(s)) return "SegmentIndex maps logical offset ranges → CAS keys with a binary search over segment bounds; O(log n) seeks. I'll memo the hot range.";
    return "Understood — folding that into the implementation plan. I'll surface a revised ImplementationArtifact at the validation gate for review.";
  };
  const send = () => {
    const text = draft.trim(); if (!text) return;
    setDraft("");
    setStreamTarget(null);
    appendMsg({ role: "user", t: hm(), text });
    const at = (ms, fn) => seqRef.current.push(setTimeout(fn, ms));
    at(320, () => pushEvent({ t: hm() + ":02", cat: "Context", type: "steer.captured", text: "router → agent context (L1)" }));
    at(760, () => addMsg({ role: "kernel", kind: "route", text: "Steering synthesized → injected at L1 · agent context updated" }));
    at(1500, () => addMsg({ role: "assistant", t: hm(), text: replyFor(text) }));
  };

  // resolve approval → drive the live sequence
  const seqRef = useRef([]);
  const clearSeq = () => { seqRef.current.forEach(clearTimeout); seqRef.current = []; };
  useEffect(() => () => clearSeq(), []);

  const resolveApproval = useCallback((kind, note) => {
    setOverlay(null);
    setApprovalResolved(true);
    setSessionState("active");
    setEvents((arr) => arr.map((e) => e.pending ? { ...e, pending: false } : e));
    const at = (ms, fn) => seqRef.current.push(setTimeout(fn, ms));

    if (kind === "reject") {
      flash("Rejected — agent re-plans without the network tool");
      pushEvent({ t: "14:12:31", cat: "Approval", type: "approval.rejected", text: "operator rejected T3 gate" });
      at(700, () => pushEvent({ t: "14:12:33", cat: "Inference", type: "infer.start", text: "re-planning · no-network constraint" }));
      at(1600, () => addMsg({ role: "assistant", t: "14:12", text: "Understood — dropping zstd. I'll compress cold segments with the JDK Deflater instead: no new dependency, no network. Slightly worse ratio, zero supply-chain surface." }));
      at(3400, () => pushEvent({ t: "14:12:48", cat: "Domain", type: "artifact.emitted", text: "ImplementationArtifact ✓ (deflater path)" }));
      at(4200, () => transition());
      return;
    }
    // approve / auto / steer
    const auto = kind === "auto";
    if (auto) setAutoApprove(true);
    flash(kind === "steer" ? "Steered + approved — note injected into agent context" : auto ? "Auto-approve enabled for session" : "Approved");
    pushEvent({ t: "14:12:14", cat: "Approval", type: kind === "steer" ? "approval.steered" : "approval.approved", text: note ? "“" + note + "”" : "operator approved" + (auto ? " · auto-session ON" : "") });
    at(500, () => pushEvent({ t: "14:12:15", cat: "Tool", type: "shell.exec", text: "gradle resolving zstd-jni …", running: true }));
    at(2100, () => setEvents((arr) => arr.map((e) => e.running ? { id: e.id, t: "14:12:19", cat: "Tool", type: "shell.exec", text: "gradle resolve OK (4.1s) · +1 dep" } : e)));
    at(2600, () => addMsg({ role: "assistant", t: "14:12", text: (note ? "Locking the version as asked. " : "") + "Resolved zstd-jni 1.5.6-3. Cold segments now zstd-compress on write; hot segments stay raw for mmap. Emitting the implementation artifact." }));
    at(4600, () => pushEvent({ t: "14:12:34", cat: "Domain", type: "artifact.emitted", text: "ImplementationArtifact ✓ valid" }));
    at(5400, () => transition());
  }, []);

  const transition = () => {
    pushEvent({ t: "14:12:36", cat: "Lifecycle", type: "stage.transition", text: "implementation → validation" });
    setStages((arr) => arr.map((s) => s.id === "implementation" ? { ...s, status: "done", note: "artifact accepted" } : s.id === "validation" ? { ...s, status: "active", enter: "14:12", note: "schema + semantic checks" } : s));
    pushEvent({ t: "14:12:36", cat: "Lifecycle", type: "stage.enter", text: "validation (role=reviewer)" });
  };

  // auto-open the approval gate once (modal interrupt demonstration)
  const raised = useRef(false);
  useEffect(() => {
    if (raised.current) return; raised.current = true;
    const id = setTimeout(() => { if (!approvalResolved) setOverlay("approval"); }, 1100);
    return () => clearTimeout(id);
  }, []);

  // ── keybind engine ──
  const cycle = (key, order) => setTweak(key, order[(order.indexOf(t[key]) + 1) % order.length]);
  const setDirection = (dir) => setTweak({ direction: dir, accent: THEMES[dir].defaultAccent });
  const cycleDir = () => setDirection(DIR_ORDER[(DIR_ORDER.indexOf(t.direction) + 1) % DIR_ORDER.length]);
  const open = (id) => {
    if (id === "appearance") { cycleDir(); return; }
    if (id === "approval") { if (approvalResolved) { flash("No pending approvals"); return; } setOverlay("approval"); return; }
    setOverlay(id);
  };

  useEffect(() => {
    const onKey = (e) => {
      const typing = /^(INPUT|TEXTAREA)$/.test(document.activeElement && document.activeElement.tagName);
      // approval modal chords
      if (overlay === "approval") {
        if (typing) return;
        if (e.key === "a") { resolveApproval("approve"); e.preventDefault(); }
        else if (e.key === "A") { resolveApproval("auto"); e.preventDefault(); }
        else if (e.key === "r") { resolveApproval("reject"); e.preventDefault(); }
        else if (e.key === "Escape") setOverlay(null);
        return;
      }
      if (e.key === "Escape") { if (overlay) { setOverlay(null); e.preventDefault(); } return; }
      if (typing) return;
      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") { setOverlay("cmd"); e.preventDefault(); return; }
      if (overlay === "cmd") return; // palette owns keys
      if (overlay) return;          // other overlays: only esc (above)
      switch (e.key) {
        case "p": case ":": case "/": setOverlay("cmd"); e.preventDefault(); break;
        case "e": open("events"); break;
        case "c": open("context"); break;
        case "g": open("graph"); break;
        case "m": open("models"); break;
        case "t": open("tools"); break;
        case "i": promptRef.current && promptRef.current.focus(); e.preventDefault(); break;
        case "a": open("approval"); break;
        case "F2": cycleDir(); e.preventDefault(); break;
        case "F3": cycle("accent", ACC_ORDER); e.preventDefault(); break;
        case "Tab": setFocusPanel((p) => p === "conv" ? "events" : "conv"); e.preventDefault(); break;
        default: break;
      }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [overlay, approvalResolved, t.direction, t.accent, resolveApproval]);

  // ── footer hints (contextual) ──
  const hints = (() => {
    if (overlay === "approval") return [["a", "approve"], ["A", "auto-session"], ["s", "steer"], ["r", "reject"], ["esc", "later"]];
    if (overlay === "cmd") return [["↑↓", "move"], ["↵", "run"], ["esc", "close"]];
    if (overlay) return [["↑↓", "navigate"], ["↵", "select"], ["esc", "back"]];
    if (promptFocus) return [["↵", "send to correx"], ["esc", "blur"]];
    return [["i", "message"], ["p", "commands"], ["a", "approval"], ["e", "events"], ["c", "context"], ["g", "graph"], ["m", "models"], ["t", "tools"], ["F2", "skin"]];
  })();

  const tokPct = Math.min(100, Math.round((D.CONTEXT_PACK.used / D.CONTEXT_PACK.budget) * 100) + (approvalResolved ? 4 : 0));
  const spin = SPIN[frame % SPIN.length];

  return (
    <div className="crx" ref={rootRef} style={{ fontSize: t.fontSize, "--scan": t.scanlines ? 1 : 0 }}>
      {t.scanlines && <div style={{ position: "absolute", inset: 0, zIndex: 30, pointerEvents: "none", background: "repeating-linear-gradient(0deg, rgb(0 0 0 / 0.16) 0 1px, transparent 1px 3px)" }} />}

      {/* top status line */}
      <div className="crx-top">
        <span className="crx-brand">corre<b>x</b></span>
        <span className="dot">·</span>
        <span className="seg">session <b>{D.SESSION.name}</b></span>
        <span className="dot">·</span>
        <span className="seg"><b>{D.SESSION.branch}</b></span>
        <span className="dot">·</span>
        <span className="seg">{activeModel} <span style={{ color: "var(--faint)" }}>({D.MODELS.find((m) => m.id === activeModel).provider})</span></span>
        {autoApprove && <span className="pill live">auto-approve</span>}
        <span className="crx-spacer" />
        <span className="crx-budget">ctx <div className="crx-meter"><i style={{ width: tokPct + "%" }} /></div> {tokPct}%</span>
        <span className={"crx-state"} style={sessionState === "active" ? { color: "var(--ok)", borderColor: "color-mix(in oklab,var(--ok),transparent 60%)", background: "color-mix(in oklab,var(--ok),transparent 88%)" } : null}>
          {sessionState === "awaiting_approval" ? "⏸ awaiting approval" : "▸ active"}
        </span>
      </div>

      {/* main split */}
      <div className="crx-main">
        <Panel n="[1]" title="router · conversation" right={D.SESSION.project} active={focusPanel === "conv"} bodyRef={convRef}
          footer={
            <div className={"prompt" + (promptFocus ? " foc" : "")} onClick={() => promptRef.current && promptRef.current.focus()}>
              <span className="pr">{promptFocus ? "❯" : "›"}</span>
              <input ref={promptRef} value={draft} placeholder="message correx — steer the run…"
                onFocus={() => setPromptFocus(true)} onBlur={() => setPromptFocus(false)}
                onChange={(e) => setDraft(e.target.value)}
                onKeyDown={(e) => { e.stopPropagation(); if (e.key === "Enter") send(); else if (e.key === "Escape") promptRef.current.blur(); }} />
              {!promptFocus && !draft && <span className="ph"><kbd>i</kbd> focus · <kbd>↵</kbd> send</span>}
            </div>
          }>
          {msgs.map((m, i) => {
            const streaming = i === streamTarget;
            const text = streaming ? m.text.slice(0, streamLen) : m.text;
            const who = m.role === "user" ? "you" : m.role === "kernel" ? "correx" : "agent";
            return (
              <div className={"msg " + m.role} key={i}>
                <span className="msg-gut"><span className="who">{who}</span>{m.t}</span>
                <span className="msg-body">{text}{streaming && streamLen < m.text.length && <span className="cursor" />}{streaming && streamLen >= m.text.length && sessionState === "awaiting_approval" && <span className="cursor" />}</span>
              </div>
            );
          })}
        </Panel>

        <Panel n="[2]" title="event stream" active={focusPanel === "events"}
          right={sessionState === "awaiting_approval" ? <span style={{ color: "var(--warn)" }}>{spin} paused</span> : <span style={{ color: "var(--ok)" }}>{spin} live</span>}>
          <div ref={evRef} style={{ height: "100%", overflow: "auto" }}>
            {events.map((e) => (
              <div className={"ev" + (e.pending ? " pending" : "")} key={e.id} onClick={() => setOverlay("events")}>
                <span className="ev-t">{e.t}</span>
                <span className={"ev-cat cat-" + e.cat}>{e.cat}</span>
                <span className="ev-tx">{e.running && <span className="spin">{spin} </span>}<b>{e.type}</b> {e.text}{e.pending && <span className="kbd-inline" style={{ marginLeft: 6 }}>a</span>}</span>
              </div>
            ))}
          </div>
        </Panel>
      </div>

      {/* footer keybind bar */}
      <div className="crx-foot">
        {hints.map(([k, l], i) => (
          <span className="kb" key={i} onClick={() => {
            const map = { p: () => setOverlay("cmd"), a: () => open("approval"), e: () => open("events"), c: () => open("context"), g: () => open("graph"), m: () => open("models"), t: () => open("tools"), i: () => promptRef.current && promptRef.current.focus(), F2: () => cycleDir() };
            map[k] && map[k]();
          }}>
            <kbd>{k}</kbd><b>{l}</b>
          </span>
        ))}
        <span className="foot-mode">{THEMES[t.direction].label} · <b>{t.accent}</b></span>
      </div>

      {toast && <div style={{ position: "absolute", bottom: 46, left: "50%", transform: "translateX(-50%)", zIndex: 50, background: "var(--panel-2)", border: "1px solid var(--accent)", color: "var(--fg-strong)", padding: "8px 16px", borderRadius: 8, fontSize: 12.5, boxShadow: "0 14px 40px -16px #000" }}>{toast}</div>}

      {/* overlays */}
      {overlay === "cmd" && <CommandPalette commands={D.COMMANDS} onRun={(id) => { setOverlay(null); setTimeout(() => open(id), 0); }} onClose={() => setOverlay(null)} />}
      {overlay === "approval" && <ApprovalModal data={D.APPROVAL} onApprove={(auto) => resolveApproval(auto ? "auto" : "approve")} onReject={() => resolveApproval("reject")} onSteer={(n) => resolveApproval("steer", n)} onClose={() => setOverlay(null)} />}
      {overlay === "events" && <EventInspector events={events} onClose={() => setOverlay(null)} />}
      {overlay === "context" && <ContextPackInspector pack={D.CONTEXT_PACK} onClose={() => setOverlay(null)} />}
      {overlay === "graph" && <StageGraph stages={stages} transitions={D.TRANSITIONS} onClose={() => setOverlay(null)} />}
      {overlay === "models" && <ModelRegistry models={D.MODELS} active={activeModel} onSwitch={(id) => { setActiveModel(id); setOverlay(null); flash("Switched → " + id + " · re-synthesizing Context Pack"); pushEvent({ t: "14:12", cat: "Context", type: "pack.resynth", text: "for " + id }); }} onClose={() => setOverlay(null)} />}
      {overlay === "tools" && <ToolPalette tools={tools} onToggle={(id) => setTools((arr) => arr.map((x) => x.id === id ? { ...x, enabled: !x.enabled } : x))} onClose={() => setOverlay(null)} />}

      {/* tweaks */}
      <TweaksPanel>
        <TweakSection label="Aesthetic direction" />
        <TweakRadio label="Chrome" value={t.direction} options={["boxed", "soft", "bare"]} onChange={(v) => setDirection(v)} />
        <TweakColor label="Accent" value={t.accent === "cyan" ? "#56c8d8" : t.accent === "amber" ? "#e8b765" : t.accent === "green" ? "#8fd96a" : t.accent === "magenta" ? "#c98fd9" : "#b9c0c9"}
          options={["#56c8d8", "#e8b765", "#8fd96a", "#c98fd9", "#b9c0c9"]}
          onChange={(hex) => { const map = { "#56c8d8": "cyan", "#e8b765": "amber", "#8fd96a": "green", "#c98fd9": "magenta", "#b9c0c9": "neutral" }; setTweak("accent", map[hex] || "cyan"); }} />
        <TweakSection label="Display" />
        <TweakSlider label="Font size" value={t.fontSize} min={11} max={17} step={1} unit="px" onChange={(v) => setTweak("fontSize", v)} />
        <TweakToggle label="Live motion" value={t.motion} onChange={(v) => setTweak("motion", v)} />
        <TweakToggle label="CRT scanlines" value={t.scanlines} onChange={(v) => setTweak("scanlines", v)} />
      </TweaksPanel>
    </div>
  );
}

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