/* global React */
const { useState, useEffect, useCallback } = React;

/* =========================================================================
   Admin panel — operator views over real data. Visible only when the
   signed-in user's tier === 'admin' (also enforced server-side).
   Tabs: Users · Analyses · Usage · Health
   ========================================================================= */

function AdminScreen() {
  const app = window.useApp();
  const [tab, setTab] = useState('users');

  if (app.tier !== 'admin') {
    return (
      <div style={{ padding: 'var(--sp-6)', maxWidth: 720, margin: '0 auto' }}>
        <div className="card">
          <div className="card-body" style={{ textAlign: 'center' }}>
            <div style={{ fontSize: 14, fontWeight: 600, marginBottom: 8 }}>Admins only</div>
            <div style={{ color: 'var(--ink-2)', fontSize: 13 }}>
              Your account does not have admin privileges.
            </div>
          </div>
        </div>
      </div>
    );
  }

  const tabs = [
    { id: 'users',    label: 'Users' },
    { id: 'analyses', label: 'Analyses' },
    { id: 'usage',    label: 'Usage' },
    { id: 'health',   label: 'Health' },
    { id: 'quality',  label: 'Diagram quality' },
  ];

  return (
    <div style={{ padding: 'var(--sp-5)', maxWidth: 1280, margin: '0 auto', display: 'flex', flexDirection: 'column', gap: 'var(--sp-4)' }}>
      <div>
        <div style={{ fontSize: 20, fontWeight: 600, letterSpacing: '-0.01em' }}>Admin</div>
        <div style={{ color: 'var(--ink-2)', fontSize: 13, marginTop: 2 }}>
          Operator views over live data. All actions are tier-gated server-side.
        </div>
      </div>
      <div className="tabs">
        {tabs.map((t) => (
          <button key={t.id} className={`tab ${tab === t.id ? 'active' : ''}`} onClick={() => setTab(t.id)}>
            {t.label}
          </button>
        ))}
      </div>
      {tab === 'users'    && <AdminUsersTab/>}
      {tab === 'analyses' && <AdminAnalysesTab/>}
      {tab === 'usage'    && <AdminUsageTab/>}
      {tab === 'health'   && <AdminHealthTab/>}
      {tab === 'quality'  && <AdminDiagramQualityTab/>}
    </div>
  );
}

function useAsync(fn, deps) {
  const [state, setState] = useState({ data: null, error: null, loading: true });
  const run = useCallback(() => {
    setState((s) => ({ ...s, loading: true, error: null }));
    fn().then(
      (data) => setState({ data, error: null, loading: false }),
      (err) => setState({ data: null, error: err, loading: false }),
    );
  }, deps); // eslint-disable-line react-hooks/exhaustive-deps
  useEffect(() => { run(); }, [run]);
  return { ...state, refresh: run };
}

function ErrBox({ error, onRetry }) {
  if (!error) return null;
  return (
    <div className="card" style={{ borderColor: 'var(--err)' }}>
      <div className="card-body" style={{ display: 'flex', justifyContent: 'space-between', gap: 'var(--sp-3)', alignItems: 'center' }}>
        <div style={{ color: 'var(--err)', fontSize: 13 }}>{String(error.message || error)}</div>
        {onRetry && <button className="btn sm" onClick={onRetry}>Retry</button>}
      </div>
    </div>
  );
}

function Loading({ label = 'Loading…' }) {
  return (
    <div style={{ padding: 'var(--sp-5)', display: 'flex', alignItems: 'center', gap: 8, color: 'var(--ink-2)', fontSize: 13 }}>
      <span className="spinner" style={{ width: 14, height: 14 }}/>
      {label}
    </div>
  );
}

/* ── Users tab ──────────────────────────────────────────────────────────── */
const TIER_OPTIONS = ['free', 'pro', 'team', 'enterprise', 'admin'];

