"use client";

/**
 * Motion primitives scoped to the promo-code pages.
 *
 * Deliberately local to `routes/promo-code-campaigns` — the rest of the console
 * stays on plain Mantine transitions, so nothing here changes shared styling.
 * Every helper degrades to a static render when the OS asks for reduced motion.
 */

import NumberFlow, { type Format } from "@number-flow/react";
import {
  motion,
  useReducedMotion,
  type Transition,
  type Variants,
} from "framer-motion";
import type { ReactNode } from "react";

/** Standard easing for the whole page: a soft "settle" curve, no bounce. */
export const EASE_OUT = [0.16, 1, 0.3, 1] as const;

export const SPRING: Transition = {
  type: "spring",
  stiffness: 260,
  damping: 26,
  mass: 0.9,
};

/** Parent that releases its children one after another. */
export const staggerParent = (stagger = 0.05, delay = 0): Variants => ({
  hidden: {},
  show: { transition: { staggerChildren: stagger, delayChildren: delay } },
});

/** Child that slides up as it fades in. Pairs with `staggerParent`. */
export const fadeUpChild: Variants = {
  hidden: { opacity: 0, y: 12 },
  show: { opacity: 1, y: 0, transition: { duration: 0.42, ease: EASE_OUT } },
};

/** Child that grows in place — used for pills and badges. */
export const popChild: Variants = {
  hidden: { opacity: 0, scale: 0.86 },
  show: { opacity: 1, scale: 1, transition: SPRING },
  exit: { opacity: 0, scale: 0.86, transition: { duration: 0.14 } },
};

interface RevealProps {
  children: ReactNode;
  /** Seconds to wait before starting. */
  delay?: number;
  /** Pixels to travel on the y axis. */
  y?: number;
  className?: string;
}

/**
 * One-shot fade + rise for a block of content. Use for cards and sections that
 * mount once; use `staggerParent`/`fadeUpChild` when siblings should cascade.
 */
export function Reveal({
  children,
  delay = 0,
  y = 14,
  className,
}: RevealProps) {
  const reduce = useReducedMotion();
  if (reduce) return <div className={className}>{children}</div>;
  return (
    <motion.div
      className={className}
      initial={{ opacity: 0, y }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.45, delay, ease: EASE_OUT }}
    >
      {children}
    </motion.div>
  );
}

interface StaggerProps {
  children: ReactNode;
  stagger?: number;
  delay?: number;
  className?: string;
}

/** Wraps children in a stagger context. Each child must be a `MotionChild`. */
export function Stagger({
  children,
  stagger = 0.05,
  delay = 0,
  className,
}: StaggerProps) {
  const reduce = useReducedMotion();
  if (reduce) return <div className={className}>{children}</div>;
  return (
    <motion.div
      className={className}
      variants={staggerParent(stagger, delay)}
      initial="hidden"
      animate="show"
    >
      {children}
    </motion.div>
  );
}

/** A single staggered child. `variant` picks the entrance style. */
export function MotionChild({
  children,
  variant = "fadeUp",
  className,
}: {
  children: ReactNode;
  variant?: "fadeUp" | "pop";
  className?: string;
}) {
  const reduce = useReducedMotion();
  if (reduce) return <div className={className}>{children}</div>;
  return (
    <motion.div
      className={className}
      variants={variant === "pop" ? popChild : fadeUpChild}
    >
      {children}
    </motion.div>
  );
}

/**
 * Height auto-animate for collapsible regions. Mantine's `Collapse` jumps when
 * the content reflows mid-transition; this measures continuously instead.
 */
export function Collapsible({
  open,
  children,
}: {
  open: boolean;
  children: ReactNode;
}) {
  const reduce = useReducedMotion();
  if (reduce) return open ? <div>{children}</div> : null;
  return (
    <motion.div
      initial={false}
      animate={
        open ? { height: "auto", opacity: 1 } : { height: 0, opacity: 0 }
      }
      transition={{
        height: { duration: 0.32, ease: EASE_OUT },
        opacity: { duration: open ? 0.28 : 0.14, delay: open ? 0.06 : 0 },
      }}
      style={{ overflow: "hidden" }}
    >
      {children}
    </motion.div>
  );
}

/**
 * Page arrival: sections rise in sequence rather than all at once.
 *
 * One timeline for the whole page instead of each card guessing its own delay —
 * `PageSection` children inherit their position from the order they appear in, so
 * inserting a section can't collide with a hand-tuned delay elsewhere.
 */
export function PageArrival({
  children,
  stagger = 0.09,
  className,
}: {
  children: ReactNode;
  stagger?: number;
  className?: string;
}) {
  const reduce = useReducedMotion();
  if (reduce) return <div className={className}>{children}</div>;
  return (
    <motion.div
      className={className}
      variants={{
        hidden: {},
        show: { transition: { staggerChildren: stagger, delayChildren: 0.05 } },
      }}
      initial="hidden"
      animate="show"
    >
      {children}
    </motion.div>
  );
}

const sectionVariants: Variants = {
  hidden: { opacity: 0, y: 18, filter: "blur(4px)" },
  show: {
    opacity: 1,
    y: 0,
    filter: "blur(0px)",
    transition: { duration: 0.5, ease: EASE_OUT },
  },
};

/** A direct child of `PageArrival`. Order in the tree sets its arrival order. */
export function PageSection({
  children,
  className,
}: {
  children: ReactNode;
  className?: string;
}) {
  const reduce = useReducedMotion();
  if (reduce) return <div className={className}>{children}</div>;
  return (
    <motion.div className={className} variants={sectionVariants}>
      {children}
    </motion.div>
  );
}

interface AnimatedNumberProps {
  value: number;
  /** Seconds for the roll. */
  duration?: number;
  /**
   * Intl options, e.g. `{ notation: "compact" }`. Narrower than
   * `Intl.NumberFormatOptions` — number-flow excludes the notations it can't
   * animate (scientific, engineering).
   */
  format?: Format;
}

/**
 * Odometer-style number: each digit rolls independently rather than the whole
 * figure counting up.
 *
 * Backed by `@number-flow/react`, which registers a custom element guarded by
 * `BROWSER && typeof HTMLElement !== "undefined"`, so it's safe under SSR and
 * renders the real figure server-side (no layout shift on hydration). It also
 * honours `prefers-reduced-motion` itself via `respectMotionPreference`.
 */
export function AnimatedNumber({
  value,
  duration = 0.9,
  format,
}: AnimatedNumberProps) {
  return (
    <NumberFlow
      value={value}
      format={format}
      willChange
      transformTiming={{
        duration: duration * 1000,
        // Same settle curve as the rest of the page's motion.
        easing: `cubic-bezier(${EASE_OUT.join(",")})`,
      }}
    />
  );
}

/**
 * Fills to `percent` of its track once visible. Used under the KPI tiles as a
 * share-of-total indicator.
 */
export function ProgressSpark({
  percent,
  color,
  delay = 0.2,
}: {
  percent: number;
  color: string;
  delay?: number;
}) {
  const reduce = useReducedMotion();
  const clamped = Math.max(0, Math.min(100, percent));
  return (
    <div
      style={{
        height: 3,
        borderRadius: 999,
        background: "var(--mantine-color-gray-2)",
        overflow: "hidden",
      }}
    >
      <motion.div
        initial={reduce ? false : { width: 0 }}
        animate={{ width: `${clamped}%` }}
        transition={{ duration: 0.8, delay, ease: EASE_OUT }}
        style={{ height: "100%", borderRadius: 999, background: color }}
      />
    </div>
  );
}

export { motion, useReducedMotion };
