// primitives.jsx
// Scroll & motion primitives used across scenes.

const { useEffect, useRef, useState, useLayoutEffect, useMemo, useCallback } = React;

/* ----------------------------------------------------------------
   useScrollY — current vertical scroll
---------------------------------------------------------------- */
function useScrollY() {
  const [y, setY] = useState(typeof window === 'undefined' ? 0 : window.scrollY);
  useEffect(() => {
    let raf = null;
    const onScroll = () => {
      if (raf) return;
      raf = requestAnimationFrame(() => {
        setY(window.scrollY);
        raf = null;
      });
    };
    window.addEventListener('scroll', onScroll, { passive: true });
    return () => { window.removeEventListener('scroll', onScroll); if (raf) cancelAnimationFrame(raf); };
  }, []);
  return y;
}

/* ----------------------------------------------------------------
   useElementProgress — scroll progress of an element (0 → 1)
   - 0 when its top hits the bottom of the viewport
   - 1 when its bottom hits the top
   Use ref on a tall container.
---------------------------------------------------------------- */
function useElementProgress(ref, { start = 'top bottom', end = 'bottom top' } = {}) {
  const [p, setP] = useState(0);

  useEffect(() => {
    let raf = null;
    const calc = () => {
      raf = null;
      const el = ref.current;
      if (!el) return;
      const rect = el.getBoundingClientRect();
      const vh = window.innerHeight;
      // start: top of element at bottom of viewport
      const startY = rect.top + window.scrollY - vh;
      // end: bottom of element at top of viewport
      const endY = rect.bottom + window.scrollY;
      const range = endY - startY;
      const cur = window.scrollY - startY;
      const t = Math.max(0, Math.min(1, cur / range));
      setP(t);
    };
    const onScroll = () => { if (raf) return; raf = requestAnimationFrame(calc); };
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll);
    calc();
    return () => {
      window.removeEventListener('scroll', onScroll);
      window.removeEventListener('resize', onScroll);
      if (raf) cancelAnimationFrame(raf);
    };
  }, [ref]);

  return p;
}

/* ----------------------------------------------------------------
   useInView — IntersectionObserver wrapper
---------------------------------------------------------------- */
function useInView(threshold = 0.18, once = true) {
  const ref = useRef(null);
  const [inView, setInView] = useState(false);
  useEffect(() => {
    if (!ref.current) return;
    let fallbackTimer = null;
    let done = false;

    // Fallback: if IO hasn't fired within 700ms (offscreen iframes, screenshot
    // capture, throttled tabs, etc.), reveal anyway. The content must never
    // remain invisible.
    const reveal = () => {
      if (done) return;
      done = true;
      setInView(true);
      if (once) obs.disconnect();
    };

    const obs = new IntersectionObserver(([e]) => {
      if (e.isIntersecting) {
        reveal();
      } else if (!once) {
        setInView(false);
      }
    }, { threshold });
    obs.observe(ref.current);

    fallbackTimer = setTimeout(reveal, 700);

    return () => {
      obs.disconnect();
      if (fallbackTimer) clearTimeout(fallbackTimer);
    };
  }, [threshold, once]);
  return [ref, inView];
}

/* ----------------------------------------------------------------
   <Reveal> — fades + lifts in once visible
---------------------------------------------------------------- */
function Reveal({ children, delay = 0, y = 28, x = 0, scale = 1, duration = 1.1, threshold = 0.16, as = 'div', className, style }) {
  const [ref, inView] = useInView(threshold);
  const Tag = as;
  const s = {
    opacity: inView ? 1 : 0,
    transform: inView ? 'none' : `translate(${x}px, ${y}px) scale(${scale})`,
    transition: `opacity ${duration}s cubic-bezier(.16,1,.3,1) ${delay}s, transform ${duration}s cubic-bezier(.16,1,.3,1) ${delay}s`,
    willChange: 'opacity, transform',
    ...style,
  };
  return <Tag ref={ref} className={className} style={s}>{children}</Tag>;
}

/* ----------------------------------------------------------------
   <SplitReveal> — word-by-word fade up
---------------------------------------------------------------- */
function SplitReveal({ text, delay = 0, stagger = 0.06, baseDuration = 0.9, threshold = 0.2, className, style }) {
  const [ref, inView] = useInView(threshold);
  const words = String(text).split(/(\s+)/);
  return (
    <span ref={ref} className={className} style={{ display: 'inline-block', ...style }}>
      {words.map((w, i) => {
        if (/^\s+$/.test(w)) return <span key={i}>{w}</span>;
        return (
          <span key={i} style={{ display: 'inline-block', overflow: 'hidden', verticalAlign: 'top' }}>
            <span
              style={{
                display: 'inline-block',
                opacity: inView ? 1 : 0,
                transform: inView ? 'translateY(0)' : 'translateY(110%)',
                transition: `opacity ${baseDuration}s cubic-bezier(.16,1,.3,1) ${delay + i * stagger}s, transform ${baseDuration}s cubic-bezier(.16,1,.3,1) ${delay + i * stagger}s`,
              }}
            >
              {w}
            </span>
          </span>
        );
      })}
    </span>
  );
}

