/* command-deck.jsx
 * Bottom pane. Single container that morphs between:
 *   idle      → configure form
 *   running   → live trace (per-case status lines)
 *   complete  → verdict + KPIs + comparison table + attention cards
 *
 * The "duang" happens here: when complete renders, each comparison row
 * animates relay-from-left + baseline-from-right + delta-pop + shock line.
 * Rows stagger by ~120ms so it reads as seven sequential impacts.
 */

const { useEffect: useEffectCmd, useState: useStateCmd, useRef: useRefCmd } = React;
// `tr` is the global i18n helper (window.byokicuTr, declared in app.jsx). Used at render
// time — in components AND in the pure build* helpers (mock-data.jsx) called during render —
// so a language toggle re-runs every lookup. The share-card PNG (drawShareCard) stays
// English by design: it's a branded dark export, not on-screen UI. (Card 92-10)

function CommandDeck({ mode, runState, form, setForm, onStart, onReset, onShowLeaderboard, canStart, tweaks }) {
  // Track displayMode separately so we can keep showing the trace pane while
  // the door wipes closed, then swap to comparison and wipe back open.
  const [displayMode, setDisplayMode] = useStateCmd(mode);
  const [wipe, setWipe] = useStateCmd("none"); // none | closing | opening
  const wipeTimersRef = useRefCmd([]);

  useEffectCmd(() => {
    // Cancel pending wipes when mode resets
    wipeTimersRef.current.forEach(clearTimeout);
    wipeTimersRef.current = [];

    if (mode === "complete" && displayMode === "running") {
      // running → complete: close, swap, open
      setWipe("closing");
      wipeTimersRef.current.push(setTimeout(() => {
        setDisplayMode("complete");
        setWipe("opening");
      }, 360));
      wipeTimersRef.current.push(setTimeout(() => setWipe("none"), 360 + 460));
    } else if (mode !== displayMode) {
      setDisplayMode(mode);
      setWipe("none");
    }
    return () => wipeTimersRef.current.forEach(clearTimeout);
  }, [mode]);

  const completed = runState.cases.filter((c) => c.status === "completed").length;
  const failed = runState.cases.filter((c) => c.status === "failed").length;
  const progressPct = mode === "complete" ? 100 : Math.round(((completed + failed) / runState.cases.length) * 100);

  const bodyClass = [
    "command-body",
    "command-body-doors",
    wipe === "closing" ? "wipe-closing doors-shut" : "",
    wipe === "opening" ? "wipe-opening doors-open" : ""
  ].filter(Boolean).join(" ");

  // Use displayMode for the head label so it stays consistent during wipe
  const headMode = wipe === "closing" ? "running" : displayMode;

  return (
    <section className="command-deck" aria-label="Command deck">
      <header className="command-head">
        <div className="left">
          <h2>
            {headMode === "idle" && tr("cmd.head.idle")}
            {headMode === "running" && tr("cmd.head.running")}
            {headMode === "complete" && tr("cmd.head.complete")}
          </h2>
          <span className="sub">
            {headMode === "idle" && tr("cmd.sub.idle")}
            {headMode === "running" && tr("cmd.sub.running", { n: runState.currentCaseIdx + 1, total: runState.cases.length, sec: (runState.elapsedMs / 1000).toFixed(1) })}
            {headMode === "complete" && tr("cmd.sub.complete", { total: runState.cases.length, sec: (runState.elapsedMs / 1000).toFixed(2) })}
          </span>
        </div>
        <div className="right">
          {(headMode === "running" || headMode === "complete") && (
            <div className="command-progress" aria-label="run progress">
              <span style={{ width: `${progressPct}%` }} />
            </div>
          )}
          {/* Card 13: Re-run lives in the verdict banner actions now. */}
        </div>
      </header>

      <div className={bodyClass}>
        {/* Door panels — they slide in from sides during close, slide out during open */}
        <div className="door-panel left"><span className="label">{tr("cmd.door.relay")}</span></div>
        <div className="door-panel right"><span className="label">{tr("cmd.door.baseline")}</span></div>

        {displayMode === "idle" && (
          <ConfigPane form={form} setForm={setForm} onStart={onStart} canStart={canStart} />
        )}
        {displayMode === "running" && (
          <TracePane runState={runState} />
        )}
        {displayMode === "complete" && (
          <ComparePane runState={runState} tweaks={tweaks} onReset={onReset} onShowLeaderboard={onShowLeaderboard} />
        )}
      </div>
    </section>
  );
}

