/* ============================================================
   Animation Studio — real, replayable animation primitives
   (the production dashboard's hooks resolve to final-state for
    robustness; these actually drive motion for the pitch.)
   ============================================================ */
const { useState, useEffect, useRef, useMemo, useCallback } = React;

/* easings */
const Ease = {
  outCubic: t => 1 - Math.pow(1 - t, 3),
  outQuint: t => 1 - Math.pow(1 - t, 5),
  inOutCubic: t => (t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2),
  outBack: (t, s = 1.7) => 1 + (s + 1) * Math.pow(t - 1, 3) + s * Math.pow(t - 1, 2),
};

/* rAF progress 0->1 over `dur` ms, restarts whenever `playKey` changes.
   `delay` ms before it begins. Returns raw 0..1 (apply your own easing). */
function useProgress(dur, playKey, delay = 0) {
  const [p, setP] = useState(0);
  useEffect(() => {
    let raf, start = null, cancelled = false;
    setP(0);
    const tick = (now) => {
      if (start === null) start = now;
      const t = now - start - delay;
      if (t < 0) { raf = requestAnimationFrame(tick); return; }
      const v = Math.min(1, t / dur);
      setP(v);
      if (v < 1 && !cancelled) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => { cancelled = true; cancelAnimationFrame(raf); };
  }, [dur, playKey, delay]);
  return p;
}

/* count-up integer toward `to`, eased, tied to playKey */
function useCountUp(to, dur, playKey, ease = Ease.outQuint) {
  const p = useProgress(dur, playKey);
  return Math.round(ease(p) * to);
}

/* per-element stagger helper: given index + step + base, returns ms delay */
const stagger = (i, step, base = 0) => base + i * step;

/* clamp + lerp */
const clamp01 = t => (t < 0 ? 0 : t > 1 ? 1 : t);
const lerp = (a, b, t) => a + (b - a) * t;

/* master clock in milliseconds (0..totalMs), restarts on playKey.
   Drive per-element entrance by inline styles: local = clamp01((elapsed - delay)/dur).
   Inline styles (not CSS @keyframes) keep visibility ungated by opacity — robust
   under render throttling and faithfully captured by tooling. */
function useClock(totalMs, playKey) {
  return useProgress(totalMs, playKey) * totalMs;
}

/* ---- reveal-on-view: bumps a key each time the element scrolls into view ----
   Lets us animate ANY static dashboard chart by wrapping it, without rewriting it. */
function useReveal(opts = {}) {
  const ref = useRef(null);
  const [key, setKey] = useState(0);
  useEffect(() => {
    const el = ref.current;
    if (!el || typeof IntersectionObserver === "undefined") { setKey(1); return; }
    const io = new IntersectionObserver((entries) => {
      entries.forEach(e => { if (e.isIntersecting) setKey(k => k + 1); });
    }, { threshold: opts.threshold != null ? opts.threshold : 0.3 });
    io.observe(el);
    return () => io.disconnect();
  }, []);
  return [ref, key];
}

/* clip/translate/fade reveal wrapper — wraps a finished (static) chart and
   wipes it in. `dir`: "up" (wipe downward) default. */
function RevealChart({ playKey, delay = 0, dur = 950, y = 16, children }) {
  const p = useProgress(dur, playKey, delay);
  const e = Ease.outCubic(p);
  const hidePct = ((1 - e) * 100).toFixed(2);
  const clip = `inset(0 0 ${hidePct}% 0)`;
  return (
    <div style={{
      clipPath: clip, WebkitClipPath: clip,
      opacity: clamp01(p * 2.4),
      transform: `translateY(${((1 - e) * y).toFixed(1)}px)`,
      willChange: "clip-path, transform, opacity",
    }}>{children}</div>
  );
}

Object.assign(window, { React, useState, useEffect, useRef, useMemo, useCallback, Ease, useProgress, useCountUp, useClock, stagger, clamp01, lerp, useReveal, RevealChart });
