/**
 * @license
 * Copyright Tyler Technologies, Inc. 
 * License: Apache-2.0
 */
import { getShadowElement } from '@tylertech/forge-core';
import { tylIconArrowDropDown } from '@tylertech/tyler-icons/standard';
import { BaseAdapter } from '../../core/base/base-adapter';
import { FOCUS_INDICATOR_CONSTANTS } from '../../focus-indicator';
import { STATE_LAYER_CONSTANTS } from '../../state-layer';
import { BASE_BUTTON_CONSTANTS } from './base-button-constants';
import { BUTTON_FORM_ATTRIBUTES, cloneAttributes } from '../../core/utils/reflect-utils';
import { internals, setDefaultAria } from '../../constants';
import { supportsPopover } from '../../core/utils/feature-detection';
export class BaseButtonAdapter extends BaseAdapter {
    constructor(component) {
        super(component);
        this._rootElement = getShadowElement(this._component, BASE_BUTTON_CONSTANTS.selectors.ROOT);
        this._focusIndicatorElement = getShadowElement(this._component, FOCUS_INDICATOR_CONSTANTS.elementName);
        this._stateLayerElement = getShadowElement(this._component, STATE_LAYER_CONSTANTS.elementName);
        this._defaultSlotElement = getShadowElement(this._component, BASE_BUTTON_CONSTANTS.selectors.DEFAULT_SLOT);
        this._endSlotElement = getShadowElement(this._component, BASE_BUTTON_CONSTANTS.selectors.END_SLOT);
    }
    get hasSlottedAnchor() {
        return !!this.getSlottedAnchor;
    }
    get getSlottedAnchor() {
        return this._defaultSlotElement.assignedElements({ flatten: true }).find(el => el.tagName === 'A');
    }
    initialize() {
        const slottedAnchor = this.getSlottedAnchor;
        this._component[setDefaultAria]({
            role: !!slottedAnchor ? null : 'button'
        }, {
            setAttribute: !this._component.hasAttribute('role') || !!slottedAnchor
        });
        this._rootElement.classList.toggle(BASE_BUTTON_CONSTANTS.classes.WITH_ANCHOR, !!slottedAnchor);
        if (!!slottedAnchor) {
            this._component.removeAttribute('tabindex');
        }
        else if (!this._component.disabled && !this._component.hasAttribute('tabindex')) {
            this._component.setAttribute('tabindex', '0');
        }
        this._focusIndicatorElement.targetElement = !!slottedAnchor ? slottedAnchor : this._component;
        this._stateLayerElement.targetElement = !!slottedAnchor ? slottedAnchor : this._component;
    }
    setDisabled(value) {
        if (value) {
            this._focusIndicatorElement.remove();
            this._stateLayerElement.remove();
        }
        else {
            this._rootElement.append(this._focusIndicatorElement, this._stateLayerElement);
        }
        const hasSlottedAnchor = this.hasSlottedAnchor;
        if (hasSlottedAnchor) {
            this._component.removeAttribute('tabindex');
            this._component[setDefaultAria]({ ariaDisabled: null }, { setAttribute: true });
        }
        else {
            if (value) {
                this._component.removeAttribute('tabindex');
            }
            else {
                this._component.setAttribute('tabindex', '0');
            }
            this._component[setDefaultAria]({ ariaDisabled: value ? 'true' : null });
        }
    }
    clickHost() {
        // Calling click() on the prototype ensures we don't end up in an infinite
        // recursion since the host overrides the HTMLElement.click() method
        HTMLElement.prototype.click.call(this._component);
    }
    forceFocusVisible() {
        this._focusIndicatorElement.active = true;
    }
    clickFormButton(type) {
        var _a;
        if (!this._component.form) {
            return; // Nothing for us to do if there is no form element associated to us
        }
        if (type === 'submit') {
            // We need to set the form value to the button value before submitting the form
            this._component[internals].setFormValue(this._component.value);
            // We don't use a real <button> since the host is the semantic button, so for
            // the "submit" button type we need to create a temporary button and click it
            // to trigger the form submission
            const tempBtn = document.createElement('button');
            tempBtn.type = type;
            cloneAttributes(this._component, tempBtn, BUTTON_FORM_ATTRIBUTES);
            // form.requestSubmit(submitter) does not work with form associated custom
            // elements. This patches the dispatched submit event to add the correct `submitter`.
            // See https://github.com/WICG/webcomponents/issues/814
            this._component.form.addEventListener('submit', evt => {
                Object.defineProperty(evt, 'submitter', {
                    configurable: true,
                    enumerable: true,
                    get: () => this._component
                });
            }, { capture: true, once: true });
            this._component.insertAdjacentElement('afterend', tempBtn);
            tempBtn.click();
            tempBtn.remove();
        }
        else if (type === 'reset') {
            (_a = this._component.form) === null || _a === void 0 ? void 0 : _a.reset();
        }
    }
    hasPopoverTarget() {
        return this._component.hasAttribute('popovertarget') || !!this._component.popoverTargetElement;
    }
    /**
     * We are emulating native behavior here since custom elements do not support integrating with the
     * native popover element. This method will attempt to find the popover target element and handle
     * its `popovertargetaction`
     */
    managePopover() {
        var _a, _b;
        if (this._component.form || !this.hasPopoverTarget() || !supportsPopover()) {
            return false;
        }
        const popoverElement = this._locatePopoverTargetElement();
        if (!popoverElement) {
            return false;
        }
        const action = (_b = (_a = this._component.getAttribute('popovertargetaction')) !== null && _a !== void 0 ? _a : this._component.popoverTargetAction) !== null && _b !== void 0 ? _b : 'toggle';
        const isPopoverOpen = popoverElement.matches(':popover-open');
        switch (action) {
            case 'show':
                if (!isPopoverOpen) {
                    popoverElement.showPopover();
                }
                return true;
            case 'hide':
                if (isPopoverOpen) {
                    popoverElement.hidePopover();
                }
                return false;
            case 'toggle':
            default:
                const result = popoverElement.togglePopover();
                // When the popover is open and is using an "auto" popover mode, we need to handle
                // clicking ourselves again to close without re-opening the popover. We do this by
                // adding a click listener to the component that will stop propagation of the event
                // and then remove itself after the first click (or when the popover is closed externally).
                if (result && popoverElement.popover !== 'manual') {
                    const listener = evt => {
                        evt.stopPropagation();
                        // The popover will already be closed via pointer, but we need to ensure that
                        // it closes via keyboard as well so we perform the following check regardless
                        if (popoverElement.matches(':popover-open')) {
                            popoverElement.hidePopover();
                        }
                    };
                    this._component.addEventListener('click', listener, { capture: true, once: true });
                    popoverElement.addEventListener('beforetoggle', async () => {
                        await new Promise(resolve => setTimeout(resolve)); // Wait a cycle to allow the click event to propagate first
                        this._component.removeEventListener('click', listener, { capture: true });
                    }, { once: true });
                }
                return result;
        }
    }
    toggleDefaultPopoverIcon(value) {
        if (value) {
            // We support a built-in "popover icon" for convenience that can be used to indicate that the button will show a popover
            const hasIcon = this._endSlotElement.querySelector('forge-icon');
            if (!hasIcon) {
                const icon = document.createElement('forge-icon');
                icon.classList.add(BASE_BUTTON_CONSTANTS.classes.POPOVER_ICON);
                icon.name = tylIconArrowDropDown.name;
                this._endSlotElement.append(icon);
            }
        }
        else {
            const icon = this._endSlotElement.querySelector('forge-icon');
            icon === null || icon === void 0 ? void 0 : icon.remove();
        }
    }
    animateStateLayer() {
        var _a;
        if (this._stateLayerElement.disabled) {
            return;
        }
        (_a = this._stateLayerElement) === null || _a === void 0 ? void 0 : _a.playAnimation();
    }
    addDefaultSlotChangeListener(listener) {
        this._defaultSlotElement.addEventListener('slotchange', listener);
    }
    _locatePopoverTargetElement() {
        var _a;
        let popoverElement = (_a = this._component.popoverTargetElement) !== null && _a !== void 0 ? _a : null;
        if (!popoverElement) {
            const rootNode = this._component.ownerDocument.getRootNode();
            if (!rootNode) {
                return null;
            }
            const targetId = this._component.getAttribute('popovertarget');
            popoverElement = rootNode.querySelector(`#${targetId}`);
        }
        return popoverElement;
    }
}
