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

interface CountUpProps {
  value: string;
  durationMs?: number;
}

interface Parsed {
  target: number;
  decimals: number;
  separator: string;
  suffix: string;
}

function parseValue(value: string): Parsed | null {
  const trimmed = value.trim();
  const spaceIdx = trimmed.indexOf(" ");
  const numberToken = spaceIdx === -1 ? trimmed : trimmed.slice(0, spaceIdx);
  const suffix = spaceIdx === -1 ? "" : trimmed.slice(spaceIdx);

  const sepMatch = numberToken.match(/[^0-9]/);
  const separator = sepMatch ? sepMatch[0] : "";
  const decimals = separator ? numberToken.split(separator)[1]?.length ?? 0 : 0;

  const normalized = separator ? numberToken.replace(separator, ".") : numberToken;
  const target = parseFloat(normalized);
  if (!Number.isFinite(target)) return null;

  return { target, decimals, separator, suffix };
}

function format(current: number, { decimals, separator, suffix }: Parsed): string {
  let num = current.toFixed(decimals);
  if (decimals > 0 && separator) {
    num = num.replace(".", separator);
  }
  return num + suffix;
}

export function CountUp({ value, durationMs = 1600 }: CountUpProps) {
  const reduce = useReducedMotion();
  const ref = useRef<HTMLSpanElement>(null);
  const inView = useInView(ref, { once: true, amount: 0.3 });
  const parsed = parseValue(value);

  const [display, setDisplay] = useState<string>(() =>
    parsed ? format(0, parsed) : value
  );

  useEffect(() => {
    if (!parsed) {
      setDisplay(value);
      return;
    }
    if (reduce) {
      setDisplay(format(parsed.target, parsed));
      return;
    }
    if (!inView) {
      setDisplay(format(0, parsed));
      return;
    }

    let raf = 0;
    let start = 0;
    const tick = (now: number) => {
      if (!start) start = now;
      const elapsed = now - start;
      const progress = Math.min(elapsed / durationMs, 1);
      const eased = 1 - Math.pow(1 - progress, 3);
      setDisplay(format(parsed.target * eased, parsed));
      if (progress < 1) {
        raf = requestAnimationFrame(tick);
      } else {
        setDisplay(format(parsed.target, parsed));
      }
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [inView, reduce, value, durationMs]);

  return <span ref={ref}>{display}</span>;
}
