import componentStyles from '../../styles/component.styles.js'
import styles from './button.styles.js'
import { classMap } from 'lit/directives/class-map.js'
import { FormControlController, validValidityState } from '../../internal/form.js'
import { HasSlotController } from '../../internal/slot.js'
import { html, literal } from 'lit/static-html.js'
import { ifDefined } from 'lit/directives/if-defined.js'
import { property, query, state } from 'lit/decorators.js'
import { watch } from '../../internal/watch.js'
import TerraElement, { type TerraFormControl } from '../../internal/terra-element.js'
import type { CSSResultGroup } from 'lit'
import type TerraDialog from '../dialog/dialog.component.js'
import type {
    TerraDialogHideEvent,
    TerraDialogShowEvent,
} from '../../terra-ui-components.js'
import TerraIcon from '../icon/icon.component.js'

/**
 * @summary Buttons represent actions that are available to the user.
 * @documentation https://terra-ui.netlify.app/components/button
 * @status stable
 * @since 1.0
 *
 * @slot - The button's label.
 * @slot prefix - A presentational prefix icon or similar element.
 * @slot suffix - A presentational suffix icon or similar element.
 *
 * @event terra-blur - emitted when the button is blurred
 * @event terra-focus - emitted when the button is focused
 *
 * @csspart base - The component's base wrapper.
 * @csspart prefix - The container that wraps the prefix.
 * @csspart label - The button's label.
 * @csspart suffix - The container that wraps the suffix.
 * @csspart caret - The button's caret icon, an `<terra-icon>` element.
 * @csspart spinner - The spinner that shows when the button is in the loading state.
 */
export default class TerraButton extends TerraElement implements TerraFormControl {
    static styles: CSSResultGroup = [componentStyles, styles]
    static dependencies = {
        'terra-icon': TerraIcon,
    }

    private readonly formControlController = new FormControlController(this, {
        assumeInteractionOn: ['click'],
    })

    private readonly hasSlotController = new HasSlotController(
        this,
        '[default]',
        'prefix',
        'suffix'
    )

    @query('.button') button: HTMLButtonElement | HTMLLinkElement

    @state() private hasFocus = false
    @state() invalid = false
    @property() title = '' // make reactive to pass through

    /** The button's theme variant. */
    @property({ reflect: true }) variant:
        | 'default'
        | 'primary'
        | 'success'
        | 'warning'
        | 'danger'
        | 'text'
        | 'pagelink' =
        'primary' /* Renders the button as bold text with a trailing red circled arrow icon to indicate navigation to a new page. Links to external content (outside NASA.gov) will render an arrow pointing to the upper right to indicate that the user will be leaving the NASA site. */

    /** The button's size. */
    @property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium'

    /** Draws the button with a caret. Used to indicate that the button triggers a dropdown menu or similar behavior. */
    @property({ type: Boolean, reflect: true }) caret = false

    /** The button's shape. Used to control the radius edge shape when button is not in a terra-button-group. */
    @property({ reflect: true }) shape: 'square' | 'square-left' | 'square-right'

    /** Disables the button. */
    @property({ type: Boolean, reflect: true }) disabled = false

    /** Draws the button in a loading state. */
    @property({ type: Boolean, reflect: true }) loading = false

    /** Draws an outlined button. */
    @property({ type: Boolean, reflect: true }) outline = false

    /**
     * Draws a circular icon button. When this attribute is present, the button expects a single `<terra-icon>` in the
     * default slot.
     */
    @property({ type: Boolean, reflect: true }) circle = false

    /**
     * The type of button. Note that the default value is `button` instead of `submit`, which is opposite of how native
     * `<button>` elements behave. When the type is `submit`, the button will submit the surrounding form.
     */
    @property() type: 'button' | 'submit' | 'reset' = 'button'

    /**
     * The name of the button, submitted as a name/value pair with form data, but only when this button is the submitter.
     * This attribute is ignored when `href` is present.
     */
    @property() name = ''

    /**
     * The value of the button, submitted as a pair with the button's name as part of the form data, but only when this
     * button is the submitter. This attribute is ignored when `href` is present.
     */
    @property() value = ''

    /** When set, the underlying button will be rendered as an `<a>` with this `href` instead of a `<button>`. */
    @property() href = ''

    /** Tells the browser where to open the link. Only used when `href` is present. */
    @property() target: '_blank' | '_parent' | '_self' | '_top'

    /**
     * When using `href`, this attribute will map to the underlying link's `rel` attribute. Unlike regular links, the
     * default is `noreferrer noopener` to prevent security exploits. However, if you're using `target` to point to a
     * specific tab/window, this will prevent that from working correctly. You can remove or change the default value by
     * setting the attribute to an empty string or a value of your choice, respectively.
     */
    @property() rel = 'noreferrer noopener'

    /** Tells the browser to download the linked file as this filename. Only used when `href` is present. */
    @property() download?: string

    /**
     * The "form owner" to associate the button with. If omitted, the closest containing form will be used instead. The
     * value of this attribute must be an id of a form in the same document or shadow root as the button.
     */
    @property() form: string

