/**
 * runtime.js — Live MCP tool-call and orchestration event feed.
 * Shows every tool invocation, phase, token saving, and memory event in real time.
 */

import { S, bus } from '../state.js';
import { api }    from '../api.js';

const $ = id => document.getElementById(id);
const esc = s => s == null ? '' : String(s)
  .replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');

/* ── State ─────────────────────────────────────────────────────────────────── */
const MAX_EVENTS = 200;
const MAX_RESOLVED_MCP = 24;
const LONG_CALL_MS = 2000;
const STALE_CALL_MS = 5 * 60 * 1000;
const _events = [];
let _mounted = false;
let _filter = 'all';
let _totalTokensSaved = 0;
let _toolCalls = 0;
// callId → { tool, startedAt, source }
let _activeTools = new Map();
// resolved MCP calls (newest-first, capped at MAX_RESOLVED_MCP)
let _resolvedMcp = [];
// tool name -> latest completion/shutdown timestamp, used to ignore older replayed starts
let _settledToolTimes = new Map();
// active-bar pulse timer
let _pulseTimer = null;
// toast queue
let _toastTimer = null;
let _tokenFlyoutOpen = false;
let _tokenFlyoutLoading = false;
let _tokenFlyoutError = '';
let _tokenTelemetry = null;

/* ── Category metadata ──────────────────────────────────────────────────────── */
const CATEGORY_META = {
  mcp:       { label: 'MCP',       color: 'var(--accent)',  icon: '⚡' },
  memory:    { label: 'Memory',    color: '#a78bfa',        icon: '◎' },
  tokens:    { label: 'Tokens',    color: 'var(--warn)',    icon: '◈' },
  runtime:   { label: 'Runtime',   color: 'var(--ok)',      icon: '▸' },
  shield:    { label: 'Shield',    color: '#f472b6',        icon: '⛨' },
  system:    { label: 'System',    color: 'var(--muted)',   icon: '·' },
};

function categoryFor(type) {
  if (type.startsWith('mcp.') || type === 'tool.invocation') return 'mcp';
  if (type.startsWith('memory.')) return 'memory';
  if (type.startsWith('tokens.') || type.startsWith('cas.')) return 'tokens';
  if (type.startsWith('phantom.') || type.startsWith('planner.') || type === 'dashboard.action') return 'runtime';
  if (type.startsWith('shield.') || type.startsWith('guardrail.')) return 'shield';
  return 'system';
}

function severityClass(type, payload) {
  if (type === 'mcp.call.complete' && payload?.error) return 'sev-bad';
  if (type === 'mcp.call.start') return 'sev-info';
  if (type === 'mcp.call.complete') return 'sev-ok';
  if (type === 'tokens.optimized') return 'sev-warn';
  if (type.startsWith('shield.') && payload?.blocked) return 'sev-bad';
  return 'sev-info';
}

function humanMs(ms) {
  if (!ms || ms < 0) return '';
  if (ms < 1000) return `${ms}ms`;
  return `${(ms / 1000).toFixed(1)}s`;
}

function fmtTokens(n) {
  const v = Number(n ?? 0);
  if (!Number.isFinite(v) || v <= 0) return '0';
  if (v >= 1_000_000) return `${(v / 1_000_000).toFixed(1)}M`;
  if (v >= 10_000) return `${Math.round(v / 1000)}k`;
  if (v >= 1000) return `${(v / 1000).toFixed(1)}k`;
  return Math.round(v).toLocaleString();
}

function fmtPct(n) {
  const v = Number(n ?? 0);
  if (!Number.isFinite(v)) return '0%';
  return `${Math.round(v)}%`;
}

function fmtTime(ts) {
  const n = Number(ts ?? 0);
  if (!Number.isFinite(n) || n <= 0) return 'recent';
  return new Date(n).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
}

function toolNameFromPayload(payload) {
  return String(payload.toolName ?? payload.tool ?? payload.name ?? '').trim();
}

