import { useEffect, useRef, useCallback } from "react";
import { useLocation } from "@tanstack/react-router";

/** Adds .in class to .reveal / .reveal-left / .reveal-right / .reveal-zoom when scrolled into view.
 *  Uses a persistent IntersectionObserver + MutationObserver that survive route changes.
 *  On route change, triggers a re-scan with RAF delay to catch newly mounted DOM.
 *  Safety net: any element still hidden after 600ms is force-shown to prevent invisible content. */
export function ScrollReveal() {
  const location = useLocation();
  const ioRef = useRef<IntersectionObserver | null>(null);
  const moRef = useRef<MutationObserver | null>(null);

  const selector = ".reveal, .reveal-left, .reveal-right, .reveal-zoom";

  const showAll = useCallback(() => {
    document.querySelectorAll<HTMLElement>(selector).forEach((el) => el.classList.add("in"));
  }, []);

  const observeAll = useCallback(() => {
    const io = ioRef.current;
    if (!io) {
      showAll();
      return;
    }
    document.querySelectorAll<HTMLElement>(selector).forEach((el) => {
      if (!el.classList.contains("in")) io.observe(el);
    });
  }, [showAll]);

  // Create persistent IO and MO — only once on mount
  useEffect(() => {
    if (typeof window === "undefined") return;

    const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    const supportsIO = typeof IntersectionObserver !== "undefined";

    if (reduce || !supportsIO) {
      showAll();
      const mo = new MutationObserver(() => showAll());
      mo.observe(document.body, { childList: true, subtree: true });
      moRef.current = mo;
      return () => { mo.disconnect(); moRef.current = null; };
    }

    const io = new IntersectionObserver(
      (entries) => {
        entries.forEach((e) => {
          if (e.isIntersecting) {
            e.target.classList.add("in");
            io.unobserve(e.target);
          }
        });
      },
      { threshold: 0.05, rootMargin: "0px 0px -10px 0px" }
    );
    ioRef.current = io;

    observeAll();

    // Watch for newly added nodes (filters, async lists, route remounts)
    const mo = new MutationObserver(() => {
      requestAnimationFrame(() => observeAll());
    });
    mo.observe(document.body, { childList: true, subtree: true });
    moRef.current = mo;

    return () => {
      io.disconnect();
      mo.disconnect();
      ioRef.current = null;
      moRef.current = null;
    };
  }, [observeAll, showAll]);

  // On every route change, re-scan DOM with delays to ensure new route content is painted
  useEffect(() => {
    if (typeof window === "undefined") return;

    // Multiple observation passes to handle any rendering delays
    const raf1 = requestAnimationFrame(() => observeAll());
    const t1 = setTimeout(() => observeAll(), 50);
    const t2 = setTimeout(() => observeAll(), 150);
    // Failsafe: force-show anything still hidden after 600ms
    const failsafe = setTimeout(showAll, 600);

    return () => {
      cancelAnimationFrame(raf1);
      clearTimeout(t1);
      clearTimeout(t2);
      clearTimeout(failsafe);
    };
  }, [location.pathname, observeAll, showAll]);

  return null;
}
