"use client";

import {
  motion,
  useReducedMotion,
  useScroll,
  useSpring,
  type Variants,
} from "motion/react";
import {
  useEffect,
  useRef,
  useState,
  type ComponentProps,
  type ReactNode,
} from "react";

/**
 * Premium, reusable motion system.
 *
 * Design goals (spec §Phase 4/5/10):
 *  - A small set of composable primitives with DISTINCT motion personalities.
 *  - SSR-SAFE: content renders VISIBLE in the server HTML (no `opacity:0` in the
 *    markup). After hydration, only elements BELOW the fold are armed — hidden
 *    instantly off-screen, then animated in when scrolled to. Above-the-fold
 *    content is never hidden, so there is no flash, no blank-without-JS, and no
 *    LCP penalty.
 *  - Every primitive degrades to a plain, fully-visible element under
 *    prefers-reduced-motion.
 */

// Expo-out — the house easing for entrances.
const EASE_OUT = [0.22, 1, 0.36, 1] as const;
// Gentle spring for interactive (hover/tap) feedback.
const HOVER_SPRING = { type: "spring", stiffness: 400, damping: 28 } as const;

type Personality = "rise" | "fade" | "blur-rise" | "scale" | "left" | "right";

function offsetFor(personality: Personality, distance: number) {
  switch (personality) {
    case "left":
      return { x: -distance, y: 0 };
    case "right":
      return { x: distance, y: 0 };
    case "fade":
    case "scale":
      return { x: 0, y: 0 };
    default:
      return { x: 0, y: distance };
  }
}

/**
 * SSR-safe scroll-reveal controller.
 * Returns `hide: true` only for elements that were below the fold at hydration
 * and have not yet scrolled into view. Everything else stays visible.
 */
function useScrollReveal() {
  const ref = useRef<HTMLDivElement>(null);
  const [armed, setArmed] = useState(false);
  const [revealed, setRevealed] = useState(false);

  useEffect(() => {
    if (typeof IntersectionObserver === "undefined") return; // stay visible
    const el = ref.current;
    if (!el) return;

    const rect = el.getBoundingClientRect();
    if (rect.top < window.innerHeight && rect.bottom > 0) return; // in view → stay visible

    setArmed(true); // below fold → hide instantly (off-screen) then reveal on scroll
    const io = new IntersectionObserver(
      (entries) => {
        if (entries.some((e) => e.isIntersecting)) {
          setRevealed(true);
          io.disconnect();
        }
      },
      { rootMargin: "0px 0px -12% 0px" },
    );
    io.observe(el);
    return () => io.disconnect();
  }, []);

  return { ref, hide: armed && !revealed };
}

/**
 * ScrollReveal — reveal a block as it scrolls into view, once.
 * `personality` selects the entrance feel; `blur` adds a soft focus-in.
 */
export function ScrollReveal({
  children,
  className,
  delay = 0,
  distance = 24,
  duration = 0.55,
  personality = "rise",
  blur = false,
}: {
  children: ReactNode;
  className?: string;
  delay?: number;
  distance?: number;
  duration?: number;
  personality?: Personality;
  blur?: boolean;
}) {
  const reduce = useReducedMotion();
  const { ref, hide } = useScrollReveal();

  if (reduce) return <div className={className}>{children}</div>;

  const { x, y } = offsetFor(personality, distance);
  const hidden = {
    opacity: 0,
    x,
    y,
    scale: personality === "scale" ? 0.96 : 1,
    filter: blur ? "blur(8px)" : "blur(0px)",
  };
  const shown = { opacity: 1, x: 0, y: 0, scale: 1, filter: "blur(0px)" };

  return (
    <motion.div
      ref={ref}
      className={className}
      initial={false}
      animate={hide ? hidden : shown}
      transition={hide ? { duration: 0 } : { duration, ease: EASE_OUT, delay }}
    >
      {children}
    </motion.div>
  );
}

/** Back-compat alias — same API as the original Reveal, now a real entrance. */
export function Reveal({
  children,
  className,
  delay = 0,
  y = 20,
}: {
  children: ReactNode;
  className?: string;
  delay?: number;
  y?: number;
}) {
  return (
    <ScrollReveal className={className} delay={delay} distance={y} personality="rise">
      {children}
    </ScrollReveal>
  );
}