/* ───── Pre-flight: inline config form ───── */
function ConfigPane({ form, setForm, onStart, canStart }) {
  return (
    <form
      className="command-pane config-form"
      onSubmit={(e) => { e.preventDefault(); if (canStart) onStart(); }}
    >
      <div className="config-grid">
        <div className="config-field full">
          <label htmlFor="endpoint">{tr("config.label.endpoint")}</label>
          <input
            id="endpoint"
            placeholder="https://my-relay.example.com/v1"
            autoComplete="off"
            value={form.endpoint}
            onChange={(e) => setForm({ ...form, endpoint: e.target.value })}
          />
        </div>
        <div className="config-field">
          <label htmlFor="protocol">{tr("config.label.protocol")}</label>
          <select
            id="protocol"
            value={form.protocol}
            onChange={(e) => setForm({ ...form, protocol: e.target.value })}
          >
            <option value="openai">{tr("config.protocol.openai")}</option>
            <option value="anthropic">{tr("config.protocol.anthropic")}</option>
          </select>
        </div>
        <div className="config-field">
          <label htmlFor="provider">{tr("config.label.provider")}</label>
          <input
            id="provider"
            placeholder="openai"
            autoComplete="off"
            value={form.provider}
            onChange={(e) => setForm({ ...form, provider: e.target.value })}
          />
        </div>
        <div className="config-field">
          <label htmlFor="model">{tr("config.label.model")}</label>
          <input
            id="model"
            placeholder="gpt-4.1"
            autoComplete="off"
            value={form.model}
            onChange={(e) => setForm({ ...form, model: e.target.value })}
          />
        </div>
        <div className="config-field full">
          <label htmlFor="apikey">{tr("config.label.apikey")}</label>
          <input
            id="apikey"
            type="password"
            placeholder="sk-…"
            autoComplete="off"
            value={form.apiKey}
            onChange={(e) => setForm({ ...form, apiKey: e.target.value })}
          />
          <span className="hint">{tr("config.hint")}</span>
        </div>
      </div>
      <div className="config-side">
        <button className="start-btn" type="submit" disabled={!canStart}>
          <span>{tr("landing.btn.dispatch")}</span>
          <span className="arrow">▶▶</span>
        </button>
        <p className="config-note">
          {tr("config.note")}
        </p>
      </div>
    </form>
  );
}

/* ───── Running: trace log ───── */
function TracePane({ runState }) {
  const { cases, currentCaseIdx } = runState;
  return (
    <div className="command-pane trace">
      {cases.map((c, i) => {
        const isCurrent = i === currentCaseIdx && c.status === "pending";
        const cls = isCurrent ? "running" : c.status;
        return (
          <div key={c.id} className={`trace-row ${cls}`}>
            <span className="idx">{tr("trace.casePrefix")} {String(i + 1).padStart(2, "0")}</span>
            <span className="name">
              {tr("case." + c.id + ".label")}
              <small>{c.id}</small>
            </span>
            <span className="latency">
              {Number.isFinite(c.latencyMs) ? `${Math.round(c.latencyMs)}ms` : (cls === "running" ? tr("trace.inflight") : "—")}
            </span>
            <span className="state">
              {cls === "completed" && tr("trace.pass")}
              {cls === "failed" && tr("trace.fail")}
              {cls === "running" && tr("trace.running")}
              {cls === "pending" && tr("trace.pending")}
            </span>
          </div>
        );
      })}
    </div>
  );
}

