// assets/ui.jsx — shared primitives, icons, simple chart helpers
// Exposes globals at the bottom (window.Icon, window.Badge, etc.) so other
// Babel script files can use them.

// ─── Icon set (inline SVG, Lucide-aligned paths) ───────────────────────────
const ICON_PATHS = {
  // nav
  dashboard:    'M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z',
  sessions:     'M3 5h18v4H3zM3 11h18v4H3zM3 17h12v4H3z',
  trace:        'M4 4v16M4 8h6a3 3 0 013 3v0a3 3 0 003 3h4M14 14l4-4M14 14l4 4',
  hook:         'M9 3v9a3 3 0 006 0V9M9 21h6',
  deploy:       'M4 17l8-13 8 13M4 17l8 4 8-4M4 17l8-4 8 4',
  cost:         'M12 2v20M17 5H9.5a3.5 3.5 0 100 7h5a3.5 3.5 0 110 7H6',
  alert:        'M12 9v4M12 17h.01M10.3 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z',
  bell:         'M6 8a6 6 0 0112 0c0 7 3 9 3 9H3s3-2 3-9M13.73 21a2 2 0 01-3.46 0',
  health:       'M3.05 11a9 9 0 1117.9 0M22 12h-3l-2-9-4 18-2-9H1',
  tools:        'M14.7 6.3a4 4 0 00-5.4 5.4l-7 7 2 2 7-7a4 4 0 005.4-5.4l-2.3 2.3-2.5-2.5 2.8-2.8z',
  // ui
  search:       'M21 21l-4.35-4.35M11 19a8 8 0 100-16 8 8 0 000 16z',
  filter:       'M22 3H2l8 9.46V19l4 2v-8.54L22 3z',
  download:     'M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3',
  copy:         'M16 4h2a2 2 0 012 2v14a2 2 0 01-2 2H6a2 2 0 01-2-2v-2M16 4a2 2 0 00-2-2h-4a2 2 0 00-2 2M16 4v4H8V4',
  external:     'M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6M15 3h6v6M10 14L21 3',
  chevron:      'M9 18l6-6-6-6',
  chevronD:     'M6 9l6 6 6-6',
  more:         'M12 12h.01M19 12h.01M5 12h.01',
  x:            'M18 6L6 18M6 6l12 12',
  plus:         'M12 5v14M5 12h14',
  check:        'M20 6L9 17l-5-5',
  arrowUp:      'M12 19V5M5 12l7-7 7 7',
  arrowDown:    'M12 5v14M5 12l7 7 7-7',
  arrowR:       'M5 12h14M12 5l7 7-7 7',
  refresh:      'M23 4v6h-6M1 20v-6h6M20.49 9A9 9 0 005.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 013.51 15',
  sun:          'M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42M12 17a5 5 0 100-10 5 5 0 000 10z',
  moon:         'M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z',
  user:         'M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2M12 11a4 4 0 100-8 4 4 0 000 8z',
  settings:     'M12 15a3 3 0 100-6 3 3 0 000 6zM19.4 15a1.7 1.7 0 00.34 1.87l.06.06a2 2 0 11-2.83 2.83l-.06-.06a1.7 1.7 0 00-1.87-.34 1.7 1.7 0 00-1 1.55V21a2 2 0 01-4 0v-.09a1.7 1.7 0 00-1.11-1.55 1.7 1.7 0 00-1.87.34l-.06.06a2 2 0 01-2.83-2.83l.06-.06a1.7 1.7 0 00.34-1.87 1.7 1.7 0 00-1.55-1H3a2 2 0 010-4h.09A1.7 1.7 0 004.64 9a1.7 1.7 0 00-.34-1.87l-.06-.06a2 2 0 012.83-2.83l.06.06a1.7 1.7 0 001.87.34H9a1.7 1.7 0 001-1.55V3a2 2 0 014 0v.09a1.7 1.7 0 001 1.55 1.7 1.7 0 001.87-.34l.06-.06a2 2 0 012.83 2.83l-.06.06a1.7 1.7 0 00-.34 1.87V9a1.7 1.7 0 001.55 1H21a2 2 0 010 4h-.09a1.7 1.7 0 00-1.55 1z',
  command:      'M18 3a3 3 0 00-3 3v12a3 3 0 003 3 3 3 0 003-3 3 3 0 00-3-3H6a3 3 0 00-3 3 3 3 0 003 3 3 3 0 003-3V6a3 3 0 00-3-3 3 3 0 00-3 3 3 3 0 003 3h12a3 3 0 003-3 3 3 0 00-3-3z',
  panelLeft:    'M3 3h18v18H3zM9 3v18',
  zap:          'M13 2L3 14h9l-1 8 10-12h-9l1-8z',
  cpu:          'M4 4h16v16H4zM9 9h6v6H9zM1 9h3M1 14h3M20 9h3M20 14h3M9 1v3M14 1v3M9 20v3M14 20v3',
  database:     'M12 2c4.97 0 9 1.34 9 3v14c0 1.66-4.03 3-9 3s-9-1.34-9-3V5c0-1.66 4.03-3 9-3zM3 5c0 1.66 4.03 3 9 3s9-1.34 9-3M3 12c0 1.66 4.03 3 9 3s9-1.34 9-3',
  globe:        'M12 2a10 10 0 100 20 10 10 0 000-20zM2 12h20M12 2a15 15 0 010 20M12 2a15 15 0 000 20',
  play:         'M5 3l14 9-14 9V3z',
  pause:        'M6 4h4v16H6zM14 4h4v16h-4z',
  fileText:     'M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8l-6-6zM14 2v6h6M16 13H8M16 17H8M10 9H8',
  loop:         'M17 1l4 4-4 4M3 11V9a4 4 0 014-4h14M7 23l-4-4 4-4M21 13v2a4 4 0 01-4 4H3',
  ban:          'M12 22a10 10 0 100-20 10 10 0 000 20zM4.93 4.93l14.14 14.14',
  shield:       'M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z',
  zapOff:       'M12.41 6.75L13 2l-2.43 2.92M18.57 12.91L21 10h-5.34M8 8l-5 6h9l-1 8 5-6',
  link:         'M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71',
  message:      'M21 11.5a8.4 8.4 0 01-.9 3.8 8.5 8.5 0 01-7.6 4.7 8.4 8.4 0 01-3.8-.9L3 21l1.9-5.7a8.4 8.4 0 01-.9-3.8 8.5 8.5 0 014.7-7.6 8.4 8.4 0 013.8-.9h.5a8.5 8.5 0 018 8v.5z',
  mail:         'M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2zM22 6l-10 7L2 6',
  discord:      'M19 5a16 16 0 00-4-1 11 11 0 00-.5 1 14 14 0 00-5 0 11 11 0 00-.5-1 16 16 0 00-4 1c-2.5 4-3 8-2.5 12a16 16 0 005 2.5l1-1.5a10 10 0 01-2-1l.5-.4a12 12 0 0010 0l.5.4a10 10 0 01-2 1l1 1.5a16 16 0 005-2.5c.5-4-.5-8-2.5-12zM9 14c-1 0-2-1-2-2s1-2 2-2 2 1 2 2-1 2-2 2zm6 0c-1 0-2-1-2-2s1-2 2-2 2 1 2 2-1 2-2 2z',
  github:       'M9 19c-5 1.5-5-2.5-7-3M15 22v-3.9a3.4 3.4 0 00-.9-2.5c3-.3 6.1-1.5 6.1-6.6a5.1 5.1 0 00-1.4-3.5 4.8 4.8 0 00-.1-3.5s-1.1-.3-3.7 1.4a12.6 12.6 0 00-6.6 0c-2.6-1.7-3.7-1.4-3.7-1.4a4.8 4.8 0 00-.1 3.5 5.1 5.1 0 00-1.4 3.6c0 5 3 6.2 6 6.5a3.4 3.4 0 00-.9 2.5V22',
  trash:        'M3 6h18M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2M10 11v6M14 11v6',
  edit:         'M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z',
  send:         'M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z',
  eye:          'M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8zM12 15a3 3 0 100-6 3 3 0 000 6z',
  eyeOff:       'M17.94 17.94A10.06 10.06 0 0112 20c-7 0-11-8-11-8a18.45 18.45 0 015.06-5.94M9.9 4.24A9.12 9.12 0 0112 4c7 0 11 8 11 8a18.5 18.5 0 01-2.16 3.19M14.12 14.12a3 3 0 11-4.24-4.24M1 1l22 22',
  spinner:      'M21 12a9 9 0 11-6.22-8.56',
  check2:       'M22 11.08V12a10 10 0 11-5.93-9.14M22 4L12 14.01l-3-3',
  layers:       'M12 2l-10 5 10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5',
  package:      'M16.5 9.4l-9-5.19M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 003 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0021 16zM3.27 6.96L12 12.01l8.73-5.05M12 22.08V12',
  trend:        'M23 6l-9.5 9.5-5-5L1 18M17 6h6v6',
  flame:        'M8.5 14.5A2.5 2.5 0 0011 12c0-1.38-.5-2-1-3-1.07-2.14-.71-4.66 1-6 0 4 2 6 4 8a4 4 0 11-6.5 4.5z',
  filter2:      'M3 6h18M6 12h12M10 18h4',
};

