// APEX FX — animation primitives
const { useEffect, useRef, useState, useCallback } = React;

/* ---------- CUSTOM CURSOR ---------- */
function CustomCursor({ accent }) {
  const dotRef = useRef(null);
  const ringRef = useRef(null);
  const labelRef = useRef(null);
  const stateRef = useRef({
    x: 0, y: 0,
    rx: 0, ry: 0,
    tx: 0, ty: 0,
    label: '',
    mode: 'default',
  });

  useEffect(() => {
    if (window.matchMedia('(pointer: coarse)').matches) return;
    document.documentElement.style.cursor = 'none';

    const onMove = (e) => {
      stateRef.current.x = e.clientX;
      stateRef.current.y = e.clientY;
      stateRef.current.tx = e.clientX;
      stateRef.current.ty = e.clientY;

      const target = e.target;
      let mode = 'default';
      let label = '';
      if (target.closest('button, a')) {
        mode = 'hover';
        const cur = target.closest('[data-cursor]');
        if (cur) label = cur.dataset.cursor;
      }
      if (target.closest('.pcard')) {
        mode = 'drag';
        label = 'VIEW';
      }
      if (target.closest('.rail')) {
        mode = 'drag';
        label = '◀ DRAG ▶';
      }
      stateRef.current.mode = mode;
      stateRef.current.label = label;
    };

    const tick = () => {
      const s = stateRef.current;
      s.rx += (s.tx - s.rx) * 0.18;
      s.ry += (s.ty - s.ry) * 0.18;
      if (dotRef.current) {
        dotRef.current.style.transform = `translate(${s.x - 3}px, ${s.y - 3}px)`;
      }
      if (ringRef.current) {
        const scale = s.mode === 'hover' ? 1.6 : s.mode === 'drag' ? 2.6 : 1;
        ringRef.current.style.transform = `translate(${s.rx - 16}px, ${s.ry - 16}px) scale(${scale})`;
        ringRef.current.style.background = s.mode === 'drag' ? accent : 'transparent';
        ringRef.current.style.mixBlendMode = s.mode === 'drag' ? 'difference' : 'normal';
      }
      if (labelRef.current) {
        labelRef.current.style.transform = `translate(${s.rx + 24}px, ${s.ry + 18}px)`;
        labelRef.current.textContent = s.label;
        labelRef.current.style.opacity = s.label ? 1 : 0;
      }
      raf = requestAnimationFrame(tick);
    };
    let raf = requestAnimationFrame(tick);
    window.addEventListener('mousemove', onMove);
    return () => {
      window.removeEventListener('mousemove', onMove);
      cancelAnimationFrame(raf);
      document.documentElement.style.cursor = '';
    };
  }, [accent]);

  return (
    <>
      <div ref={ringRef} style={{
        position:'fixed', top:0, left:0, width:32, height:32,
        border:`1px solid ${accent}`, borderRadius:'50%',
        pointerEvents:'none', zIndex:9999,
        transition:'transform 0.18s cubic-bezier(0.2,0.8,0.2,1), background 0.2s',
      }} />
      <div ref={dotRef} style={{
        position:'fixed', top:0, left:0, width:6, height:6,
        background:accent, borderRadius:'50%',
        pointerEvents:'none', zIndex:10000,
      }} />
      <div ref={labelRef} className="mono" style={{
        position:'fixed', top:0, left:0,
        fontSize:9, letterSpacing:'0.2em', color:accent,
        pointerEvents:'none', zIndex:9999, opacity:0,
        transition:'opacity 0.2s',
        textTransform:'uppercase',
      }} />
    </>
  );
}

