"use client";

import { useEffect, useRef, useState } from "react";

type Props = {
  /** Number to count to. */
  to: number;
  prefix?: string;
  suffix?: string;
  /** en-IN grouping (25,000) unless false. */
  grouped?: boolean;
  durationMs?: number;
};

const formatter = new Intl.NumberFormat("en-IN");

/**
 * Counts up to `to` once it scrolls into view. Server/no-JS render shows the
 * final value, so the number is always correct for crawlers and screen
 * readers (the animated copy is aria-hidden).
 */
export default function CountUp({ to, prefix = "", suffix = "", grouped = true, durationMs = 1600 }: Props) {
  const ref = useRef<HTMLSpanElement>(null);
  const [value, setValue] = useState(to);

  useEffect(() => {
    const el = ref.current;
    if (!el || typeof IntersectionObserver === "undefined") return;
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;

    let frame = 0;
    const observer = new IntersectionObserver(
      (entries) => {
        if (!entries.some((e) => e.isIntersecting)) return;
        observer.disconnect();
        const start = performance.now();
        const tick = (now: number) => {
          const t = Math.min(1, (now - start) / durationMs);
          const eased = 1 - Math.pow(1 - t, 3);
          setValue(Math.round(to * eased));
          if (t < 1) frame = requestAnimationFrame(tick);
        };
        setValue(0);
        frame = requestAnimationFrame(tick);
      },
      { threshold: 0.4 }
    );
    observer.observe(el);
    return () => {
      observer.disconnect();
      cancelAnimationFrame(frame);
    };
  }, [to, durationMs]);

  const text = `${prefix}${grouped ? formatter.format(value) : value}${suffix}`;
  const final = `${prefix}${grouped ? formatter.format(to) : to}${suffix}`;

  return (
    <span ref={ref}>
      <span aria-hidden className="tabular-nums">
        {text}
      </span>
      <span className="sr-only">{final}</span>
    </span>
  );
}