/* ───── Complete: verdict + KPIs + comparison + attention cards ───── */
function ComparePane({ runState, tweaks, onReset, onShowLeaderboard }) {
  // Card 12: real relay + server-baseline runs come through runState (fed by
  // window.byokicuBridge), replacing the prototype's static RELAY_RUN globals.
  const relay = runState.relayRun;
  const baseline = runState.baselineRun;
  if (!relay || !baseline) return null;

  const baselineAvailable = !baseline.unavailable && baseline.status !== "unavailable";
  const runId = (runState.target && runState.target.runId) || relay.runId || "";

  const comparisons = buildCaseComparisons(relay, baseline);
  const notables = buildNotables(comparisons, relay, baseline);
  // Card 21: a non-recoverable error (4xx/5xx/timeout) means the evaluation did
  // not finish — the verdict is `incompleted`, never trusted/pass, and no
  // (misleading) numeric trust score is shown.
  const incompleted = !!runState.fatalError;
  const verdict = incompleted
    ? { key: "incompleted", stamp: "INCOMPLETED", headline: "Evaluation did not complete — results below are partial." }
    : getOverallVerdict(comparisons);

  const intensityClass =
    tweaks?.collisionIntensity === "brutal" ? "brutal" :
    tweaks?.collisionIntensity === "subtle" ? "" :
    "sharp";

  // Card 13: latency / input / output are now the first rows of the comparison
  // table (the four KPI boxes were removed), sharing the collision animation.
  const metricRows = buildMetricRows(relay, baseline, baselineAvailable);
  const tableRows = [
    ...metricRows,
    ...comparisons.map((c) => ({
      ...c,
      kind: "case",
      tone: c.verdict === "bad" ? "bad" : c.verdict === "attn" ? "attn" : "good",
      // Card 14: fail = 0; pass scored by latency deviation (full if no baseline). Display-only
      // now — the trust score is computed by computeTrustScore below, not summed from rows.
      score: c.relay.status === "failed" ? 0 : (baselineAvailable ? deviationScore(c.latPct) : 10)
    }))
  ];

  // byok.icu score v2 (2026-06-09): correctness 49 (7 cases × 7) + per-case speed 21 (× up to 3)
  // + run-level performance 30 (the 3 metric rows). LOCKSTEP with console computeTrustScore
  // (byokicu/src/evalTarget/leaderboardStore.ts).
  const totalScore = computeTrustScore(metricRows, comparisons, baselineAvailable);
  const passCount = comparisons.filter((c) => c.relay.status === "completed").length;
  const baselinePass = comparisons.filter((c) => c.baseline.status === "completed").length;
  // Card 19: which baseline the server actually resolved (name / date / match).
  const baselineInfo = baselineOriginInfo(baseline, baselineAvailable);

  return (
    <div className="command-pane" style={{ gap: 20 }}>
      {/* Card 21: lightweight results-page nav — back to Run, or jump to the
          home Leader Board. Keeps the SPA state; no reload. */}
      <nav className="results-nav" aria-label="Results navigation">
        <button type="button" className="results-nav-link" onClick={onReset}>{tr("results.nav.run")}</button>
        <button type="button" className="results-nav-link" onClick={onShowLeaderboard}>{tr("results.nav.lb")}</button>
      </nav>
      {runState.fatalError && (
        <div
          role="alert"
          style={{
            padding: "12px 16px",
            borderRadius: 8,
            background: "rgba(255,133,151,0.12)",
            color: "var(--bad, #ff8597)",
            border: "1px solid var(--bad, #ff8597)",
            fontFamily: "var(--font-mono, monospace)",
            fontSize: "0.85rem"
          }}
        >
          {tr("fatal.stopped", { err: runState.fatalError })}
        </div>
      )}
      <div className={`verdict-banner ${verdict.key}`}>
        <div className="left">
          <h3>{tr("verdict.title")}</h3>
          <div className="headline">
            {tr("verdict.relayPrefix")} <span className="stamp">{tr("verdict.stamp." + verdict.key)}</span>
          </div>
          <p className="why">{tr("verdict.headline." + verdict.key)}</p>
          {incompleted ? (
            <div className="trust-score incompleted" aria-label="trust score">
              <strong>—</strong>
              <span>{tr("verdict.incompleted.label")}</span>
            </div>
          ) : (
            <div className="trust-score" aria-label="trust score">
              <strong>{totalScore}</strong>
              <span>{tr("verdict.trustScoreSuffix")}</span>
            </div>
          )}
        </div>
        <div className="actions">
          <button className="btn-ghost" onClick={() => copyRunId(runId)}>⎘ {tr("action.copy")}</button>
          <button className="btn-ghost" onClick={onReset}>↻ {tr("action.rerun")}</button>
          <button className="btn-ghost" onClick={() => shareRun({ runId, score: incompleted ? null : totalScore, verdict, relay, baseline, baselineAvailable, baselineInfo, passCount, baselinePass, total: comparisons.length, relayModel: (runState.target && runState.target.model) || "", relayHost: (runState.target && runState.target.apiEndpointHost) || "" })}>↗ {tr("action.share")}</button>
          <button className="btn-ghost" onClick={() => window.__byokicu_replayImpact?.()}>💥 {tr("action.duang")}</button>
        </div>
      </div>

      {baselineInfo && (
        <div className={`baseline-origin ${baselineInfo.approx ? "approx" : ""}`}>
          <span className="bo-label">{tr("bo.label")}</span>
          <b>{baselineInfo.identity}</b>
          {baselineInfo.tested ? <span className="bo-date">· {tr("bo.tested", { date: baselineInfo.tested })}</span> : null}
          {baselineInfo.label ? <span className="bo-match">· {baselineInfo.label}{baselineInfo.approx ? " ⚠" : ""}</span> : null}
        </div>
      )}

      <div className="deck-section-title">
        <h3>{tr("sec.sxs.title")}</h3>
        <span className="meta">{tr("sec.sxs.meta")}</span>
      </div>

      <CompareTable rows={tableRows} intensityClass={intensityClass} />

      <div className="deck-section-title">
        <h3>{tr("sec.attn.title")}</h3>
        <span className="meta">{tr("sec.attn.meta")}</span>
      </div>

      <div className="attention">
        {notables.map((n, i) => <NoteCard key={i} n={n} />)}
        <BoundaryNote relay={relay} baseline={baseline} />
      </div>
    </div>
  );
}