function Icon({ name, size = 16, strokeWidth = 2, style, className = '', title }) {
  const path = ICON_PATHS[name];
  if (!path) return null;
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none"
      stroke="currentColor" strokeWidth={strokeWidth} strokeLinecap="round" strokeLinejoin="round"
      style={style} className={className} aria-label={title}>
      <path d={path} />
    </svg>
  );
}

// ─── Badge ────────────────────────────────────────────────────────────────
function Badge({ children, kind = 'neutral', outline = false, dot = false, mono = true, style }) {
  const cls = `badge ${outline ? 'outline' : kind}`;
  return (
    <span className={cls} style={mono ? null : { fontFamily: 'var(--font-sans)', ...style }}>
      {dot && <span className="dot" />}
      {children}
    </span>
  );
}

// Status badge for sessions/deployments
function StatusBadge({ status }) {
  const map = {
    running:    ['info', '⟳', 'live-pulse'],
    completed:  ['success', '●'],
    succeeded:  ['success', '●'],
    failed:     ['danger', '●'],
    pending:    ['warning', '●'],
    building:   ['info', '⟳', 'live-pulse'],
    queued:     ['neutral', '○'],
    deployed:   ['success', '●'],
    sanitized:  ['accent', '●'],
    aborted:    ['neutral', '●'],
  };
  const [k, mark, anim] = map[status] || ['neutral', '●'];
  return (
    <span className={`badge ${k}`}>
      <span className={anim} style={{ fontSize: 9, lineHeight: 1 }}>{mark}</span>
      {status}
    </span>
  );
}

