// Motion primitives for the anTee-Up landing page.
// Premium easing, GPU-friendly transforms, reduced-motion aware.
//
// Exports (via window):
//   useReveal(opts)        — IntersectionObserver-driven enter
//   Reveal                 — wrapper that fades+slides children
//   useScroll()            — current scroll Y (rAF-throttled)
//   useParallax(speed)     — translateY based on scroll, element-relative
//   useMousePosition()     — { x, y } in viewport (rAF-throttled)
//   useTilt(opts)          — 3D tilt onto a ref based on cursor (in-element)
//   MagneticButton         — magnetic hover (children translate toward cursor)
//   useCounter(end, dur)   — ramps 0 → end over `dur` ms when visible
//   useReducedMotion()     — boolean for prefers-reduced-motion
//   Stagger                — wraps children with sequential reveal delays
//
// All hooks no-op (return identity values / 0 transform) when reduced-motion
// is requested.

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

const EASE_OUT_EXPO  = 'cubic-bezier(0.16, 1, 0.3, 1)';
const EASE_OUT_QUART = 'cubic-bezier(0.25, 1, 0.5, 1)';
const EASE_IN_OUT    = 'cubic-bezier(0.65, 0, 0.35, 1)';

// ─── prefers-reduced-motion ───────────────────────────────────────
function useReducedMotion() {
  const [reduced, setReduced] = useState(() =>
    typeof window !== 'undefined' && window.matchMedia &&
    window.matchMedia('(prefers-reduced-motion: reduce)').matches);
  useEffect(() => {
    if (!window.matchMedia) return;
    const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
    const h = e => setReduced(e.matches);
    mq.addEventListener?.('change', h);
    return () => mq.removeEventListener?.('change', h);
  }, []);
  return reduced;
}

// ─── useReveal ────────────────────────────────────────────────────
// Returns [ref, visible]. visible becomes true the first time the element
// crosses the threshold; never flips back to false (entries don't re-trigger).
function useReveal({ threshold = 0.15, rootMargin = '0px 0px -10% 0px', once = true } = {}) {
  const ref = useRef(null);
  const [visible, setVisible] = useState(false);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    let cancelled = false;
    const tryReveal = () => {
      if (cancelled) return false;
      const r = el.getBoundingClientRect();
      const vh = window.innerHeight || document.documentElement.clientHeight;
      const vw = window.innerWidth || document.documentElement.clientWidth;
      // Anywhere in or near viewport → reveal now.
      if (r.top < vh * 1.2 && r.bottom > -vh * 0.2 && r.left < vw + 200 && r.right > -200) {
        setVisible(true);
        return true;
      }
      return false;
    };
    // Pass 1: synchronously after layout.
    if (tryReveal()) return;
    // Pass 2: next frame, after any subsequent layout settles.
    const raf = requestAnimationFrame(tryReveal);
    // IntersectionObserver for elements below the initial viewport.
    let io;
    if (typeof IntersectionObserver !== 'undefined') {
      io = new IntersectionObserver(entries => {
        entries.forEach(e => {
          if (e.isIntersecting) {
            setVisible(true);
            if (once && io) io.unobserve(e.target);
          } else if (!once) {
            setVisible(false);
          }
        });
      }, { threshold, rootMargin });
      io.observe(el);
    }
    // Pass 3: belt-and-suspenders — if nothing has flipped us visible after
    // 1.4s, force it. Covers iframe contexts where IO never fires.
    const fallback = setTimeout(() => {
      if (!cancelled) setVisible(true);
    }, 1400);
    return () => {
      cancelled = true;
      cancelAnimationFrame(raf);
      clearTimeout(fallback);
      if (io) io.disconnect();
    };
  }, [threshold, rootMargin, once]);
  return [ref, visible];
}

