// useReveal — IntersectionObserver-based scroll-trigger hook.
// Adds .in to the element when it scrolls into view.

// useReveal — adds .in to the ref'd element when it scrolls into view.
// Uses IntersectionObserver when available, with a scroll-listener fallback
// (some sandboxed iframes throttle IO so it never fires).

function useReveal({ threshold = 0.15, once = true, rootMargin = "0px 0px -10% 0px" } = {}) {
  const ref = React.useRef(null);
  const [inView, setInView] = React.useState(false);

  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;

    const fire = () => {
      setInView(true);
      el.classList.add("in");
    };
    const unfire = () => {
      setInView(false);
      el.classList.remove("in");
    };

    const check = () => {
      const r = el.getBoundingClientRect();
      const vh = window.innerHeight || document.documentElement.clientHeight;
      const vw = window.innerWidth || document.documentElement.clientWidth;
      // Element is "in view" once its top crosses 90% of the viewport
      // (matches the -10% bottom rootMargin from the IO version).
      const visible =
        r.bottom > 0 &&
        r.top < vh * 0.9 &&
        r.right > 0 &&
        r.left < vw;
      if (visible) {
        fire();
        return true;
      }
      if (!once) unfire();
      return false;
    };

    // Try IntersectionObserver first; some preview sandboxes don't fire it,
    // so we ALSO run a scroll-based check as a belt-and-braces backup.
    let io = null;
    if (typeof IntersectionObserver !== "undefined") {
      io = new IntersectionObserver(
        (entries) => {
          for (const e of entries) {
            if (e.isIntersecting) {
              fire();
              if (once && io) io.disconnect();
            } else if (!once) {
              unfire();
            }
          }
        },
        { threshold, rootMargin }
      );
      io.observe(el);
    }

    // Fallback: scroll/resize listener + initial check on next frame.
    let done = false;
    const onScroll = () => {
      if (done && once) return;
      if (check() && once) {
        done = true;
        window.removeEventListener("scroll", onScroll);
        window.removeEventListener("resize", onScroll);
        document.removeEventListener("visibilitychange", onScroll);
      }
    };
    // Initial check after layout settles.
    const raf = requestAnimationFrame(() => {
      onScroll();
      // And once more after a beat to cover late layout (fonts, images).
      setTimeout(onScroll, 300);
    });
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    // Re-check when the tab becomes visible (CSS transitions and IO callbacks
    // are paused while the document is hidden, so anything that became
    // visible in the meantime needs a kick).
    document.addEventListener("visibilitychange", onScroll);

    return () => {
      if (io) io.disconnect();
      cancelAnimationFrame(raf);
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onScroll);
      document.removeEventListener("visibilitychange", onScroll);
    };
  }, [threshold, once, rootMargin]);

  return [ref, inView];
}

// useCountUp — animate a number to its target on view. Pass formatter.
function useCountUp(target, { duration = 1200, trigger = true, fmt = (n) => Math.round(n).toString() } = {}) {
  const [val, setVal] = React.useState(fmt(0));
  React.useEffect(() => {
    if (!trigger) return;
    const t0 = performance.now();
    let raf;
    const tick = (t) => {
      const p = Math.min(1, (t - t0) / duration);
      const eased = 1 - Math.pow(1 - p, 3);
      setVal(fmt(target * eased));
      if (p < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [target, duration, trigger]);
  return val;
}

// useParallax — drives --parallax-y on the ref'd element based on its position
// in the viewport. `speed` is a unitless multiplier (0.1–0.4 feels right for
// background motifs).
function useParallax({ speed = 0.18, max = 120 } = {}) {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    let raf = null;
    const update = () => {
      raf = null;
      const r = el.getBoundingClientRect();
      const vh = window.innerHeight || 1;
      // Center of element relative to viewport center, normalised -1..1
      const t = (r.top + r.height / 2 - vh / 2) / vh;
      const y = Math.max(-max, Math.min(max, -t * vh * speed));
      el.style.setProperty("--parallax-y", `${y.toFixed(1)}px`);
    };
    const onScroll = () => { if (!raf) raf = requestAnimationFrame(update); };
    update();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    return () => {
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onScroll);
      if (raf) cancelAnimationFrame(raf);
    };
  }, [speed, max]);
  return ref;
}

// Parallax — drop-in wrapper. <Parallax speed={0.2} className="...">...</Parallax>
function Parallax({ children, speed = 0.18, max = 120, as = "div", className = "", style, ...rest }) {
  const ref = useParallax({ speed, max });
  const Tag = as;
  return (
    <Tag ref={ref} className={`parallax-slow ${className}`} style={style} {...rest}>
      {children}
    </Tag>
  );
}

Object.assign(window, { useReveal, useCountUp, useParallax, Parallax });