function AdminUsersTab() {
  const { data, error, loading, refresh } = useAsync(() => window.API.admin.listUsers(), []);
  const [busyId, setBusyId] = useState(null);

  const setTier = async (u, tier) => {
    if (tier === u.tier) return;
    setBusyId(u.userId);
    try { await window.API.admin.setTier(u.userId, tier); refresh(); }
    finally { setBusyId(null); }
  };

  const deleteUser = async (u) => {
    const ok = window.confirm(
      `Permanently delete ${u.email || u.userId}?\n\n`
      + 'This wipes their DDB rows AND removes them from Cognito. Cannot be undone.',
    );
    if (!ok) return;
    setBusyId(u.userId);
    try { await window.API.admin.deleteUser(u.userId, u.username); refresh(); }
    catch (e) { window.alert(`Delete failed: ${e.message || e}`); }
    finally { setBusyId(null); }
  };

  const disableUser = async (u) => {
    if (!window.confirm(`Disable sign-in for ${u.email || u.userId}?`)) return;
    setBusyId(u.userId);
    try { await window.API.admin.disableUser(u.userId, u.username); refresh(); }
    catch (e) { window.alert(`Disable failed: ${e.message || e}`); }
    finally { setBusyId(null); }
  };

  if (loading) return <Loading label="Loading users…"/>;
  if (error)   return <ErrBox error={error} onRetry={refresh}/>;
  const items = data?.items ?? [];

  return (
    <div className="card">
      <div className="card-header">
        <div>
          <div style={{ fontWeight: 600, fontSize: 13.5 }}>Users</div>
          <div style={{ fontSize: 12, color: 'var(--ink-2)' }}>{items.length} accounts</div>
        </div>
        <button className="btn sm" onClick={refresh}>Refresh</button>
      </div>
      <div style={{ overflowX: 'auto' }}>
        <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12.5 }}>
          <thead>
            <tr style={{ textAlign: 'left', color: 'var(--ink-2)', borderBottom: '1px solid var(--line)' }}>
              <th style={th()}>Email</th>
              <th style={th()}>Name</th>
              <th style={th()}>Tier</th>
              <th style={th()}>Status</th>
              <th style={th()}>Convos</th>
              <th style={th()}>Joined</th>
              <th style={th()}/>
            </tr>
          </thead>
          <tbody>
            {items.map((u) => (
              <tr key={u.userId} style={{ borderBottom: '1px solid var(--line)' }}>
                <td style={td()}>
                  <div style={{ fontFamily: 'var(--font-mono)', fontSize: 12 }}>{u.email || '—'}</div>
                  <div style={{ fontSize: 10.5, color: 'var(--ink-3)', fontFamily: 'var(--font-mono)' }}>{u.userId.slice(0, 8)}…</div>
                </td>
                <td style={td()}>{[u.givenName, u.familyName].filter(Boolean).join(' ') || '—'}</td>
                <td style={td()}>
                  <select
                    value={u.tier}
                    disabled={busyId === u.userId}
                    onChange={(e) => setTier(u, e.target.value)}
                    style={{ fontSize: 12, padding: '2px 4px', background: 'var(--bg-1)', border: '1px solid var(--line)', borderRadius: 3, color: 'var(--ink-0)' }}
                  >
                    {TIER_OPTIONS.map((t) => <option key={t} value={t}>{t}</option>)}
                  </select>
                </td>
                <td style={td()}>
                  <span className={`tier-pill ${u.enabled ? 'pro' : 'free'}`} style={{ fontSize: 10 }}>
                    <span className="dot"/>
                    {u.enabled ? (u.status || 'active') : 'disabled'}
                  </span>
                </td>
                <td style={td()}>{u.conversationCount}</td>
                <td style={{ ...td(), fontFamily: 'var(--font-mono)', fontSize: 11.5, color: 'var(--ink-2)' }}>
                  {u.createdAt ? u.createdAt.slice(0, 10) : '—'}
                </td>
                <td style={td()}>
                  <div style={{ display: 'flex', gap: 4, justifyContent: 'flex-end' }}>
                    {u.enabled && (
                      <button className="btn sm ghost" disabled={busyId === u.userId} onClick={() => disableUser(u)} title="Disable sign-in">
                        Disable
                      </button>
                    )}
                    <button className="btn sm ghost" disabled={busyId === u.userId} onClick={() => deleteUser(u)} style={{ color: 'var(--err)' }}>
                      Delete
                    </button>
                  </div>
                </td>
              </tr>
            ))}
            {items.length === 0 && (
              <tr><td colSpan={7} style={{ ...td(), textAlign: 'center', color: 'var(--ink-3)' }}>No users.</td></tr>
            )}
          </tbody>
        </table>
      </div>
    </div>
  );
}

