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

import logWebUserEvent from '@/logging/logWebUserEvent';

import { StudioContextType } from '../StudioContext';
import {
  copy,
  cut,
  duplicate,
  magneticCut,
  magneticDuplicate,
  magneticPaste,
  paste,
} from '../actions/copyPaste';
import deleteTimelineSelection from '../actions/deleteTimelineSelection';
import deleteTracks from '../actions/deleteTracks';
import magneticDeleteTimelineSelection from '../actions/magneticDeleteTimelineSelection';
import promoteTakeLane from '../actions/promoteTakeLane';
import reorderTracks from '../actions/reorderTracks';
import selectAll from '../actions/selectAll';
import splitTimelineSelection from '../actions/splitTimelineSelection';
import toggleClipFade from '../actions/toggleClipFade';
import toggleClipsMute from '../actions/toggleClipsMute';
import toggleTimelineLoop from '../actions/toggleTimelineLoop';
import toggleTrackMute from '../actions/toggleTrackMute';
import toggleTrackSolo from '../actions/toggleTrackSolo';
import {
  getSelectedClips,
  getSelectionEndBeats,
  getSelectionEndSeconds,
  getSelectionStartBeats,
  getSelectionStartSeconds,
  getTakeLanesById,
  getTracksAndExpandedTakeLanes,
} from '../selectors';
import { StudioProjectState } from '../types';

type KeyCommand = {
  key: string;
  modifiers?: {
    ctrl?: boolean;
    cmd?: boolean;
    shift?: boolean;
    alt?: boolean;
  };
  condition?: (state: StudioProjectState) => boolean;
} & (
  | {
      action: (
        state: StudioProjectState
      ) =>
        | StudioProjectState
        | ((prev: StudioProjectState) => StudioProjectState);
    }
  | {
      sideEffect: (state: StudioProjectState) => void;
    }
);

type KeyCommandOverride = KeyCommand & {
  id: string;
};