function eventMillis(evt, payload) {
  const raw = evt?.time ?? payload?.time ?? payload?.ts ?? null;
  const numeric = Number(raw);
  if (Number.isFinite(numeric) && numeric > 0) return numeric;
  const parsed = Date.parse(String(raw ?? ''));
  return Number.isFinite(parsed) ? parsed : Date.now();
}

function capResolvedMcp() {
  if (_resolvedMcp.length > MAX_RESOLVED_MCP) _resolvedMcp.length = MAX_RESOLVED_MCP;
}

function rememberSettledTool(tool, eventTime) {
  if (!tool) return;
  const previous = _settledToolTimes.get(tool) ?? 0;
  if (eventTime >= previous) _settledToolTimes.set(tool, eventTime);
  if (_settledToolTimes.size > MAX_RESOLVED_MCP * 2) {
    const oldest = [..._settledToolTimes.entries()].sort((a, b) => a[1] - b[1])[0]?.[0];
    if (oldest) _settledToolTimes.delete(oldest);
  }
}

function wasSettledAfter(tool, eventTime) {
  const settledAt = tool ? _settledToolTimes.get(tool) : null;
  return Number.isFinite(settledAt) && Number.isFinite(eventTime) && eventTime <= settledAt;
}

function activeToolKey(trackKey, tool) {
  if (trackKey && _activeTools.has(trackKey)) return trackKey;
  if (tool && _activeTools.has(tool)) return tool;
  if (tool) {
    for (const [key, entry] of _activeTools.entries()) {
      if (entry.tool === tool) return key;
    }
  }
  if (!trackKey && !tool && _activeTools.size === 1) {
    return _activeTools.keys().next().value;
  }
  return null;
}

function settleActiveTool(trackKey, tool, payload = {}, opts = {}) {
  const key = activeToolKey(trackKey, tool);
  if (!key) return false;
  const entry = _activeTools.get(key);
  if (!entry) return false;
  const elapsed = Number(opts.durationMs ?? payload.durationMs ?? 0) || (Date.now() - entry.startedAt);
  const status = String(payload.status ?? '').toLowerCase();
  _resolvedMcp.unshift({
    tool: entry.tool || tool || key,
    durationMs: elapsed,
    error: payload.error ?? (status && status !== 'ok' ? status : null),
    stale: opts.stale === true,
  });
  rememberSettledTool(entry.tool || tool || key, Number(opts.eventTime ?? Date.now()));
  capResolvedMcp();
  _activeTools.delete(key);
  if (entry.callId) _activeTools.delete(entry.callId);
  if (entry.tool) _activeTools.delete(entry.tool);
  if (trackKey) _activeTools.delete(trackKey);
  if (tool) _activeTools.delete(tool);
  return true;
}

function expireStaleActiveTools() {
  const now = Date.now();
  for (const [key, entry] of [..._activeTools.entries()]) {
    if (now - entry.startedAt > STALE_CALL_MS) {
      settleActiveTool(key, entry.tool, {}, { stale: true, durationMs: now - entry.startedAt });
    }
  }
}

function clearActiveToolsAsStale() {
  for (const [key, entry] of [..._activeTools.entries()]) {
    settleActiveTool(key, entry.tool, {}, { stale: true, durationMs: Date.now() - entry.startedAt });
  }
}

