import appCommandManifest from '../shared/appCommandManifest.json' with { type: 'json' }
import type { SidebarFilter } from '../types'
import { isMac } from '../utils/platform'
import type { ViewMode } from './useViewMode'

type AppCommandKey = keyof typeof appCommandManifest.commands
type ShortcutEventLike = Pick<KeyboardEvent, 'altKey' | 'ctrlKey' | 'metaKey' | 'shiftKey' | 'key' | 'code'>

export type AppCommandShortcutCombo =
  | 'command-or-ctrl'
  | 'command-or-ctrl-shift'
  | 'command-shift'
export type AppCommandDeterministicQaMode =
  | 'renderer-shortcut-event'
  | 'native-menu-command'

export interface AppCommandDeterministicQaDefinition {
  preferredMode: AppCommandDeterministicQaMode
  supportsRendererShortcutEvent: boolean
  supportsNativeMenuCommand: boolean
  requiresManualNativeAcceleratorQa: boolean
}

export interface AppCommandShortcutEventOptions {
  preferControl?: boolean
}

export type AppCommandShortcutEventInit = Pick<
  KeyboardEventInit,
  'altKey' | 'bubbles' | 'cancelable' | 'code' | 'ctrlKey' | 'key' | 'metaKey' | 'shiftKey'
>

type SimpleHandlerKey =
  | '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 =
  | 'onToggleOrganized'
  | 'onToggleFavorite'
  | 'onArchiveNote'
  | 'onDeleteNote'

type AppCommandRoute =
  | { kind: 'view-mode'; value: ViewMode }
  | { kind: 'filter'; value: SidebarFilter }
  | { kind: 'handler'; handler: SimpleHandlerKey }
  | { kind: 'active-tab-handler'; handler: ActiveTabHandlerKey }

interface AppCommandShortcutDefinition {
  combo: AppCommandShortcutCombo
  key: string
  aliases?: string[]
  code?: string
  display: string
}

interface AppCommandManifestShortcutDefinition extends AppCommandShortcutDefinition {
  accelerator: string
  requiresManualNativeAcceleratorQa?: boolean
}

interface AppCommandManifestDefinition {
  id: string
  route: AppCommandRoute
  menuOwned: boolean
  shortcut?: AppCommandManifestShortcutDefinition
  preferredShortcutQaMode?: AppCommandDeterministicQaMode
}

export interface AppCommandDefinition {
  route: AppCommandRoute
  menuOwned: boolean
  shortcut?: AppCommandShortcutDefinition
  preferredShortcutQaMode?: AppCommandDeterministicQaMode
}

type PlatformLabel = string | {
  macos?: string
  windows?: string
  linux?: string
  default: string
}

type AppCommandMenuManifestItem =
  | { kind: 'separator' }
  | {
      kind: 'command'
      command: AppCommandKey
      id?: string
      label: PlatformLabel
      accelerator?: string | null
      enabled?: boolean
    }
  | {
      kind: 'menu-event'
      id: string
      label: PlatformLabel
      accelerator?: string | null
      enabled?: boolean
    }

interface AppCommandMenuManifestSection {
  label: string
  items: AppCommandMenuManifestItem[]
}

export type AppCommandMenuItem =
  | { kind: 'separator' }
  | {
      kind: 'command'
      commandId: string
      menuItemId: string
      label: string
      shortcut?: string
      enabled?: boolean
    }

type AppCommandMenuStateGroupReference =
  | { command: AppCommandKey }
  | { id: string }

type AppCommandMenuStateGroupName = keyof typeof appCommandManifest.menuStateGroups

const APP_COMMAND_MANIFEST_COMMANDS = appCommandManifest.commands as Record<AppCommandKey, AppCommandManifestDefinition>
const APP_COMMAND_MANIFEST_MENUS = appCommandManifest.menus as AppCommandMenuManifestSection[]
const APP_COMMAND_MANIFEST_APP_MENU = appCommandManifest.appMenu as AppCommandMenuManifestItem[]
const APP_COMMAND_MANIFEST_STATE_GROUPS = appCommandManifest.menuStateGroups as Record<
  AppCommandMenuStateGroupName,
  AppCommandMenuStateGroupReference[]
