import Element from '../element/Element.js';
import * as PropertySymbol from '../../PropertySymbol.js';
import CSSStyleDeclaration from '../../css/declaration/CSSStyleDeclaration.js';
import PointerEvent from '../../event/events/PointerEvent.js';
import NodeTypeEnum from '../node/NodeTypeEnum.js';
import HTMLElementUtility from './HTMLElementUtility.js';
import DOMStringMap from '../../dom/DOMStringMap.js';
import WindowBrowserContext from '../../window/WindowBrowserContext.js';
/**
 * HTML Element.
 *
 * Reference:
 * https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement.
 */
export default class HTMLElement extends Element {
    // Events
    oncopy = null;
    oncut = null;
    onpaste = null;
    oninvalid = null;
    onanimationcancel = null;
    onanimationend = null;
    onanimationiteration = null;
    onanimationstart = null;
    onbeforeinput = null;
    oninput = null;
    onchange = null;
    ongotpointercapture = null;
    onlostpointercapture = null;
    onpointercancel = null;
    onpointerdown = null;
    onpointerenter = null;
    onpointerleave = null;
    onpointermove = null;
    onpointerout = null;
    onpointerover = null;
    onpointerup = null;
    ontransitioncancel = null;
    ontransitionend = null;
    ontransitionrun = null;
    ontransitionstart = null;
    // Internal properties
    [PropertySymbol.accessKey] = '';
    [PropertySymbol.contentEditable] = 'inherit';
    [PropertySymbol.isContentEditable] = false;
    [PropertySymbol.offsetHeight] = 0;
    [PropertySymbol.offsetWidth] = 0;
    [PropertySymbol.offsetLeft] = 0;
    [PropertySymbol.offsetTop] = 0;
    [PropertySymbol.clientHeight] = 0;
    [PropertySymbol.clientWidth] = 0;
    [PropertySymbol.clientLeft] = 0;
    [PropertySymbol.clientTop] = 0;
    [PropertySymbol.style] = null;
    [PropertySymbol.dataset] = null;
    // Private properties
    #customElementDefineCallback = null;
    /**
     * Returns access key.
     *
     * @returns Access key.
     */
    get accessKey() {
        return this[PropertySymbol.accessKey];
    }
    /**
     * Sets access key.
     *
     * @param accessKey Access key.
     */
    set accessKey(accessKey) {
        this[PropertySymbol.accessKey] = accessKey;
    }
    /**
     * Returns content editable.
     *
     * @returns Content editable.
     */
    get contentEditable() {
        return this[PropertySymbol.contentEditable];
    }
    /**
     * Sets content editable.
     *
     * @param contentEditable Content editable.
     */
    set contentEditable(contentEditable) {
        this[PropertySymbol.contentEditable] = contentEditable;
    }
    /**
     * Returns is content editable.
     *
     * @returns Is content editable.
     */
    get isContentEditable() {
        return this[PropertySymbol.isContentEditable];
    }
    /**
     * Returns offset height.
     *
     * @returns Offset height.
     */
    get offsetHeight() {
        return this[PropertySymbol.offsetHeight];
    }
    /**
     * Returns offset width.
     *
     * @returns Offset width.
     */
    get offsetWidth() {
        return this[PropertySymbol.offsetWidth];
    }
    /**
     * Returns offset left.
     *
     * @returns Offset left.
     */
    get offsetLeft() {
        return this[PropertySymbol.offsetLeft];
    }
    /**
     * Returns offset top.
     *
     * @returns Offset top.
     */
    get offsetTop() {
        return this[PropertySymbol.offsetTop];
    }
    /**
     * Returns client height.
     *
     * @returns Client height.
     */
    get clientHeight() {
        return this[PropertySymbol.clientHeight];
    }
    /**
     * Returns client width.
     *
     * @returns Client width.
     */
    get clientWidth() {
        return this[PropertySymbol.clientWidth];
    }
    /**
     * Returns client left.
     *
     * @returns Client left.
     */
    get clientLeft() {
        return this[PropertySymbol.clientLeft];
    }
    /**
     * Returns client top.
     *
     * @returns Client top.
     */
    get clientTop() {
        return this[PropertySymbol.clientTop];
    }
    /**
     * Returns tab index.
     *
     * @returns Tab index.
     */
    get tabIndex() {
        const tabIndex = this.getAttribute('tabindex');
        return tabIndex !== null ? Number(tabIndex) : -1;
    }
    /**
     * Returns tab index.
     *
     * @param tabIndex Tab index.
     */
    set tabIndex(tabIndex) {
        if (tabIndex === -1) {
            this.removeAttribute('tabindex');
        }
        else {
            this.setAttribute('tabindex', String(tabIndex));
        }
    }
    /**
     * Returns inner text, which is the rendered appearance of text.
     *
     * @see https://html.spec.whatwg.org/multipage/dom.html#the-innertext-idl-attribute
     * @returns Inner text.
     */
    get innerText() {
        if (!this[PropertySymbol.isConnected]) {
            return this.textContent;
        }
        let result = '';
        for (const childNode of this[PropertySymbol.nodeArray]) {
            if (childNode[PropertySymbol.nodeType] === NodeTypeEnum.elementNode) {
                const childElement = childNode;
                const computedStyle = this[PropertySymbol.window].getComputedStyle(childElement);
                if (childElement[PropertySymbol.tagName] !== 'SCRIPT' &&
                    childElement[PropertySymbol.tagName] !== 'STYLE') {
                    const display = computedStyle.display;
                    if (display !== 'none') {
                        const textTransform = computedStyle.textTransform;
                        if ((display === 'block' || display === 'flex') && result) {
                            result += '\n';
                        }
                        let text = childElement.innerText;
                        switch (textTransform) {
                            case 'uppercase':
                                text = text.toUpperCase();
                                break;
                            case 'lowercase':
                                text = text.toLowerCase();
                                break;
                            case 'capitalize':
                                text = text.replace(/(^|\s)\S/g, (l) => l.toUpperCase());
                                break;
                        }
                        result += text;
                    }
                }
            }
            else if (childNode[PropertySymbol.nodeType] === NodeTypeEnum.textNode) {
                result += childNode.textContent.replace(/[\n\r]/, '');
            }
        }
        return result;
    }
    /**
     * Sets the inner text, which is the rendered appearance of text.
     *
     * @see https://html.spec.whatwg.org/multipage/dom.html#the-innertext-idl-attribute
     * @param innerText Inner text.
     */
    set innerText(text) {
        const childNodes = this[PropertySymbol.nodeArray];
        while (childNodes.length) {
            this.removeChild(childNodes[0]);
        }
        const texts = text.split(/[\n\r]/);
        const ownerDocument = this[PropertySymbol.ownerDocument];
        for (let i = 0, max = texts.length; i < max; i++) {
            if (i !== 0) {
                this.appendChild(ownerDocument.createElement('br'));
            }
            this.appendChild(ownerDocument.createTextNode(texts[i]));
        }
    }
    /**
     * Returns outer text.
     *
     * @see https://html.spec.whatwg.org/multipage/dom.html#the-innertext-idl-attribute
     * @returns HTML.
     */
    get outerText() {
        return this.innerText;
    }
    /**
     * Sets outer text.
     *
     * @see https://html.spec.whatwg.org/multipage/dom.html#the-innertext-idl-attribute
     * @param text Text.
     */
    set outerText(text) {
        if (!this[PropertySymbol.parentNode]) {
            throw new this[PropertySymbol.window].DOMException("Failed to set the 'outerHTML' property on 'Element': This element has no parent node.");
        }
        const texts = text.split(/[\n\r]/);
        for (let i = 0, max = texts.length; i < max; i++) {
            if (i !== 0) {
                this[PropertySymbol.parentNode].insertBefore(this[PropertySymbol.ownerDocument].createElement('br'), this);
            }
            this[PropertySymbol.parentNode].insertBefore(this[PropertySymbol.ownerDocument].createTextNode(texts[i]), this);
        }
        this[PropertySymbol.parentNode].removeChild(this);
    }
    /**
     * Returns style.
     *
     * @returns Style.
     */
    get style() {
        if (!this[PropertySymbol.style]) {
            this[PropertySymbol.style] = new CSSStyleDeclaration(PropertySymbol.illegalConstructor, this[PropertySymbol.window], { element: this });
        }
        return this[PropertySymbol.style];
    }
    /**
     * Sets style.
     *
     * @param cssText Style as text.
     * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style#setting_styles
     */
    set style(cssText) {
        this.style.cssText = typeof cssText === 'string' ? cssText : '';
    }
    /**
     * Returns data set.
     *
     * @returns Data set.
     */
    get dataset() {
        return (this[PropertySymbol.dataset] ??= new DOMStringMap(PropertySymbol.illegalConstructor, this));
    }
    /**
     * Returns direction.
     *
     * @returns Direction.
     */
    get dir() {
        return this.getAttribute('dir') || '';
    }
    /**
     * Returns direction.
     *
     * @param direction Direction.
     */
    set dir(direction) {
        this.setAttribute('dir', direction);
    }
    /**
     * Returns hidden.
     *
     * @returns Hidden.
     */
    get hidden() {
        return this.getAttribute('hidden') !== null;
    }
    /**
     * Returns hidden.
     *
     * @param hidden Hidden.
     */
    set hidden(hidden) {
        if (!hidden) {
            this.removeAttribute('hidden');
        }
        else {
            this.setAttribute('hidden', '');
        }
    }
    /**
     * Returns inert.
     *
     * @returns Inert.
     */
    get inert() {
        return this.getAttribute('inert') !== null;
    }
    /**
     * Returns inert.
     *
     * @param inert Inert.
     */
    set inert(inert) {
        if (!inert) {
            this.removeAttribute('inert');
        }
        else {
            this.setAttribute('inert', '');
        }
    }
    /**
     * Returns language.
     *
     * @returns Language.
     */
    get lang() {
        return this.getAttribute('lang') || '';
    }
    /**
     * Returns language.
     *
     * @param language Language.
     */
    set lang(lang) {
        this.setAttribute('lang', lang);
    }
    /**
     * Returns title.
     *
     * @returns Title.
     */
    get title() {
        return this.getAttribute('title') || '';
    }
    /**
     * Returns title.
     *
     * @param title Title.
     */
    set title(title) {
        this.setAttribute('title', title);
    }
    /**
     * Returns popover.
     *
     * @returns Popover.
     */
    get popover() {
        const value = this.getAttribute('popover');
        switch (value) {
            case null:
                return null;
            case '':
            case 'auto':
                return 'auto';
            case 'manual':
                return 'manual';
            default:
                return 'manual';
        }
    }
    /**
     * Sets popover.
     *
     * @param value Value.
     */
    set popover(value) {
        if (value === null) {
            this.removeAttribute('popover');
            return;
        }
        this.setAttribute('popover', value);
    }
    /**
     * Triggers a click event.
     */
    click() {
        this.dispatchEvent(new PointerEvent('click', {
            bubbles: true,
            composed: true,
            cancelable: true
        }));
    }
    /**
     * Triggers a blur event.
     */
    blur() {
        HTMLElementUtility.blur(this);
    }
    /**
     * Triggers a focus event.
     */
    focus() {
        HTMLElementUtility.focus(this);
    }
    /**
     * @override
     */
    [PropertySymbol.cloneNode](deep = false) {
        const clone = super[PropertySymbol.cloneNode](deep);
        clone[PropertySymbol.accessKey] = this[PropertySymbol.accessKey];
        clone[PropertySymbol.contentEditable] = this[PropertySymbol.contentEditable];
        clone[PropertySymbol.isContentEditable] = this[PropertySymbol.isContentEditable];
        return clone;
    }
    /**
     * @override
     * @see https://html.spec.whatwg.org/multipage/dom.html#htmlelement
     */
    [PropertySymbol.connectedToNode]() {
        const window = this[PropertySymbol.window];
        const localName = this[PropertySymbol.localName];
        const allCallbacks = window.customElements[PropertySymbol.callbacks];
        // This element can potentially be a custom element that has not been defined yet
        // Therefore we need to register a callback for when it is defined in CustomElementRegistry and replace it with the registered element (see #404)
        if (this.constructor === window.HTMLElement && localName.includes('-') && allCallbacks) {
            if (!this.#customElementDefineCallback) {
                const callback = this.#onCustomElementConnected.bind(this);
                const callbacks = allCallbacks.get(localName);
                if (callbacks) {
                    callbacks.push(callback);
                }
                else {
                    allCallbacks.set(localName, [callback]);
                }
                this.#customElementDefineCallback = callback;
            }
        }
        super[PropertySymbol.connectedToNode]();
    }
    /**
     * @override
     */
    [PropertySymbol.disconnectedFromNode]() {
        const window = this[PropertySymbol.window];
        const localName = this[PropertySymbol.localName];
        const allCallbacks = window.customElements[PropertySymbol.callbacks];
        // This element can potentially be a custom element that has not been defined yet
        // Therefore we need to register a callback for when it is defined in CustomElementRegistry and replace it with the registered element (see #404)
        if (this.constructor === window.HTMLElement && localName.includes('-') && allCallbacks) {
            const callbacks = allCallbacks.get(localName);
            if (callbacks && this.#customElementDefineCallback) {
                const index = callbacks.indexOf(this.#customElementDefineCallback);
                if (index !== -1) {
                    callbacks.splice(index, 1);
                }
                if (!callbacks.length) {
                    allCallbacks.delete(localName);
                }
                this.#customElementDefineCallback = null;
            }
        }
        super[PropertySymbol.disconnectedFromNode]();
    }
    /**
     * Triggered when a custom element is connected to the DOM.
     */
    #onCustomElementConnected() {
        if (!this[PropertySymbol.parentNode]) {
            return;
        }
        const localName = this[PropertySymbol.localName];
        const newElement = this[PropertySymbol.ownerDocument].createElement(localName);
        const newCache = newElement[PropertySymbol.cache];
        newElement[PropertySymbol.nodeArray] = this[PropertySymbol.nodeArray];
        newElement[PropertySymbol.elementArray] = this[PropertySymbol.elementArray];
        newElement[PropertySymbol.childNodes] = null;
        newElement[PropertySymbol.children] = null;
        newElement[PropertySymbol.isConnected] = this[PropertySymbol.isConnected];
        newElement[PropertySymbol.rootNode] = this[PropertySymbol.rootNode];
        newElement[PropertySymbol.formNode] = this[PropertySymbol.formNode];
        newElement[PropertySymbol.parentNode] = this[PropertySymbol.parentNode];
        newElement[PropertySymbol.selectNode] = this[PropertySymbol.selectNode];
        newElement[PropertySymbol.textAreaNode] = this[PropertySymbol.textAreaNode];
        newElement[PropertySymbol.mutationListeners] = this[PropertySymbol.mutationListeners];
        newElement[PropertySymbol.isValue] = this[PropertySymbol.isValue];
        newElement[PropertySymbol.cache] = this[PropertySymbol.cache];
        newElement[PropertySymbol.affectsCache] = this[PropertySymbol.affectsCache];
        newElement[PropertySymbol.attributes][PropertySymbol.namedItems] =
            this[PropertySymbol.attributes][PropertySymbol.namedItems];
        newElement[PropertySymbol.attributes][PropertySymbol.namespaceItems] =
            this[PropertySymbol.attributes][PropertySymbol.namespaceItems];
        for (const attr of newElement[PropertySymbol.attributes][PropertySymbol.namedItems].values()) {
            attr[PropertySymbol.ownerElement] = newElement;
        }
        this[PropertySymbol.nodeArray] = [];
        this[PropertySymbol.elementArray] = [];
        this[PropertySymbol.childNodes] = null;
        this[PropertySymbol.children] = null;
        this[PropertySymbol.rootNode] = null;
        this[PropertySymbol.formNode] = null;
        this[PropertySymbol.selectNode] = null;
        this[PropertySymbol.textAreaNode] = null;
        this[PropertySymbol.mutationListeners] = [];
        this[PropertySymbol.isValue] = null;
        this[PropertySymbol.cache] = newCache;
        this[PropertySymbol.affectsCache] = [];
        this[PropertySymbol.attributes][PropertySymbol.namedItems] = new Map();
        this[PropertySymbol.attributes][PropertySymbol.namespaceItems] = new Map();
        const parentChildNodes = this[PropertySymbol.parentNode][PropertySymbol.nodeArray];
        const parentChildElements = this[PropertySymbol.parentNode][PropertySymbol.elementArray];
        parentChildNodes[parentChildNodes.indexOf(this)] = newElement;
        parentChildElements[parentChildElements.indexOf(this)] = newElement;
        if (newElement[PropertySymbol.isConnected] && newElement.connectedCallback) {
            const result = newElement.connectedCallback();
            /**
             * It is common to import dependencies in the connectedCallback() method of web components.
             * As Happy DOM doesn't have support for dynamic imports yet, this is a temporary solution to wait for imports in connectedCallback().
             *
             * @see https://github.com/capricorn86/happy-dom/issues/1442
             */
            if (result instanceof Promise) {
                const asyncTaskManager = new WindowBrowserContext(this[PropertySymbol.window]).getAsyncTaskManager();
                if (asyncTaskManager) {
                    const taskID = asyncTaskManager.startTask();
                    result
                        .then(() => asyncTaskManager.endTask(taskID))
                        .catch(() => asyncTaskManager.endTask(taskID));
                }
            }
        }
        this[PropertySymbol.disconnectedFromDocument]();
    }
}
//# sourceMappingURL=HTMLElement.js.map