import { classMap } from 'lit/directives/class-map.js'
import { html } from 'lit'
import { property, query, state } from 'lit/decorators.js'
import { watch } from '../../internal/watch.js'
import componentStyles from '../../styles/component.styles.js'
import TerraElement from '../../internal/terra-element.js'
import TerraIcon from '../icon/icon.component.js'
import styles from './option.styles.js'
import type { CSSResultGroup } from 'lit'

/**
 * @summary Options define the selectable items within various form controls such as [select](/components/select).
 * @documentation https://terra-ui.netlify.app/components/option
 * @status stable
 * @since 1.0
 *
 * @dependency terra-icon
 *
 * @slot - The option's label.
 * @slot prefix - Used to prepend an icon or similar element to the menu item.
 * @slot suffix - Used to append an icon or similar element to the menu item.
 *
 * @csspart checked-icon - The checked icon, an `<terra-icon>` element.
 * @csspart base - The component's base wrapper.
 * @csspart label - The option's label.
 * @csspart prefix - The container that wraps the prefix.
 * @csspart suffix - The container that wraps the suffix.
 *
 * @cssproperty --terra-option-* - All option design tokens from horizon.css are supported.
 */
export default class TerraOption extends TerraElement {
    static styles: CSSResultGroup = [componentStyles, styles]
    static dependencies = { 'terra-icon': TerraIcon }

    private isInitialized = false

    @query('.option__label') defaultSlot: HTMLSlotElement

    @state() current = false // the user has keyed into the option, but hasn't selected it yet (shows a highlight)
    @state() selected = false // the option is selected and has aria-selected="true"
    @state() hasHover = false // we need this because Safari doesn't honor :hover styles while dragging

    /**
     * The option's value. When selected, the containing form control will receive this value. The value must be unique
     * from other options in the same group. Values may not contain spaces, as spaces are used as delimiters when listing
     * multiple values.
     */
    @property({ reflect: true }) value = ''

    /** Draws the option in a disabled state, preventing selection. */
    @property({ type: Boolean, reflect: true }) disabled = false

    connectedCallback() {
        super.connectedCallback()
        this.setAttribute('role', 'option')
        this.setAttribute('aria-selected', 'false')
    }

    private handleDefaultSlotChange() {
        if (this.isInitialized) {
            // When the label changes, tell the controller to update
            customElements.whenDefined('terra-select').then(() => {
                const controller = this.closest('terra-select')
                if (controller) {
                    controller.handleDefaultSlotChange()
                }
            })
        } else {
            this.isInitialized = true
        }
    }

    private handleMouseEnter() {
        this.hasHover = true
    }

    private handleMouseLeave() {
        this.hasHover = false
    }

    @watch('disabled')
    handleDisabledChange() {
        this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false')
    }

    @watch('selected')
    handleSelectedChange() {
        this.setAttribute('aria-selected', this.selected ? 'true' : 'false')
    }

    @watch('value')
    handleValueChange() {
        // Ensure the value is a string. This ensures the next line doesn't error and allows framework users to pass numbers
        // instead of requiring them to cast the value to a string.
        if (typeof this.value !== 'string') {
            this.value = String(this.value)
        }

        if (this.value.includes(' ')) {
            console.error(
                `Option values cannot include a space. All spaces have been replaced with underscores.`,
                this
            )
            this.value = this.value.replace(/ /g, '_')
        }
    }

    /** Returns a plain text label based on the option's content. */
    getTextLabel() {
        const nodes = this.childNodes
        let label = ''

        ;[...nodes].forEach(node => {
            if (node.nodeType === Node.ELEMENT_NODE) {
                if (!(node as HTMLElement).hasAttribute('slot')) {
                    label += (node as HTMLElement).textContent
                }
            }

            if (node.nodeType === Node.TEXT_NODE) {
                label += node.textContent
            }
        })

        return label.trim()
    }

    render() {
        return html`
            <div
                part="base"
                class=${classMap({
                    option: true,
                    'option--current': this.current,
                    'option--disabled': this.disabled,
                    'option--selected': this.selected,
                    'option--hover': this.hasHover,
                })}
                @mouseenter=${this.handleMouseEnter}
                @mouseleave=${this.handleMouseLeave}
            >
                <terra-icon
                    part="checked-icon"
                    class="option__check"
                    name="outline-check"
                    library="heroicons"
                    aria-hidden="true"
                ></terra-icon>
                <slot part="prefix" name="prefix" class="option__prefix"></slot>
                <slot
                    part="label"
                    class="option__label"
                    @slotchange=${this.handleDefaultSlotChange}
                ></slot>
                <slot part="suffix" name="suffix" class="option__suffix"></slot>
            </div>
        `
    }
}