// ─── KPI Card ─────────────────────────────────────────────────────────────
function KpiCard({ label, value, unit, delta, deltaLabel, sparkData, sparkColor, icon }) {
  const dir = delta == null ? 'neutral' : delta > 0 ? 'up' : delta < 0 ? 'down' : 'neutral';
  return (
    <div className="card kpi">
      <div className="kpi-label">
        {icon && <Icon name={icon} size={12} />}
        {label}
      </div>
      <div className="kpi-value">
        {value}
        {unit && <span className="unit">{unit}</span>}
      </div>
      {delta != null && (
        <div className={`kpi-delta ${dir}`}>
          <Icon name={delta > 0 ? 'arrowUp' : 'arrowDown'} size={10} />
          {Math.abs(delta)}% {deltaLabel || 'vs prev'}
        </div>
      )}
      {sparkData && <Sparkline data={sparkData} color={sparkColor} />}
    </div>
  );
}

// ─── Charts (SVG, no deps) ────────────────────────────────────────────────

function Sparkline({ data, color = 'var(--accent)', height = 28, width = '100%', area = true }) {
  if (!data || data.length === 0) return null;
  const w = 200; // viewBox width, scales via CSS
  const h = height;
  const max = Math.max(...data, 1);
  const min = Math.min(...data, 0);
  const range = max - min || 1;
  const step = w / (data.length - 1 || 1);
  const pts = data.map((v, i) => [i * step, h - ((v - min) / range) * (h - 4) - 2]);
  const linePath = 'M' + pts.map(p => p.join(',')).join(' L');
  const areaPath = linePath + ` L${w},${h} L0,${h} Z`;
  return (
    <svg className="kpi-spark" viewBox={`0 0 ${w} ${h}`} preserveAspectRatio="none" style={{ width, height, display: 'block' }}>
      {area && <path d={areaPath} fill={color} opacity=".15" />}
      <path d={linePath} stroke={color} strokeWidth="1.5" fill="none" vectorEffect="non-scaling-stroke" />
    </svg>
  );
}