// ─── <Reveal> component ───────────────────────────────────────────
// Uses a STYLESHEET-driven transition keyed off `data-visible`, not inline
// style. This survives React re-renders that would otherwise restart an
// inline-style-based transition (the parent's scroll/mouse state changes
// trigger child re-renders constantly).
function Reveal({ children, delay = 0, dy = 24, dx = 0, duration = 700, threshold = 0.15, as: As = 'div', style, className = '', ...rest }) {
  const reduced = useReducedMotion();
  const [ref, visible] = useReveal({ threshold });
  const show = visible || reduced;
  // Pass dy/dx/delay/duration as CSS variables — actual transition lives in
  // the stylesheet (.antee-reveal). React may still touch these on re-render
  // but they don't affect the transitioning properties (opacity/transform).
  const reveal = useMemo(() => ({
    '--rv-dy':    `${dy}px`,
    '--rv-dx':    `${dx}px`,
    '--rv-delay': `${delay}ms`,
    '--rv-dur':   `${duration}ms`,
  }), [dy, dx, delay, duration]);
  const merged = useMemo(() => ({ ...reveal, ...style }), [reveal, style]);
  return (
    <As ref={ref}
      className={`antee-reveal ${className}`.trim()}
      data-visible={show ? 'true' : 'false'}
      style={merged}
      {...rest}>{children}</As>
  );
}

// ─── <Stagger> ────────────────────────────────────────────────────
// Wraps an array of children, giving each a sequential reveal delay.
function Stagger({ children, baseDelay = 0, step = 70, dy = 24, ...rest }) {
  return Children.map(children, (child, i) =>
    child == null ? null
      : <Reveal delay={baseDelay + i * step} dy={dy} {...rest}>{child}</Reveal>);
}

// ─── useScroll ────────────────────────────────────────────────────
// rAF-throttled scrollY. Cheap if you have one consumer; multiple consumers
// share the same listener via this module.
let _scrollListeners = new Set();
let _scrollRaf = null;
let _scrollY = (typeof window !== 'undefined' ? window.scrollY : 0);
function _scrollTick() {
  _scrollY = window.scrollY;
  _scrollListeners.forEach(fn => fn(_scrollY));
  _scrollRaf = null;
}
function _scrollHandler() {
  if (_scrollRaf == null) _scrollRaf = requestAnimationFrame(_scrollTick);
}
if (typeof window !== 'undefined') {
  window.addEventListener('scroll', _scrollHandler, { passive: true });
}
function useScroll() {
  const [y, setY] = useState(_scrollY);
  useEffect(() => {
    _scrollListeners.add(setY);
    return () => _scrollListeners.delete(setY);
  }, []);
  return y;
}

// ─── useParallax ──────────────────────────────────────────────────
// Returns a transform string. Uses element top relative to viewport.
function useParallax(speed = 0.2) {
  const reduced = useReducedMotion();
  const ref = useRef(null);
  const [offset, setOffset] = useState(0);
  useEffect(() => {
    if (reduced) return;
    const el = ref.current;
    if (!el) return;
    let raf = null;
    const update = () => {
      const rect = el.getBoundingClientRect();
      const center = rect.top + rect.height / 2;
      const viewportCenter = window.innerHeight / 2;
      const delta = (center - viewportCenter);
      setOffset(-delta * speed);
      raf = null;
    };
    const onScroll = () => { if (raf == null) raf = requestAnimationFrame(update); };
    update();
    _scrollListeners.add(onScroll);
    return () => _scrollListeners.delete(onScroll);
  }, [speed, reduced]);
  return [ref, offset];
}

// ─── useMousePosition ─────────────────────────────────────────────
let _mouse = { x: -9999, y: -9999 };
let _mouseListeners = new Set();
let _mouseRaf = null;
function _mouseTick() {
  _mouseListeners.forEach(fn => fn(_mouse));
  _mouseRaf = null;
}
if (typeof window !== 'undefined') {
  window.addEventListener('pointermove', (e) => {
    _mouse = { x: e.clientX, y: e.clientY };
    if (_mouseRaf == null) _mouseRaf = requestAnimationFrame(_mouseTick);
  }, { passive: true });
}
function useMousePosition() {
  const [p, setP] = useState(_mouse);
  useEffect(() => {
    _mouseListeners.add(setP);
    return () => _mouseListeners.delete(setP);
  }, []);
  return p;
}

