// motion.jsx — scroll-driven motion primitives, Claude easing
const { useEffect, useRef, useState, useCallback, useLayoutEffect } = React;

const EASE = "cubic-bezier(0.16, 1, 0.3, 1)";
const PREFERS_REDUCE = typeof window !== "undefined"
  && window.matchMedia
  && window.matchMedia("(prefers-reduced-motion: reduce)").matches;

function clamp(n, a = 0, b = 1) { return Math.max(a, Math.min(b, n)); }
function lerp(a, b, t) { return a + (b - a) * t; }

// Reveal element on enter — once. Variants: 'up'|'zoom'|'left'|'right'
function Reveal({ as = "div", variant = "up", delay = 0, amount = 0.2, className = "", style, children, ...rest }) {
  const ref = useRef(null);
  const [seen, setSeen] = useState(PREFERS_REDUCE);
  useEffect(() => {
    if (PREFERS_REDUCE) return;
    const el = ref.current; if (!el) return;
    const io = new IntersectionObserver(([e]) => {
      if (e.isIntersecting) { setSeen(true); io.disconnect(); }
    }, { threshold: amount });
    io.observe(el);
    return () => io.disconnect();
  }, [amount]);

  const initial = {
    up:    { opacity: 0, transform: "translateY(30px)" },
    zoom:  { opacity: 0, transform: "scale(0.92)" },
    left:  { opacity: 0, transform: "translateX(-40px)" },
    right: { opacity: 0, transform: "translateX(40px)" },
    fade:  { opacity: 0, transform: "translateY(0)" },
  }[variant];
  const target = { opacity: 1, transform: "translateY(0) translateX(0) scale(1)" };

  const Comp = as;
  return (
    <Comp
      ref={ref}
      className={className}
      style={{
        transition: `opacity 600ms ${EASE} ${delay}ms, transform 600ms ${EASE} ${delay}ms`,
        ...(seen ? target : initial),
        ...style,
      }}
      {...rest}
    >
      {children}
    </Comp>
  );
}

// Stagger container — children get incremental delay via data-stagger or order
function Stagger({ as = "div", step = 0.1, amount = 0.15, className = "", style, children, ...rest }) {
  const ref = useRef(null);
  const [seen, setSeen] = useState(PREFERS_REDUCE);
  useEffect(() => {
    if (PREFERS_REDUCE) return;
    const el = ref.current; if (!el) return;
    const io = new IntersectionObserver(([e]) => {
      if (e.isIntersecting) { setSeen(true); io.disconnect(); }
    }, { threshold: amount });
    io.observe(el);
    return () => io.disconnect();
  }, [amount]);

  const Comp = as;
  return (
    <Comp ref={ref} className={className} style={style} {...rest}>
      {React.Children.map(children, (child, i) => {
        if (!React.isValidElement(child)) return child;
        const baseStyle = child.props.style || {};
        const initial = { opacity: 0, transform: "translateY(28px)" };
        const target  = { opacity: 1, transform: "translateY(0)" };
        return React.cloneElement(child, {
          style: {
            transition: `opacity 600ms ${EASE} ${i * step}s, transform 600ms ${EASE} ${i * step}s`,
            ...(seen ? target : initial),
            ...baseStyle,
          },
        });
      })}
    </Comp>
  );
}

// Float — gentle infinite y-bob using rAF, no CSS keyframes
function Float({ amplitude = 6, duration = 3, delay = 0, className = "", style, children, ...rest }) {
  const ref = useRef(null);
  useEffect(() => {
    if (PREFERS_REDUCE) return;
    const el = ref.current; if (!el) return;
    let raf;
    const start = performance.now();
    const tick = (now) => {
      const t = ((now - start) / 1000 + delay) / duration;
      const y = Math.sin(t * Math.PI * 2) * amplitude;
      el.style.transform = `translateY(${y.toFixed(2)}px)`;
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [amplitude, duration, delay]);
  return <div ref={ref} className={className} style={style} {...rest}>{children}</div>;
}

// useScroll — global scroll progress (0..1 across full page)
function useScrollY() {
  const [y, setY] = useState(0);
  useEffect(() => {
    const onScroll = () => setY(window.scrollY || 0);
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);
  return y;
}

// useElementProgress — 0 when element top hits viewport bottom, 1 when bottom hits top.
// Returns a ref + progress (0..1, clamped) and rect data.
function useElementProgress({ start = "top bottom", end = "bottom top" } = {}) {
  const ref = useRef(null);
  const [p, setP] = useState(0);
  useEffect(() => {
    const update = () => {
      const el = ref.current; if (!el) return;
      const r = el.getBoundingClientRect();
      const vh = window.innerHeight;
      // 0 when r.top == vh (just entering); 1 when r.bottom == 0 (fully past)
      const total = r.height + vh;
      const passed = vh - r.top;
      setP(clamp(passed / total));
    };
    update();
    window.addEventListener("scroll", update, { passive: true });
    window.addEventListener("resize", update);
    return () => {
      window.removeEventListener("scroll", update);
      window.removeEventListener("resize", update);
    };
  }, []);
  return [ref, p];
}

// usePinProgress — for pinned/sticky scrolling sections
// Progress 0..1 across the entire pinned section's scroll length.
function usePinProgress() {
  const ref = useRef(null);
  const [p, setP] = useState(0);
  useEffect(() => {
    const update = () => {
      const el = ref.current; if (!el) return;
      const r = el.getBoundingClientRect();
      const vh = window.innerHeight;
      const scrollable = el.offsetHeight - vh;
      if (scrollable <= 0) { setP(0); return; }
      const passed = -r.top;
      setP(clamp(passed / scrollable));
    };
    update();
    window.addEventListener("scroll", update, { passive: true });
    window.addEventListener("resize", update);
    return () => {
      window.removeEventListener("scroll", update);
      window.removeEventListener("resize", update);
    };
  }, []);
  return [ref, p];
}

// Parallax — translates element by `factor * scrollY`
function Parallax({ factor = -0.15, className = "", style, children, ...rest }) {
  const ref = useRef(null);
  useEffect(() => {
    if (PREFERS_REDUCE) return;
    const el = ref.current; if (!el) return;
    const update = () => {
      const y = (window.scrollY || 0) * factor;
      el.style.transform = `translate3d(0, ${y.toFixed(2)}px, 0)`;
    };
    update();
    window.addEventListener("scroll", update, { passive: true });
    return () => window.removeEventListener("scroll", update);
  }, [factor]);
  return <div ref={ref} className={className} style={style} {...rest}>{children}</div>;
}

// ScrollScene — child receives progress (0..1) based on its own viewport position.
// Wraps a render-prop pattern.
function ScrollScene({ children, className = "", style }) {
  const [ref, p] = useElementProgress();
  return <div ref={ref} className={className} style={style}>{children(p)}</div>;
}

Object.assign(window, {
  Reveal, Stagger, Float, Parallax, ScrollScene,
  useScrollY, useElementProgress, usePinProgress,
  EASE, clamp, lerp, PREFERS_REDUCE,
});