/* Card 14: 0-10 score from deviation magnitude (0% → 10, ≥100% → 0). Still used for the
   comparison-table metric/case row deltas (display only — no longer feeds the trust score). */
function deviationScore(pct) {
  return Math.round(10 * Math.max(0, 1 - Math.abs(pct) / 100));
}

/* byok.icu TRUST SCORE v2 (2026-06-09): 100 pts = correctness 49 + per-case speed 21 + perf 30.
   - CORRECTNESS (49): each COMPLETED case earns 7 (failed/missing → 0).
   - PER-CASE SPEED (21): each COMPLETED case earns up to 3, scaled by symmetric latency deviation
     (|pct|) from the baseline — faster OR slower both lose points (matching the official model is
     the trust signal). No baseline → full 3.
   - RUN-LEVEL PERFORMANCE (30): the 3 metric rows' deviationScore totals (durationMs / input /
     output), reused as-is from buildMetricRows.
   LOCKSTEP with the console computeTrustScore (byokicu/src/evalTarget/leaderboardStore.ts). */
const CASE_CORRECTNESS = 7; // 7 × 7 = 49
const CASE_SPEED_MAX = 3;   // 7 × 3 = 21
function speedBonus(pct) {
  return CASE_SPEED_MAX * Math.max(0, 1 - Math.abs(pct) / 100);
}
function computeTrustScore(metricRows, comparisons, baselineAvailable) {
  let total = 0;
  for (const m of metricRows || []) total += m.score || 0; // run-level performance (30)
  for (const c of comparisons || []) {
    if (!c || !c.relay || c.relay.status !== "completed") continue; // failed/missing case → 0
    total += CASE_CORRECTNESS + (baselineAvailable ? speedBonus(typeof c.latPct === "number" ? c.latPct : 0) : CASE_SPEED_MAX);
  }
  return Math.round(total);
}

/* Card 19: label the baseline match. Tiers 3/4 return a *different* model than
 * the user ran (real numbers, approximate comparison) — flag those as 近似 so a
 * fallback isn't read as an exact baseline. */
// Card 92-10: was hardcoded Chinese (showed Chinese even in the English UI); now keyed
// so each label resolves to the active language at render time.
const BASELINE_MATCH_LABELS = {
  exact: { key: "match.exact", approx: false },
  model_exact: { key: "match.model_exact", approx: false },
  model_prefix: { key: "match.model_prefix", approx: true },
  provider_latest: { key: "match.provider_latest", approx: true },
  // byok.icu: universal fallback when no same-provider baseline exists — compared vs OpenAI's latest.
  openai_latest: { key: "match.openai_latest", approx: true }
};

function fmtBaselineDate(iso) {
  const m = String(iso || "").match(/^(\d{4})-(\d{2})-(\d{2})/);
  return m ? `${m[1]}-${m[2]}-${m[3]}` : "";
}

/* Build the baseline-origin descriptor (model name, tested date, match label)
 * shown above the comparison table and on the share card. */
function baselineOriginInfo(baseline, baselineAvailable) {
  if (!baseline || !baselineAvailable) return null;
  const t = baseline.target || {};
  const identity = [t.providerId, t.model].filter(Boolean).join(" / ") || "server baseline";
  const meta = BASELINE_MATCH_LABELS[baseline.matchType] || null;
  return {
    identity,
    tested: fmtBaselineDate(baseline.testedAt || baseline.storedAt),
    label: meta ? tr(meta.key) : "",
    approx: meta ? meta.approx : false
  };
}

/* Card 13: the comparison table leads with latency/input/output metric rows,
 * then the 7 case rows. Both kinds share the slam/collision animation. */