// LineChart with multiple series + axis
function LineChart({ series, labels, height = 200, yFmt = v => v, showGrid = true, smooth = false, area = false, kind = 'line' }) {
  const w = 800, h = height;
  const padL = 40, padR = 12, padT = 12, padB = 28;
  const innerW = w - padL - padR, innerH = h - padT - padB;
  if (!series || series.length === 0) return null;
  const n = series[0].data.length;
  const max = Math.max(...series.flatMap(s => s.data), 1);
  const min = 0;
  const range = max - min || 1;
  const xStep = innerW / Math.max(n - 1, 1);
  const x = i => padL + i * xStep;
  const y = v => padT + innerH - ((v - min) / range) * innerH;

  // Y ticks (4)
  const yTicks = [0, 0.25, 0.5, 0.75, 1].map(p => min + p * range);

  return (
    <svg viewBox={`0 0 ${w} ${h}`} style={{ width: '100%', height, display: 'block' }}>
      {showGrid && yTicks.map((v, i) => (
        <line key={i} x1={padL} x2={w - padR} y1={y(v)} y2={y(v)} stroke="var(--border-subtle)" strokeWidth="1" />
      ))}
      {yTicks.map((v, i) => (
        <text key={'lt'+i} x={padL - 6} y={y(v) + 3} textAnchor="end" fontSize="9.5" fill="var(--text-tertiary)" fontFamily="var(--font-mono)">{yFmt(v)}</text>
      ))}
      {labels && labels.map((l, i) => (
        (i % Math.ceil(n / 8) === 0 || i === n - 1) &&
        <text key={'xt'+i} x={x(i)} y={h - 8} textAnchor="middle" fontSize="9.5" fill="var(--text-tertiary)" fontFamily="var(--font-mono)">{l}</text>
      ))}
      {series.map((s, si) => {
        const pts = s.data.map((v, i) => [x(i), y(v)]);
        const path = 'M' + pts.map(p => p.join(',')).join(' L');
        const color = s.color || `var(--chart-${(si % 8) + 1})`;
        const fill = area ? (color.startsWith('var') ? color : color) : 'none';
        return (
          <g key={si}>
            {kind === 'area' && <path d={path + ` L${x(n-1)},${y(0)} L${x(0)},${y(0)} Z`} fill={color} opacity=".18" />}
            <path d={path} stroke={color} strokeWidth="1.5" fill="none" />
          </g>
        );
      })}
    </svg>
  );
}