/* ---------- TEXT SCRAMBLE ---------- */
const SCRAMBLE_CHARS = '▓▒░█◆◇◈■□▲▼◀▶/\\<>{}[]+*=~';
function useScramble(target, opts = {}) {
  const [out, setOut] = useState(target);
  const ref = useRef({ raf: null });
  const start = useCallback(() => {
    cancelAnimationFrame(ref.current.raf);
    const dur = opts.duration || 900;
    const t0 = performance.now();
    const tick = (t) => {
      const p = Math.min(1, (t - t0) / dur);
      const reveal = Math.floor(p * target.length);
      let s = '';
      for (let i = 0; i < target.length; i++) {
        if (i < reveal) s += target[i];
        else if (target[i] === ' ' || target[i] === '\n') s += target[i];
        else s += SCRAMBLE_CHARS[Math.floor(Math.random() * SCRAMBLE_CHARS.length)];
      }
      setOut(s);
      if (p < 1) ref.current.raf = requestAnimationFrame(tick);
      else setOut(target);
    };
    ref.current.raf = requestAnimationFrame(tick);
  }, [target, opts.duration]);
  useEffect(() => () => cancelAnimationFrame(ref.current.raf), []);
  return [out, start];
}

function ScrambleText({ children, className, style, trigger = 'mount' }) {
  const target = String(children);
  const [text, scramble] = useScramble(target, { duration: 800 });
  const ref = useRef(null);
  useEffect(() => {
    if (trigger === 'mount') scramble();
  }, []);
  useEffect(() => {
    if (trigger !== 'hover' || !ref.current) return;
    const el = ref.current;
    const onEnter = () => scramble();
    el.addEventListener('mouseenter', onEnter);
    return () => el.removeEventListener('mouseenter', onEnter);
  }, [trigger, scramble]);
  return <span ref={ref} className={className} style={style}>{text}</span>;
}

/* ---------- COUNTER ---------- */
function Counter({ to, from = 0, prefix = '', suffix = '', duration = 1400, format }) {
  const [val, setVal] = useState(from);
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    let started = false;
    const io = new IntersectionObserver((entries) => {
      entries.forEach(e => {
        if (e.isIntersecting && !started) {
          started = true;
          const t0 = performance.now();
          const tick = (t) => {
            const p = Math.min(1, (t - t0) / duration);
            const eased = 1 - Math.pow(1 - p, 3);
            setVal(from + (to - from) * eased);
            if (p < 1) requestAnimationFrame(tick);
          };
          requestAnimationFrame(tick);
        }
      });
    }, { threshold: 0.4 });
    io.observe(el);
    return () => io.disconnect();
  }, [to, from, duration]);
  const display = format ? format(val) : Math.round(val).toString();
  return <span ref={ref}>{prefix}{display}{suffix}</span>;
}

/* ---------- REVEAL ON SCROLL ---------- */
function Reveal({ children, delay = 0, y = 24, as: Tag = 'div', style, className }) {
  const ref = useRef(null);
  const [show, setShow] = useState(false);
  useEffect(() => {
    if (!ref.current) return;
    const io = new IntersectionObserver((entries) => {
      entries.forEach(e => { if (e.isIntersecting) setShow(true); });
    }, { threshold: 0.15 });
    io.observe(ref.current);
    return () => io.disconnect();
  }, []);
  return (
    <Tag ref={ref} className={className} style={{
      ...style,
      opacity: show ? 1 : 0,
      transform: show ? 'translateY(0)' : `translateY(${y}px)`,
      transition: `opacity 0.7s cubic-bezier(0.2,0.8,0.2,1) ${delay}ms, transform 0.7s cubic-bezier(0.2,0.8,0.2,1) ${delay}ms`,
    }}>{children}</Tag>
  );
}

