/* prototype-app.jsx — main React app for the Osynax prototype.
   Uses globals exposed by prototype-data.jsx: Icons, STAGES, CITATIONS,
   PRS, STATUS_STYLE, timeAgo. */

const { useState, useEffect, useRef, useMemo } = React;

/* ============ THEME (matches dashboard/app/globals.css) ============ */
const T = {
  bg:        "#0a0a0f",
  surface:   "#0f0f1a",
  card:      "#0c0c14",
  border:    "#1a1a2e",
  borderHi:  "#1e1e2e",
  text:      "#e2e8f0",
  muted:     "#64748b",
  dim:       "#475569",
  accent:    "#3b82f6",
  accent2:   "#60a5fa",
  safe:      "#22c55e",
  unsafe:    "#ef4444",
  warn:      "#eab308",
  remediate: "#4ade80",
  esc:       "#eab308",
  mono:      `"JetBrains Mono", "Fira Code", ui-monospace, monospace`,
};

/* ============ TOP NAV (matches dashboard/components/Nav.tsx) ============ */
function Nav({ pageId, onNav }) {
  const links = [
    { id: "evaluations", label: "Evaluations", icon: Icons.List },
    { id: "analytics",   label: "Analytics",   icon: Icons.BarChart },
    { id: "stream",      label: "Live Stream", icon: Icons.Terminal },
  ];
  return (
    <nav style={{
      display: "flex", alignItems: "center", gap: 24,
      padding: "12px 40px",
      borderBottom: `1px solid ${T.border}`,
      backgroundColor: T.card,
    }}>
      <a href="#" onClick={(e) => { e.preventDefault(); onNav("evaluations"); }}
         style={{ display: "flex", alignItems: "center", gap: 8, textDecoration: "none" }}>
        <Icons.Shield size={18} color={T.accent} />
        <span style={{ color: T.accent, fontWeight: 700, fontSize: 15 }}>osynax</span>
      </a>
      <div style={{ display: "flex", gap: 4, marginLeft: 24 }}>
        {links.map(({ id, label, icon: Ic }) => {
          const active = pageId === id || (pageId === "detail" && id === "evaluations");
          return (
            <a key={id} href="#" onClick={(e) => { e.preventDefault(); onNav(id); }}
               style={{
                 display: "flex", alignItems: "center", gap: 6,
                 padding: "6px 14px", borderRadius: 6,
                 fontSize: 12, fontWeight: 500, textDecoration: "none",
                 color: active ? T.text : T.muted,
                 backgroundColor: active ? T.borderHi : "transparent",
                 transition: "all 150ms",
               }}>
              <Ic size={14} color="currentColor" />
              {label}
            </a>
          );
        })}
      </div>
      <div style={{ marginLeft: "auto", display: "flex", alignItems: "center", gap: 12 }}>
        <div style={{
          display: "flex", alignItems: "center", gap: 6,
          fontFamily: T.mono, fontSize: 11, color: T.muted,
        }}>
          <span style={{
            width: 6, height: 6, borderRadius: "50%",
            background: T.safe, boxShadow: `0 0 8px ${T.safe}`,
          }}/>
          example-bank/lending-core
        </div>
        <div style={{
          width: 24, height: 24, borderRadius: "50%",
          background: "linear-gradient(135deg, #3b82f6, #1e3a5f)",
          display: "flex", alignItems: "center", justifyContent: "center",
          fontSize: 10, color: "#fff", fontWeight: 600, fontFamily: T.mono,
        }}>RC</div>
      </div>
    </nav>
  );
}

/* ============ STATUS BADGE ============ */
function StatusBadge({ status, animate = false }) {
  const s = STATUS_STYLE[status] || STATUS_STYLE.pending;
  return (
    <span style={{
      display: "inline-flex", alignItems: "center", gap: 6,
      padding: "4px 10px", borderRadius: 6,
      fontSize: 12, fontWeight: 600,
      backgroundColor: s.bg, color: s.text,
    }}>
      {status === "evaluating" && <SpinIcon color={s.text} size={12} />}
      {s.label}
    </span>
  );
}

function VerdictDisplay({ verdict }) {
  if (!verdict) return <span style={{ color: T.dim }}>—</span>;
  const isSafe = verdict === "Safe";
  const Ic = isSafe ? Icons.ShieldCheck : verdict === "Escalated" ? Icons.ShieldAlert : Icons.ShieldX;
  const color = isSafe ? T.safe : verdict === "Escalated" ? T.warn : T.unsafe;
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 6, color, fontWeight: 700, fontSize: 13 }}>
      <Ic size={16} color={color} />
      {verdict}
    </span>
  );
}

function SpinIcon({ color = T.text, size = 14 }) {
  return (
    <span style={{ display: "inline-flex", animation: "osspin 1s linear infinite" }}>
      <Icons.Loader size={size} color={color} />
    </span>
  );
}

