"use client";

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

/** Length of the closing animation; keep in step with duration-200 in the markup. */
export const MODAL_EXIT_MS = 200;

/**
 * Open/close animation for a modal, driven by CSS classes rather than
 * framer-motion's AnimatePresence.
 *
 * framer-motion hands opacity to the browser's animation engine and, when an
 * exit finishes, drops that animation a frame before React removes the
 * element - long enough for a closed dialog to blink back at full opacity.
 * With a CSS transition the closed state is simply the element's class, so
 * there is nothing to fall back to.
 *
 * Returns:
 *   mounted - render the modal at all
 *   shown   - it is in its open position (off: faded, lowered, slightly small)
 *
 * Usage:
 *   const { mounted, shown } = useModalTransition(isOpen);
 *   if (!mounted) return null;
 *   <div className={shown ? "opacity-100" : "opacity-0"} />
 */
export function useModalTransition(isOpen: boolean) {
  const [mounted, setMounted] = useState(isOpen);
  const [shown, setShown] = useState(false);
  const exitTimer = useRef<ReturnType<typeof setTimeout> | null>(null);

  useEffect(() => {
    if (exitTimer.current) {
      clearTimeout(exitTimer.current);
      exitTimer.current = null;
    }

    if (isOpen) {
      setMounted(true);
      return;
    }

    // Closing: play the fade, then unmount. A timer rather than
    // transitionend, which never fires when reduced motion turns the
    // transition off, or while the tab is in the background.
    setShown(false);
    exitTimer.current = setTimeout(() => setMounted(false), MODAL_EXIT_MS + 50);
  }, [isOpen]);

  // Move to the open position only once the closed one has been painted;
  // switching classes in the same frame as mounting would skip the animation.
  useEffect(() => {
    if (!mounted || !isOpen) return;

    let second = 0;
    const first = requestAnimationFrame(() => {
      second = requestAnimationFrame(() => setShown(true));
    });

    return () => {
      cancelAnimationFrame(first);
      cancelAnimationFrame(second);
    };
  }, [mounted, isOpen]);

  useEffect(() => {
    return () => {
      if (exitTimer.current) clearTimeout(exitTimer.current);
    };
  }, []);

  return { mounted, shown };
}
