type MutableCurrent<T> = {
  current: T;
};

type AttachDocumentMouseDrag = (
  documentTarget: Document,
  onMove: (event: MouseEvent) => void,
  onUp: (event: MouseEvent) => void,
) => () => void;

type MultiDragStateLike<TMode extends string = string> = {
  selectedAllocationIds: string[];
  multiDragDaysDelta: number;
  isMultiDragging: boolean;
  multiDragMode: TMode;
};

type BeginAllocationMultiDragSessionParams<TState extends MultiDragStateLike, TMode extends string> = {
  startMouseX: number;
  dragMode: TMode;
  documentTarget: Document;
  cleanupRef: MutableCurrent<(() => void) | null>;
  stateRef: MutableCurrent<TState>;
  setState: (state: TState) => void;
  startState: (state: TState, dragMode: TMode) => TState;
  updateState: (state: TState, daysDelta: number) => TState | null;
  finalizeState: (state: TState) => TState;
  toDaysDelta: (deltaX: number) => number;
  onComplete: (daysDelta: number, finalState: TState) => void;
  attachDrag: AttachDocumentMouseDrag;
};

export function beginAllocationMultiDragSession<TState extends MultiDragStateLike, TMode extends string>({
  startMouseX,
  dragMode,
  documentTarget,
  cleanupRef,
  stateRef,
  setState,
  startState,
  updateState,
  finalizeState,
  toDaysDelta,
  onComplete,
  attachDrag,
}: BeginAllocationMultiDragSessionParams<TState, TMode>) {
  const initialState = startState(stateRef.current, dragMode);
  stateRef.current = initialState;
  setState(initialState);
  cleanupRef.current?.();

  function handleMove(event: MouseEvent) {
    const updated = updateState(stateRef.current, toDaysDelta(event.clientX - startMouseX));
    if (!updated) return;

    stateRef.current = updated;
    setState(updated);
  }

  function handleUp(event: MouseEvent) {
    cleanupRef.current?.();
    cleanupRef.current = null;

    const finalState = finalizeState(stateRef.current);
    stateRef.current = finalState;
    setState(finalState);

    const finalDelta = toDaysDelta(event.clientX - startMouseX);
    if (finalDelta !== 0) {
      onComplete(finalDelta, finalState);
    }
  }

  cleanupRef.current = attachDrag(documentTarget, handleMove, handleUp);
}
