// ==== Util ============================================================
// Shared hooks + helpers across modules.

const { useState, useEffect, useRef, useMemo, useCallback, useLayoutEffect } = React;
// Each Babel <script> gets its own scope. Expose hooks on window so siblings see them.
window.useState = useState;
window.useEffect = useEffect;
window.useRef = useRef;
window.useMemo = useMemo;
window.useCallback = useCallback;
window.useLayoutEffect = useLayoutEffect;

// Mulberry-style deterministic RNG
function mulberry32(seed) {
  let t = (seed * 1e9) >>> 0;
  return function () {
    t = (t + 0x6D2B79F5) >>> 0;
    let r = Math.imul(t ^ (t >>> 15), 1 | t);
    r = (r + Math.imul(r ^ (r >>> 7), 61 | r)) ^ r;
    return ((r ^ (r >>> 14)) >>> 0) / 4294967296;
  };
}

// IntersectionObserver "in-view" hook
function useInView(opts = { threshold: 0.2 }) {
  const ref = useRef(null);
  const [seen, setSeen] = useState(false);
  useEffect(() => {
    if (!ref.current) return;
    const io = new IntersectionObserver(([e]) => {
      if (e.isIntersecting) { setSeen(true); io.disconnect(); }
    }, opts);
    io.observe(ref.current);
    return () => io.disconnect();
  }, []);
  return [ref, seen];
}

// Active section observer — drives rail + dock highlight
function useActiveSection(ids) {
  const [active, setActive] = useState(ids[0]);
  useEffect(() => {
    const observers = [];
    const ratios = new Map(ids.map(id => [id, 0]));
    ids.forEach(id => {
      const el = document.getElementById(id);
      if (!el) return;
      const io = new IntersectionObserver(([e]) => {
        ratios.set(id, e.intersectionRatio);
        let best = ids[0], bestR = -1;
        for (const [k, v] of ratios.entries()) {
          if (v > bestR) { bestR = v; best = k; }
        }
        if (bestR > 0) setActive(best);
      }, { threshold: [0, 0.15, 0.4, 0.7] });
      io.observe(el);
      observers.push(io);
    });
    return () => observers.forEach(o => o.disconnect());
  }, [ids.join(",")]);
  return active;
}

// Typewriter
function useTypewriter(text, opts = {}) {
  const { speed = 26, start = true, delay = 0 } = opts;
  const [out, setOut] = useState("");
  useEffect(() => {
    if (!start) return;
    let i = 0;
    let t;
    const begin = setTimeout(function step() {
      if (i <= text.length) {
        setOut(text.slice(0, i));
        i++;
        t = setTimeout(step, speed);
      }
    }, delay);
    return () => { clearTimeout(begin); clearTimeout(t); };
  }, [text, start, speed, delay]);
  return out;
}

// Sticky scroll progress (0..1) for a referenced element while it's in viewport
function useScrollProgress(ref, opts = {}) {
  const [p, setP] = useState(0);
  useEffect(() => {
    function onScroll() {
      if (!ref.current) return;
      const r = ref.current.getBoundingClientRect();
      const vh = window.innerHeight;
      const total = r.height + vh;
      const passed = vh - r.top;
      const prog = Math.max(0, Math.min(1, passed / total));
      setP(prog);
    }
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    return () => { window.removeEventListener("scroll", onScroll); window.removeEventListener("resize", onScroll); };
  }, []);
  return p;
}

// Throttled mouse position (-1..1, -1..1)
function useMouse() {
  const ref = useRef({ x: 0, y: 0 });
  useEffect(() => {
    function on(e) {
      ref.current = {
        x: (e.clientX / window.innerWidth) * 2 - 1,
        y: (e.clientY / window.innerHeight) * 2 - 1,
      };
    }
    window.addEventListener("pointermove", on);
    return () => window.removeEventListener("pointermove", on);
  }, []);
  return ref;
}

// Smooth scroll to id
function scrollToId(id) {
  const el = document.getElementById(id);
  if (!el) return;
  const y = el.getBoundingClientRect().top + window.scrollY - 20;
  window.scrollTo({ top: y, behavior: "smooth" });
}

// Format helpers
const pad2 = (n) => String(n).padStart(2, "0");
function clockNow() {
  const d = new Date();
  return `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`;
}

// Reduced-motion preference
function prefersReducedMotion() {
  return window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
}

// Mobile detection (graceful fallback for heavy 3D scenes)
function isHandheld() {
  return window.matchMedia && window.matchMedia("(max-width: 880px)").matches;
}

Object.assign(window, {
  mulberry32, useInView, useActiveSection, useTypewriter, useScrollProgress, useMouse,
  scrollToId, pad2, clockNow, prefersReducedMotion, isHandheld
});
