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

const DEFAULT_DEBOUNCE_MS = 600;

interface UseLiveDraftFieldOptions {
  remoteValue: string;
  commit: (value: string) => void | Promise<void>;
  debounceMs?: number;
}

export interface LiveDraftField {
  value: string;
  onChange: (value: string) => void;
  onFocus: () => void;
  onBlur: () => void;
}

/**
 * State for a multiplayer always-editable text field: local draft while
 * typing, debounced write-through, immediate flush on blur. Incoming remote
 * values are applied only while the field is unfocused, so collaborators'
 * snapshot updates never clobber in-progress typing (blur flushes pending
 * writes synchronously, so unfocused implies no pending draft). Concurrent
 * edits to the same field are last-write-wins.
 */
export function useLiveDraftField({
  remoteValue,
  commit,
  debounceMs = DEFAULT_DEBOUNCE_MS,
}: UseLiveDraftFieldOptions): LiveDraftField {
  const [value, setValue] = useState(remoteValue);
  const [focused, setFocused] = useState(false);

  // Sync from remote during render (derived-state pattern) unless the user
  // is editing the field.
  const [prevRemote, setPrevRemote] = useState(remoteValue);
  if (remoteValue !== prevRemote) {
    setPrevRemote(remoteValue);
    if (!focused) {
      setValue(remoteValue);
    }
  }

  // Written only in handlers; read by flush() so its identity stays stable.
  const valueRef = useRef(remoteValue);
  const pendingRef = useRef(false);
  const timerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
  const commitRef = useRef(commit);
  useEffect(() => {
    commitRef.current = commit;
  }, [commit]);

  const flush = useCallback(() => {
    clearTimeout(timerRef.current);
    timerRef.current = undefined;
    if (!pendingRef.current) return;
    pendingRef.current = false;
    void commitRef.current(valueRef.current);
  }, []);

  // Flush any pending write when the field unmounts (e.g. playback advances).
  useEffect(() => flush, [flush]);

  const onChange = useCallback(
    (next: string) => {
      valueRef.current = next;
      setValue(next);
      pendingRef.current = true;
      clearTimeout(timerRef.current);
      timerRef.current = setTimeout(flush, debounceMs);
    },
    [debounceMs, flush],
  );

  const onFocus = useCallback(() => {
    setFocused(true);
  }, []);

  const onBlur = useCallback(() => {
    setFocused(false);
    flush();
  }, [flush]);

  return { value, onChange, onFocus, onBlur };
}
