// Reusable primitives: state hook, animated number, rarity label,
// filter row + toggle, particle burst.

const { useState, useEffect, useRef, useMemo, useCallback } = React;

function useRareState() {
  const [active, setActive] = useState(() => {
    const init = {};
    window.RARE_FILTERS.forEach((f) => { init[f.id] = f.defaultOn; });
    return init;
  });
  // Most recently toggled filter id — lets reactive headline/bio segments
  // color their shine in that specific filter's hue.
  const [lastToggled, setLastToggled] = useState(null);

  const pool = useMemo(() => {
    let n = window.BASE_POP;
    window.RARE_FILTERS.forEach((f) => { if (active[f.id]) n *= f.passRate; });
    return n;
  }, [active]);

  const toggle = useCallback((id) => {
    setActive((s) => ({ ...s, [id]: !s[id] }));
    setLastToggled(id);
  }, []);
  return { active, toggle, pool, lastToggled };
}

// Animates between target values. On rapid changes, the next animation
// resumes from the *current* displayed value (not from a stale "from" that
// would let it strand at an intermediate number). The `format` prop lets
// callers control rendering — defaults to formatPool for legacy use.
function SmoothNumber({ value, style, className, duration = 700, format }) {
  const fmt = format || window.formatPool;
  const [display, setDisplay] = useState(value);
  const displayRef = useRef(value);
  const frameRef = useRef(null);
  useEffect(() => {
    const from = displayRef.current;
    const to = value;
    if (from === to) return;
    const start = performance.now();
    const tick = (now) => {
      const t = Math.min(1, (now - start) / duration);
      const eased = t === 1 ? 1 : 1 - Math.pow(2, -10 * t);
      const next = from + (to - from) * eased;
      displayRef.current = next;
      setDisplay(next);
      if (t < 1) frameRef.current = requestAnimationFrame(tick);
    };
    frameRef.current = requestAnimationFrame(tick);
    return () => { if (frameRef.current) cancelAnimationFrame(frameRef.current); };
  }, [value, duration]);
  return <span className={className} style={style}>{fmt(display)}</span>;
}

function RarityLabel({ pool, style }) {
  const target = window.rarityLabel(pool);
  const [label, setLabel] = useState(target);
  useEffect(() => {
    if (label === target) return;
    let alive = true;
    let current = label;
    const deleteEach = () => {
      if (!alive) return;
      if (current.length === 0) { typeIn(); return; }
      current = current.slice(0, -1);
      setLabel(current);
      setTimeout(deleteEach, 22);
    };
    let i = 0;
    const typeIn = () => {
      if (!alive) return;
      i++;
      current = target.slice(0, i);
      setLabel(current);
      if (i < target.length) setTimeout(typeIn, 36);
    };
    deleteEach();
    return () => { alive = false; };
  }, [target]);
  return (
    <span style={{
      fontFamily: 'var(--font-mono)',
      color: 'var(--fg)',
      letterSpacing: '0.18em',
      fontWeight: 600,
      ...style,
    }}>
      {label}<span style={{ opacity: 0.65, animation: 'rareBlink 1s steps(2) infinite' }}>▊</span>
    </span>
  );
}

