import { useCallback, useEffect, useRef } from 'react';

/**
 * Keep a textarea/input uncontrolled, but commit its value to
 * an expensive parent state (Context, Redux, Jotai, etc.) only
 * after `delay` ms of silence.
 *
 * - No React state is updated while the user types
 * - The component that renders the textarea never re-renders
 *   until `delay` elapses
 */
export function useDebouncedCommit<
  T extends HTMLTextAreaElement | HTMLInputElement,
>(externalValue: string, commit: (v: string) => void, delay = 300) {
  const ref = useRef<T | null>(null);
  const latest = useRef<string>(externalValue);
  const timer = useRef<NodeJS.Timeout | null>(null);

  // Sync DOM ↔ external value when the latter changes from elsewhere
  useEffect(() => {
    if (
      ref.current &&
      document.activeElement !== ref.current &&
      ref.current.value !== externalValue
    ) {
      ref.current.value = externalValue;
      latest.current = externalValue;
    }
  }, [externalValue]);

  // Debounced commit
  const onInput = useCallback(
    (e: React.FormEvent<T>) => {
      latest.current = (e.target as T).value;
      if (timer.current) {
        clearTimeout(timer.current);
      }
      timer.current = setTimeout(() => commit(latest.current), delay);
    },
    [commit, delay]
  );

  // Clean up on unmount
  useEffect(
    () => () => {
      if (timer.current) clearTimeout(timer.current);
    },
    []
  );

  return { ref, onInput } as const;
}
