/* ============================================================
   app.jsx — shell + landing, wired to the gradio.Server backend.
   Adapted from the designer's prototype: the demo's fake upload
   is replaced with a real file picker that calls /analyze_trace
   through @gradio/client; the tweaks panel is dropped and the
   theme is pinned to the dusk-survey dark mode.
   ============================================================ */

function BrandMark({ size = 34 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 40 40" fill="none" aria-hidden="true" className="brandmark">
      <circle cx="20" cy="20" r="17" stroke="var(--edge-strong)" strokeWidth="1.2" />
      <path d="M8 24 C 14 16, 18 28, 24 18 S 32 12, 33 14" stroke="var(--ink-3)" strokeWidth="1" fill="none" strokeDasharray="1.5 3" />
      <path d="M6 20 C 13 10, 20 30, 27 16 S 34 14, 35 20" stroke="var(--accent)" strokeWidth="2" fill="none" strokeLinecap="round" />
      <circle cx="13" cy="20" r="2.4" fill="var(--tone-stable)" />
      <circle cx="22" cy="22.5" r="2.4" fill="var(--tone-partial)" />
      <circle cx="30" cy="17" r="2.4" fill="var(--tone-risk)" />
      <path d="M30 17 L30 9 L36 11 L30 13" fill="var(--accent)" />
    </svg>
  );
}

function TopBar() {
  return (
    <header className="topbar">
      <div className="topbar__brand">
        <BrandMark />
        <div className="topbar__word">
          <span className="topbar__name">Trace Field Notes</span>
          <span className="topbar__tag mono">narrative analysis for coding-agent traces</span>
        </div>
      </div>
      <div className="topbar__right mono">
        <span className="topbar__pill">narrative-only</span>
        <span className="topbar__pill">privacy-first</span>
      </div>
    </header>
  );
}

const ENGINES = [
  ["qwen", "Quick analysis", "Qwen3.5 9B"],
  ["nemotron", "Deeper analysis", "Nemotron 3 Nano 30B-A3B"],
  ["deterministic", "Rule-based", "no model, always on"],
];

function Toggle({ on, set, label, sub, locked }) {
  return (
    <button className={"toggle" + (on ? " toggle--on" : "") + (locked ? " toggle--locked" : "")}
      onClick={() => !locked && set(!on)} aria-pressed={on}>
      <span className="toggle__sw"><span className="toggle__knob" /></span>
      <span className="toggle__txt">
        <span className="toggle__label">{label}{locked ? " 🔒" : ""}</span>
        <span className="toggle__sub muted">{sub}</span>
      </span>
    </button>
  );
}

