import Lenis from "lenis";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);

const prefersReduced = window.matchMedia(
  "(prefers-reduced-motion: reduce)"
).matches;

// ── 1. Lenis smooth scroll ────────────────────────────────────
const lenis = new Lenis({
  duration: 1.2,
  easing: (t: number) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
  smoothWheel: !prefersReduced,
});

gsap.ticker.add((time) => lenis.raf(time * 1000));
gsap.ticker.lagSmoothing(0);

// ── 2. Counter animation utility ──────────────────────────────
function animateCounter(el: HTMLElement, target: number) {
  if (prefersReduced) {
    el.textContent = target.toLocaleString();
    return;
  }
  const start = performance.now();
  const duration = 1800;
  const easeOut = (t: number) => 1 - Math.pow(1 - t, 3);
  const tick = (now: number) => {
    const t = Math.min((now - start) / duration, 1);
    el.textContent = Math.round(easeOut(t) * target).toLocaleString();
    if (t < 1) requestAnimationFrame(tick);
  };
  requestAnimationFrame(tick);
}

// ── 3. Counter observer (IO) ──────────────────────────────────
const counterIO = new IntersectionObserver(
  (entries) => {
    entries.forEach((e) => {
      if (!e.isIntersecting) return;
      const el = e.target as HTMLElement;
      animateCounter(el, +(el.dataset.target || 0));
      counterIO.unobserve(el);
    });
  },
  { threshold: 0.2 }
);

function observeCounters() {
  document
    .querySelectorAll<HTMLElement>(
      "[data-reveal='counter']:not([data-observed])"
    )
    .forEach((el) => {
      el.setAttribute("data-observed", "true");
      counterIO.observe(el);
    });
}

// ── 4. Cleanup stale ScrollTriggers ───────────────────────────
// On SPA navigation, old page elements are removed from the DOM.
// Their ScrollTrigger instances must be killed to avoid leaks.
function cleanupStaleScrollTriggers() {
  ScrollTrigger.getAll().forEach((st) => {
    const trigger = st.vars.trigger as HTMLElement | undefined;
    if (trigger && !document.contains(trigger)) {
      st.kill();
    }
  });
}

// ── 5. Mouse-move parallax on images & text ────────────────────
function initMouseParallax() {
  if (prefersReduced) return;

  // Images — shift ±7px following cursor
  document
    .querySelectorAll<HTMLElement>(
      "section img:not(.absolute):not([class*='absolute']):not([data-mouse-parallax])"
    )
    .forEach((img) => {
      const wrapper = img.parentElement;
      if (
        !wrapper ||
        wrapper.tagName === "SECTION" ||
        wrapper === document.body
      )
        return;

      img.setAttribute("data-mouse-parallax", "true");

      const xTo = gsap.quickTo(img, "x", {
        duration: 0.5,
        ease: "power2.out",
      });
      const yTo = gsap.quickTo(img, "y", {
        duration: 0.5,
        ease: "power2.out",
      });

      wrapper.addEventListener("mousemove", (e: MouseEvent) => {
        const rect = wrapper.getBoundingClientRect();
        const cx = (e.clientX - rect.left) / rect.width - 0.5;
        const cy = (e.clientY - rect.top) / rect.height - 0.5;
        xTo(cx * 14);
        yTo(cy * 14);
      });

      wrapper.addEventListener("mouseleave", () => {
        xTo(0);
        yTo(0);
      });
    });

  // Text — subtle shift on headings & paragraphs within each section
  document
    .querySelectorAll<HTMLElement>("section:not([data-text-parallax])")
    .forEach((section) => {
      section.setAttribute("data-text-parallax", "true");

      const textEls = section.querySelectorAll<HTMLElement>(
        "h1:not(nav h1), h2, h3, p:not(nav p):not(header p):not(footer p)"
      );
      if (textEls.length === 0) return;

      const quickSets = Array.from(textEls).map((el) => ({
        xTo: gsap.quickTo(el, "x", { duration: 0.6, ease: "power2.out" }),
        yTo: gsap.quickTo(el, "y", { duration: 0.6, ease: "power2.out" }),
      }));

      section.addEventListener("mousemove", (e: MouseEvent) => {
        const rect = section.getBoundingClientRect();
        const cx = (e.clientX - rect.left) / rect.width - 0.5;
        const cy = (e.clientY - rect.top) / rect.height - 0.5;
        quickSets.forEach((qs) => {
          qs.xTo(cx * 10);
          qs.yTo(cy * 6);
        });
      });

      section.addEventListener("mouseleave", () => {
        quickSets.forEach((qs) => {
          qs.xTo(0);
          qs.yTo(0);
        });
      });
    });
}