/* ============ STAT CARD ============ */
function StatCard({ label, value, color, hint }) {
  return (
    <div style={{
      flex: 1, padding: "14px 18px",
      backgroundColor: T.card, borderRadius: 10,
      border: `1px solid ${T.border}`,
    }}>
      <div style={{ fontSize: 22, fontWeight: 700, color, fontFamily: T.mono, letterSpacing: "-0.01em" }}>
        {value}
      </div>
      <div style={{
        fontSize: 11, color: T.dim, marginTop: 2,
        textTransform: "uppercase", letterSpacing: "0.05em",
        fontFamily: T.mono,
      }}>
        {label}
      </div>
      {hint && (
        <div style={{ fontSize: 11, color: T.muted, marginTop: 4 }}>{hint}</div>
      )}
    </div>
  );
}

/* ============ EVALUATIONS LIST ============ */
function EvaluationsList({ onOpenPR }) {
  const counts = useMemo(() => {
    const c = { total: PRS.length, safe: 0, unsafe: 0, evaluating: 0, escalated: 0, remediated: 0 };
    PRS.forEach(p => { c[p.status] = (c[p.status] || 0) + 1; });
    return c;
  }, []);

  return (
    <div style={{ padding: "32px 40px", maxWidth: 1240, margin: "0 auto" }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 28 }}>
        <div>
          <h1 style={{ margin: 0, fontSize: 22, fontWeight: 700, display: "flex", alignItems: "center", gap: 10 }}>
            <Icons.Shield size={22} color={T.accent} />
            <span style={{ color: T.accent }}>osynax</span>
            <span style={{ color: T.muted, fontWeight: 400, fontSize: 14 }}>dashboard</span>
          </h1>
          <p style={{ margin: "4px 0 0", fontSize: 12, color: T.dim, fontFamily: T.mono }}>
            Pull request security evaluations · live from Postgres
          </p>
        </div>
        <button onClick={() => {}} style={{
          display: "flex", alignItems: "center", gap: 6,
          background: "transparent", border: `1px solid ${T.borderHi}`,
          borderRadius: 8, padding: "8px 14px",
          color: T.muted, fontSize: 12, cursor: "pointer",
          fontFamily: T.mono,
        }}>
          <Icons.Refresh size={14} color={T.muted} />
          Refresh
        </button>
      </div>

      <div style={{ display: "flex", gap: 16, marginBottom: 24 }}>
        <StatCard label="Total PRs" value={counts.total} color={T.accent} />
        <StatCard label="Safe" value={counts.safe || 0} color={T.safe} />
        <StatCard label="Unsafe" value={counts.unsafe || 0} color={T.unsafe} />
        <StatCard label="Escalated" value={counts.escalated || 0} color={T.warn} />
        <StatCard label="Remediated" value={counts.remediated || 0} color={T.remediate} />
        <StatCard label="Evaluating" value={counts.evaluating || 0} color={T.accent2} />
      </div>

      <div style={{
        backgroundColor: T.card, borderRadius: 12,
        border: `1px solid ${T.border}`, overflow: "hidden",
      }}>
        <div style={{
          display: "grid",
          gridTemplateColumns: "80px 1fr 160px 130px 110px 100px 24px",
          padding: "12px 20px",
          borderBottom: `1px solid ${T.border}`,
          fontSize: 11, fontWeight: 600, color: T.dim,
          textTransform: "uppercase", letterSpacing: "0.05em",
          fontFamily: T.mono,
        }}>
          <span>PR #</span>
          <span>Title</span>
          <span>Author</span>
          <span>Status</span>
          <span>Verdict</span>
          <span>When</span>
          <span></span>
        </div>

        {PRS.map(pr => (
          <PRRow key={pr.id} pr={pr} onClick={() => onOpenPR(pr.id)} />
        ))}
      </div>

      <p style={{ marginTop: 18, fontSize: 11, color: T.dim, fontFamily: T.mono, textAlign: "center" }}>
        Click any row to open the live evaluation view.
      </p>
    </div>
  );
}