>

const MENU_LABEL_KEYS = {
  File: 'menu.file',
  Edit: 'menu.edit',
  View: 'menu.view',
  Go: 'menu.go',
  Note: 'menu.note',
  Vault: 'menu.vault',
  Window: 'menu.window',
  'New Note': 'command.note.newNote',
  'New Type': 'command.note.newType',
  'Quick Open': 'menu.file.quickOpen',
  'Quick Open (Cmd+O)': 'menu.file.quickOpenCmdO',
  'Quick Open (Ctrl+O)': 'menu.file.quickOpenCtrlO',
  Save: 'menu.file.save',
  Undo: 'command.note.undo',
  Redo: 'command.note.redo',
  'Paste without Formatting': 'menu.edit.pasteWithoutFormatting',
  'Find in Note': 'command.note.findInNote',
  'Replace in Note': 'command.note.replaceInNote',
  'Find in Vault': 'menu.edit.findInVault',
  'Toggle Note List Search': 'menu.edit.toggleNoteListSearch',
  'Toggle Diff Mode': 'command.view.toggleDiff',
  'Editor Only': 'command.view.editorOnly',
  'Editor + Notes': 'command.view.editorNoteList',
  'All Panels': 'menu.view.allPanels',
  'Toggle Properties Panel': 'command.view.toggleProperties',
  'Toggle AI Panel': 'command.view.toggleAiPanel',
  'Zoom In': 'menu.view.zoomIn',
  'Zoom Out': 'menu.view.zoomOut',
  'Actual Size': 'menu.view.actualSize',
  'Command Palette': 'menu.view.commandPalette',
  'All Notes': 'menu.go.allNotes',
  Archived: 'menu.go.archived',
  Changes: 'menu.go.changes',
  Inbox: 'menu.go.inbox',
  'Go Back': 'command.navigation.goBack',
  'Go Forward': 'command.navigation.goForward',
  'Toggle Organized': 'menu.note.toggleOrganized',
  'Archive Note': 'command.note.archiveNote',
  'Delete Note': 'command.note.deleteNote',
  'Restore Deleted Note': 'command.note.restoreDeleted',
  'Open in New Window': 'command.note.openNewWindow',
  'Export Note as PDF': 'command.note.exportPdf',
  'Toggle Raw Editor': 'command.view.toggleRaw',
  'Toggle Table of Contents': 'menu.note.toggleTableOfContents',
  'Toggle Backlinks': 'command.view.toggleBacklinks',
  'Open Vault…': 'command.settings.openVault',
  'Remove Vault from List': 'command.settings.removeVault',
  'Restore Getting Started': 'command.settings.restoreGettingStarted',
  'Add Remote…': 'menu.vault.addRemote',
  'Commit & Push': 'command.git.commitPush',
  'Pull from Remote': 'command.git.pull',
  'Resolve Conflicts': 'command.git.resolveConflicts',
  'View Pending Changes': 'command.git.viewChanges',
  'Reload Vault': 'command.settings.reloadVault',
  'Repair Vault': 'command.settings.repairVault',
  'Set Up External AI Tools…': 'command.settings.setupExternalAi',
} as const

export type AppCommandMenuLabelKey = (typeof MENU_LABEL_KEYS)[keyof typeof MENU_LABEL_KEYS]
export type AppCommandMenuTranslator = (key: AppCommandMenuLabelKey) => string

export const APP_COMMAND_IDS = Object.fromEntries(
  Object.entries(APP_COMMAND_MANIFEST_COMMANDS).map(([key, command]) => [key, command.id]),
) as { readonly [K in AppCommandKey]: string }

export type AppCommandId = (typeof APP_COMMAND_IDS)[AppCommandKey]

