/* Animation primitives: WordsPullUp / WordsPullUpMultiStyle drive via Web Animations API
   on mount (reliable regardless of CSS keyframe scheduling). AnimatedLetter is
   framer-motion scroll-linked. */

const { motion, useScroll, useTransform } = window.Motion || {};

const EASE_OUT_EXPO = [0.16, 1, 0.3, 1];
const EASE_CARD = [0.22, 1, 0.36, 1];
const CSS_EASE = "cubic-bezier(0.16, 1, 0.3, 1)";

function runPullUp(container, stagger) {
  if (!container) return;
  const spans = container.querySelectorAll("[data-pullup]");
  // If page is hidden (e.g. headless/iframe not visible), snap to final state so
  // content is not permanently invisible while animations wait for visibility.
  const forceFinal = typeof document !== "undefined" && document.hidden;
  spans.forEach((el, i) => {
    if (forceFinal) {
      el.style.opacity = "1";
      el.style.transform = "translateY(0)";
      return;
    }
    try {
      el.animate(
        [
        { opacity: 0, transform: "translateY(20px)" },
        { opacity: 1, transform: "translateY(0)" }],

        {
          duration: 900,
          delay: i * stagger * 1000,
          easing: CSS_EASE,
          fill: "both"
        }
      );
    } catch (e) {
      el.style.opacity = "1";
      el.style.transform = "translateY(0)";
    }
  });
}

/**
 * WordsPullUp — splits text by spaces, animates each word via WAAPI.
 */
function WordsPullUp({ text, className = "", style = {}, stagger = 0.08, showAsterisk = false, as = "h1" }) {
  const ref = React.useRef(null);
  const words = text.split(" ");
  const Tag = as;

  React.useEffect(() => {
    runPullUp(ref.current, stagger);
  }, [text, stagger]);

  return (
    <Tag
      ref={ref}
      className={className}
      style={{ display: "flex", flexWrap: "wrap", rowGap: "0.05em", ...style }}>
      
      {words.map((word, wi) => {
        const isLast = wi === words.length - 1;
        return (
          <span
            key={wi}
            data-pullup=""
            style={{
              display: "inline-block",
              position: "relative",
              whiteSpace: "nowrap",
              marginRight: wi < words.length - 1 ? "0.22em" : 0
            }}>
            
            {word}
            {showAsterisk && isLast ?
            <span
              aria-hidden="true"
              style={{
                position: "absolute",
                top: "0.65em",
                right: "-0.3em",
                fontSize: "0.31em",
                fontWeight: 400,
                lineHeight: 1
              }}>
              
                *
              </span> :
            null}
          </span>);

      })}
    </Tag>);

}

/**
 * WordsPullUpMultiStyle — array of {text, className} segments, animated word-by-word.
 */
function WordsPullUpMultiStyle({ segments, className = "", stagger = 0.08, wrapperStyle = {} }) {
  const ref = React.useRef(null);
  const flat = [];
  segments.forEach((seg) => {
    const words = seg.text.split(" ").filter(Boolean);
    words.forEach((w) => flat.push({ word: w, className: seg.className || "" }));
  });

  React.useEffect(() => {
    runPullUp(ref.current, stagger);
  }, [stagger, flat.length]);

  return (
    <div
      ref={ref}
      className={className}
      style={{
        display: "flex",
        flexWrap: "wrap",
        justifyContent: "center",
        rowGap: "0.35em",
        ...wrapperStyle
      }}>
      
      {flat.map((item, i) =>
      <span
        key={i}
        data-pullup=""
        className={item.className}
        style={{
          display: "inline-block",
          marginRight: "0.22em",
          whiteSpace: "nowrap", fontFamily: "BlinkMacSystemFont"
        }}>
        
          {item.word}
        </span>
      )}
    </div>);

}

/** FadeUp — single element that fades + translates up via WAAPI on mount. */
function FadeUp({ as: Tag = "div", delay = 0, children, className = "", style = {}, ...rest }) {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    try {
      el.animate(
        [
        { opacity: 0, transform: "translateY(20px)" },
        { opacity: 1, transform: "translateY(0)" }],

        { duration: 900, delay: delay * 1000, easing: CSS_EASE, fill: "both" }
      );
    } catch (e) {
      el.style.opacity = "1";
      el.style.transform = "translateY(0)";
    }
  }, [delay]);
  return (
    <Tag ref={ref} className={className} style={style} {...rest}>
      {children}
    </Tag>);

}

