/**
 * DropdownMenu - Dropdown action menu with nested sub-menus
 * @countermeasure/web-components/navigation/dropdown-menu
 */

import { resolveContainer, uniqueId } from '../../primitives/utils'
import { setSafeIconHtml } from '../../utils/sanitize'
import {
  type DropdownMenuConfig,
  type DropdownMenuItem,
  type DropdownPlacement,
  type MenuItem,
  type MenuRadioGroup,
} from './types'

export type {
  DropdownMenuConfig,
  DropdownMenuItem,
  DropdownPlacement,
  MenuItem,
  MenuRadioGroup,
  MenuRadioItem,
} from './types'

/** Query all focusable menu items within a container */
function getNavigableItems(container: HTMLElement): HTMLElement[] {
  return Array.from(
    container.querySelectorAll<HTMLElement>(
      '.cmm-dropdown-menu__item:not(.cmm-dropdown-menu__item--disabled)'
    )
  )
}

export class DropdownMenu {
  private readonly trigger: HTMLElement
  private readonly items: DropdownMenuItem[]
  private readonly placement: DropdownPlacement
  private readonly className: string | undefined
  private readonly onOpenCallback: (() => void) | undefined
  private readonly onCloseCallback: (() => void) | undefined
  private readonly menuId: string

  private menuEl: HTMLElement | null = null
  private subMenuEl: HTMLElement | null = null
  private opened = false
  private destroyed = false
  private activeIndex = -1
  private subMenuActiveIndex = -1

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

  constructor(config: DropdownMenuConfig) {
    resolveContainer(config.container)
    this.trigger = config.trigger
    this.items = [...config.items]
    this.placement = config.placement ?? 'bottom-start'
    this.className = config.className
    this.onOpenCallback = config.onOpen
    this.onCloseCallback = config.onClose
    this.menuId = uniqueId('cmm-dropdown-menu')

    this.handleTriggerClick = () => {
      this.toggle()
    }

    this.handleDocumentClick = (e: MouseEvent) => {
      const target = e.target
      if (
        target instanceof Node &&
        this.menuEl &&
        !this.menuEl.contains(target) &&
        !this.trigger.contains(target) &&
        this.subMenuEl?.contains(target) !== true
      ) {
        this.close()
      }
    }

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

    this.trigger.setAttribute('aria-haspopup', 'menu')
    this.trigger.setAttribute('aria-expanded', 'false')
    this.trigger.addEventListener('click', this.handleTriggerClick)
  }

  /** Check whether the menu is currently open */
  isOpen(): boolean {
    return this.opened
  }

  /** Open the dropdown menu */
  open(): void {
    if (this.destroyed || this.opened) {
      return
    }

    this.opened = true
    this.activeIndex = 0
    this.trigger.setAttribute('aria-expanded', 'true')

    this.createMenuElement()
    this.positionMenu()

    // Delay adding document listeners to avoid immediate close
    setTimeout(() => {
      document.addEventListener('click', this.handleDocumentClick)
    }, 0)
    document.addEventListener('keydown', this.handleKeyDown)

    this.highlightActiveItem()
    this.onOpenCallback?.()
  }

  /** Close the dropdown menu */
  close(): void {
    if (!this.opened) {
      return
    }

    this.opened = false
    this.activeIndex = -1
    this.trigger.setAttribute('aria-expanded', 'false')

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

    this.closeSubMenu()

    if (this.menuEl) {
      this.menuEl.remove()
      this.menuEl = null
    }

    this.onCloseCallback?.()
  }

  /** Toggle the menu open/closed */
  toggle(): void {
    if (this.opened) {
      this.close()
    } else {
      this.open()
    }
  }

  /** Update the menu items */
  update(items: DropdownMenuItem[]): void {
    this.items.length = 0
    this.items.push(...items)

    if (this.opened) {
      this.close()
      this.open()
    }
  }

  /** Remove all listeners and clean up */
  destroy(): void {
    if (this.destroyed) {
      return
    }
    this.destroyed = true

    this.close()
    this.trigger.removeEventListener('click', this.handleTriggerClick)
    this.trigger.removeAttribute('aria-haspopup')
    this.trigger.removeAttribute('aria-expanded')
  }