const APP_COMMAND_SET = new Set<string>(Object.values(APP_COMMAND_IDS))

function toShortcutDefinition(
  shortcut: AppCommandManifestShortcutDefinition | undefined,
): AppCommandShortcutDefinition | undefined {
  if (!shortcut) return undefined

  return {
    combo: shortcut.combo,
    key: shortcut.key,
    aliases: shortcut.aliases,
    code: shortcut.code,
    display: shortcut.display,
  }
}

export const APP_COMMAND_DEFINITIONS = Object.fromEntries(
  Object.values(APP_COMMAND_MANIFEST_COMMANDS).map((command) => [
    command.id,
    {
      route: command.route,
      menuOwned: command.menuOwned,
      shortcut: toShortcutDefinition(command.shortcut),
      preferredShortcutQaMode: command.preferredShortcutQaMode,
    },
  ]),
) as Record<AppCommandId, AppCommandDefinition>

function resolvePlatformLabel(label: PlatformLabel): string {
  if (typeof label === 'string') return label
  if (isMac() && label.macos) return label.macos
  return label.default
}

function localizeMenuLabel(label: string, t?: AppCommandMenuTranslator): string {
  const key = Reflect.get(MENU_LABEL_KEYS, label) as AppCommandMenuLabelKey | undefined
  return key && t ? t(key) : label
}

function formatAcceleratorDisplay(accelerator: string): string {
  const commandPrefix = isMac() ? '⌘' : 'Ctrl+'
  const commandShiftPrefix = isMac() ? '⌘⇧' : 'Ctrl+Shift+'

  return accelerator
    .replaceAll('CmdOrCtrl+Shift+', commandShiftPrefix)
    .replaceAll('CmdOrCtrl+', commandPrefix)
    .replaceAll('Backspace', isMac() ? '⌫' : 'Backspace')
    .replaceAll('Delete', isMac() ? '⌦' : 'Delete')
    .replaceAll('Left', isMac() ? '←' : 'Left')
    .replaceAll('Right', isMac() ? '→' : 'Right')
    .replaceAll('Enter', isMac() ? '↵' : 'Enter')
}

function menuShortcutForCommand(
  item: Extract<AppCommandMenuManifestItem, { kind: 'command' }>,
  command: AppCommandManifestDefinition,
): string | undefined {
  if (typeof item.accelerator === 'string') return formatAcceleratorDisplay(item.accelerator)
  if (command.shortcut) return formatShortcutDisplay(command.shortcut)
  return undefined
}

function toMenuItem(item: AppCommandMenuManifestItem, t?: AppCommandMenuTranslator): AppCommandMenuItem {
  if (item.kind === 'separator') return { kind: 'separator' }

  if (item.kind === 'menu-event') {
    const label = resolvePlatformLabel(item.label)
    return {
      kind: 'command',
      commandId: item.id,
      menuItemId: item.id,
      label: localizeMenuLabel(label, t),
      shortcut: typeof item.accelerator === 'string'
        ? formatAcceleratorDisplay(item.accelerator)
        : undefined,
      enabled: item.enabled,
    }
  }

  const command = Reflect.get(APP_COMMAND_MANIFEST_COMMANDS, item.command) as AppCommandManifestDefinition
  const label = resolvePlatformLabel(item.label)
  return {
    kind: 'command',
    commandId: command.id,
    menuItemId: item.id ?? command.id,
    label: localizeMenuLabel(label, t),
    shortcut: menuShortcutForCommand(item, command),
    enabled: item.enabled,
  }
}

function menuCommandIds(items: AppCommandMenuItem[]): string[] {
  return items.flatMap(item => item.kind === 'command' ? [item.commandId] : [])
}

export function getAppCommandMenuSections(t?: AppCommandMenuTranslator) {
  return APP_COMMAND_MANIFEST_MENUS.map(section => ({
    label: localizeMenuLabel(section.label, t),
    items: section.items.map(item => toMenuItem(item, t)),
  }))
}

