/* ============================================================
   UI primitives — robust, progressive-enhancement motion.
   Hidden/animated states are gated on html.animate-ready, which
   is only added via requestAnimationFrame. In environments where
   rAF is frozen (some preview sandboxes) the class never lands,
   so all content renders in its visible resting state.
   ============================================================ */
const { useState, useEffect, useRef, useCallback } = React;

/* arm animations only when rAF actually runs (real browser) */
requestAnimationFrame(() => requestAnimationFrame(() => {
  document.documentElement.classList.add("animate-ready");
}));

/* ---------- scroll-reveal (CSS class via IntersectionObserver) ---------- */
function Reveal({ children, delay = 0, className = "", as = "div" }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current; if (!el) return;
    if (!("IntersectionObserver" in window)) { el.classList.add("in"); return; }
    const io = new IntersectionObserver((es) => {
      es.forEach((e) => { if (e.isIntersecting) { el.classList.add("in"); io.unobserve(el); } });
    }, { rootMargin: "-8% 0px" });
    io.observe(el);
    return () => io.disconnect();
  }, []);
  const Tag = as;
  return <Tag ref={ref} className={`reveal ${className}`} style={{ transitionDelay: `${delay}s` }}>{children}</Tag>;
}

/* ---------- magnetic wrapper ---------- */
function Magnetic({ children, strength = 0.3, className = "" }) {
  const ref = useRef(null);
  const onMove = useCallback((e) => {
    const el = ref.current; if (!el) return;
    const r = el.getBoundingClientRect();
    const x = (e.clientX - (r.left + r.width / 2)) * strength;
    const y = (e.clientY - (r.top + r.height / 2)) * strength;
    el.style.transform = `translate(${x}px, ${y}px)`;
  }, [strength]);
  const onLeave = useCallback(() => { if (ref.current) ref.current.style.transform = "translate(0,0)"; }, []);
  return (
    <span ref={ref} className={className} onMouseMove={onMove} onMouseLeave={onLeave}
      style={{ display: "inline-flex", transition: "transform .35s cubic-bezier(.2,.8,.2,1)" }}>
      {children}
    </span>
  );
}

/* ---------- pill button ---------- */
function Pill({ variant = "lime", children, ico = "↗", href, magnetic = true, ...rest }) {
  const inner = (
    <a className={`pill pill--${variant}`} data-cursor="hot" href={href} {...rest}>
      <span>{children}</span>
      {ico && <span className="pill-ico">{ico}</span>}
    </a>
  );
  return magnetic ? <Magnetic strength={0.25}>{inner}</Magnetic> : inner;
}

/* ---------- count-up number (rAF, with timer fallback) ---------- */
function Counter({ to, suffix = "", decimals = 0, dur = 1.4 }) {
  const ref = useRef(null);
  const [val, setVal] = useState(0);
  const started = useRef(false);
  useEffect(() => {
    const el = ref.current; if (!el) return;
    const run = () => {
      if (started.current) return; started.current = true;
      const start = performance.now();
      let rafActive = false;
      const tick = (now) => {
        rafActive = true;
        const p = Math.min((now - start) / (dur * 1000), 1);
        setVal(to * (1 - Math.pow(1 - p, 3)));
        if (p < 1) requestAnimationFrame(tick);
      };
      requestAnimationFrame(tick);
      // fallback: if rAF never advances, snap to final value
      setTimeout(() => { if (!rafActive) setVal(to); }, 500);
    };
    let fired = false;
    const trigger = () => { if (!fired) { fired = true; run(); } };
    if ("IntersectionObserver" in window) {
      const io = new IntersectionObserver((es) => es.forEach((e) => { if (e.isIntersecting) trigger(); }), { threshold: 0.4 });
      io.observe(el);
      const t = setTimeout(trigger, 1200); // fallback if IO frozen
      return () => { io.disconnect(); clearTimeout(t); };
    }
    trigger();
  }, [to, dur]);
  return <span ref={ref}>{val.toFixed(decimals)}<span className="suf">{suffix}</span></span>;
}

/* ---------- custom cursor (rAF for ring; dot is instant) ---------- */
function Cursor() {
  const dot = useRef(null), ring = useRef(null);
  useEffect(() => {
    if (matchMedia("(hover: none)").matches) return;
    let rx = 0, ry = 0, dx = 0, dy = 0, raf;
    const move = (e) => {
      dx = e.clientX; dy = e.clientY;
      if (dot.current) dot.current.style.transform = `translate(${dx}px,${dy}px) translate(-50%,-50%)`;
      const t = e.target.closest('[data-cursor="hot"], a, button');
      if (ring.current) ring.current.classList.toggle("hot", !!t);
    };
    const loop = () => {
      rx += (dx - rx) * 0.18; ry += (dy - ry) * 0.18;
      if (ring.current) ring.current.style.transform = `translate(${rx}px,${ry}px) translate(-50%,-50%)`;
      raf = requestAnimationFrame(loop);
    };
    window.addEventListener("mousemove", move); loop();
    return () => { window.removeEventListener("mousemove", move); cancelAnimationFrame(raf); };
  }, []);
  return (<><div ref={ring} className="cursor-ring" /><div ref={dot} className="cursor-dot" /></>);
}

/* ---------- scroll progress bar ---------- */
function Progress() {
  const ref = useRef(null);
  useEffect(() => {
    const onScroll = () => {
      const h = document.documentElement;
      const p = h.scrollTop / (h.scrollHeight - h.clientHeight || 1);
      if (ref.current) ref.current.style.transform = `scaleX(${p})`;
    };
    window.addEventListener("scroll", onScroll, { passive: true }); onScroll();
    return () => window.removeEventListener("scroll", onScroll);
  }, []);
  return <div ref={ref} className="progress" style={{ width: "100%", transform: "scaleX(0)" }} />;
}

Object.assign(window, { Reveal, Magnetic, Pill, Counter, Cursor, Progress });