/* ── Analyses tab ───────────────────────────────────────────────────────── */
function AdminAnalysesTab() {
  const [limit, setLimit] = useState(50);
  const { data, error, loading, refresh } = useAsync(() => window.API.admin.listAnalyses(limit), [limit]);

  if (loading) return <Loading label="Loading analyses…"/>;
  if (error)   return <ErrBox error={error} onRetry={refresh}/>;
  const items = data?.items ?? [];

  return (
    <div className="card">
      <div className="card-header">
        <div>
          <div style={{ fontWeight: 600, fontSize: 13.5 }}>Analyses (cross-user)</div>
          <div style={{ fontSize: 12, color: 'var(--ink-2)' }}>Most recently updated · {items.length} shown</div>
        </div>
        <div style={{ display: 'flex', gap: 6 }}>
          <select value={limit} onChange={(e) => setLimit(Number(e.target.value))}
                  style={{ fontSize: 12, padding: '2px 6px', background: 'var(--bg-1)', border: '1px solid var(--line)', borderRadius: 3, color: 'var(--ink-0)' }}>
            <option value={25}>25</option>
            <option value={50}>50</option>
            <option value={100}>100</option>
          </select>
          <button className="btn sm" onClick={refresh}>Refresh</button>
        </div>
      </div>
      <div style={{ overflowX: 'auto' }}>
        <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12.5 }}>
          <thead>
            <tr style={{ textAlign: 'left', color: 'var(--ink-2)', borderBottom: '1px solid var(--line)' }}>
              <th style={th()}>Title</th>
              <th style={th()}>User</th>
              <th style={th()}>Versions</th>
              <th style={th()}>Created</th>
              <th style={th()}>Updated</th>
              <th style={th()}>ID</th>
            </tr>
          </thead>
          <tbody>
            {items.map((a) => (
              <tr key={`${a.userId}/${a.convoId}`} style={{ borderBottom: '1px solid var(--line)' }}>
                <td style={td()}>{a.title || <span style={{ color: 'var(--ink-3)' }}>untitled</span>}</td>
                <td style={{ ...td(), fontFamily: 'var(--font-mono)', fontSize: 11.5, color: 'var(--ink-2)' }}>{a.userId.slice(0, 8)}…</td>
                <td style={td()}>{a.currentVersion}</td>
                <td style={{ ...td(), fontFamily: 'var(--font-mono)', fontSize: 11.5, color: 'var(--ink-2)' }}>{(a.createdAt || '').slice(0, 16).replace('T', ' ')}</td>
                <td style={{ ...td(), fontFamily: 'var(--font-mono)', fontSize: 11.5, color: 'var(--ink-2)' }}>{(a.updatedAt || '').slice(0, 16).replace('T', ' ')}</td>
                <td style={{ ...td(), fontFamily: 'var(--font-mono)', fontSize: 11.5, color: 'var(--ink-3)' }}>{a.convoId?.slice(0, 10)}…</td>
              </tr>
            ))}
            {items.length === 0 && (
              <tr><td colSpan={6} style={{ ...td(), textAlign: 'center', color: 'var(--ink-3)' }}>No analyses.</td></tr>
            )}
          </tbody>
        </table>
      </div>
    </div>
  );
}

/* ── Usage tab ──────────────────────────────────────────────────────────── */
function AdminUsageTab() {
  const [month, setMonth] = useState(() => new Date().toISOString().slice(0, 7));
  const { data, error, loading, refresh } = useAsync(() => window.API.admin.getUsage(month), [month]);

  if (loading) return <Loading label="Loading usage…"/>;
  if (error)   return <ErrBox error={error} onRetry={refresh}/>;
  const items = data?.items ?? [];

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--sp-4)' }}>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 'var(--sp-3)' }}>
        <Stat label="Active users" value={data?.activeUsers ?? 0}/>
        <Stat label="Total analyses" value={data?.totalAnalyses ?? 0}/>
        <Stat label="Avg / user" value={items.length ? (data.totalAnalyses / items.length).toFixed(1) : '0'}/>
      </div>
      <div className="card">
        <div className="card-header">
          <div>
            <div style={{ fontWeight: 600, fontSize: 13.5 }}>Per-user usage</div>
            <div style={{ fontSize: 12, color: 'var(--ink-2)' }}>Month · {month}</div>
          </div>
          <div style={{ display: 'flex', gap: 6 }}>
            <input type="month" value={month} onChange={(e) => setMonth(e.target.value)}
                   style={{ fontSize: 12, padding: '2px 6px', background: 'var(--bg-1)', border: '1px solid var(--line)', borderRadius: 3, color: 'var(--ink-0)' }}/>
            <button className="btn sm" onClick={refresh}>Refresh</button>
          </div>
        </div>
        <div style={{ overflowX: 'auto' }}>
          <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12.5 }}>
            <thead>
              <tr style={{ textAlign: 'left', color: 'var(--ink-2)', borderBottom: '1px solid var(--line)' }}>
                <th style={th()}>User</th>
                <th style={th()}>Analyses</th>
                <th style={th()}>Last activity</th>
              </tr>
            </thead>
            <tbody>
              {items.map((row) => (
                <tr key={row.userId} style={{ borderBottom: '1px solid var(--line)' }}>
                  <td style={{ ...td(), fontFamily: 'var(--font-mono)', fontSize: 11.5 }}>{row.userId}</td>
                  <td style={td()}>{row.analysisCount}</td>
                  <td style={{ ...td(), fontFamily: 'var(--font-mono)', fontSize: 11.5, color: 'var(--ink-2)' }}>
                    {row.updatedAt ? row.updatedAt.replace('T', ' ').slice(0, 16) : '—'}
                  </td>
                </tr>
              ))}
              {items.length === 0 && (
                <tr><td colSpan={3} style={{ ...td(), textAlign: 'center', color: 'var(--ink-3)' }}>No activity for {month}.</td></tr>
              )}
            </tbody>
          </table>
        </div>
      </div>
    </div>
  );
}

