import { html, PropertyValues, TemplateResult } from 'lit'
import { property, state, queryAssignedElements } from 'lit/decorators.js'
import { classMap } from 'lit/directives/class-map.js'
import { nanoid } from 'nanoid'
import UiList from '../../list/internals/List.js'
import UiMenuItem from './MenuItem.js'
import UiSubMenu from './SubMenu.js'
import { setDisabled } from '../../../lib/disabled.js'
import UiListItem from '../../list/internals/ListItem.js'
import { bound } from '../../../decorators/bound.js'

/**
 * Material Design 3 Menu component with sub-menu support.
 * Uses Popover API and Anchor Positioning API for modern positioning.
 *
 * @fires select - Dispatched when a menu item is selected
 * @fires close - Dispatched when the menu is closed
 */
export default class Menu extends UiList {
  /**
   * Whether the menu is currently open
   * @attribute
   */
  @property({ type: Boolean, reflect: true }) accessor open = false

  /**
   * Whether the menu is disabled
   * @attribute
   */
  @property({ type: Boolean, reflect: true }) accessor disabled = false

  /**
   * Currently active sub-menu
   */
  @state() accessor activeSubMenu: UiSubMenu | null = null

  /**
   * Assigned menu items from light DOM
   */
  @queryAssignedElements({ selector: 'ui-menu-item' }) protected accessor assignedMenuItems!: UiMenuItem[]

  constructor() {
    super()
    this.selector = 'ui-menu-item'
    this.ariaExpanded = 'false'
    this.addEventListener('beforetoggle', this.handleBeforeToggle.bind(this))
  }

  override connectedCallback(): void {
    super.connectedCallback()
    this.setAttribute('role', 'menu')
    this.setAttribute('tabindex', '-1')
    if (!this.hasAttribute('popover')) {
      this.setAttribute('popover', 'auto')
    }
    if (!this.id) {
      this.id = nanoid()
    }
  }

  protected override updated(changedProperties: PropertyValues<this>): void {
    super.updated(changedProperties)

    if (changedProperties.has('disabled')) {
      setDisabled(this, this.disabled)
    }
  }

  override togglePopover(force?: boolean): boolean {
    this.open = !this.open
    this.ariaExpanded = String(this.open)
    this.tabIndex = this.open ? 0 : -1
    const result = super.togglePopover(force)
    if (this.open) {
      this.positionMenu()
      this.focus()
    }
    return result
  }

  /**
   * Shows the menu
   */
  show(): void {
    this.tabIndex = 0 // Make menu focusable
    this.ariaExpanded = 'true'
    this.showPopover()
    this.open = true
    this.positionMenu()
    this.focus()
    this.dispatchEvent(new CustomEvent('open', { bubbles: false, composed: true }))
  }

  /**
   * Hides the menu
   */
  hide(): void {
    this.tabIndex = -1
    this.ariaExpanded = 'false'
    this.hidePopover()
    this.open = false
    this.closeSubMenu()
    this.dispatchEvent(new CustomEvent('close', { bubbles: false, composed: true }))
  }

  positionMenu(): void {
    // when there's more space above the anchor, position the menu above it
    const box = this.getBoundingClientRect()
    // Now, we determine, whether to position the menu above or below the anchor
    // in a way, that if we have enough space below the anchor, we position it below,
    // otherwise we position it above the anchor.

    // our starting point is the anchor being positioned below the anchor
    const menuBottom = box.top + box.height
    if (menuBottom <= innerHeight) {
      // if the menu fits below the anchor, we leave it as is.
      return
    }
    // we do not make association from the menu to the anchor, so we make an assumption
    // that the anchor is 40px high, which is the default height of a button.
    // it can be different, but this is a good starting point.
    const anchorHeight = 40
    const anchorBottom = box.top
    const anchorTop = anchorBottom - anchorHeight
    const menuHeight = box.height

    const spaceBelow = innerHeight - anchorBottom
    const spaceAbove = anchorTop

    const diffBelow = spaceBelow - menuHeight
    const diffAbove = spaceAbove - menuHeight
    // The initial check ensures the menu does not fit below. Now, check if it fits above.
    if (diffAbove >= 0) {
      this.style.setProperty('position-area', 'top span-right')
    } else if (diffAbove > diffBelow) {
      // It doesn't fit in either direction. Choose the one with less overflow (larger, i.e., less negative, diff).
      this.style.setProperty('position-area', 'top span-right')
      this.style.maxHeight = `${spaceAbove}px`
    } else {
      this.style.setProperty('position-area', 'bottom span-right')
      this.style.maxHeight = `${spaceBelow}px`
    }
  }

  /**
   * Handles beforetoggle event from popover
   */
  protected handleBeforeToggle(e: Event): void {
    const toggleEvent = e as ToggleEvent
    if (toggleEvent.newState === 'closed') {
      this.open = false
      this.closeSubMenu()
    }
  }

  /**
   * Handles keyboard navigation for the menu
   */
  override handleKeydown(e: KeyboardEvent): void {
    if (!this.open || e.defaultPrevented) return

    switch (e.key) {
      case 'Escape':
        e.preventDefault()
        this.hide()
        break
      case 'ArrowRight':
        e.preventDefault()
        this.openSubMenu()
        break
      case 'ArrowLeft':
        e.preventDefault()
        this.closeSubMenu()
        break
      default:
        // Let the parent UiList handle other keys
        super.handleKeydown(e)
    }
  }

  @bound
  handleSubMenuSelect(e: CustomEvent): void {
    super.notifySelect(e.detail.item, e.detail.index)
  }

  /**
   * Opens the sub-menu for the currently active item
   */
  protected openSubMenu(): void {
    const activeItem = this.activeListItem as UiMenuItem
    if (activeItem?.hasSubMenu) {
      activeItem.openSubMenu()
    }
  }

  /**
   * Closes the currently open sub-menu
   */
  closeSubMenu(): void {
    if (this.activeSubMenu) {
      this.activeSubMenu.removeEventListener('select', this.handleSubMenuSelect as EventListener)
      this.activeSubMenu.hide()
      this.activeSubMenu = null
    }
  }

  /**
   * Sets the active sub-menu
   */
  setActiveSubMenu(subMenu: UiSubMenu | null): void {
    this.activeSubMenu = subMenu
    subMenu?.addEventListener('select', this.handleSubMenuSelect as EventListener)
  }

  override notifySelect(item: UiListItem, index?: number): boolean {
    this.hide()
    return super.notifySelect(item, index)
  }

  /**
   * Handles sub-menu opening
   */
  protected handleSubMenuOpen(e: CustomEvent): void {
    const subMenu = e.detail.subMenu
    this.setActiveSubMenu(subMenu)
  }

  /**
   * Handles slot changes to update menu items
   */
  protected handleSlotChange(): void {
    // Update the items list when slot content changes
    this.updateItems()
  }

  override render(): TemplateResult {
    const classes = classMap({
      'menu-container': true,
    })

    return html`
      <div class=${classes}>
        <slot @slotchange=${this.handleSlotChange}></slot>
      </div>
    `
  }
}
