"use client";

import { useEffect, useRef } from "react";
import {
  animate,
  useInView,
  useReducedMotion,
} from "motion/react";
import { formatNumber } from "@/lib/utils";

/**
 * Odometer for the capacity figures. A mill's numbers are the argument, so they
 * get to count themselves up when they come into view.
 */
export function CountUp({
  to,
  from = 0,
  duration = 1.9,
  suffix = "",
  prefix = "",
  separator = true,
  className,
}: {
  to: number;
  from?: number;
  duration?: number;
  suffix?: string;
  prefix?: string;
  separator?: boolean;
  className?: string;
}) {
  const ref = useRef<HTMLSpanElement>(null);
  const started = useRef(false);
  const inView = useInView(ref, { once: true, amount: 0.6 });
  const reduced = useReducedMotion();

  const format = (n: number) =>
    separator ? formatNumber(Math.round(n)) : String(Math.round(n));

  // Wind the figure back to its start once on mount, so the server rendered
  // final value never animates down from itself.
  //
  // A safety net rides along with it: if the figure has still not been asked to
  // animate after a few seconds, the final value is restored. An observer that
  // never fires must not be able to strand a capacity figure on zero, which is
  // the one failure here that would actively misinform a reader.
  useEffect(() => {
    if (reduced) return;

    const node = ref.current;
    if (node) node.textContent = `${prefix}${format(from)}${suffix}`;

    const rescue = window.setTimeout(() => {
      if (started.current) return;
      const current = ref.current;
      if (current) current.textContent = `${prefix}${format(to)}${suffix}`;
    }, 4000);

    return () => window.clearTimeout(rescue);
    // Runs once, deliberately.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

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

    if (reduced || !inView) return;

    started.current = true;

    const controls = animate(from, to, {
      duration,
      ease: [0.16, 1, 0.3, 1],
      onUpdate: (value) => {
        node.textContent = `${prefix}${format(value)}${suffix}`;
      },
    });

    return () => controls.stop();
    // format is derived from separator, which is covered.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [inView, reduced, from, to, duration, prefix, suffix, separator]);

  return (
    <span ref={ref} className={className}>
      {/* Server rendered value, so the final number is in the HTML for crawlers
          and is what a user without JS sees. */}
      {`${prefix}${format(to)}${suffix}`}
    </span>
  );
}
