/**
 * Shared primitives for BuzzLead deck.
 * Timing, reveal wrappers, state counter hook.
 */

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

// ── Timing constants ────────────────────────────────────
const TIMING = {
  micro: 200,
  entry: 450,
  scene: 800,
  pulse: 1800,
  counter: 1200,
  typewriter: 28, // ms per char
};

const EASE = 'cubic-bezier(0.22, 1, 0.36, 1)';

// ── Type scale (1920×1080) ──────────────────────────────
const TYPE_SCALE = {
  display: 120,
  title: 64,
  subtitle: 44,
  body: 24,
  small: 18,
  label: 13,
  numeric: 140,
};
const SPACING = {
  paddingTop: 100,
  paddingBottom: 80,
  paddingX: 100,
  titleGap: 52,
  itemGap: 28,
};

// ── useSlideState: per-slide click-state counter ───────
// Each slide declares how many click states it has. When the slide
// becomes active, state resets to 0. ArrowRight increments until max,
// then lets the deck-stage advance. ArrowLeft decrements within slide.
function useSlideState(maxStates, slideRef) {
  const [state, setState] = useState(0);
  const stateRef = useRef(0);
  const activeRef = useRef(false);
  const hostRef = useRef(null);

  const resolveHost = () => {
    const el = slideRef.current;
    if (!el) return null;
    return el.closest('[data-deck-slide]') || el;
  };

  // Write data-slide-busy on the host based on current state + active.
  const syncBusy = () => {
    const host = hostRef.current || resolveHost();
    if (!host) return;
    hostRef.current = host;
    if (!activeRef.current || maxStates <= 0) {
      host.removeAttribute('data-slide-busy');
      return;
    }
    const s = stateRef.current;
    const canFwd = s < maxStates;
    const canBack = s > 0;
    let flag = '';
    if (canFwd && canBack) flag = 'both';
    else if (canFwd) flag = 'fwd';
    else if (canBack) flag = 'back';
    if (flag) host.setAttribute('data-slide-busy', flag);
    else host.removeAttribute('data-slide-busy');
  };

  // Re-sync whenever state changes (covers internal advance).
  useEffect(() => {
    stateRef.current = state;
    syncBusy();
  }, [state, maxStates]);

  useEffect(() => {
    const deck = document.querySelector('deck-stage');
    if (!deck) return;

    const onSlideChange = (e) => {
      const host = hostRef.current || resolveHost();
      hostRef.current = host;
      if (!host) return;
      const isActive = e.detail.slide === host;
      activeRef.current = isActive;
      if (isActive) {
        stateRef.current = 0;
        setState(0);
        syncBusy(); // imperative: ensures flag is set even when state was already 0
      } else {
        host.removeAttribute('data-slide-busy');
      }
    };
    deck.addEventListener('slidechange', onSlideChange);

    // Init: may already be the active slide on mount.
    const initTimer = setTimeout(() => {
      const host = resolveHost();
      hostRef.current = host;
      if (deck._slides && host && deck._slides[deck._index] === host) {
        activeRef.current = true;
        stateRef.current = 0;
        syncBusy();
      }
    }, 80);

    return () => {
      clearTimeout(initTimer);
      deck.removeEventListener('slidechange', onSlideChange);
    };
  }, []);

  useEffect(() => {
    const onKey = (e) => {
      if (!activeRef.current) return;
      if (e.target && (e.target.isContentEditable ||
          /^(INPUT|TEXTAREA|SELECT)$/.test(e.target.tagName))) return;
      if (e.metaKey || e.ctrlKey || e.altKey) return;

      if (e.key === 'ArrowRight' || e.key === 'PageDown' || e.key === ' ' || e.key === 'Spacebar') {
        if (stateRef.current < maxStates) {
          e.preventDefault();
          const next = stateRef.current + 1;
          stateRef.current = next;
          setState(next);
        }
      } else if (e.key === 'ArrowLeft' || e.key === 'PageUp') {
        if (stateRef.current > 0) {
          e.preventDefault();
          const next = stateRef.current - 1;
          stateRef.current = next;
          setState(next);
        }
      }
    };
    window.addEventListener('keydown', onKey, true);
    return () => window.removeEventListener('keydown', onKey, true);
  }, [maxStates]);

  return state;
}