// ─── useTilt ──────────────────────────────────────────────────────
// 3D tilt within element. Returns ref + transform string.
function useTilt({ max = 6, perspective = 1000, lerp = 0.12 } = {}) {
  const reduced = useReducedMotion();
  const ref = useRef(null);
  const [transform, setTransform] = useState(`perspective(${perspective}px) rotateX(0deg) rotateY(0deg)`);
  useEffect(() => {
    if (reduced) return;
    const el = ref.current;
    if (!el) return;
    let rx = 0, ry = 0, tx = 0, ty = 0, raf = null;
    const onMove = (e) => {
      const rect = el.getBoundingClientRect();
      const cx = rect.left + rect.width / 2;
      const cy = rect.top + rect.height / 2;
      tx = ((e.clientX - cx) / (rect.width / 2)) * max;
      ty = -((e.clientY - cy) / (rect.height / 2)) * max;
      if (raf == null) raf = requestAnimationFrame(loop);
    };
    const onLeave = () => { tx = 0; ty = 0; if (raf == null) raf = requestAnimationFrame(loop); };
    const loop = () => {
      rx = rx + (ty - rx) * lerp;
      ry = ry + (tx - ry) * lerp;
      setTransform(`perspective(${perspective}px) rotateX(${rx.toFixed(2)}deg) rotateY(${ry.toFixed(2)}deg)`);
      if (Math.abs(rx - ty) > 0.05 || Math.abs(ry - tx) > 0.05) {
        raf = requestAnimationFrame(loop);
      } else {
        raf = null;
      }
    };
    el.addEventListener('pointermove', onMove);
    el.addEventListener('pointerleave', onLeave);
    return () => {
      el.removeEventListener('pointermove', onMove);
      el.removeEventListener('pointerleave', onLeave);
      if (raf) cancelAnimationFrame(raf);
    };
  }, [max, perspective, lerp, reduced]);
  return [ref, transform];
}

// ─── MagneticButton ───────────────────────────────────────────────
function MagneticButton({ children, strength = 0.3, as: As = 'a', style, ...rest }) {
  const reduced = useReducedMotion();
  const ref = useRef(null);
  useEffect(() => {
    if (reduced) return;
    const el = ref.current;
    if (!el) return;
    let raf = null;
    const onMove = (e) => {
      const rect = el.getBoundingClientRect();
      const cx = rect.left + rect.width / 2;
      const cy = rect.top + rect.height / 2;
      const dx = (e.clientX - cx) * strength;
      const dy = (e.clientY - cy) * strength;
      if (raf) cancelAnimationFrame(raf);
      raf = requestAnimationFrame(() => {
        el.style.transform = `translate3d(${dx}px, ${dy}px, 0)`;
      });
    };
    const onLeave = () => {
      el.style.transform = `translate3d(0,0,0)`;
    };
    el.addEventListener('pointermove', onMove);
    el.addEventListener('pointerleave', onLeave);
    return () => {
      el.removeEventListener('pointermove', onMove);
      el.removeEventListener('pointerleave', onLeave);
    };
  }, [strength, reduced]);
  return (
    <As ref={ref} style={{
      transition: `transform 400ms ${EASE_OUT_EXPO}`,
      willChange: 'transform',
      display: 'inline-flex',
      ...style,
    }} {...rest}>{children}</As>
  );
}

