// Deterministic pseudo-random so numbers are stable per token
function mulberry32(seed) {
  return function() {
    let t = seed += 0x6D2B79F5;
    t = Math.imul(t ^ t >>> 15, t | 1);
    t ^= t + Math.imul(t ^ t >>> 7, t | 61);
    return ((t ^ t >>> 14) >>> 0) / 4294967296;
  };
}

function seededRand(seed) { return mulberry32(seed); }

// Format a float as a small signed decimal like "+0.23" / "-1.04"
function fmt(n, digits = 2) {
  if (!isFinite(n)) return ' NaN';
  const s = n.toFixed(digits);
  return (n >= 0 ? '+' : '') + s;
}

// Toy tokenizer: splits on spaces + punctuation, keeps leading space like real BPE
function toyTokenize(text) {
  if (!text) return [];
  // split keeping the separators so " the" is distinguishable from "the"
  const parts = [];
  let buf = '';
  for (let i = 0; i < text.length; i++) {
    const ch = text[i];
    if (ch === ' ') {
      if (buf) parts.push(buf);
      buf = ' ';
    } else if (/[.,!?;:'"()[\]{}]/.test(ch)) {
      if (buf) parts.push(buf);
      parts.push(ch);
      buf = '';
    } else {
      buf += ch;
    }
  }
  if (buf) parts.push(buf);
  // give each a deterministic id
  return parts.map((t) => {
    let h = 2166136261;
    for (let i = 0; i < t.length; i++) { h ^= t.charCodeAt(i); h = Math.imul(h, 16777619); }
    const id = Math.abs(h) % 50000;
    return { text: t, id };
  });
}

// Lerp and clamp
const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
const lerp = (a, b, t) => a + (b - a) * t;
const smooth = (t) => t * t * (3 - 2 * t);

// A single "digit" in the flowing number column
function Glyph({ ch, alpha = 1, color = 'var(--ink)' }) {
  return (
    <span style={{
      color, opacity: alpha, fontFamily: 'var(--mono)', fontVariantNumeric: 'tabular-nums',
      display: 'inline-block', textShadow: `0 0 6px ${color}`,
    }}>{ch}</span>
  );
}

// Cell with flicker — used inside tensor grids
function TensorCell({ value, highlight = 0, color = 'var(--ink)', tiny = false }) {
  const alpha = 0.25 + 0.75 * Math.min(1, Math.abs(value));
  const pulse = highlight;
  const c = highlight > 0
    ? `color-mix(in oklch, ${color}, var(--amber) ${Math.round(pulse * 100)}%)`
    : color;
  return (
    <span style={{
      fontFamily: 'var(--mono)', fontSize: tiny ? 9 : 10,
      fontVariantNumeric: 'tabular-nums',
      color: c, opacity: alpha,
      textShadow: highlight > 0 ? `0 0 8px ${c}` : `0 0 4px ${c}`,
      transition: 'color 200ms, opacity 200ms, text-shadow 200ms',
    }}>{fmt(value)}</span>
  );
}

// Simple tiny panel chrome
function Panel({ title, subtitle, stage, active, progress, children, style, onExplain }) {
  const bar = active ? Math.round((progress || 0) * 100) : 0;
  const [helpHover, setHelpHover] = React.useState(false);
  return (
    <div style={{
      position: 'relative',
      border: `1px solid ${active ? 'color-mix(in oklch, var(--ink) 70%, transparent)' : 'var(--line)'}`,
      borderRadius: 8,
      background: active
        ? 'linear-gradient(180deg, rgba(140,255,160,0.03), rgba(140,255,160,0.008))'
        : 'rgba(18,24,18,0.5)',
      padding: '14px 16px 16px',
      boxShadow: active
        ? '0 0 18px color-mix(in oklch, var(--ink) 10%, transparent)'
        : 'none',
      transition: 'border-color 300ms, background 300ms, box-shadow 300ms',
      ...style,
    }}>
      {active && (
        <div style={{
          position: 'absolute', left: -1, top: 10, bottom: 10, width: 2,
          background: 'var(--ink)', borderRadius: 1,
          boxShadow: '0 0 6px var(--ink)',
          pointerEvents: 'none',
        }} />
      )}
      <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 12, marginBottom: 10 }}>
        <div style={{ display: 'flex', alignItems: 'baseline', gap: 10, minWidth: 0 }}>
          <div style={{
            fontFamily: 'var(--mono)', fontSize: 10,
            color: active ? 'var(--ink)' : 'var(--text-faint)',
            textShadow: active ? '0 0 6px var(--ink)' : 'none',
            letterSpacing: '0.1em',
          }}>STAGE {String(stage).padStart(2, '0')}</div>
          <div style={{
            fontFamily: 'var(--sans)', fontWeight: 500,
            fontSize: active ? 14 : 13,
            letterSpacing: '0.02em',
            color: active ? 'var(--ink)' : 'var(--text-dim)',
            textShadow: active ? '0 0 10px var(--ink)' : 'none',
            transition: 'font-size 200ms',
          }}>{title}</div>
          {subtitle && <div style={{ fontFamily: 'var(--mono)', fontSize: 10, color: active ? 'var(--text-dim)' : 'var(--text-faint)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', minWidth: 0 }}>{subtitle}</div>}
        </div>
        <div style={{
          display: 'inline-flex', alignItems: 'center', gap: 10, flexShrink: 0,
          fontFamily: 'var(--mono)', fontSize: 10,
          color: active ? 'var(--ink)' : 'var(--text-faint)',
        }}>
          {onExplain && (
            <button
              onClick={onExplain}
              onMouseEnter={() => setHelpHover(true)}
              onMouseLeave={() => setHelpHover(false)}
              title={`Explain stage ${String(stage).padStart(2, '0')}`}
              aria-label="Explain this stage"
              style={{
                display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                width: 16, height: 16, padding: 0, cursor: 'pointer',
                fontFamily: 'var(--mono)', fontSize: 10, lineHeight: 1,
                color: helpHover ? 'var(--bg)' : (active ? 'var(--ink)' : 'var(--text-faint)'),
                background: helpHover ? 'var(--ink)' : 'transparent',
                border: `1px solid ${helpHover ? 'var(--ink)' : (active ? 'color-mix(in oklch, var(--ink) 45%, transparent)' : 'var(--line-strong)')}`,
                borderRadius: '50%',
                boxShadow: helpHover ? '0 0 8px color-mix(in oklch, var(--ink) 45%, transparent)' : 'none',
                transition: 'all 140ms',
              }}
            >?</button>
          )}
          <span style={{
            display: 'inline-flex', alignItems: 'center', gap: 6,
            textShadow: active ? '0 0 6px var(--ink)' : 'none',
          }}>
            {active && <span style={{
              display: 'inline-block', width: 6, height: 6, borderRadius: '50%',
              background: 'var(--ink)', boxShadow: '0 0 8px var(--ink)',
              animation: 'stage-pulse 1.4s ease-in-out infinite',
            }} />}
            {active ? `${bar.toString().padStart(3, '0')}%` : '···'}
          </span>
        </div>
      </div>
      {children}
      {/* progress underline — thicker when active */}
      <div style={{ position: 'absolute', left: 0, right: 0, bottom: 0, height: active ? 2 : 1, background: 'transparent', overflow: 'hidden', borderRadius: '0 0 8px 8px' }}>
        <div style={{
          height: '100%', width: `${bar}%`,
          background: 'linear-gradient(90deg, transparent, var(--ink), transparent)',
          transition: 'width 300ms linear', opacity: 0.9,
          filter: 'drop-shadow(0 0 4px var(--ink))',
        }} />
      </div>
      <style>{`@keyframes stage-pulse { 0%, 100% { opacity: 0.4; } 50% { opacity: 1; } }`}</style>
    </div>
  );
}

// ── residualRNG ───────────────────────────────────────────────────────────
// Shared deterministic-per-(token, layer, head, seed) value source. 2D
// Stage 03/04 and the 3D scene-3d.jsx both sample this so a single prompt
// produces the same magnitude / attention / MLP values in both views.
// Seeds used across the app (namespace):
//   1  — residual magnitude (cell Y-scale in 3D, row strength in 2D)
//   3  — attention per-head-per-src contribution
//   7  — attention per-target-layer modulation
//   11 — MLP write strength
// Keep seeds disjoint so the three surfaces don't visually rhyme.
function residualRNG(token, layer, head = 0, seed = 1) {
  let h = 2166136261;
  const s = `${seed}|${token}|${layer}|${head}`;
  for (let i = 0; i < s.length; i++) {
    h ^= s.charCodeAt(i);
    h = Math.imul(h, 16777619);
  }
  return ((h >>> 0) % 1000) / 1000;
}

// Expose globals
Object.assign(window, { mulberry32, seededRand, fmt, toyTokenize, clamp, lerp, smooth, Glyph, TensorCell, Panel, residualRNG });
