/* Root App component */

const {
  HeroSection, PitchSection, ArchitectureSection,
  ChooserSection, DepositFlowSection, WithdrawalFlowSection, CloseSection,
  MockupOverlay,
} = window;

const SECTIONS = [
  { id: "hero",         label: "Hero" },
  { id: "pitch",        label: "What it means" },
  { id: "architecture", label: "Architecture" },
  { id: "chooser",      label: "Live the flow" },
  { id: "deposit-flow", label: "Deposit" },
  { id: "withdraw-flow",label: "Withdrawal" },
  { id: "close",        label: "Contact" },
];

const App = () => {
  const [activeFlow, setActiveFlow] = React.useState(null);
  const [activeSection, setActiveSection] = React.useState("hero");
  const rootRef = React.useRef(null);

  // Custom section-locked scroll: one panel per wheel/key, with resistance.
  React.useEffect(() => {
    const scroller = rootRef.current;
    if (!scroller) return;

    let animFrame = 0;
    let lockUntil = 0;
    // Inertia damping: each wheel event pushes this forward; we only accept
    // a new step once the wheel has been silent for ~120ms. This prevents
    // the trailing tail of a trackpad swipe from triggering a second step.
    let wheelIdleUntil = 0;
    let currentIdx = 0;

    const panels = () => Array.from(scroller.querySelectorAll(".panel"));

    const animateTo = (target, duration = 520) => {
      cancelAnimationFrame(animFrame);
      const start = scroller.scrollTop;
      const dist = target - start;
      if (Math.abs(dist) < 1) return;
      const t0 = performance.now();
      // Softer ease-in-out — gentle takeoff and gentle landing, no hard impact
      const ease = (t) => (t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2);
      const step = (now) => {
        const p = Math.min(1, (now - t0) / duration);
        scroller.scrollTop = start + dist * ease(p);
        if (p < 1) animFrame = requestAnimationFrame(step);
        else triggerLandingBounce(target);
      };
      animFrame = requestAnimationFrame(step);
    };

    const triggerLandingBounce = (targetTop) => {
      const list = panels();
      const vh = scroller.clientHeight;
      const idx = Math.round(targetTop / vh);
      const panel = list[idx];
      if (!panel) return;
      panel.classList.remove("just-landed");
      void panel.offsetWidth;
      panel.classList.add("just-landed");
      setTimeout(() => panel.classList.remove("just-landed"), 700);
    };

    const goTo = (idx) => {
      const list = panels();
      const next = Math.max(0, Math.min(list.length - 1, idx));
      currentIdx = next;
      const vh = scroller.clientHeight;
      // lock for the whole animation + small buffer so trackpad inertia can't sneak through
      lockUntil = performance.now() + 600;
      animateTo(next * vh, 520);
    };

    const stepBy = (dir) => {
      if (performance.now() < lockUntil) return;
      const list = panels();
      // step from the TARGET (currentIdx), not from current scrollTop
      const next = Math.max(0, Math.min(list.length - 1, currentIdx + dir));
      if (next === currentIdx) return;
      goTo(next);
    };

    // Resync currentIdx if anything external (resize, anchor) lands us elsewhere
    const resync = () => {
      const vh = scroller.clientHeight;
      currentIdx = Math.round(scroller.scrollTop / vh);
    };

    const onWheel = (e) => {
      if (e.target.closest && e.target.closest(".mockup-overlay")) return;
      e.preventDefault();
      if (Math.abs(e.deltaY) < 4) return;
      const now = performance.now();
      // any wheel event pushes the idle window forward
      const wasInIdle = now >= wheelIdleUntil;
      wheelIdleUntil = now + 120;
      // only fire a step when the wheel was previously idle (i.e. fresh gesture)
      // AND we're not still locked from the previous transition
      if (!wasInIdle) return;
      if (now < lockUntil) return;
      stepBy(e.deltaY > 0 ? 1 : -1);
    };

    const onKey = (e) => {
      if (document.querySelector(".mockup-overlay.open")) return;
      if (["ArrowDown", "PageDown", " "].includes(e.key)) { e.preventDefault(); stepBy(1); }
      else if (["ArrowUp", "PageUp"].includes(e.key)) { e.preventDefault(); stepBy(-1); }
      else if (e.key === "Home") { e.preventDefault(); goTo(0); }
      else if (e.key === "End")  { e.preventDefault(); goTo(panels().length - 1); }
    };

    let touchStartY = null;
    let touchActive = false;
    const onTouchStart = (e) => {
      if (e.target.closest && e.target.closest(".mockup-overlay")) return;
      touchStartY = e.touches[0].clientY;
      touchActive = true;
    };
    const onTouchMove = (e) => {
      if (!touchActive) return;
      e.preventDefault();
    };
    const onTouchEnd = (e) => {
      if (!touchActive || touchStartY === null) return;
      touchActive = false;
      const endY = (e.changedTouches[0] || {}).clientY;
      if (endY === undefined) return;
      const dy = touchStartY - endY;
      touchStartY = null;
      if (Math.abs(dy) < 30) return;
      stepBy(dy > 0 ? 1 : -1);
    };

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

    scroller.__goToIndex = goTo;

    return () => {
      scroller.removeEventListener("wheel", onWheel);
      window.removeEventListener("keydown", onKey);
      window.removeEventListener("resize", resync);
      scroller.removeEventListener("touchstart", onTouchStart);
      scroller.removeEventListener("touchmove", onTouchMove);
      scroller.removeEventListener("touchend", onTouchEnd);
      cancelAnimationFrame(animFrame);
    };
  }, []);

  // Scroll to a section by id (used by nav + chooser CTAs)
  const scrollTo = React.useCallback((id) => {
    const list = Array.from(document.querySelectorAll("[data-section-id]"));
    const idx = list.findIndex((el) => el.getAttribute("data-section-id") === id);
    if (idx < 0) return;
    const scroller = rootRef.current;
    if (scroller && scroller.__goToIndex) scroller.__goToIndex(idx);
  }, []);

  // Track which section is currently visible
  React.useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting && entry.intersectionRatio >= 0.5) {
            const id = entry.target.getAttribute("data-section-id");
            if (id) setActiveSection(id);
          }
        });
      },
      { threshold: [0.5] }
    );

    document.querySelectorAll("[data-section-id]").forEach((el) => observer.observe(el));
    return () => observer.disconnect();
  }, []);

  // Trigger reveal animations: observe whole sections, then mark their reveals.
  // Observing sections (which never resize) avoids ResizeObserver loop warnings
  // that can fire when observing individually-animating elements.
  React.useEffect(() => {
    const sectionObserver = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          const reveals = entry.target.querySelectorAll(".reveal");
          if (entry.isIntersecting && entry.intersectionRatio >= 0.25) {
            reveals.forEach((el) => el.classList.add("in-view"));
          }
        });
      },
      { threshold: [0.25] }
    );

    document.querySelectorAll(".panel").forEach((el) => sectionObserver.observe(el));
    return () => sectionObserver.disconnect();
  }, []);

  // Scroll-driven parallax: per-panel, drives 3 layers (bg orbs, mid grid, fg content)
  // BG drifts fast in opposite direction (deep parallax), MID drifts moderate, FG drifts slow.
  // Perf: use offsetTop (no forced layout), cache vh, skip panels far off-screen,
  // memoize last values so unchanged frames don't trigger style invalidation.
  React.useEffect(() => {
    const scroller = rootRef.current;
    if (!scroller) return;
    const panels = Array.from(scroller.querySelectorAll(".panel"));
    let rafId = 0;
    let ticking = false;
    let vh = scroller.clientHeight;
    // Cache offsetTop per panel (only changes on resize)
    let offsets = panels.map((p) => p.offsetTop);
    // Memoize last quantized p per panel — skip writes when unchanged
    const lastP = new Array(panels.length).fill(NaN);

    const update = () => {
      ticking = false;
      const scrollTop = scroller.scrollTop;
      const vhHalf = vh / 2;
      for (let i = 0; i < panels.length; i++) {
        const panel = panels[i];
        const center = offsets[i] + vhHalf - scrollTop;
        const progress = (center - vhHalf) / vh;
        // Clamp
        let p = progress;
        if (p > 1.3) p = 1.3; else if (p < -1.3) p = -1.3;
        // Skip far-off panels — their fade goes to 0 anyway, and we only need to write once
        const farOff = Math.abs(p) >= 1.25;
        // Quantize to ~0.5px steps so identical-frame scrolls become no-ops
        const pq = Math.round(p * 200) / 200;
        if (pq === lastP[i]) continue;
        lastP[i] = pq;
        if (farOff) {
          // Park values at limit, fade fully out
          panel.style.setProperty("--pfg-opacity", "0");
          continue;
        }
        // Tuned magnitudes — slightly lighter than before to reduce paint area
        panel.style.setProperty("--pbg",  String(pq * 260));
        panel.style.setProperty("--pmid", String(pq * 130));
        panel.style.setProperty("--pfg",  String(pq * -70));
        const absP = pq < 0 ? -pq : pq;
        const fade = absP >= 1 ? 0 : 1 - absP * 0.85;
        panel.style.setProperty("--pfg-opacity", String(fade));
        panel.style.setProperty("--pbg-scale", String(1.2 + absP * 0.04));
      }
    };

    const onScroll = () => {
      if (!ticking) {
        rafId = requestAnimationFrame(update);
        ticking = true;
      }
    };
    const onResize = () => {
      vh = scroller.clientHeight;
      offsets = panels.map((p) => p.offsetTop);
      // Invalidate memoized values so all panels rewrite once
      for (let i = 0; i < lastP.length; i++) lastP[i] = NaN;
      update();
    };
    update();
    scroller.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onResize);
    return () => {
      scroller.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onResize);
      cancelAnimationFrame(rafId);
    };
  }, []);

  // Lock body scroll when overlay open
  React.useEffect(() => {
    if (activeFlow) {
      document.body.style.overflow = "hidden";
    } else {
      document.body.style.overflow = "";
    }
  }, [activeFlow]);

  // Section after closing overlay -> restore last position naturally (no action needed)
  const openFlow = (flow) => {
    setActiveFlow(flow);
  };
  const closeFlow = () => setActiveFlow(null);

  const onStart = () => scrollTo("pitch");
  const onBackToTop = () => scrollTo("hero");

  return (
    <React.Fragment>
      <div className="chrome-top">
        <div className="chrome-logo">
          <img src="assets/swapin-logo-white.svg" alt="Swapin" />
          <span style={{ opacity: 0.4, margin: "0 4px" }}>·</span>
          <span style={{ fontSize: 11, fontWeight: 500, color: "rgba(255,255,255,0.5)", textTransform: "uppercase", letterSpacing: "0.1em", whiteSpace: "nowrap" }}>Stablecoin payment API integration demo</span>
        </div>
        <div className="chrome-nav">
          {SECTIONS.map((s) => (
            <button
              key={s.id}
              className={activeSection === s.id ? "active" : ""}
              onClick={() => scrollTo(s.id)}
            >{s.label}</button>
          ))}
        </div>
      </div>

      <div className="scroll-progress">
        {SECTIONS.map((s, i) => (
          <div
            key={s.id}
            className={`dot ${activeSection === s.id ? "active" : ""}`}
            onClick={() => scrollTo(s.id)}
          >
            <span className="label">{`0${i + 1}`.slice(-2)} — {s.label}</span>
          </div>
        ))}
      </div>

      <div className="scroll-root" ref={rootRef}>
        <HeroSection onStart={onStart} />
        <PitchSection />
        <ArchitectureSection />
        <ChooserSection onOpen={openFlow} />
        <DepositFlowSection onOpen={openFlow} />
        <WithdrawalFlowSection onOpen={openFlow} />
        <CloseSection onBackToTop={onBackToTop} />
      </div>

      <MockupOverlay flow={activeFlow} onClose={closeFlow} onSwitchFlow={setActiveFlow} />
    </React.Fragment>
  );
};

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