function buildMetricRows(relay, baseline, baselineAvailable) {
  const sign = (n) => (n > 0 ? "+" : "");
  const ru = relay.usage || {};
  const bu = baseline.usage || {};
  const metric = (id, label, r, b, opts) => {
    const rv = r || 0;
    const bv = b || 0;
    const d = rv - bv;
    const pct = bv ? (d / bv) * 100 : 0;
    const fmt = opts.time ? (v) => `${(v / 1000).toFixed(2)}s` : fmtTokens;
    const dTxt = opts.time ? `${sign(d)}${(d / 1000).toFixed(2)}s` : `${sign(d)}${d}`;
    const over = Math.abs(pct) > opts.thresh;
    const tone = over ? "attn" : "good";
    return {
      id, kind: "metric", label,
      relayText: fmt(rv),
      baselineText: baselineAvailable ? fmt(bv) : "—",
      // Card 14: token value turns red past the same threshold as the %.
      relayHot: !opts.time && baselineAvailable && over,
      delta: baselineAvailable
        ? { txt: `${dTxt} · ${sign(pct)}${pct.toFixed(opts.time ? 0 : 1)}%`, tone }
        : { txt: "—", tone: "good" },
      tone: baselineAvailable ? tone : "good",
      // Card 14: 0-10 deviation score (full when there's no baseline).
      score: baselineAvailable ? deviationScore(pct) : 10
    };
  };
  return [
    metric("__metric_latency", tr("metric.latency"), ru.durationMs, bu.durationMs, { time: true, thresh: 20 }),
    metric("__metric_input", tr("metric.input"), ru.inputTokens, bu.inputTokens, { thresh: 8 }),
    metric("__metric_output", tr("metric.output"), ru.outputTokens, bu.outputTokens, { thresh: 8 })
  ];
}

/* The comparison table — each row assembles with row-impact.
 * We drive cell position via React state (not just CSS keyframes) so the
 * animation survives any environment that freezes CSS animation timelines.
 */
function CompareTable({ rows, intensityClass }) {
  // For each row, track a slam progress 0..1 and an impact-flag epoch.
  const [progress, setProgress] = useStateCmd(() => new Map());
  const [impacted, setImpacted] = useStateCmd(() => new Set());
  const [keyEpoch, setKeyEpoch] = useStateCmd(0);

  useEffectCmd(() => {
    let timers = [];

    const animateRow = (id) => {
      const start = performance.now();
      const DUR = 340;
      const tick = () => {
        const t = performance.now() - start;
        const frac = Math.min(1, t / DUR);
        const eased = easeOutBack(frac);
        setProgress((prev) => {
          const next = new Map(prev);
          next.set(id, eased);
          return next;
        });
        if (frac >= 1) {
          // Mark impacted so the row-shake + shock-line CSS animation triggers
          setImpacted((prev) => {
            const next = new Set(prev);
            next.add(id);
            return next;
          });
          return;
        }
        timers.push(setTimeout(tick, 16));
      };
      tick();
    };

    const reveal = () => {
      timers.forEach(clearTimeout);
      timers = [];
      setProgress(new Map());
      setImpacted(new Set());
      setKeyEpoch((e) => e + 1);
      rows.forEach((r, i) => {
        timers.push(setTimeout(() => animateRow(r.id), 80 + i * 110));
      });
    };

    reveal();
    window.__byokicu_replayImpact = reveal;
    return () => {
      timers.forEach(clearTimeout);
      delete window.__byokicu_replayImpact;
    };
    // eslint-disable-next-line
  }, []);

  return (
    <div className="compare">
      <div className="compare-head">
        <div>{tr("ct.case")}</div>
        <div className="relay-col">{tr("ct.relay")}</div>
        <div className="baseline-col">{tr("ct.baseline")}</div>
        <div style={{ justifyContent: "flex-end" }}>Δ</div>
      </div>
      <div className="compare-body">
        {rows.map((row) => (
          <CompareRow
            key={`${keyEpoch}-${row.id}`}
            row={row}
            progress={progress.get(row.id) ?? 0}
            impacted={impacted.has(row.id)}
            intensityClass={intensityClass}
          />
        ))}
      </div>
    </div>
  );
}

function easeOutBack(t) {
  // overshoot then settle
  const c1 = 1.7;
  const c3 = c1 + 1;
  return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
}

