// HealthPanel — Phase 6 R · #H surface for /api/admin/health/audit.
//
// Renders the one-shot daily-life-signal JSON as a glanceable status
// board. Lets the trader see if everything's working as designed
// without curl-ing or staring at logs:
//   - System: deployed_commit, broker, anthropic
//   - Brain: total nodes, orphan %, persona count
//   - Cron: every cron-driven persona + when they fired today
//   - Cascade today: brief emailed?, persona memos count, last_run
//   - Alerts: which channels configured + recipient
//   - Auth: supabase configured + strict mode flag
//   - Issues: human-readable list of anything that's drifted
//
// Pulls /api/admin/health/audit on mount and on a 60s poll. Surfaces
// as a modal-style panel that can be opened from the topbar's "Health"
// button or by pressing Cmd+Shift+H.
const { useState: hUseState, useEffect: hUseEffect, useCallback: hUseCallback } = React;

function HealthPanel({ open, onClose }) {
  if (!open) return null;
  const [data, setData] = hUseState(null);
  const [briefStatus, setBriefStatus] = hUseState(null);
  const [loading, setLoading] = hUseState(true);
  const [err, setErr] = hUseState('');
  const [lastFetch, setLastFetch] = hUseState(null);
  const [redelivering, setRedelivering] = hUseState(false);
  const [redeliverMsg, setRedeliverMsg] = hUseState('');
  // Autonomy surface — populated when /api/events/* endpoints exist
  // (lights up after claude/autonomous-codebase-oDhbG merges to main).
  // Until then these stay null and the section renders an empty state.
  const [autonomy, setAutonomy] = hUseState(null);

  const load = hUseCallback(async () => {
    setLoading(true);
    try {
      const [auditR, statusR, snapR, personasR, recsR] = await Promise.all([
        fetch('/api/admin/health/audit'),
        fetch('/api/brief/status'),
        // The autonomy endpoints below ship in the autonomous-triggers
        // branch — best-effort fetches; 404s are expected pre-merge.
        fetch('/api/events/snapshot').catch(() => null),
        fetch('/api/events/personas').catch(() => null),
        fetch('/api/trade-recs?status=pending&limit=50').catch(() => null),
      ]);
      if (!auditR.ok) throw new Error(`audit HTTP ${auditR.status}`);
      const j = await auditR.json();
      setData(j);
      if (statusR.ok) {
        try { setBriefStatus(await statusR.json()); } catch (_) {}
      }
      // Compose autonomy state from whatever subset of endpoints answered
      const auto = {};
      if (snapR && snapR.ok) {
        try { auto.snapshot = await snapR.json(); } catch (_) {}
      }
      if (personasR && personasR.ok) {
        try { auto.personas = await personasR.json(); } catch (_) {}
      }
      if (recsR && recsR.ok) {
        try { auto.recs = await recsR.json(); } catch (_) {}
      }
      // Only commit autonomy state when at least ONE endpoint answered —
      // otherwise the section renders the "not yet enabled" empty state.
      setAutonomy((auto.snapshot || auto.personas || auto.recs) ? auto : null);
      setErr('');
      setLastFetch(new Date());
    } catch (e) {
      setErr(String(e.message || e));
    } finally {
      setLoading(false);
    }
  }, []);

  // Hit /api/morning-brief/generate?force=true to redeliver. The endpoint
  // returns 202 + async-mode (Stage 4 only) when today's brief.md already
  // exists, so this is fast and safe even after a creds rotation.
  const redeliverBrief = hUseCallback(async () => {
    if (redelivering) return;
    setRedelivering(true);
    setRedeliverMsg('');
    try {
      const r = await fetch('/api/morning-brief/generate', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ force: true }),
      });
      if (!r.ok) throw new Error(`HTTP ${r.status}`);
      const j = await r.json();
      setRedeliverMsg(j.skipped ? 'already shipped today' :
                      j.async ? 'queued — re-checking in 8s…' :
                      j.ok ? 'sent ✓' : 'unknown response');
      // Re-poll status after a short delay
      setTimeout(load, 8000);
    } catch (e) {
      setRedeliverMsg(`failed: ${e.message || e}`);
    } finally {
      setRedelivering(false);
    }
  }, [redelivering, load]);

  hUseEffect(() => {
    if (!open) return;
    load();
    const id = setInterval(load, 60_000);
    return () => clearInterval(id);
  }, [open, load]);

  const allClear = data && data.all_clear;
  const issues = data?.issues || [];
  const dotColor = (val) => val ? '#5fb37c' : '#d96a6a';
  const Section = ({ title, children }) => (
    <div style={{
      marginBottom: 14, padding: '12px 14px',
      background: 'var(--bg-2)', border: '1px solid var(--line)',
      borderRadius: 4,
    }}>
      <div style={{
        fontSize: 9, letterSpacing: '0.18em', textTransform: 'uppercase',
        color: 'var(--accent)', fontWeight: 700, marginBottom: 8,
      }}>{title}</div>
      {children}
    </div>
  );
  const Row = ({ k, v, color }) => (
    <div style={{
      display: 'flex', justifyContent: 'space-between',
      fontSize: 11, fontFamily: "'JetBrains Mono', monospace",
      lineHeight: 1.7, gap: 12,
    }}>
      <span style={{ color: 'var(--text-dim)' }}>{k}</span>
      <span style={{ color: color || 'var(--text)', textAlign: 'right' }}>{v}</span>
    </div>
  );

  return (
    <div style={{
      position: 'fixed', inset: 0, zIndex: 100,
      background: 'rgba(10,10,12,0.85)', backdropFilter: 'blur(8px)',
      display: 'flex', alignItems: 'flex-start', justifyContent: 'center',
      padding: '60px 20px',
    }} onClick={onClose}>
      <div onClick={e => e.stopPropagation()} style={{
        width: '100%', maxWidth: 760, maxHeight: 'calc(100vh - 120px)', overflowY: 'auto',
        background: 'var(--bg)', border: '1px solid var(--line-2)',
        borderRadius: 6, padding: 24, fontFamily: 'var(--font-body)',
      }}>
        {/* Header */}
        <div style={{
          display: 'flex', alignItems: 'baseline', gap: 12,
          paddingBottom: 14, marginBottom: 14, borderBottom: '1px solid var(--line)',
        }}>
          <span style={{
            display: 'inline-block', width: 10, height: 10, borderRadius: '50%',
            background: allClear ? '#5fb37c' : (issues.length > 2 ? '#d96a6a' : '#d9a85f'),
            boxShadow: allClear ? '0 0 8px #5fb37c' : 'none',
          }}/>
          <h1 style={{
            fontFamily: 'Instrument Serif, serif', fontStyle: 'italic',
            fontSize: 24, margin: 0, color: 'var(--text)',
          }}>System Health</h1>
          <span style={{
            fontSize: 10, letterSpacing: '0.18em', textTransform: 'uppercase',
            color: allClear ? '#5fb37c' : '#d9a85f',
          }}>
            {allClear ? 'all clear' : `${issues.length} issue${issues.length === 1 ? '' : 's'}`}
          </span>
          {lastFetch && (
            <span style={{
              marginLeft: 'auto', fontSize: 10, color: 'var(--text-dim)',
              fontFamily: "'JetBrains Mono', monospace",
            }}>
              {loading ? 'refreshing…' : `updated ${lastFetch.toLocaleTimeString()}`}
            </span>
          )}
          <button onClick={load} disabled={loading} style={{
            padding: '4px 10px', fontSize: 10, fontFamily: "'JetBrains Mono', monospace",
            background: 'transparent', color: 'var(--text-2)',
            border: '1px solid var(--line)', borderRadius: 3, cursor: 'pointer',
          }}>↻</button>
          <button onClick={onClose} style={{
            padding: '4px 10px', fontSize: 10, fontFamily: "'JetBrains Mono', monospace",
            background: 'transparent', color: 'var(--text-dim)',
            border: '1px solid var(--line)', borderRadius: 3, cursor: 'pointer',
          }}>close</button>
        </div>

        {err && (
          <div style={{
            padding: 12, marginBottom: 14,
            background: 'rgba(217,106,106,0.12)', color: '#d96a6a',
            border: '1px solid rgba(217,106,106,0.4)', borderRadius: 4,
            fontSize: 11,
          }}>
            Failed to load /api/admin/health/audit: {err}
          </div>
        )}

        {data && (
          <>
            {/* Issues at the top — if any */}
            {issues.length > 0 && (
              <Section title={`Issues · ${issues.length}`}>
                {issues.map((s, i) => (
                  <div key={i} style={{
                    fontSize: 11, padding: '4px 0', color: '#d9a85f',
                    fontFamily: "'JetBrains Mono', monospace",
                  }}>
                    ⚠ {s}
                  </div>
                ))}
              </Section>
            )}

            {/* System */}
            <Section title="System">
              <Row k="commit" v={data.system?.deployed_commit || '—'} />
              <Row k="version" v={data.system?.version || '—'} />
              <Row k="broker" v={data.system?.broker_ok ? 'ok' : 'down'}
                   color={dotColor(data.system?.broker_ok)} />
              <Row k="" v={data.system?.broker_detail || ''}
                   color="var(--text-dim)" />
              <Row k="anthropic" v={data.system?.anthropic ? 'configured' : 'missing'}
                   color={dotColor(data.system?.anthropic)} />
              <Row k="brain dir" v={data.system?.investing_brain_dir || '—'} />
            </Section>

            {/* Brain */}
            <Section title="Brain">
              <Row k="total nodes" v={data.brain?.total_nodes ?? '—'} />
              <Row k="orphans" v={`${data.brain?.orphan_count ?? '—'} (${data.brain?.orphan_pct ?? 0}%)`}
                   color={(data.brain?.orphan_pct ?? 0) > 5 ? '#d9a85f' : 'var(--text)'} />
              <Row k="persona count" v={data.brain?.persona_count ?? '—'} />
              <Row k="markdown files" v={data.storage?.md_files ?? '—'} />
            </Section>

            {/* Cascade today */}
            <Section title={`Cascade · ${data.cascade_today?.date || 'today'}`}>
              <Row k="brief" v={
                data.cascade_today?.brief_exists
                  ? `${(data.cascade_today.brief_chars || 0).toLocaleString()} chars`
                  : 'not generated'
              } color={dotColor(data.cascade_today?.brief_exists)} />
              <Row k="brief emailed" v={
                data.cascade_today?.brief_emailed
                  ? `via ${data.cascade_today?.brief_emailed_via || 'gmail'}`
                  : 'not emailed'
              } color={dotColor(data.cascade_today?.brief_emailed)} />

              {/* Per-stage breakdown — pulled from /api/brief/status */}
              {briefStatus && (
                <BriefStageTimeline status={briefStatus}
                                    redelivering={redelivering}
                                    redeliverMsg={redeliverMsg}
                                    onRedeliver={redeliverBrief} />
              )}

              {Object.entries(data.cascade_today?.personas || {}).map(([role, p]) => (
                <Row key={role}
                     k={role}
                     v={`${p.memos_today} memo${p.memos_today === 1 ? '' : 's'} · ${p.last_run || '—'} · cron ${p.cron}`}
                     color={p.last_run === 'ok' ? '#5fb37c' : 'var(--text)'} />
              ))}
            </Section>

            {/* Cron */}
            <Section title="Cron schedule">
              {(data.cron?.personas || []).map(p => (
                <Row key={p.role}
                     k={`${p.role}`}
                     v={`${p.cron} ${p.tz} · email=${p.emails_on_success ? 'yes' : 'no'} · reads=[${(p.reads || []).join(', ') || '—'}]`} />
              ))}
              {data.cron?.scheduler_disabled && (
                <Row k="status" v="DISABLED via env" color="#d96a6a" />
              )}
            </Section>

            {/* Autonomy — event-triggered personas + queued recs.
                Only renders when /api/events/* endpoints respond (i.e.
                the autonomous-triggers branch has merged). When pre-merge,
                renders an "off" hint so the surface stays predictable. */}
            <Section title="Autonomy · event triggers">
              {autonomy ? (
                <AutonomySnapshot autonomy={autonomy} />
              ) : (
                <Row k="status" v="not enabled (event endpoints not deployed)"
                     color="var(--text-dim)" />
              )}
              {/* Budget guard surface — propose_trade caps. Visible whether
                  or not the autonomy endpoints are live, since the SQLite
                  trade_recommendations table is always there. */}
              {data.autonomy_budget && !data.autonomy_budget.error && (
                <div style={{ marginTop: 10, paddingTop: 8,
                  borderTop: '1px dashed var(--line)' }}>
                  <Row
                    k="open queue"
                    v={`${data.autonomy_budget.open} / ${data.autonomy_budget.max_open || '∞'} pending`}
                    color={data.autonomy_budget.open_blocked ? '#d96a6a'
                          : data.autonomy_budget.max_open > 0 && data.autonomy_budget.open >= data.autonomy_budget.max_open * 0.7
                            ? '#d9a85f' : 'var(--text)'}
                  />
                  <Row
                    k="filed today"
                    v={`${data.autonomy_budget.today} / ${data.autonomy_budget.max_per_day || '∞'} in last 24h`}
                    color={data.autonomy_budget.today_blocked ? '#d96a6a'
                          : data.autonomy_budget.max_per_day > 0 && data.autonomy_budget.today >= data.autonomy_budget.max_per_day * 0.7
                            ? '#d9a85f' : 'var(--text)'}
                  />
                </div>
              )}
            </Section>

            {/* Alerts */}
            <Section title="Alert channels">
              {Object.entries(data.alerts?.channels || {}).map(([ch, ok]) => (
                <Row key={ch} k={ch}
                     v={ok ? 'configured' : 'not configured'}
                     color={dotColor(ok)} />
              ))}
              <Row k="recipient" v={data.alerts?.recipient || '—'} />
              <Row k="total channels" v={data.alerts?.channels_count ?? 0}
                   color={(data.alerts?.channels_count ?? 0) >= 2 ? '#5fb37c'
                        : (data.alerts?.channels_count ?? 0) === 1 ? '#d9a85f' : '#d96a6a'} />
            </Section>

            {/* Auth */}
            <Section title="Auth">
              <Row k="supabase configured" v={data.auth?.supabase_configured ? 'yes' : 'no'}
                   color={dotColor(data.auth?.supabase_configured)} />
              <Row k="strict mode" v={data.auth?.strict_mode ? 'on' : 'soft-fallback'}
                   color={data.auth?.strict_mode ? '#5fb37c' : '#d9a85f'} />
              <Row k="invite list" v={`${data.auth?.invite_list_size ?? 0} emails`} />
            </Section>
          </>
        )}
      </div>
    </div>
  );
}

