/**
 * CommandPalette component - modal-style command palette
 * @countermeasure/web-components/components/search
 */

import { type CommandEntry, type CommandPaletteConfig } from './types'

function resolveKeybinding(keybinding: string): { ctrl: boolean; key: string } {
  const parts = keybinding.toLowerCase().split('+')
  let ctrl = false
  let key = ''
  for (const part of parts) {
    const trimmed = part.trim()
    if (trimmed === 'ctrl' || trimmed === 'meta' || trimmed === 'cmd') {
      ctrl = true
    } else {
      key = trimmed
    }
  }
  return { ctrl, key }
}

export class CommandPalette {
  private commands: CommandEntry[]
  private readonly overlayEl: HTMLElement
  private readonly containerEl: HTMLElement
  private readonly inputEl: HTMLInputElement
  private readonly resultsEl: HTMLElement
  private filteredCommands: CommandEntry[] = []
  private activeIndex = 0
  private destroyed = false

  constructor(config: CommandPaletteConfig) {
    this.commands = [...config.commands]

    this.overlayEl = document.createElement('div')
    this.overlayEl.className = 'cmm-command-palette__overlay'
    this.overlayEl.style.display = 'none'

    this.containerEl = document.createElement('div')
    this.containerEl.className = 'cmm-command-palette'

    this.inputEl = document.createElement('input')
    this.inputEl.type = 'text'
    this.inputEl.className = 'cmm-command-palette__input'
    if (config.placeholder !== undefined) {
      this.inputEl.placeholder = config.placeholder
    } else {
      this.inputEl.placeholder = 'Type a command...'
    }

    this.resultsEl = document.createElement('div')
    this.resultsEl.className = 'cmm-command-palette__results'

    this.containerEl.appendChild(this.inputEl)
    this.containerEl.appendChild(this.resultsEl)
    this.overlayEl.appendChild(this.containerEl)
    document.body.appendChild(this.overlayEl)

    this.inputEl.addEventListener('input', this.handleInput)
    this.inputEl.addEventListener('keydown', this.handleKeyDown)
    this.overlayEl.addEventListener('click', this.handleOverlayClick)

    const keybinding = config.keybinding ?? 'ctrl+k'
    document.addEventListener('keydown', this.createGlobalKeyHandler(keybinding))
  }

  open(): void {
    if (this.destroyed) {
      return
    }
    this.overlayEl.style.display = ''
    this.inputEl.value = ''
    this.activeIndex = 0
    this.filteredCommands = [...this.commands]
    this.renderResults()
    this.inputEl.focus()
  }

  close(): void {
    this.overlayEl.style.display = 'none'
    this.inputEl.value = ''
    this.filteredCommands = []
    this.resultsEl.innerHTML = ''
  }

  setCommands(commands: CommandEntry[]): void {
    this.commands = [...commands]
  }

  destroy(): void {
    if (this.destroyed) {
      return
    }
    this.destroyed = true

    this.inputEl.removeEventListener('input', this.handleInput)
    this.inputEl.removeEventListener('keydown', this.handleKeyDown)
    this.overlayEl.removeEventListener('click', this.handleOverlayClick)
    document.removeEventListener('keydown', this.globalKeyHandler)

    if (this.overlayEl.parentNode) {
      this.overlayEl.parentNode.removeChild(this.overlayEl)
    }
  }

  private globalKeyHandler: (event: KeyboardEvent) => void = () => {
    // Placeholder; replaced in createGlobalKeyHandler
  }

  private createGlobalKeyHandler(keybinding: string): (event: KeyboardEvent) => void {
    const parsed = resolveKeybinding(keybinding)
    this.globalKeyHandler = (event: KeyboardEvent) => {
      const ctrlPressed = event.ctrlKey || event.metaKey
      if (parsed.ctrl && ctrlPressed && event.key.toLowerCase() === parsed.key) {
        event.preventDefault()
        if (this.overlayEl.style.display === 'none') {
          this.open()
        } else {
          this.close()
        }
      }
    }
    return this.globalKeyHandler
  }

  private readonly handleInput = (): void => {
    const query = this.inputEl.value.toLowerCase().trim()

    if (query.length === 0) {
      this.filteredCommands = [...this.commands]
    } else {
      this.filteredCommands = this.commands.filter(cmd => cmd.label.toLowerCase().includes(query))
    }

    this.activeIndex = 0
    this.renderResults()
  }

  private renderResults(): void {
    this.resultsEl.innerHTML = ''

    if (this.filteredCommands.length === 0) {
      return
    }

    const groups = new Map<string, CommandEntry[]>()
    const ungrouped: CommandEntry[] = []

    for (const cmd of this.filteredCommands) {
      if (cmd.group) {
        let groupList = groups.get(cmd.group)
        if (!groupList) {
          groupList = []
          groups.set(cmd.group, groupList)
        }
        groupList.push(cmd)
      } else {
        ungrouped.push(cmd)
      }
    }

    let index = 0

    const createItem = (cmd: CommandEntry, itemIndex: number): HTMLElement => {
      const item = document.createElement('div')
      item.className = 'cmm-command-palette__item'
      if (itemIndex === this.activeIndex) {
        item.classList.add('cmm-command-palette__item--active')
      }

      const labelText = document.createElement('span')
      labelText.textContent = cmd.label
      item.appendChild(labelText)

      if (cmd.keybinding) {
        const kbEl = document.createElement('span')
        kbEl.className = 'cmm-command-palette__keybinding'
        kbEl.textContent = cmd.keybinding
        item.appendChild(kbEl)
      }

      item.addEventListener('click', () => {
        this.executeCommand(cmd)
      })

      return item
    }

    if (ungrouped.length > 0) {
      for (const cmd of ungrouped) {
        this.resultsEl.appendChild(createItem(cmd, index))
        index += 1
      }
    }

    for (const [groupName, groupCommands] of groups) {
      const groupHeader = document.createElement('div')
      groupHeader.className = 'cmm-command-palette__group'
      groupHeader.textContent = groupName
      this.resultsEl.appendChild(groupHeader)

      for (const cmd of groupCommands) {
        this.resultsEl.appendChild(createItem(cmd, index))
        index += 1
      }
    }
  }

  private executeCommand(cmd: CommandEntry): void {
    this.close()
    cmd.handler()
  }

  private readonly handleKeyDown = (event: KeyboardEvent): void => {
    if (event.key === 'ArrowDown') {
      event.preventDefault()
      if (this.filteredCommands.length > 0) {
        this.activeIndex = Math.min(this.activeIndex + 1, this.filteredCommands.length - 1)
        this.renderResults()
      }
    } else if (event.key === 'ArrowUp') {
      event.preventDefault()
      this.activeIndex = Math.max(this.activeIndex - 1, 0)
      this.renderResults()
    } else if (event.key === 'Enter') {
      event.preventDefault()
      const selected = this.filteredCommands[this.activeIndex]
      if (selected) {
        this.executeCommand(selected)
      }
    } else if (event.key === 'Escape') {
      this.close()
    }
  }

  private readonly handleOverlayClick = (event: MouseEvent): void => {
    if (event.target === this.overlayEl) {
      this.close()
    }
  }
}