/* ── Mount ──────────────────────────────────────────────────────────────────── */
function mount() {
  const el = $('runtime-view');
  if (!el) return;
  el.innerHTML = `
    <div class="runtime-header">
      <div class="runtime-kpis" id="rt-kpis">
        <div class="rt-kpi" id="rt-kpi-tools">
          <div class="rt-kpi-val" id="rt-toolcalls">0</div>
          <div class="rt-kpi-lbl">Tool Calls</div>
        </div>
        <button class="rt-kpi rt-kpi-action" id="rt-kpi-saved" type="button" aria-expanded="false" aria-controls="rt-token-flyout" title="Open token telemetry">
          <div class="rt-kpi-val" id="rt-tokens-saved">0</div>
          <div class="rt-kpi-lbl">Tokens Saved</div>
        </button>
        <div class="rt-kpi">
          <div class="rt-kpi-val" id="rt-active-count">0</div>
          <div class="rt-kpi-lbl">Active Now</div>
        </div>
      </div>

      <div class="rt-mcp-strip" id="rt-mcp-strip"></div>
      <div class="rt-token-flyout-slot" id="rt-token-flyout-slot"></div>

      <div class="rt-filter-bar">
        <button class="rt-filter-btn active" data-filter="all">All</button>
        <button class="rt-filter-btn" data-filter="mcp">MCP</button>
        <button class="rt-filter-btn" data-filter="memory">Memory</button>
        <button class="rt-filter-btn" data-filter="tokens">Tokens</button>
        <button class="rt-filter-btn" data-filter="runtime">Runtime</button>
        <button class="rt-filter-btn" data-filter="shield">Shield</button>
        <button class="rt-filter-btn rt-filter-clear" id="rt-clear-btn">Clear</button>
      </div>
    </div>

    <div class="rt-feed-wrap">
      <div class="rt-feed" id="rt-feed">
        <div class="rt-empty">
          <div class="rt-empty-icon">⚡</div>
          <div class="rt-empty-title">Waiting for events…</div>
          <div class="rt-empty-sub">Tool calls, memory stores, token savings, and orchestration events appear here in real time.</div>
        </div>
      </div>
    </div>
  `;

  // Filter buttons
  el.querySelectorAll('.rt-filter-btn[data-filter]').forEach(btn => {
    btn.addEventListener('click', () => {
      _filter = btn.dataset.filter;
      el.querySelectorAll('.rt-filter-btn[data-filter]').forEach(b =>
        b.classList.toggle('active', b.dataset.filter === _filter));
      renderFeed();
    });
  });

  $('rt-clear-btn').addEventListener('click', () => {
    _events.length = 0;
    _totalTokensSaved = 0;
    _toolCalls = 0;
    _activeTools.clear();
    _settledToolTimes.clear();
    renderAll();
  });

  $('rt-kpi-saved')?.addEventListener('click', () => {
    _tokenFlyoutOpen = !_tokenFlyoutOpen;
    renderTokenFlyout();
    if (_tokenFlyoutOpen && !_tokenTelemetry && !_tokenFlyoutLoading) {
      void loadTokenTelemetry();
    }
  });

  _mounted = true;
  renderAll();
}

/* ── Render ─────────────────────────────────────────────────────────────────── */
function renderAll() {
  expireStaleActiveTools();
  renderKPIs();
  renderTokenFlyout();
  renderMcpStrip();
  renderFeed();
}

function renderKPIs() {
  const toolEl = $('rt-toolcalls');
  const savedEl = $('rt-tokens-saved');
  const activeEl = $('rt-active-count');
  if (toolEl) toolEl.textContent = _toolCalls.toLocaleString();
  if (savedEl) savedEl.textContent = _totalTokensSaved > 0
    ? (_totalTokensSaved >= 1000 ? `${(_totalTokensSaved / 1000).toFixed(1)}k` : String(_totalTokensSaved))
    : '0';
  if (activeEl) activeEl.textContent = String(_activeTools.size);
  const tokenBtn = $('rt-kpi-saved');
  if (tokenBtn) tokenBtn.setAttribute('aria-expanded', _tokenFlyoutOpen ? 'true' : 'false');
}

async function loadTokenTelemetry() {
  _tokenFlyoutLoading = true;
  _tokenFlyoutError = '';
  renderTokenFlyout();
  try {
    const [summary, lifetimeRaw, bySource, timeline] = await Promise.all([
      api('/api/tokens/summary', 0),
      api('/api/tokens/lifetime', 0),
      api('/api/tokens/by-source', 0),
      api('/api/tokens/timeline?limit=8', 0),
    ]);
    _tokenTelemetry = {
      summary: summary ?? {},
      lifetime: lifetimeRaw?.data ?? lifetimeRaw ?? {},
      bySource: bySource ?? {},
      timeline: Array.isArray(timeline) ? timeline : [],
    };
  } catch (err) {
    _tokenFlyoutError = err?.message || String(err);
  } finally {
    _tokenFlyoutLoading = false;
    renderTokenFlyout();
  }
}

