import type { MutableRefObject } from 'react'
import type { SidebarFilter } from '../types'
import {
  APP_COMMAND_DEFINITIONS,
  type AppCommandId,
  type AppCommandDefinition,
} from './appCommandCatalog'
import type { ViewMode } from './useViewMode'
import type { NoteListMultiSelectionCommands } from '../components/note-list/multiSelectionCommands'

export const APP_COMMAND_EVENT_NAME = 'laputa:dispatch-command'

export {
  APP_COMMAND_IDS,
  findShortcutCommandId,
  findShortcutCommandIdForEvent,
  isAppCommandId,
  isNativeMenuCommandId,
} from './appCommandCatalog'
export type { AppCommandDefinition, AppCommandId, AppCommandShortcutCombo } from './appCommandCatalog'

export type AppCommandDispatchSource =
  | 'direct'
  | 'renderer-keyboard'
  | 'native-menu'
  | 'app-event'

type SuppressedShortcutSource = Extract<AppCommandDispatchSource, 'renderer-keyboard'>

export interface AppCommandHandlers {
  onSetViewMode: (mode: ViewMode) => void
  onCreateNote: () => void
  onCreateType?: () => void
  onQuickOpen: () => void
  onSave: () => void
  onOpenSettings: () => void
  onToggleInspector: () => void
  onCommandPalette: () => void
  onZoomIn: () => void
  onZoomOut: () => void
  onZoomReset: () => void
  onToggleOrganized?: (path: string) => void
  onToggleFavorite?: (path: string) => void
  onArchiveNote: (path: string) => void
  onDeleteNote: (path: string) => void
  onFindInNote?: () => void
  onUndo?: () => void
  onRedo?: () => void
  onReplaceInNote?: () => void
  onPastePlainText: () => void
  onSearch: () => void
  onToggleRawEditor?: () => void
  onToggleDiff?: () => void
  onToggleAIChat?: () => void
  onToggleTableOfContents?: () => void
  onExportNoteAsPdf?: () => void
  onGoBack?: () => void
  onGoForward?: () => void
  onCheckForUpdates?: () => void
  onSelectFilter?: (filter: SidebarFilter) => void
  onOpenVault?: () => void
  onRemoveActiveVault?: () => void
  onRestoreGettingStarted?: () => void
  onAddRemote?: () => void
  onCommitPush?: () => void
  onPull?: () => void
  onResolveConflicts?: () => void
  onViewChanges?: () => void
  onInstallMcp?: () => void
  onOpenInNewWindow?: () => void
  onReloadVault?: () => void
  onRepairVault?: () => void
  onRestoreDeletedNote?: () => void
  activeTabPathRef: MutableRefObject<string | null>
  multiSelectionCommandRef?: MutableRefObject<NoteListMultiSelectionCommands | null>
}

type SimpleHandlerKey = keyof Pick<
  AppCommandHandlers,
  | 'onOpenSettings'
  | 'onCheckForUpdates'
  | 'onCreateNote'
  | 'onCreateType'
  | 'onQuickOpen'
  | 'onSave'
  | 'onFindInNote'
  | 'onUndo'
  | 'onRedo'
  | 'onReplaceInNote'
  | 'onPastePlainText'
  | 'onSearch'
  | 'onToggleRawEditor'
  | 'onToggleDiff'
  | 'onToggleInspector'
  | 'onToggleAIChat'
  | 'onToggleTableOfContents'
  | 'onExportNoteAsPdf'
  | 'onCommandPalette'
  | 'onZoomIn'
  | 'onZoomOut'
  | 'onZoomReset'
  | 'onGoBack'
  | 'onGoForward'
  | 'onOpenVault'
  | 'onRemoveActiveVault'
  | 'onRestoreGettingStarted'
  | 'onAddRemote'
  | 'onCommitPush'
  | 'onPull'
  | 'onResolveConflicts'
  | 'onViewChanges'
  | 'onInstallMcp'
  | 'onReloadVault'
  | 'onRepairVault'
  | 'onOpenInNewWindow'
  | 'onRestoreDeletedNote'
>

type ActiveTabHandlerKey = keyof Pick<
  AppCommandHandlers,
  'onToggleOrganized' | 'onToggleFavorite' | 'onArchiveNote' | 'onDeleteNote'
>

type SimpleHandlerExecutor = (handlers: AppCommandHandlers) => void
type ActiveTabHandlerExecutor = (handlers: AppCommandHandlers, path: string) => void