// ── 6. Auto-animate sections on scroll ────────────────────────
function autoAnimateSections() {
  if (prefersReduced) return;

  const vh = window.innerHeight;

  document
    .querySelectorAll<HTMLElement>("section:not([data-animated])")
    .forEach((section) => {
      section.setAttribute("data-animated", "true");

      const headings = section.querySelectorAll<HTMLElement>("h2, h3");
      const paragraphs = section.querySelectorAll<HTMLElement>(
        "p:not(nav p):not(header p):not(footer p)"
      );
      const buttons = section.querySelectorAll<HTMLElement>(
        "button:not([disabled]):not(.marquee-track button), a[href]:not(nav a):not(header a)"
      );
      const gridChildren = section.querySelectorAll<HTMLElement>(
        "[class*='grid'] > *"
      );
      const contentImages = section.querySelectorAll<HTMLElement>(
        "img:not(.absolute):not([class*='absolute'])"
      );

      const isBelowFold = (el: HTMLElement) =>
        el.getBoundingClientRect().top > vh;

      // ── Headings — slide up + fade (below-fold only) ──
      headings.forEach((el) => {
        if (!isBelowFold(el)) return;
        gsap.fromTo(
          el,
          { y: 40, opacity: 0 },
          {
            y: 0,
            opacity: 1,
            duration: 0.8,
            ease: "power3.out",
            scrollTrigger: {
              trigger: el,
              start: "top 88%",
              toggleActions: "play none none none",
            },
          }
        );
      });

      // ── Paragraphs — slide up + fade (below-fold only) ──
      paragraphs.forEach((el) => {
        if (!isBelowFold(el)) return;
        gsap.fromTo(
          el,
          { y: 24, opacity: 0 },
          {
            y: 0,
            opacity: 1,
            duration: 0.6,
            ease: "power2.out",
            scrollTrigger: {
              trigger: el,
              start: "top 90%",
              toggleActions: "play none none none",
            },
          }
        );
      });

      // ── Buttons — slide up + fade (below-fold only) ──
      buttons.forEach((el) => {
        if (!isBelowFold(el)) return;
        gsap.fromTo(
          el,
          { y: 16, opacity: 0 },
          {
            y: 0,
            opacity: 1,
            duration: 0.5,
            ease: "power2.out",
            scrollTrigger: {
              trigger: el,
              start: "top 92%",
              toggleActions: "play none none none",
            },
          }
        );
      });

      // ── Grid children — staggered entrance (below-fold only) ──
      if (gridChildren.length > 1) {
        const below = Array.from(gridChildren).filter((el) =>
          isBelowFold(el)
        );
        if (below.length > 0) {
          gsap.fromTo(
            below,
            { y: 30, opacity: 0 },
            {
              y: 0,
              opacity: 1,
              duration: 0.5,
              stagger: 0.1,
              ease: "power2.out",
              scrollTrigger: {
                trigger: below[0]?.parentElement ?? section,
                start: "top 85%",
                toggleActions: "play none none none",
              },
            }
          );
        }
      }

      // ── Content images — parallax on parent wrapper ──
      contentImages.forEach((el) => {
        const wrapper = el.parentElement;
        if (!wrapper || wrapper === section || wrapper.tagName === "SECTION")
          return;
        if (wrapper.hasAttribute("data-parallax")) return;
        wrapper.setAttribute("data-parallax", "true");
        gsap.to(wrapper, {
          yPercent: -3,
          ease: "none",
          scrollTrigger: {
            trigger: wrapper,
            start: "top bottom",
            end: "bottom top",
            scrub: 1,
          },
        });
      });
    });

  // Recalculate all ScrollTrigger positions after setup
  ScrollTrigger.refresh();

  // Attach mouse-move parallax after scroll animations are set up
  initMouseParallax();
}

// ── 7. SPA navigation handler ─────────────────────────────────
// Debounced re-init that waits for layout to settle after navigation.
let navTimer: ReturnType<typeof setTimeout>;

function onDOMChange() {
  clearTimeout(navTimer);
  navTimer = setTimeout(() => {
    cleanupStaleScrollTriggers();
    observeCounters();
    autoAnimateSections();
  }, 250);
}

// ── 8. Initialize ─────────────────────────────────────────────
function init() {
  observeCounters();
  setTimeout(autoAnimateSections, 150);
}

if (document.readyState === "loading") {
  document.addEventListener("DOMContentLoaded", init);
} else {
  requestAnimationFrame(init);
}

const mo = new MutationObserver(onDOMChange);
mo.observe(document.body, { childList: true, subtree: true });

export { lenis };
