/* Anim helpers — entrance triggers + count-up + draw-on.
   Listens for the deck-stage 'slideIndexChanged' postMessage and exposes
   useSlideEnter(slideIdx) → boolean (true once that slide becomes active).
   Reduced-motion safe: when the user prefers reduced motion, every helper
   short-circuits to "instantly visible / instantly final value". */

(function () {
  const { useState, useEffect, useRef } = React;

  // Treat reduced-motion AND static-snapshot contexts (PPTX export, html-to-image,
  // print, ?static query) as "skip animations, render finals immediately".
  const _prefersReduced = typeof window !== 'undefined' &&
    window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  const _isStatic = typeof window !== 'undefined' && (
    /[?&]static\b/.test(window.location.search) ||
    window.matchMedia && window.matchMedia('print').matches
  );
  const REDUCE = _prefersReduced || _isStatic;

  // Single source of truth for current slide index, broadcast via the
  // deck-stage postMessage contract (parent posts back to itself too).
  let _current = 0;
  const _subs = new Set();
  window.addEventListener('message', (e) => {
    const d = e && e.data;
    if (d && typeof d.slideIndexChanged === 'number') {
      _current = d.slideIndexChanged;
      _subs.forEach(fn => fn(_current));
    }
  });

  function useSlideEnter(slideIdx) {
    const [entered, setEntered] = useState(_current === slideIdx);
    useEffect(() => {
      if (REDUCE) { setEntered(true); return; }
      const sub = (i) => setEntered(i === slideIdx);
      _subs.add(sub);
      if (_current === slideIdx) setEntered(true);
      return () => _subs.delete(sub);
    }, [slideIdx]);
    return REDUCE ? true : entered;
  }

  // Tween a number from `from` to `to` over `dur` ms once `active` flips true.
  function useCountUp(active, to, { from = 0, dur = 900, decimals = 0 } = {}) {
    const [v, setV] = useState(active ? to : from);
    const rafRef = useRef(null);
    useEffect(() => {
      if (!active) { setV(from); return; }
      if (REDUCE) { setV(to); return; }
      const t0 = performance.now();
      const step = (t) => {
        const p = Math.min(1, (t - t0) / dur);
        // ease-out cubic
        const e = 1 - Math.pow(1 - p, 3);
        const cur = from + (to - from) * e;
        setV(cur);
        if (p < 1) rafRef.current = requestAnimationFrame(step);
      };
      rafRef.current = requestAnimationFrame(step);
      return () => rafRef.current && cancelAnimationFrame(rafRef.current);
    }, [active, to]);
    return decimals ? v.toFixed(decimals) : Math.round(v).toLocaleString();
  }

  // Trivial fade+rise-in style helper. delay in ms.
  function enterStyle(active, { y = 12, delay = 0, dur = 380 } = {}) {
    if (REDUCE) return { opacity: 1, transform: 'none' };
    return {
      opacity: active ? 1 : 0,
      transform: active ? 'translateY(0)' : `translateY(${y}px)`,
      transition: `opacity ${dur}ms cubic-bezier(0.16, 1, 0.3, 1) ${delay}ms, transform ${dur}ms cubic-bezier(0.16, 1, 0.3, 1) ${delay}ms`,
      willChange: 'opacity, transform',
    };
  }

  // Stroke-draw helper: returns the strokeDasharray/Offset pair for a given length.
  function drawStyle(active, len, { delay = 0, dur = 1200 } = {}) {
    if (REDUCE) return { strokeDasharray: 'none', strokeDashoffset: 0 };
    return {
      strokeDasharray: len,
      strokeDashoffset: active ? 0 : len,
      transition: `stroke-dashoffset ${dur}ms cubic-bezier(0.4, 0, 0.2, 1) ${delay}ms`,
    };
  }

  window.SoAnim = { useSlideEnter, useCountUp, enterStyle, drawStyle, REDUCE };
})();
