/**
 * @license
 * Copyright 2023 Tyler Technologies, Inc. 
 * License: Apache-2.0
 */
import { addClass, getShadowElement, removeClass, getActiveElement } from '@tylertech/forge-core';
import { RADIO_CONSTANTS } from './radio-constants';
import { ForgeRipple, ForgeRippleFoundation } from '../ripple';
import { createUserInteractionListener } from '../core/utils';
export class RadioAdapter {
    constructor(_component) {
        this._component = _component;
        this._rootElement = getShadowElement(this._component, RADIO_CONSTANTS.selectors.RADIO);
        this._containerElement = getShadowElement(this._component, RADIO_CONSTANTS.selectors.WRAPPER);
    }
    // ForgeRippleCapableSurface
    get root() {
        return this._rootElement;
    }
    get unbounded() {
        return true;
    }
    get disabled() {
        return this._isDisabled();
    }
    connect() {
        const labelElement = this._component.querySelector(RADIO_CONSTANTS.selectors.LABEL);
        if (labelElement) {
            labelElement.setAttribute(RADIO_CONSTANTS.attributes.SLOT, RADIO_CONSTANTS.selectors.LABEL);
        }
    }
    async deferRippleInitialization() {
        if (typeof this._destroyUserInteractionListener === 'function') {
            this._destroyUserInteractionListener();
        }
        const { userInteraction, destroy } = createUserInteractionListener(this._rootElement);
        this._destroyUserInteractionListener = destroy;
        const { type } = await userInteraction;
        this._destroyUserInteractionListener = undefined;
        if (!this._component.isConnected) {
            return;
        }
        this._rippleInstance = this._createRipple();
        if (type === 'focusin') {
            this._rippleInstance.handleFocus();
        }
    }
    destroyRipple() {
        var _a;
        if (typeof this._destroyUserInteractionListener === 'function') {
            this._destroyUserInteractionListener();
            this._destroyUserInteractionListener = undefined;
        }
        (_a = this._rippleInstance) === null || _a === void 0 ? void 0 : _a.destroy();
        this._rippleInstance = undefined;
    }
    setHostAttribute(name, value = '') {
        this._component.setAttribute(name, value);
    }
    removeHostAttribute(name) {
        this._component.removeAttribute(name);
    }
    addRootClass(className) {
        this._rootElement.classList.add(className);
    }
    removeRootClass(className) {
        this._rootElement.classList.remove(className);
    }
    addContainerClass(className) {
        this._containerElement.classList.add(className);
    }
    removeContainerClass(className) {
        this._containerElement.classList.remove(className);
    }
    syncDisabledStateWithInput() {
        if (this.inputDisabled) {
            this.addRootClass(RADIO_CONSTANTS.classes.DISABLED);
            this.removeRootClass(RADIO_CONSTANTS.classes.ENABLED);
            this.addContainerClass(RADIO_CONSTANTS.classes.WRAPPER_DISABLED);
        }
        else {
            this.addRootClass(RADIO_CONSTANTS.classes.ENABLED);
            this.removeRootClass(RADIO_CONSTANTS.classes.DISABLED);
            this.removeContainerClass(RADIO_CONSTANTS.classes.WRAPPER_DISABLED);
        }
    }
    syncFocusedStateWithInput() {
        const activeElement = getActiveElement(this._component.ownerDocument);
        if (activeElement === this._inputElement) {
            this.addRootClass(RADIO_CONSTANTS.classes.FOCUSED);
        }
        else {
            this.removeRootClass(RADIO_CONSTANTS.classes.FOCUSED);
        }
    }
    syncCheckedStateWithInput() {
        if (this.inputChecked) {
            this.addRootClass(RADIO_CONSTANTS.classes.CHECKED);
        }
        else {
            this.removeRootClass(RADIO_CONSTANTS.classes.CHECKED);
        }
    }
    addInputEventListener(event, callback) {
        if (!this._inputElement) {
            return;
        }
        this._inputElement.addEventListener(event, callback);
    }
    removeInputEventListener(event, callback) {
        if (!this._inputElement) {
            return;
        }
        this._inputElement.removeEventListener(event, callback);
    }
    addInputDisabledAttributeChangeListener(callback) {
        this._setupInputDisabledAttributeMutationObserver(callback);
    }
    removeInputDisabledAttributeChangeListener() {
        this._cleanupInputDisabledAttributeMutationObserver();
    }
    // Get the collection of all forge-radios under the containing radiogroup ancestor.
    getRadiosFromContainingRadiogroup() {
        const radioGroup = this._getContainingRadioGroup(this._component);
        if (radioGroup == null) {
            return null;
        }
        return radioGroup.querySelectorAll(RADIO_CONSTANTS.elementName);
    }
    syncRadiogroupCheckStyles() {
        const radios = this.getRadiosFromContainingRadiogroup();
        if (radios) {
            radios.forEach(x => x.syncCheckedState());
        }
    }
    extendNativeInputSetter(callback) {
        if (!this._inputElement) {
            return;
        }
        const nativeInput = this._inputElement;
        const inputProto = Object.getPrototypeOf(nativeInput);
        RADIO_CONSTANTS.inputProperties.forEach(controlState => {
            const desc = Object.getOwnPropertyDescriptor(inputProto, controlState);
            // We have to check for this descriptor, since some browsers (Safari) don't support its return.
            // See: https://bugs.webkit.org/show_bug.cgi?id=49739
            if (!this._validPropertyDescriptor(desc)) {
                return;
            }
            if (desc) {
                const nativeInputDesc = {
                    configurable: desc.configurable,
                    enumerable: desc.enumerable,
                    get: desc.get,
                    set: (state) => {
                        var _a;
                        (_a = desc.set) === null || _a === void 0 ? void 0 : _a.call(nativeInput, state);
                        callback();
                    }
                };
                Object.defineProperty(nativeInput, controlState, nativeInputDesc);
            }
        });
    }
    revertNativeInputSetter() {
        if (!this._inputElement) {
            return;
        }
        const nativeInput = this._inputElement;
        const inputProto = Object.getPrototypeOf(nativeInput);
        RADIO_CONSTANTS.inputProperties.forEach(controlState => {
            const desc = Object.getOwnPropertyDescriptor(inputProto, controlState);
            // We have to check for this descriptor, since some browsers (Safari) don't support its return.
            // See: https://bugs.webkit.org/show_bug.cgi?id=49739
            if (!this._validPropertyDescriptor(desc)) {
                return;
            }
            Object.defineProperty(nativeInput, controlState, desc);
        });
    }
    _validPropertyDescriptor(inputPropDesc) {
        return !!inputPropDesc && typeof inputPropDesc.set === 'function';
    }
    // Recursively crawl up the node tree looking for the containing radiogroup ancestor element.
    _getContainingRadioGroup(element) {
        if (!element || !element.parentElement) {
            throw new Error('Unable to locate ancestor element with role="radiogroup". When using radios, a parent with this attribute must be applied.');
        }
        const parentElement = element.parentElement;
        const roleValue = parentElement.getAttribute(RADIO_CONSTANTS.attributes.ROLE);
        if (!!roleValue && roleValue.toLocaleLowerCase() === RADIO_CONSTANTS.attributes.RADIOGROUP_ROLE.toLocaleLowerCase()) {
            return parentElement;
        }
        return this._getContainingRadioGroup(parentElement);
    }
    _setupInputDisabledAttributeMutationObserver(callback) {
        if (!this._inputElement) {
            return;
        }
        this._cleanupInputDisabledAttributeMutationObserver();
        const mutationCallback = (mutationRecords) => this._inputDisabledAttributeMutationCallback(mutationRecords, callback);
        this._inputAttributeMutationObserver = new MutationObserver(mutationCallback);
        const mutationOptions = {
            attributes: true,
            attributeOldValue: true,
            attributeFilter: ['disabled']
        };
        this._inputAttributeMutationObserver.observe(this._inputElement, mutationOptions);
    }
    _cleanupInputDisabledAttributeMutationObserver() {
        if (this._inputAttributeMutationObserver) {
            this._inputAttributeMutationObserver.disconnect();
            delete this._inputAttributeMutationObserver;
        }
    }
    _inputDisabledAttributeMutationCallback(mutationRecords, callback) {
        for (const mutationRecord of mutationRecords) {
            // Skip:
            //   - Non-attribute mutations.
            //   - Mutations without an attribute name.
            //   - Mutations without a matching attribute name.
            //   - Mutations that did not change the attribute value.
            if (mutationRecord.type !== 'attributes'
                || !mutationRecord.attributeName
                || mutationRecord.attributeName !== 'disabled'
                || mutationRecord.target[mutationRecord.attributeName] === mutationRecord.oldValue) {
                continue;
            }
            callback();
        }
    }
    get inputDisabled() {
        if (!this._inputElement) {
            return false;
        }
        return this._inputElement.disabled;
    }
    get inputChecked() {
        if (!this._inputElement) {
            return false;
        }
        return this._inputElement.checked;
    }
    get _inputElement() {
        if (!this._nativeInputElement) {
            this._nativeInputElement = this._component.querySelector(RADIO_CONSTANTS.selectors.RADIO_INPUT);
        }
        return this._nativeInputElement;
    }
    _isDisabled() {
        return this._inputElement ? this._inputElement.disabled : false;
    }
    _createRipple() {
        const adapter = Object.assign(Object.assign({}, ForgeRipple.createAdapter(this)), { deregisterInteractionHandler: (evtType, handler) => {
                if (this._inputElement) {
                    this._inputElement.removeEventListener(evtType, handler, { passive: true });
                }
            }, isSurfaceActive: () => this._inputElement ? this._inputElement.matches(':active') : false, isUnbounded: () => Boolean(this.unbounded), registerInteractionHandler: (evtType, handler) => {
                if (this._inputElement) {
                    this._inputElement.addEventListener(evtType, handler, { passive: true });
                }
            }, isSurfaceDisabled: () => this._inputElement ? this._inputElement.disabled : false, addClass: (className) => addClass(className, this._rootElement), removeClass: (className) => removeClass(className, this._rootElement), updateCssVariable: (varName, value) => this._rootElement.style.setProperty(varName, value) });
        return new ForgeRipple(this._rootElement, new ForgeRippleFoundation(adapter));
    }
}
