const { useState, useEffect, useRef } = React;

/* Scroll reveal: fade + rise + blur, once. */
function useReveal(delay = 0) {
  const ref = useRef(null);
  const [shown, setShown] = useState(false);
  useEffect(() => {
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) { setShown(true); return; }
    const el = ref.current; if (!el) return;
    const io = new IntersectionObserver((es) => {
      es.forEach((e) => { if (e.isIntersecting) { setTimeout(() => setShown(true), delay); io.disconnect(); } });
    }, { threshold: 0.12, rootMargin: "0px 0px -8% 0px" });
    io.observe(el);
    return () => io.disconnect();
  }, [delay]);
  return [ref, shown];
}

function Reveal({ children, delay = 0, y = 22, as = "div", style, ...rest }) {
  const [ref, shown] = useReveal(delay);
  const Tag = as;
  return React.createElement(Tag, { ref, ...rest, style: {
    opacity: shown ? 1 : 0,
    transform: shown ? "none" : `translateY(${y}px)`,
    filter: shown ? "blur(0)" : "blur(6px)",
    transition: "opacity .7s cubic-bezier(.2,.7,.3,1), transform .7s cubic-bezier(.2,.7,.3,1), filter .7s cubic-bezier(.2,.7,.3,1)",
    ...style } }, children);
}

/* Count up to a target once visible. Preserves suffixes like K and +. */
function Counter({ value, duration = 1200 }) {
  const m = String(value).match(/^([\d.]+)(.*)$/);
  const target = m ? parseFloat(m[1]) : 0;
  const suffix = m ? m[2] : "";
  const decimals = m && m[1].includes(".") ? 1 : 0;
  const [ref, shown] = useReveal(0);
  const [n, setN] = useState(0);
  useEffect(() => {
    if (!shown) return;
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) { setN(target); return; }
    let raf, t0;
    const step = (t) => {
      if (!t0) t0 = t;
      const p = Math.min(1, (t - t0) / duration);
      setN(target * (1 - Math.pow(1 - p, 3)));
      if (p < 1) raf = requestAnimationFrame(step);
    };
    raf = requestAnimationFrame(step);
    return () => cancelAnimationFrame(raf);
  }, [shown, target, duration]);
  return <span ref={ref}>{n.toFixed(decimals)}{suffix}</span>;
}

/* Parallax translate driven by scroll position. */
function useParallax(strength = 0.12) {
  const ref = useRef(null);
  useEffect(() => {
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
    let raf = 0;
    const onScroll = () => {
      if (raf) return;
      raf = requestAnimationFrame(() => {
        raf = 0;
        const el = ref.current; if (!el) return;
        const r = el.getBoundingClientRect();
        const mid = r.top + r.height / 2 - window.innerHeight / 2;
        el.style.transform = `translate3d(0, ${(-mid * strength).toFixed(1)}px, 0)`;
      });
    };
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => { window.removeEventListener("scroll", onScroll); if (raf) cancelAnimationFrame(raf); };
  }, [strength]);
  return ref;
}

/* Pointer-following glow, confined to the element. */
function GlowSurface({ children, radius = 420, style, ...rest }) {
  const ref = useRef(null);
  const onMove = (e) => {
    const el = ref.current; if (!el) return;
    const r = el.getBoundingClientRect();
    el.style.setProperty("--gx", `${e.clientX - r.left}px`);
    el.style.setProperty("--gy", `${e.clientY - r.top}px`);
    el.style.setProperty("--go", "1");
  };
  const onLeave = () => { const el = ref.current; if (el) el.style.setProperty("--go", "0"); };
  return (
    <div ref={ref} onMouseMove={onMove} onMouseLeave={onLeave} {...rest} style={{ position: "relative", isolation: "isolate", ...style }}>
      <div aria-hidden="true" style={{ position: "absolute", inset: 0, zIndex: -1, pointerEvents: "none", opacity: "var(--go,0)", transition: "opacity .4s ease", background: `radial-gradient(${radius}px circle at var(--gx,50%) var(--gy,50%), color-mix(in srgb, var(--accent) 14%, transparent), transparent 70%)` }}></div>
      {children}
    </div>
  );
}

/* Card tilt on pointer. */
function Tilt({ children, max = 6, style, ...rest }) {
  const ref = useRef(null);
  const reduced = typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
  const onMove = (e) => {
    if (reduced) return;
    const el = ref.current; if (!el) return;
    const r = el.getBoundingClientRect();
    const px = (e.clientX - r.left) / r.width - 0.5;
    const py = (e.clientY - r.top) / r.height - 0.5;
    el.style.transform = `perspective(900px) rotateX(${(-py * max).toFixed(2)}deg) rotateY(${(px * max).toFixed(2)}deg) translateY(-2px)`;
  };
  const onLeave = () => { const el = ref.current; if (el) el.style.transform = "perspective(900px) rotateX(0) rotateY(0) translateY(0)"; };
  return <div ref={ref} onMouseMove={onMove} onMouseLeave={onLeave} {...rest} style={{ transition: "transform .35s cubic-bezier(.2,.7,.3,1)", willChange: "transform", ...style }}>{children}</div>;
}

/* Soft cursor companion — a lagging accent halo, desktop pointers only. */
function CursorGlow() {
  useEffect(() => {
    if (window.matchMedia("(pointer: coarse)").matches) return;
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
    const dot = document.createElement("div");
    dot.className = "cursor-halo";
    document.body.appendChild(dot);
    let x = window.innerWidth / 2, y = window.innerHeight / 2, cx = x, cy = y, raf;
    const move = (e) => { x = e.clientX; y = e.clientY; dot.style.opacity = "1"; };
    const over = (e) => { dot.dataset.on = e.target.closest("a,button,input,textarea,[role=button]") ? "1" : "0"; };
    const loop = () => { cx += (x - cx) * 0.14; cy += (y - cy) * 0.14; dot.style.transform = `translate3d(${cx}px, ${cy}px, 0) translate(-50%,-50%)`; raf = requestAnimationFrame(loop); };
    loop();
    window.addEventListener("mousemove", move, { passive: true });
    window.addEventListener("mouseover", over, { passive: true });
    return () => { cancelAnimationFrame(raf); window.removeEventListener("mousemove", move); window.removeEventListener("mouseover", over); dot.remove(); };
  }, []);
  return null;
}

/* Rotating word in the hero. */
function Rotator({ words, interval = 2400 }) {
  const [i, setI] = useState(0);
  const [out, setOut] = useState(false);
  useEffect(() => {
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
    const t = setInterval(() => {
      setOut(true);
      setTimeout(() => { setI((v) => (v + 1) % words.length); setOut(false); }, 340);
    }, interval);
    return () => clearInterval(t);
  }, [words.length, interval]);
  /* Sizer holds the tallest word so the headline never changes height mid-rotation. */
  return (
    <span style={{ display: "grid", position: "relative" }}>
      {words.map((w) => <span key={w} aria-hidden="true" style={{ gridArea: "1/1", visibility: "hidden" }}>{w}</span>)}
      <span style={{ gridArea: "1/1", color: "var(--accent)", opacity: out ? 0 : 1, transform: out ? "translateY(-14px)" : "none", filter: out ? "blur(5px)" : "blur(0)", transition: "all .34s cubic-bezier(.2,.7,.3,1)" }}>{words[i]}</span>
    </span>
  );
}

Object.assign(window, { useReveal, Reveal, Counter, useParallax, GlowSurface, Tilt, CursorGlow, Rotator });
