// EvidencePanel — the repo station's evidence AT THE APPROVAL GATE.
//
// A repo-delivery station records what it did as `evidence-*` task artifacts
// (assembly-line-runtime/conductor/repo-evidence.ts): meta.json (the machine
// facts), diff-stat.txt, diff.patch, test-output.txt and — when a regression
// isolation run happened — prefix-test-output.txt. Before this panel existed
// the artifacts were rendered NOWHERE, so approving a repo station meant
// approving a change whose diff and test output the operator never saw.
//
// The snapshot carries artifact NAMES only, so each file's text is fetched by
// name from the viewer's read-only artifact endpoint (window.Api.getTaskArtifact
// → GET /projects/:slug/tasks/:id/artifacts/:name), which resolves the name
// against that task's own recorded artifact list — never an arbitrary path.
//
// HONEST DISABLED STATE: a missing or unreadable artifact renders an explicit
// "Evidence unavailable — <reason>". Never a blank box and never a silent empty
// <pre>: the operator must be able to tell "the tests printed nothing" apart
// from "we could not read the test output".
//
// WHAT to show for each slot is decided by the pure window.Evidence.evidenceView
// (lib/evidence.js, tests/evidence-gate.test.mjs); this file only fetches and
// paints it.

const EvidenceUnavailable = ({ reason }) => (
  <div className="text-xs text-amber-800 bg-amber-50 border border-amber-200 rounded-lg px-3 py-2 flex items-start gap-2">
    <AlertCircle size={14} className="shrink-0 mt-0.5" />
    <span>
      <span className="font-bold">Evidence unavailable</span>
      {reason ? <span className="text-amber-700"> — {reason}</span> : null}
    </span>
  </div>
);

const EvidenceChip = ({ label, value, tone = "neutral", title }) => {
  const cls =
    tone === "green" ? "bg-emerald-50 text-emerald-800 border-emerald-300" :
    tone === "red" ? "bg-red-50 text-red-700 border-red-300" :
    tone === "amber" ? "bg-amber-50 text-amber-800 border-amber-300" :
    "bg-white text-neutral-700 border-neutral-300";
  return (
    <span title={title} className={`inline-flex items-baseline gap-1.5 text-xs font-semibold border rounded-lg px-2.5 py-1 ${cls}`}>
      <span className="text-[10px] font-bold uppercase tracking-widest opacity-70">{label}</span>
      <span className="tabular-nums">{value}</span>
    </span>
  );
};

// One collapsible monospace file view. Scrolls inside its own box so a
// 4000-line patch cannot push the Approve button off the screen.
const EvidenceFileView = ({ panel }) => {
  const [open, setOpen] = React.useState(!!panel.defaultOpen);
  const { state } = panel;
  return (
    <div className="border border-neutral-200 rounded-xl bg-white overflow-hidden">
      <button
        onClick={() => setOpen((o) => !o)}
        className="w-full flex items-center gap-2 px-3 py-2 text-left hover:bg-neutral-50 transition-colors"
      >
        {open ? <ChevronDown size={14} /> : <ChevronRight size={14} />}
        <span className="text-[11px] font-bold uppercase tracking-[0.18em] text-neutral-700">{panel.title}</span>
        {state === "loading" && <span className="text-[11px] text-neutral-400 font-medium">loading…</span>}
        {(state === "absent" || state === "error") && (
          <span className="text-[11px] text-amber-700 font-semibold">unavailable</span>
        )}
        {state === "ok" && panel.truncated && (
          <span className="text-[11px] text-amber-700 font-semibold">truncated</span>
        )}
      </button>
      {open && (
        <div className="px-3 pb-3">
          {state === "loading" && <div className="text-xs text-neutral-400 py-2">Loading…</div>}
          {(state === "absent" || state === "error") && <EvidenceUnavailable reason={panel.reason} />}
          {state === "ok" && (
            <>
              {panel.truncated && (
                <div className="text-[11px] text-amber-700 mb-1">
                  Showing the first {(panel.text || "").length.toLocaleString()} of {(panel.size || 0).toLocaleString()} bytes — open the file on disk for the rest.
                </div>
              )}
              {(panel.text || "").trim() === "" ? (
                <div className="text-xs text-neutral-500 italic py-2">(recorded, but empty — the command produced no output)</div>
              ) : (
                <pre className="text-[11px] leading-relaxed font-mono text-neutral-800 bg-neutral-50 border border-neutral-200 rounded-lg p-3 max-h-96 overflow-auto whitespace-pre">
                  {panel.text}
                </pre>
              )}
            </>
          )}
        </div>
      )}
    </div>
  );
};

