/**
 * ContextMenu component
 * @countermeasure/web-components/components/context-menu
 */

export interface MenuItemDef {
  /** Stable unique identifier for the item. */
  id: string
  /** Visible label. Ignored when `separator` is `true`. */
  label: string
  /** Optional icon HTML rendered before the label. */
  icon?: string
  /**
   * Disable interaction.
   * @default false
   */
  disabled?: boolean
  /**
   * Render a non-interactive divider instead of an item.
   * @default false
   */
  separator?: boolean
  /** Optional keybinding hint rendered on the right. */
  shortcut?: string
  /** Function invoked when the item is activated. */
  handler?: () => void
}

export interface ContextMenuConfig {
  /** Menu items in display order. */
  items: MenuItemDef[]
}

export class ContextMenu {
  private readonly menuEl: HTMLElement
  private readonly items: MenuItemDef[]
  private activeIndex = -1
  private closed = false

  private constructor(x: number, y: number, config: ContextMenuConfig) {
    this.items = config.items

    this.menuEl = document.createElement('div')
    this.menuEl.className = 'cmm-context-menu'
    this.menuEl.style.position = 'fixed'
    this.menuEl.style.left = `${String(x)}px`
    this.menuEl.style.top = `${String(y)}px`
    this.menuEl.setAttribute('tabindex', '-1')

    this.renderItems()
    document.body.appendChild(this.menuEl)

    this.adjustPosition(x, y)

    document.addEventListener('click', this.handleDocumentClick)
    document.addEventListener('keydown', this.handleKeyDown)
    this.menuEl.focus()
  }

  static show(x: number, y: number, config: ContextMenuConfig): ContextMenu {
    return new ContextMenu(x, y, config)
  }

  close(): void {
    if (this.closed) {
      return
    }
    this.closed = true

    document.removeEventListener('click', this.handleDocumentClick)
    document.removeEventListener('keydown', this.handleKeyDown)

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

  private renderItems(): void {
    this.menuEl.innerHTML = ''
    let navigableIndex = 0

    for (const item of this.items) {
      if (item.separator === true) {
        const sep = document.createElement('div')
        sep.className = 'cmm-context-menu__separator'
        this.menuEl.appendChild(sep)
        continue
      }

      const itemEl = document.createElement('div')
      itemEl.className = 'cmm-context-menu__item'

      if (item.disabled === true) {
        itemEl.classList.add('cmm-context-menu__item--disabled')
      }

      if (navigableIndex === this.activeIndex) {
        itemEl.classList.add('cmm-context-menu__item--active')
      }

      itemEl.dataset.index = String(navigableIndex)

      if (item.icon) {
        const iconEl = document.createElement('span')
        iconEl.className = 'cmm-context-menu__icon'
        iconEl.textContent = item.icon
        itemEl.appendChild(iconEl)
      }

      const labelEl = document.createElement('span')
      labelEl.textContent = item.label
      itemEl.appendChild(labelEl)

      if (item.shortcut) {
        const shortcutEl = document.createElement('span')
        shortcutEl.className = 'cmm-context-menu__shortcut'
        shortcutEl.textContent = item.shortcut
        itemEl.appendChild(shortcutEl)
      }

      if (item.disabled !== true) {
        itemEl.addEventListener('click', (event: MouseEvent) => {
          event.stopPropagation()
          if (item.handler) {
            item.handler()
          }
          this.close()
        })
      }

      this.menuEl.appendChild(itemEl)
      navigableIndex += 1
    }
  }

  private adjustPosition(x: number, y: number): void {
    const rect = this.menuEl.getBoundingClientRect()
    const viewportWidth = window.innerWidth
    const viewportHeight = window.innerHeight

    let adjustedX = x
    let adjustedY = y

    if (x + rect.width > viewportWidth) {
      adjustedX = viewportWidth - rect.width
    }
    if (y + rect.height > viewportHeight) {
      adjustedY = viewportHeight - rect.height
    }
    if (adjustedX < 0) {
      adjustedX = 0
    }
    if (adjustedY < 0) {
      adjustedY = 0
    }

    this.menuEl.style.left = `${String(adjustedX)}px`
    this.menuEl.style.top = `${String(adjustedY)}px`
  }

  private getNavigableItems(): MenuItemDef[] {
    return this.items.filter(item => item.separator !== true)
  }

  private readonly handleDocumentClick = (event: MouseEvent): void => {
    const target = event.target
    if (target instanceof Node && !this.menuEl.contains(target)) {
      this.close()
    }
  }

  private readonly handleKeyDown = (event: KeyboardEvent): void => {
    const navigable = this.getNavigableItems()

    if (event.key === 'ArrowDown') {
      event.preventDefault()
      this.activeIndex = Math.min(this.activeIndex + 1, navigable.length - 1)
      this.renderItems()
    } else if (event.key === 'ArrowUp') {
      event.preventDefault()
      this.activeIndex = Math.max(this.activeIndex - 1, 0)
      this.renderItems()
    } else if (event.key === 'Enter') {
      event.preventDefault()
      if (this.activeIndex >= 0) {
        const selected = navigable[this.activeIndex]
        if (selected && selected.disabled !== true && selected.handler) {
          selected.handler()
        }
        this.close()
      }
    } else if (event.key === 'Escape') {
      event.preventDefault()
      this.close()
    }
  }
}