/* ----------------------------------------------------------------
   <Counter> — counts to target when in view
---------------------------------------------------------------- */
function Counter({ to, duration = 2200, prefix = '', suffix = '', format = null }) {
  const [ref, inView] = useInView(0.4);
  const [val, setVal] = useState(0);
  useEffect(() => {
    if (!inView) return;
    const start = performance.now();
    let raf;
    const tick = (t) => {
      const p = Math.min(1, (t - start) / duration);
      // ease-out cubic
      const e = 1 - Math.pow(1 - p, 3);
      setVal(Math.round(to * e));
      if (p < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => raf && cancelAnimationFrame(raf);
  }, [inView, to, duration]);

  const display = format ? format(val) : val.toLocaleString('en-IN');
  return <span ref={ref}>{prefix}{display}{suffix}</span>;
}

/* ----------------------------------------------------------------
   <Parallax> — light parallax based on element progress
---------------------------------------------------------------- */
function Parallax({ children, speed = 0.25, className, style }) {
  const ref = useRef(null);
  const p = useElementProgress(ref);
  // map 0..1 → -range..+range
  const range = 80 * speed * 3;
  const offset = (p - 0.5) * range;
  return (
    <div ref={ref} className={className} style={{ ...style, transform: `translate3d(0, ${offset}px, 0)`, willChange: 'transform' }}>
      {children}
    </div>
  );
}

/* ----------------------------------------------------------------
   <Magnetic> — subtle cursor magnet effect on buttons
---------------------------------------------------------------- */
function Magnetic({ children, strength = 0.22, className, style }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const handle = (e) => {
      const r = el.getBoundingClientRect();
      const x = e.clientX - r.left - r.width / 2;
      const y = e.clientY - r.top - r.height / 2;
      el.style.transform = `translate(${x * strength}px, ${y * strength}px)`;
    };
    const reset = () => { el.style.transform = ''; };
    el.addEventListener('mousemove', handle);
    el.addEventListener('mouseleave', reset);
    return () => {
      el.removeEventListener('mousemove', handle);
      el.removeEventListener('mouseleave', reset);
    };
  }, [strength]);
  return (
    <span ref={ref} className={className} style={{ display: 'inline-block', transition: 'transform .35s cubic-bezier(.16,1,.3,1)', ...style }}>
      {children}
    </span>
  );
}

/* ----------------------------------------------------------------
   <CursorDot> — site-wide gold cursor follower
---------------------------------------------------------------- */
function CursorDot({ enabled = true }) {
  const dotRef = useRef(null);
  const ringRef = useRef(null);

  // Detect touch / mobile — never render the cursor follower there
  const [isPointer, setIsPointer] = useState(() => {
    if (typeof window === 'undefined') return false;
    return window.matchMedia('(hover: hover) and (pointer: fine)').matches;
  });
  useEffect(() => {
    const mq = window.matchMedia('(hover: hover) and (pointer: fine)');
    const update = () => setIsPointer(mq.matches);
    mq.addEventListener('change', update);
    return () => mq.removeEventListener('change', update);
  }, []);

  useEffect(() => {
    if (!enabled || !isPointer) return;
    let mx = window.innerWidth/2, my = window.innerHeight/2;
    let rx = mx, ry = my;
    let raf;
    const onMove = (e) => { mx = e.clientX; my = e.clientY; };
    window.addEventListener('mousemove', onMove);
    const tick = () => {
      rx += (mx - rx) * 0.18;
      ry += (my - ry) * 0.18;
      if (dotRef.current) {
        dotRef.current.style.left = mx + 'px';
        dotRef.current.style.top = my + 'px';
      }
      if (ringRef.current) {
        ringRef.current.style.left = rx + 'px';
        ringRef.current.style.top = ry + 'px';
      }
      raf = requestAnimationFrame(tick);
    };
    tick();
    return () => { window.removeEventListener('mousemove', onMove); cancelAnimationFrame(raf); };
  }, [enabled, isPointer]);

  if (!enabled || !isPointer) return null;
  return (
    <>
      <div ref={dotRef} className="cursor-dot" />
      <div ref={ringRef} className="cursor-ring" />
    </>
  );
}

/* ----------------------------------------------------------------
   clamp / lerp / map helpers
---------------------------------------------------------------- */
const clamp = (v, a = 0, b = 1) => Math.max(a, Math.min(b, v));
const lerp = (a, b, t) => a + (b - a) * t;
const mapRange = (v, [a, b], [c, d]) => lerp(c, d, clamp((v - a) / (b - a)));

Object.assign(window, {
  useScrollY, useElementProgress, useInView,
  Reveal, SplitReveal, Counter, Parallax, Magnetic, CursorDot,
  clamp, lerp, mapRange,
});