// ─── useCounter ───────────────────────────────────────────────────
// Counts from 0 → end over `duration` ms once visible.
function useCounter(end, { duration = 1400, decimals = 0 } = {}) {
  const reduced = useReducedMotion();
  const [ref, visible] = useReveal({ threshold: 0.3 });
  const [value, setValue] = useState(reduced ? end : 0);
  useEffect(() => {
    if (!visible || reduced) return;
    const start = performance.now();
    let raf;
    const tick = (now) => {
      const t = Math.min((now - start) / duration, 1);
      const eased = 1 - Math.pow(1 - t, 3); // ease-out-cubic
      const v = end * eased;
      setValue(decimals === 0 ? Math.round(v) : Number(v.toFixed(decimals)));
      if (t < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [visible, end, duration, decimals, reduced]);
  return [ref, value];
}

// ─── Floating idle ────────────────────────────────────────────────
// Tiny vertical breathing motion (CSS animation, GPU layer).
function injectIdleKeyframes() {
  if (document.getElementById('antee-motion-keyframes')) return;
  const style = document.createElement('style');
  style.id = 'antee-motion-keyframes';
  style.textContent = `
    .antee-reveal {
      opacity: 0;
      transform: translate3d(var(--rv-dx, 0px), var(--rv-dy, 24px), 0);
      transition:
        opacity   var(--rv-dur, 700ms) cubic-bezier(0.16, 1, 0.3, 1) var(--rv-delay, 0ms),
        transform var(--rv-dur, 700ms) cubic-bezier(0.16, 1, 0.3, 1) var(--rv-delay, 0ms);
      will-change: opacity, transform;
    }
    .antee-reveal[data-visible="true"] {
      opacity: 1;
      transform: translate3d(0, 0, 0);
    }
    @keyframes anteeFloat {
      0%, 100% { transform: translate3d(0,0,0); }
      50%      { transform: translate3d(0,-6px,0); }
    }
    @keyframes anteeFloatGentle {
      0%, 100% { transform: translate3d(0,0,0); }
      50%      { transform: translate3d(0,-3px,0); }
    }
    @keyframes anteeShimmer {
      0%   { background-position: -200% 0; }
      100% { background-position: 200% 0; }
    }
    @keyframes anteePulse {
      0%, 100% { opacity: 1; }
      50%      { opacity: 0.55; }
    }
    @keyframes anteeBlink {
      0%, 100% { opacity: 1; }
      50%      { opacity: 0.3; }
    }
    @keyframes anteeDrawLine {
      0%   { transform: scaleX(0); }
      100% { transform: scaleX(1); }
    }
    @keyframes anteeRingPulse {
      0%   { box-shadow: 0 0 0 0 rgba(201,168,76,0.4); }
      70%  { box-shadow: 0 0 0 14px rgba(201,168,76,0); }
      100% { box-shadow: 0 0 0 0 rgba(201,168,76,0); }
    }
    .antee-grain-shimmer {
      background-image: linear-gradient(90deg,
        transparent 0%,
        rgba(226,196,122,0.5) 50%,
        transparent 100%);
      background-size: 200% 100%;
      -webkit-background-clip: text;
      background-clip: text;
      -webkit-text-fill-color: transparent;
      animation: anteeShimmer 6s linear infinite;
    }
    .antee-respect-motion *, .antee-respect-motion *::before, .antee-respect-motion *::after {
      animation-duration: 0.001s !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.001s !important;
    }
  `;
  document.head.appendChild(style);
}
if (typeof document !== 'undefined') injectIdleKeyframes();

// ─── ScrollProgress (thin gold bar across the top) ────────────────
function ScrollProgress({ color = '#C9A84C', height = 2 }) {
  const [pct, setPct] = useState(0);
  useEffect(() => {
    const update = () => {
      const max = document.documentElement.scrollHeight - window.innerHeight;
      const y = window.scrollY;
      setPct(max > 0 ? Math.min(y / max, 1) : 0);
    };
    update();
    _scrollListeners.add(update);
    return () => _scrollListeners.delete(update);
  }, []);
  return (
    <div style={{
      position: 'fixed', top: 0, left: 0, right: 0, height,
      zIndex: 200, pointerEvents: 'none',
      background: 'rgba(255,255,255,0.04)',
    }}>
      <div style={{
        height: '100%', width: '100%',
        background: color, transformOrigin: '0 50%',
        transform: `scaleX(${pct})`,
        boxShadow: `0 0 12px ${color}`,
      }}/>
    </div>
  );
}

Object.assign(window, {
  Reveal, Stagger, useReveal, useScroll, useParallax,
  useMousePosition, useTilt, MagneticButton, useCounter,
  useReducedMotion, ScrollProgress,
  EASE_OUT_EXPO, EASE_OUT_QUART, EASE_IN_OUT,
});