function PRRow({ pr, onClick }) {
  const [hover, setHover] = useState(false);
  return (
    <div
      onClick={onClick}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      style={{
        display: "grid",
        gridTemplateColumns: "80px 1fr 160px 130px 110px 100px 24px",
        padding: "14px 20px",
        borderBottom: `1px solid #111119`,
        alignItems: "center",
        fontSize: 13,
        cursor: "pointer",
        background: hover ? "#111119" : "transparent",
        transition: "background 150ms",
      }}>
      <span style={{ color: T.accent, fontWeight: 700, fontFamily: T.mono }}>#{pr.githubPrNumber}</span>
      <span style={{ color: T.text, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
        {pr.title}
      </span>
      <span style={{ color: T.muted, fontFamily: T.mono, fontSize: 12 }}>{pr.author}</span>
      <StatusBadge status={pr.status} />
      <VerdictDisplay verdict={pr.verdict} />
      <span style={{ color: T.dim, fontSize: 12, fontFamily: T.mono, display: "flex", alignItems: "center", gap: 4 }}>
        <Icons.Clock size={12} color={T.dim} />
        {timeAgo(pr.createdAt)}
      </span>
      <span style={{ color: hover ? T.text : T.dim, display: "flex", justifyContent: "flex-end" }}>
        <Icons.ChevronRight size={14} color="currentColor" />
      </span>
    </div>
  );
}

/* ============ PIPELINE (animated) ============
   Mirrors EvaluationDashboard.tsx pipeline nodes exactly. */

const PIPELINE_TIMING = {
  init:        { start: 0.2,  end: 0.7 },
  planner:     { start: 0.8,  end: 2.4 },
  sast:        { start: 2.5,  end: 3.7 },
  evaluator:   { start: 3.8,  end: 6.0 },
  auditor:     { start: 6.1,  end: 7.5 },
  remediation: { start: 7.6,  end: 9.2 },
};

function Pipeline({ playhead, hasRemediation }) {
  const stages = hasRemediation ? STAGES : STAGES.filter(s => s.id !== "remediation");
  return (
    <div style={{
      display: "flex", alignItems: "center", justifyContent: "center",
      padding: "20px 0", marginBottom: 8,
    }}>
      {stages.map((stage, i) => {
        const timing = PIPELINE_TIMING[stage.id];
        let status = null;
        if (playhead >= timing.start && playhead < timing.end) status = "started";
        else if (playhead >= timing.end) status = "completed";
        const Ic = Icons[stage.icon];

        let borderColor = T.borderHi;
        let bgColor = "#111119";
        let iconColor = T.dim;
        let glow = "none";

        if (status === "started") {
          borderColor = T.accent;
          bgColor = "#1e3a5f";
          iconColor = T.accent2;
          glow = "0 0 16px rgba(59,130,246,0.4)";
        } else if (status === "completed") {
          borderColor = T.safe;
          bgColor = "#0f2e1a";
          iconColor = T.safe;
          glow = "0 0 8px rgba(34,197,94,0.2)";
        }

        return (
          <div key={stage.id} style={{ display: "flex", alignItems: "center" }}>
            {i > 0 && (
              <div style={{
                width: 48, height: 2,
                background: status ? (status === "completed" ? T.safe : T.accent) : T.borderHi,
                transition: "background 500ms",
              }} />
            )}
            <div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 8 }}>
              <div style={{
                width: 48, height: 48, borderRadius: "50%",
                border: `2px solid ${borderColor}`, background: bgColor,
                display: "flex", alignItems: "center", justifyContent: "center",
                boxShadow: glow, transition: "all 500ms ease",
              }}>
                {status === "started" ? (
                  <SpinIcon color={iconColor} size={20} />
                ) : status === "completed" ? (
                  <Icons.CheckCircle size={20} color={iconColor} />
                ) : (
                  <Ic size={20} color={iconColor} />
                )}
              </div>
              <span style={{
                fontSize: 10, fontWeight: 600,
                color: status === "completed" ? T.safe : status === "started" ? T.accent2 : T.dim,
                textTransform: "uppercase", letterSpacing: "0.05em",
                transition: "color 500ms", fontFamily: T.mono,
              }}>
                {stage.label}
              </span>
            </div>
          </div>
        );
      })}
    </div>
  );
}

/* ============ TERMINAL (streaming reasoning) ============ */

function TypedText({ text, charsVisible }) {
  const shown = text.slice(0, Math.max(0, charsVisible));
  const isTyping = charsVisible < text.length;
  return (
    <span>
      {shown}
      {isTyping && (
        <span style={{
          display: "inline-block", width: 7, height: 14,
          background: T.accent, marginLeft: 2, verticalAlign: "middle",
          animation: "osblink 1s step-end infinite",
        }} />
      )}
    </span>
  );
}