  /** Close any open sub-menu */
  private closeSubMenu(): void {
    if (this.subMenuEl) {
      this.subMenuEl.remove()
      this.subMenuEl = null
    }
    this.subMenuActiveIndex = -1
  }

  /** Create the menu DOM element */
  private createMenuElement(): void {
    if (this.menuEl) {
      this.menuEl.remove()
    }

    const menu = document.createElement('div')
    menu.className = 'cmm-dropdown-menu'
    menu.id = this.menuId
    menu.setAttribute('role', 'menu')
    menu.setAttribute('tabindex', '-1')

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

    this.renderItems(menu, this.items)

    document.body.appendChild(menu)
    this.menuEl = menu
  }

  /** Render menu items into a container element */
  private renderItems(container: HTMLElement, items: DropdownMenuItem[]): void {
    for (const item of items) {
      if (this.isRadioGroup(item)) {
        this.renderRadioGroup(container, item)
        continue
      }

      const menuItem = item

      if (menuItem.type === 'separator') {
        const sep = document.createElement('div')
        sep.className = 'cmm-dropdown-menu__separator'
        sep.setAttribute('role', 'separator')
        container.appendChild(sep)
        continue
      }

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

      if (menuItem.disabled === true) {
        itemEl.classList.add('cmm-dropdown-menu__item--disabled')
        itemEl.setAttribute('aria-disabled', 'true')
      }

      // Checkbox indicator
      if (menuItem.type === 'checkbox') {
        const checkSpan = document.createElement('span')
        checkSpan.className = 'cmm-dropdown-menu__check'
        checkSpan.setAttribute('aria-hidden', 'true')
        checkSpan.textContent = menuItem.checked === true ? '\u2713' : ''
        itemEl.appendChild(checkSpan)

        if (menuItem.checked === true) {
          itemEl.classList.add('cmm-dropdown-menu__item--checked')
        }
      }

      // Icon
      if (menuItem.icon) {
        const iconSpan = document.createElement('span')
        iconSpan.className = 'cmm-dropdown-menu__icon'
        iconSpan.setAttribute('aria-hidden', 'true')
        setSafeIconHtml(iconSpan, menuItem.icon)
        itemEl.appendChild(iconSpan)
      }

      // Label
      if (menuItem.label) {
        const labelSpan = document.createElement('span')
        labelSpan.className = 'cmm-dropdown-menu__label'
        labelSpan.textContent = menuItem.label
        itemEl.appendChild(labelSpan)
      }

      // Shortcut
      if (menuItem.shortcut) {
        const shortcutSpan = document.createElement('span')
        shortcutSpan.className = 'cmm-dropdown-menu__shortcut'
        shortcutSpan.textContent = menuItem.shortcut
        itemEl.appendChild(shortcutSpan)
      }

      // Sub-menu arrow
      if (menuItem.children && menuItem.children.length > 0) {
        const arrowSpan = document.createElement('span')
        arrowSpan.className = 'cmm-dropdown-menu__arrow'
        arrowSpan.setAttribute('aria-hidden', 'true')
        arrowSpan.textContent = '\u25B8'
        itemEl.appendChild(arrowSpan)

        itemEl.addEventListener('mouseenter', () => {
          if (menuItem.children) {
            this.openSubMenu(itemEl, menuItem.children)
          }
        })
      } else {
        itemEl.addEventListener('mouseenter', () => {
          this.closeSubMenu()
        })
      }

      // Click handler
      if (menuItem.disabled !== true && (!menuItem.children || menuItem.children.length === 0)) {
        itemEl.addEventListener('click', (e: MouseEvent) => {
          e.stopPropagation()

          if (menuItem.type === 'checkbox') {
            menuItem.checked = menuItem.checked !== true
          }

          if (menuItem.onSelect) {
            menuItem.onSelect()
          }
          this.close()
        })
      }

      container.appendChild(itemEl)
    }
  }

