/**
 * Menubar - Horizontal menu bar with dropdown sub-menus
 * @countermeasure/web-components/navigation/menubar
 */

import { resolveContainer, uniqueId } from '../../primitives/utils'
import { setSafeIconHtml } from '../../utils/sanitize'
import { type MenubarConfig, type MenubarItem, type MenuItem } from './types'

export type { MenubarConfig, MenubarItem, MenuItem } from './types'

export class Menubar {
  private readonly container: HTMLElement
  private readonly items: MenubarItem[]
  private readonly className: string | undefined
  private readonly menubarId: string

  private barEl: HTMLElement | null = null
  private openIndex = -1
  private activeItemIndex = -1
  private dropdownEl: HTMLElement | null = null
  private destroyed = false

  /** Bound event handlers for cleanup */
  private readonly handleDocumentClick: (e: MouseEvent) => void
  private readonly handleKeyDown: (e: KeyboardEvent) => void

  constructor(config: MenubarConfig) {
    this.container = resolveContainer(config.container)
    this.items = [...config.items]
    this.className = config.className
    this.menubarId = uniqueId('cmm-menubar')

    this.handleDocumentClick = (e: MouseEvent) => {
      const target = e.target
      if (
        target instanceof Node &&
        this.barEl &&
        !this.barEl.contains(target) &&
        this.dropdownEl &&
        !this.dropdownEl.contains(target)
      ) {
        this.closeMenu()
      }
    }

    this.handleKeyDown = (e: KeyboardEvent) => {
      this.onKeyDown(e)
    }

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

  /** Update the menubar items and re-render */
  update(items: MenubarItem[]): void {
    if (this.destroyed) {
      return
    }
    this.items.length = 0
    this.items.push(...items)
    this.closeMenu()
    this.render()
  }

  /** Remove all DOM elements and clean up */
  destroy(): void {
    if (this.destroyed) {
      return
    }
    this.destroyed = true
    this.closeMenu()
    document.removeEventListener('click', this.handleDocumentClick)
    document.removeEventListener('keydown', this.handleKeyDown)
    this.container.innerHTML = ''
    this.barEl = null
  }

  /** Close the currently open menu */
  private closeMenu(): void {
    if (this.dropdownEl) {
      this.dropdownEl.remove()
      this.dropdownEl = null
    }
    this.openIndex = -1
    this.activeItemIndex = -1
    this.updateTriggerStates()
  }

  /** Open a specific top-level menu by index */
  private openMenu(index: number): void {
    this.closeMenu()

    const menuItem = this.items[index]
    if (!menuItem || menuItem.children.length === 0) {
      return
    }

    this.openIndex = index
    this.activeItemIndex = 0
    this.updateTriggerStates()

    // Get the trigger button position
    const triggers = this.barEl?.querySelectorAll<HTMLElement>('.cmm-menubar__trigger')
    const triggerEl = triggers?.[index]
    if (!triggerEl) {
      return
    }

    const rect = triggerEl.getBoundingClientRect()

    // Create dropdown
    const dropdown = document.createElement('div')
    dropdown.className = 'cmm-menubar__dropdown'
    dropdown.setAttribute('role', 'menu')
    dropdown.id = uniqueId('cmm-menubar-dropdown')

    triggerEl.setAttribute('aria-controls', dropdown.id)

    this.renderMenuItems(dropdown, menuItem.children)

    document.body.appendChild(dropdown)
    this.dropdownEl = dropdown

    // Position below the trigger
    const dropdownRect = dropdown.getBoundingClientRect()
    let left = rect.left
    let top = rect.bottom + 2

    // Flip if near viewport edges
    if (left + dropdownRect.width > window.innerWidth) {
      left = window.innerWidth - dropdownRect.width - 4
    }
    if (top + dropdownRect.height > window.innerHeight) {
      top = rect.top - dropdownRect.height - 2
    }

    dropdown.style.left = `${String(Math.max(0, left))}px`
    dropdown.style.top = `${String(Math.max(0, top))}px`

    this.highlightActiveItem()
  }

  /** Render menu items into a container */
  private renderMenuItems(container: HTMLElement, items: MenuItem[]): void {
    for (const item of items) {
      if (item.type === 'separator') {
        const sep = document.createElement('div')
        sep.className = 'cmm-menubar__separator'
        sep.setAttribute('role', 'separator')
        container.appendChild(sep)
        continue
      }

      const itemEl = document.createElement('div')
      itemEl.className = 'cmm-menubar__item'
      itemEl.setAttribute('role', 'menuitem')

      if (item.disabled === true) {
        itemEl.classList.add('cmm-menubar__item--disabled')
        itemEl.setAttribute('aria-disabled', 'true')
      }

      if ((item.type === 'checkbox' || item.type === 'radio') && item.checked === true) {
        itemEl.classList.add('cmm-menubar__item--checked')

        const checkSpan = document.createElement('span')
        checkSpan.className = 'cmm-menubar__check'
        checkSpan.setAttribute('aria-hidden', 'true')
        checkSpan.textContent = item.type === 'radio' ? '\u25CF' : '\u2713'
        itemEl.appendChild(checkSpan)
      }

      if (item.icon) {
        const iconSpan = document.createElement('span')
        iconSpan.className = 'cmm-menubar__icon'
        iconSpan.setAttribute('aria-hidden', 'true')
        setSafeIconHtml(iconSpan, item.icon)
        itemEl.appendChild(iconSpan)
      }

      if (item.label) {
        const labelSpan = document.createElement('span')
        labelSpan.className = 'cmm-menubar__label'
        labelSpan.textContent = item.label
        itemEl.appendChild(labelSpan)
      }

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

      if (item.children && item.children.length > 0) {
        const arrowSpan = document.createElement('span')
        arrowSpan.className = 'cmm-menubar__arrow'
        arrowSpan.setAttribute('aria-hidden', 'true')
        arrowSpan.textContent = '\u25B8'
        itemEl.appendChild(arrowSpan)
      }

      if (item.disabled !== true) {
        itemEl.addEventListener('click', (e: MouseEvent) => {
          e.stopPropagation()
          if (item.onSelect) {
            item.onSelect()
          }
          this.closeMenu()
        })
      }

      container.appendChild(itemEl)
    }
  }

  /** Update aria-expanded on trigger buttons */
  private updateTriggerStates(): void {
    const triggers = this.barEl?.querySelectorAll<HTMLElement>('.cmm-menubar__trigger')
    if (!triggers) {
      return
    }

    triggers.forEach((trigger, i) => {
      const isOpen = i === this.openIndex
      trigger.setAttribute('aria-expanded', isOpen ? 'true' : 'false')
      if (isOpen) {
        trigger.classList.add('cmm-menubar__trigger--active')
      } else {
        trigger.classList.remove('cmm-menubar__trigger--active')
      }
    })
  }

  /** Highlight the active item in the dropdown */
  private highlightActiveItem(): void {
    if (!this.dropdownEl) {
      return
    }

    const menuItems = this.dropdownEl.querySelectorAll<HTMLElement>(
      '.cmm-menubar__item:not(.cmm-menubar__item--disabled)'
    )

    menuItems.forEach((el, i) => {
      if (i === this.activeItemIndex) {
        el.classList.add('cmm-menubar__item--active')
      } else {
        el.classList.remove('cmm-menubar__item--active')
      }
    })
  }

  /** Get the count of navigable items in the current dropdown */
  private getNavigableItemCount(): number {
    if (!this.dropdownEl) {
      return 0
    }
    return this.dropdownEl.querySelectorAll('.cmm-menubar__item:not(.cmm-menubar__item--disabled)')
      .length
  }

  /** Handle keyboard navigation */
  private onKeyDown(e: KeyboardEvent): void {
    if (this.destroyed) {
      return
    }

    // Only handle keys when the menubar or dropdown is focused
    const target = e.target
    if (
      target instanceof Node &&
      this.barEl &&
      !this.barEl.contains(target) &&
      this.dropdownEl?.contains(target) !== true
    ) {
      // Check if nothing is focused and the menu is open
      if (this.openIndex < 0) {
        return
      }
    }

    if (e.key === 'Escape') {
      if (this.openIndex >= 0) {
        e.preventDefault()
        this.closeMenu()
      }
      return
    }

    if (e.key === 'ArrowRight') {
      e.preventDefault()
      if (this.openIndex >= 0) {
        const nextIndex = (this.openIndex + 1) % this.items.length
        this.openMenu(nextIndex)
      }
      return
    }

    if (e.key === 'ArrowLeft') {
      e.preventDefault()
      if (this.openIndex >= 0) {
        const prevIndex = (this.openIndex - 1 + this.items.length) % this.items.length
        this.openMenu(prevIndex)
      }
      return
    }

    if (e.key === 'ArrowDown') {
      e.preventDefault()
      if (this.openIndex >= 0) {
        const count = this.getNavigableItemCount()
        if (count > 0) {
          this.activeItemIndex = Math.min(this.activeItemIndex + 1, count - 1)
          this.highlightActiveItem()
        }
      }
      return
    }

    if (e.key === 'ArrowUp') {
      e.preventDefault()
      if (this.openIndex >= 0) {
        this.activeItemIndex = Math.max(this.activeItemIndex - 1, 0)
        this.highlightActiveItem()
      }
      return
    }

    if (e.key === 'Home') {
      e.preventDefault()
      if (this.openIndex >= 0) {
        this.activeItemIndex = 0
        this.highlightActiveItem()
      }
      return
    }

    if (e.key === 'End') {
      e.preventDefault()
      if (this.openIndex >= 0) {
        const count = this.getNavigableItemCount()
        this.activeItemIndex = Math.max(0, count - 1)
        this.highlightActiveItem()
      }
      return
    }

    if (e.key === 'Enter' || e.key === ' ') {
      if (this.openIndex >= 0 && this.activeItemIndex >= 0 && this.dropdownEl) {
        e.preventDefault()
        const menuItems = this.dropdownEl.querySelectorAll<HTMLElement>(
          '.cmm-menubar__item:not(.cmm-menubar__item--disabled)'
        )
        const activeEl = menuItems[this.activeItemIndex]
        if (activeEl) {
          activeEl.click()
        }
      }
    }
  }

  /** Render the full menubar */
  private render(): void {
    this.container.innerHTML = ''

    const bar = document.createElement('div')
    bar.className = 'cmm-menubar'
    bar.id = this.menubarId
    bar.setAttribute('role', 'menubar')

    if (this.className) {
      bar.classList.add(this.className)
    }

    for (let i = 0; i < this.items.length; i++) {
      const menuItem = this.items[i]
      if (!menuItem) {
        continue
      }

      const trigger = document.createElement('button')
      trigger.className = 'cmm-menubar__trigger'
      trigger.type = 'button'
      trigger.textContent = menuItem.label
      trigger.setAttribute('aria-haspopup', 'true')
      trigger.setAttribute('aria-expanded', 'false')

      const index = i
      trigger.addEventListener('click', (e: MouseEvent) => {
        e.stopPropagation()
        if (this.openIndex === index) {
          this.closeMenu()
        } else {
          this.openMenu(index)
        }
      })

      trigger.addEventListener('mouseenter', () => {
        if (this.openIndex >= 0 && this.openIndex !== index) {
          this.openMenu(index)
        }
      })

      bar.appendChild(trigger)
    }

    this.container.appendChild(bar)
    this.barEl = bar
  }
}
