// Motion system — no-build equivalents of Framer Motion scroll/entrance behaviour.
// All effects respect prefers-reduced-motion.
const { useState, useRef, useCallback } = React;
// Use layout effects for mount/observer work: passive effects can be deferred
// indefinitely in throttled/offscreen iframes, leaving entrance states stuck.
const useEffect = React.useLayoutEffect;
const EASE = 'cubic-bezier(0.16,1,0.3,1)';

function usePrefersReducedMotion() {
  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 on = () => setReduced(mq.matches);
    mq.addEventListener ? mq.addEventListener('change', on) : mq.addListener(on);
    return () => {
      mq.removeEventListener ? mq.removeEventListener('change', on) : mq.removeListener(on);
    };
  }, []);
  return reduced;
}

// True when entrance animations must NOT gate visibility. Three signals:
//  1. prefers-reduced-motion, or the document is hidden at mount.
//  2. the document becomes hidden later (visibilitychange).
//  3. the animation clock is frozen while the wall clock advances — this happens in
//     throttled/offscreen rendering contexts where document.hidden never flips true.
//     Detecting it directly means primary content can never get stuck at opacity 0,
//     while leaving real-browser animations (incl. scroll reveals) fully intact.
function useStaticRender() {
  const [s, setS] = useState(() => {
    const reduced = typeof window !== 'undefined' && window.matchMedia &&
      window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    const hidden = typeof document !== 'undefined' && document.hidden;
    return !!(reduced || hidden);
  });
  useEffect(() => {
    if (s) return undefined;
    const onVis = () => { if (document.hidden) setS(true); };
    document.addEventListener('visibilitychange', onVis);

    // Frozen-engine probe: inspect a real entrance animation. In a throttled render
    // context the animation's currentTime stays pinned at 0 even though wall time and
    // the document timeline advance, which would leave content stuck at opacity 0.
    // A real browser will have advanced it, so animations (incl. scroll reveals) stay on.
    let timer = setTimeout(() => {
      const el = document.querySelector('.hero-anim, .reveal-anim');
      if (el && el.getAnimations) {
        const a = el.getAnimations()[0];
        if (a && a.playState !== 'finished' && (a.currentTime == null || a.currentTime < 30)) {
          setS(true);
        }
      }
    }, 450);
    return () => {
      document.removeEventListener('visibilitychange', onVis);
      if (timer) clearTimeout(timer);
    };
  }, [s]);
  return s;
}

// ---- Shared scroll registry for parallax blobs (single rAF loop) ----
const _blobs = new Set();
let _ticking = false;
function _onScroll() {
  if (_ticking) return;
  _ticking = true;
  requestAnimationFrame(() => {
    const vh = window.innerHeight || 1;
    _blobs.forEach((b) => {
      const el = b.el;
      if (!el) return;
      const r = el.getBoundingClientRect();
      const center = r.top + r.height / 2;
      const t = Math.max(-1.2, Math.min(1.2, (center - vh / 2) / vh));
      const ty = -t * b.speed;
      const rot = t * b.rotate;
      el.style.transform = `translate3d(0, ${ty.toFixed(2)}px, 0) rotate(${rot.toFixed(2)}deg)`;
    });
    _ticking = false;
  });
}
if (typeof window !== 'undefined') {
  window.addEventListener('scroll', _onScroll, { passive: true });
  window.addEventListener('resize', _onScroll, { passive: true });
}

// Soft organic blob. Drifts/rotates on scroll unless reduced motion.
function Blob({ color, size = 360, speed = 32, rotate = 6, opacity = 0.5, blur = 8, className = '', style = {} }) {
  const ref = useRef(null);
  const reduced = usePrefersReducedMotion();
  useEffect(() => {
    if (reduced) return;
    const entry = { el: ref.current, speed, rotate };
    _blobs.add(entry);
    _onScroll();
    return () => _blobs.delete(entry);
  }, [reduced, speed, rotate]);
  return (
    <div
      ref={ref}
      aria-hidden="true"
      className={'pointer-events-none absolute ' + className}
      style={{
        width: size,
        height: size,
        opacity,
        background: color,
        filter: `blur(${blur}px)`,
        borderRadius: '42% 58% 63% 37% / 41% 44% 56% 59%',
        willChange: reduced ? 'auto' : 'transform',
        ...style,
      }}
    />
  );
}