const EvidencePanel = ({ task, projectSlug = null }) => {
  const E = window.Evidence;
  // name -> { status: 'loading'|'ok'|'error'|'absent', text, size, truncated, error }
  const [files, setFiles] = React.useState({});
  const recordedKey = E ? E.evidenceArtifacts(task).map((a) => a.name).sort().join("|") : "";

  React.useEffect(() => {
    if (!E || !E.hasEvidence(task)) return undefined;
    let live = true;
    const wanted = E.evidenceFetchNames(task);
    const present = new Set(E.evidenceArtifacts(task).map((a) => a.name));
    setFiles(Object.fromEntries(wanted.map((n) => [n, { status: present.has(n) ? "loading" : "absent" }])));
    for (const name of wanted) {
      if (!present.has(name)) continue;
      window.Api.getTaskArtifact(projectSlug, task.id, name)
        .then((res) => {
          if (!live) return;
          setFiles((prev) => ({ ...prev, [name]: { status: "ok", text: res.text ?? "", size: res.size ?? 0, truncated: !!res.truncated } }));
        })
        .catch((err) => {
          if (!live) return;
          setFiles((prev) => ({ ...prev, [name]: { status: "error", error: String(err?.message ?? err) } }));
        });
    }
    return () => { live = false; };
  }, [E, projectSlug, task?.id, recordedKey]);

  const view = E ? E.evidenceView(task, files) : null;
  if (!view) return null; // no evidence-* artifacts → not a repo station
  const sum = view.summary;
  const secs = (ms) => (ms == null ? "—" : ms >= 1000 ? `${(ms / 1000).toFixed(1)}s` : `${ms}ms`);

  return (
    <div className="bg-white border-2 border-neutral-200 rounded-2xl p-4 space-y-3">
      <div>
        <div className="text-[11px] font-bold uppercase tracking-[0.22em] text-neutral-700 flex items-center gap-1.5">
          <GitCommit size={13} /> Evidence
        </div>
        <p className="text-xs text-neutral-500 mt-1 font-medium">
          Recorded by the station runtime — the actual diff and the actual test run. Read it before you approve.
        </p>
      </div>

      {/* Machine facts. Absent/unparseable meta → an explicit note, never an empty row. */}
      {view.summaryLoading && <div className="text-xs text-neutral-400">Loading evidence…</div>}
      {view.summaryUnavailable && <EvidenceUnavailable reason={view.summaryUnavailable} />}
      {view.incompleteSuiteIds.length > 0 && (
        <div className="text-sm font-semibold text-red-800 bg-red-50 border-2 border-red-300 rounded-lg px-3 py-2 flex items-start gap-2" role="alert">
          <AlertCircle size={16} className="shrink-0 mt-0.5" />
          <span>Exit code missing: {view.incompleteSuiteIds.join(", ")}</span>
        </div>
      )}
      {sum && (
        <>
          <div className="flex flex-wrap gap-2">
            <EvidenceChip
              label="Tests"
              value={sum.timedOut ? "timed out" : sum.testsPassed == null ? "not recorded" : sum.testsPassed ? "pass (exit 0)" : `FAIL (exit ${sum.exitCode})`}
              tone={sum.timedOut ? "red" : sum.testsPassed == null ? "amber" : sum.testsPassed ? "green" : "red"}
              title={sum.command || undefined}
            />
            <EvidenceChip label="Diff" value={`${sum.diffLines == null ? "?" : sum.diffLines.toLocaleString()} lines`} />
            <EvidenceChip label="Files" value={sum.changedFiles.length} />
            {sum.durationMs != null && <EvidenceChip label="Took" value={secs(sum.durationMs)} />}
            {sum.installExitCode != null && (
              <EvidenceChip label="Install" value={`exit ${sum.installExitCode}`} tone={sum.installExitCode === 0 ? "neutral" : "red"} />
            )}
            {sum.prefixExit != null && (
              <EvidenceChip
                label="Pre-fix run"
                value={`exit ${sum.prefixExit}`}
                tone={sum.prefixDemonstratesBug ? "green" : "red"}
                title={sum.prefixDemonstratesBug
                  ? "Non-zero at the base commit — the new test does demonstrate the bug"
                  : "Passed at the base commit — the new test does NOT demonstrate the bug"}
              />
            )}
          </div>
          {sum.command && (
            <div className="text-[11px] text-neutral-500">
              <span className="font-bold uppercase tracking-widest text-neutral-400">Command</span>{" "}
              <span className="font-mono text-neutral-700 break-all">{sum.command}</span>
            </div>
          )}
          {/* One row per suite when the project gated this visit on more than one runner.
              Without it the chips above describe the PRIMARY suite only, and the operator
              cannot tell whether the second suite was actually run — which is the whole
              reason RepoConfig.suites exists. */}
          {sum.suites.length > 1 && (
            <div className="text-[11px] text-neutral-500 space-y-0.5">
              <span className="font-bold uppercase tracking-widest text-neutral-400">Suites</span>
              <ul className="space-y-0.5">
                {sum.suites.map((s) => (
                  <li key={s.id} className="break-all">
                    <span className="font-mono font-bold text-neutral-700">{s.id}</span>
                    <span className="text-neutral-500">
                      {" "}<span className="font-mono">{s.command ?? "(command not recorded)"}</span>
                      {s.cwd ? <> in <span className="font-mono">{s.cwd}</span></> : null}
                      {" — "}
                      <span className={s.timedOut || s.testsPassed === false ? "text-red-600 font-bold" : s.testsPassed ? "text-emerald-700 font-bold" : "text-amber-600 font-bold"}>
                        {s.timedOut ? "timed out" : s.testsPassed == null ? "exit not recorded" : s.testsPassed ? "pass (exit 0)" : `FAIL (exit ${s.exitCode})`}
                      </span>
                      {s.prefixExit != null && <> · pre-fix run exit {s.prefixExit}</>}
                    </span>
                  </li>
                ))}
              </ul>
            </div>
          )}
          {sum.changedFiles.length > 0 && (
            <div className="max-h-32 overflow-auto border border-neutral-200 rounded-lg bg-neutral-50 px-3 py-2">
              <ul className="text-[11px] font-mono text-neutral-700 space-y-0.5">
                {sum.changedFiles.map((f) => <li key={f} className="break-all">{f}</li>)}
              </ul>
            </div>
          )}
          {!sum.changed && (
            <div className="text-xs text-neutral-600 bg-neutral-50 border border-neutral-200 rounded-lg px-3 py-2">
              No files changed at this station — there is nothing in the diff to review.
            </div>
          )}
        </>
      )}

      {/* The files themselves — diffstat, diff, test output, pre-fix run. */}
      <div className="space-y-2">
        {view.panels.map((p) => <EvidenceFileView key={p.name} panel={p} />)}
      </div>
    </div>
  );
};

window.EvidencePanel = EvidencePanel;
