/* NEXABOT — GSAP scroll animations (Hubtown-style word reveals + fades + parallax) */
(function () {
  let installed = false;

  function splitWords(el) {
    if (!el || el.dataset.splitDone) return;
    el.dataset.splitDone = "1";
    const build = (src, dst) => {
      src.childNodes.forEach((ch) => {
        if (ch.nodeType === 3) {
          const parts = ch.textContent.split(/(\s+)/);
          parts.forEach((p) => {
            if (p === "") return;
            if (/^\s+$/.test(p)) { dst.appendChild(document.createTextNode(p)); return; }
            const mask = document.createElement("span"); mask.className = "word-mask";
            const w = document.createElement("span"); w.className = "word"; w.textContent = p;
            mask.appendChild(w); dst.appendChild(mask);
          });
        } else if (ch.nodeType === 1) {
          const clone = ch.cloneNode(false);
          dst.appendChild(clone);
          build(ch, clone);
        }
      });
    };
    const tmp = document.createElement("span");
    build(el, tmp);
    el.textContent = "";
    while (tmp.firstChild) el.appendChild(tmp.firstChild);
  }

  function clearShown(el) {
    if (!window.gsap) return;
    if (parseFloat(getComputedStyle(el).opacity) < 0.03) {
      gsap.set(el, { opacity: 1, y: 0, clearProps: "transform" });
    }
    const ws = el.querySelectorAll(".word");
    if (ws.length && parseFloat(getComputedStyle(ws[0]).opacity) < 0.03) {
      gsap.set(ws, { yPercent: 0, opacity: 1, clearProps: "transform" });
    }
  }

  // safety: anytime an element is in view but still hidden (throttled/paused rAF), force it visible
  function forceVisibleInView() {
    if (!window.gsap) return;
    const vh = window.innerHeight;
    document.querySelectorAll(".reveal, [data-split]").forEach((el) => {
      const r = el.getBoundingClientRect();
      if (r.bottom > 0 && r.top < vh * 0.96) clearShown(el);
    });
  }

  function installSafety() {
    if (installed) return; installed = true;
    let deb;
    const onScroll = () => { forceVisibleInView(); clearTimeout(deb); deb = setTimeout(forceVisibleInView, 120); };
    window.addEventListener("scroll", onScroll, { passive: true });
    document.addEventListener("visibilitychange", () => { if (document.visibilityState === "visible") setTimeout(forceVisibleInView, 120); });
    // timer backstop: reveals in-view content even when scroll/rAF are throttled (e.g. background tab)
    setInterval(forceVisibleInView, 500);
    setTimeout(forceVisibleInView, 400);
    setTimeout(forceVisibleInView, 1200);
    // smooth scroll for user anchor clicks (kept off the global CSS so programmatic scroll stays instant)
    document.addEventListener("click", (e) => {
      const a = e.target.closest && e.target.closest('a[href^="#"]');
      if (!a) return;
      const id = a.getAttribute("href");
      if (id.length < 2) return;
      const target = document.querySelector(id);
      if (!target) return;
      e.preventDefault();
      window.scrollTo({ top: target.getBoundingClientRect().top + window.scrollY - 70, behavior: "smooth" });
    });
  }

  const NexaAnim = {
    init() {
      const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
      const hasGSAP = !!(window.gsap && window.ScrollTrigger);
      const root = document.documentElement;

      if (!hasGSAP || reduce || document.body.classList.contains("no-anim")) {
        root.classList.remove("gsap-on");
        return false; // caller uses CSS-reveal fallback
      }
      root.classList.add("gsap-on");
      gsap.registerPlugin(ScrollTrigger);

      // tear down previous (e.g. on language switch)
      ScrollTrigger.getAll().forEach((t) => t.kill());

      // split static headings (reset if React replaced their text, e.g. on language switch)
      document.querySelectorAll("[data-split]").forEach((el) => {
        if (el.dataset.splitDone && !el.querySelector(".word")) delete el.dataset.splitDone;
        splitWords(el);
      });

      // headline word-rise
      gsap.utils.toArray("[data-split]").forEach((el) => {
        const words = el.querySelectorAll(".word");
        if (!words.length) return;
        gsap.set(el, { opacity: 1 });
        gsap.from(words, {
          yPercent: 115, opacity: 0, duration: 0.95, ease: "power3.out", stagger: 0.04,
          scrollTrigger: { trigger: el, start: "top 90%", once: true },
        });
      });

      // generic fade-up blocks
      gsap.utils.toArray(".reveal").forEach((el) => {
        gsap.from(el, {
          y: 42, opacity: 0, duration: 1.0, ease: "power3.out",
          scrollTrigger: { trigger: el, start: "top 92%", once: true },
        });
      });

      // parallax fog canvases
      gsap.utils.toArray(".hero-canvas, .portfolio-canvas").forEach((c) => {
        const sec = c.closest("section");
        if (!sec) return;
        gsap.to(c, { yPercent: 16, ease: "none", scrollTrigger: { trigger: sec, start: "top top", end: "bottom top", scrub: true } });
      });

      ScrollTrigger.refresh();
      installSafety();
      setTimeout(() => ScrollTrigger.refresh(), 400);
      return true;
    },
  };

  window.NexaAnim = NexaAnim;
})();