/* ── Health tab ─────────────────────────────────────────────────────────── */
function AdminHealthTab() {
  const { data, error, loading, refresh } = useAsync(() => window.API.admin.getHealth(), []);
  if (loading) return <Loading label="Loading health…"/>;
  if (error)   return <ErrBox error={error} onRetry={refresh}/>;

  const counts = data?.counts ?? { running: 0, completed: 0, failed: 0 };
  const successPct = data?.successRate == null ? '—' : `${(data.successRate * 100).toFixed(1)}%`;
  const failures = data?.recentFailures ?? [];

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--sp-4)' }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
        <div style={{ fontSize: 12, color: 'var(--ink-2)' }}>Last 24h · since {data?.since?.slice(0, 16).replace('T', ' ')}</div>
        <button className="btn sm" onClick={refresh}>Refresh</button>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 'var(--sp-3)' }}>
        <Stat label="Total runs" value={data?.total ?? 0}/>
        <Stat label="Completed" value={counts.completed} tone="ok"/>
        <Stat label="Failed" value={counts.failed} tone={counts.failed > 0 ? 'err' : null}/>
        <Stat label="Success rate" value={successPct}/>
      </div>
      <div className="card">
        <div className="card-header">
          <div style={{ fontWeight: 600, fontSize: 13.5 }}>Recent failures</div>
          <div style={{ fontSize: 12, color: 'var(--ink-2)' }}>{failures.length} shown</div>
        </div>
        <div style={{ overflowX: 'auto' }}>
          <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12.5 }}>
            <thead>
              <tr style={{ textAlign: 'left', color: 'var(--ink-2)', borderBottom: '1px solid var(--line)' }}>
                <th style={th()}>Started</th>
                <th style={th()}>User</th>
                <th style={th()}>Run ID</th>
                <th style={th()}>Reason</th>
              </tr>
            </thead>
            <tbody>
              {failures.map((f) => (
                <tr key={f.runId} style={{ borderBottom: '1px solid var(--line)' }}>
                  <td style={{ ...td(), fontFamily: 'var(--font-mono)', fontSize: 11.5, color: 'var(--ink-2)' }}>
                    {(f.startedAt || '').slice(0, 16).replace('T', ' ')}
                  </td>
                  <td style={{ ...td(), fontFamily: 'var(--font-mono)', fontSize: 11.5 }}>{f.userId.slice(0, 8)}…</td>
                  <td style={{ ...td(), fontFamily: 'var(--font-mono)', fontSize: 11.5, color: 'var(--ink-3)' }}>{f.runId.slice(0, 10)}…</td>
                  <td style={{ ...td(), color: 'var(--err)' }}>{f.reason || '—'}</td>
                </tr>
              ))}
              {failures.length === 0 && (
                <tr><td colSpan={4} style={{ ...td(), textAlign: 'center', color: 'var(--ink-3)' }}>No failures in the last 24h.</td></tr>
              )}
            </tbody>
          </table>
        </div>
      </div>
    </div>
  );
}

function Stat({ label, value, tone }) {
  const color = tone === 'ok' ? 'var(--ok)' : tone === 'err' ? 'var(--err)' : 'var(--ink-0)';
  return (
    <div className="card">
      <div className="card-body">
        <div style={{ fontSize: 11, color: 'var(--ink-2)', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 6 }}>{label}</div>
        <div style={{ fontSize: 26, fontWeight: 600, letterSpacing: '-0.02em', color }}>{value}</div>
      </div>
    </div>
  );
}

const th = () => ({ padding: '8px 10px', fontWeight: 500, fontSize: 11, textTransform: 'uppercase', letterSpacing: '0.06em' });
const td = () => ({ padding: '8px 10px', verticalAlign: 'middle' });

/* ── Diagram quality tab ────────────────────────────────────────────────────
   Live scoreboard from the diagram-quality learning loop. Pulls from
   GET /admin/diagram-quality which lists s3://diagrams/corpus/, replays
   each captured architecture through synthesise → layout → validate, and
   returns aggregate violation counts. Operator can snapshot the current
   scoreboard as the new baseline; subsequent loads compare against it so
   regressions and improvements are visible immediately. */