function LandingView({ onAnalyze, onSample, error }) {
  const [staged, setStaged] = React.useState(null); // { name, file }
  const [redact, setRedact] = React.useState(true);
  const [userCtx, setUserCtx] = React.useState(true);
  const [engine, setEngine] = React.useState("qwen");
  const [dragOver, setDragOver] = React.useState(false);
  const [copied, setCopied] = React.useState(false);
  const fileRef = React.useRef(null);

  const chosen = ENGINES.find((e) => e[0] === engine) || ENGINES[2];
  const engineLabel = chosen[1] + ": " + chosen[2];

  function onFiles(list) {
    const f = list && list[0];
    if (f) setStaged({ name: f.name, file: f });
  }
  function pick() { if (fileRef.current) fileRef.current.click(); }
  function run() {
    if (!staged) return;
    onAnalyze({ file: staged.file, include_user_context: userCtx, redact_secrets: redact, analysis_engine: engine, engineLabel });
  }

  const AGENT_PROMPT = `Use this Space as a tool.
1. Read its /agents.md endpoint.
2. Find my latest local agent session log
   (Codex ~/.codex/sessions, Claude ~/.claude/projects).
3. Review and redact secrets before upload.
4. Upload the JSONL and request a narrative difficulty analysis.
5. Return the report. Do not publish the raw trace.`;

  return (
    <div className="landing">
      <TopBar />

      <section className="hero">
        <Kicker>Field report · qualitative, not a leaderboard</Kicker>
        <h1 className="hero__title">See how your coding agent<br /> got stuck, detoured, recovered<span className="hero__amp"> &amp; </span>claimed success.</h1>
        <p className="hero__sub">
          Upload a Codex, Claude Code, or Pi Agent session log. Trace Field Notes reads only the agent's
          <em> narrated</em> messages — what it planned, where it snagged, how it rerouted, and how honestly it called it done —
          and charts the session as a trail you can walk.
        </p>
      </section>

      <div className="privacy">
        <span className="privacy__mark">!</span>
        <p>
          Agent traces can carry prompts, command output, local paths, screenshots, secrets, and private code.
          <b> Review and redact before uploading or sharing.</b> This app analyzes only visible narrative messages and ignores raw tool telemetry by default.
        </p>
      </div>

      {error ? (
        <div className="privacy" style={{ borderColor: "var(--tone-risk)", borderLeftColor: "var(--tone-risk)" }}>
          <span className="privacy__mark" style={{ background: "var(--tone-risk)" }}>×</span>
          <p><b>Analysis failed.</b> {error}</p>
        </div>
      ) : null}

      <div className="landing__grid">
        {/* LEFT: upload */}
        <div className="panel card card--raised">
          <SectionHead kicker="Step 01" title="Bring a trace" />
          <input ref={fileRef} type="file" accept=".jsonl,.json,.txt,.log" style={{ display: "none" }}
            onChange={(e) => onFiles(e.target.files)} />
          <div
            className={"drop" + (dragOver ? " drop--over" : "") + (staged ? " drop--staged" : "")}
            onDragOver={(e) => { e.preventDefault(); setDragOver(true); }}
            onDragLeave={() => setDragOver(false)}
            onDrop={(e) => { e.preventDefault(); setDragOver(false); onFiles(e.dataTransfer.files); }}
            onClick={pick}
            role="button" tabIndex={0}
            onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") pick(); }}
          >
            {staged ? (
              <div className="drop__staged">
                <span className="drop__file mono">{staged.name}</span>
                <span className="label">staged · click Analyze</span>
              </div>
            ) : (
              <div className="drop__empty">
                <div className="drop__icon">⤓</div>
                <span className="drop__title">Drop a <code>.jsonl</code> trace</span>
                <span className="muted">or click to choose · .json .txt .log accepted</span>
              </div>
            )}
          </div>

          <div className="opts">
            <Toggle on={redact} set={setRedact} label="Redact likely secrets" sub="emails, tokens, keys, paths" />
            <Toggle on={userCtx} set={setUserCtx} label="Include user context" sub="user prompts as framing" />
            <Toggle on={true} set={() => {}} locked label="Ignore tool contents" sub="locked for this release" />
          </div>

          <div className="engine">
            <Label>Analysis engine</Label>
            <div className="engine__opts">
              {ENGINES.map(([key, name, detail]) => (
                <button key={key}
                  className={"engine__opt" + (engine === key ? " engine__opt--on" : "")}
                  onClick={() => setEngine(key)}>
                  <span className="engine__name">{name}</span>
                  <span className="engine__detail mono">{detail}</span>
                </button>
              ))}
            </div>
            <p className="engine__note muted">Quick and Deeper run a small model on the Space GPU. Rule-based needs no model and never fails.</p>
          </div>

          <div className="panel__actions">
            <button className="btn btn--primary" disabled={!staged} onClick={run}>
              Analyze my trace
            </button>
            <button className="btn" onClick={() => onSample("short")}>Sample · short</button>
            <button className="btn" onClick={() => onSample("long")}>Sample · long</button>
          </div>
        </div>

        {/* RIGHT: guide */}
        <div className="guide">
          <div className="panel card">
            <SectionHead kicker="Step 00" title="Find your session log" />
            <table className="paths">
              <tbody>
                {[
                  ["Codex", "~/.codex/sessions"],
                  ["Claude Code", "~/.claude/projects"],
                  ["Pi Agent", "~/.pi/agent/sessions"],
                ].map(([a, p]) => (
                  <tr key={a}>
                    <td className="paths__agent">{a}</td>
                    <td className="paths__path mono">{p}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>

          <div className="panel card">
            <div className="agentcall__hd">
              <SectionHead kicker="Hands-free" title="Let the agent call it" />
              <button className="btn btn--sm btn--ghost" onClick={() => {
                try { navigator.clipboard && navigator.clipboard.writeText(AGENT_PROMPT); } catch (e) {}
                setCopied(true); setTimeout(() => setCopied(false), 1400);
              }}>
                {copied ? "copied ✓" : "copy prompt"}
              </button>
            </div>
            <p className="agentcall__blurb">Using Codex or Claude Code? Point it at this Space's <span className="mono">agents.md</span>. It finds your latest log, redacts it, uploads, and returns the report.</p>
            <pre className="agentcall__pre mono">{AGENT_PROMPT}</pre>
          </div>

          <div className="getrow">
            {[
              ["Elevation trail", "every snag as a waypoint"],
              ["Detour read", "exploration vs wandering"],
              ["Closeout audit", "honest, or overclaimed?"],
            ].map(([t, s]) => (
              <div className="getrow__item" key={t}>
                <span className="getrow__t">{t}</span>
                <span className="getrow__s muted">{s}</span>
              </div>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
}

const PIPELINE = [
  "Uploading the trace",
  "Extracting narrative messages",
  "Redacting likely secrets",
  "Charting difficulty episodes",
  "Classifying with the codebook",
  "Synthesizing field notes",
];

function Analyzing({ label }) {
  const [step, setStep] = React.useState(0);
  React.useEffect(() => {
    const id = setInterval(() => setStep((s) => (s + 1) % (PIPELINE.length + 1)), 700);
    return () => clearInterval(id);
  }, []);
  return (
    <div className="analyzing">
      <div className="analyzing__card card card--raised">
        <svg viewBox="0 0 320 120" className="analyzing__svg" aria-hidden="true">
          <line x1="20" y1="100" x2="300" y2="100" stroke="var(--rule)" strokeDasharray="2 6" />
          <path className="analyzing__trail"
            d="M20 96 C 70 60, 100 104, 150 70 S 230 30, 300 44"
            fill="none" stroke="var(--accent)" strokeWidth="2.6" strokeLinecap="round" />
          <circle className="analyzing__dot" r="4.5" fill="var(--accent)" />
        </svg>
        <Kicker>Surveying the trace · {label}</Kicker>
        <ul className="analyzing__steps">
          {PIPELINE.map((s, i) => (
            <li key={s} className={i < step ? "done" : i === step ? "active" : ""}>
              <span className="analyzing__tick mono">{i < step ? "✓" : i === step ? "…" : "·"}</span>{s}
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
}

function EmptyReport({ data, onReset }) {
  return (
    <div className="report">
      <ReportHeader data={data} />
      <section className="sec">
        <div className="card" style={{ padding: "28px 30px" }}>
          <SectionHead kicker="No episode surfaced" title="No explicit difficulty episode was strong enough to classify" />
          <p className="sec-head__sub" style={{ maxWidth: "70ch" }}>
            The trace yielded {data.narrative_message_count} visible narrative messages, but none carried clear
            self-reported blockage, detour, or recovery language. That does not prove the session was trouble-free —
            only that the narrative did not say so. Try the redacted-narrative export to read it yourself.
          </p>
          <div style={{ marginTop: 18 }}>
            <button className="btn btn--sm btn--ghost" onClick={onReset}>← Analyze another trace</button>
          </div>
        </div>
      </section>
    </div>
  );
}

function App() {
  const [stage, setStage] = React.useState("landing"); // landing | analyzing | report
  const [data, setData] = React.useState(null);
  const [engineLabel, setEngineLabel] = React.useState("");
  const [error, setError] = React.useState("");

  async function analyze({ file, include_user_context, redact_secrets, analysis_engine, engineLabel }) {
    setError("");
    setEngineLabel(engineLabel || analysis_engine);
    setStage("analyzing");
    window.scrollTo({ top: 0 });
    try {
      const g = window.__gradio;
      if (!g) throw new Error("Client is still loading — reload the page and try again.");
      const client = await g.clientPromise;
      const res = await client.predict("/analyze_trace", {
        trace_file: g.handle_file(file),
        include_user_context: !!include_user_context,
        redact_secrets: !!redact_secrets,
        analysis_engine,
      });
      const out = Array.isArray(res.data) ? res.data[0] : res.data;
      if (!out || typeof out !== "object") throw new Error("The analyzer returned an empty response.");
      setData(out);
      setStage("report");
    } catch (e) {
      setError(String((e && e.message) || e));
      setStage("landing");
    }
    window.scrollTo({ top: 0 });
  }

  function loadSample(key) {
    const base = key === "short" ? window.TFN.SHORT : window.TFN.LONG;
    setError("");
    setEngineLabel(base.engine);
    setData(base);
    setStage("report");
    window.scrollTo({ top: 0 });
  }

  function reset() { setStage("landing"); setData(null); window.scrollTo({ top: 0 }); }

  const reportData = data ? Object.assign({}, data, { engine: engineLabel || data.engine }) : null;
  const hasEpisodes = reportData && reportData.episodes && reportData.episodes.length;

  return (
    <div className="app-root" data-theme="dark" data-density="regular" data-voice="journal">
      <div className="backdrop"><div className="grain" /><TopoBackground /></div>
      <div className="page">
        {stage === "landing" && <LandingView onAnalyze={analyze} onSample={loadSample} error={error} />}
        {stage === "analyzing" && <Analyzing label={engineLabel} />}
        {stage === "report" && (
          <div className="report-wrap">
            <button className="report-back btn btn--sm btn--ghost" onClick={reset}>← New trace</button>
            {hasEpisodes
              ? <ReportView data={reportData} variant="trail" onReset={reset} />
              : <EmptyReport data={reportData} onReset={reset} />}
            <footer className="foot">
              <span className="mono">Trace Field Notes</span>
              <span className="muted">Qualitative narrative analysis · we report what the agent said, not whether its code is correct.</span>
            </footer>
          </div>
        )}
      </div>
    </div>
  );
}

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