// Per-stage timeline for today's brief. Reads /api/brief/status's
// {stages, attempts} and renders a 5-column row (COLLECT → ENRICH →
// SYNTHESIZE → COMPOSE → DELIVER) with each stage's tick / cross /
// dash + the most recent error if the stage failed. Plus a redeliver
// button so the user can retry the DELIVER stage after fixing creds
// without leaving the panel.
//
// SYNTHESIZE is the heavy LLM stage (multi-persona team round) — the
// most common failure point when Anthropic credits run out. Surfacing
// it as a distinct cell makes the failure mode legible.
const BRIEF_STAGES = ['COLLECT', 'ENRICH', 'SYNTHESIZE', 'COMPOSE', 'DELIVER'];

function BriefStageTimeline({ status, redelivering, redeliverMsg, onRedeliver }) {
  // For each stage, find the LAST attempt's outcome to color the cell.
  // 'ok' = green, 'fail' = red, anything else (skipped, missing) = dim.
  const lastByStage = {};
  for (const a of (status.attempts || [])) {
    if (a.stage) lastByStage[a.stage.toUpperCase()] = a;
  }

  // Most recent failure (if any) — surfaced under the timeline so the
  // user sees the error string without expanding the full attempts log.
  const recentFail = (status.attempts || []).slice().reverse()
    .find(a => a.status === 'fail');

  const cellColor = (stageKey) => {
    const last = lastByStage[stageKey];
    if (!last) return 'var(--text-dim)';
    if (last.status === 'ok') return '#5fb37c';
    if (last.status === 'fail') return '#d96a6a';
    return 'var(--text-dim)';
  };
  const cellGlyph = (stageKey) => {
    const last = lastByStage[stageKey];
    if (!last) return '·';
    if (last.status === 'ok') return '✓';
    if (last.status === 'fail') return '✗';
    return '~';
  };

  return (
    <div style={{
      marginTop: 8, marginBottom: 4,
      padding: 10,
      background: 'rgba(0,0,0,0.20)',
      border: '1px dashed var(--line)',
      borderRadius: 3,
    }}>
      <div style={{
        display: 'flex', justifyContent: 'space-between', alignItems: 'center',
        marginBottom: 10,
      }}>
        <span style={{
          fontSize: 9, letterSpacing: '0.18em', textTransform: 'uppercase',
          color: 'var(--text-dim)', fontWeight: 700,
        }}>
          stages
        </span>
        <button
          onClick={onRedeliver}
          disabled={redelivering}
          title="POST /api/morning-brief/generate {force:true} — reuses today's brief.md, just re-runs Stage 4 (DELIVER). Use after rotating Gmail/Resend creds."
          style={{
            padding: '3px 10px', fontSize: 9,
            letterSpacing: '0.10em', textTransform: 'uppercase', fontWeight: 700,
            fontFamily: "'JetBrains Mono', monospace",
            background: 'transparent', color: 'var(--accent)',
            border: '1px solid var(--accent)', borderRadius: 3,
            cursor: redelivering ? 'wait' : 'pointer',
            opacity: redelivering ? 0.5 : 1,
          }}>
          {redelivering ? 'sending…' : '↻ redeliver'}
        </button>
      </div>

      {/* Timeline row: each cell has glyph above + label below */}
      <div style={{ display: 'flex', gap: 6, marginBottom: 8 }}>
        {BRIEF_STAGES.map((stage, i) => (
          <div key={stage} style={{
            flex: 1, textAlign: 'center',
            padding: '6px 4px',
            background: 'var(--bg-3)',
            border: '1px solid var(--line)',
            borderRadius: 3,
            position: 'relative',
          }}>
            <div style={{
              fontSize: 18, color: cellColor(stage),
              fontFamily: "'JetBrains Mono', monospace", fontWeight: 700,
              lineHeight: 1.1,
            }}>{cellGlyph(stage)}</div>
            <div style={{
              fontSize: 8, letterSpacing: '0.16em', textTransform: 'uppercase',
              color: 'var(--text-dim)', marginTop: 4,
            }}>{stage}</div>
            {/* Connector arrow between stages */}
            {i < BRIEF_STAGES.length - 1 && (
              <span style={{
                position: 'absolute', right: -8, top: '50%',
                transform: 'translateY(-50%)',
                fontSize: 9, color: 'var(--text-dim)',
                fontFamily: "'JetBrains Mono', monospace",
                pointerEvents: 'none',
                zIndex: 1,
              }}>›</span>
            )}
          </div>
        ))}
      </div>

      {recentFail && (
        <div style={{
          padding: 8, marginTop: 4,
          background: 'rgba(217, 106, 106, 0.10)',
          border: '1px solid rgba(217, 106, 106, 0.30)',
          borderRadius: 3,
          fontSize: 10, fontFamily: "'JetBrains Mono', monospace",
          color: '#d96a6a',
          lineHeight: 1.5,
        }}>
          <div style={{ fontWeight: 700, marginBottom: 2 }}>
            ✗ {recentFail.stage || '?'} — {new Date(recentFail.ts).toLocaleTimeString()}
          </div>
          <div style={{ color: '#e58a8a' }}>
            {recentFail.error || '(no error string)'}
          </div>
        </div>
      )}

      {redeliverMsg && (
        <div style={{
          marginTop: 6,
          fontSize: 10, fontFamily: "'JetBrains Mono', monospace",
          color: redeliverMsg.startsWith('failed') ? '#d96a6a' : 'var(--accent)',
        }}>
          {redeliverMsg}
        </div>
      )}
    </div>
  );
}