function CompareRow({ row, progress, impacted, intensityClass }) {
  const tone = row.tone || "good";
  const cls = [
    "compare-row",
    tone,
    impacted ? `impacted ${intensityClass}` : "",
    impacted ? "shock-on" : ""
  ].filter(Boolean).join(" ");

  // Cells slam in via inline transform. Easing already applied to progress.
  const relayStyle = {
    transform: `translateX(${(progress - 1) * 100}%)`,
    opacity: Math.min(1, progress * 1.4)
  };
  const baselineStyle = {
    transform: `translateX(${(1 - progress) * 100}%)`,
    opacity: Math.min(1, progress * 1.4)
  };
  const caseStyle = { opacity: Math.min(1, progress * 1.6) };
  const deltaStyle = {
    transform: `scale(${0.5 + progress * 0.5})`,
    opacity: Math.min(1, Math.max(0, progress - 0.3) * 1.5)
  };

  // Card 13: metric rows (latency / input / output) reuse the same grid + slam.
  if (row.kind === "metric") {
    return (
      <div className={cls}>
        <div className="case-cell" style={caseStyle}>
          <span className="case-name">{row.label}</span>
        </div>
        <div className="relay-cell" style={relayStyle}>
          <span className={row.relayHot ? "ms hot" : "ms"}>{row.relayText}</span>
        </div>
        <div className="baseline-cell" style={baselineStyle}>
          <span className="ms">{row.baselineText}</span>
        </div>
        <div className="delta-cell" style={deltaStyle}>{row.delta.txt}</div>
        <div className="shock" />
      </div>
    );
  }

  const c = row;
  const relayChip =
    c.relay.status === "completed" ? <span className="chip pass"><span className="d" />{tr("trace.pass")}</span>
    : <span className="chip fail"><span className="d" />{tr("trace.fail")}</span>;
  const baseChip =
    c.baseline.status === "completed" ? <span className="chip pass"><span className="d" />{tr("trace.pass")}</span>
    : <span className="chip fail"><span className="d" />{statusLabel(c.baseline.status)}</span>;

  const arrow = c.latPct > 0 ? "▲" : c.latPct < 0 ? "▼" : "·";

  return (
    <div className={cls}>
      <div className="case-cell" style={caseStyle}>
        <span className="case-name">{tr("case." + c.id + ".label")}</span>
        <span className="case-note">{c.note}</span>
      </div>
      <div className="relay-cell" style={relayStyle}>
        {relayChip}
        <span className="ms">{Math.round(c.relay.latencyMs)}ms</span>
      </div>
      <div className="baseline-cell" style={baselineStyle}>
        {baseChip}
        <span className="ms">{Math.round(c.baseline.latencyMs)}ms</span>
      </div>
      <div className="delta-cell" style={deltaStyle}>
        {arrow} {Math.abs(c.latPct).toFixed(0)}%
      </div>
      <div className="shock" />
    </div>
  );
}

/* Card 13: verdict-banner action handlers. Browser-only APIs; guarded. */
function copyRunId(runId) {
  if (!runId) return;
  try {
    if (navigator.clipboard && navigator.clipboard.writeText) {
      navigator.clipboard.writeText(String(runId));
    }
  } catch (e) { /* ignore */ }
}

/* Card 14: Share renders a 640×360 summary card and shares it as a PNG file
 * (navigator.share with files where supported, else downloads). Browser-only;
 * all DOM/canvas access is guarded. `data` carries the run summary. */
function shareRun(data) {
  data = data || {};
  let canvas;
  try { canvas = document.createElement("canvas"); } catch (e) { return; }
  canvas.width = 640;
  canvas.height = 360;
  const ctx = canvas.getContext ? canvas.getContext("2d") : null;
  if (!ctx) return;
  try { drawShareCard(ctx, data); } catch (e) { /* ignore draw errors */ }

  const text = data.runId
    ? `byok.icu run ${data.runId} · trust score ${data.score}/100`
    : "byok.icu relay API test";

  const finish = (blob) => {
    try {
      if (blob && navigator.canShare && navigator.share) {
        const file = new File([blob], "byok-icu-run.png", { type: "image/png" });
        if (navigator.canShare({ files: [file] })) {
          navigator.share({ files: [file], title: "byok.icu · relay API test", text }).catch(() => downloadCanvas(canvas));
          return;
        }
      }
    } catch (e) { /* fall through to download */ }
    downloadCanvas(canvas);
  };

  try {
    if (canvas.toBlob) canvas.toBlob(finish, "image/png");
    else finish(null);
  } catch (e) {
    downloadCanvas(canvas);
  }
}

function downloadCanvas(canvas) {
  try {
    const a = document.createElement("a");
    a.href = canvas.toDataURL("image/png");
    a.download = "byok-icu-run.png";
    if (document.body) document.body.appendChild(a);
    a.click();
    if (a.remove) a.remove();
  } catch (e) { /* ignore */ }
}