export default function useKeyCommands(
  studioContext: StudioContextType,
  overrides: KeyCommandOverride[] = []
) {
  const {
    state,
    setState,
    undo,
    redo,
    magnetMode,
    oneTrackMode,
    timelineController,
  } = studioContext;

  const { interactingInTimelineRef } = timelineController;

  const defaultKeyCommands: KeyCommand[] = useMemo(
    () => [
      {
        key: '0',
        action: (state) => {
          const selectedClips = getSelectedClips(state);
          const mute = selectedClips.some((clip) => clip.mute);
          return toggleClipsMute(
            getSelectedClips(state).map((clip) => clip.id),
            !mute
          );
        },
      },
      {
        key: 'ArrowUp',
        modifiers: {
          ctrl: true,
        },
        action: (state) => {
          const minSelectedTrackIndex = state.tracks.findIndex((track) =>
            state.selection.trackIds.includes(track.id)
          );
          const maxSelectedTrackIndex = state.tracks.findLastIndex((track) =>
            state.selection.trackIds.includes(track.id)
          );
          if (minSelectedTrackIndex === 0) return state;
          if (maxSelectedTrackIndex === -1) return state;

          return reorderTracks(
            state.tracks[minSelectedTrackIndex - 1].id,
            state.tracks[maxSelectedTrackIndex].id,
            false
          )(state);
        },
      },
      {
        key: 'ArrowDown',
        modifiers: {
          ctrl: true,
        },
        action: (state) => {
          const minSelectedTrackIndex = state.tracks.findIndex((track) =>
            state.selection.trackIds.includes(track.id)
          );
          const maxSelectedTrackIndex = state.tracks.findLastIndex((track) =>
            state.selection.trackIds.includes(track.id)
          );
          if (maxSelectedTrackIndex === state.tracks.length - 1) return state;
          if (minSelectedTrackIndex === -1) return state;
          return reorderTracks(
            state.tracks[maxSelectedTrackIndex + 1].id,
            state.tracks[minSelectedTrackIndex].id,
            true
          )(state);
        },
      },
      {
        key: 'ArrowUp',
        action: (state) => {
          const focusedTrackId = state.selection.focusedTrackId;
          if (!focusedTrackId) return state;
          const tracksAndTakeLanes = getTracksAndExpandedTakeLanes(state);
          const focusedTrackIndex = tracksAndTakeLanes.findIndex(
            (track) => track.id === focusedTrackId
          );
          if (focusedTrackIndex === 0) return state;
          return {
            ...state,
            selection: {
              ...state.selection,
              focusedTrackId: tracksAndTakeLanes[focusedTrackIndex - 1].id,
              trackIds: [tracksAndTakeLanes[focusedTrackIndex - 1].id],
            },
          };
        },
      },
      {
        key: 'ArrowDown',
        action: (state) => {
          const tracksAndTakeLanes = getTracksAndExpandedTakeLanes(state);
          const focusedTrackId = state.selection.focusedTrackId;
          if (!focusedTrackId) return state;
          const focusedTrackIndex = tracksAndTakeLanes.findIndex(
            (track) => track.id === focusedTrackId
          );
          if (focusedTrackIndex === tracksAndTakeLanes.length - 1) return state;
          return {
            ...state,
            selection: {
              ...state.selection,
              focusedTrackId: tracksAndTakeLanes[focusedTrackIndex + 1].id,
              trackIds: [tracksAndTakeLanes[focusedTrackIndex + 1].id],
            },
          };
        },
      },
      {
        key: 'f',
        modifiers: {
          ctrl: true,
        },
        sideEffect: () => {
          timelineController.setFollowMode((prev) => !prev);
        },
      },
      {
        key: 'Backspace',
        condition: (state) =>
          state.selection.focusedArea === 'tracks' &&
          !(oneTrackMode && state.tracks.length <= 1),
        action: (state) =>
          state.selection.trackIds.length > 0
            ? deleteTracks(state.selection.trackIds)
            : state,

        sideEffect: (state) => {
          logWebUserEvent({
            actionName: 'EditV3Delete',
            context: {
              editSessionId: studioContext.editSessionId,
              editingClipId: state.editClipId || 'MISSING_EDIT_CLIP_ID',
              trigger: 'keyboard',
            },
          });
        },
      },
      {
        key: 'Backspace',
        condition: (state) => state.selection.focusedArea === 'timeline',
        action: (state) => {
          if (
            Math.abs(state.selection.anchorBeats - state.selection.focusBeats) <
            0.000001
          ) {
            return state;
          }

          return magnetMode
            ? magneticDeleteTimelineSelection(state)
            : deleteTimelineSelection(state);
        },

        sideEffect: (state) => {
          logWebUserEvent({
            actionName: 'EditV3Delete',
            context: {
              editSessionId: studioContext.editSessionId,
              editingClipId: state.editClipId || 'MISSING_EDIT_CLIP_ID',
              trigger: 'keyboard',
            },
          });
        },
      },
      {
        key: 'Delete',
        condition: (state) =>
          state.selection.focusedArea === 'tracks' &&
          !(oneTrackMode && state.tracks.length <= 1),
        action: (state) =>
          state.selection.trackIds.length > 0
            ? deleteTracks(state.selection.trackIds)
            : state,

        sideEffect: (state) => {
          logWebUserEvent({
            actionName: 'EditV3Delete',
            context: {
              editSessionId: studioContext.editSessionId,
              editingClipId: state.editClipId || 'MISSING_EDIT_CLIP_ID',
              trigger: 'keyboard',
            },
          });
        },
      },
      {
        key: 'Delete',
        condition: (state) => state.selection.focusedArea === 'timeline',
        action: (state) => {
          if (
            Math.abs(state.selection.anchorBeats - state.selection.focusBeats) <
            0.000001
          ) {
            return state;
          }
          return magnetMode
            ? magneticDeleteTimelineSelection(state)
            : deleteTimelineSelection(state);
        },
        sideEffect: (state) => {
          logWebUserEvent({
            actionName: 'EditV3Delete',
            context: {
              editSessionId: studioContext.editSessionId,
              editingClipId: state.editClipId || 'MISSING_EDIT_CLIP_ID',
              trigger: 'keyboard',
            },
          });
        },
      },
      {
        key: 'c',
        modifiers: {
          ctrl: false,
          shift: false,
        },
        action: (state) => {
          return {
            ...state,
            metronome: {
              ...state.metronome,
              enabled: !state.metronome.enabled,
            },
          };
        },
      },
      {
        key: '!',
        modifiers: {
          shift: true,
        },
        sideEffect: () => {
          studioContext.setGridMultiplier((prev) => Math.max(prev / 2, 1 / 32));
        },
      },
      {
        key: '@',
        modifiers: {
          shift: true,
        },
        sideEffect: () => {
          studioContext.setGridMultiplier((prev) => Math.min(prev * 2, 32));
        },
      },
      {
        key: 'l',
        modifiers: {
          ctrl: true,
        },
        action: toggleTimelineLoop,
        sideEffect: () => {},
      },
      {
        key: 'F',
        modifiers: {
          shift: true,
        },
        action: toggleClipFade,
        sideEffect: () => {},
      },
      {
        key: 'a',
        modifiers: {
          ctrl: true,
        },
        condition: (state) => state.selection.focusedArea === 'timeline',
        action: (state) => selectAll(state),
        sideEffect: (state) => {
          logWebUserEvent({
            actionName: 'EditV3Selection',
            context: {
              editSessionId: studioContext.editSessionId,
              editingClipId: state.editClipId || 'MISSING_EDIT_CLIP_ID',
              startBeats: getSelectionStartBeats(state),
              endBeats: getSelectionEndBeats(state),
              startSeconds: getSelectionStartSeconds(state),
              endSeconds: getSelectionEndSeconds(state),
              trigger: 'keyboard',
            },
          });
        },
      },
      {
        key: 'e',
        modifiers: {
          ctrl: true,
        },
        condition: (state) => state.selection.focusedArea === 'timeline',
        action: (state) => splitTimelineSelection(state),
        sideEffect: (state) => {
          logWebUserEvent({
            actionName: 'EditV3Split',
            context: {
              editSessionId: studioContext.editSessionId,
              editingClipId: state.editClipId || 'MISSING_EDIT_CLIP_ID',
              trigger: 'keyboard',
            },
          });
        },
      },
      {
        key: 'd',
        modifiers: {
          ctrl: true,
        },
        action: (state) =>
          magnetMode ? magneticDuplicate(state) : duplicate(state),
        sideEffect: (state) => {
          logWebUserEvent({
            actionName: 'EditV3Duplicate',
            context: {
              editSessionId: studioContext.editSessionId,
              editingClipId: state.editClipId || 'MISSING_EDIT_CLIP_ID',
              trigger: 'keyboard',
            },
          });
        },
      },
      {
        key: 'c',
        modifiers: {
          ctrl: true,
        },
        action: (state) => copy(state),
        sideEffect: (state) => {
          logWebUserEvent({
            actionName: 'EditV3Copy',
            context: {
              editSessionId: studioContext.editSessionId,
              editingClipId: state.editClipId || 'MISSING_EDIT_CLIP_ID',
              trigger: 'keyboard',
            },
          });
        },
      },
      {
        key: 'x',
        modifiers: {
          ctrl: true,
        },
        action: (state) => (magnetMode ? magneticCut(state) : cut(state)),
        sideEffect: (state) => {
          logWebUserEvent({
            actionName: 'EditV3Cut',
            context: {
              editSessionId: studioContext.editSessionId,
              editingClipId: state.editClipId || 'MISSING_EDIT_CLIP_ID',
              trigger: 'keyboard',
            },
          });
        },
      },
      {
        key: 'v',
        modifiers: {
          ctrl: true,
        },
        action: (state) => {
          return magnetMode ? magneticPaste(state) : paste(state);
        },
        sideEffect: (state) => {
          logWebUserEvent({
            actionName: 'EditV3Paste',
            context: {
              editSessionId: studioContext.editSessionId,
              editingClipId: state.editClipId || 'MISSING_EDIT_CLIP_ID',
              trigger: 'keyboard',
            },
          });
        },
      },
      {
        key: 'z',
        modifiers: {
          ctrl: true,
          shift: true,
        },
        sideEffect: (state) => {
          redo();
          logWebUserEvent({
            actionName: 'EditV3Redo',
            context: {
              editSessionId: studioContext.editSessionId,
              editingClipId: state.editClipId || 'MISSING_EDIT_CLIP_ID',
              trigger: 'keyboard',
            },
          });
        },
      },
      {
        key: 'z',
        modifiers: {
          ctrl: true,
        },
        sideEffect: (state) => {
          undo();
          logWebUserEvent({
            actionName: 'EditV3Undo',
            context: {
              editSessionId: studioContext.editSessionId,
              editingClipId: state.editClipId || 'MISSING_EDIT_CLIP_ID',
              trigger: 'keyboard',
            },
          });
        },
      },
      {
        key: 'y',
        modifiers: {
          ctrl: true,
        },
        sideEffect: (state) => {
          redo();
          logWebUserEvent({
            actionName: 'EditV3Redo',
            context: {
              editSessionId: studioContext.editSessionId,
              editingClipId: state.editClipId || 'MISSING_EDIT_CLIP_ID',
              trigger: 'keyboard',
            },
          });
        },
      },
      {
        key: 'Enter',
        condition: (state) => state.selection.focusedArea === 'timeline',
        action: (state) => {
          if (state.selection.trackIds.length !== 1) return state;
          const selectedTakeLane =
            getTakeLanesById(state)[state.selection.trackIds[0]];
          if (!selectedTakeLane) return state;
          console.log(selectedTakeLane);
          return promoteTakeLane(
            selectedTakeLane.id,
            getSelectionStartBeats(state),
            getSelectionEndBeats(state)
          )(state);
        },
      },
      {
        key: 's',
        modifiers: {
          ctrl: true,
          shift: true,
        },
        action: (state) =>
          !oneTrackMode && state.selection.focusedTrackId
            ? toggleTrackSolo(state.selection.focusedTrackId, false)(state)
            : state,
      },
      {
        key: 'm',
        modifiers: {
          ctrl: true,
          shift: true,
        },
        action: (state) =>
          !oneTrackMode && state.selection.focusedTrackId
            ? toggleTrackMute(state.selection.focusedTrackId)(state)
            : state,
      },
    ],
    [undo, redo, magnetMode, oneTrackMode]
  );

  const handleKeyDown = useCallback(
    (event: KeyboardEvent) => {
      if (
        document.activeElement instanceof HTMLTextAreaElement ||
        document.activeElement instanceof HTMLInputElement ||
        (document.activeElement instanceof HTMLElement &&
          document.activeElement.getAttribute('contenteditable') === 'true')
      ) {
        return;
      }

      const key = event.key;
      const modifiers = {
        ctrl: event.ctrlKey || event.metaKey,
        shift: event.shiftKey,
        alt: event.altKey,
      };

      if (
        !interactingInTimelineRef.current &&
        ['c', 'v', 'x'].includes(key) &&
        modifiers.ctrl
      ) {
        return;
      }

      // Check overrides first
      const override = overrides.find(
        (cmd) =>
          cmd.key === key &&
          (!cmd.modifiers ||
            Object.entries(cmd.modifiers).every(
              ([mod, value]) =>
                modifiers[mod as keyof typeof modifiers] === value
            )) &&
          (cmd.condition ? cmd.condition(state) : true)
      );

      if (override) {
        event.preventDefault();
        event.stopPropagation();
        if ('action' in override) {
          setState(override.action(state));
        }
        if ('sideEffect' in override) {
          override.sideEffect(studioContext.stateRef.current);
        }
        return;
      }

      // Then check default commands
      const command = defaultKeyCommands.find(
        (cmd) =>
          cmd.key === key &&
          (!cmd.modifiers ||
            Object.entries(cmd.modifiers).every(
              ([mod, value]) =>
                modifiers[mod as keyof typeof modifiers] === value
            )) &&
          (cmd.condition ? cmd.condition(state) : true)
      );

      if (command) {
        event.preventDefault();
        event.stopPropagation();
        if ('action' in command) {
          setState(command.action(state));
        }
        if ('sideEffect' in command) {
          command.sideEffect(studioContext.stateRef.current);
        }
      }
    },
    [state, setState, overrides]
  );

  useEffect(() => {
    document.addEventListener('keydown', handleKeyDown);
    return () => {
      document.removeEventListener('keydown', handleKeyDown);
    };
  }, [handleKeyDown]);
}
