/* Motion primitives — drop-in replacements that mirror Framer Motion's API.
   In a real project: `import { motion } from "framer-motion"` and use
   <motion.div initial={{opacity:0,y:24}} whileInView={{opacity:1,y:0}} viewport={{once:true}} transition={{duration:0.7, ease:[0.22,1,0.36,1]}} />.
   Here we re-implement just enough with IntersectionObserver + CSS to keep
   the demo dependency-light and prove the timings and easings feel right. */

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

const EASE_OUT = "cubic-bezier(0.22, 1, 0.36, 1)";  // soft, premium settle
const EASE_SPRING = "cubic-bezier(0.34, 1.56, 0.64, 1)";  // gentle overshoot

const prefersReducedMotion = () =>
  typeof window !== "undefined" &&
  window.matchMedia &&
  window.matchMedia("(prefers-reduced-motion: reduce)").matches;

/* ---------- useReveal: IntersectionObserver one-shot ---------- */
function useReveal({ threshold = 0.18, rootMargin = "0px 0px -8% 0px", once = true } = {}) {
  const ref = useRef(null);
  const [shown, setShown] = useState(false);
  useEffect(() => {
    if (!ref.current) return;
    if (prefersReducedMotion()) { setShown(true); return; }
    const io = new IntersectionObserver(
      (entries) => {
        entries.forEach((e) => {
          if (e.isIntersecting) {
            setShown(true);
            if (once) io.disconnect();
          } else if (!once) {
            setShown(false);
          }
        });
      },
      { threshold, rootMargin }
    );
    io.observe(ref.current);
    return () => io.disconnect();
  }, [threshold, rootMargin, once]);
  return [ref, shown];
}

/* ---------- Reveal: opacity+translate on scroll ---------- */
function Reveal({ as = "div", delay = 0, y = 24, x = 0, duration = 0.75, className = "", style, children, ...rest }) {
  const Tag = as;
  const [ref, shown] = useReveal();
  const reduced = prefersReducedMotion();
  const base = {
    transition: `opacity ${duration}s ${EASE_OUT} ${delay}s, transform ${duration}s ${EASE_OUT} ${delay}s`,
    opacity: shown || reduced ? 1 : 0,
    transform: shown || reduced ? "translate3d(0,0,0)" : `translate3d(${x}px, ${y}px, 0)`,
    willChange: "opacity, transform",
    ...style,
  };
  return React.createElement(Tag, { ref, className, style: base, ...rest }, children);
}

/* ---------- Stagger: children get incremental delay ---------- */
function Stagger({ as = "div", step = 0.08, initialDelay = 0, y = 22, className = "", children, ...rest }) {
  const arr = React.Children.toArray(children);
  return React.createElement(
    as,
    { className, ...rest },
    arr.map((child, i) =>
      React.cloneElement(<Reveal y={y} delay={initialDelay + i * step}>{child}</Reveal>, { key: i })
    )
  );
}

/* ---------- MagneticButton: cursor pulls element gently ---------- */
function Magnetic({ strength = 0.35, children, className = "", style, as = "div", ...rest }) {
  const ref = useRef(null);
  const Tag = as;
  useEffect(() => {
    const el = ref.current;
    if (!el || prefersReducedMotion()) return;
    let raf;
    const onMove = (e) => {
      const r = el.getBoundingClientRect();
      const x = (e.clientX - (r.left + r.width / 2)) * strength;
      const y = (e.clientY - (r.top + r.height / 2)) * strength;
      cancelAnimationFrame(raf);
      raf = requestAnimationFrame(() => {
        el.style.transform = `translate3d(${x}px, ${y}px, 0)`;
      });
    };
    const onLeave = () => {
      el.style.transform = `translate3d(0,0,0)`;
    };
    el.addEventListener("mousemove", onMove);
    el.addEventListener("mouseleave", onLeave);
    return () => {
      el.removeEventListener("mousemove", onMove);
      el.removeEventListener("mouseleave", onLeave);
      cancelAnimationFrame(raf);
    };
  }, [strength]);
  return React.createElement(
    Tag,
    {
      ref,
      className,
      style: { transition: `transform 0.45s ${EASE_OUT}`, willChange: "transform", ...style },
      ...rest,
    },
    children
  );
}