function ReasoningBlock({ stageId, label, icon: Ic, reasoning, status, charsVisible, sast }) {
  if (!status) return null;
  const isRunning = status === "started";
  return (
    <div style={{ marginBottom: 22 }}>
      <div style={{
        display: "flex", alignItems: "center", gap: 8,
        fontWeight: 700, fontSize: 13,
        color: status === "completed" ? T.safe : T.warn,
        marginBottom: 8, fontFamily: T.mono,
      }}>
        <Ic size={15} color="currentColor" />
        [{label}] {isRunning ? "Processing..." : "Complete"}
        {isRunning && <SpinIcon size={13} color="currentColor" />}
      </div>
      {reasoning && (
        <div style={{
          paddingLeft: 16, borderLeft: `2px solid ${T.borderHi}`,
          color: "#8b9cb8", whiteSpace: "pre-wrap", wordBreak: "break-word",
          fontFamily: T.mono, fontSize: 13, lineHeight: 1.75,
        }}>
          <TypedText text={reasoning} charsVisible={charsVisible} />
        </div>
      )}
      {stageId === "sast" && sast && status === "completed" && (
        <div style={{
          marginTop: 10, paddingLeft: 16,
          fontSize: 13, fontWeight: 600,
          color: sast.passed ? T.safe : T.unsafe,
          display: "flex", alignItems: "center", gap: 8,
          fontFamily: T.mono,
        }}>
          <Icons.ScanSearch size={14} color="currentColor" />
          SAST: {sast.passed ? "PASS" : "FAIL"} — {sast.findings} findings
          ({sast.critical} critical, {sast.high} high)
        </div>
      )}
    </div>
  );
}

