import type { MediaPlayerInstance } from '@vidstack/react'

/**
 * Player keyboard shortcut registry. Each shortcut has:
 *   - id: stable, used as the override map key in user prefs
 *   - keys: array of binding strings; the first is the canonical default
 *     shown in the keyboard hints overlay
 *   - description: shown in the hints overlay and Settings → Shortcuts
 *   - category: for grouping in the hints overlay
 *   - handler: receives the player instance + a context carrying the
 *     wider state setters and refs the player UI exposes
 *
 * Binding string format: `Ctrl+Shift+Alt+<key>` or just `<key>`. Modifier
 * order is fixed (Ctrl, Shift, Alt) for canonical comparison. The
 * key name matches `KeyboardEvent.key` (case-insensitive matching).
 */

export type ShortcutCategory =
  | 'playback'
  | 'navigation'
  | 'audio'
  | 'subtitles'
  | 'tools'
  | 'view'

export interface ShortcutContext {
  // Loose signature so a react-router NavigateFunction satisfies it
  navigate: (to: any, options?: any) => void
  showSeekIndicator: (direction: 'forward' | 'backward') => void
  showControls: () => void
  setStreamInfoOpen: (fn: (o: boolean) => boolean) => void
  setIsMuted: (m: boolean) => void
  toggleFullscreen: () => void
  togglePictureInPicture: () => void
  toggleTheaterMode: () => void
  takeScreenshot: () => void
  toggleHints: () => void
  bumpVolume: (delta: number) => void
  cycleSpeed: (direction: 1 | -1) => void
  resetSpeed: () => void
  bumpSubtitleDelay: (deltaMs: number) => void
  resetSubtitleDelay: () => void
  bumpAudioDelay: (deltaMs: number) => void
  resetAudioDelay: () => void
  setLoopA: () => void
  setLoopB: () => void
  clearLoop: () => void
  addBookmark: () => void
  toggleBookmarksPanel: () => void
  toggleSubSearch: () => void
  stepFrame: (direction: 1 | -1) => void
  prevChapter: () => void
  nextChapter: () => void
}

export type ShortcutHandler = (player: MediaPlayerInstance, ctx: ShortcutContext) => void

export interface Shortcut {
  id: string
  keys: string[]
  description: string
  category: ShortcutCategory
  handler: ShortcutHandler
}