function FilterRow({ f, i, on, onToggle, onOpen, isOpen }) {
  const [flash, setFlash] = useState(false);
  const [rowHover, setRowHover] = useState(false);
  const firstRef = useRef(true);
  useEffect(() => {
    if (firstRef.current) { firstRef.current = false; return; }
    setFlash(true);
    const t = setTimeout(() => setFlash(false), 450);
    return () => clearTimeout(t);
  }, [on]);

  // Click anywhere on the row to toggle (except the `?` button, which has
  // its own click handler that stops propagation). Toggle pill + `?` scale
  // by 1.1× on row hover so the click target reads as live.
  return (
    <div
      onClick={onToggle}
      onMouseEnter={() => setRowHover(true)}
      onMouseLeave={() => setRowHover(false)}
      role="button"
      aria-pressed={on}
      style={{
        padding: '7px 2px',
        borderTop: '1px solid rgba(var(--fg-rgb), 0.08)',
        opacity: on ? 1 : 0.44,
        transition: 'opacity 0.25s ease, background 0.2s',
        position: 'relative',
        cursor: 'pointer',
        background: isOpen
          ? `rgba(${f.colorRgb}, 0.08)`
          : rowHover
            ? `rgba(${f.colorRgb}, 0.04)`
            : 'transparent',
      }}
    >
      {flash && (
        <div style={{
          position: 'absolute', left: 0, right: 0, bottom: 0, height: 1,
          background: f.color, transformOrigin: 'left',
          animation: 'flashSweep 0.45s ease-out',
        }} />
      )}
      <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
        <button
          onClick={(e) => { e.stopPropagation(); onToggle(); }}
          aria-label={`toggle ${f.label}`}
          style={{
            width: 26, height: 14, borderRadius: 8, border: 'none',
            background: on ? f.color : 'rgba(var(--fg-rgb), 0.15)',
            position: 'relative', cursor: 'pointer', padding: 0,
            flexShrink: 0,
            transform: rowHover ? 'scale(1.1)' : 'scale(1)',
            transition: 'background 0.2s, transform 0.18s cubic-bezier(0.22,0.9,0.3,1)',
            boxShadow: on ? `0 0 0 0.5px rgba(${f.colorRgb}, 0.33)` : 'none',
          }}
        >
          <span style={{
            position: 'absolute', top: 2, left: on ? 13 : 2,
            width: 10, height: 10, borderRadius: '50%',
            background: 'var(--bg)', transition: 'left 0.2s',
          }} />
        </button>
        <div style={{ flex: 1, fontFamily: 'var(--font-mono)', fontSize: 11, lineHeight: 1.3, minWidth: 0 }}>
          <div style={{ fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
            <span style={{ opacity: 0.5, marginRight: 6 }}>{String(i + 1).padStart(2, '0')}</span>
            {f.label}
          </div>
          <div style={{ fontSize: 9, opacity: 0.55, marginTop: 1, letterSpacing: '0.04em' }}>{f.detail}</div>
        </div>
        <button
          onClick={(e) => { e.stopPropagation(); onOpen(); }}
          style={{
            background: 'transparent',
            border: `1px solid ${isOpen ? f.color : 'rgba(var(--fg-rgb), 0.15)'}`,
            color: isOpen ? f.color : 'var(--fg)',
            opacity: isOpen ? 1 : 0.65,
            width: 20, height: 20, borderRadius: 3, cursor: 'pointer',
            fontFamily: 'var(--font-mono)', fontSize: 10,
            transform: rowHover ? 'scale(1.1)' : 'scale(1)',
            transition: 'opacity 0.15s, color 0.15s, border-color 0.15s, transform 0.18s cubic-bezier(0.22,0.9,0.3,1)',
          }}
        >?</button>
      </div>
    </div>
  );
}

function ParticleBurst({ color }) {
  const particles = useMemo(() => {
    const N = 11;
    const baseAngle = Math.random() * Math.PI * 2;
    return Array.from({ length: N }).map((_, i) => {
      const jitter = (Math.random() - 0.5) * 0.35;
      const angle = baseAngle + (i / N) * Math.PI * 2 + jitter;
      const dist = 28 + Math.random() * 22;
      return {
        dx: Math.cos(angle) * dist,
        dy: Math.sin(angle) * dist,
        size: 3 + Math.random() * 3,
        delay: Math.random() * 60,
        duration: 620 + Math.random() * 260,
      };
    });
  }, []);
  return (
    <span aria-hidden="true" style={{
      position: 'absolute', left: '50%', top: '50%', width: 0, height: 0,
      pointerEvents: 'none', zIndex: 5,
    }}>
      {particles.map((p, i) => (
        <span key={i} style={{
          position: 'absolute',
          left: -p.size / 2, top: -p.size / 2,
          width: p.size, height: p.size, borderRadius: '50%',
          background: color,
          boxShadow: `0 0 6px ${color}`,
          ['--dx']: `${p.dx}px`, ['--dy']: `${p.dy}px`,
          animation: `rareParticleOut ${p.duration}ms cubic-bezier(0.22,0.9,0.3,1) ${p.delay}ms 1 forwards`,
          opacity: 0,
        }} />
      ))}
    </span>
  );
}

Object.assign(window, { useRareState, SmoothNumber, RarityLabel, FilterRow, ParticleBurst });