/* ============ CITATION CARD ============ */
function CitationCard({ id }) {
  const c = CITATIONS[id];
  const [open, setOpen] = useState(false);
  if (!c) return null;
  return (
    <div style={{
      border: `1px solid ${T.border}`,
      borderRadius: 8, overflow: "hidden", background: "#0a0a12",
    }}>
      <button onClick={() => setOpen(o => !o)} style={{
        width: "100%", textAlign: "left", background: "transparent",
        border: 0, padding: "14px 16px",
        display: "grid", gridTemplateColumns: "auto 1fr auto", gap: 12,
        alignItems: "center", cursor: "pointer", color: T.text,
        fontFamily: T.mono,
      }}>
        <Icons.BookOpen size={14} color={T.accent2} />
        <div style={{ minWidth: 0 }}>
          <div style={{ fontSize: 12, color: T.accent2, fontWeight: 600 }}>
            {c.framework} · {c.ref}
          </div>
          <div style={{ fontSize: 13, color: T.text, marginTop: 2, fontFamily: "system-ui" }}>
            {c.title}
          </div>
        </div>
        <span style={{ color: T.muted, transform: open ? "rotate(180deg)" : "none", transition: "transform 200ms" }}>
          <Icons.ChevronDown size={14} color="currentColor" />
        </span>
      </button>
      {open && (
        <div style={{
          padding: "0 16px 16px 16px", borderTop: `1px solid ${T.border}`,
          paddingTop: 14,
        }}>
          <blockquote style={{
            margin: 0, padding: "12px 14px",
            background: "#11111a", borderRadius: 6,
            fontSize: 13, lineHeight: 1.65, color: "#a8b3c9",
            borderLeft: `2px solid ${T.accent}`,
            fontStyle: "italic",
          }}>
            "{c.text}"
          </blockquote>
          <div style={{
            marginTop: 10, display: "flex", justifyContent: "space-between",
            alignItems: "center", fontSize: 11, color: T.muted, fontFamily: T.mono,
          }}>
            <a href={c.url} target="_blank" rel="noreferrer" style={{
              color: T.accent2, textDecoration: "none",
              display: "inline-flex", alignItems: "center", gap: 6,
            }}>
              {c.url.replace(/^https?:\/\//, "")}
              <Icons.ExternalLink size={11} color={T.accent2} />
            </a>
            <span>Licence · {c.licence}</span>
          </div>
        </div>
      )}
    </div>
  );
}

/* ============ DIFF VIEWER ============ */
function DiffViewer({ patch }) {
  if (!patch) return null;
  return (
    <pre style={{
      backgroundColor: "#0a0a12",
      border: `1px solid ${T.border}`,
      borderRadius: 8,
      padding: "16px 18px",
      fontSize: 12, lineHeight: 1.6,
      overflowX: "auto", margin: 0,
      fontFamily: T.mono, whiteSpace: "pre",
    }}>
      {patch.split("\n").map((line, i) => {
        let color = "#94a3b8";
        if (line.startsWith("+") && !line.startsWith("+++")) color = T.safe;
        else if (line.startsWith("-") && !line.startsWith("---")) color = T.unsafe;
        else if (line.startsWith("@@")) color = T.accent2;
        else if (line.startsWith("---") || line.startsWith("+++")) color = T.text;
        return <span key={i} style={{ color }}>{line}{"\n"}</span>;
      })}
    </pre>
  );
}

/* ============ VERDICT PANEL ============ */
function VerdictPanel({ pr, visible }) {
  if (!visible) return null;
  const v = pr.verdict;
  const isSafe = v === "Safe";
  const isEscalated = v === "Escalated";
  const colour = isSafe ? T.safe : isEscalated ? T.warn : T.unsafe;
  const bg = isSafe ? "rgba(22,101,52,0.15)" : isEscalated ? "rgba(133,77,14,0.15)" : "rgba(127,29,29,0.15)";
  const bd = isSafe ? "#166534" : isEscalated ? "#854d0e" : "#7f1d1d";
  const Ic = isSafe ? Icons.ShieldCheck : isEscalated ? Icons.ShieldAlert : Icons.ShieldX;
  return (
    <div style={{
      marginTop: 24, padding: "20px 24px", borderRadius: 10,
      textAlign: "center", border: `1px solid ${bd}`,
      background: bg, color: colour,
    }}>
      <div style={{
        display: "flex", alignItems: "center", justifyContent: "center", gap: 12,
        fontSize: 18, fontWeight: 700, fontFamily: T.mono, letterSpacing: "0.05em",
      }}>
        <Ic size={24} color={colour} />
        VERDICT: {v.toUpperCase()}
      </div>
      <p style={{ margin: "8px 0 0", fontSize: 11, opacity: 0.7, fontFamily: T.mono }}>
        SAST Gate + LLM Evaluator + Adversarial Auditor · attempt 1
      </p>
    </div>
  );
}

/* ============ DETAIL VIEW ============ */
function EvaluationDetail({ prId, onBack }) {
  const pr = PRS.find(p => p.id === prId);
  if (!pr) return null;

  const hasRemediation = pr.status === "unsafe" || pr.status === "remediated";
  const terminalEnd = hasRemediation ? PIPELINE_TIMING.remediation.end : PIPELINE_TIMING.auditor.end;
  const totalDuration = terminalEnd + 0.6;

  const [playhead, setPlayhead] = useState(0);
  const [playing, setPlaying] = useState(true);
  const [showRaw, setShowRaw] = useState(false);
  const rafRef = useRef(null);
  const lastTsRef = useRef(null);
  const scrollRef = useRef(null);

  useEffect(() => {
    // Reset on PR change
    setPlayhead(0);
    setPlaying(true);
  }, [prId]);

  useEffect(() => {
    if (!playing) {
      lastTsRef.current = null;
      return;
    }
    const step = (ts) => {
      if (lastTsRef.current == null) lastTsRef.current = ts;
      const dt = (ts - lastTsRef.current) / 1000;
      lastTsRef.current = ts;
      setPlayhead(p => {
        const next = p + dt;
        if (next >= totalDuration) {
          setPlaying(false);
          return totalDuration;
        }
        return next;
      });
      rafRef.current = requestAnimationFrame(step);
    };
    rafRef.current = requestAnimationFrame(step);
    return () => {
      if (rafRef.current) cancelAnimationFrame(rafRef.current);
      lastTsRef.current = null;
    };
  }, [playing, totalDuration]);

  // Auto-scroll terminal as new content streams in
  useEffect(() => {
    const el = scrollRef.current;
    if (el && playing) el.scrollTop = el.scrollHeight;
  }, [playhead, playing]);

  const stageStatus = (sid) => {
    const t = PIPELINE_TIMING[sid];
    if (playhead < t.start) return null;
    if (playhead < t.end)   return "started";
    return "completed";
  };

  const charsForStage = (sid, text) => {
    if (!text) return 0;
    const t = PIPELINE_TIMING[sid];
    if (playhead < t.start) return 0;
    if (playhead >= t.end)   return text.length;
    const prog = (playhead - t.start) / (t.end - t.start);
    return Math.floor(text.length * prog);
  };

  const showCitations = playhead > PIPELINE_TIMING.evaluator.start + 0.4 && pr.findings.length > 0;
  const showPatch = playhead > PIPELINE_TIMING.remediation.start + 0.6 && pr.patch;
  const showVerdict = playhead > terminalEnd - 0.1 && pr.verdict;

  const replay = () => { setPlayhead(0); setPlaying(true); };
  const skipToEnd = () => { setPlayhead(totalDuration); setPlaying(false); };

  return (
    <div style={{ padding: "24px 40px 80px", maxWidth: 1240, margin: "0 auto" }}>

      {/* breadcrumb + header */}
      <button onClick={onBack} style={{
        display: "inline-flex", alignItems: "center", gap: 6,
        background: "transparent", border: 0, color: T.muted,
        fontSize: 12, fontFamily: T.mono, cursor: "pointer",
        padding: "8px 0", marginBottom: 16,
      }}>
        <Icons.ChevronLeft size={14} color="currentColor" />
        Back to evaluations
      </button>

      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 24, gap: 24 }}>
        <div style={{ minWidth: 0 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 8 }}>
            <Icons.GitPullRequest size={18} color={T.accent} />
            <span style={{ color: T.accent, fontFamily: T.mono, fontSize: 14, fontWeight: 700 }}>
              #{pr.githubPrNumber}
            </span>
            <StatusBadge status={pr.status} />
          </div>
          <h1 style={{ margin: 0, fontSize: 22, fontWeight: 600, color: T.text, lineHeight: 1.2 }}>
            {pr.title}
          </h1>
          <div style={{
            marginTop: 12, display: "flex", flexWrap: "wrap", gap: "8px 20px",
            fontSize: 12, color: T.muted, fontFamily: T.mono,
          }}>
            <span style={{ display: "inline-flex", alignItems: "center", gap: 6 }}>
              <Icons.GitBranch size={12} color={T.muted} />
              {pr.headBranch} → {pr.baseBranch}
            </span>
            <span>{pr.repo}</span>
            <span>@{pr.author}</span>
            <span>{pr.headSha}</span>
            <span style={{ color: T.safe }}>+{pr.diff.add}</span>
            <span style={{ color: T.unsafe }}>−{pr.diff.del}</span>
            <span>{pr.diff.files} files</span>
            <span>{timeAgo(pr.createdAt)}</span>
          </div>
        </div>
        <div style={{ display: "flex", gap: 8, flexShrink: 0 }}>
          <button onClick={replay} style={btnGhost}>
            <Icons.Refresh size={14} color="currentColor" />
            Replay
          </button>
          <button onClick={skipToEnd} style={btnGhost}>Skip to end</button>
        </div>
      </div>

      {/* Pipeline */}
      <div style={{
        background: T.card, border: `1px solid ${T.border}`,
        borderRadius: 12, padding: "10px 0", marginBottom: 18,
      }}>
        <Pipeline playhead={playhead} hasRemediation={hasRemediation} />
        <PipelineScrub playhead={playhead} total={totalDuration}
                       onSeek={(t) => { setPlayhead(t); setPlaying(false); }}
                       playing={playing}
                       onPlayPause={() => setPlaying(p => !p)} />
      </div>

      <div style={{ display: "grid", gridTemplateColumns: pr.findings.length > 0 ? "1fr 360px" : "1fr", gap: 18 }}>
        {/* Terminal / reasoning */}
        <div style={{
          background: T.card, border: `1px solid ${T.border}`,
          borderRadius: 12, overflow: "hidden",
          boxShadow: "0 8px 32px rgba(0,0,0,0.4)",
        }}>
          <div style={{
            display: "flex", alignItems: "center", gap: 8,
            padding: "12px 18px", background: "#0e0e18",
            borderBottom: `1px solid ${T.border}`,
          }}>
            <div style={{ width: 11, height: 11, borderRadius: "50%", background: "#ff5f57" }} />
            <div style={{ width: 11, height: 11, borderRadius: "50%", background: "#febc2e" }} />
            <div style={{ width: 11, height: 11, borderRadius: "50%", background: "#28c840" }} />
            <span style={{ marginLeft: 14, fontSize: 12, color: T.dim, fontFamily: T.mono }}>
              osynax-eval — {pr.repo.split("/").pop()}/pull/{pr.githubPrNumber}
            </span>
          </div>

          <div ref={scrollRef} style={{
            height: 460, overflowY: "auto",
            padding: "20px 24px",
            fontSize: 13, lineHeight: 1.75, fontFamily: T.mono,
          }} className="osterm-scroll">

            {STAGES.filter(s => hasRemediation || s.id !== "remediation").map(s => (
              <ReasoningBlock
                key={s.id}
                stageId={s.id}
                label={s.label}
                icon={Icons[s.icon]}
                reasoning={pr.reasoning[s.id]}
                status={stageStatus(s.id)}
                charsVisible={charsForStage(s.id, pr.reasoning[s.id] || "")}
                sast={pr.sast}
              />
            ))}

            <VerdictPanel pr={pr} visible={showVerdict} />

            {showPatch && (
              <div style={{ marginTop: 22 }}>
                <div style={{
                  display: "flex", alignItems: "center", justifyContent: "space-between",
                  marginBottom: 10, fontFamily: T.mono,
                }}>
                  <span style={{
                    display: "flex", alignItems: "center", gap: 8,
                    fontSize: 13, fontWeight: 700, color: T.warn,
                  }}>
                    <Icons.Wrench size={15} color={T.warn} />
                    Remediation patch opened as PR #{pr.githubPrNumber}-fix
                  </span>
                  <button onClick={() => setShowRaw(s => !s)} style={{
                    background: "transparent", border: 0, color: T.muted,
                    fontSize: 11, fontFamily: T.mono, cursor: "pointer",
                  }}>
                    {showRaw ? "hide raw" : "view raw"}
                  </button>
                </div>
                <DiffViewer patch={pr.patch} />
              </div>
            )}

            {pr.status === "evaluating" && (
              <div style={{
                marginTop: 16, padding: 14, borderRadius: 8,
                background: "rgba(30,58,95,0.4)", border: `1px solid ${T.accent}`,
                color: T.accent2, fontSize: 12, fontFamily: T.mono,
                display: "flex", alignItems: "center", gap: 10,
              }}>
                <SpinIcon color={T.accent2} size={13} />
                Live evaluation in progress · webhook received {timeAgo(pr.createdAt)}
              </div>
            )}
          </div>
        </div>

        {/* Citations sidebar */}
        {pr.findings.length > 0 && (
          <aside>
            <div style={{
              fontSize: 11, color: T.dim, fontFamily: T.mono,
              letterSpacing: "0.06em", textTransform: "uppercase",
              marginBottom: 10, paddingLeft: 4,
            }}>
              Citations · grounding for verdict
            </div>
            <div style={{ display: "flex", flexDirection: "column", gap: 10,
                          opacity: showCitations ? 1 : 0.25,
                          transition: "opacity 600ms" }}>
              {pr.findings.map(id => <CitationCard key={id} id={id} />)}
            </div>
            <p style={{
              marginTop: 12, fontSize: 11, color: T.dim,
              fontFamily: T.mono, lineHeight: 1.6, paddingLeft: 4,
            }}>
              Retrieved via hybrid search · pgvector cosine + tsvector FTS, merged with RRF (k=60).
              Click any citation to expand the verbatim clause text.
            </p>
          </aside>
        )}
      </div>
    </div>
  );
}