/* word-by-word reveal */
function RevealWords({ text, delay = 0, stagger = 60, className, style }) {
  const ref = useRef(null);
  const [show, setShow] = useState(false);
  useEffect(() => {
    if (!ref.current) return;
    const io = new IntersectionObserver((entries) => {
      entries.forEach(e => { if (e.isIntersecting) setShow(true); });
    }, { threshold: 0.2 });
    io.observe(ref.current);
    return () => io.disconnect();
  }, []);
  const words = String(text).split(' ');
  return (
    <span ref={ref} className={className} style={{...style, display:'inline-block'}}>
      {words.map((w, i) => (
        <span key={i} style={{display:'inline-block', overflow:'hidden', verticalAlign:'top'}}>
          <span style={{
            display:'inline-block',
            transform: show ? 'translateY(0)' : 'translateY(110%)',
            opacity: show ? 1 : 0,
            transition: `transform 0.7s cubic-bezier(0.2,0.85,0.2,1) ${delay + i*stagger}ms, opacity 0.4s ease ${delay + i*stagger}ms`,
          }}>{w}{i < words.length - 1 ? ' ' : ''}</span>
        </span>
      ))}
    </span>
  );
}

/* ---------- MAGNETIC HOVER ---------- */
function useMagnet(strength = 0.35) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const onMove = (e) => {
      const r = el.getBoundingClientRect();
      const cx = r.left + r.width/2;
      const cy = r.top + r.height/2;
      const dx = (e.clientX - cx) * strength;
      const dy = (e.clientY - cy) * strength;
      el.style.transform = `translate(${dx}px, ${dy}px)`;
    };
    const onLeave = () => { el.style.transform = ''; };
    el.addEventListener('mousemove', onMove);
    el.addEventListener('mouseleave', onLeave);
    return () => {
      el.removeEventListener('mousemove', onMove);
      el.removeEventListener('mouseleave', onLeave);
    };
  }, [strength]);
  return ref;
}

function Magnetic({ children, strength = 0.35, style, className, ...rest }) {
  const ref = useMagnet(strength);
  return (
    <div ref={ref} className={className} style={{...style, transition:'transform 0.5s cubic-bezier(0.2,0.8,0.2,1)', display:'inline-block'}} {...rest}>
      {children}
    </div>
  );
}

/* ---------- MOUSE PARALLAX ---------- */
function useMouseParallax(intensity = 12) {
  const [pos, setPos] = useState({ x: 0, y: 0 });
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const onMove = (e) => {
      const r = el.getBoundingClientRect();
      const cx = r.left + r.width/2;
      const cy = r.top + r.height/2;
      const dx = ((e.clientX - cx) / r.width) * intensity;
      const dy = ((e.clientY - cy) / r.height) * intensity;
      setPos({ x: dx, y: dy });
    };
    const onLeave = () => setPos({x:0,y:0});
    el.addEventListener('mousemove', onMove);
    el.addEventListener('mouseleave', onLeave);
    return () => {
      el.removeEventListener('mousemove', onMove);
      el.removeEventListener('mouseleave', onLeave);
    };
  }, [intensity]);
  return [ref, pos];
}

/* ---------- 3D TILT ---------- */
function useTilt(max = 8) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const onMove = (e) => {
      const r = el.getBoundingClientRect();
      const px = (e.clientX - r.left) / r.width;
      const py = (e.clientY - r.top) / r.height;
      const rx = (0.5 - py) * max * 2;
      const ry = (px - 0.5) * max * 2;
      el.style.transform = `perspective(900px) rotateX(${rx}deg) rotateY(${ry}deg)`;
    };
    const onLeave = () => { el.style.transform = ''; };
    el.addEventListener('mousemove', onMove);
    el.addEventListener('mouseleave', onLeave);
    return () => {
      el.removeEventListener('mousemove', onMove);
      el.removeEventListener('mouseleave', onLeave);
    };
  }, [max]);
  return ref;
}

/* ---------- GLITCH TEXT ---------- */
function GlitchText({ children, className, style, accent = '#e5ff00' }) {
  return (
    <span className={`glitch-wrap ${className||''}`} style={{position:'relative', display:'inline-block', ...style}} data-text={children}>
      <span style={{position:'relative', zIndex:2}}>{children}</span>
      <span aria-hidden style={{
        position:'absolute', inset:0, color:accent, mixBlendMode:'screen',
        clipPath: 'inset(0 0 0 0)',
        animation:'glitchA 3.6s infinite steps(1,end)',
      }}>{children}</span>
      <span aria-hidden style={{
        position:'absolute', inset:0, color:'#ff2bd6', mixBlendMode:'screen',
        animation:'glitchB 3.6s infinite steps(1,end)',
      }}>{children}</span>
    </span>
  );
}

