// deep-report.jsx — the "Highlights" view of a deep-scout report.
// The markdown is the artifact you forward; this is the 10-second read:
// verdict first, then incidents ranked by evidence strength — the strongest
// one opens by itself, so the receipt is the first thing below the verdict.
// Renders the structured view built server-side by lib/report-view.mjs.
const { useState, useRef, useEffect } = React;

// ---- tiny inline-markdown (bold + code) used by story sentences ----------
function mdInline(text) {
  const parts = [];
  String(text || "").split(/(\*\*[^*]+\*\*|`[^`]+`)/g).forEach((chunk, index) => {
    if (/^\*\*/.test(chunk)) parts.push(<strong key={index}>{chunk.slice(2, -2)}</strong>);
    else if (/^`/.test(chunk)) parts.push(<code key={index} className="story-code">{chunk.slice(1, -1)}</code>);
    else if (chunk) parts.push(chunk);
  });
  return parts;
}

// ---- dates ----------------------------------------------------------------
function fmtDate(iso) {
  if (!iso) return null;
  const date = new Date(iso);
  return Number.isNaN(date.getTime()) ? null : date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
}

// "same day" / "1 day later" / "12 days later" — null when either date is
// missing (older reports have no culprit date; never guess).
function fixDelay(incident) {
  const days = incident.daysToFix;
  if (days == null) return null;
  if (days === 0) return "the same day";
  return days === 1 ? "1 day later" : `${days} days later`;
}

// ---- verdict decision → pill palette ---------------------------------------
// New reports carry the decision a merge queue would act on: BLOCKED is red
// (the merge would have been refused), ESCALATED amber, PASSED neutral.
// Legacy reports have no decision and fall back to the status palette.
const DECISION_PILL = { block: "high", escalate: "med", pass: "neutral" };

// Copyable one-liner for a blocked incident's failing probe — the receipt.
function ReproChip({ command }) {
  const [copied, setCopied] = useState(false);
  return (
    <button
      onClick={(event) => {
        event.stopPropagation();
        navigator.clipboard?.writeText(command);
        // Copying the failing-probe command is the strongest engagement signal
        // the report has — worth its own analytics event.
        try { window.posthog?.capture?.("repro_command_copied", { command }); } catch {}
        setCopied(true);
        setTimeout(() => setCopied(false), 1300);
      }}
      title={`${command} — copies the command; the probe exits non-zero while the regression is present`}
      style={{ display: "inline-flex", alignItems: "center", gap: 5, flexShrink: 0,
        background: "var(--surface-2)", border: "1px solid var(--border)", borderRadius: 6,
        padding: "2px 8px", fontFamily: "var(--font-mono)", fontSize: 10.5, fontWeight: 600,
        color: copied ? "var(--pass-text)" : "var(--text-2)", cursor: "pointer" }}
    >
      {copied ? Icon.check({ s: 11 }) : Icon.copy({ s: 11 })}
      {copied ? "copied" : "repro"}
    </button>
  );
}

// ---- cold-email export -----------------------------------------------------
// Plain text — no markdown, no tables — so it pastes cleanly into Gmail or
// Outlook. The three strongest catches: view.incidents is already ranked by
// evidence strength server-side (report-view.mjs), so filter to real hits and
// take the head of the list.
function plainStory(text) {
  return String(text || "").replace(/\*\*([^*]*)\*\*/g, "$1").replace(/`([^`]*)`/g, "$1");
}

// One line of hard evidence per incident: an executed before/after flip when
// we have one (the strongest claim), else the sharpest line from the diff.
function emailEvidenceLine(finding) {
  if (!finding) return null;
  const clip = (text) => { const s = String(text); return s.length > 90 ? `${s.slice(0, 87)}...` : s; };
  const flip = (finding.boundary?.cases || []).find((row) => row.changed);
  if (flip && finding.boundary.executed) {
    return `Ran both versions on the same input (${clip(flip.input)}): before ${clip(flip.before)} -> after ${clip(flip.after)}`;
  }
  const removed = finding.deps?.removed?.[0];
  const added = finding.deps?.added?.[0];
  if (removed && added) return `The change: - ${clip(removed)}  ->  + ${clip(added)}`;
  if (removed) return `What disappeared: ${clip(removed)}`;
  if (added) return `What appeared: ${clip(added)}`;
  if (flip) return `Before: ${clip(flip.before)} -> after: ${clip(flip.after)}`;
  return null;
}

function buildEmailSummary(view) {
  const summary = view.summary || {};
  const control = view.control || summary.control || null;
  const catches = (view.incidents || []).filter((incident) => incident.status === "hit").slice(0, 3);
  const commitUrl = (sha) => `https://github.com/${view.repo}/commit/${sha}`;
  const lines = [];
  // Lead with THEIR problems — a cold-email reader gives the first line one
  // skim; Kaval's own scorecard only earns attention after the evidence has.
  const count = catches.length;
  lines.push(`${count === 1 ? "A recent commit" : `${count} recent commits`} in ${view.repo} ${count === 1 ? "shipped a regression" : "each shipped a regression"} your team had to go back and fix. Kaval flagged ${count === 1 ? "it" : `all ${count}`} pre-merge — replaying your history blind to the future and executing the changed code, both versions. No model opinions, only observed behavior:`);
  catches.forEach((incident, index) => {
    lines.push("");
    const subject = incident.culprit?.subject || incident.fix?.msg || "(no subject)";
    lines.push(`${index + 1}. "${subject}"`);
    const finding = (incident.findings || [])[0];
    if (finding) {
      // The story sentence when it carries real content; keyword-lane stories
      // ("Head changes ... in <file>") just restate the title — there, the
      // title plus the file path says more in fewer words.
      const story = plainStory(finding.story || "");
      const boilerplate = !story || /^Head (changes|adds|removes)\b/.test(story);
      lines.push(boilerplate
        ? `   ${plainStory(finding.title)}${finding.file ? ` (${finding.file})` : ""}.`
        : `   ${plainStory(finding.title)} — ${story}`);
      const evidence = emailEvidenceLine(finding);
      if (evidence) lines.push(`   ${evidence}`);
    }
    if (incident.decision === "block") {
      lines.push("   Kaval would have blocked this merge with a runnable failing probe.");
    }
    if (incident.fix) {
      const delay = fixDelay(incident);
      lines.push(`   Your team's fix${delay ? ` landed ${delay}` : ""}: "${incident.fix.msg}" (${incident.fix.sha}).`);
    }
    if (incident.culprit?.sha) lines.push(`   ${commitUrl(incident.culprit.sha)}`);
  });
  lines.push("");
  // The closer is the no-noise claim — never the hit count. "Caught 9 of 13"
  // reads as "missed 4" to a cold reader; zero false alarms is the line that
  // separates Kaval from every linter they ignore.
  const noise = control?.commits
    ? `It also ran on ${control.commits} clean commits from the same history and raised ${control.blocks === 0 ? "zero false alarms" : `${control.blocks} false alarm${control.blocks === 1 ? "" : "s"}`}. `
    : "";
  lines.push(`${noise}Every claim is reproducible from the SHAs — happy to send the full report, or point Kaval at any repo you pick.`);
  return lines.join("\n");
}