function PipelineScrub({ playhead, total, onSeek, playing, onPlayPause }) {
  const trackRef = useRef(null);
  const seek = (e) => {
    if (!trackRef.current) return;
    const r = trackRef.current.getBoundingClientRect();
    const x = Math.max(0, Math.min(1, (e.clientX - r.left) / r.width));
    onSeek(x * total);
  };
  const pct = total > 0 ? (playhead / total) * 100 : 0;
  return (
    <div style={{
      display: "flex", alignItems: "center", gap: 14,
      padding: "0 28px 14px",
    }}>
      <button onClick={onPlayPause} style={{
        width: 26, height: 26, borderRadius: 4,
        background: T.borderHi, border: `1px solid ${T.border}`,
        color: T.text, cursor: "pointer",
        display: "flex", alignItems: "center", justifyContent: "center",
      }}>
        {playing ? (
          <svg width="10" height="10" viewBox="0 0 10 10"><rect x="2" y="1" width="2" height="8" fill="currentColor"/><rect x="6" y="1" width="2" height="8" fill="currentColor"/></svg>
        ) : (
          <svg width="10" height="10" viewBox="0 0 10 10"><path d="M2 1l6 4-6 4z" fill="currentColor"/></svg>
        )}
      </button>
      <div ref={trackRef} onClick={seek} style={{
        flex: 1, height: 18, cursor: "pointer", position: "relative",
        display: "flex", alignItems: "center",
      }}>
        <div style={{ position: "absolute", left: 0, right: 0, height: 2, background: T.border, borderRadius: 1 }}/>
        <div style={{ position: "absolute", left: 0, width: pct + "%", height: 2, background: T.accent, borderRadius: 1 }}/>
        <div style={{
          position: "absolute", left: pct + "%", top: "50%",
          width: 10, height: 10, marginLeft: -5, marginTop: -5,
          background: T.text, borderRadius: "50%",
          boxShadow: `0 0 0 3px ${T.bg}`,
        }}/>
      </div>
      <span style={{
        fontFamily: T.mono, fontSize: 11, color: T.muted,
        fontVariantNumeric: "tabular-nums", width: 64, textAlign: "right",
      }}>
        {playhead.toFixed(1)}s / {total.toFixed(1)}s
      </span>
    </div>
  );
}