// ── Reveal: renders children with rise-in when `show` is true ──
function Reveal({ show, delay = 0, as: Tag = 'div', className = '', style, children, kind = 'rise' }) {
  const cls = {
    rise: 'rise-in',
    left: 'slide-in-left',
    right: 'slide-in-right',
    fade: 'fade-in',
  }[kind] || 'rise-in';

  return (
    <Tag
      className={`${cls} ${show ? 'in' : ''} ${className}`}
      style={{ transitionDelay: show ? `${delay}ms` : '0ms', ...style }}
    >
      {children}
    </Tag>
  );
}

// ── Counter: ticks from 0 to target over duration ──────
function Counter({ value, prefix = '', suffix = '', duration = TIMING.counter, active, format = (n) => n.toLocaleString() }) {
  const [display, setDisplay] = useState(0);
  const startedRef = useRef(false);
  const rafRef = useRef(null);

  useEffect(() => {
    if (!active) {
      setDisplay(0);
      startedRef.current = false;
      return;
    }
    if (startedRef.current) return;
    startedRef.current = true;
    const start = performance.now();
    const step = (now) => {
      const t = Math.min(1, (now - start) / duration);
      // ease-out cubic
      const eased = 1 - Math.pow(1 - t, 3);
      setDisplay(Math.round(value * eased));
      if (t < 1) rafRef.current = requestAnimationFrame(step);
    };
    rafRef.current = requestAnimationFrame(step);
    return () => cancelAnimationFrame(rafRef.current);
  }, [active, value, duration]);

  return (
    <span className="mono-num">
      {prefix}{format(display)}{suffix}
    </span>
  );
}

// ── Typewriter ─────────────────────────────────────────
function Typewriter({ text, speed = TIMING.typewriter, active, startDelay = 0, onDone }) {
  const [idx, setIdx] = useState(0);
  useEffect(() => {
    if (!active) { setIdx(0); return; }
    let i = 0;
    let timer;
    const kick = () => {
      timer = setTimeout(function tick() {
        i++;
        setIdx(i);
        if (i < text.length) timer = setTimeout(tick, speed);
        else if (onDone) onDone();
      }, speed);
    };
    const start = setTimeout(kick, startDelay);
    return () => { clearTimeout(start); clearTimeout(timer); };
  }, [active, text, speed, startDelay]);
  return <>{text.slice(0, idx)}<span className={idx < text.length && active ? 'cursor' : 'cursor hidden'}>▋</span></>;
}

// ── State-dot indicator (bottom-right of slide) ────────
function StateHint({ state, max }) {
  if (max <= 0) return null;
  return (
    <div className="state-hint">
      {Array.from({ length: max + 1 }).map((_, i) => (
        <div key={i} className={`dot ${i <= state ? 'active' : ''}`} />
      ))}
    </div>
  );
}

// ── Bee mark ───────────────────────────────────────────
function BeeMark() {
  return <img className="bee-mark" src="assets/buzzlead-bee.png" alt="" />;
}

// ── Slide footer meta ──────────────────────────────────
function SlideFooter({ n, total, title }) {
  const num = String(n).padStart(2, '0');
  const tot = String(total).padStart(2, '0');
  return (
    <div className="slide-footer">
      <span>BuzzLead · 2026</span>
      <span>{title}</span>
      <span>{num} / {tot}</span>
    </div>
  );
}

// Expose
Object.assign(window, {
  TIMING, EASE, TYPE_SCALE, SPACING,
  useSlideState, Reveal, Counter, Typewriter,
  StateHint, BeeMark, SlideFooter,
});