/* ---------- PAGE LOADER ---------- */
function PageLoader({ accent }) {
  const [done, setDone] = useState(false);
  const [pct, setPct] = useState(0);
  useEffect(() => {
    const t0 = performance.now();
    const dur = 1500;
    const tick = (t) => {
      const e = Math.min(1, (t - t0) / dur);
      const eased = 1 - Math.pow(1 - e, 2);
      setPct(Math.floor(eased * 100));
      if (e < 1) requestAnimationFrame(tick);
      else setTimeout(() => setDone(true), 350);
    };
    requestAnimationFrame(tick);
  }, []);
  if (done) return null;
  return (
    <div style={{
      position:'fixed', inset:0, zIndex:9998,
      background:'#050505',
      display:'flex', alignItems:'center', justifyContent:'center',
      flexDirection:'column', gap:32,
      transition:'opacity 0.5s, transform 0.6s cubic-bezier(0.7,0,0.3,1)',
      opacity: pct >= 100 ? 0 : 1,
      transform: pct >= 100 ? 'translateY(-100%)' : 'translateY(0)',
    }}>
      <div className="display" style={{fontSize:'min(28vw, 320px)', lineHeight:0.85, color:accent, position:'relative'}}>
        APEX
      </div>
      <div style={{display:'flex', gap:18, alignItems:'center', minWidth:280}}>
        <div className="mono" style={{fontSize:10, letterSpacing:'0.3em', color:'var(--ink-dim)'}}>LOADING DROP_04</div>
        <div style={{flex:1, height:1, background:'#1a1a1a', position:'relative'}}>
          <div style={{position:'absolute', inset:'-1px 0 -1px 0', width:`${pct}%`, background:accent, transition:'width 0.05s linear'}} />
        </div>
        <div className="mono" style={{fontSize:10, letterSpacing:'0.3em', color:accent}}>{String(pct).padStart(3,'0')}</div>
      </div>
    </div>
  );
}

/* ---------- SCROLL PROGRESS ---------- */
function ScrollProgress({ accent }) {
  const [p, setP] = useState(0);
  useEffect(() => {
    const onScroll = () => {
      const max = document.documentElement.scrollHeight - window.innerHeight;
      setP(max > 0 ? window.scrollY / max : 0);
    };
    window.addEventListener('scroll', onScroll, { passive: true });
    onScroll();
    return () => window.removeEventListener('scroll', onScroll);
  }, []);
  return (
    <div style={{
      position:'fixed', top:0, left:0, right:0, height:2,
      zIndex:99, pointerEvents:'none',
    }}>
      <div style={{height:'100%', width:`${p*100}%`, background:accent, transition:'width 0.05s linear', boxShadow:`0 0 12px ${accent}`}} />
    </div>
  );
}

/* ---------- IS MOBILE ---------- */
function useIsMobile(breakpoint = 768) {
  const [isMobile, setIsMobile] = useState(() => window.innerWidth <= breakpoint);
  useEffect(() => {
    const mq = window.matchMedia(`(max-width: ${breakpoint}px)`);
    const handler = (e) => setIsMobile(e.matches);
    mq.addEventListener('change', handler);
    return () => mq.removeEventListener('change', handler);
  }, [breakpoint]);
  return isMobile;
}

Object.assign(window, {
  CustomCursor, ScrambleText, useScramble, Counter, Reveal, RevealWords,
  useMagnet, Magnetic, useMouseParallax, useTilt, GlitchText, PageLoader, ScrollProgress,
  useIsMobile,
});