const SIMPLE_HANDLER_EXECUTORS: readonly [SimpleHandlerKey, SimpleHandlerExecutor][] = [
  ['onOpenSettings', (handlers) => handlers.onOpenSettings()],
  ['onCheckForUpdates', (handlers) => handlers.onCheckForUpdates?.()],
  ['onCreateNote', (handlers) => handlers.onCreateNote()],
  ['onCreateType', (handlers) => handlers.onCreateType?.()],
  ['onQuickOpen', (handlers) => handlers.onQuickOpen()],
  ['onSave', (handlers) => handlers.onSave()],
  ['onFindInNote', (handlers) => handlers.onFindInNote?.()],
  ['onUndo', (handlers) => handlers.onUndo?.()],
  ['onRedo', (handlers) => handlers.onRedo?.()],
  ['onReplaceInNote', (handlers) => handlers.onReplaceInNote?.()],
  ['onPastePlainText', (handlers) => handlers.onPastePlainText()],
  ['onSearch', (handlers) => handlers.onSearch()],
  ['onToggleRawEditor', (handlers) => handlers.onToggleRawEditor?.()],
  ['onToggleDiff', (handlers) => handlers.onToggleDiff?.()],
  ['onToggleInspector', (handlers) => handlers.onToggleInspector()],
  ['onToggleAIChat', (handlers) => handlers.onToggleAIChat?.()],
  ['onToggleTableOfContents', (handlers) => handlers.onToggleTableOfContents?.()],
  ['onExportNoteAsPdf', (handlers) => handlers.onExportNoteAsPdf?.()],
  ['onCommandPalette', (handlers) => handlers.onCommandPalette()],
  ['onZoomIn', (handlers) => handlers.onZoomIn()],
  ['onZoomOut', (handlers) => handlers.onZoomOut()],
  ['onZoomReset', (handlers) => handlers.onZoomReset()],
  ['onGoBack', (handlers) => handlers.onGoBack?.()],
  ['onGoForward', (handlers) => handlers.onGoForward?.()],
  ['onOpenVault', (handlers) => handlers.onOpenVault?.()],
  ['onRemoveActiveVault', (handlers) => handlers.onRemoveActiveVault?.()],
  ['onRestoreGettingStarted', (handlers) => handlers.onRestoreGettingStarted?.()],
  ['onAddRemote', (handlers) => handlers.onAddRemote?.()],
  ['onCommitPush', (handlers) => handlers.onCommitPush?.()],
  ['onPull', (handlers) => handlers.onPull?.()],
  ['onResolveConflicts', (handlers) => handlers.onResolveConflicts?.()],
  ['onViewChanges', (handlers) => handlers.onViewChanges?.()],
  ['onInstallMcp', (handlers) => handlers.onInstallMcp?.()],
  ['onReloadVault', (handlers) => handlers.onReloadVault?.()],
  ['onRepairVault', (handlers) => handlers.onRepairVault?.()],
  ['onOpenInNewWindow', (handlers) => handlers.onOpenInNewWindow?.()],
  ['onRestoreDeletedNote', (handlers) => handlers.onRestoreDeletedNote?.()],
]

const ACTIVE_TAB_HANDLER_EXECUTORS: readonly [ActiveTabHandlerKey, ActiveTabHandlerExecutor][] = [
  ['onToggleOrganized', (handlers, path) => handlers.onToggleOrganized?.(path)],
  ['onToggleFavorite', (handlers, path) => handlers.onToggleFavorite?.(path)],
  ['onArchiveNote', (handlers, path) => handlers.onArchiveNote(path)],
  ['onDeleteNote', (handlers, path) => handlers.onDeleteNote(path)],
]

function runSimpleHandler(handler: SimpleHandlerKey, handlers: AppCommandHandlers): void {
  const executor = SIMPLE_HANDLER_EXECUTORS.find(([key]) => key === handler)?.[1]
  executor?.(handlers)
}

function runActiveTabHandler(handler: ActiveTabHandlerKey, handlers: AppCommandHandlers, path: string): void {
  const executor = ACTIVE_TAB_HANDLER_EXECUTORS.find(([key]) => key === handler)?.[1]
  executor?.(handlers, path)
}

const SHORTCUT_ECHO_DEDUPE_WINDOW_MS = 150
let lastCommandDispatch:
  | {
      id: AppCommandId
      source: AppCommandDispatchSource
      timestamp: number
    }
  | null = null

