import {
  useEffect,
  useRef,
  useState,
  type CSSProperties,
} from 'react';
import { SPRINGS, prefersReducedMotion } from './utils.js';

export interface NumberFlowProps {
  /** Target value to animate to. */
  value: number;
  /** Tween duration in ms. Default 700. */
  duration?: number;
  /** Decimal places. Default 0. */
  decimals?: number;
  /** Optional formatter (overrides `decimals` + locale). */
  format?: (n: number) => string;
  /** Locale for default formatter. Default 'en-US'. */
  locale?: string;
  /** Prefix rendered before the number (e.g. '$'). */
  prefix?: string;
  /** Suffix rendered after the number (e.g. '%'). */
  suffix?: string;
  /** Bypass animation. */
  disableMotion?: boolean;
  className?: string;
  style?: CSSProperties;
}

/**
 * `<NumberFlow>` — smoothly tweens a number from its previous value to
 * the new one. Counts up, counts down, and respects
 * `prefers-reduced-motion` (snaps to final value).
 *
 * Pure RAF — no dependencies. Cancels in-flight tweens when `value`
 * changes mid-animation so the latest target always wins.
 */
export function NumberFlow({
  value,
  duration = 700,
  decimals = 0,
  format,
  locale = 'en-US',
  prefix,
  suffix,
  disableMotion,
  className,
  style,
}: NumberFlowProps) {
  const [display, setDisplay] = useState(value);
  const fromRef = useRef(value);
  const rafRef = useRef<number | null>(null);
  const reduced = disableMotion ?? prefersReducedMotion();

  useEffect(() => {
    if (reduced) {
      fromRef.current = value;
      setDisplay(value);
      return;
    }
    const from = fromRef.current;
    const start = performance.now();
    const tween = (now: number) => {
      const t = Math.min(1, (now - start) / duration);
      // Apply a snappy ease-out so the number doesn't feel linear.
      const eased = 1 - Math.pow(1 - t, 3);
      const current = from + (value - from) * eased;
      setDisplay(current);
      if (t < 1) {
        rafRef.current = requestAnimationFrame(tween);
      } else {
        fromRef.current = value;
        rafRef.current = null;
      }
    };
    rafRef.current = requestAnimationFrame(tween);
    return () => {
      if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
    };
  }, [value, duration, reduced]);

  const formatted = format
    ? format(display)
    : new Intl.NumberFormat(locale, {
        minimumFractionDigits: decimals,
        maximumFractionDigits: decimals,
      }).format(display);

  return (
    <span
      data-testid="bl-number-flow"
      data-target={value}
      className={className}
      style={{
        fontVariantNumeric: 'tabular-nums',
        transition: `color 200ms ${SPRINGS.snappy}`,
        ...style,
      }}
    >
      {prefix}
      {formatted}
      {suffix}
    </span>
  );
}