function StackedBars({ data, labels, keys, colors, height = 200, yFmt = v => v }) {
  const w = 800, h = height;
  const padL = 40, padR = 12, padT = 12, padB = 28;
  const innerW = w - padL - padR, innerH = h - padT - padB;
  const totals = data.map(d => keys.reduce((s, k) => s + (d[k] || 0), 0));
  const max = Math.max(...totals, 1);
  const xStep = innerW / data.length;
  const barW = xStep * 0.7;

  const yTicks = [0, 0.25, 0.5, 0.75, 1].map(p => p * max);
  return (
    <svg viewBox={`0 0 ${w} ${h}`} style={{ width: '100%', height, display: 'block' }}>
      {yTicks.map((v, i) => (
        <g key={i}>
          <line x1={padL} x2={w - padR} y1={padT + innerH - (v / max) * innerH} y2={padT + innerH - (v / max) * innerH} stroke="var(--border-subtle)" />
          <text x={padL - 6} y={padT + innerH - (v / max) * innerH + 3} textAnchor="end" fontSize="9.5" fill="var(--text-tertiary)" fontFamily="var(--font-mono)">{yFmt(v)}</text>
        </g>
      ))}
      {data.map((d, i) => {
        let yAcc = padT + innerH;
        return (
          <g key={i} transform={`translate(${padL + i * xStep + (xStep - barW) / 2}, 0)`}>
            {keys.map((k, ki) => {
              const v = d[k] || 0;
              const bh = (v / max) * innerH;
              yAcc -= bh;
              return <rect key={k} x={0} y={yAcc} width={barW} height={Math.max(bh, 0.5)} fill={colors[ki] || `var(--chart-${(ki%8)+1})`} />;
            })}
            {labels[i] && (i % Math.ceil(data.length / 12) === 0 || i === data.length - 1) && (
              <text x={barW / 2} y={h - 8} textAnchor="middle" fontSize="9.5" fill="var(--text-tertiary)" fontFamily="var(--font-mono)">{labels[i]}</text>
            )}
          </g>
        );
      })}
    </svg>
  );
}

function HBarChart({ data, valueFmt = v => v, height = 'auto', maxBars = 10, color = 'var(--chart-1)' }) {
  // data: [{label, value, color?}]
  const max = Math.max(...data.map(d => d.value), 1);
  return (
    <div className="hbar-chart" style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
      {data.slice(0, maxBars).map((d, i) => (
        <div key={i} style={{ display: 'grid', gridTemplateColumns: '160px 1fr 60px', alignItems: 'center', gap: 8, fontSize: 11.5 }}>
          <div style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontFamily: 'var(--font-mono)', color: 'var(--text-secondary)' }} title={d.label}>{d.label}</div>
          <div style={{ background: 'var(--bg-elevated)', borderRadius: 3, height: 14, position: 'relative', overflow: 'hidden' }}>
            <div style={{ width: `${(d.value / max) * 100}%`, height: '100%', background: d.color || color, borderRadius: 3, transition: 'width .3s' }} />
          </div>
          <div style={{ textAlign: 'right', fontFamily: 'var(--font-mono)', color: 'var(--text-primary)', fontVariantNumeric: 'tabular-nums' }}>{valueFmt(d.value)}</div>
        </div>
      ))}
    </div>
  );
}

function Donut({ segments, size = 120, thickness = 18, centerLabel, centerSub }) {
  const total = segments.reduce((s, x) => s + x.value, 0) || 1;
  const r = (size - thickness) / 2;
  const c = 2 * Math.PI * r;
  let acc = 0;
  return (
    <div style={{ position: 'relative', width: size, height: size, flexShrink: 0 }}>
      <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} style={{ transform: 'rotate(-90deg)' }}>
        <circle cx={size/2} cy={size/2} r={r} fill="none" stroke="var(--bg-elevated)" strokeWidth={thickness} />
        {segments.map((s, i) => {
          const len = (s.value / total) * c;
          const off = acc;
          acc += len;
          return (
            <circle key={i} cx={size/2} cy={size/2} r={r} fill="none"
              stroke={s.color || `var(--chart-${(i%8)+1})`}
              strokeWidth={thickness}
              strokeDasharray={`${len} ${c - len}`}
              strokeDashoffset={-off} />
          );
        })}
      </svg>
      {centerLabel && (
        <div style={{ position: 'absolute', inset: 0, display: 'grid', placeItems: 'center', textAlign: 'center' }}>
          <div>
            <div style={{ fontSize: 18, fontWeight: 600, letterSpacing: '-.02em' }}>{centerLabel}</div>
            {centerSub && <div style={{ fontSize: 10, color: 'var(--text-tertiary)', fontFamily: 'var(--font-mono)' }}>{centerSub}</div>}
          </div>
        </div>
      )}
    </div>
  );
}