/* ---------- Parallax wrapper (subtle Y translate on scroll) ---------- */
function Parallax({ speed = 0.18, children, className = "", style, ...rest }) {
  const ref = useRef(null);
  useEffect(() => {
    if (prefersReducedMotion()) return;
    const el = ref.current;
    if (!el) return;
    let raf;
    const onScroll = () => {
      cancelAnimationFrame(raf);
      raf = requestAnimationFrame(() => {
        const r = el.getBoundingClientRect();
        const vh = window.innerHeight;
        const center = r.top + r.height / 2 - vh / 2;
        el.style.transform = `translate3d(0, ${center * -speed}px, 0)`;
      });
    };
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    return () => {
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onScroll);
      cancelAnimationFrame(raf);
    };
  }, [speed]);
  return (
    <div ref={ref} className={className} style={{ willChange: "transform", ...style }} {...rest}>
      {children}
    </div>
  );
}

/* ---------- useScrollY: page scroll position ---------- */
function useScrollY() {
  const [y, setY] = useState(0);
  useEffect(() => {
    const onScroll = () => setY(window.scrollY);
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);
  return y;
}

/* ---------- Letter-by-letter heading reveal ---------- */
function SplitText({ text, className = "", delay = 0, stagger = 0.018, as = "span" }) {
  const [ref, shown] = useReveal();
  const Tag = as;
  const reduced = prefersReducedMotion();
  const words = text.split(" ");
  return (
    <Tag ref={ref} className={className} aria-label={text}>
      {words.map((word, wi) => (
        <span key={wi} style={{ display: "inline-block", whiteSpace: "nowrap" }}>
          {[...word].map((ch, ci) => {
            const i = wi * 100 + ci;
            return (
              <span
                key={ci}
                aria-hidden="true"
                style={{
                  display: "inline-block",
                  transform: shown || reduced ? "translate3d(0,0,0)" : "translate3d(0, 0.5em, 0)",
                  opacity: shown || reduced ? 1 : 0,
                  transition: `opacity 0.6s ${EASE_OUT} ${delay + i * stagger}s, transform 0.7s ${EASE_OUT} ${delay + i * stagger}s`,
                  willChange: "opacity, transform",
                }}
              >
                {ch}
              </span>
            );
          })}
          {wi < words.length - 1 && <span style={{ display: "inline-block", width: "0.28em" }}> </span>}
        </span>
      ))}
    </Tag>
  );
}

/* ---------- LineReveal: each line clips up from hidden overflow ---------- */
/* The cinematic technique used by Apple, Stripe, Awwwards sites.
   Pass `lines` as an array of strings or a single string.            */
function LineReveal({ lines, delay = 0, stagger = 0.11, className = "", style, as = "span" }) {
  const [ref, shown] = useReveal({ threshold: 0.1 });
  const reduced = prefersReducedMotion();
  const arr = Array.isArray(lines) ? lines : [lines];
  const Tag = as;
  return (
    <Tag ref={ref} className={className} style={style} aria-label={arr.join(" ")}>
      {arr.map((line, i) => (
        <span key={i} style={{ display: "block", overflow: "hidden", lineHeight: 1.08 }}>
          <span
            aria-hidden
            style={{
              display: "block",
              transform: shown || reduced ? "translateY(0)" : "translateY(108%)",
              opacity: shown || reduced ? 1 : 0,
              transition: `transform 0.9s ${EASE_OUT} ${delay + i * stagger}s, opacity 0.5s ease ${delay + i * stagger}s`,
              willChange: "transform, opacity",
            }}
          >
            {line}
          </span>
        </span>
      ))}
    </Tag>
  );
}

/* ---------- TiltCard: 3D perspective tilt + gloss on hover ---------- */
function TiltCard({ children, strength = 9, className = "", style, gloss = true, ...rest }) {
  const ref = useRef(null);
  const glossRef = useRef(null);
  useEffect(() => {
    const el = ref.current;
    if (!el || prefersReducedMotion()) return;
    let raf;
    const onMove = (e) => {
      const r = el.getBoundingClientRect();
      const x = (e.clientX - r.left) / r.width - 0.5;   // -0.5 → 0.5
      const y = (e.clientY - r.top)  / r.height - 0.5;
      cancelAnimationFrame(raf);
      raf = requestAnimationFrame(() => {
        el.style.transform = `perspective(900px) rotateY(${x * strength}deg) rotateX(${-y * strength}deg) scale3d(1.025,1.025,1.025)`;
        if (glossRef.current) {
          // gloss follows mouse — polar opposite direction
          glossRef.current.style.background =
            `radial-gradient(circle at ${(x + 0.5) * 100}% ${(y + 0.5) * 100}%, rgba(255,255,255,0.18) 0%, transparent 65%)`;
          glossRef.current.style.opacity = "1";
        }
      });
    };
    const onLeave = () => {
      el.style.transform = "perspective(900px) rotateY(0deg) rotateX(0deg) scale3d(1,1,1)";
      if (glossRef.current) glossRef.current.style.opacity = "0";
    };
    el.addEventListener("mousemove", onMove);
    el.addEventListener("mouseleave", onLeave);
    return () => {
      el.removeEventListener("mousemove", onMove);
      el.removeEventListener("mouseleave", onLeave);
      cancelAnimationFrame(raf);
    };
  }, [strength]);
  return (
    <div
      ref={ref}
      className={className}
      style={{ transition: `transform 0.55s ${EASE_OUT}`, willChange: "transform", transformStyle: "preserve-3d", position: "relative", ...style }}
      {...rest}
    >
      {gloss && (
        <div
          ref={glossRef}
          aria-hidden
          style={{
            position: "absolute", inset: 0, borderRadius: "inherit",
            opacity: 0, pointerEvents: "none", zIndex: 2,
            transition: "opacity 0.3s ease",
          }}
        />
      )}
      {children}
    </div>
  );
}

/* ---------- BlurReveal: blur+scale fade for images/hero previews ---------- */
function BlurReveal({ children, delay = 0, duration = 1.0, className = "", style, ...rest }) {
  const [ref, shown] = useReveal({ threshold: 0.08 });
  const reduced = prefersReducedMotion();
  return (
    <div
      ref={ref}
      className={className}
      style={{
        transition: `opacity ${duration}s ${EASE_OUT} ${delay}s, filter ${duration}s ${EASE_OUT} ${delay}s, transform ${duration}s ${EASE_OUT} ${delay}s`,
        opacity: shown || reduced ? 1 : 0,
        filter: shown || reduced ? "blur(0px)" : "blur(16px)",
        transform: shown || reduced ? "scale(1)" : "scale(0.96)",
        willChange: "opacity, filter, transform",
        ...style,
      }}
      {...rest}
    >
      {children}
    </div>
  );
}

/* ---------- ScrambleText: random-char decode reveal on scroll ---------- */
const _CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@#$%&";
function ScrambleText({ text, delay = 0, speed = 42, className = "" }) {
  const [display, setDisplay] = useState(text);
  const [ref, shown] = useReveal();
  const timerRef = useRef(null);
  const ivRef = useRef(null);
  const hasRun = useRef(false);
  useEffect(() => {
    if (!shown || hasRun.current || prefersReducedMotion()) {
      if (shown) setDisplay(text);
      return;
    }
    hasRun.current = true;
    let step = 0;
    const total = text.length * 4;
    timerRef.current = setTimeout(() => {
      ivRef.current = setInterval(() => {
        const progress = step / total;
        const revealed = Math.floor(progress * text.length);
        setDisplay(
          text.split("").map((ch, i) => {
            if (ch === " ") return " ";
            if (i < revealed) return ch;
            return _CHARS[Math.floor(Math.random() * _CHARS.length)];
          }).join("")
        );
        step++;
        if (step > total) { clearInterval(ivRef.current); setDisplay(text); }
      }, speed);
    }, delay * 1000);
    return () => { clearTimeout(timerRef.current); clearInterval(ivRef.current); };
  }, [shown]);
  return <span ref={ref} className={className} aria-label={text} style={{ fontVariantNumeric: "tabular-nums" }}>{display}</span>;
}

/* ---------- SpringReveal: scale+translate with overshoot bounce ---------- */
function SpringReveal({ children, delay = 0, className = "", style, ...rest }) {
  const [ref, shown] = useReveal({ threshold: 0.1 });
  const reduced = prefersReducedMotion();
  return (
    <div
      ref={ref}
      className={className}
      style={{
        transition: `opacity 0.55s ease ${delay}s, transform 0.75s ${EASE_SPRING} ${delay}s`,
        opacity: shown || reduced ? 1 : 0,
        transform: shown || reduced ? "scale(1) translateY(0px)" : "scale(0.9) translateY(28px)",
        willChange: "opacity, transform",
        ...style,
      }}
      {...rest}
    >
      {children}
    </div>
  );
}

Object.assign(window, {
  Reveal, Stagger, Magnetic, Parallax, SplitText,
  LineReveal, TiltCard, BlurReveal, ScrambleText, SpringReveal,
  useReveal, useScrollY, prefersReducedMotion,
  EASE_OUT, EASE_SPRING,
});