    /** Used to override the form owner's `action` attribute. */
    @property({ attribute: 'formaction' }) formAction: string

    /** Used to override the form owner's `enctype` attribute.  */
    @property({ attribute: 'formenctype' })
    formEnctype:
        | 'application/x-www-form-urlencoded'
        | 'multipart/form-data'
        | 'text/plain'

    /** Used to override the form owner's `method` attribute.  */
    @property({ attribute: 'formmethod' }) formMethod: 'post' | 'get'

    /** Used to override the form owner's `novalidate` attribute. */
    @property({ attribute: 'formnovalidate', type: Boolean }) formNoValidate: boolean

    /** Used to override the form owner's `target` attribute. */
    @property({ attribute: 'formtarget' }) formTarget:
        | '_self'
        | '_blank'
        | '_parent'
        | '_top'
        | string

    /** Used to connect this button to a dqialog on the page. Clicking the button will toggle the dialog's visiblity */
    @property({ attribute: 'for-dialog' })
    forDialog?: string

    /** The ARIA role for the button. Defaults to 'button'. */
    @property({ reflect: true }) role: string | null = 'button'

    /** if button is used to control another element on the page, such as an accordion or dialog, this state communicates whether the controlled element is expanded */
    @state()
    expanded: boolean = false

    /** Gets the validity state object */
    get validity() {
        if (this.isButton()) {
            return (this.button as HTMLButtonElement).validity
        }

        return validValidityState
    }

    /** Gets the validation message */
    get validationMessage() {
        if (this.isButton()) {
            return (this.button as HTMLButtonElement).validationMessage
        }

        return ''
    }

    firstUpdated() {
        if (this.isButton()) {
            this.formControlController.updateValidity()
        }
    }

    connectedCallback() {
        super.connectedCallback()

        if (this.forDialog) {
            document.addEventListener(
                'terra-dialog-show',
                this.#handleDialogStateChange.bind(this)
            )
            document.addEventListener(
                'terra-dialog-hide',
                this.#handleDialogStateChange.bind(this)
            )
        }
    }

    disconnectedCallback() {
        super.disconnectedCallback()

        document.removeEventListener(
            'terra-dialog-show',
            this.#handleDialogStateChange.bind(this)
        )
        document.removeEventListener(
            'terra-dialog-hide',
            this.#handleDialogStateChange.bind(this)
        )
    }