const STAGGER_ITEM: Variants = {
  hidden: { opacity: 0, y: 18, transition: { duration: 0 } },
  show: { opacity: 1, y: 0, transition: { duration: 0.5, ease: EASE_OUT } },
};

/**
 * StaggerContainer — reveals its <StaggerItem> children in sequence when the
 * group scrolls into view. SSR-safe (visible until armed below the fold).
 */
export function StaggerContainer({
  children,
  className,
  stagger = 0.08,
  delay = 0,
}: {
  children: ReactNode;
  className?: string;
  stagger?: number;
  delay?: number;
}) {
  const reduce = useReducedMotion();
  const { ref, hide } = useScrollReveal();

  if (reduce) return <div className={className}>{children}</div>;

  return (
    <motion.div
      ref={ref}
      className={className}
      initial={false}
      animate={hide ? "hidden" : "show"}
      variants={{
        hidden: {},
        show: { transition: { staggerChildren: stagger, delayChildren: delay } },
      }}
    >
      {children}
    </motion.div>
  );
}

export function StaggerItem({
  children,
  className,
}: {
  children: ReactNode;
  className?: string;
}) {
  const reduce = useReducedMotion();
  if (reduce) return <div className={className}>{children}</div>;
  return (
    <motion.div className={className} variants={STAGGER_ITEM}>
      {children}
    </motion.div>
  );
}

// Back-compat aliases for the earlier RevealGroup/RevealItem names.
export const RevealGroup = StaggerContainer;
export const RevealItem = StaggerItem;

/**
 * AnimatedCard — a container with a subtle hover lift and tap press.
 */
export function AnimatedCard({
  children,
  className,
  lift = 4,
  ...rest
}: {
  children: ReactNode;
  className?: string;
  lift?: number;
} & Omit<ComponentProps<typeof motion.div>, "children">) {
  const reduce = useReducedMotion();
  if (reduce) return <div className={className}>{children}</div>;
  return (
    <motion.div
      className={className}
      whileHover={{ y: -lift }}
      whileTap={{ scale: 0.985 }}
      transition={HOVER_SPRING}
      {...rest}
    >
      {children}
    </motion.div>
  );
}

/**
 * AnimatedButton — a motion <button> with hover-grow and tap-press.
 * Use for bespoke buttons; the shared <Button> component has its own motion.
 */
export function AnimatedButton({
  children,
  className,
  ...rest
}: ComponentProps<typeof motion.button>) {
  const reduce = useReducedMotion();
  return (
    <motion.button
      className={className}
      whileHover={reduce ? undefined : { scale: 1.03 }}
      whileTap={reduce ? undefined : { scale: 0.97 }}
      transition={HOVER_SPRING}
      {...rest}
    >
      {children}
    </motion.button>
  );
}

// Client-session flag: false during SSR and the very first mount, true after.
// Lets us render the first page load visible (SSR-safe, good LCP) while still
// animating on subsequent client-side navigations.
let hasMountedOnce = false;

/**
 * PageTransition — wraps route content. Placed in a template.tsx so Next
 * re-mounts (and re-runs the entrance) on every navigation. No global preloader.
 *
 * The FIRST load renders with initial={false} so the SSR HTML is fully visible
 * (never opacity:0 in the markup). Only client-side navigations — which don't
 * touch SSR — play the fade/rise entrance (>0.2s per spec).
 */
export function PageTransition({ children }: { children: ReactNode }) {
  const reduce = useReducedMotion();
  const [isNavigation] = useState(hasMountedOnce);
  useEffect(() => {
    hasMountedOnce = true;
  }, []);

  if (reduce) return <>{children}</>;

  return (
    <motion.div
      initial={isNavigation ? { opacity: 0, y: 8 } : false}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.32, ease: EASE_OUT }}
    >
      {children}
    </motion.div>
  );
}

/**
 * ScrollProgress — 2px reading-progress bar, primary→coral, tied to scroll.
 * Hidden under reduced motion.
 */
export function ScrollProgress() {
  const { scrollYProgress } = useScroll();
  const scaleX = useSpring(scrollYProgress, {
    stiffness: 120,
    damping: 30,
    mass: 0.3,
  });

  return (
    <motion.div
      aria-hidden
      style={{ scaleX }}
      className="fixed inset-x-0 top-0 z-[60] h-0.5 origin-left bg-primary motion-reduce:hidden"
    />
  );
}