export const APP_COMMAND_MENU_SECTIONS = getAppCommandMenuSections()

const APP_COMMAND_APP_MENU_ITEMS = APP_COMMAND_MANIFEST_APP_MENU.map(item => toMenuItem(item))

export const APP_COMMAND_MENU_STATE_GROUPS = Object.fromEntries(
  Object.entries(APP_COMMAND_MANIFEST_STATE_GROUPS).map(([name, references]) => [
    name,
    references.map(reference => 'command' in reference
      ? (Reflect.get(APP_COMMAND_MANIFEST_COMMANDS, reference.command) as AppCommandManifestDefinition).id
      : reference.id),
  ]),
) as Record<AppCommandMenuStateGroupName, string[]>

const NATIVE_MENU_COMMAND_SET = new Set<string>(
  [
    ...APP_COMMAND_MENU_SECTIONS.flatMap(section => menuCommandIds(section.items)),
    ...menuCommandIds(APP_COMMAND_APP_MENU_ITEMS),
  ].filter(id => APP_COMMAND_SET.has(id)),
)

const MANUAL_NATIVE_ACCELERATOR_QA_COMMAND_SET = new Set<AppCommandId>(
  Object.values(APP_COMMAND_MANIFEST_COMMANDS)
    .filter(command => command.shortcut?.requiresManualNativeAcceleratorQa)
    .map(command => command.id),
)

const shortcutKeyMaps = {
  'command-or-ctrl': new Map<string, AppCommandId>(),
  'command-or-ctrl-shift': new Map<string, AppCommandId>(),
  'command-shift': new Map<string, AppCommandId>(),
} satisfies Record<AppCommandShortcutCombo, Map<string, AppCommandId>>

const shortcutCodeMaps = {
  'command-or-ctrl': new Map<string, AppCommandId>(),
  'command-or-ctrl-shift': new Map<string, AppCommandId>(),
  'command-shift': new Map<string, AppCommandId>(),
} satisfies Record<AppCommandShortcutCombo, Map<string, AppCommandId>>

const COMMAND_ONLY_COMBOS: readonly AppCommandShortcutCombo[] = ['command-or-ctrl']
const COMMAND_SHIFT_COMBOS: readonly AppCommandShortcutCombo[] = ['command-shift', 'command-or-ctrl-shift']
const COMMAND_OR_CTRL_SHIFT_COMBOS: readonly AppCommandShortcutCombo[] = ['command-or-ctrl-shift']
const NO_SHORTCUT_COMBOS: readonly AppCommandShortcutCombo[] = []

function normalizeShortcutKey(key: string): string {
  return key.length === 1 ? key.toLowerCase() : key
}

function isPlatformRedoAlternate(event: ShortcutEventLike): boolean {
  return !isMac()
    && !event.altKey
    && !event.metaKey
    && event.ctrlKey
    && !event.shiftKey
    && normalizeShortcutKey(event.key) === 'y'
}

for (const [id, definition] of Object.entries(APP_COMMAND_DEFINITIONS) as Array<[AppCommandId, AppCommandDefinition]>) {
  const shortcut = definition.shortcut
  if (!shortcut) continue
  const shortcutKeyMap = Reflect.get(shortcutKeyMaps, shortcut.combo) as Map<string, AppCommandId>
  shortcutKeyMap.set(normalizeShortcutKey(shortcut.key), id)
  for (const alias of shortcut.aliases ?? []) {
    shortcutKeyMap.set(normalizeShortcutKey(alias), id)
  }
  if (shortcut.code) {
    const shortcutCodeMap = Reflect.get(shortcutCodeMaps, shortcut.combo) as Map<string, AppCommandId>
    shortcutCodeMap.set(shortcut.code, id)
  }
}

export function isAppCommandId(value: string): value is AppCommandId {
  return APP_COMMAND_SET.has(value)
}

