"use client";

import * as React from "react";
import {
  type SpringOptions,
  type UseInViewOptions,
  useInView,
  useMotionValue,
  useSpring,
} from "motion/react";

interface CountingNumberProps extends React.HTMLAttributes<HTMLSpanElement> {
  number: number;
  fromNumber?: number;
  padStart?: boolean;
  inView?: boolean;
  inViewMargin?: UseInViewOptions["margin"];
  inViewOnce?: boolean;
  decimalSeparator?: string;
  transition?: SpringOptions;
  decimalPlaces?: number;
}

const CountingNumber = React.forwardRef<HTMLSpanElement, CountingNumberProps>(
  (
    {
      number,
      fromNumber = 0,
      padStart = false,
      inView = false,
      inViewMargin = "0px",
      inViewOnce = true,
      decimalSeparator = ".",
      transition = { stiffness: 90, damping: 50 },
      decimalPlaces = 0,
      className,
      ...props
    },
    ref
  ) => {
    const localRef = React.useRef<HTMLSpanElement>(null);
    React.useImperativeHandle(ref, () => localRef.current as HTMLSpanElement);

    const numberStr = number.toString();
    const decimals =
      typeof decimalPlaces === "number"
        ? decimalPlaces
        : numberStr.includes(".")
        ? numberStr.split(".")[1].length
        : 0;

    const motionVal = useMotionValue(fromNumber);
    const springVal = useSpring(motionVal, transition);
    const inViewResult = useInView(localRef, {
      once: inViewOnce,
      margin: inViewMargin,
    });
    const isInView = !inView || inViewResult;

    React.useEffect(() => {
      if (isInView) motionVal.set(number);
    }, [isInView, number, motionVal]);

    React.useEffect(() => {
      const unsubscribe = springVal.on("change", (latest) => {
        if (localRef.current) {
          let formatted =
            decimals > 0
              ? latest.toFixed(decimals)
              : Math.round(latest).toString();

          if (decimals > 0) {
            formatted = formatted.replace(".", decimalSeparator);
          }

          if (padStart) {
            const finalIntLength = Math.floor(Math.abs(number)).toString()
              .length;
            const [intPart, fracPart] = formatted.split(decimalSeparator);
            const paddedInt = intPart.padStart(finalIntLength, "0");
            formatted = fracPart
              ? `${paddedInt}${decimalSeparator}${fracPart}`
              : paddedInt;
          }

          localRef.current.textContent = formatted;
        }
      });
      return () => unsubscribe();
    }, [springVal, decimals, padStart, number, decimalSeparator]);

    const finalIntLength = Math.floor(Math.abs(number)).toString().length;
    const initialText = padStart
      ? "0".padStart(finalIntLength, "0") +
        (decimals > 0 ? decimalSeparator + "0".repeat(decimals) : "")
      : "0" + (decimals > 0 ? decimalSeparator + "0".repeat(decimals) : "");

    return (
      <span ref={localRef} className={className} {...props}>
        {initialText}
      </span>
    );
  }
);

CountingNumber.displayName = "CountingNumber";

export { CountingNumber, type CountingNumberProps };
