'use client';

import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
import { cn } from '@/lib/cn';

export interface AnimatedNumberProps {
  value: number;
  formatValue?: (value: number) => string;
  durationMs?: number;
  className?: string;
  ariaLabel?: string;
  testId?: string;
  reserveValue?: number;
}

function subscribeToReducedMotion(onChange: () => void): () => void {
  if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return () => {};
  const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
  mediaQuery.addEventListener('change', onChange);
  return () => mediaQuery.removeEventListener('change', onChange);
}

function getReducedMotionSnapshot(): boolean {
  return (
    typeof window !== 'undefined' &&
    window.matchMedia?.('(prefers-reduced-motion: reduce)').matches === true
  );
}

function defaultFormatValue(value: number): string {
  return Math.round(value).toString();
}

function countDigits(value: number): number {
  return Math.max(1, Math.abs(Math.trunc(value)).toString().length);
}

export function AnimatedNumber({
  value,
  formatValue = defaultFormatValue,
  durationMs = 420,
  className,
  ariaLabel,
  testId,
  reserveValue,
}: AnimatedNumberProps) {
  const [displayValue, setDisplayValue] = useState(value);
  const previousValueRef = useRef(value);
  const frameRef = useRef<number | null>(null);
  const reducedMotion = useSyncExternalStore(
    subscribeToReducedMotion,
    getReducedMotionSnapshot,
    () => false,
  );

  useEffect(() => {
    const from = previousValueRef.current;
    previousValueRef.current = value;

    if (from === value) {
      setDisplayValue(value);
      return;
    }

    if (reducedMotion) {
      return;
    }

    const start = performance.now();

    const tick = (now: number) => {
      const elapsed = now - start;
      const progress = Math.min(1, elapsed / durationMs);
      const eased = 1 - Math.pow(1 - progress, 3);
      setDisplayValue(from + (value - from) * eased);
      if (progress < 1) {
        frameRef.current = window.requestAnimationFrame(tick);
      } else {
        frameRef.current = null;
      }
    };

    frameRef.current = window.requestAnimationFrame(tick);

    return () => {
      if (frameRef.current !== null) {
        window.cancelAnimationFrame(frameRef.current);
        frameRef.current = null;
      }
    };
  }, [durationMs, reducedMotion, value]);

  const formattedValue = formatValue(reducedMotion ? value : displayValue);
  const widthProbe = useMemo(() => {
    const maxDigits = Math.max(countDigits(value), countDigits(reserveValue ?? 0));
    const sign = value < 0 || (reserveValue ?? 0) < 0 ? '-' : '';
    return formatValue(Number(`${sign}${'9'.repeat(maxDigits)}`));
  }, [formatValue, reserveValue, value]);

  return (
    <span
      aria-label={ariaLabel}
      className={cn('relative inline-grid tabular-nums', className)}
      data-testid={testId}
      data-fx="odometer"
    >
      <span
        aria-hidden="true"
        data-testid="animated-number-width-probe"
        className="invisible col-start-1 row-start-1"
      >
        {widthProbe}
      </span>
      <span className="col-start-1 row-start-1" data-animated-number-value="true">
        {formattedValue}
      </span>
    </span>
  );
}