export function isNativeMenuCommandId(value: string): value is AppCommandId {
  return NATIVE_MENU_COMMAND_SET.has(value)
}

export function getDeterministicShortcutQaDefinition(
  id: AppCommandId,
): AppCommandDeterministicQaDefinition | null {
  const definition = Reflect.get(APP_COMMAND_DEFINITIONS, id) as AppCommandDefinition
  if (!definition.shortcut) return null

  return {
    preferredMode:
      definition.preferredShortcutQaMode
      ?? (definition.menuOwned ? 'native-menu-command' : 'renderer-shortcut-event'),
    supportsRendererShortcutEvent: true,
    supportsNativeMenuCommand: definition.menuOwned,
    requiresManualNativeAcceleratorQa: MANUAL_NATIVE_ACCELERATOR_QA_COMMAND_SET.has(id),
  }
}

export function getShortcutEventInit(
  id: AppCommandId,
  options: AppCommandShortcutEventOptions = {},
): AppCommandShortcutEventInit | null {
  const shortcut = (Reflect.get(APP_COMMAND_DEFINITIONS, id) as AppCommandDefinition).shortcut
  if (!shortcut) return null

  const useControl = options.preferControl ?? false

  return {
    key: shortcut.key,
    code: shortcut.code,
    altKey: false,
    bubbles: true,
    cancelable: true,
    ctrlKey: useControl,
    metaKey: !useControl,
    shiftKey: shortcut.combo !== 'command-or-ctrl',
  }
}

export function shortcutCombosForEvent({
  altKey,
  ctrlKey,
  metaKey,
  shiftKey,
}: Pick<ShortcutEventLike, 'altKey' | 'ctrlKey' | 'metaKey' | 'shiftKey'>): readonly AppCommandShortcutCombo[] {
  if (altKey || (!metaKey && !ctrlKey)) return NO_SHORTCUT_COMBOS
  if (isMac() && ctrlKey) return NO_SHORTCUT_COMBOS
  if (shiftKey) {
    return metaKey && !ctrlKey ? COMMAND_SHIFT_COMBOS : COMMAND_OR_CTRL_SHIFT_COMBOS
  }
  return COMMAND_ONLY_COMBOS
}

export function findShortcutCommandId(
  combo: AppCommandShortcutCombo,
  key: string,
  code?: string,
): AppCommandId | null {
  const keyMatch = (Reflect.get(shortcutKeyMaps, combo) as Map<string, AppCommandId>).get(normalizeShortcutKey(key))
  if (keyMatch) return keyMatch

  if (code) {
    const codeMatch = (Reflect.get(shortcutCodeMaps, combo) as Map<string, AppCommandId>).get(code)
    if (codeMatch) return codeMatch
  }

  return null
}

export function findShortcutCommandIdForEvent(event: ShortcutEventLike): AppCommandId | null {
  if (isPlatformRedoAlternate(event)) return APP_COMMAND_IDS.editRedo

  for (const combo of shortcutCombosForEvent(event)) {
    const commandId = findShortcutCommandId(combo, event.key, event.code)
    if (commandId) return commandId
  }
  return null
}

export function formatShortcutDisplay(
  shortcut: Pick<AppCommandShortcutDefinition, 'display'>,
): string {
  if (isMac()) return shortcut.display

  return shortcut.display
    .replaceAll('⌘⇧', 'Ctrl+Shift+')
    .replaceAll('⌘', 'Ctrl+')
    .replaceAll('⌫', 'Backspace')
    .replaceAll('⌦', 'Delete')
    .replaceAll('←', 'Left')
    .replaceAll('→', 'Right')
    .replaceAll('↵', 'Enter')
}

export function getAppCommandShortcutDisplay(id: AppCommandId): string | undefined {
  const shortcut = (Reflect.get(APP_COMMAND_DEFINITIONS, id) as AppCommandDefinition).shortcut
  return shortcut ? formatShortcutDisplay(shortcut) : undefined
}