/** AnimatedLetter — opacity ramps from 0.2 → 1 based on scroll progress. */
function AnimatedLetter({ char, progress, range, letterKey }) {
  const opacity = useTransform(progress, range, [0.2, 1]);
  if (char === " ") return <span key={letterKey}>&nbsp;</span>;
  return <motion.span key={letterKey} style={{ opacity, display: "inline-block" }}>{char}</motion.span>;
}

/** ScrollRevealText — wraps a string, maps each char through AnimatedLetter. */
function ScrollRevealText({ text, className = "", style = {} }) {
  const ref = React.useRef(null);
  const { scrollYProgress } = useScroll({
    target: ref,
    offset: ["start 0.8", "end 0.2"]
  });
  const chars = Array.from(text);
  const total = chars.length;

  return (
    <p ref={ref} className={className} style={style}>
      {chars.map((c, i) => {
        const cp = i / total;
        const range = [Math.max(0, cp - 0.1), Math.min(1, cp + 0.05)];
        return <AnimatedLetter key={`c${i}`} letterKey={`c${i}`} char={c} progress={scrollYProgress} range={range} />;
      })}
    </p>);

}

/** useTilt — subtle 3D tilt + pan toward cursor on hover, pointer devices only.
    Returns an object spread onto a wrapper that provides CSS perspective. */
function useTilt(ref, { maxTilt = 4, maxPan = 6, lerp = 0.12 } = {}) {
  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    if (!window.matchMedia || !window.matchMedia("(hover: hover) and (pointer: fine)").matches) return;

    let targetRX = 0, targetRY = 0, targetTX = 0, targetTY = 0;
    let rx = 0, ry = 0, tx = 0, ty = 0;
    let rafId = null, running = false;

    const apply = () => {
      el.style.transform = `perspective(1400px) translate3d(${tx}px, ${ty}px, 0) rotateX(${rx}deg) rotateY(${ry}deg)`;
    };

    const tick = () => {
      rx += (targetRX - rx) * lerp;
      ry += (targetRY - ry) * lerp;
      tx += (targetTX - tx) * lerp;
      ty += (targetTY - ty) * lerp;
      apply();
      if (
        Math.abs(targetRX - rx) < 0.02 &&
        Math.abs(targetRY - ry) < 0.02 &&
        Math.abs(targetTX - tx) < 0.05 &&
        Math.abs(targetTY - ty) < 0.05
      ) {
        rx = targetRX; ry = targetRY; tx = targetTX; ty = targetTY;
        apply();
        running = false;
        rafId = null;
        return;
      }
      rafId = requestAnimationFrame(tick);
    };

    const start = () => {
      if (!running) {
        running = true;
        rafId = requestAnimationFrame(tick);
      }
    };

    const onMove = (e) => {
      const rect = el.getBoundingClientRect();
      const nx = (e.clientX - rect.left) / rect.width - 0.5;
      const ny = (e.clientY - rect.top) / rect.height - 0.5;
      targetRY = nx * maxTilt;
      targetRX = -ny * maxTilt;
      targetTX = nx * maxPan;
      targetTY = ny * maxPan;
      start();
    };

    const onLeave = () => {
      targetRX = 0; targetRY = 0; targetTX = 0; targetTY = 0;
      start();
    };

    el.style.willChange = "transform";
    el.addEventListener("mousemove", onMove);
    el.addEventListener("mouseleave", onLeave);

    return () => {
      el.removeEventListener("mousemove", onMove);
      el.removeEventListener("mouseleave", onLeave);
      if (rafId) cancelAnimationFrame(rafId);
      el.style.transform = "";
      el.style.willChange = "";
    };
  }, [ref, maxTilt, maxPan, lerp]);
}

Object.assign(window, {
  WordsPullUp,
  WordsPullUpMultiStyle,
  AnimatedLetter,
  ScrollRevealText,
  FadeUp,
  useTilt,
  EASE_OUT_EXPO,
  EASE_CARD
});