const btnGhost = {
  display: "inline-flex", alignItems: "center", gap: 6,
  background: "transparent", border: `1px solid ${T.borderHi}`,
  borderRadius: 6, padding: "7px 12px",
  color: T.muted, fontSize: 12, cursor: "pointer",
  fontFamily: T.mono,
};

/* ============ ANALYTICS placeholder ============ */
function AnalyticsPage() {
  return (
    <div style={{ padding: "32px 40px", maxWidth: 1240, margin: "0 auto" }}>
      <h1 style={{ margin: 0, fontSize: 22, fontWeight: 700, color: T.text, display: "flex", alignItems: "center", gap: 10 }}>
        <Icons.BarChart size={22} color={T.accent} />
        <span style={{ color: T.accent }}>Analytics</span>
      </h1>
      <p style={{ marginTop: 10, color: T.muted, fontFamily: T.mono, fontSize: 13 }}>
        7-day verdict distribution · framework hit rate · evaluator agreement
      </p>

      <div style={{ marginTop: 24, display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 16 }}>
        <StatCard label="PRs reviewed · 7d" value="284" color={T.accent} hint="↑ 18% vs prior 7d" />
        <StatCard label="Avg. eval latency" value="11.8s" color={T.text} hint="p95 17.4s" />
        <StatCard label="Auditor flip rate" value="4.2%" color={T.warn} hint="Evaluator → Auditor disagreement" />
      </div>

      <div style={{
        marginTop: 24, background: T.card, border: `1px solid ${T.border}`,
        borderRadius: 12, padding: 28, color: T.muted, fontFamily: T.mono,
      }}>
        <div style={{ fontSize: 12, color: T.dim, letterSpacing: "0.06em", textTransform: "uppercase" }}>
          Verdict mix · last 7 days
        </div>
        <div style={{ marginTop: 18, display: "flex", height: 28, borderRadius: 4, overflow: "hidden" }}>
          <div style={{ flex: 198, background: T.safe }} title="Safe · 198" />
          <div style={{ flex: 41,  background: T.unsafe }} title="Unsafe · 41" />
          <div style={{ flex: 28,  background: T.remediate }} title="Remediated · 28" />
          <div style={{ flex: 11,  background: T.warn }} title="Escalated · 11" />
          <div style={{ flex: 6,   background: T.accent2 }} title="Merged · 6" />
        </div>
        <div style={{ marginTop: 14, display: "flex", gap: 18, fontSize: 12 }}>
          <Legend color={T.safe} label="Safe · 198"/>
          <Legend color={T.unsafe} label="Unsafe · 41"/>
          <Legend color={T.remediate} label="Remediated · 28"/>
          <Legend color={T.warn} label="Escalated · 11"/>
          <Legend color={T.accent2} label="Merged · 6"/>
        </div>
      </div>

      <p style={{ marginTop: 32, color: T.dim, fontSize: 12, fontFamily: T.mono, textAlign: "center" }}>
        Demo data · the live dashboard pulls from <code style={{ color: T.accent2 }}>/api/analytics/verdicts</code>.
      </p>
    </div>
  );
}
function Legend({ color, label }) {
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 6, color: T.muted }}>
      <span style={{ width: 8, height: 8, borderRadius: 1, background: color }} />
      {label}
    </span>
  );
}

