// ScrollHijack — Saint-Laurent-style locked slideshow for the home route.
//
// Replaces native scroll on the home page with a one-gesture-one-panel system:
// each wheel flick, swipe, or arrow press advances EXACTLY one full-viewport
// panel and locks there — you can never sit between panels or see two at once.
// The track is a vertical stack of 100dvh panels translated by transform with a
// calm ease; a cooldown plus a wheel-idle re-arm absorbs trackpad inertia so one
// flick never skips two panels.
//
// It owns no chrome of its own — the nav and WhatsApp FAB live ABOVE this
// container (higher z-index, outside the track) so they stay fixed and
// functional across every panel. Other components talk to it through window
// events: dispatch `hijack:go` ({dir:±1} or {to:index}) to drive it; it emits
// `hijack:change` ({index,count}) on every settle and `hijack:end` on unmount.
const HIJACK_TRANSITION = 720;   // ms — must match the CSS transition on .hijack__track
const HIJACK_COOLDOWN   = 720;   // ms — input ignored until the glide has settled
const HIJACK_REARM      = 120;   // ms of wheel silence before a new flick may fire

function ScrollHijack({ children }) {
  const items = React.Children.toArray(children).filter(Boolean);
  const count = items.length;
  const [index, setIndex] = React.useState(0);
  const indexRef = React.useRef(0);
  const lockRef  = React.useRef(false);   // true while a glide is in flight
  const trackRef = React.useRef(null);

  // Translate the track to the current panel. `animate=false` snaps without a
  // transition (used on resize / orientation change so it never visibly slides).
  const applyTransform = React.useCallback((animate = true) => {
    const track = trackRef.current;
    if (!track) return;
    // Mobile uses native scroll — never translate the track.
    if (window.matchMedia("(max-width: 760px)").matches) { track.style.transform = "none"; return; }
    const first = track.children[0];
    const h = (first && first.offsetHeight) || window.innerHeight;
    if (!animate) {
      const prev = track.style.transition;
      track.style.transition = "none";
      track.style.transform = `translate3d(0, ${-indexRef.current * h}px, 0)`;
      void track.offsetHeight;            // force reflow so the next change animates
      track.style.transition = prev || "";
    } else {
      track.style.transform = `translate3d(0, ${-indexRef.current * h}px, 0)`;
    }
  }, []);

  const goTo = React.useCallback((next) => {
    const clamped = Math.min(count - 1, Math.max(0, next));
    if (clamped === indexRef.current || lockRef.current) return;
    lockRef.current = true;
    indexRef.current = clamped;
    setIndex(clamped);
    window.dispatchEvent(new CustomEvent("hijack:change", { detail: { index: clamped, count } }));
    setTimeout(() => { lockRef.current = false; }, HIJACK_COOLDOWN);
  }, [count]);

  const go = React.useCallback((dir) => goTo(indexRef.current + dir), [goTo]);

  React.useEffect(() => { applyTransform(true); }, [index, applyTransform]);

  React.useEffect(() => {
    const mq = window.matchMedia("(max-width: 760px)");

    // --- Mobile: fall back to native vertical scroll. The transform-driven
    //     hijack with touch-action:none + 100dvh panels stutters on touch and
    //     fights the address-bar dynamic-viewport resize, so on phones the
    //     panels simply stack and scroll natively (desktop keeps the hijack).
    //     An IntersectionObserver still emits `hijack:change` so the nav tone
    //     adapts per panel, and `hijack:go` (hero cue, anchor jumps) becomes a
    //     smooth native scroll to the target panel. ---
    if (mq.matches) {
      const cont = trackRef.current ? trackRef.current.parentElement : null;
      if (cont) cont.classList.add("hijack--native");
      if (trackRef.current) trackRef.current.style.transform = "none";
      const panels = trackRef.current ? Array.prototype.slice.call(trackRef.current.children) : [];
      const io = new IntersectionObserver((entries) => {
        entries.forEach((en) => {
          if (en.isIntersecting) {
            const i = panels.indexOf(en.target);
            if (i >= 0) window.dispatchEvent(new CustomEvent("hijack:change", { detail: { index: i, count } }));
          }
        });
      }, { threshold: 0.5 });
      panels.forEach((p) => io.observe(p));
      const onGoNative = (e) => {
        const d = e.detail || {};
        const cur = Math.round(window.pageYOffset / window.innerHeight);
        let i = typeof d.to === "number" ? d.to : cur + (d.dir || 1);
        i = Math.max(0, Math.min(panels.length - 1, i));
        const target = panels[i];
        if (target) window.scrollTo({ top: target.getBoundingClientRect().top + window.pageYOffset, behavior: "smooth" });
      };
      window.addEventListener("hijack:go", onGoNative);
      const onMQ = () => window.location.reload();   // re-init cleanly when crossing the breakpoint
      mq.addEventListener("change", onMQ);
      return () => {
        io.disconnect();
        window.removeEventListener("hijack:go", onGoNative);
        mq.removeEventListener("change", onMQ);
        if (cont) cont.classList.remove("hijack--native");
      };
    }

    applyTransform(false);
    // Announce the starting panel once the rest of the tree (e.g. the FAB) has
    // mounted its listeners.
    const announce = requestAnimationFrame(() =>
      window.dispatchEvent(new CustomEvent("hijack:change", { detail: { index: indexRef.current, count } })));

    // Skip hijack while a full-screen overlay is actually OPEN (the closed menu
    // overlay stays in the DOM with pointer-events:none, so test interactivity,
    // not mere existence) — lets the overlay scroll/handle input natively.
    const blocked = () => {
      const els = document.querySelectorAll(".menu-overlay, .tm-overlay");
      for (const el of els) {
        if (el.getAttribute("aria-hidden") === "true") continue;
        if (getComputedStyle(el).pointerEvents === "none") continue;
        return true;
      }
      return false;
    };

    // --- wheel / trackpad: fire on the first event of a flick, then disarm
    //     until the wheel goes quiet, so inertia tails can't advance again. ---
    let canFire = true, quietTimer = 0;
    const onWheel = (e) => {
      if (blocked()) return;
      e.preventDefault();                 // body is overflow-hidden anyway; belt & braces
      clearTimeout(quietTimer);
      quietTimer = setTimeout(() => { canFire = true; }, HIJACK_REARM);
      if (canFire && !lockRef.current && Math.abs(e.deltaY) >= 6) {
        canFire = false;
        go(e.deltaY > 0 ? 1 : -1);
      }
    };

    // --- keyboard: arrows + Page keys, never trapping form fields or Tab. ---
    const onKey = (e) => {
      const t = e.target;
      const tag = (t && t.tagName ? t.tagName : "").toLowerCase();
      if (tag === "input" || tag === "textarea" || tag === "select" || (t && t.isContentEditable)) return;
      if (blocked()) return;
      switch (e.key) {
        case "ArrowDown": case "PageDown": e.preventDefault(); go(1); break;
        case "ArrowUp":   case "PageUp":   e.preventDefault(); go(-1); break;
        case "Home": e.preventDefault(); goTo(0); break;
        case "End":  e.preventDefault(); goTo(count - 1); break;
        default: break;
      }
    };

    // --- touch: one vertical swipe = one panel. ---
    let tsY = 0, tsX = 0, touching = false;
    const onTouchStart = (e) => { const t = e.touches[0]; tsY = t.clientY; tsX = t.clientX; touching = true; };
    const onTouchMove  = (e) => { if (touching && !blocked() && e.cancelable) e.preventDefault(); };
    const onTouchEnd   = (e) => {
      if (!touching) return;
      touching = false;
      if (blocked()) return;
      const t = e.changedTouches[0];
      const dy = t.clientY - tsY, dx = t.clientX - tsX;
      if (Math.abs(dy) < 44 || Math.abs(dx) > Math.abs(dy)) return;   // ignore taps & horizontal
      go(dy < 0 ? 1 : -1);                                            // swipe up → next
    };

    const onGo = (e) => {
      const d = e.detail || {};
      if (typeof d.to === "number") goTo(d.to); else go(d.dir || 1);
    };
    const onResize = () => applyTransform(false);
    const onMQ = () => window.location.reload();   // re-init cleanly when crossing the breakpoint
    mq.addEventListener("change", onMQ);

    window.addEventListener("wheel", onWheel, { passive: false });
    window.addEventListener("keydown", onKey);
    window.addEventListener("touchstart", onTouchStart, { passive: true });
    window.addEventListener("touchmove", onTouchMove, { passive: false });
    window.addEventListener("touchend", onTouchEnd);
    window.addEventListener("hijack:go", onGo);
    window.addEventListener("resize", onResize);

    return () => {
      cancelAnimationFrame(announce);
      clearTimeout(quietTimer);
      window.removeEventListener("wheel", onWheel);
      window.removeEventListener("keydown", onKey);
      window.removeEventListener("touchstart", onTouchStart);
      window.removeEventListener("touchmove", onTouchMove);
      window.removeEventListener("touchend", onTouchEnd);
      window.removeEventListener("hijack:go", onGo);
      window.removeEventListener("resize", onResize);
      mq.removeEventListener("change", onMQ);
      window.dispatchEvent(new CustomEvent("hijack:end"));
    };
  }, [go, goTo, count, applyTransform]);

  return (
    <div className="hijack">
      <div className="hijack__track" ref={trackRef}>
        {items.map((child, i) => (
          <div className="hijack__panel" key={i}>{child}</div>
        ))}
      </div>
    </div>
  );
}
window.ScrollHijack = ScrollHijack;
