// ── ANSI terminal renderer ────────────────────────────────────────────────────

const ANSI_COLOR: Record<string, string> = {
  '30': '#4a4a4a', '31': 'var(--red)', '32': 'var(--green)',
  '33': 'var(--gold)', '34': '#60a5fa', '35': '#c084fc',
  '36': 'var(--cyan)', '37': '#e5e7eb',
  '90': '#6b7280', '91': '#f87171', '92': '#4ade80',
  '93': '#facc15', '94': '#818cf8', '95': '#e879f9',
  '96': '#22d3ee', '97': '#f9fafb',
};

export function ansiToHtml(text: string): string {
  let html = '';
  let open = false;
  for (const chunk of text.split(/(\x1b\[[0-9;]*m)/)) {
    const m = chunk.match(/^\x1b\[([0-9;]*)m$/);
    if (m) {
      if (open) { html += '</span>'; open = false; }
      const codes = m[1].split(';');
      for (const c of codes) {
        if (c === '' || c === '0') break;
        if (c === '1') { html += '<span style="font-weight:700">'; open = true; break; }
        if (c === '2') { html += '<span style="opacity:0.55">'; open = true; break; }
        if (ANSI_COLOR[c]) { html += `<span style="color:${ANSI_COLOR[c]}"`; open = true; break; }
      }
      if (open && !html.endsWith('>')) html += '>';
    } else if (chunk) {
      html += chunk.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
    }
  }
  if (open) html += '</span>';
  return html;
}

export function fmt(n: number | null | undefined, decimals = 1, suffix = ''): string {
  if (n == null) return '—';
  return n.toFixed(decimals) + suffix;
}

export function fmtPct(n: number | null | undefined, decimals = 1): string {
  if (n == null) return '—';
  return `${n.toFixed(decimals)}%`;
}

export function fmtDate(s: string | null | undefined): string {
  if (!s) return '—';
  return new Date(s).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: '2-digit' });
}
