/* global React */
const { useEffect, useRef, useState, useCallback } = React;

// Track scroll progress of an element through the viewport.
// Returns p ∈ [0,1] where 0 = element top entering bottom of viewport, 1 = element bottom leaving top.
function useScrollProgress(ref) {
  const [p, setP] = useState(0);
  useEffect(() => {
    function onScroll() {
      const el = ref.current;
      if (!el) return;
      const rect = el.getBoundingClientRect();
      const vh = window.innerHeight;
      const total = rect.height + vh;
      const scrolled = vh - rect.top;
      const raw = scrolled / total;
      setP(Math.max(0, Math.min(1, raw)));
    }
    onScroll();
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll);
    return () => {
      window.removeEventListener('scroll', onScroll);
      window.removeEventListener('resize', onScroll);
    };
  }, [ref]);
  return p;
}

// Local progress: start when element enters viewport top area, end when exits.
function useInViewProgress(ref, opts = {}) {
  const { enterAt = 0.9, exitAt = 0.1 } = opts;
  const [p, setP] = useState(0);
  useEffect(() => {
    function onScroll() {
      const el = ref.current;
      if (!el) return;
      const rect = el.getBoundingClientRect();
      const vh = window.innerHeight;
      const top = rect.top / vh;  // 1 = just entered bottom
      // Map [enterAt -> exitAt] to [0 -> 1]
      const raw = (enterAt - top) / (enterAt - exitAt);
      setP(Math.max(0, Math.min(1, raw)));
    }
    onScroll();
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll);
    return () => {
      window.removeEventListener('scroll', onScroll);
      window.removeEventListener('resize', onScroll);
    };
  }, [ref, enterAt, exitAt]);
  return p;
}

function useReveal() {
  const ref = useRef(null);
  const [inView, setInView] = useState(false);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const io = new IntersectionObserver(([e]) => {
      if (e.isIntersecting) { setInView(true); io.disconnect(); }
    }, { threshold: 0.15 });
    io.observe(el);
    return () => io.disconnect();
  }, []);
  return [ref, inView];
}

function clamp(v, a, b) { return Math.max(a, Math.min(b, v)); }
function lerp(a, b, t) { return a + (b - a) * t; }
function easeOut(t) { return 1 - Math.pow(1 - t, 3); }
function easeInOut(t) { return t < 0.5 ? 2*t*t : 1 - Math.pow(-2*t+2, 2)/2; }

Object.assign(window, {
  useScrollProgress, useInViewProgress, useReveal,
  clamp, lerp, easeOut, easeInOut,
});
