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

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

type MultiSelectSessionState = {
  isSelecting: boolean;
};

type CompleteMultiSelectResult<TState> = {
  nextState: TState;
};

type BeginCanvasMultiSelectSessionParams<TState extends MultiSelectSessionState> = {
  clientX: number;
  clientY: number;
  documentTarget: Document;
  cleanupRef: MutableCurrent<(() => void) | null>;
  stateRef: MutableCurrent<TState>;
  setState: (state: TState) => void;
  createInitialState: (clientX: number, clientY: number) => TState;
  updateState: (state: TState, clientX: number, clientY: number) => TState;
  completeState: (
    state: TState,
    clientX: number,
    clientY: number,
    initialState: TState,
  ) => CompleteMultiSelectResult<TState>;
  initialState: TState;
  attachDrag: AttachDocumentMouseDrag;
};

export function beginCanvasMultiSelectSession<TState extends MultiSelectSessionState>({
  clientX,
  clientY,
  documentTarget,
  cleanupRef,
  stateRef,
  setState,
  createInitialState,
  updateState,
  completeState,
  initialState,
  attachDrag,
}: BeginCanvasMultiSelectSessionParams<TState>) {
  const nextState = createInitialState(clientX, clientY);
  stateRef.current = nextState;
  setState(nextState);
  cleanupRef.current?.();

  function handleMove(event: MouseEvent) {
    const currentState = stateRef.current;
    if (!currentState.isSelecting) return;

    const updated = updateState(currentState, event.clientX, event.clientY);
    stateRef.current = updated;
    setState(updated);
  }

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

    const currentState = stateRef.current;
    if (!currentState.isSelecting) return;

    const result = completeState(currentState, event.clientX, event.clientY, initialState);
    stateRef.current = result.nextState;
    setState(result.nextState);
  }

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