"use client";

import { animate, useInView, useReducedMotion } from "framer-motion";
import { useEffect, useRef, useState } from "react";

type Props = {
  /** The number to land on. */
  value: number;
  /** Where the count starts. A year should roll a few digits, not run up
   *  from zero, so the caller sets its own starting point. */
  from?: number;
  /** Seconds. */
  duration?: number;
  className?: string;
};

/**
 * Counts up to a number once, the first time it scrolls into view.
 *
 * The animated digits are hidden from assistive tech and the real value is
 * exposed alongside them, so a screen reader announces "3" rather than every
 * intermediate frame. Honours `prefers-reduced-motion`: the final value is
 * rendered immediately.
 */
export function CountUp({
  value,
  from = 0,
  duration = 1.4,
  className = "",
}: Props) {
  const ref = useRef<HTMLSpanElement>(null);
  const inView = useInView(ref, { once: true, margin: "-80px" });
  const reduced = useReducedMotion();
  const [count, setCount] = useState(from);

  useEffect(() => {
    if (reduced || !inView) return;

    const controls = animate(from, value, {
      duration,
      ease: [0.22, 1, 0.36, 1],
      onUpdate: (latest) => setCount(Math.round(latest)),
    });

    return () => controls.stop();
  }, [inView, reduced, from, value, duration]);

  // Derived rather than stored: with reduced motion there is no animation to
  // run, so the final value is simply what renders.
  const display = reduced ? value : count;

  return (
    <span ref={ref} className={`tabular-nums ${className}`}>
      <span aria-hidden>{display}</span>
      <span className="sr-only">{value}</span>
    </span>
  );
}
