<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>AIR Blackbox — Episode Replay</title>
<style>
  :root {
    --bg: #0b0e14; --panel: #11151f; --border: #1f2533;
    --text: #d7dde8; --dim: #7d8799; --accent: #4cc38a;
    --warn: #e5484d; --info: #5b9dd9; --mono: ui-monospace, SFMono-Regular, Menlo, monospace;
  }
  * { box-sizing: border-box; margin: 0; }
  body { background: var(--bg); color: var(--text); font: 14px/1.5 -apple-system, "Segoe UI", sans-serif; height: 100vh; display: flex; flex-direction: column; }
  header { display: flex; align-items: center; gap: 14px; padding: 12px 18px; border-bottom: 1px solid var(--border); }
  header h1 { font-size: 15px; font-weight: 600; letter-spacing: .03em; }
  header .sub { color: var(--dim); font-size: 12px; }
  #chainBadge { margin-left: auto; font: 12px var(--mono); padding: 4px 10px; border-radius: 4px; border: 1px solid var(--border); }
  .chain-valid { color: var(--accent); border-color: var(--accent) !important; }
  .chain-broken { color: var(--warn); border-color: var(--warn) !important; }
  .chain-na { color: var(--dim); }
  main { flex: 1; display: flex; min-height: 0; }
  #list { width: 380px; border-right: 1px solid var(--border); overflow-y: auto; }
  .run { padding: 10px 14px; border-bottom: 1px solid var(--border); cursor: pointer; }
  .run:hover, .run.active { background: var(--panel); }
  .run .top { display: flex; justify-content: space-between; font: 12px var(--mono); }
  .run .model { color: var(--info); }
  .run .meta { color: var(--dim); font-size: 12px; margin-top: 2px; }
  .status-success { color: var(--accent); }
  .status-error { color: var(--warn); }
  #detail { flex: 1; overflow-y: auto; padding: 22px 26px; }
  #detail .empty { color: var(--dim); margin-top: 40px; text-align: center; }
  .card { background: var(--panel); border: 1px solid var(--border); border-radius: 6px; padding: 14px 16px; margin-bottom: 14px; }
  .card h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .08em; color: var(--dim); margin-bottom: 10px; }
  .kv { display: grid; grid-template-columns: 180px 1fr; gap: 6px 14px; font: 13px var(--mono); }
  .kv .k { color: var(--dim); }
  .kv .v { word-break: break-all; }
  .timeline { font: 13px var(--mono); }
  .step { display: flex; gap: 12px; padding: 6px 0; }
  .step .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); margin-top: 6px; flex: none; }
  .step.err .dot { background: var(--warn); }
  .hint { color: var(--dim); font: 12px var(--mono); margin-top: 6px; }
  code { font: 12px var(--mono); background: var(--bg); padding: 2px 6px; border-radius: 3px; }
</style>
</head>
<body>
<header>
  <h1>AIR BLACKBOX</h1>
  <span class="sub">Episode Replay</span>
  
  <span id="chainBadge" class="chain-na">CHAIN: checking…</span>
</header>
<main>
  <div id="list"></div>
  <div id="detail"><div class="empty">Select a run to inspect it.</div></div>
</main>
<script>
const listEl = document.getElementById('list');
const detailEl = document.getElementById('detail');
const badgeEl = document.getElementById('chainBadge');
let activeId = null;

function fmtTime(ts) {
  try { return new Date(ts).toLocaleTimeString(); } catch { return ts; }
}
function fmtMs(ms) {
  return (ms === null || ms === undefined) ? '\u2014' : ms + ' ms';
}

function setChainBadge(chain) {
  if (!chain || !chain.available) {
    badgeEl.className = 'chain-na';
    badgeEl.textContent = 'CHAIN: unavailable';
  } else if (chain.chain_valid) {
    badgeEl.className = 'chain-valid';
    badgeEl.textContent = `CHAIN: VERIFIED (${chain.chain_length} entries)`;
  } else {
    badgeEl.className = 'chain-broken';
    badgeEl.textContent = `CHAIN: BROKEN at entry ${chain.chain_broken_at}`;
  }
}

