/**
 * Single entry point for keyboard handling.
 *
 * Sets up a capture-phase listener that intercepts keydown events
 * before ProseMirror sees them. The provided keybinding defines what
 * each key does in various contexts (palette open/closed, etc.).
 *
 * Keybindings are swappable — pass a different binding to support
 * vim, emacs, or custom keymaps without changing any routing logic.
 *
 * Palette navigation (ArrowDown/ArrowUp/Enter) is NOT handled here.
 * Each Block manages its own suggestion menu keyboard via a separate
 * capture-phase listener when its local session is active.
 */

import type { EditorView } from "prosemirror-view"
import { onUnmounted, type Ref, watch } from "vue"
import type { PatternOpenPayload } from "@/composables/usePatternPlugin"

// ── Types ────────────────────────────────────────────────────────

export type KeyboardAction = "close-palette" | "undo" | "redo"

export interface KeyboardContext {
  isPaletteOpen: boolean
}

export interface KeyBinding {
  match(event: KeyboardEvent, ctx: KeyboardContext): KeyboardAction | null
}

export interface KeyboardConfig {
  keybinding: KeyBinding
  activeSession: Ref<PatternOpenPayload | null>
  activeView: Ref<EditorView | null>
  onUndo: () => void
  onRedo: () => void
}

// ── Composables ──────────────────────────────────────────────────

export function useKeyboard(config: KeyboardConfig) {
  const { keybinding, activeSession, activeView, onUndo, onRedo } = config

  function onKeydownCapture(e: KeyboardEvent) {
    const target = e.target as HTMLElement | null
    if (!target?.closest(".editor-shell")) return

    const ctx: KeyboardContext = {
      isPaletteOpen: activeSession.value !== null,
    }

    const action = keybinding.match(e, ctx)
    if (!action) return

    e.preventDefault()
    e.stopPropagation()

    switch (action) {
      case "undo":
        onUndo()
        break
      case "redo":
        onRedo()
        break
      case "close-palette":
        activeSession.value?.close("cancelled")
        activeSession.value = null
        break
    }
  }

  function onFocusTrap(e: FocusEvent) {
    const target = e.target as HTMLElement | null
    if (!target) return
    if (target.closest("[data-command-palette]")) {
      activeView.value?.focus()
    }
  }

  // Keydown listener is always active (undo/redo are always-available shortcuts).
  // Focus trap is active only when the palette is open.
  onUnmounted(() => {
    document.removeEventListener("keydown", onKeydownCapture, true)
    document.removeEventListener("focusin", onFocusTrap, true)
  })

  // Add the permanent keydown listener on mount. The palette-only focusin
  // listener is managed by the activeSession watch below.
  document.addEventListener("keydown", onKeydownCapture, true)

  watch(activeSession, (session, oldSession) => {
    if (session && !oldSession) {
      document.addEventListener("focusin", onFocusTrap, true)
    } else if (!session && oldSession) {
      document.removeEventListener("focusin", onFocusTrap, true)
    }
  })
}

// ── Built-in keybindings ─────────────────────────────────────────

export const defaultKeyBinding: KeyBinding = {
  match(e, ctx) {
    // Palette keyboard (ArrowUp/Down/Enter) is handled by each Block's
    // local capture listener. Only Escape is handled here.
    if (ctx.isPaletteOpen) {
      if (e.key === "Escape") return "close-palette"
    }

    // Editor-level shortcuts (always active).
    if ((e.metaKey || e.ctrlKey) && e.key === "z" && !e.shiftKey) {
      return "undo"
    }
    if (
      (e.metaKey || e.ctrlKey) &&
      (e.key === "Z" || (e.key === "z" && e.shiftKey))
    ) {
      return "redo"
    }

    return null
  },
}

export const vimKeyBinding: KeyBinding = {
  match(e, ctx) {
    if (ctx.isPaletteOpen) {
      if (e.key === "Escape") return "close-palette"
      if (e.key === "[" && e.ctrlKey) return "close-palette"
    }

    if ((e.metaKey || e.ctrlKey) && e.key === "z" && !e.shiftKey) {
      return "undo"
    }
    if (
      (e.metaKey || e.ctrlKey) &&
      (e.key === "Z" || (e.key === "z" && e.shiftKey))
    ) {
      return "redo"
    }

    return null
  },
}