function renderTokenFlyout() {
  const slot = $('rt-token-flyout-slot');
  if (!slot) return;
  const btn = $('rt-kpi-saved');
  if (btn) btn.setAttribute('aria-expanded', _tokenFlyoutOpen ? 'true' : 'false');
  if (!_tokenFlyoutOpen) {
    slot.innerHTML = '';
    return;
  }

  if (_tokenFlyoutLoading && !_tokenTelemetry) {
    slot.innerHTML = `<div class="rt-token-flyout" id="rt-token-flyout">
      <div class="rt-token-head"><div><div class="rt-token-title">Token telemetry</div><div class="rt-token-sub">Loading current runtime ledger...</div></div><button class="rt-token-close" id="rt-token-close" type="button">Close</button></div>
    </div>`;
  } else if (_tokenFlyoutError) {
    slot.innerHTML = `<div class="rt-token-flyout" id="rt-token-flyout">
      <div class="rt-token-head"><div><div class="rt-token-title">Token telemetry</div><div class="rt-token-sub rt-token-error">${esc(_tokenFlyoutError)}</div></div><button class="rt-token-close" id="rt-token-close" type="button">Close</button></div>
    </div>`;
  } else {
    const data = _tokenTelemetry ?? {};
    const summary = data.summary ?? {};
    const lifetime = data.lifetime ?? {};
    const bySource = data.bySource ?? {};
    const timeline = Array.isArray(data.timeline) ? data.timeline : [];
    const sourceRows = Object.entries(bySource).slice(0, 5).map(([source, value]) => {
      const item = typeof value === 'object' && value ? value : { savedTokens: Number(value ?? 0) };
      const saved = item.savedTokens ?? item.tokensSaved ?? item.saved ?? 0;
      const gross = item.grossInputTokens ?? item.tokensOptimized ?? item.gross ?? 0;
      return `<div class="rt-token-row"><span>${esc(source)}</span><strong>${fmtTokens(saved)}</strong><span>${fmtTokens(gross)} gross</span></div>`;
    }).join('') || '<div class="rt-token-muted">No source ledger yet.</div>';
    const timelineRows = timeline.slice(0, 6).map((item) => {
      const saved = item.savedTokens ?? item.tokensSaved ?? item.saved ?? 0;
      const run = item.runId ?? item.sessionId ?? item.source ?? 'runtime';
      return `<div class="rt-token-row"><span>${esc(String(run).slice(0, 18))}</span><strong>${fmtTokens(saved)}</strong><span>${fmtTime(item.timestamp ?? item.ts ?? item.time)}</span></div>`;
    }).join('') || '<div class="rt-token-muted">No recent token events yet.</div>';
    slot.innerHTML = `<div class="rt-token-flyout" id="rt-token-flyout">
      <div class="rt-token-head">
        <div>
          <div class="rt-token-title">Token telemetry</div>
          <div class="rt-token-sub">Runtime ledger, lifetime savings, and live SSE savings for this tab.</div>
        </div>
        <button class="rt-token-close" id="rt-token-close" type="button">Close</button>
      </div>
      <div class="rt-token-grid">
        <div class="rt-token-stat"><span>Recent saved</span><strong>${fmtTokens(summary.savedTokens ?? summary.saved)}</strong></div>
        <div class="rt-token-stat"><span>Recent compression</span><strong>${fmtPct(summary.compressionPct)}</strong></div>
        <div class="rt-token-stat"><span>Lifetime saved</span><strong>${fmtTokens(lifetime.savedTokens ?? lifetime.totalSaved)}</strong></div>
        <div class="rt-token-stat"><span>Live tab saved</span><strong>${fmtTokens(_totalTokensSaved)}</strong></div>
      </div>
      <div class="rt-token-columns">
        <div><div class="rt-token-section-title">By source</div>${sourceRows}</div>
        <div><div class="rt-token-section-title">Recent runs</div>${timelineRows}</div>
      </div>
    </div>`;
  }

  $('rt-token-close')?.addEventListener('click', () => {
    _tokenFlyoutOpen = false;
    renderTokenFlyout();
    renderKPIs();
  });
}