async function loadRuns() {
  try {
    const r = await fetch('/api/runs?limit=200');
    const data = await r.json();
    setChainBadge(data.chain);
    listEl.innerHTML = '';
    if (!data.runs.length) {
      listEl.innerHTML = '<div class="run"><div class="meta">No runs recorded yet. Send traffic through the gateway.</div></div>';
      return;
    }
    for (const run of data.runs) {
      const div = document.createElement('div');
      div.className = 'run' + (run.run_id === activeId ? ' active' : '');
      div.innerHTML = `
        <div class="top">
          <span class="model">${run.model || '\u2014'}</span>
          <span class="status-${run.status}">${run.status}</span>
        </div>
        <div class="meta">${fmtTime(run.timestamp)} · ${run.tokens?.total ?? 0} tok · ${fmtMs(run.duration_ms)} · ${run.provider || ''}</div>`;
      div.onclick = () => showRun(run.run_id);
      listEl.appendChild(div);
    }
  } catch (e) {
    listEl.innerHTML = '<div class="run"><div class="meta">Could not load runs: ' + e + '</div></div>';
  }
}

async function showRun(id) {
  activeId = id;
  document.querySelectorAll('.run').forEach(el => el.classList.remove('active'));
  const r = await fetch('/api/run/' + encodeURIComponent(id));
  if (!r.ok) { detailEl.innerHTML = '<div class="empty">Run not found.</div>'; return; }
  const { run, chain } = await r.json();
  setChainBadge(chain);

  const steps = [
    { label: `request received → ${run.endpoint}`, ok: true },
    { label: `routed to provider: ${run.provider} (model ${run.model})`, ok: true },
    { label: `request vaulted: ${run.request_vault_ref || 'vault disabled'}`, ok: !!run.request_vault_ref },
    { label: `response received: ${run.tokens?.total ?? 0} tokens in ${fmtMs(run.duration_ms)}`, ok: run.status === 'success' },
    { label: `response vaulted: ${run.response_vault_ref || 'vault disabled'}`, ok: !!run.response_vault_ref },
    { label: `AIR record written + audit chain appended`, ok: true },
  ];

  detailEl.innerHTML = `
    <div class="card">
      <h2>Run ${run.run_id}</h2>
      <div class="kv">
        <span class="k">timestamp</span><span class="v">${run.timestamp}</span>
        <span class="k">trace_id</span><span class="v">${run.trace_id || ''}</span>
        <span class="k">status</span><span class="v status-${run.status}">${run.status}${run.error ? ' — ' + run.error : ''}</span>
        <span class="k">model</span><span class="v">${run.model}</span>
        <span class="k">provider</span><span class="v">${run.provider}</span>
        <span class="k">tokens (in/out/total)</span><span class="v">${run.tokens?.prompt ?? 0} / ${run.tokens?.completion ?? 0} / ${run.tokens?.total ?? 0}</span>
        <span class="k">duration</span><span class="v">${fmtMs(run.duration_ms)}</span>
      </div>
    </div>
    <div class="card">
      <h2>Episode timeline</h2>
      <div class="timeline">
        ${steps.map(s => `<div class="step${s.ok ? '' : ' err'}"><div class="dot"></div><div>${s.label}</div></div>`).join('')}
      </div>
    </div>
    <div class="card">
      <h2>Integrity</h2>
      <div class="kv">
        <span class="k">request_checksum</span><span class="v">${run.request_checksum || ''}</span>
        <span class="k">response_checksum</span><span class="v">${run.response_checksum || ''}</span>
        <span class="k">request_vault_ref</span><span class="v">${run.request_vault_ref || 'n/a'}</span>
        <span class="k">response_vault_ref</span><span class="v">${run.response_vault_ref || 'n/a'}</span>
      </div>
      <div class="hint">Replay this run and diff behavior: <code>replayctl ${run.run_id}</code></div>
    </div>`;
  loadRuns();
}

loadRuns();
setInterval(loadRuns, 10000);
</script>
</body>
</html>