function EmailCopyButton({ view }) {
  const [copied, setCopied] = useState(false);
  // Internal sales tool — only the admin (KAVAL_ADMIN_EMAILS, verified server
  // side and mirrored onto window.KAVAL_ADMIN) ever sees it. Public viewers
  // and signed-out visitors never get a cold-email button.
  if (!window.KAVAL_ADMIN) return null;
  if (!(view.incidents || []).some((incident) => incident.status === "hit")) return null;
  return (
    <button
      onClick={() => {
        navigator.clipboard?.writeText(buildEmailSummary(view));
        try { window.posthog?.capture?.("email_summary_copied", { repo: view.repo }); } catch {}
        setCopied(true);
        setTimeout(() => setCopied(false), 1300);
      }}
      title="Copies the top 3 problems as plain text — paste straight into an email"
      style={{ display: "inline-flex", alignItems: "center", gap: 6, flexShrink: 0,
        background: "var(--surface-2)", border: "1px solid var(--border)", borderRadius: 7,
        padding: "5px 11px", fontSize: 11.5, fontWeight: 600,
        color: copied ? "var(--pass-text)" : "var(--text-2)", cursor: "pointer" }}
    >
      {copied ? Icon.check({ s: 12 }) : Icon.copy({ s: 12 })}
      {copied ? "copied" : "Copy for email"}
    </button>
  );
}

// ---- incident status → palette -------------------------------------------
function incidentTone(incident) {
  if (incident.status === "hit") {
    if (incident.strong && !incident.bulk) {
      return { seg: { background: "var(--pass)", color: "#fff", border: "1px solid var(--pass)" }, pill: "pass" };
    }
    return { seg: { background: "var(--pass-subtle)", color: "var(--pass-text)", border: "1px solid var(--pass-border)" }, pill: "pass" };
  }
  if (incident.status === "miss") {
    return { seg: { background: "var(--high-subtle)", color: "var(--high-text)", border: "1px solid var(--high-border)" }, pill: "high" };
  }
  return { seg: { background: "var(--surface-3)", color: "var(--text-3)", border: "1px solid var(--border)" }, pill: "neutral" };
}

// ---- hero: the verdict, big, plus one clickable segment per incident -----
function SegmentBar({ incidents, onJump }) {
  return (
    <div style={{ display: "flex", gap: 4, width: "100%" }}>
      {incidents.map((incident) => {
        const tone = incidentTone(incident);
        return (
          <button
            key={incident.n}
            onClick={() => onJump(incident.n)}
            title={`#${incident.n} ${incident.fix.msg} — ${incident.verdict}`}
            style={{ ...tone.seg, flex: 1, minWidth: 0, height: 30, borderRadius: 7, cursor: "pointer",
              fontFamily: "var(--font-mono)", fontSize: 11, fontWeight: 600, padding: 0,
              transition: "transform .12s ease, box-shadow .12s ease" }}
            onMouseEnter={(event) => { event.currentTarget.style.transform = "translateY(-2px)"; event.currentTarget.style.boxShadow = "var(--shadow-sm)"; }}
            onMouseLeave={(event) => { event.currentTarget.style.transform = "none"; event.currentTarget.style.boxShadow = "none"; }}
          >
            {incident.n}
          </button>
        );
      })}
    </div>
  );
}

function LegendDot({ swatch, label }) {
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 5, fontSize: 11.5, color: "var(--text-3)" }}>
      <span style={{ width: 9, height: 9, borderRadius: 3, ...swatch }} />
      {label}
    </span>
  );
}