function renderMcpStrip() {
  const strip = $('rt-mcp-strip');
  if (!strip) return;
  const now = Date.now();

  const inflight = [..._activeTools.values()].map(({ tool, startedAt }) => {
    const ms = now - startedAt;
    const cls = ms > LONG_CALL_MS ? 'rt-mcp-pill rt-mcp-long' : 'rt-mcp-pill rt-mcp-active';
    return `<div class="${cls}">
      <span class="rt-pulse"></span>
      <span class="rt-mcp-tool">${esc(tool)}</span>
      <span class="rt-mcp-elapsed">${humanMs(ms)}</span>
    </div>`;
  });

  const resolved = _resolvedMcp.slice(0, MAX_RESOLVED_MCP).map(({ tool, durationMs, error, stale }) => {
    const cls = error ? 'rt-mcp-pill rt-mcp-err' : stale ? 'rt-mcp-pill rt-mcp-long' : 'rt-mcp-pill rt-mcp-done';
    const badge = error
      ? `<span class="rt-mcp-badge rt-mcp-badge-err">err</span>`
      : stale
        ? `<span class="rt-mcp-badge rt-mcp-badge-ok">stale</span>`
      : `<span class="rt-mcp-badge rt-mcp-badge-ok">${humanMs(durationMs)}</span>`;
    return `<div class="${cls}">${esc(tool)}${badge}</div>`;
  });

  if (inflight.length === 0 && resolved.length === 0) {
    strip.innerHTML = '';
    return;
  }

  strip.innerHTML = `<div class="rt-mcp-strip-inner">${inflight.join('')}${resolved.join('')}</div>`;
}

// Keep elapsed timer ticking while tools are active
function ensurePulseTimer() {
  if (_pulseTimer || _activeTools.size === 0) return;
  _pulseTimer = setInterval(() => {
    expireStaleActiveTools();
    if (_activeTools.size === 0) {
      clearInterval(_pulseTimer);
      _pulseTimer = null;
    }
    renderMcpStrip();
  }, 500);
}

function eventMatchesFilter(ev) {
  if (_filter === 'all') return true;
  return ev.category === _filter;
}

function renderFeed() {
  const feed = $('rt-feed');
  if (!feed) return;

  const visible = _events.filter(eventMatchesFilter);
  if (visible.length === 0) {
    feed.innerHTML = `<div class="rt-empty">
      <div class="rt-empty-icon">⚡</div>
      <div class="rt-empty-title">${_events.length > 0 ? 'No events match filter' : 'Waiting for events…'}</div>
      <div class="rt-empty-sub">Tool calls, memory stores, token savings, and orchestration events appear here.</div>
    </div>`;
    return;
  }

  // Render newest-first
  feed.innerHTML = [...visible].reverse().map(ev => eventCard(ev)).join('');
}

function eventCard(ev) {
  const meta = CATEGORY_META[ev.category] ?? CATEGORY_META.system;
  const sev = ev.severityClass;
  const timeStr = ev.timeStr;
  const saved = ev.tokensSaved > 0
    ? `<span class="rt-ev-badge rt-badge-tokens">-${ev.tokensSaved.toLocaleString()} tok</span>` : '';
  const dur = ev.durationMs > 0
    ? `<span class="rt-ev-badge rt-badge-dur">${humanMs(ev.durationMs)}</span>` : '';
  const errBadge = ev.error
    ? `<span class="rt-ev-badge rt-badge-err">error</span>` : '';

  return `<div class="rt-ev ${sev}">
    <div class="rt-ev-lead">
      <span class="rt-ev-icon" style="color:${meta.color}">${meta.icon}</span>
      <span class="rt-ev-type">${esc(ev.label)}</span>
      ${saved}${dur}${errBadge}
    </div>
    <div class="rt-ev-detail">${esc(ev.detail)}</div>
    <div class="rt-ev-time">${timeStr}</div>
  </div>`;
}