  /** Render a radio group */
  private renderRadioGroup(container: HTMLElement, group: MenuRadioGroup): void {
    const groupEl = document.createElement('div')
    groupEl.className = 'cmm-dropdown-menu__radio-group'
    groupEl.setAttribute('role', 'group')

    for (const radioItem of group.items) {
      const itemEl = document.createElement('div')
      itemEl.className = 'cmm-dropdown-menu__item'
      itemEl.setAttribute('role', 'menuitemradio')

      if (radioItem.disabled === true) {
        itemEl.classList.add('cmm-dropdown-menu__item--disabled')
        itemEl.setAttribute('aria-disabled', 'true')
      }

      const isChecked = group.value === radioItem.value
      itemEl.setAttribute('aria-checked', isChecked ? 'true' : 'false')

      const checkSpan = document.createElement('span')
      checkSpan.className = 'cmm-dropdown-menu__check'
      checkSpan.setAttribute('aria-hidden', 'true')
      checkSpan.textContent = isChecked ? '\u25CF' : ''
      itemEl.appendChild(checkSpan)

      if (isChecked) {
        itemEl.classList.add('cmm-dropdown-menu__item--checked')
      }

      const labelSpan = document.createElement('span')
      labelSpan.className = 'cmm-dropdown-menu__label'
      labelSpan.textContent = radioItem.label
      itemEl.appendChild(labelSpan)

      if (radioItem.disabled !== true) {
        itemEl.addEventListener('click', (e: MouseEvent) => {
          e.stopPropagation()
          group.value = radioItem.value
          if (group.onChange) {
            group.onChange(radioItem.value)
          }
          this.close()
        })
      }

      groupEl.appendChild(itemEl)
    }

    container.appendChild(groupEl)
  }

  /** Open a sub-menu next to the parent item */
  private openSubMenu(parentEl: HTMLElement, children: MenuItem[]): void {
    this.closeSubMenu()

    const sub = document.createElement('div')
    sub.className = 'cmm-dropdown-menu cmm-dropdown-menu--sub'
    sub.setAttribute('role', 'menu')

    this.renderItems(sub, children)
    document.body.appendChild(sub)
    this.subMenuEl = sub

    // Position the sub-menu
    const parentRect = parentEl.getBoundingClientRect()
    const subRect = sub.getBoundingClientRect()

    let left = parentRect.right + 2
    let top = parentRect.top

    // Flip left if near right edge
    if (left + subRect.width > window.innerWidth) {
      left = parentRect.left - subRect.width - 2
    }

    // Adjust top if near bottom edge
    if (top + subRect.height > window.innerHeight) {
      top = window.innerHeight - subRect.height - 4
    }

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

    this.subMenuActiveIndex = 0
    this.highlightSubMenuActiveItem()
  }

  /** Position the main menu relative to the trigger */
  private positionMenu(): void {
    if (!this.menuEl) {
      return
    }

    const triggerRect = this.trigger.getBoundingClientRect()
    const menuRect = this.menuEl.getBoundingClientRect()
    const viewportWidth = window.innerWidth
    const viewportHeight = window.innerHeight

    let top: number
    let left: number

    const isTop = this.placement.startsWith('top')
    const isEnd = this.placement.endsWith('end')

    if (isTop) {
      top = triggerRect.top - menuRect.height - 4
    } else {
      top = triggerRect.bottom + 4
    }

    if (isEnd) {
      left = triggerRect.right - menuRect.width
    } else {
      left = triggerRect.left
    }

    // Auto-flip vertically
    if (!isTop && top + menuRect.height > viewportHeight) {
      top = triggerRect.top - menuRect.height - 4
    } else if (isTop && top < 0) {
      top = triggerRect.bottom + 4
    }

    // Clamp horizontally
    left = Math.max(4, Math.min(left, viewportWidth - menuRect.width - 4))
    top = Math.max(4, Math.min(top, viewportHeight - menuRect.height - 4))

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

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

    const navigable = getNavigableItems(this.menuEl)
    navigable.forEach((el, i) => {
      if (i === this.activeIndex) {
        el.classList.add('cmm-dropdown-menu__item--active')
      } else {
        el.classList.remove('cmm-dropdown-menu__item--active')
      }
    })
  }