function VerdictHero({ view, onJump }) {
  const summary = view.summary || {};
  // New reports headline the verdict a merge queue would have acted on;
  // legacy reports (no blockedCulprits) keep the flagged/analyzed framing.
  const hasVerdicts = typeof summary.blockedCulprits === "number";
  const control = view.control || summary.control || null;
  const setAside = (summary.notRegression || 0) + (summary.outOfScope || 0);
  // The hero number is the CATCH rate (hits/analyzed) — never the block
  // count. "1/13 BLOCKED" reads like Kaval found 1 of 13; the gate's block and
  // escalate counts are what it would have done about the catches.
  const subParts = [];
  if (hasVerdicts) {
    if (summary.blockedCulprits) subParts.push(`${summary.blockedCulprits} auto-blocked with a failing probe`);
    if (summary.escalatedCulprits) subParts.push(`${summary.escalatedCulprits} escalated to a human`);
    if (summary.misses) subParts.push(`${summary.misses} missed — shown below`);
  } else {
    if (summary.strongHits) subParts.push(`${summary.strongHits} with strong evidence`);
    if (summary.misses) subParts.push(`${summary.misses} missed — shown below`);
  }
  if (setAside) subParts.push(`${setAside} set aside (additive, nothing regressed)`);

  return (
    <div style={{ display: "flex", gap: 28, alignItems: "center", flexWrap: "wrap", padding: "22px 24px 18px" }}>
      <div style={{ minWidth: 230 }}>
        <div style={{ display: "flex", alignItems: "baseline", gap: 10 }}>
          <span style={{ fontSize: 46, fontWeight: 650, letterSpacing: "-0.035em", lineHeight: 1, color: "var(--text)" }}>
            {summary.hits ?? 0}<span style={{ color: "var(--text-4)", fontWeight: 500 }}>/{summary.analyzed ?? 0}</span>
          </span>
        </div>
        <div style={{ fontSize: 13.5, fontWeight: 560, color: "var(--text)", marginTop: 6 }}>
          {hasVerdicts ? "culprits caught pre-merge" : "culprits flagged before merge"}
        </div>
        <div style={{ fontSize: 12, color: "var(--text-3)", marginTop: 3 }}>{subParts.join(" · ")}</div>
        {hasVerdicts && control && (
          <div style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: 12, fontWeight: 600, marginTop: 8,
            color: control.blocks ? "var(--high-text)" : "var(--pass-text)" }}>
            {control.blocks ? Icon.warn({ s: 13 }) : Icon.shieldCheck({ s: 13 })}
            {control.blocks} false block{control.blocks === 1 ? "" : "s"} on {control.commits} clean commit{control.commits === 1 ? "" : "s"}
          </div>
        )}
      </div>
      <div style={{ flex: 1, minWidth: 320 }}>
        <div style={{ fontSize: 12, color: "var(--text-3)", marginBottom: 8, lineHeight: 1.55 }}>
          {summary.incidents ?? view.incidents.length} changes in this repo's history needed a follow-up fix. Kaval replayed each one
          pre-merge, <strong style={{ color: "var(--text-2)" }}>blind to the future</strong> — {hasVerdicts
            ? "it ran the changed code, both versions, and blocked merges only on reproduced unlicensed behavior."
            : "green means it pointed at the code the fix later had to touch."}
        </div>
        <SegmentBar incidents={view.incidents} onJump={onJump} />
        <div style={{ display: "flex", gap: 14, marginTop: 8, flexWrap: "wrap", alignItems: "center" }}>
          <LegendDot swatch={{ background: "var(--pass)" }} label="flagged, strong evidence" />
          <LegendDot swatch={{ background: "var(--pass-subtle)", border: "1px solid var(--pass-border)" }} label="flagged" />
          <LegendDot swatch={{ background: "var(--high-subtle)", border: "1px solid var(--high-border)" }} label="missed" />
          <LegendDot swatch={{ background: "var(--surface-3)", border: "1px solid var(--border)" }} label="set aside" />
          {/* Cold-outreach export: top 3 problems as paste-ready plain text.
              Lives in normal flow (end of the legend row) so it can never
              cover the hero copy the way an absolute corner pin did. */}
          <div style={{ marginLeft: "auto" }}>
            <EmailCopyButton view={view} />
          </div>
        </div>
      </div>
    </div>
  );
}