function clipText(s, n) {
  s = String(s == null ? "" : s);
  return s.length > n ? `${s.slice(0, n - 1)}…` : s;
}

function drawShareCard(ctx, d) {
  const W = 640, H = 360;
  const ru = (d.relay && d.relay.usage) || {};
  const bu = (d.baseline && d.baseline.usage) || {};
  const verdict = d.verdict || { key: "ok" };
  const vkey = verdict.key || "ok";
  const stamp = vkey === "ok" ? "#5af28b" : vkey === "bad" ? "#ff5d6c" : vkey === "incompleted" ? "#ff8597" : "#ffae3a";
  const ms = (v) => (Number.isFinite(v) ? `${(v / 1000).toFixed(2)}s` : "—");
  const tk = (v) => (Number.isFinite(v) ? (v >= 1000 ? `${(v / 1000).toFixed(1)}k` : String(v)) : "—");

  ctx.fillStyle = "#07090c";
  ctx.fillRect(0, 0, W, H);
  ctx.strokeStyle = "rgba(90,242,139,0.35)";
  ctx.lineWidth = 2;
  ctx.strokeRect(8, 8, W - 16, H - 16);

  ctx.textBaseline = "alphabetic";
  ctx.textAlign = "left";

  // Brand (slogan removed until we have a better one).
  ctx.fillStyle = "#5af28b";
  ctx.font = "700 26px 'Space Grotesk', system-ui, sans-serif";
  ctx.fillText("byok.icu", 34, 54);

  // byok.icu: what was tested — relay model (prominent), endpoint host + baseline model beneath.
  const relayModel = d.relayModel || (d.relay && d.relay.model) || "—";
  ctx.fillStyle = "#d7dee6";
  ctx.font = "600 14px 'IBM Plex Mono', monospace";
  ctx.fillText(clipText(relayModel, 42), 34, 76);
  if (d.relayHost) {
    ctx.fillStyle = "#6b7886";
    ctx.font = "500 11px 'IBM Plex Mono', monospace";
    ctx.fillText(`@ ${clipText(d.relayHost, 44)}`, 34, 92);
  }
  const bi = d.baselineInfo;
  const baselineModel = d.baselineAvailable && bi && bi.identity ? bi.identity : "—";
  ctx.fillStyle = "#6b7886";
  ctx.font = "500 11px 'IBM Plex Mono', monospace";
  ctx.fillText(`${tr("share.baseline")}: ${clipText(baselineModel, 38)}`, 34, 107);

  // Verdict stamp + headline — localized via key, matching the on-page banner (the verdict
  // object's own stamp/headline are hardcoded English, so resolve from the key instead).
  ctx.fillStyle = stamp;
  ctx.font = "700 32px 'Space Grotesk', system-ui, sans-serif";
  ctx.fillText(tr("verdict.stamp." + vkey), 34, 150);
  ctx.fillStyle = "#b9c2cc";
  ctx.font = "400 14px 'Inter', system-ui, sans-serif";
  wrapText(ctx, tr("verdict.headline." + vkey), 34, 174, 360, 18, 2);

  ctx.textAlign = "right";
  ctx.fillStyle = stamp;
  ctx.font = "700 88px 'IBM Plex Mono', monospace";
  ctx.fillText(d.score == null ? "—" : String(d.score), W - 34, 150);
  ctx.fillStyle = "#6b7886";
  ctx.font = "500 13px 'IBM Plex Mono', monospace";
  ctx.fillText(tr("share.trustScore"), W - 34, 174);
  ctx.textAlign = "left";

  const total = d.total || 0;
  const rows = [
    [tr("share.latency"), ms(ru.durationMs), d.baselineAvailable ? ms(bu.durationMs) : "—"],
    [tr("share.outputTokens"), tk(ru.outputTokens), d.baselineAvailable ? tk(bu.outputTokens) : "—"],
    [tr("share.inputTokens"), tk(ru.inputTokens), d.baselineAvailable ? tk(bu.inputTokens) : "—"],
    [tr("share.casesPassed"), `${d.passCount || 0} / ${total}`, d.baselineAvailable ? `${d.baselinePass || 0} / ${total}` : "—"]
  ];
  const y0 = 244;
  ctx.font = "600 11px 'IBM Plex Mono', monospace";
  ctx.fillStyle = "#6b7886";
  ctx.fillText(tr("share.metric"), 34, y0);
  ctx.fillText(tr("share.yourRelay"), 360, y0);
  ctx.fillText(tr("share.baselineCol"), 510, y0);
  ctx.strokeStyle = "rgba(255,255,255,0.08)";
  ctx.beginPath();
  ctx.moveTo(34, y0 + 8);
  ctx.lineTo(W - 34, y0 + 8);
  ctx.stroke();
  ctx.font = "500 13px 'IBM Plex Mono', monospace";
  rows.forEach((r, i) => {
    const y = y0 + 30 + i * 21;
    ctx.fillStyle = "#b9c2cc"; ctx.fillText(r[0], 34, y);
    ctx.fillStyle = "#6dd5ff"; ctx.fillText(r[1], 360, y);
    ctx.fillStyle = "#80fcaa"; ctx.fillText(r[2], 510, y);
  });
  // byok.icu: removed the overlapping bottom run-id + baseline footer lines.
}

