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

/**
 * Animates a number from 0 → target using requestAnimationFrame with ease-out timing.
 * Animation triggers when the referenced element enters the viewport (IntersectionObserver).
 *
 * @param target  - The number to animate to. If it changes from 0 to a positive value,
 *                  the animation resets and re-triggers.
 * @param duration - Animation duration in ms (default 1200).
 * @returns `{ value, ref }` — current display value and a ref to attach to the observed element.
 */
export function useCountUp<T extends HTMLElement = HTMLElement>(
  target: number,
  duration = 1200,
): { value: number; ref: React.RefObject<T> } {
  const [value, setValue] = useState(0);
  const ref = useRef<T>(null);
  const rafId = useRef<number | null>(null);
  const hasAnimated = useRef(false);
  const isVisible = useRef(false);

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

    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry?.isIntersecting) {
          isVisible.current = true;
          // Trigger animation if we have a real target and haven't animated yet
          if (target > 0 && !hasAnimated.current) {
            startAnimation();
          }
        }
      },
      { threshold: 0.3 },
    );
    observer.observe(el);

    function startAnimation() {
      hasAnimated.current = true;
      const start = performance.now();

      function tick(now: number) {
        const elapsed = now - start;
        const t = Math.min(elapsed / duration, 1);
        // Ease-out cubic: 1 - (1 - t)^3
        const eased = 1 - Math.pow(1 - t, 3);
        setValue(Math.round(eased * target));

        if (t < 1) {
          rafId.current = requestAnimationFrame(tick);
        }
      }

      rafId.current = requestAnimationFrame(tick);
    }

    // If already visible when target arrives (e.g., stats loaded after scroll)
    if (isVisible.current && target > 0 && !hasAnimated.current) {
      startAnimation();
    }

    return () => {
      observer.disconnect();
      if (rafId.current !== null) {
        cancelAnimationFrame(rafId.current);
      }
    };
  }, [target, duration]);

  // Reset when target changes from 0 → positive (async data load)
  useEffect(() => {
    if (target === 0) {
      hasAnimated.current = false;
      setValue(0);
    }
  }, [target]);

  return { value, ref };
}