    #handleDialogStateChange(e: TerraDialogShowEvent | TerraDialogHideEvent) {
        if (this.forDialog && this.forDialog === e.target.id) {
            // make sure the aria expanded property gets updated when the dialog hides/shows
            // https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-expanded#buttons
            this.expanded = (e.target as any).open
        }
    }

    private handleBlur() {
        this.hasFocus = false
        this.emit('terra-blur')
    }

    private handleFocus() {
        this.hasFocus = true
        this.emit('terra-focus')
    }

    private handleClick() {
        if (this.type === 'submit') {
            // @ts-ignore
            this.formControlController.submit(this)
        }

        if (this.type === 'reset') {
            // @ts-ignore
            this.formControlController.reset(this)
        }

        if (this.forDialog) {
            // this is a trigger for a dialog, go ahead and show the dialog
            // First try document.getElementById (for dialogs in light DOM)
            let el = document.getElementById(this.forDialog) as TerraDialog

            // If not found, search in shadow DOMs
            if (!el) {
                // Helper function to recursively search shadow roots
                const findInShadowRoots = (
                    root: Document | ShadowRoot
                ): TerraDialog | null => {
                    // Try getElementById if available (Document has it, ShadowRoot doesn't)
                    if (
                        'getElementById' in root &&
                        typeof root.getElementById === 'function'
                    ) {
                        const found = root.getElementById(this.forDialog!)
                        if (found) {
                            return found as TerraDialog
                        }
                    }

                    // Search all elements in this root
                    const allElements = root.querySelectorAll('*')
                    for (const element of allElements) {
                        // Check if this element is the dialog we're looking for
                        if (
                            element.id === this.forDialog &&
                            element.tagName.toLowerCase() === 'terra-dialog'
                        ) {
                            return element as TerraDialog
                        }

                        // If element has a shadow root, search it recursively
                        if (
                            element.shadowRoot &&
                            element.shadowRoot.mode === 'open'
                        ) {
                            const found = findInShadowRoots(element.shadowRoot)
                            if (found) {
                                return found
                            }
                        }
                    }

                    return null
                }

                const found = findInShadowRoots(document)
                if (found) {
                    el = found
                }
            }

            el?.show()
        }
    }

    private handleInvalid(event: Event) {
        this.formControlController.setValidity(false)
        this.formControlController.emitInvalidEvent(event)
    }

    private isButton() {
        return this.href ? false : true
    }

    private isLink() {
        return this.href ? true : false
    }

    @watch('disabled', { waitUntilFirstUpdate: true })
    handleDisabledChange() {
        if (this.isButton()) {
            // Disabled form controls are always valid
            this.formControlController.setValidity(this.disabled)
        }
    }

    @watch('href')
    handleHrefChange() {
        this.role = this.isLink() ? null : 'button'
    }

    /** Simulates a click on the button. */
    click() {
        this.button.click()
    }

    /** Sets focus on the button. */
    focus(options?: FocusOptions) {
        this.button.focus(options)
    }

    /** Removes focus from the button. */
    blur() {
        this.button.blur()
    }

    /** Checks for validity but does not show a validation message. Returns `true` when valid and `false` when invalid. */
    checkValidity() {
        if (this.isButton()) {
            return (this.button as HTMLButtonElement).checkValidity()
        }

        return true
    }

    /** Gets the associated form, if one exists. */
    getForm(): HTMLFormElement | null {
        return this.formControlController.getForm()
    }

    /** Checks for validity and shows the browser's validation message if the control is invalid. */
    reportValidity() {
        if (this.isButton()) {
            return (this.button as HTMLButtonElement).reportValidity()
        }

        return true
    }

    /** Sets a custom validation message. Pass an empty string to restore validity. */
    setCustomValidity(message: string) {
        if (this.isButton()) {
            ;(this.button as HTMLButtonElement).setCustomValidity(message)
            this.formControlController.updateValidity()
        }
    }

    setPageLinkIcon(href: string): string {
        /* Set icon for pageLink button based on whether link is internal or external */

        const parsedUrl = new URL(href)
        const linkDomain = parsedUrl.hostname
        const hostDomain = globalThis.location.hostname
        return linkDomain === hostDomain
            ? 'outline-arrow-right'
            : 'outline-arrow-up-right'
    }

    setFontSize(size: string): string {
        /* Scale icon size based on pageLink button size. */

        switch (size) {
            case 'small':
                return '.8rem'
            case 'medium':
                return '1.2rem'
            case 'large':
                return '1.5rem'
            default:
                return '1.5rem'
        }
    }

    render() {
        const isLink = this.isLink()
        const tag = isLink ? literal`a` : literal`button`

        /* eslint-disable lit/no-invalid-html */
        /* eslint-disable lit/binding-positions */
        return html`
      <${tag}
        part="base"
        class=${classMap({
            button: true,
            'button--default': this.variant === 'default',
            'button--primary': this.variant === 'primary',
            'button--success': this.variant === 'success',
            'button--warning': this.variant === 'warning',
            'button--danger': this.variant === 'danger',
            'button--text': this.variant === 'text',
            'button--pagelink': this.variant === 'pagelink',
            'button--small': this.size === 'small',
            'button--medium': this.size === 'medium',
            'button--large': this.size === 'large',
            'button--caret': this.caret,
            'button--square-left': this.shape === 'square-left',
            'button--square': this.shape === 'square',
            'button--square-right': this.shape === 'square-right',
            'button--circle': this.circle,
            'button--disabled': this.disabled,
            'button--focused': this.hasFocus,
            'button--loading': this.loading,
            'button--standard': !this.outline,
            'button--outline': this.outline,
            'button--has-label': this.hasSlotController.test('[default]'),
            'button--has-prefix': this.hasSlotController.test('prefix'),
            'button--has-suffix': this.hasSlotController.test('suffix'),
        })}
        ?disabled=${ifDefined(isLink ? undefined : this.disabled)}
        type=${ifDefined(isLink ? undefined : this.type)}
        title=${
            this
                .title /* An empty title prevents browser validation tooltips from appearing on hover */
        }
        name=${ifDefined(isLink ? undefined : this.name)}
        value=${ifDefined(isLink ? undefined : this.value)}
        href=${ifDefined(isLink ? this.href : undefined)}
        target=${ifDefined(isLink ? this.target : undefined)}
        download=${ifDefined(isLink ? this.download : undefined)}
        rel=${ifDefined(isLink ? this.rel : undefined)}
        role=${ifDefined(this.role)}
        aria-disabled=${this.disabled ? 'true' : 'false'}
        tabindex=${this.disabled ? '-1' : '0'}
        @blur=${this.handleBlur}
        @focus=${this.handleFocus}
        @invalid=${this.isButton() ? this.handleInvalid : null}
        @click=${this.handleClick}
        aria-controls=${ifDefined(this.forDialog || undefined)} 
        ?aria-haspopup=${this.forDialog ? 'dialog' : undefined}
        aria-expanded=${ifDefined(this.forDialog ? this.expanded : undefined)}
      >
        <slot name="prefix" part="prefix" class="button__prefix"></slot>
        <slot part="label" class="button__label"></slot>
        <slot name="suffix" part="suffix" class="button__suffix">
            ${
                this.variant == 'pagelink'
                    ? html`
                          <span>
                              <terra-icon
                                  name=${this.setPageLinkIcon(this.href)}
                                  library="heroicons"
                                  font-size=${this.setFontSize(this.size)}
                              ></terra-icon>
                          </span>
                      `
                    : ``
            }
        </slot>
        ${
            this.caret
                ? html`
                      <span part="caret" class="button__caret">
                          <terra-icon
                              name="solid-chevron-down"
                              library="heroicons"
                              aria-hidden="true"
                          ></terra-icon>
                      </span>
                  `
                : ''
        }
      </${tag}>
    `
    }
}