/* ── Toast system ───────────────────────────────────────────────────────────── */
function showToast(msg, kind = 'info', durationMs = 4000) {
  let container = $('rt-toast-container');
  if (!container) {
    container = document.createElement('div');
    container.id = 'rt-toast-container';
    container.className = 'rt-toast-container';
    document.body.appendChild(container);
  }
  const toast = document.createElement('div');
  toast.className = `rt-toast rt-toast-${kind}`;
  toast.textContent = msg;
  container.appendChild(toast);
  setTimeout(() => {
    toast.classList.add('rt-toast-fade');
    setTimeout(() => toast.remove(), 400);
  }, durationMs);
}

function showUpgradeNudge(msg, ctaUrl) {
  let bar = $('rt-upgrade-bar');
  if (bar) return; // already showing
  bar = document.createElement('div');
  bar.id = 'rt-upgrade-bar';
  bar.className = 'rt-upgrade-bar';
  bar.innerHTML = `<span class="rt-upgrade-msg">${esc(msg)}</span>
    <a class="rt-upgrade-btn" href="${esc(ctaUrl)}" target="_blank" rel="noopener">Upgrade ↗</a>
    <button class="rt-upgrade-dismiss" onclick="document.getElementById('rt-upgrade-bar')?.remove()">✕</button>`;
  const view = $('runtime-view');
  if (view) view.prepend(bar);
}