function Heatmap({ rows, cols, getValue, getLabel, max, colorScale = ['var(--bg-elevated)', 'var(--accent)'] }) {
  const m = max || Math.max(1, ...rows.flatMap(r => cols.map(c => getValue(r, c))));
  return (
    <div style={{ display: 'inline-grid', gridTemplateColumns: `repeat(${cols.length}, 14px)`, gap: 2 }}>
      {rows.map((r, ri) => cols.map((c, ci) => {
        const v = getValue(r, c);
        const alpha = v === 0 ? 0 : (0.12 + (v / m) * 0.88);
        return <div key={`${ri}-${ci}`} title={getLabel?.(r, c, v) || `${v}`}
          style={{ width: 14, height: 14, borderRadius: 2,
            background: v === 0 ? 'var(--bg-elevated)' : `color-mix(in oklab, ${colorScale[1]} ${alpha * 100}%, transparent)`,
            border: '1px solid var(--border-subtle)'
          }} />;
      }))}
    </div>
  );
}

// ─── Misc ─────────────────────────────────────────────────────────────────
function Mono({ children, dim }) {
  return <span className="mono" style={{ color: dim ? 'var(--text-tertiary)' : undefined, fontSize: 11.5 }}>{children}</span>;
}

function Pill({ children, color = 'neutral' }) {
  return <span className={`badge ${color}`}>{children}</span>;
}

function CopyButton({ text, label = 'Copy' }) {
  const [copied, setCopied] = React.useState(false);
  return (
    <button className="btn xs ghost" onClick={(e) => { e.stopPropagation(); navigator.clipboard?.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 1200); }}>
      <Icon name={copied ? 'check' : 'copy'} size={12} />
      {copied ? 'Copied' : label}
    </button>
  );
}

// Truncate id (Posthog style: 8 chars + ellipsis)
function shortId(id, n = 8) {
  if (!id) return '';
  return id.length <= n + 1 ? id : id.slice(0, n) + '…';
}

function formatRel(ts) {
  const now = Date.now();
  const diff = Math.floor((now - ts) / 1000);
  if (diff < 60) return diff + 's ago';
  if (diff < 3600) return Math.floor(diff / 60) + 'm ago';
  if (diff < 86400) return Math.floor(diff / 3600) + 'h ago';
  return Math.floor(diff / 86400) + 'd ago';
}

function formatDur(secs) {
  if (secs == null) return '—';
  if (secs < 60) return secs.toFixed(1) + 's';
  const m = Math.floor(secs / 60);
  const s = Math.floor(secs % 60);
  return `${m}m ${s.toString().padStart(2, '0')}s`;
}

function formatMoney(v, prec = 2) {
  if (v == null) return '—';
  return '$' + v.toFixed(prec);
}

function classFor(num, thresholds) {
  // [warnAt, dangerAt]
  if (num >= thresholds[1]) return 'text-danger';
  if (num >= thresholds[0]) return 'text-warning';
  return 'text-success';
}

// Page-level toast manager (very small)
function useToasts() {
  const [toasts, setToasts] = React.useState([]);
  const push = React.useCallback((t) => {
    const id = Math.random().toString(36).slice(2);
    setToasts(prev => [...prev, { ...t, id }]);
    setTimeout(() => setToasts(prev => prev.filter(x => x.id !== id)), t.duration || 3000);
  }, []);
  const ui = (
    <div className="toast-stack">
      {toasts.map(t => <div key={t.id} className={`toast ${t.kind || ''}`}>{t.icon}{t.message}</div>)}
    </div>
  );
  return [push, ui];
}

Object.assign(window, {
  Icon, Badge, StatusBadge, KpiCard, Sparkline, LineChart, StackedBars, HBarChart, Donut, Heatmap,
  Mono, Pill, CopyButton, shortId, formatRel, formatDur, formatMoney, classFor, useToasts,
});
