import { html } from 'lit'
import { query } from 'lit/decorators.js'
import componentStyles from '../../styles/component.styles.js'
import styles from './menu.styles.js'
import type { CSSResultGroup } from 'lit'
import type TerraMenuItem from '../menu-item/menu-item.component.js'
import TerraElement from '../../internal/terra-element.js'

export interface MenuSelectEventDetail {
    item: TerraMenuItem
}

/**
 * @summary Menus provide a list of options for the user to choose from.
 * @documentation https://terra-ui.netlify.app/components/menu
 * @status stable
 * @since 1.0
 *
 * @slot - The menu's content, typically `<terra-menu-item>` elements.
 *
 * @event terra-select - Emitted when a menu item is selected. The event detail contains the selected menu item.
 */
export default class TerraMenu extends TerraElement {
    static styles: CSSResultGroup = [componentStyles, styles]

    @query('slot') defaultSlot: HTMLSlotElement

    connectedCallback() {
        super.connectedCallback()
        this.setAttribute('role', 'menu')
    }

    private handleClick(event: MouseEvent) {
        const menuItemTypes = ['menuitem', 'menuitemcheckbox']

        const composedPath = event.composedPath()
        const target = composedPath.find((el: Element) =>
            menuItemTypes.includes(el?.getAttribute?.('role') || '')
        )

        if (!target) return

        const closestMenu = composedPath.find(
            (el: Element) => el?.getAttribute?.('role') === 'menu'
        )
        const clickHasSubmenu = closestMenu !== this

        // Make sure we're the menu thats supposed to be handling the click event.
        if (clickHasSubmenu) return

        // This isn't true. But we use it for TypeScript checks below.
        const item = target as TerraMenuItem

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

        this.emit('terra-select', { detail: { item } })
    }

    private handleKeyDown(event: KeyboardEvent) {
        // Make a selection when pressing enter or space
        if (event.key === 'Enter' || event.key === ' ') {
            const item = this.getCurrentItem()
            event.preventDefault()
            event.stopPropagation()

            // Simulate a click to support @click handlers on menu items that also work with the keyboard
            item?.click()
        }

        // Move the selection when pressing down or up
        else if (['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key)) {
            const items = this.getAllItems()
            const activeItem = this.getCurrentItem()
            let index = activeItem ? items.indexOf(activeItem) : 0

            if (items.length > 0) {
                event.preventDefault()
                event.stopPropagation()

                if (event.key === 'ArrowDown') {
                    index++
                } else if (event.key === 'ArrowUp') {
                    index--
                } else if (event.key === 'Home') {
                    index = 0
                } else if (event.key === 'End') {
                    index = items.length - 1
                }

                if (index < 0) {
                    index = items.length - 1
                }
                if (index > items.length - 1) {
                    index = 0
                }

                this.setCurrentItem(items[index])
                items[index].focus()
            }
        }
    }

    private handleMouseDown(event: MouseEvent) {
        const target = event.target as HTMLElement

        if (this.isMenuItem(target)) {
            this.setCurrentItem(target as TerraMenuItem)
        }
    }

    private handleSlotChange() {
        const items = this.getAllItems()

        // Reset the roving tab index when the slotted items change
        if (items.length > 0) {
            this.setCurrentItem(items[0])
        }
    }

    private isMenuItem(item: HTMLElement) {
        return (
            item.tagName.toLowerCase() === 'terra-menu-item' ||
            ['menuitem', 'menuitemcheckbox', 'menuitemradio'].includes(
                item.getAttribute('role') ?? ''
            )
        )
    }

    /** @internal Gets all slotted menu items, ignoring dividers, headers, and other elements. */
    getAllItems() {
        return [...this.defaultSlot.assignedElements({ flatten: true })].filter(
            (el: HTMLElement) => {
                if (el.inert || !this.isMenuItem(el)) {
                    return false
                }
                return true
            }
        ) as TerraMenuItem[]
    }

    /**
     * @internal Gets the current menu item, which is the menu item that has `tabindex="0"` within the roving tab index.
     * The menu item may or may not have focus, but for keyboard interaction purposes it's considered the "active" item.
     */
    getCurrentItem() {
        return this.getAllItems().find(i => i.getAttribute('tabindex') === '0')
    }

    /**
     * @internal Sets the current menu item to the specified element. This sets `tabindex="0"` on the target element and
     * `tabindex="-1"` to all other items. This method must be called prior to setting focus on a menu item.
     */
    setCurrentItem(item: TerraMenuItem) {
        const items = this.getAllItems()

        // Update tab indexes
        items.forEach(i => {
            i.setAttribute('tabindex', i === item ? '0' : '-1')
        })
    }

    render() {
        return html`
            <slot
                @slotchange=${this.handleSlotChange}
                @click=${this.handleClick}
                @keydown=${this.handleKeyDown}
                @mousedown=${this.handleMouseDown}
            ></slot>
        `
    }
}