/* ============ STREAM placeholder — jumps you to the in-progress PR ============ */
function StreamPage({ onOpenPR }) {
  return (
    <div style={{ padding: "32px 40px", maxWidth: 880, margin: "0 auto" }}>
      <h1 style={{ margin: 0, fontSize: 22, fontWeight: 700, color: T.text, display: "flex", alignItems: "center", gap: 10 }}>
        <Icons.Terminal size={22} color={T.accent} />
        <span style={{ color: T.accent }}>Live Stream</span>
      </h1>
      <p style={{ marginTop: 10, color: T.muted, fontFamily: T.mono, fontSize: 13 }}>
        One PR is mid-evaluation. Open it to watch the agents work.
      </p>
      <div style={{ marginTop: 24, background: T.card, border: `1px solid ${T.border}`, borderRadius: 12, padding: 20 }}>
        {PRS.filter(p => p.status === "evaluating").map(pr => (
          <div key={pr.id} onClick={() => onOpenPR(pr.id)} style={{
            display: "flex", alignItems: "center", gap: 14, padding: "12px 14px",
            borderRadius: 8, cursor: "pointer", background: "#11111a",
            border: `1px solid ${T.accent}`,
          }}>
            <SpinIcon color={T.accent2} size={16} />
            <div style={{ flex: 1 }}>
              <div style={{ color: T.text, fontSize: 14, fontWeight: 600 }}>
                #{pr.githubPrNumber} {pr.title}
              </div>
              <div style={{ color: T.muted, fontSize: 12, fontFamily: T.mono, marginTop: 2 }}>
                {pr.author} · {pr.headBranch} → {pr.baseBranch} · {timeAgo(pr.createdAt)}
              </div>
            </div>
            <Icons.ChevronRight size={16} color={T.muted} />
          </div>
        ))}
      </div>
    </div>
  );
}

/* ============ ROOT APP ============ */
function App() {
  const [route, setRoute] = useState({ page: "evaluations", prId: null });

  const onNav = (id) => setRoute({ page: id, prId: null });
  const onOpenPR = (prId) => setRoute({ page: "detail", prId });
  const onBack = () => setRoute({ page: "evaluations", prId: null });

  return (
    <div data-screen-label={
      route.page === "detail"
        ? `Detail · PR #${PRS.find(p => p.id === route.prId)?.githubPrNumber}`
        : `Dashboard · ${route.page}`
    } style={{
      minHeight: "100%",
      background: T.bg, color: T.text,
      fontFamily: T.mono,
    }}>
      <Nav pageId={route.page} onNav={onNav} />
      {route.page === "evaluations" && <EvaluationsList onOpenPR={onOpenPR} />}
      {route.page === "detail"      && <EvaluationDetail prId={route.prId} onBack={onBack} />}
      {route.page === "analytics"   && <AnalyticsPage />}
      {route.page === "stream"      && <StreamPage onOpenPR={onOpenPR} />}
    </div>
  );
}

window.OsynaxApp = App;