// ---- evidence pieces ------------------------------------------------------
function ProbeTable({ boundary }) {
  if (!boundary?.cases?.length) return null;
  return (
    <table className="probe-table">
      <thead>
        <tr>
          <th>{boundary.executed ? "test input" : `input (${boundary.field || "field"})`}</th>
          <th>before this change</th>
          <th>after this change</th>
        </tr>
      </thead>
      <tbody>
        {boundary.cases.map((row, index) => (
          <tr key={index} className={row.changed ? "probe-flip" : ""}>
            <td>{row.input}</td>
            <td>{row.before}</td>
            <td>
              {row.after}
              {row.changed && <span className="probe-flag">changed</span>}
            </td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

// Static evidence rendered in the same before/after shape as executed probes:
// removed lines = how it behaved before, added lines = after. Honest about
// the source — this comes from the merged diff, not an executed probe.
function BeforeAfterDiff({ deps }) {
  if (!deps) return null;
  const removed = deps.removed || [];
  const added = deps.added || [];
  if (!removed.length && !added.length) return null;
  const lineStyle = (color) => ({ fontFamily: "var(--font-mono)", fontSize: 11.5, lineHeight: 1.7, color, overflowWrap: "anywhere" });
  return (
    <div>
      <table className="probe-table">
        <thead>
          <tr>
            <th style={{ width: "50%" }}>before this change</th>
            <th>after this change</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>
              {removed.length
                ? removed.map((line, index) => <div key={index} style={lineStyle("var(--high-text)")}><span style={{ opacity: 0.65, marginRight: 7 }}>−</span>{line}</div>)
                : <span style={{ color: "var(--text-4)" }}>(not present)</span>}
            </td>
            <td>
              {added.length
                ? added.map((line, index) => <div key={index} style={lineStyle("var(--pass-text)")}><span style={{ opacity: 0.65, marginRight: 7 }}>+</span>{line}</div>)
                : <span style={{ color: "var(--text-4)" }}>(removed, nothing replaced it)</span>}
            </td>
          </tr>
        </tbody>
      </table>
      <div style={{ fontSize: 10.5, color: "var(--text-4)", marginTop: 4 }}>from the merged diff</div>
    </div>
  );
}

function DiffToggle({ diff }) {
  const [open, setOpen] = useState(false);
  if (!diff?.length) return null;
  return (
    <div>
      <button onClick={() => setOpen(!open)}
        style={{ display: "inline-flex", alignItems: "center", gap: 5, background: "none", border: "none",
          padding: 0, fontSize: 11.5, fontWeight: 560, color: "var(--text-3)", cursor: "pointer" }}>
        <span style={{ display: "inline-block", transition: "transform .14s ease", transform: open ? "rotate(90deg)" : "none" }}>{Icon.chevron({ s: 11 })}</span>
        raw diff ({diff.length} lines)
      </button>
      {open && (
        <pre style={{ margin: "6px 0 0", background: "var(--surface-2)", border: "1px solid var(--border)", borderRadius: 8,
          padding: "8px 10px", fontSize: 11.5, lineHeight: 1.65, overflowX: "auto" }}>
          {diff.map((entry, index) => (
            <div key={index} style={{ color: entry.t === "add" ? "var(--pass-text)" : entry.t === "del" ? "var(--high-text)" : "var(--text-3)" }}>
              {{ add: "+ ", del: "- ", ctx: "  " }[entry.t] || "  "}{entry.s}
            </div>
          ))}
        </pre>
      )}
    </div>
  );
}

// One finding, rendered as evidence: meta row, plain-English story, proof.
function EvidenceCard({ finding, lead = false }) {
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 10,
      background: lead ? "var(--surface)" : "var(--surface)", border: "1px solid var(--border)",
      borderRadius: 10, padding: lead ? "16px 18px" : "13px 15px" }}>
      <div style={{ display: "flex", alignItems: "center", gap: 9, flexWrap: "wrap" }}>
        <SeverityTag sev={finding.severity} size="sm" />
        <span style={{ fontSize: 11.5, fontWeight: 560, color: "var(--text-2)" }}>{finding.typeLabel}</span>
        {finding.executed && <Pill tone="pass" dot>observed, not inferred</Pill>}
        <div style={{ flex: 1 }} />
        {finding.file && <span style={{ fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--text-3)", maxWidth: 320, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{finding.file}</span>}
      </div>
      <div style={{ fontSize: lead ? 14.5 : 13.5, fontWeight: 600, letterSpacing: "-0.01em" }}>{finding.title}</div>
      {finding.story && (
        <div style={{ fontSize: lead ? 13.5 : 12.5, lineHeight: 1.65, color: "var(--text-2)" }}>{mdInline(finding.story)}</div>
      )}
      {!finding.story && finding.summary && (
        <div style={{ fontSize: 12.5, lineHeight: 1.6, color: "var(--text-2)" }}>{finding.summary}</div>
      )}
      <ProbeTable boundary={finding.boundary} />
      <BeforeAfterDiff deps={finding.deps} />
      <DiffToggle diff={finding.diff} />
      {finding.fixTouched && (
        <div style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 12, fontWeight: 580, color: "var(--pass-text)" }}>
          {Icon.check({ s: 13 })}
          {finding.fixTouched === "tests"
            ? "The eventual fix touched the tests covering this exact file."
            : "The eventual fix changed this exact file — Kaval pointed here first."}
        </div>
      )}
    </div>
  );
}

// ---- install banner ---------------------------------------------------------
// The conversion path: every report — gallery example, front-door run, PR
// page — carries one quiet pitch for the GitHub App. Renders nothing until
// the app exists (app.jsx sets window.KAVAL_GITHUB_APP from /api/config).
// `?ref=hn|ph|twitter` rides along as the install's `state` so launch-day
// installs keep their source for attribution server-side.
function InstallBanner({ context = {} }) {
  const app = window.KAVAL_GITHUB_APP || {};
  if (!app.installUrl) return null;
  let ref = "";
  try {
    ref = new URLSearchParams(window.location.search).get("ref") || sessionStorage.getItem("kaval_ref") || "";
  } catch {}
  const href = ref ? `${app.installUrl}?state=${encodeURIComponent(ref)}` : app.installUrl;
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 14, flexWrap: "wrap", margin: "0 24px 16px",
      padding: "14px 18px", background: "var(--surface-2)", border: "1px solid var(--border)", borderRadius: 10 }}>
      <div style={{ flex: 1, minWidth: 260 }}>
        <div style={{ fontSize: 13.5, fontWeight: 620, letterSpacing: "-0.01em" }}>Want this verdict on every PR before it merges?</div>
        <div style={{ fontSize: 12, color: "var(--text-3)", marginTop: 3 }}>Install the Kaval GitHub App — advisory checks on every PR, free during early access.</div>
      </div>
      <a
        href={href}
        target="_blank"
        rel="noreferrer"
        onClick={() => { try { window.posthog?.capture?.("banner_install_clicked", { ...context, ref: ref || null }); } catch {} }}
        style={{ display: "inline-flex", alignItems: "center", gap: 8, flexShrink: 0, height: 36,
          padding: "0 16px", background: "var(--accent)", color: "var(--accent-fg)", borderRadius: "var(--radius-sm)",
          fontSize: 13.5, fontWeight: 560, letterSpacing: "-0.01em", textDecoration: "none", boxShadow: "var(--shadow-sm)" }}
      >
        Install on GitHub
      </a>
    </div>
  );
}

// ---- the incident timeline ------------------------------------------------
// One numbered step of the expanded incident story.
function StoryStep({ label, last = false, children }) {
  return (
    <div style={{ display: "flex", gap: 12 }}>
      <div style={{ display: "flex", flexDirection: "column", alignItems: "center", flexShrink: 0, width: 14 }}>
        <span style={{ width: 8, height: 8, borderRadius: "50%", background: "var(--text-4)", marginTop: 5 }} />
        {!last && <span style={{ flex: 1, width: 2, background: "var(--border)", marginTop: 4, borderRadius: 1 }} />}
      </div>
      <div style={{ flex: 1, minWidth: 0, paddingBottom: last ? 0 : 14 }}>
        <div style={{ fontSize: 10.5, fontWeight: 650, letterSpacing: "0.07em", textTransform: "uppercase", color: "var(--text-4)", marginBottom: 5 }}>{label}</div>
        {children}
      </div>
    </div>
  );
}

