import { useCallback, useEffect, useRef, useState } from 'react';
import { flushSync } from 'react-dom';

interface UsePressedStateOptions {
  /** How long the pressed state should remain after mouseup/mouseleave. */
  releaseDelayMs?: number;
  /** preventDefault on mousedown — needed when an adjacent input must keep focus. */
  preventDefaultOnMouseDown?: boolean;
  /** stopPropagation on mousedown — true by default; rarely worth disabling. */
  stopPropagationOnMouseDown?: boolean;
}

interface PressedStateHandlers {
  onMouseDown: (e: React.MouseEvent) => void;
  onMouseUp: () => void;
  onMouseLeave: () => void;
}

interface UsePressedStateReturn {
  isPressed: boolean;
  handlers: PressedStateHandlers;
  /**
   * Defer a callback by releaseDelayMs so the pressed colour has time to paint
   * before the callback (e.g. a navigation) tears the button down. Cancelled
   * automatically on unmount.
   */
  runDelayed: (cb: () => void) => void;
}

// Press feedback that survives React 18 batching and old-Firefox :active quirks.
// flushSync commits the pressed state synchronously so the browser paints it
// before any follow-up state change. A trailing release timer guarantees the
// press colour stays on screen long enough for one transition cycle.
export function usePressedState({
  releaseDelayMs = 150,
  preventDefaultOnMouseDown = false,
  stopPropagationOnMouseDown = true,
}: UsePressedStateOptions = {}): UsePressedStateReturn {
  const [isPressed, setIsPressed] = useState(false);
  const releaseTimerRef = useRef<NodeJS.Timeout | null>(null);
  const delayedActionTimerRef = useRef<NodeJS.Timeout | null>(null);

  const clearReleaseTimer = useCallback(() => {
    if (releaseTimerRef.current) {
      clearTimeout(releaseTimerRef.current);
      releaseTimerRef.current = null;
    }
  }, []);

  const clearDelayedActionTimer = useCallback(() => {
    if (delayedActionTimerRef.current) {
      clearTimeout(delayedActionTimerRef.current);
      delayedActionTimerRef.current = null;
    }
  }, []);

  const scheduleRelease = useCallback(() => {
    clearReleaseTimer();
    releaseTimerRef.current = setTimeout(() => {
      setIsPressed(false);
      releaseTimerRef.current = null;
    }, releaseDelayMs);
  }, [clearReleaseTimer, releaseDelayMs]);

  const onMouseDown = useCallback(
    (e: React.MouseEvent) => {
      if (preventDefaultOnMouseDown) e.preventDefault();
      if (stopPropagationOnMouseDown) e.stopPropagation();
      clearReleaseTimer();
      flushSync(() => {
        setIsPressed(true);
      });
    },
    [preventDefaultOnMouseDown, stopPropagationOnMouseDown, clearReleaseTimer]
  );

  const runDelayed = useCallback(
    (cb: () => void) => {
      clearDelayedActionTimer();
      delayedActionTimerRef.current = setTimeout(() => {
        delayedActionTimerRef.current = null;
        cb();
      }, releaseDelayMs);
    },
    [clearDelayedActionTimer, releaseDelayMs]
  );

  useEffect(() => {
    return () => {
      clearReleaseTimer();
      clearDelayedActionTimer();
    };
  }, [clearReleaseTimer, clearDelayedActionTimer]);

  return {
    isPressed,
    handlers: {
      onMouseDown,
      onMouseUp: scheduleRelease,
      onMouseLeave: scheduleRelease,
    },
    runDelayed,
  };
}