// Reveal on scroll: fade + rise via CSS keyframe (robust against stuck transitions).
function Reveal({ children, delay = 0, once = true, as = 'div', className = '', style = {} }) {
  const Tag = as;
  const ref = useRef(null);
  const [seen, setSeen] = useState(false);
  const staticRender = useStaticRender();
  useEffect(() => {
    if (staticRender) { setSeen(true); return; }
    const el = ref.current;
    if (!el || !('IntersectionObserver' in window)) { setSeen(true); return; }
    const io = new IntersectionObserver(
      (entries) => {
        entries.forEach((e) => {
          if (e.isIntersecting) {
            setSeen(true);
            if (once) io.unobserve(e.target);
          }
        });
      },
      { threshold: 0.12, rootMargin: '0px 0px -8% 0px' }
    );
    io.observe(el);
    return () => io.disconnect();
  }, [staticRender, once]);
  const anim = staticRender
    ? {}
    : seen
      ? { animation: `riseIn 560ms ${EASE} ${delay}ms both` }
      : { opacity: 0 };
  return (
    <Tag ref={ref} className={'reveal-anim ' + className} style={{ ...style, ...anim }}>
      {children}
    </Tag>
  );
}

// Faint film grain overlay.
const _grainSvg =
  "<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%' height='100%' filter='url(#n)' opacity='0.6'/></svg>";
function Grain({ opacity = 0.05, className = '' }) {
  return (
    <div
      aria-hidden="true"
      className={'pointer-events-none absolute inset-0 ' + className}
      style={{
        opacity,
        mixBlendMode: 'multiply',
        backgroundImage: `url("data:image/svg+xml;utf8,${encodeURIComponent(_grainSvg)}")`,
        backgroundSize: '140px 140px',
      }}
    />
  );
}

// Organic curved divider between sections. `top` = colour above, `bottom` = fill of next section.
function WaveDivider({ top, bottom, height = 'clamp(44px,6vw,84px)', variant = 0 }) {
  const paths = [
    'M0,46 C260,92 520,2 760,28 C980,52 1230,86 1440,42 L1440,90 L0,90 Z',
    'M0,40 C200,8 460,84 720,52 C980,20 1240,72 1440,40 L1440,90 L0,90 Z',
  ];
  return (
    <div aria-hidden="true" style={{ background: top, lineHeight: 0 }}>
      <svg viewBox="0 0 1440 90" preserveAspectRatio="none" style={{ display: 'block', width: '100%', height }}>
        <path d={paths[variant % paths.length]} fill={bottom} />
      </svg>
    </div>
  );
}

// Magnetic hover (max few px toward cursor). Disabled on reduced motion / touch.
function useMagnetic(strength = 3) {
  const ref = useRef(null);
  const reduced = usePrefersReducedMotion();
  const onMove = useCallback((e) => {
    if (reduced) return;
    const el = ref.current;
    if (!el) return;
    const r = el.getBoundingClientRect();
    const mx = e.clientX - (r.left + r.width / 2);
    const my = e.clientY - (r.top + r.height / 2);
    const dx = Math.max(-strength, Math.min(strength, mx * 0.25));
    const dy = Math.max(-strength, Math.min(strength, my * 0.25));
    el.style.transform = `translate(${dx}px, ${dy}px)`;
  }, [reduced, strength]);
  const onLeave = useCallback(() => {
    const el = ref.current;
    if (el) el.style.transform = 'translate(0,0)';
  }, []);
  return { ref, onMouseMove: onMove, onMouseLeave: onLeave };
}

Object.assign(window, { usePrefersReducedMotion, useStaticRender, Blob, Reveal, Grain, WaveDivider, useMagnetic, EASE });
