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

export type InFlightDrag = {
  type: 'clip' | 'clip-edge' | 'selection' | 'selection-edge';
  id: string;
  delta: number;
};

export const inFlightDragKeys = {
  clipTrack: (clipId: string) => `clipTrack-${clipId}`,
  clip: (clipId: string) => `clip-${clipId}`,
  clipGain: (clipId: string) => `clipGain-${clipId}`,
  clipFade: (clipId: string, inOrOut: 'in' | 'out') =>
    `clipFade-${clipId}-${inOrOut}`,
  selection: () => 'selectionSelection',
  selectionEdge: (edge: 'anchor' | 'focus') => `selectionEdge-${edge}`,
  selectionTrackDelta: () => `selectionTrackDelta`, // for moving a selection between tracks
  selectionTrackRange: () => 'selectionTrackRange', // for stretching a selection across tracks
  clipEdge: (clipId: string, edge: 'start' | 'end') =>
    `clipEdge-${clipId}-${edge}`,
  timelineLoopEdge: (edge: 'start' | 'end') => `timelineLoopEdge-${edge}`,
  gapEnd: (gapStart: number, trackId: string) =>
    `gapEnd-${gapStart}-${trackId}`,
  songFadeIn: () => 'songFadeIn',
  songFadeOut: () => 'songFadeOut',
  contextWindow: () => 'contextWindow',
};

export function useInFlightDrags(frameCountRef: { current: number }) {
  const inFlightDragsRef = useRef<Record<string, number>>({});
  const suspendedDragsRef = useRef<Record<string, number>>({});
  const [isAnyDragging, setIsAnyDragging] = useState(false);
  const get = useCallback(
    (key: string) => inFlightDragsRef.current[key] || 0,
    []
  );
  const hasMatching = useCallback(
    (fn: (key: string) => boolean) =>
      Object.keys(inFlightDragsRef.current).some((key) => fn(key)),
    []
  );
  const has = useCallback(
    (key: string) => inFlightDragsRef.current[key] !== undefined,
    []
  );
  const suspendAll = useCallback(() => {
    if (Object.keys(inFlightDragsRef.current).length === 0) {
      return;
    }
    suspendedDragsRef.current = { ...inFlightDragsRef.current };
    inFlightDragsRef.current = {};
  }, []);
  const resumeAll = useCallback(() => {
    if (Object.keys(suspendedDragsRef.current).length === 0) {
      return;
    }
    inFlightDragsRef.current = { ...suspendedDragsRef.current };
    suspendedDragsRef.current = {};
  }, []);
  const update = useCallback((key: string, delta: number) => {
    inFlightDragsRef.current[key] = delta;
    frameCountRef.current++;
    setIsAnyDragging(true);
  }, []);
  const finish = useCallback((key: string) => {
    delete inFlightDragsRef.current[key];
    setIsAnyDragging(Object.keys(inFlightDragsRef.current).length > 0);
  }, []);
  const finishAll = useCallback(() => {
    suspendedDragsRef.current = {};
    inFlightDragsRef.current = {};
    setIsAnyDragging(false);
  }, []);

  return useMemo(
    () => ({
      suspendAll,
      resumeAll,
      update,
      finish,
      finishAll,
      has,
      hasMatching,
      get,
      isAnyDragging,
    }),
    [
      isAnyDragging,
      get,
      has,
      hasMatching,
      suspendAll,
      resumeAll,
      update,
      finish,
      finishAll,
    ]
  );
}