function AdminDiagramQualityTab() {
  const { data, error, loading, refresh } = useAsync(
    () => window.API.admin.getDiagramQuality(),
    [],
  );
  const [savingBaseline, setSavingBaseline] = useState(false);
  const [baselineNote, setBaselineNote] = useState('');
  const [baselineError, setBaselineError] = useState(null);
  const [drillHash, setDrillHash] = useState(null);

  if (loading) return <Loading label="Replaying corpus…"/>;
  if (error)   return <ErrBox error={error} onRetry={refresh}/>;

  const total = data?.total ?? 0;
  const byKind = data?.byKind ?? {};
  const preTotal = data?.preTotal ?? 0;
  const preByKind = data?.preByKind ?? {};
  const repairsTotal = data?.repairsTotal ?? 0;
  const repairsByKind = data?.repairsByKind ?? {};
  const baseline = data?.baseline ?? null;
  const comparison = data?.comparison ?? null;
  const offenders = data?.worstOffenders ?? [];
  const skipped = data?.skipped ?? [];
  const corpusSize = data?.corpusSize ?? 0;
  const replayedCount = data?.replayedCount ?? 0;

  const orderedKinds = [
    'containment-overflow',
    'containment-padding',
    'port-wrong-side',
    'port-bunching',
    'az-ordering',
    'dead-whitespace',
    'excessive-bends',
  ];

  const kindDescriptions = {
    'containment-overflow':
      "A child group's bounding box extends past its parent's bounding box — e.g. a subnet pokes outside its VPC. Means the layout engine ran out of space inside the parent and clipped the child instead of growing the parent.",
    'containment-padding':
      "A child group sits flush against its parent's border (gap below MIN_CONTAINMENT_PADDING_PX). The diagram looks cramped — services touching the VPC edge instead of breathing inside it.",
    'port-wrong-side':
      "An edge connects to the wrong side of a node. E.g. a connection from ALB to EC2 leaves ALB on its left when EC2 is to its right, so the line loops awkwardly around the node.",
    'port-bunching':
      'Two edges share the same side of a node and sit closer than MIN_PORT_SPACING_PX, so the lines visually overlap or merge into a single thick stroke.',
    'az-ordering':
      'Availability-zone siblings inside a parent group are not in alphabetical order (eu-west-1a should appear before 1b before 1c). Inconsistent ordering makes diagrams harder to scan.',
    'dead-whitespace':
      'A group container has too much empty area relative to its content (ratio above DEAD_WHITESPACE_RATIO). The group looks oversized — typically padding misconfigured or a sparse VPC.',
    'excessive-bends':
      "An edge has more than 2 bends after simplification — the path winds around obstacles instead of taking a clean L or U shape. Two well-placed icons need at most 2 bends; anything more means the source/target are sitting where no clean orthogonal path exists, so the fix is moving the services, not the line.",
  };

  // Bar scale uses pre-repair counts so the visual width reflects raw
  // severity. Auto-repaired share is rendered as a green segment under
  // the surviving (red) segment, so each bar reads "what came in, what
  // got fixed, what's still broken" at a glance.
  const max = Math.max(1, ...orderedKinds.map((k) => preByKind[k] ?? byKind[k] ?? 0));

  const onSaveBaseline = async () => {
    setSavingBaseline(true);
    setBaselineError(null);
    try {
      await window.API.admin.saveDiagramQualityBaseline(baselineNote);
      setBaselineNote('');
      refresh();
    } catch (e) {
      setBaselineError(e?.message || 'Failed to save baseline');
    } finally {
      setSavingBaseline(false);
    }
  };

  const baselineDelta = (kind) => {
    if (!baseline?.byKind) return null;
    const before = baseline.byKind[kind] ?? 0;
    const after = byKind[kind] ?? 0;
    if (after === before) return null;
    const delta = after - before;
    const tone = delta > 0 ? 'err' : 'ok';
    return { delta, tone, before, after };
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--sp-4)' }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
        <div style={{ fontSize: 12, color: 'var(--ink-2)' }}>
          {corpusSize} corpus entries · {replayedCount} replayed
          {skipped.length > 0 && ` · ${skipped.length} skipped`}
          {baseline?.capturedAt && ` · baseline ${baseline.capturedAt.slice(0, 16).replace('T', ' ')}`}
        </div>
        <button className="btn sm" onClick={refresh}>Replay</button>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 'var(--sp-3)' }}>
        <Stat label="Pre-repair" value={preTotal}/>
        <Stat label="Auto-repaired" value={repairsTotal} tone={repairsTotal > 0 ? 'ok' : null}/>
        <Stat label="Surviving" value={total} tone={total === 0 ? 'ok' : total > (baseline?.total ?? 0) ? 'err' : null}/>
        <Stat
          label={baseline ? 'Δ vs baseline' : 'Baseline'}
          value={baseline ? (total - baseline.total > 0 ? `+${total - baseline.total}` : `${total - baseline.total}`) : '—'}
          tone={baseline ? (total > baseline.total ? 'err' : total < baseline.total ? 'ok' : null) : null}
        />
      </div>

      <div className="card">
        <div className="card-header">
          <div style={{ fontWeight: 600, fontSize: 13.5 }}>Violations by kind</div>
          <div style={{ fontSize: 12, color: 'var(--ink-2)' }}>
            {baseline ? 'current vs baseline' : 'no baseline saved yet'}
          </div>
        </div>
        <div style={{ padding: 'var(--sp-3) var(--sp-4)' }}>
          {orderedKinds.map((kind) => {
            const post = byKind[kind] ?? 0;
            const pre = preByKind[kind] ?? post;
            const repaired = repairsByKind[kind] ?? Math.max(0, pre - post);
            const baseCount = baseline?.byKind?.[kind] ?? 0;
            const basePct = baseline ? (baseCount / max) * 100 : 0;
            const repairedPct = (repaired / max) * 100;
            const survivingPct = (post / max) * 100;
            const delta = baselineDelta(kind);
            return (
              <div key={kind} style={{ marginBottom: 10 }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12, marginBottom: 4 }}>
                  <div style={{ flex: 1, fontFamily: 'var(--font-mono)', display: 'flex', alignItems: 'center', gap: 6 }}>
                    <span>{kind}</span>
                    <span
                      title={kindDescriptions[kind]}
                      aria-label={kindDescriptions[kind]}
                      style={{
                        display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                        width: 14, height: 14, borderRadius: '50%',
                        border: '1px solid var(--ink-3)', color: 'var(--ink-2)',
                        fontSize: 9.5, fontFamily: 'var(--font-sans, sans-serif)',
                        cursor: 'help', userSelect: 'none', lineHeight: 1,
                      }}
                    >?</span>
                  </div>
                  <div style={{ fontSize: 11, color: 'var(--ink-2)', minWidth: 110, textAlign: 'right' }}>
                    <span style={{ color: 'var(--ok)' }}>{repaired} fixed</span>
                    {' · '}
                    <span style={{ color: post > 0 ? 'var(--err)' : 'var(--ink-2)', fontWeight: 600 }}>{post} left</span>
                    <span style={{ color: 'var(--ink-3)' }}> / {pre}</span>
                  </div>
                  {delta && (
                    <div style={{ fontSize: 11, color: delta.tone === 'err' ? 'var(--err)' : 'var(--ok)', minWidth: 64, textAlign: 'right' }}>
                      {delta.delta > 0 ? `+${delta.delta}` : delta.delta} vs {delta.before}
                    </div>
                  )}
                </div>
                <div style={{ position: 'relative', height: 10, background: 'var(--bg-1)', borderRadius: 3, overflow: 'hidden' }}>
                  {baseline && (
                    <div style={{
                      position: 'absolute', inset: 0,
                      width: `${basePct}%`, background: 'var(--ink-3)', opacity: 0.2,
                    }}/>
                  )}
                  <div style={{
                    position: 'absolute', top: 0, bottom: 0, left: 0,
                    width: `${repairedPct}%`,
                    background: 'var(--ok)', opacity: 0.85,
                  }}/>
                  <div style={{
                    position: 'absolute', top: 0, bottom: 0,
                    left: `${repairedPct}%`,
                    width: `${survivingPct}%`,
                    background: 'var(--err)',
                  }}/>
                </div>
              </div>
            );
          })}
        </div>
      </div>

      <div className="card">
        <div className="card-header">
          <div style={{ fontWeight: 600, fontSize: 13.5 }}>Worst offenders</div>
          <div style={{ fontSize: 12, color: 'var(--ink-2)' }}>{offenders.length} entries — click a row to inspect</div>
        </div>
        <div style={{ overflowX: 'auto' }}>
          <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12.5 }}>
            <thead>
              <tr style={{ textAlign: 'left', color: 'var(--ink-2)', borderBottom: '1px solid var(--line)' }}>
                <th style={th()}>Hash</th>
                <th style={th()}>Source</th>
                <th style={th()}>Pre → Post</th>
                <th style={th()}>Top kinds</th>
              </tr>
            </thead>
            <tbody>
              {offenders.map((e) => {
                // Show top kinds keyed off the BIGGER of pre / post so a
                // fully-repaired entry still tells you what the validator
                // originally caught.
                const blend = {};
                for (const k of orderedKinds) {
                  blend[k] = Math.max(e.preByKind?.[k] ?? 0, e.byKind?.[k] ?? 0);
                }
                const topKinds = Object.entries(blend)
                  .filter(([, n]) => n > 0)
                  .sort(([, a], [, b]) => b - a)
                  .slice(0, 3)
                  .map(([k, n]) => `${k} (${n})`)
                  .join(', ');
                const pre = e.preCount ?? e.count ?? 0;
                const post = e.count ?? 0;
                return (
                  <tr
                    key={e.hash}
                    onClick={() => setDrillHash(e.hash)}
                    style={{ borderBottom: '1px solid var(--line)', cursor: 'pointer' }}
                    onMouseEnter={(ev) => { ev.currentTarget.style.background = 'var(--bg-1)'; }}
                    onMouseLeave={(ev) => { ev.currentTarget.style.background = ''; }}
                  >
                    <td style={{ ...td(), fontFamily: 'var(--font-mono)', fontSize: 11.5 }}>{e.hash.slice(0, 12)}…</td>
                    <td style={{ ...td(), fontSize: 11.5 }}>{e.source || '—'}</td>
                    <td style={{ ...td(), fontWeight: 600 }}>
                      <span style={{ color: 'var(--ink-2)' }}>{pre}</span>
                      <span style={{ color: 'var(--ink-3)', margin: '0 4px' }}>→</span>
                      <span style={{ color: post > 0 ? 'var(--err)' : 'var(--ok)' }}>{post}</span>
                    </td>
                    <td style={{ ...td(), fontSize: 11.5, color: 'var(--ink-2)' }}>{topKinds}</td>
                  </tr>
                );
              })}
              {offenders.length === 0 && (
                <tr><td colSpan={4} style={{ ...td(), textAlign: 'center', color: 'var(--ink-3)' }}>No violations across the corpus.</td></tr>
              )}
            </tbody>
          </table>
        </div>
      </div>

      {drillHash && (
        <DiagramQualityDrillDown
          hash={drillHash}
          onClose={() => setDrillHash(null)}
        />
      )}

      <div className="card">
        <div className="card-header">
          <div style={{ fontWeight: 600, fontSize: 13.5 }}>Save baseline</div>
          <div style={{ fontSize: 12, color: 'var(--ink-2)' }}>
            Snapshot the current scoreboard so future replays show deltas.
          </div>
        </div>
        <div style={{ padding: 'var(--sp-3) var(--sp-4)', display: 'flex', gap: 10, alignItems: 'center' }}>
          <input
            className="field"
            placeholder="Optional note (e.g. 'before port-side fix')"
            value={baselineNote}
            onChange={(e) => setBaselineNote(e.target.value)}
            style={{ flex: 1 }}
            maxLength={500}
          />
          <button
            className="btn sm primary"
            disabled={savingBaseline}
            onClick={onSaveBaseline}
          >{savingBaseline ? 'Saving…' : 'Save as baseline'}</button>
        </div>
        {baselineError && (
          <div style={{ padding: '0 var(--sp-4) var(--sp-3)', color: 'var(--err)', fontSize: 12 }}>
            {baselineError}
          </div>
        )}
      </div>
    </div>
  );
}

