// Shared primitive components.
const { useState, useEffect, useRef, useMemo, useCallback } = React;

// ── helpers ───────────────────────────────────────────────────────────────────
function signalColor(prob){
  if (prob >= 0.6) return 'red';
  if (prob <= 0.4) return 'green';
  return 'amber';
}
function signalLabel(prob){
  if (prob >= 0.6) return 'AI';
  if (prob <= 0.4) return 'HUMAN';
  return 'MIXED';
}
function fmt3(n){ return Number(n).toFixed(3); }
function pct0(n){ return Math.round(n*100) + '%'; }
function fmtBytes(n){
  if (n < 1024) return n + ' B';
  if (n < 1024*1024) return (n/1024).toFixed(1) + ' KB';
  return (n/1024/1024).toFixed(2) + ' MB';
}
function commas(n){ return Number(n).toLocaleString('en-US'); }
function layerMeta(key){ return window.LAYER_META.find(l=>l.key===key); }
function verdictTone(score){
  if (score >= 0.7) return { tone:'red',   bg:'rgba(239,68,68,.08)',  border:'rgba(239,68,68,.35)' };
  if (score <= 0.3) return { tone:'green', bg:'rgba(16,185,129,.08)', border:'rgba(16,185,129,.35)' };
  return { tone:'amber', bg:'rgba(245,158,11,.08)', border:'rgba(245,158,11,.35)' };
}

// ── 9-cell AI probability bar ─────────────────────────────────────────────────
function ProbBar({ value, cells = 9, animate = false, delayMs = 0 }){
  const [filled, setFilled] = useState(animate ? 0 : value);
  useEffect(() => {
    if (!animate) { setFilled(value); return; }
    let raf, t0;
    const t = setTimeout(() => {
      const dur = 400;
      const tick = (now) => {
        if (!t0) t0 = now;
        const k = Math.min(1, (now - t0)/dur);
        const eased = 1 - Math.pow(1-k, 2.4);
        setFilled(value * eased);
        if (k < 1) raf = requestAnimationFrame(tick);
      };
      raf = requestAnimationFrame(tick);
    }, delayMs);
    return () => { clearTimeout(t); cancelAnimationFrame(raf); };
  }, [value, animate, delayMs]);

  const color = signalColor(value);
  const filledCells = Math.round(filled * cells);
  return (
    <span className="bar" aria-label={`AI probability ${fmt3(value)}`}>
      {Array.from({length: cells}).map((_,i) => (
        <span key={i} className={`bar-cell ${i < filledCells ? 'on '+color : ''}`}></span>
      ))}
    </span>
  );
}

// ── ASCII spinner ─────────────────────────────────────────────────────────────
const SPINNER_FRAMES = '⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏';
function AsciiSpinner({ color = 'var(--blue)' }){
  const [i, setI] = useState(0);
  useEffect(() => {
    const id = setInterval(() => setI(x => (x+1) % SPINNER_FRAMES.length), 80);
    return () => clearInterval(id);
  }, []);
  return <span className="mono" style={{color}}>{SPINNER_FRAMES[i]}</span>;
}

// ── Signal pill ───────────────────────────────────────────────────────────────
function SignalPill({ signal }){
  const map = { AI:'red', HUMAN:'green', MIXED:'amber' };
  const cls = map[signal] || 'muted';
  const dot = signal === 'AI' ? '🔴' : signal === 'HUMAN' ? '🟢' : '🟡';
  return <span className={`sig ${cls}`}><span style={{fontSize:9}}>{dot}</span>{signal}</span>;
}

// ── Tiny inline line chart ────────────────────────────────────────────────────
function MiniLine({ values, width=440, height=120, color='var(--blue)', threshold }){
  if (!values || values.length === 0) return null;
  const min = Math.min(...values), max = Math.max(...values);
  const pad = (max-min) * 0.15 + 1;
  const lo = min - pad, hi = max + pad;
  const w = width, h = height, mx = 28, my = 14;
  const stepX = (w - mx*2) / Math.max(1, values.length - 1);
  const y = v => h - my - ((v - lo)/(hi - lo)) * (h - my*2);
  const path = values.map((v,i) => `${i===0?'M':'L'} ${mx + i*stepX} ${y(v)}`).join(' ');
  const ticks = [lo, (lo+hi)/2, hi].map(t => +t.toFixed(0));
  return (
    <svg width={w} height={h} style={{display:'block'}}>
      {/* grid */}
      {ticks.map((t,i) => (
        <g key={i}>
          <line className="gridline" x1={mx} x2={w-mx} y1={y(t)} y2={y(t)} />
          <text x={4} y={y(t)+3} fontSize="9" fontFamily="JetBrains Mono" fill="var(--text-dim)">{t}</text>
        </g>
      ))}
      {threshold != null && (
        <line x1={mx} x2={w-mx} y1={y(threshold)} y2={y(threshold)}
              stroke="var(--red)" strokeDasharray="3 3" strokeWidth="1" opacity="0.6" />
      )}
      <path d={path} fill="none" stroke={color} strokeWidth="1.4" />
      {values.map((v,i) => (
        <circle key={i} cx={mx + i*stepX} cy={y(v)} r="2" fill={color} />
      ))}
    </svg>
  );
}

// ── Tiny histogram ────────────────────────────────────────────────────────────
function MiniHistogram({ buckets, width=420, height=60, color='var(--blue)' }){
  const max = Math.max(...buckets, 1);
  const w = width, h = height;
  const bw = (w - 4) / buckets.length;
  return (
    <svg width={w} height={h} style={{display:'block'}}>
      {buckets.map((v,i) => {
        const bh = (v/max) * (h - 14);
        const t = i / (buckets.length-1);
        const c = t < 0.4 ? 'var(--green)' : t > 0.6 ? 'var(--red)' : 'var(--amber)';
        return <rect key={i} x={2 + i*bw} y={h - bh - 2} width={bw - 2} height={bh} fill={c} opacity="0.85" />;
      })}
    </svg>
  );
}

// ── Heatmap of sentences ──────────────────────────────────────────────────────
function HeatColor(p){
  const paper = document.body.dataset.theme === 'paper';
  // green → amber → red gradient (theme-aware)
  const palette = paper
    ? { g:[79,107,58],  a:[176,124,29], r:[176,58,46]  } // muted ink tones
    : { g:[16,185,129], a:[245,158,11], r:[239,68,68]  };
  if (p < 0.4){
    const t = p / 0.4;
    const r = Math.round(palette.g[0] + (palette.a[0]-palette.g[0])*t);
    const g = Math.round(palette.g[1] + (palette.a[1]-palette.g[1])*t);
    const b = Math.round(palette.g[2] + (palette.a[2]-palette.g[2])*t);
    return `rgb(${r}, ${g}, ${b})`;
  } else {
    const t = (p - 0.4) / 0.6;
    const r = Math.round(palette.a[0] + (palette.r[0]-palette.a[0])*t);
    const g = Math.round(palette.a[1] + (palette.r[1]-palette.a[1])*t);
    const b = Math.round(palette.a[2] + (palette.r[2]-palette.a[2])*t);
    return `rgb(${r}, ${g}, ${b})`;
  }
}

Object.assign(window, {
  signalColor, signalLabel, fmt3, pct0, fmtBytes, commas, layerMeta, verdictTone,
  ProbBar, AsciiSpinner, SignalPill, MiniLine, MiniHistogram, HeatColor,
});
