"use client";

/**
 * CountUp — animates a number from 0 to its target when it scrolls into view.
 * Uses GSAP + ScrollTrigger, formats with optional prefix/suffix/decimals and
 * tabular figures. Under reduced-motion it renders the final value immediately.
 * The full value is also written to the DOM on mount so it is correct even if
 * JS/animation never runs (accessible + SSR-safe).
 */

import { useEffect, useRef } from "react";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";

gsap.registerPlugin(ScrollTrigger);

type Props = {
  value: number;
  prefix?: string;
  suffix?: string;
  decimals?: number;
  className?: string;
};

export default function CountUp({
  value,
  prefix = "",
  suffix = "",
  decimals = 0,
  className = "",
}: Props) {
  const ref = useRef<HTMLSpanElement>(null);

  const format = (n: number) =>
    `${prefix}${n.toLocaleString("en-US", {
      minimumFractionDigits: decimals,
      maximumFractionDigits: decimals,
    })}${suffix}`;

  useEffect(() => {
    const el = ref.current;
    if (!el) return;

    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
      el.textContent = format(value);
      return;
    }

    const obj = { n: 0 };
    el.textContent = format(0);

    const ctx = gsap.context(() => {
      gsap.to(obj, {
        n: value,
        duration: 2,
        ease: "power2.out",
        scrollTrigger: { trigger: el, start: "top 85%", once: true },
        onUpdate: () => {
          el.textContent = format(obj.n);
        },
      });
    }, el);

    return () => ctx.revert();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [value, prefix, suffix, decimals]);

  // Render full value at SSR/first paint; the effect resets to 0 then animates.
  return (
    <span ref={ref} className={className}>
      {format(value)}
    </span>
  );
}