// Autonomy section content. Surfaces three streams from the autonomous-
// triggers branch (claude/autonomous-codebase-oDhbG) via best-effort
// fetches; renders the subset that answered.
//
//   /api/events/snapshot   → live signal (vix, drawdown, bwd, ...) the
//                            triggers are evaluated against
//   /api/events/personas   → every persona with `trigger:` frontmatter,
//                            its armed/latched state, last-fire ts
//   /api/trade-recs        → propose_trade-filed recs awaiting review
//
// Why duplicate Today tab's AutonomyStrip here: Health is the single
// "is everything OK" diagnostic surface — the trader checks it before
// market open. Mixing autonomy state into the same surface saves a tab
// hunt for "why didn't the hedge fire when VIX spiked?".
function AutonomySnapshot({ autonomy }) {
  const snap = autonomy.snapshot || {};
  const personas = (autonomy.personas?.personas || autonomy.personas?.entries || autonomy.personas || []);
  const personaList = Array.isArray(personas) ? personas : (personas.personas || []);
  const recs = (autonomy.recs?.recs || autonomy.recs || []);
  const recList = Array.isArray(recs) ? recs : (recs.recs || []);

  const Row = ({ k, v, color }) => (
    <div style={{
      display: 'flex', justifyContent: 'space-between',
      fontSize: 11, fontFamily: "'JetBrains Mono', monospace",
      lineHeight: 1.7, gap: 12,
    }}>
      <span style={{ color: 'var(--text-dim)' }}>{k}</span>
      <span style={{ color: color || 'var(--text)', textAlign: 'right' }}>{v}</span>
    </div>
  );

  // Snapshot pills — render the standard signals if present
  const snapFields = [
    { key: 'vix', label: 'vix', fmt: (v) => v?.toFixed?.(2) ?? '—' },
    { key: 'drawdown_pct', label: 'drawdown', fmt: (v) => v != null ? `${(v * 100).toFixed(2)}%` : '—' },
    { key: 'bwd', label: 'BWD', fmt: (v) => v?.toFixed?.(2) ?? '—' },
    { key: 'largest_concentration_pct', label: 'concentration', fmt: (v) => v != null ? `${(v * 100).toFixed(1)}%` : '—' },
    { key: 'market_value', label: 'MV', fmt: (v) => v != null ? `$${(v / 1000).toFixed(1)}k` : '—' },
  ];

  return (
    <>
      {/* Snapshot pills */}
      {Object.keys(snap).length > 0 && (
        <div style={{
          display: 'flex', gap: 6, flexWrap: 'wrap', marginBottom: 10,
          paddingBottom: 8, borderBottom: '1px dashed var(--line)',
        }}>
          {snapFields.map(f => {
            const v = snap[f.key];
            if (v == null) return null;
            return (
              <span key={f.key} style={{
                padding: '3px 8px',
                fontSize: 10, fontFamily: "'JetBrains Mono', monospace",
                background: 'var(--bg-3)',
                border: '1px solid var(--line)',
                borderRadius: 3,
                color: 'var(--text-2)',
              }}>
                <span style={{ color: 'var(--text-dim)' }}>{f.label}</span>{' '}
                <b style={{ color: 'var(--text)' }}>{f.fmt(v)}</b>
              </span>
            );
          })}
          {snap.ts && (
            <span style={{
              fontSize: 9, color: 'var(--text-dim)',
              fontFamily: "'JetBrains Mono', monospace",
              marginLeft: 'auto', alignSelf: 'center',
            }}>
              snapshot {new Date(snap.ts).toLocaleTimeString()}
            </span>
          )}
        </div>
      )}

      {/* Trigger personas — one row per persona with state pill */}
      {personaList.length > 0 && (
        <div style={{ marginBottom: 8 }}>
          <div style={{
            fontSize: 9, letterSpacing: '0.16em', textTransform: 'uppercase',
            color: 'var(--text-dim)', marginBottom: 6, fontWeight: 600,
          }}>
            wired triggers · {personaList.length}
          </div>
          {personaList.map((p, i) => {
            const state = p.state || (p.evaluates ? (p.armed ? 'hot' : 'latched') : 'waiting');
            const stateColor = {
              hot: '#d96a6a', latched: '#d9a85f',
              waiting: '#5fb37c', unavailable: 'var(--text-dim)',
            }[state] || 'var(--text)';
            const role = p.role || p.name || `persona-${i}`;
            const expr = p.trigger || p.expression || '';
            const lastFire = p.last_fired || p.last_fire_ts;
            return (
              <div key={role} style={{
                display: 'flex', alignItems: 'baseline', gap: 8,
                padding: '4px 0',
                fontSize: 11, fontFamily: "'JetBrains Mono', monospace",
              }}>
                <span style={{ color: stateColor, width: 10, textAlign: 'center' }}>●</span>
                <span style={{ color: 'var(--text)', minWidth: 130 }}>{role}</span>
                <span style={{ color: 'var(--text-dim)', flex: 1, fontSize: 10 }}>
                  {expr}
                </span>
                {lastFire && (
                  <span style={{ color: 'var(--text-dim)', fontSize: 9 }}>
                    fired {new Date(lastFire).toLocaleString(undefined, { month: 'numeric', day: 'numeric', hour: 'numeric', minute: '2-digit' })}
                  </span>
                )}
              </div>
            );
          })}
        </div>
      )}

      {/* Queued trade-recs counter */}
      {recList.length > 0 && (
        <Row
          k="queued recs"
          v={`${recList.length} pending propose_trade ${recList.length === 1 ? 'rec' : 'recs'}`}
          color="#d9a85f"
        />
      )}
      {recList.length === 0 && personaList.length > 0 && (
        <Row k="queued recs" v="none pending" color="var(--text-dim)" />
      )}
    </>
  );
}

window.HealthPanel = HealthPanel;