function wrapText(ctx, text, x, y, maxWidth, lineHeight, maxLines) {
  const words = String(text || "").split(/\s+/).filter(Boolean);
  const lines = [];
  let line = "";
  for (let i = 0; i < words.length; i++) {
    const test = line ? line + " " + words[i] : words[i];
    if (ctx.measureText(test).width > maxWidth && line) { lines.push(line); line = words[i]; }
    else line = test;
  }
  if (line) lines.push(line);
  const max = maxLines || lines.length;
  for (let i = 0; i < Math.min(lines.length, max); i++) {
    let ln = lines[i];
    if (i === max - 1 && lines.length > max) ln = ln.replace(/\s*\S*$/, "") + "…";
    ctx.fillText(ln, x, y + i * lineHeight);
  }
}

function Kpi({ label, val, baseline, delta }) {
  return (
    <div className="kpi">
      <div className="label">{label}</div>
      <div className="val">{val}</div>
      <div className="baseline">{baseline}</div>
      <div className={`delta ${delta.tone}`}>{delta.txt}</div>
    </div>
  );
}

function NoteCard({ n }) {
  return (
    <div className={`note-card ${n.severity}`}>
      <div className="lbl">
        <span>{n.label}</span>
        <b>{tr(n.severity === "bad" ? "note.badge.fail" : n.severity === "warn" ? "note.badge.attn" : "note.badge.ok")}</b>
      </div>
      <h4>{n.title}</h4>
      {n.body}
    </div>
  );
}

/* Card 12: the prototype hardcoded a "boundary matched" card. For a real trust
 * tool that claim must come from the run's actual apiBoundarySummary, so this
 * is data-driven from relay.boundary (compared to baseline when available). */
function BoundaryNote({ relay, baseline }) {
  const rb = (relay && relay.boundary) || {};
  const bb = (baseline && baseline.boundary) || {};
  const baselineAvailable = baseline && !baseline.unavailable && baseline.status !== "unavailable";
  const legal = rb.producedLegalToolCall;
  const finish = rb.finishReason;
  const finishMatch = !baselineAvailable || !bb.finishReason || bb.finishReason === finish;
  const ok = legal !== false && finishMatch;
  const finishLabel = finish || "n/a";
  const legalLabel =
    legal === true ? tr("bn.legal.true")
    : legal === false ? tr("bn.legal.false")
    : tr("bn.legal.unknown");
  const baselineNote = baselineAvailable && bb.finishReason ? tr("bn.baselineNote", { reason: bb.finishReason }) : "";
  return (
    <div className={`note-card ${ok ? "good" : "warn"}`}>
      <div className="lbl"><span>{tr("bn.label")}</span><b>{ok ? tr("bn.matched") : tr("bn.attn")}</b></div>
      <h4>{ok ? tr("bn.title.ok") : tr("bn.title.bad")}</h4>
      <p>
        {tr("bn.body.pre")}<code>finish_reason: {finishLabel}</code>{tr("bn.body.and")}{legalLabel}{baselineNote}{ok ? tr("bn.tail.ok") : tr("bn.tail.bad")}
      </p>
    </div>
  );
}

function fmtTokens(n) {
  if (!Number.isFinite(n)) return "—";
  if (n >= 1000) return `${(n / 1000).toFixed(1)}k`;
  return String(n);
}

/* Card 92-10: a non-completed baseline shows its raw status (failed/unavailable/timeout/…).
   Map the known ones; fall back to the raw token for anything unmapped. */
function statusLabel(s) {
  const t = tr("status." + s);
  return t === "status." + s ? s : t;
}

Object.assign(window, { CommandDeck });