  /** Highlight the active item in the sub-menu */
  private highlightSubMenuActiveItem(): void {
    if (!this.subMenuEl) {
      return
    }

    const navigable = getNavigableItems(this.subMenuEl)
    navigable.forEach((el, i) => {
      if (i === this.subMenuActiveIndex) {
        el.classList.add('cmm-dropdown-menu__item--active')
      } else {
        el.classList.remove('cmm-dropdown-menu__item--active')
      }
    })
  }

  /** Handle keyboard navigation */
  private onKeyDown(e: KeyboardEvent): void {
    // Handle sub-menu navigation if a sub-menu is open
    if (this.subMenuEl) {
      if (e.key === 'ArrowDown') {
        e.preventDefault()
        const count = getNavigableItems(this.subMenuEl).length
        if (count > 0) {
          this.subMenuActiveIndex = Math.min(this.subMenuActiveIndex + 1, count - 1)
          this.highlightSubMenuActiveItem()
        }
        return
      }

      if (e.key === 'ArrowUp') {
        e.preventDefault()
        this.subMenuActiveIndex = Math.max(this.subMenuActiveIndex - 1, 0)
        this.highlightSubMenuActiveItem()
        return
      }

      if (e.key === 'ArrowLeft' || e.key === 'Escape') {
        e.preventDefault()
        this.closeSubMenu()
        return
      }

      if (e.key === 'Enter' || e.key === ' ') {
        e.preventDefault()
        const navigable = getNavigableItems(this.subMenuEl)
        const activeEl = navigable[this.subMenuActiveIndex]
        if (activeEl) {
          activeEl.click()
        }
        return
      }

      return
    }

    // Main menu navigation
    if (e.key === 'Escape') {
      e.preventDefault()
      this.close()
      this.trigger.focus()
      return
    }

    if (e.key === 'ArrowDown') {
      e.preventDefault()
      if (this.menuEl) {
        const count = getNavigableItems(this.menuEl).length
        if (count > 0) {
          this.activeIndex = Math.min(this.activeIndex + 1, count - 1)
          this.highlightActiveItem()
        }
      }
      return
    }

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

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

    if (e.key === 'End') {
      e.preventDefault()
      if (this.menuEl) {
        const count = getNavigableItems(this.menuEl).length
        this.activeIndex = Math.max(0, count - 1)
        this.highlightActiveItem()
      }
      return
    }

    if (e.key === 'ArrowRight') {
      e.preventDefault()
      // Open sub-menu if the active item has children
      if (this.menuEl) {
        const navigable = getNavigableItems(this.menuEl)
        const activeEl = navigable[this.activeIndex]
        if (activeEl !== undefined && activeEl.querySelector('.cmm-dropdown-menu__arrow')) {
          const flatItems = this.getFlatMenuItems()
          const flatItem = flatItems[this.activeIndex]
          if (flatItem !== undefined && 'children' in flatItem) {
            this.openSubMenu(activeEl, flatItem.children)
          }
        }
      }
      return
    }

    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault()
      if (this.menuEl) {
        const navigable = getNavigableItems(this.menuEl)
        const activeEl = navigable[this.activeIndex]
        if (activeEl) {
          // If has children, open sub-menu instead
          if (activeEl.querySelector('.cmm-dropdown-menu__arrow')) {
            const flatItems = this.getFlatMenuItems()
            const flatItem = flatItems[this.activeIndex]
            if (flatItem !== undefined && 'children' in flatItem) {
              this.openSubMenu(activeEl, flatItem.children)
            }
          } else {
            activeEl.click()
          }
        }
      }
    }
  }

  /** Get a flat list of navigable menu item data (excluding separators) */
  private getFlatMenuItems(): DropdownMenuItem[] {
    const result: DropdownMenuItem[] = []

    for (const item of this.items) {
      if (this.isRadioGroup(item)) {
        for (const radioItem of item.items) {
          result.push({
            type: 'item',
            label: radioItem.label,
            value: radioItem.value,
            disabled: radioItem.disabled,
          } as MenuItem)
        }
      } else {
        const menuItem = item
        if (menuItem.type !== 'separator') {
          result.push(menuItem)
        }
      }
    }

    return result
  }

  /** Type guard for radio group */
  private isRadioGroup(item: DropdownMenuItem): item is MenuRadioGroup {
    return item.type === 'radio-group' && 'items' in item
  }
}