/** Canonical shortcuts. Order matches what the hints overlay should show. */
export const SHORTCUTS: Shortcut[] = [
  // Playback
  {
    id: 'playPause',
    keys: ['Space', 'k'],
    description: 'Play / Pause',
    category: 'playback',
    handler: (p) => {
      if (p.paused) p.play()
      else p.pause()
    },
  },
  {
    id: 'seekBack',
    keys: ['ArrowLeft', 'j'],
    description: 'Back 10 seconds',
    category: 'playback',
    handler: (p, ctx) => {
      p.currentTime = Math.max(0, (p.currentTime ?? 0) - 10)
      ctx.showSeekIndicator('backward')
    },
  },
  {
    id: 'seekForward',
    keys: ['ArrowRight', 'l'],
    description: 'Forward 10 seconds',
    category: 'playback',
    handler: (p, ctx) => {
      p.currentTime = (p.currentTime ?? 0) + 10
      ctx.showSeekIndicator('forward')
    },
  },
  {
    id: 'speedUp',
    keys: ['>', 'Shift+.'],
    description: 'Speed up',
    category: 'playback',
    handler: (_p, ctx) => ctx.cycleSpeed(1),
  },
  {
    id: 'speedDown',
    keys: ['<', 'Shift+,'],
    description: 'Slow down',
    category: 'playback',
    handler: (_p, ctx) => ctx.cycleSpeed(-1),
  },
  {
    id: 'speedReset',
    keys: ['='],
    description: 'Reset speed to 1×',
    category: 'playback',
    handler: (_p, ctx) => ctx.resetSpeed(),
  },
  {
    id: 'frameBack',
    keys: [','],
    description: 'Previous frame (when paused)',
    category: 'playback',
    handler: (_p, ctx) => ctx.stepFrame(-1),
  },
  {
    id: 'frameForward',
    keys: ['.'],
    description: 'Next frame (when paused)',
    category: 'playback',
    handler: (_p, ctx) => ctx.stepFrame(1),
  },
  // Audio
  {
    id: 'mute',
    keys: ['m'],
    description: 'Mute / unmute',
    category: 'audio',
    handler: (p, ctx) => {
      p.muted = !p.muted
      ctx.setIsMuted(p.muted)
    },
  },
  {
    id: 'volumeUp',
    keys: ['ArrowUp'],
    description: 'Volume up',
    category: 'audio',
    handler: (_p, ctx) => ctx.bumpVolume(0.05),
  },
  {
    id: 'volumeDown',
    keys: ['ArrowDown'],
    description: 'Volume down',
    category: 'audio',
    handler: (_p, ctx) => ctx.bumpVolume(-0.05),
  },
  {
    id: 'audioDelayEarlier',
    keys: ['['],
    description: 'Audio earlier 100ms',
    category: 'audio',
    handler: (_p, ctx) => ctx.bumpAudioDelay(-100),
  },
  {
    id: 'audioDelayLater',
    keys: [']'],
    description: 'Audio later 100ms',
    category: 'audio',
    handler: (_p, ctx) => ctx.bumpAudioDelay(100),
  },
  {
    id: 'audioDelayReset',
    keys: ['\\'],
    description: 'Reset audio delay',
    category: 'audio',
    handler: (_p, ctx) => ctx.resetAudioDelay(),
  },
  // Subtitles
  {
    id: 'subDelayEarlier',
    keys: ['z'],
    description: 'Subtitles earlier 100ms',
    category: 'subtitles',
    handler: (_p, ctx) => ctx.bumpSubtitleDelay(-100),
  },
  {
    id: 'subDelayLater',
    keys: ['x'],
    description: 'Subtitles later 100ms',
    category: 'subtitles',
    handler: (_p, ctx) => ctx.bumpSubtitleDelay(100),
  },
  {
    id: 'subDelayReset',
    keys: ['Shift+z'],
    description: 'Reset subtitle delay',
    category: 'subtitles',
    handler: (_p, ctx) => ctx.resetSubtitleDelay(),
  },
  {
    id: 'subSearch',
    keys: ['/'],
    description: 'Search subtitles',
    category: 'subtitles',
    handler: (_p, ctx) => ctx.toggleSubSearch(),
  },
  // Tools
  {
    id: 'loopA',
    keys: ['Shift+['],
    description: 'Set loop point A',
    category: 'tools',
    handler: (_p, ctx) => ctx.setLoopA(),
  },
  {
    id: 'loopB',
    keys: ['Shift+]'],
    description: 'Set loop point B',
    category: 'tools',
    handler: (_p, ctx) => ctx.setLoopB(),
  },
  {
    id: 'loopClear',
    keys: ['Shift+\\'],
    description: 'Clear A-B loop',
    category: 'tools',
    handler: (_p, ctx) => ctx.clearLoop(),
  },
  {
    id: 'bookmark',
    keys: ['b'],
    description: 'Add bookmark',
    category: 'tools',
    handler: (_p, ctx) => ctx.addBookmark(),
  },
  {
    id: 'bookmarksPanel',
    keys: ['Shift+b'],
    description: 'Open bookmarks',
    category: 'tools',
    handler: (_p, ctx) => ctx.toggleBookmarksPanel(),
  },
  {
    id: 'screenshot',
    keys: ['Ctrl+Shift+s'],
    description: 'Copy frame to clipboard',
    category: 'tools',
    handler: (_p, ctx) => ctx.takeScreenshot(),
  },
  {
    id: 'streamInfo',
    keys: ['i'],
    description: 'Toggle stream info',
    category: 'tools',
    handler: (_p, ctx) => ctx.setStreamInfoOpen(o => !o),
  },
  {
    id: 'hints',
    keys: ['?', 'Shift+/'],
    description: 'Show keyboard shortcuts',
    category: 'tools',
    handler: (_p, ctx) => ctx.toggleHints(),
  },
  {
    id: 'prevChapter',
    keys: ['Shift+ArrowLeft'],
    description: 'Previous chapter',
    category: 'playback',
    handler: (_p, ctx) => ctx.prevChapter(),
  },
  {
    id: 'nextChapter',
    keys: ['Shift+ArrowRight'],
    description: 'Next chapter',
    category: 'playback',
    handler: (_p, ctx) => ctx.nextChapter(),
  },
  // View
  {
    id: 'fullscreen',
    keys: ['f'],
    description: 'Toggle fullscreen',
    category: 'view',
    handler: (_p, ctx) => ctx.toggleFullscreen(),
  },
  {
    id: 'pip',
    keys: ['p'],
    description: 'Picture-in-picture',
    category: 'view',
    handler: (_p, ctx) => ctx.togglePictureInPicture(),
  },
  {
    id: 'theater',
    keys: ['t'],
    description: 'Theater mode',
    category: 'view',
    handler: (_p, ctx) => ctx.toggleTheaterMode(),
  },
  // Navigation
  {
    id: 'exit',
    keys: ['Escape'],
    description: 'Exit player',
    category: 'navigation',
    handler: (_p, ctx) => ctx.navigate(-1),
  },
]

/**
 * Normalise a binding string for comparison. Lowercases the key but keeps
 * the modifier order canonical (Ctrl+Shift+Alt+key).
 */
export function normalizeBinding(binding: string): string {
  const parts = binding.split('+').map(p => p.trim())
  const key = parts.pop() || ''
  const mods = new Set(parts.map(p => p.toLowerCase()))
  const out: string[] = []
  if (mods.has('ctrl')) out.push('Ctrl')
  if (mods.has('shift')) out.push('Shift')
  if (mods.has('alt')) out.push('Alt')
  out.push(key.length === 1 ? key.toLowerCase() : key)
  return out.join('+')
}

/** Build the canonical binding string for a KeyboardEvent. */
export function eventToBinding(e: KeyboardEvent): string {
  const out: string[] = []
  if (e.ctrlKey || e.metaKey) out.push('Ctrl')
  if (e.shiftKey) out.push('Shift')
  if (e.altKey) out.push('Alt')
  // KeyboardEvent.key already accounts for shift on letters - we want the
  // raw key, so for letters use lowercase.
  let key = e.key
  if (key === ' ') key = 'Space'
  else if (key.length === 1) key = key.toLowerCase()
  out.push(key)
  return out.join('+')
}

/**
 * Resolve user's bindings (with overrides applied) into a flat map of
 * normalised binding -> shortcut id.
 */
export function buildBindingMap(overrides: Record<string, string[]> = {}): Map<string, string> {
  const map = new Map<string, string>()
  for (const sc of SHORTCUTS) {
    const keys = overrides[sc.id]?.length ? overrides[sc.id] : sc.keys
    for (const k of keys) map.set(normalizeBinding(k), sc.id)
  }
  return map
}

export function findShortcut(id: string): Shortcut | undefined {
  return SHORTCUTS.find(s => s.id === id)
}