let lastSuppressedShortcutCommand:
  | {
      id: AppCommandId
      source: SuppressedShortcutSource
      timestamp: number
    }
  | null = null

function now(): number {
  return globalThis.performance?.now?.() ?? Date.now()
}

function isShortcutEchoPair(a: AppCommandDispatchSource, b: AppCommandDispatchSource): boolean {
  return (
    (a === 'renderer-keyboard' && b === 'native-menu') ||
    (a === 'native-menu' && b === 'renderer-keyboard')
  )
}

function shouldSuppressDuplicateCommand(
  id: AppCommandId,
  source: AppCommandDispatchSource,
  currentTimestamp: number,
): boolean {
  if (!lastCommandDispatch || lastCommandDispatch.id !== id) return false
  if (!isShortcutEchoPair(source, lastCommandDispatch.source)) return false
  return currentTimestamp - lastCommandDispatch.timestamp <= SHORTCUT_ECHO_DEDUPE_WINDOW_MS
}

function shouldSuppressShortcutEchoAfterKeyboardYield(
  id: AppCommandId,
  source: AppCommandDispatchSource,
  currentTimestamp: number,
): boolean {
  if (source !== 'native-menu') return false
  if (!lastSuppressedShortcutCommand || lastSuppressedShortcutCommand.id !== id) return false
  return currentTimestamp - lastSuppressedShortcutCommand.timestamp <= SHORTCUT_ECHO_DEDUPE_WINDOW_MS
}

function dispatchActiveTabCommand(
  pathRef: MutableRefObject<string | null>,
  handler: (path: string) => void,
): boolean {
  const path = pathRef.current
  if (!path) return false
  handler(path)
  return true
}

function dispatchMultiSelectionCommand(
  selectionRef: MutableRefObject<NoteListMultiSelectionCommands | null> | undefined,
  handler: ActiveTabHandlerKey,
): boolean | null {
  const selection = selectionRef?.current
  if (!selection || selection.selectedPaths.length <= 1) return null

  if (handler === 'onDeleteNote') {
    selection.deleteSelected?.()
    return !!selection.deleteSelected
  }

  if (handler === 'onToggleOrganized') {
    selection.organizeSelected?.()
    return !!selection.organizeSelected
  }

  return false
}

function dispatchDefinition(
  definition: AppCommandDefinition,
  handlers: AppCommandHandlers,
): boolean {
  switch (definition.route.kind) {
    case 'view-mode':
      handlers.onSetViewMode(definition.route.value)
      return true
    case 'filter':
      handlers.onSelectFilter?.(definition.route.value)
      return true
    case 'handler': {
      runSimpleHandler(definition.route.handler as SimpleHandlerKey, handlers)
      return true
    }
    case 'active-tab-handler': {
      const handler = definition.route.handler
      const multiSelectionResult = dispatchMultiSelectionCommand(
        handlers.multiSelectionCommandRef,
        handler as ActiveTabHandlerKey,
      )
      if (multiSelectionResult !== null) {
        return multiSelectionResult
      }

      return dispatchActiveTabCommand(
        handlers.activeTabPathRef,
        (path) => runActiveTabHandler(handler as ActiveTabHandlerKey, handlers, path),
      )
    }
  }
}

export function dispatchAppCommand(id: AppCommandId, handlers: AppCommandHandlers): boolean {
  return executeAppCommand(id, handlers, 'direct')
}

export function executeAppCommand(
  id: AppCommandId,
  handlers: AppCommandHandlers,
  source: AppCommandDispatchSource,
): boolean {
  const timestamp = now()
  if (shouldSuppressShortcutEchoAfterKeyboardYield(id, source, timestamp)) {
    return false
  }
  if (shouldSuppressDuplicateCommand(id, source, timestamp)) {
    return false
  }

  const definition = Reflect.get(APP_COMMAND_DEFINITIONS, id) as AppCommandDefinition
  const dispatched = dispatchDefinition(definition, handlers)
  if (dispatched) {
    lastCommandDispatch = { id, source, timestamp }
  }
  return dispatched
}

export function recordSuppressedShortcutCommand(
  id: AppCommandId,
  source: SuppressedShortcutSource = 'renderer-keyboard',
): void {
  lastSuppressedShortcutCommand = { id, source, timestamp: now() }
}

export function resetAppCommandDispatchStateForTests(): void {
  lastCommandDispatch = null
  lastSuppressedShortcutCommand = null
}