/* ── Ingest SSE event ────────────────────────────────────────────────────────── */
export function ingestEvent(evt) {
  const { type = '', payload = {} } = evt;
  const category = categoryFor(type);
  const tool = toolNameFromPayload(payload);
  const eventTime = eventMillis(evt, payload);
  const tokensSaved = Number(payload.tokensSaved ?? evt.tokensSaved ?? 0);
  const durationMs = Number(payload.durationMs ?? 0);
  const phase = String(payload.phase ?? payload.stage ?? '');

  // Track active tools keyed by callId (or tool name as fallback)
  const callId = String(payload.callId ?? '');
  const trackKey = callId || tool;
  if (type === 'mcp.call.start' && trackKey) {
    if (!wasSettledAfter(tool, eventTime)) {
      _activeTools.set(trackKey, { tool, startedAt: Date.now(), callId });
      _toolCalls++;
      ensurePulseTimer();
    }
  } else if (
    type === 'mcp.call.complete' ||
    type === 'mcp.handler.complete' ||
    type === 'mcp.handler.failed' ||
    (type === 'tool.invocation' && trackKey)
  ) {
    if (!settleActiveTool(trackKey, tool, payload, { durationMs, eventTime })) {
      rememberSettledTool(tool || trackKey, eventTime);
    }
  } else if (type === 'nexus.shutdown' || type === 'orchestrator.disposed') {
    clearActiveToolsAsStale();
  }

  // Toast for license events
  if (type === 'license.tierChanged') {
    showToast(`License: ${String(payload.from ?? '?')} → ${String(payload.to ?? '?').toUpperCase()}`, 'info', 5000);
  }
  if (type === 'license.upgradeNudge') {
    showUpgradeNudge(String(payload.message ?? ''), String(payload.ctaUrl ?? ''));
  }
  if (type === 'orchestrator.run.start') {
    showToast(`Orchestrating: ${String(payload.goal ?? '').slice(0, 60)}…`, 'info', 3000);
  }

  if (tokensSaved > 0) _totalTokensSaved += tokensSaved;

  // Build human-readable label + detail
  let label = type;
  let detail = '';
  switch (type) {
    case 'mcp.call.start':
      label = `${tool || 'tool'} · start`;
      detail = phase || String(payload.goal ?? '').slice(0, 80);
      break;
    case 'mcp.call.complete':
      label = `${tool || 'tool'} · done`;
      detail = payload.error ? String(payload.error).slice(0, 80) : String(payload.summary ?? '').slice(0, 80);
      break;
    case 'tool.invocation':
      label = `invoke · ${tool || String(payload.name ?? 'tool')}`;
      detail = String(payload.args ?? '').slice(0, 80);
      break;
    case 'memory.store':
      label = 'memory · store';
      detail = [
        payload.id ? `id ${String(payload.id).slice(0, 12)}` : null,
        payload.tier ? `tier ${payload.tier}` : null,
        payload.priority != null ? `priority ${Number(payload.priority).toFixed(2)}` : null,
        Array.isArray(payload.tags) && payload.tags.length ? `tags ${payload.tags.slice(0, 3).join(', ')}` : null,
      ].filter(Boolean).join(' · ') || String(payload.key ?? payload.content ?? '').slice(0, 80);
      break;
    case 'memory.recall':
      label = 'memory · recall';
      detail = [
        `${Number(payload.count ?? 0)} recalled`,
        payload.crossClient ? 'cross-client' : null,
        String(payload.query ?? '').slice(0, 60),
      ].filter(Boolean).join(' · ');
      break;
    case 'tokens.optimized': {
      const src = payload.source ? ` · ${payload.source}` : '';
      label = `tokens · optimized${src}`;
      const savings = Number(payload.savings ?? 0);
      detail = savings > 0
        ? `${savings.toLocaleString()} tokens saved · ${payload.pct ?? 0}% compression`
        : 'optimizer ran (no candidate files)';
      break;
    }
    case 'planner.stage':
      label = `planner · ${phase || 'stage'}`;
      detail = String(payload.description ?? payload.owner ?? '').slice(0, 80);
      break;
    case 'phantom.worker.start':
      label = 'phantom · worker start';
      detail = String(payload.workerId ?? '').slice(0, 40);
      break;
    case 'phantom.worker.complete':
      label = 'phantom · worker done';
      detail = String(payload.workerId ?? '').slice(0, 40);
      break;
    case 'dashboard.action':
      label = `action · ${String(payload.action ?? '').replace('orchestrate.', '')}`;
      detail = String(payload.target ?? payload.status ?? '').slice(0, 80);
      break;
    case 'guardrail.check':
      label = `shield · ${payload.passed ? 'passed' : 'blocked'}`;
      detail = String(payload.rule ?? payload.reason ?? '').slice(0, 80);
      break;
    case 'install.step':
      label = `install · step ${payload.step ?? '?'}/${payload.total ?? '?'} · ${payload.status ?? ''}`;
      detail = String(payload.label ?? payload.detail ?? '').slice(0, 80);
      break;
    case 'license.tierChanged':
      label = 'license · tier changed';
      detail = `${payload.from ?? '?'} → ${payload.to ?? '?'} (${payload.reason ?? ''})`;
      break;
    case 'license.upgradeNudge':
      label = 'license · upgrade nudge';
      detail = String(payload.message ?? '').slice(0, 80);
      break;
    case 'orchestrator.run.start':
      label = 'orchestrator · run start';
      detail = String(payload.goal ?? '').slice(0, 80);
      break;
    case 'orchestrator.run.complete':
      label = 'orchestrator · run complete';
      detail = payload.durationMs ? humanMs(Number(payload.durationMs)) : '';
      break;
    default:
      label = type.replace(/\./g, ' · ');
      detail = String(payload.message ?? payload.source ?? '').slice(0, 80);
  }

  const now = new Date();
  const timeStr = now.toTimeString().slice(0, 8);

  _events.push({
    id: `${Date.now()}_${Math.random()}`,
    type, category, label, detail, timeStr,
    tokensSaved, durationMs,
    error: payload.error ?? null,
    severityClass: severityClass(type, payload),
  });

  if (_events.length > MAX_EVENTS) _events.splice(0, _events.length - MAX_EVENTS);

  if (_mounted && S.tab === 'runtime') {
    renderAll();
  }
}

/* ── Public API ─────────────────────────────────────────────────────────────── */
export function load() {
  if (!_mounted) mount();
  else renderAll();
}

export function render() {
  if (_mounted) renderAll();
}