/* ── Drill-down panel ───────────────────────────────────────────────────────
   Renders a fixed full-screen overlay with the captured architecture's
   re-rendered SVG, the user's thumbs-down comment (if any), the surviving
   violations, and a per-kind list of repairs that fired. Click outside or
   press ✕ to close. */
function DiagramQualityDrillDown({ hash, onClose }) {
  const { data, error, loading } = useAsync(
    () => window.API.admin.getDiagramQualityCorpusEntry(hash),
    [hash],
  );

  const orderedKinds = [
    'containment-overflow',
    'containment-padding',
    'port-wrong-side',
    'port-bunching',
    'az-ordering',
    'dead-whitespace',
    'excessive-bends',
  ];

  return (
    <div
      onClick={onClose}
      style={{
        position: 'fixed', inset: 0, background: 'rgba(20,20,30,0.55)',
        zIndex: 200, display: 'flex', alignItems: 'center', justifyContent: 'center',
        padding: 'var(--sp-4)',
      }}
    >
      <div
        onClick={(e) => e.stopPropagation()}
        style={{
          background: 'var(--bg-0)', borderRadius: 6, width: 'min(1200px, 96vw)',
          maxHeight: '92vh', overflow: 'hidden', display: 'flex', flexDirection: 'column',
          boxShadow: '0 20px 60px rgba(0,0,0,0.4)',
        }}
      >
        <div style={{
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
          padding: 'var(--sp-3) var(--sp-4)', borderBottom: '1px solid var(--line)',
        }}>
          <div>
            <div style={{ fontWeight: 600, fontSize: 13.5 }}>Diagram inspector</div>
            <div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--ink-2)' }}>{hash}</div>
          </div>
          <button className="btn sm" onClick={onClose}>Close</button>
        </div>
        <div style={{ overflow: 'auto', padding: 'var(--sp-4)' }}>
          {loading && <Loading label="Re-rendering…"/>}
          {error && <ErrBox error={error}/>}
          {data && (
            <div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1.5fr) minmax(280px, 1fr)', gap: 'var(--sp-4)' }}>
              <div>
                <div style={{
                  border: '1px solid var(--line)', borderRadius: 4, padding: 8,
                  background: '#FFFFFF', overflow: 'auto', maxHeight: '60vh',
                }} dangerouslySetInnerHTML={{ __html: data.svg }}/>
                <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 6 }}>
                  Re-rendered now using today's pipeline ({data.repair?.hintsApplied?.length ? 'with comment hints' : 'no hints'}).
                </div>
              </div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--sp-3)' }}>
                {data.feedback?.comment && (
                  <div className="card">
                    <div className="card-header">
                      <div style={{ fontWeight: 600, fontSize: 13 }}>User comment</div>
                      <div style={{ fontSize: 11, color: 'var(--ink-2)' }}>{data.feedback.rating}</div>
                    </div>
                    <div style={{ padding: 'var(--sp-3) var(--sp-4)', fontSize: 12.5, whiteSpace: 'pre-wrap' }}>
                      {data.feedback.comment}
                    </div>
                  </div>
                )}
                <div className="card">
                  <div className="card-header">
                    <div style={{ fontWeight: 600, fontSize: 13 }}>Repairs</div>
                    <div style={{ fontSize: 11, color: 'var(--ink-2)' }}>auto-fixed by deterministic pass</div>
                  </div>
                  <div style={{ padding: 'var(--sp-3) var(--sp-4)', fontSize: 12 }}>
                    {orderedKinds.map((k) => {
                      const fixed = data.repair?.repairsByKind?.[k] ?? 0;
                      const left = data.repair?.postCounts?.[k] ?? 0;
                      const pre = data.repair?.preCounts?.[k] ?? 0;
                      if (pre === 0 && left === 0) return null;
                      return (
                        <div key={k} style={{ display: 'flex', justifyContent: 'space-between', padding: '3px 0', fontFamily: 'var(--font-mono)', fontSize: 11.5 }}>
                          <span>{k}</span>
                          <span>
                            <span style={{ color: 'var(--ok)' }}>{fixed}↓</span>
                            <span style={{ color: 'var(--ink-3)', margin: '0 4px' }}>·</span>
                            <span style={{ color: left > 0 ? 'var(--err)' : 'var(--ink-2)' }}>{left} left</span>
                          </span>
                        </div>
                      );
                    })}
                    {data.repair?.hintsApplied?.length > 0 && (
                      <div style={{ marginTop: 8, paddingTop: 8, borderTop: '1px solid var(--line)', fontSize: 11, color: 'var(--ink-2)' }}>
                        Hints from comment: {data.repair.hintsApplied.join(', ')}
                      </div>
                    )}
                  </div>
                </div>
                {data.violations?.length > 0 && (
                  <div className="card">
                    <div className="card-header">
                      <div style={{ fontWeight: 600, fontSize: 13 }}>Surviving violations</div>
                      <div style={{ fontSize: 11, color: 'var(--ink-2)' }}>{data.violations.length}</div>
                    </div>
                    <div style={{ padding: 'var(--sp-3) var(--sp-4)', maxHeight: 220, overflow: 'auto', fontFamily: 'var(--font-mono)', fontSize: 11 }}>
                      {data.violations.slice(0, 50).map((v, i) => (
                        <div key={i} style={{ padding: '2px 0', color: 'var(--ink-2)' }}>
                          <span style={{ color: 'var(--err)' }}>{v.kind}</span>
                          {' '}
                          {Object.entries(v).filter(([k]) => k !== 'kind').map(([k, val]) => `${k}=${typeof val === 'object' ? JSON.stringify(val) : val}`).join(' ')}
                        </div>
                      ))}
                      {data.violations.length > 50 && (
                        <div style={{ color: 'var(--ink-3)' }}>… and {data.violations.length - 50} more</div>
                      )}
                    </div>
                  </div>
                )}
                <details style={{ fontSize: 12 }}>
                  <summary style={{ cursor: 'pointer', color: 'var(--ink-2)' }}>Architecture JSON</summary>
                  <pre style={{
                    background: 'var(--bg-1)', padding: 8, borderRadius: 4,
                    fontSize: 10.5, overflow: 'auto', maxHeight: 280,
                  }}>{JSON.stringify(data.architecture, null, 2)}</pre>
                </details>
              </div>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

window.AdminScreen = AdminScreen;