// An incident reads in shipping order: a commit landed, Kaval (replaying it
// blind) flagged files, and days later a fix had to touch those same files.
// That overlap is the product claim, so the row tells it in that order.
function IncidentRow({ incident, flash, defaultOpen = false, repo }) {
  const [open, setOpen] = useState(defaultOpen);
  const tone = incidentTone(incident);
  const findings = incident.findings || [];
  // A culprit alone is enough to expand: the story steps (dates, fix files)
  // carry the proof chain even without evidence cards.
  const expandable = findings.length > 0 || incident.reason || incident.culpritScope || Boolean(incident.culprit);
  const others = findings.slice(1).map((finding) => finding.name).filter(Boolean);
  const delay = fixDelay(incident);
  const flaggedFiles = new Set((incident.findings || []).map((finding) => finding.file).filter(Boolean));

  return (
    <div id={`incident-${incident.n}`}
      style={{ borderTop: "1px solid var(--border)",
        boxShadow: flash ? "inset 0 0 0 2px var(--ring)" : "none", transition: "box-shadow .4s ease" }}>
      <div onClick={() => expandable && setOpen(!open)}
        style={{ display: "flex", alignItems: "center", gap: 10, padding: "11px 24px", cursor: expandable ? "pointer" : "default" }}>
        <span style={{ ...tone.seg, width: 24, height: 24, borderRadius: 6, display: "inline-flex", alignItems: "center",
          justifyContent: "center", fontFamily: "var(--font-mono)", fontSize: 10.5, fontWeight: 600, flexShrink: 0 }}>
          {incident.n}
        </span>
        <div style={{ flex: 1, minWidth: 0, display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
          {/* Collapsed row reads human-first: date + what the commit said.
              SHAs live in the expanded story steps, not up here. */}
          {incident.culprit ? (
            <>
              {fmtDate(incident.culprit.when) && (
                <span style={{ fontSize: 11.5, fontWeight: 620, color: "var(--text-3)", fontVariantNumeric: "tabular-nums", flexShrink: 0 }}>
                  {fmtDate(incident.culprit.when)}
                </span>
              )}
              {incident.culprit.subject && (
                <span style={{ fontSize: 13, fontWeight: 580, letterSpacing: "-0.01em", maxWidth: 280, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                  “{incident.culprit.subject}”
                </span>
              )}
              <span style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: 12, color: "var(--text-3)" }}>
                <span style={{ color: "var(--text-4)" }}>{Icon.arrowRight({ s: 12 })}</span>
                {/* "needed a fix" is the regression story; set-aside incidents
                    were followed by fix-LIKE commits, nothing regressed. */}
                {incident.status === "hit" || incident.status === "miss" ? "needed a fix" : "followed"}{delay ? ` ${delay}` : ""}{incident.status === "hit" || incident.status === "miss" ? ":" : " by:"}
              </span>
              <span style={{ fontSize: 12, color: "var(--text-3)", maxWidth: 260, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>“{incident.fix.msg}”</span>
            </>
          ) : (
            <>
              {fmtDate(incident.fix.when) && (
                <span style={{ fontSize: 11.5, fontWeight: 620, color: "var(--text-3)", fontVariantNumeric: "tabular-nums", flexShrink: 0 }}>
                  {fmtDate(incident.fix.when)}
                </span>
              )}
              <span style={{ fontSize: 13, fontWeight: 580, letterSpacing: "-0.01em", maxWidth: 420, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>“{incident.fix.msg}”</span>
            </>
          )}
          {incident.attempts > 1 && <Pill tone="med">{incident.attempts} attempts to fix</Pill>}
          {/* Rework incidents were never confessed: the repairing commit's
              message said nothing — code shape (young lines deleted) found it. */}
          {incident.kind === "rework" && (
            <span title={'found by code shape — the "fix" never said it was one'}>
              <Pill tone="med">found by code shape</Pill>
            </span>
          )}
        </div>
        {incident.repro && <ReproChip command={incident.repro.command} />}
        <Pill tone={incident.decision ? DECISION_PILL[incident.decision] || "neutral" : tone.pill} dot>{incident.verdict}</Pill>
        {expandable && (
          <span style={{ color: "var(--text-4)", display: "inline-block", transition: "transform .14s ease", transform: open ? "rotate(90deg)" : "none" }}>
            {Icon.chevron({ s: 13 })}
          </span>
        )}
      </div>
      {open && (
        <div style={{ padding: "6px 24px 18px 58px" }}>
          {incident.culprit ? (
            <>
              <StoryStep label={`What shipped${fmtDate(incident.culprit.when) ? ` — ${fmtDate(incident.culprit.when)}` : ""}`}>
                <div style={{ display: "flex", alignItems: "center", gap: 7, flexWrap: "wrap", fontSize: 12.5, color: "var(--text-2)" }}>
                  <ShaChip sha={incident.culprit.sha} tone="head" repo={repo} />
                  {incident.culprit.subject && <span>“{incident.culprit.subject}”</span>}
                  <span style={{ color: "var(--text-3)" }}>
                    {incident.status === "hit" || incident.status === "miss" ? "merged with the problem already inside." : "merged."}
                  </span>
                </div>
                {incident.culpritScope && (
                  <div style={{ fontSize: 12, color: "var(--text-3)", lineHeight: 1.55, marginTop: 6 }}>
                    {Icon.warn({ s: 12, style: { display: "inline-block", verticalAlign: "-2px", marginRight: 5 } })}
                    Honesty note: this was a {incident.culpritScope} — “pre-merge review would have caught it” is a weaker claim for bulk drops.
                  </div>
                )}
              </StoryStep>
              <StoryStep label={incident.status === "hit"
                ? "What Kaval flagged, replaying that merge blind to the future"
                : "What Kaval said, replaying that merge blind to the future"}>
                {incident.reason && (
                  <div style={{ fontSize: 12.5, color: "var(--text-2)", lineHeight: 1.6, background: "var(--surface-2)",
                    border: "1px solid var(--border)", borderRadius: 8, padding: "9px 12px" }}>
                    {incident.reason}
                  </div>
                )}
                <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
                  {findings.slice(0, 2).map((finding) => <EvidenceCard key={finding.id} finding={finding} />)}
                </div>
                {incident.decision === "block" && incident.repro && (
                  <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap", marginTop: 10,
                    background: "var(--high-subtle)", border: "1px solid var(--high-border)", borderRadius: 8,
                    padding: "9px 12px", fontSize: 12, color: "var(--high-text)", lineHeight: 1.55 }}>
                    {incident.blockRule && (
                      <span style={{ flexBasis: "100%" }}>
                        <span style={{ fontWeight: 620 }}>Why a block: </span>{incident.blockRule}.
                      </span>
                    )}
                    <span style={{ fontWeight: 620 }}>Run the failing probe yourself:</span>
                    <code className="story-code" style={{ overflowWrap: "anywhere" }}>{incident.repro.command}</code>
                    <span style={{ color: "var(--text-3)" }}>— exits non-zero while the regression is present.</span>
                  </div>
                )}
                {incident.decision === "escalate" && incident.escalateReasons?.length > 0 && (
                  <div style={{ marginTop: 10, fontSize: 12, color: "var(--text-2)", lineHeight: 1.6,
                    background: "var(--surface-2)", border: "1px solid var(--border)", borderRadius: 8, padding: "9px 12px" }}>
                    <span style={{ fontWeight: 620, color: "var(--med-text, var(--text-2))" }}>Why a human, not a block: </span>
                    {incident.escalateReasons.join("; ")}
                  </div>
                )}
                {(others.length > 0 || incident.moreFindings > 0) && (
                  <div style={{ fontSize: 11.5, color: "var(--text-3)", marginTop: 8 }}>
                    Also in this change: {others.map((name, index) => <code key={index} className="story-code" style={{ marginRight: 5 }}>{name}</code>)}
                    {incident.moreFindings > 0 && `+${incident.moreFindings} more in report.md`}
                  </div>
                )}
              </StoryStep>
              <StoryStep last label={`What happened next${fmtDate(incident.fix.when) ? ` — ${fmtDate(incident.fix.when)}` : ""}`}>
                <div style={{ fontSize: 12.5, color: "var(--text-2)", lineHeight: 1.65 }}>
                  {delay ? `${delay[0].toUpperCase()}${delay.slice(1)}, ` : "Later, "}
                  {incident.fix.author ? `${incident.fix.author} shipped ` : "a fix shipped: "}
                  <ShaChip sha={incident.fix.sha} label="fix" repo={repo} /> <span style={{ fontWeight: 580 }}>“{incident.fix.msg}”</span>
                  {incident.status === "hit" && " — touching the same code flagged above."}
                </div>
                {incident.fixFiles?.length > 0 && (
                  <div style={{ display: "flex", alignItems: "baseline", gap: 6, flexWrap: "wrap", marginTop: 7, fontSize: 11.5, color: "var(--text-3)" }}>
                    The fix touched:
                    {incident.fixFiles.map((file) => (
                      <code key={file} className="story-code" style={flaggedFiles.has(file) ? { color: "var(--pass-text)", borderColor: "var(--pass-border)", background: "var(--pass-subtle)" } : undefined}>
                        {file.split("/").pop()}{flaggedFiles.has(file) ? " ✓ flagged" : ""}
                      </code>
                    ))}
                    {incident.fixFilesMore > 0 && <span>+{incident.fixFilesMore} more</span>}
                  </div>
                )}
              </StoryStep>
            </>
          ) : (
            <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
              {incident.reason && (
                <div style={{ fontSize: 12.5, color: "var(--text-2)", lineHeight: 1.6, background: "var(--surface-2)",
                  border: "1px solid var(--border)", borderRadius: 8, padding: "9px 12px" }}>
                  {incident.reason}
                </div>
              )}
              {findings.slice(0, 2).map((finding) => <EvidenceCard key={finding.id} finding={finding} />)}
            </div>
          )}
        </div>
      )}
    </div>
  );
}

// ---- top signals beyond the incidents -------------------------------------
function TopFindingRow({ finding }) {
  const [open, setOpen] = useState(false);
  return (
    <div style={{ borderTop: "1px solid var(--border)" }}>
      <div onClick={() => setOpen(!open)} style={{ display: "flex", alignItems: "center", gap: 10, padding: "9px 24px", cursor: "pointer" }}>
        <SeverityTag sev={finding.severity} size="sm" />
        <span style={{ fontSize: 12.5, fontWeight: 560, flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{finding.title}</span>
        {finding.file && <span style={{ fontFamily: "var(--font-mono)", fontSize: 10.5, color: "var(--text-4)", maxWidth: 280, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{finding.file}</span>}
        <span style={{ color: "var(--text-4)", display: "inline-block", transition: "transform .14s ease", transform: open ? "rotate(90deg)" : "none" }}>{Icon.chevron({ s: 12 })}</span>
      </div>
      {open && <div style={{ padding: "0 24px 12px" }}><EvidenceCard finding={finding} /></div>}
    </div>
  );
}

// ---- control group: clean commits, same pipeline ---------------------------
// What makes "pass" credible: commits never implicated in any fix, run through
// the identical verdict pipeline. One small square per commit; the number that
// matters is false blocks (target: 0). Escalations are shown honestly.
function ControlStrip({ control, repo }) {
  const records = control.records || [];
  const toneFor = (decision) => (
    decision === "block"
      ? { background: "var(--high)", color: "#fff", border: "1px solid var(--high)" }
      : decision === "escalate"
        ? { background: "var(--med-subtle, var(--surface-3))", color: "var(--med-text, var(--text-2))", border: "1px solid var(--med-border, var(--border))" }
        : { background: "var(--pass-subtle)", color: "var(--pass-text)", border: "1px solid var(--pass-border)" }
  );
  return (
    <div>
      <div style={{ padding: "16px 24px 10px", borderTop: "1px solid var(--border)" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }}>
          <div style={{ fontSize: 14.5, fontWeight: 620, letterSpacing: "-0.015em" }}>Control group — clean commits, same pipeline</div>
          <Pill tone={control.blocks ? "high" : "pass"} dot>
            {control.blocks} false block{control.blocks === 1 ? "" : "s"} / {control.commits}
          </Pill>
          {control.escalated > 0 && <Pill tone="med">{control.escalated} escalated</Pill>}
        </div>
        <div style={{ fontSize: 12, color: "var(--text-3)", marginTop: 3, lineHeight: 1.55 }}>
          {control.commits} commit{control.commits === 1 ? "" : "s"} that never needed a fix — and share no files with any incident above — went
          through the identical pipeline: blast radius, intent, both-versions execution, verdict. A tool that blocks good merges is worse than no tool.
        </div>
      </div>
      {records.length > 0 && (
        <div style={{ display: "flex", gap: 4, flexWrap: "wrap", padding: "2px 24px 16px" }}>
          {records.map((record, index) => {
            const squareStyle = { ...toneFor(record.decision), display: "inline-flex", alignItems: "center", justifyContent: "center",
              minWidth: 58, height: 24, borderRadius: 6, padding: "0 7px",
              fontFamily: "var(--font-mono)", fontSize: 10.5, fontWeight: 600, textDecoration: "none" };
            const title = `${record.sha || ""} ${record.subject || ""} — ${record.decision || "?"}${record.escalateReasons?.length ? `: ${record.escalateReasons[0]}` : ""}`;
            return repo && record.sha ? (
              <a key={record.sha || index} href={`https://github.com/${repo}/commit/${record.sha}`} target="_blank" rel="noreferrer noopener" title={title} style={squareStyle}>{record.sha}</a>
            ) : (
              <span key={record.sha || index} title={title} style={squareStyle}>{record.sha || "?"}</span>
            );
          })}
        </div>
      )}
    </div>
  );
}

// ---- the whole highlights screen ------------------------------------------
function DeepReportHighlights({ view }) {
  const [flashN, setFlashN] = useState(null);
  const flashTimer = useRef(null);

  function jump(n) {
    const row = document.getElementById(`incident-${n}`);
    if (row) row.scrollIntoView({ behavior: "smooth", block: "center" });
    setFlashN(n);
    clearTimeout(flashTimer.current);
    flashTimer.current = setTimeout(() => setFlashN(null), 1400);
  }
  useEffect(() => () => clearTimeout(flashTimer.current), []);

  const summary = view.summary;
  const stats = view.stats || {};
  const scope = view.commitsScanned ? `${view.commitsScanned} recent commits` : "the scanned window";

  return (
    <div>
      {summary && <VerdictHero view={view} onJump={jump} />}
      <InstallBanner context={{ surface: "deep_report", repo: view.repo || null }} />
      {view.incidents.length > 0 && (
        <div>
          <div style={{ padding: "16px 24px 10px", borderTop: "1px solid var(--border)" }}>
            <div style={{ fontSize: 14.5, fontWeight: 620, letterSpacing: "-0.015em" }}>Every incident, replayed</div>
            <div style={{ fontSize: 12, color: "var(--text-3)", marginTop: 3 }}>
              Ranked by evidence strength, scored against this repo&rsquo;s real reverts and hotfixes — misses included. Click a row for the full story.
            </div>
          </div>
          {/* #1 is the strongest story by construction — open it so the first
              thing on screen below the verdict is the full proof chain. */}
          {view.incidents.map((incident) => (
            <IncidentRow key={incident.n} incident={incident} flash={flashN === incident.n} defaultOpen={incident.n === 1 && incident.status === "hit"} repo={view.repo} />
          ))}
        </div>
      )}
      {view.control && <ControlStrip control={view.control} repo={view.repo} />}
      {view.topFindings?.length > 0 && (
        <div>
          <div style={{ padding: "16px 24px 10px", borderTop: "1px solid var(--border)" }}>
            <div style={{ fontSize: 14.5, fontWeight: 620, letterSpacing: "-0.015em" }}>Top signals across recent history</div>
            <div style={{ fontSize: 12, color: "var(--text-3)", marginTop: 3 }}>
              High-signal changes a reviewer would want to know landed — whether or not they have caused damage yet.
            </div>
          </div>
          {view.topFindings.map((finding, index) => <TopFindingRow key={finding.id || index} finding={finding} />)}
        </div>
      )}
      <div style={{ padding: "14px 24px 18px", borderTop: "1px solid var(--border)", fontSize: 11.5, color: "var(--text-3)", lineHeight: 1.6 }}>
        {stats.findings ?? 0} findings across {scope}{stats.high ? ` (${stats.high} high)` : ""}
        {stats.findingsPerCulprit != null && <> · median {stats.findingsPerCulprit} findings per analyzed change</>}
        {stats.hitRate != null && <> · {Math.round(stats.hitRate * 100)}% backtest hit rate</>}
        <br />
        Every claim is reproducible from the listed SHAs — Kaval executes the code before and after each change. No model decides what is true:
        every flip is deterministically observed; AI only parses commit intent into a licensed delta and orders findings.
      </div>
    </div>
  );
}

// ---- PR advisory report (/pr/:owner/:repo/:number) -------------------------
// One PR, one verdict, a handful of findings — the page a "Details" link on a
// GitHub check opens. Public by design: anyone on the PR can read it. Renders
// buildPrView (report-view.mjs); evidence cards are the same ones the deep
// report uses, so a flip reads identically here and in the gallery.
const PR_DECISION_COPY = {
  block: { label: "Kaval would block this", sub: "Executed evidence of an unlicensed behavior change — the diff does something the PR never claimed." },
  escalate: { label: "Worth a human look", sub: "Kaval found behavior changes it could not license against the PR's stated intent. Advisory only — nothing is blocked." },
  pass: { label: "No silent change", sub: "Kaval ran the changed code at base and head; observed behavior matches the PR's stated intent." },
};

function PrReport({ view }) {
  const decision = PR_DECISION_COPY[view.decision] || PR_DECISION_COPY.escalate;
  const counts = view.counts || {};
  const coverage = view.coverage || {};
  const partial = coverage.filesTruncated || coverage.patchesMissing > 0;
  const date = fmtDate(view.generatedAt);

  return (
    <div>
      {/* verdict hero: decision first, the PR it judged second */}
      <div style={{ padding: "22px 24px 18px" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }}>
          <Pill tone={DECISION_PILL[view.decision] || "med"} dot>{(view.decision || "escalate").toUpperCase()}</Pill>
          <span style={{ fontSize: 19, fontWeight: 650, letterSpacing: "-0.02em" }}>{decision.label}</span>
        </div>
        <div style={{ fontSize: 12.5, color: "var(--text-3)", marginTop: 7, lineHeight: 1.6, maxWidth: 640 }}>{decision.sub}</div>
        <div style={{ display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap", marginTop: 12 }}>
          {view.prUrl && (
            <a href={view.prUrl} target="_blank" rel="noreferrer"
              style={{ fontSize: 13, fontWeight: 600, color: "var(--text)", textDecoration: "none" }}>
              {view.repo}#{view.prNumber}{view.prTitle ? ` — ${view.prTitle}` : ""} ↗
            </a>
          )}
          {view.baseSha && view.headSha && (
            <span style={{ fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--text-4)" }}>
              {view.baseSha} → {view.headSha}
            </span>
          )}
        </div>
        <div style={{ fontSize: 12, color: "var(--text-3)", marginTop: 10 }}>
          {counts.licensed || 0} licensed · {counts.unlicensed || 0} unlicensed · {counts.ambiguous || 0} ambiguous
          {view.intent !== "parsed" && <> · the PR description doesn't state a clear intent, so nothing could be licensed</>}
        </div>
      </div>

      {view.escalateReasons?.length > 0 && (
        <div style={{ padding: "14px 24px 16px", borderTop: "1px solid var(--border)" }}>
          <div style={{ fontSize: 13.5, fontWeight: 620, letterSpacing: "-0.01em", marginBottom: 8 }}>Why a human should look</div>
          <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
            {view.escalateReasons.map((reason, index) => (
              <div key={index} style={{ display: "flex", gap: 8, fontSize: 12.5, lineHeight: 1.6, color: "var(--text-2)" }}>
                <span style={{ color: "var(--med-text, var(--text-3))", flexShrink: 0 }}>•</span>
                {reason}
              </div>
            ))}
          </div>
        </div>
      )}

      {view.findings?.length > 0 && (
        <div style={{ padding: "14px 24px 16px", borderTop: "1px solid var(--border)" }}>
          <div style={{ fontSize: 13.5, fontWeight: 620, letterSpacing: "-0.01em", marginBottom: 3 }}>The evidence</div>
          <div style={{ fontSize: 12, color: "var(--text-3)", marginBottom: 12 }}>
            Kaval ran the changed code at both ends of this PR. Every flip below was observed, not inferred.
          </div>
          <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
            {view.findings.map((finding, index) => <EvidenceCard key={finding.id || index} finding={finding} lead={index === 0} />)}
          </div>
        </div>
      )}

      {partial && (
        <div style={{ margin: "0 24px 16px", padding: "10px 14px", background: "var(--surface-2)",
          border: "1px solid var(--border)", borderRadius: 8, fontSize: 12, lineHeight: 1.6, color: "var(--text-2)" }}>
          <strong>Partial coverage.</strong>{" "}
          {coverage.filesTruncated && <>The diff exceeds GitHub's comparison window, so only the first {coverage.fileCount} files were analyzed. </>}
          {coverage.patchesMissing > 0 && <>{coverage.patchesMissing} file{coverage.patchesMissing === 1 ? "" : "s"} had no readable patch. </>}
          This verdict covers what Kaval could see — it is not a clean bill for the rest.
        </div>
      )}

      <InstallBanner context={{ surface: "pr_report", repo: view.repo || null, prNumber: view.prNumber || null }} />

      <div style={{ padding: "14px 24px 18px", borderTop: "1px solid var(--border)", fontSize: 11.5, color: "var(--text-3)", lineHeight: 1.6 }}>
        Advisory check — Kaval never blocks a merge. {coverage.fileCount || 0} file{coverage.fileCount === 1 ? "" : "s"} across {coverage.totalCommits || 0} commit{coverage.totalCommits === 1 ? "" : "s"} analyzed
        {view.engineVersion && <> · engine {view.engineVersion}</>}
        {date && <> · {date}</>}
        <br />
        Every claim is reproducible from the listed SHAs — Kaval executes the code before and after the change. No model decides what is true:
        every flip is deterministically observed; AI only parses PR intent into a licensed delta.
      </div>
    </div>
  );
}

Object.assign(window, { DeepReportHighlights, PrReport, InstallBanner });
