/* Viralis — animation primitives */
const { useState: useS, useEffect: useE, useRef: useR } = React;

// useInView: returns [ref, inView] — fires once when element enters viewport
function useInView(opts = { threshold: 0.2, once: true }) {
  const ref = useR(null);
  const [inView, setInView] = useS(false);
  useE(() => {
    if (!ref.current) return;
    const el = ref.current;
    const io = new IntersectionObserver(([e]) => {
      if (e.isIntersecting) {
        setInView(true);
        if (opts.once) io.disconnect();
      } else if (!opts.once) {
        setInView(false);
      }
    }, { threshold: opts.threshold ?? 0.2 });
    io.observe(el);
    return () => io.disconnect();
  }, []);
  return [ref, inView];
}

// Typewriter / sequenced reveal for an array of strings
function useStaggered(items, { delay = 60, start = 0, active = true } = {}) {
  const [shown, setShown] = useS(0);
  useE(() => {
    if (!active) return;
    setShown(0);
    let i = 0;
    const tick = () => {
      i++;
      setShown(i);
      if (i < items.length) setTimeout(tick, delay);
    };
    const t = setTimeout(tick, start);
    return () => clearTimeout(t);
  }, [active, items.length]);
  return shown;
}

// Number tween from start to end
function useTween(target, { duration = 1200, active = true, ease = (t) => 1 - Math.pow(1 - t, 3) } = {}) {
  const [v, setV] = useS(active ? 0 : target);
  useE(() => {
    if (!active) { setV(0); return; }
    let raf, t0;
    const step = (now) => {
      if (!t0) t0 = now;
      const p = Math.min(1, (now - t0) / duration);
      setV(target * ease(p));
      if (p < 1) raf = requestAnimationFrame(step);
    };
    raf = requestAnimationFrame(step);
    return () => cancelAnimationFrame(raf);
  }, [active, target, duration]);
  return v;
}

Object.assign(window, { useInView, useStaggered, useTween });
