import EventTarget from '../../event/EventTarget.js';
import * as PropertySymbol from '../../PropertySymbol.js';
import NodeTypeEnum from './NodeTypeEnum.js';
import NodeDocumentPositionEnum from './NodeDocumentPositionEnum.js';
import NodeUtility from './NodeUtility.js';
import NodeList from './NodeList.js';
import MutationRecord from '../../mutation-observer/MutationRecord.js';
import MutationTypeEnum from '../../mutation-observer/MutationTypeEnum.js';
import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum.js';
import WindowBrowserContext from '../../window/WindowBrowserContext.js';
import NodeFactory from '../NodeFactory.js';
/**
 * Node.
 */
export default class Node extends EventTarget {
    // Public properties
    static ELEMENT_NODE = NodeTypeEnum.elementNode;
    static ATTRIBUTE_NODE = NodeTypeEnum.attributeNode;
    static TEXT_NODE = NodeTypeEnum.textNode;
    static CDATA_SECTION_NODE = NodeTypeEnum.cdataSectionNode;
    static COMMENT_NODE = NodeTypeEnum.commentNode;
    static DOCUMENT_NODE = NodeTypeEnum.documentNode;
    static DOCUMENT_TYPE_NODE = NodeTypeEnum.documentTypeNode;
    static DOCUMENT_FRAGMENT_NODE = NodeTypeEnum.documentFragmentNode;
    static PROCESSING_INSTRUCTION_NODE = NodeTypeEnum.processingInstructionNode;
    static DOCUMENT_POSITION_CONTAINED_BY = NodeDocumentPositionEnum.containedBy;
    static DOCUMENT_POSITION_CONTAINS = NodeDocumentPositionEnum.contains;
    static DOCUMENT_POSITION_DISCONNECTED = NodeDocumentPositionEnum.disconnect;
    static DOCUMENT_POSITION_FOLLOWING = NodeDocumentPositionEnum.following;
    static DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = NodeDocumentPositionEnum.implementationSpecific;
    static DOCUMENT_POSITION_PRECEDING = NodeDocumentPositionEnum.preceding;
    // Internal properties
    [PropertySymbol.isConnected] = false;
    [PropertySymbol.parentNode] = null;
    [PropertySymbol.nodeType];
    [PropertySymbol.rootNode] = null;
    [PropertySymbol.styleNode] = null;
    [PropertySymbol.textAreaNode] = null;
    [PropertySymbol.formNode] = null;
    [PropertySymbol.selectNode] = null;
    [PropertySymbol.mutationListeners] = [];
    [PropertySymbol.nodeArray] = [];
    [PropertySymbol.elementArray] = [];
    [PropertySymbol.childNodes] = null;
    [PropertySymbol.assignedToSlot] = null;
    [PropertySymbol.cache] = {
        querySelector: new Map(),
        querySelectorAll: new Map(),
        matches: new Map(),
        elementsByTagName: new Map(),
        elementsByTagNameNS: new Map(),
        elementByTagName: new Map(),
        elementById: new Map(),
        computedStyle: null
    };
    [PropertySymbol.affectsCache] = [];
    /**
     * Constructor.
     */
    constructor() {
        super();
        // Window injected by WindowContextClassExtender (used when the Node can be created using "new" keyword)
        if (this[PropertySymbol.window]) {
            this[PropertySymbol.ownerDocument] = this[PropertySymbol.window].document;
        }
        else {
            const ownerDocument = NodeFactory.pullOwnerDocument();
            if (!ownerDocument) {
                throw new TypeError('Illegal constructor');
            }
            this[PropertySymbol.ownerDocument] = ownerDocument;
            this[PropertySymbol.window] = ownerDocument[PropertySymbol.window];
        }
    }
    /**
     * Returns `Symbol.toStringTag`.
     *
     * @returns `Symbol.toStringTag`.
     */
    get [Symbol.toStringTag]() {
        return this.constructor.name;
    }
    /**
     * Returns connected state.
     *
     * @returns Connected state.
     */
    get isConnected() {
        return this[PropertySymbol.isConnected];
    }
    /**
     * Returns owner document.
     *
     * @returns Owner document.
     */
    get ownerDocument() {
        return this[PropertySymbol.ownerDocument];
    }
    /**
     * Returns parent node.
     *
     * @returns Parent node.
     */
    get parentNode() {
        return this[PropertySymbol.parentNode];
    }
    /**
     * Returns node type.
     *
     * @returns Node type.
     */
    get nodeType() {
        return this[PropertySymbol.nodeType];
    }
    /**
     * Get child nodes.
     *
     * @returns Child nodes list.
     */
    get childNodes() {
        if (!this[PropertySymbol.childNodes]) {
            this[PropertySymbol.childNodes] = new NodeList(PropertySymbol.illegalConstructor, this[PropertySymbol.nodeArray]);
        }
        return this[PropertySymbol.childNodes];
    }
    /**
     * Get text value of children.
     *
     * @returns Text content.
     */
    get textContent() {
        // Sub-classes should implement this method.
        return null;
    }
    /**
     * Sets text content.
     *
     * @param _textContent Text content.
     */
    set textContent(_textContent) {
        // Do nothing.
        // Sub-classes should implement this method.
    }
    /**
     * Node value.
     *
     * @returns Node value.
     */
    get nodeValue() {
        return null;
    }
    /**
     * Sets node value.
     */
    set nodeValue(_nodeValue) {
        // Do nothing
    }
    /**
     * Node name.
     *
     * @returns Node name.
     */
    get nodeName() {
        return '';
    }
    /**
     * Previous sibling.
     *
     * @returns Node.
     */
    get previousSibling() {
        if (this[PropertySymbol.parentNode]) {
            const nodeArray = this[PropertySymbol.parentNode][PropertySymbol.nodeArray];
            const index = nodeArray.indexOf(this);
            if (index > 0) {
                return nodeArray[index - 1];
            }
        }
        return null;
    }
    /**
     * Next sibling.
     *
     * @returns Node.
     */
    get nextSibling() {
        if (this[PropertySymbol.parentNode]) {
            const nodeArray = this[PropertySymbol.parentNode][PropertySymbol.nodeArray];
            const index = nodeArray.indexOf(this);
            if (index > -1 && index + 1 < nodeArray.length) {
                return nodeArray[index + 1];
            }
        }
        return null;
    }
    /**
     * First child.
     *
     * @returns Node.
     */
    get firstChild() {
        const nodeArray = this[PropertySymbol.nodeArray];
        if (nodeArray.length > 0) {
            return nodeArray[0];
        }
        return null;
    }
    /**
     * Last child.
     *
     * @returns Node.
     */
    get lastChild() {
        const nodeArray = this[PropertySymbol.nodeArray];
        if (nodeArray.length > 0) {
            return nodeArray[nodeArray.length - 1];
        }
        return null;
    }
    /**
     * Returns parent element.
     *
     * @returns Element.
     */
    get parentElement() {
        let parent = this[PropertySymbol.parentNode];
        while (parent && parent[PropertySymbol.nodeType] !== NodeTypeEnum.elementNode) {
            parent = parent[PropertySymbol.parentNode];
        }
        return parent;
    }
    /**
     * Returns base URI.
     *
     * @returns Base URI.
     */
    get baseURI() {
        const base = this[PropertySymbol.ownerDocument].querySelector('base');
        if (base) {
            return base.href;
        }
        return this[PropertySymbol.window].location.href;
    }
    /**
     * Returns "true" if the node has child nodes.
     *
     * @returns "true" if the node has child nodes.
     */
    hasChildNodes() {
        return this[PropertySymbol.nodeArray].length > 0;
    }
    /**
     * Returns "true" if this node contains the other node.
     *
     * @param otherNode Node to test with.
     * @returns "true" if this node contains the other node.
     */
    contains(otherNode) {
        if (otherNode === undefined) {
            return false;
        }
        return NodeUtility.isInclusiveAncestor(this, otherNode);
    }
    /**
     * Returns closest root node (Document or ShadowRoot).
     *
     * @param options Options.
     * @param options.composed A Boolean that indicates whether the shadow root should be returned (false, the default), or a root node beyond shadow root (true).
     * @returns Node.
     */
    getRootNode(options) {
        if (!this[PropertySymbol.isConnected]) {
            return this;
        }
        if (this[PropertySymbol.rootNode] && !options?.composed) {
            return this[PropertySymbol.rootNode];
        }
        return this[PropertySymbol.ownerDocument];
    }
    /**
     * Clones a node.
     *
     * @param [deep=false] "true" to clone deep.
     * @returns Cloned node.
     */
    cloneNode(deep = false) {
        return this[PropertySymbol.cloneNode](deep);
    }
    /**
     * Append a child node to childNodes.
     *
     * @param  node Node to append.
     * @returns Appended node.
     */
    appendChild(node) {
        if (arguments.length < 1) {
            throw new this[PropertySymbol.window].TypeError(`Failed to execute 'appendChild' on 'Node': 1 argument required, but only 0 present`);
        }
        return this[PropertySymbol.appendChild](node);
    }
    /**
     * Remove Child element from childNodes array.
     *
     * @param node Node to remove.
     * @returns Removed node.
     */
    removeChild(node) {
        if (arguments.length < 1) {
            throw new this[PropertySymbol.window].TypeError(`Failed to execute 'removeChild' on 'Node': 1 argument required, but only 0 present`);
        }
        return this[PropertySymbol.removeChild](node);
    }
    /**
     * Inserts a node before another.
     *
     * @param newNode Node to insert.
     * @param referenceNode Node to insert before.
     * @returns Inserted node.
     */
    insertBefore(newNode, referenceNode) {
        if (arguments.length < 2) {
            throw new this[PropertySymbol.window].TypeError(`Failed to execute 'insertBefore' on 'Node': 2 arguments required, but only ${arguments.length} present.`);
        }
        return this[PropertySymbol.insertBefore](newNode, referenceNode);
    }
    /**
     * Replaces a node with another.
     *
     * @param newChild New child.
     * @param oldChild Old child.
     * @returns Replaced node.
     */
    replaceChild(newChild, oldChild) {
        if (arguments.length < 2) {
            throw new this[PropertySymbol.window].TypeError(`Failed to execute 'replaceChild' on 'Node': 2 arguments required, but only ${arguments.length} present.`);
        }
        return this[PropertySymbol.replaceChild](newChild, oldChild);
    }
    /**
     * Clones a node.
     *
     * @param [deep=false] "true" to clone deep.
     * @returns Cloned node.
     */
    [PropertySymbol.cloneNode](deep = false) {
        const clone = NodeFactory.createNode(this[PropertySymbol.ownerDocument], this.constructor);
        // Document has childNodes directly when it is created
        // We need to remove them
        if (clone[PropertySymbol.nodeArray].length) {
            const childNodes = clone[PropertySymbol.nodeArray];
            while (childNodes.length) {
                clone.removeChild(childNodes[0]);
            }
        }
        if (deep) {
            for (const childNode of this[PropertySymbol.nodeArray]) {
                const childClone = childNode.cloneNode(true);
                childClone[PropertySymbol.parentNode] = clone;
                clone[PropertySymbol.nodeArray].push(childClone);
                if (childClone[PropertySymbol.nodeType] === NodeTypeEnum.elementNode) {
                    clone[PropertySymbol.elementArray].push(childClone);
                }
            }
        }
        return clone;
    }
    /**
     * Append a child node to childNodes.
     *
     * @param  node Node to append.
     * @param [disableValidations=false] "true" to disable validations.
     * @returns Appended node.
     */
    [PropertySymbol.appendChild](node, disableValidations = false) {
        if (!disableValidations) {
            if (node === this) {
                throw new this[PropertySymbol.window].DOMException("Failed to execute 'appendChild' on 'Node': Not possible to append a node as a child of itself.");
            }
            if (NodeUtility.isInclusiveAncestor(node, this, true)) {
                throw new this[PropertySymbol.window].DOMException("Failed to execute 'appendChild' on 'Node': The new node is a parent of the node to insert to.", DOMExceptionNameEnum.domException);
            }
        }
        // If the type is DocumentFragment, then the child nodes of if it should be moved instead of the actual node.
        // See: https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment
        if (node[PropertySymbol.nodeType] === NodeTypeEnum.documentFragmentNode) {
            const childNodes = node[PropertySymbol.nodeArray];
            while (childNodes.length) {
                this.appendChild(childNodes[0]);
            }
            return node;
        }
        // Remove the node from its previous parent if it has any.
        if (node[PropertySymbol.parentNode]) {
            node[PropertySymbol.parentNode][PropertySymbol.removeChild](node);
        }
        node[PropertySymbol.parentNode] = this[PropertySymbol.proxy] || this;
        node[PropertySymbol.clearCache]();
        this[PropertySymbol.nodeArray].push(node);
        if (node[PropertySymbol.nodeType] === NodeTypeEnum.elementNode) {
            this[PropertySymbol.elementArray].push(node);
        }
        node[PropertySymbol.connectedToNode]();
        // Mutation listeners
        for (const mutationListener of this[PropertySymbol.mutationListeners]) {
            if (mutationListener.options?.subtree && mutationListener.callback.deref()) {
                node[PropertySymbol.observeMutations](mutationListener);
            }
        }
        this[PropertySymbol.reportMutation](new MutationRecord({
            target: this,
            type: MutationTypeEnum.childList,
            addedNodes: [node]
        }));
        return node;
    }
    /**
     * Remove Child element from childNodes array.
     *
     * @param node Node to remove.
     * @returns Removed node.
     */
    [PropertySymbol.removeChild](node) {
        node[PropertySymbol.parentNode] = null;
        node[PropertySymbol.clearCache]();
        const index = this[PropertySymbol.nodeArray].indexOf(node);
        if (index === -1) {
            throw new this[PropertySymbol.window].DOMException(`Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.`);
        }
        this[PropertySymbol.nodeArray].splice(index, 1);
        if (node[PropertySymbol.nodeType] === NodeTypeEnum.elementNode) {
            const index = this[PropertySymbol.elementArray].indexOf(node);
            if (index !== -1) {
                this[PropertySymbol.elementArray].splice(index, 1);
            }
        }
        if (node[PropertySymbol.assignedToSlot]) {
            const index = node[PropertySymbol.assignedToSlot][PropertySymbol.assignedNodes].indexOf(node);
            if (index !== -1) {
                node[PropertySymbol.assignedToSlot][PropertySymbol.assignedNodes].splice(index, 1);
            }
            node[PropertySymbol.assignedToSlot] = null;
        }
        node[PropertySymbol.disconnectedFromNode]();
        // Mutation listeners
        for (const mutationListener of this[PropertySymbol.mutationListeners]) {
            if (mutationListener.options?.subtree && mutationListener.callback.deref()) {
                node[PropertySymbol.unobserveMutations](mutationListener);
            }
        }
        this[PropertySymbol.reportMutation](new MutationRecord({
            target: this,
            type: MutationTypeEnum.childList,
            removedNodes: [node]
        }));
        return node;
    }
    /**
     * Inserts a node before another.
     *
     * @param newNode Node to insert.
     * @param referenceNode Node to insert before.
     * @returns Inserted node.
     */
    [PropertySymbol.insertBefore](newNode, referenceNode) {
        if (newNode === referenceNode) {
            return newNode;
        }
        if (NodeUtility.isInclusiveAncestor(newNode, this, true)) {
            throw new this[PropertySymbol.window].DOMException("Failed to execute 'insertBefore' on 'Node': The new node is a parent of the node to insert to.", DOMExceptionNameEnum.domException);
        }
        // If the type is DocumentFragment, then the child nodes of if it should be moved instead of the actual node.
        // See: https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment
        if (newNode[PropertySymbol.nodeType] === NodeTypeEnum.documentFragmentNode) {
            const childNodes = newNode[PropertySymbol.nodeArray];
            while (childNodes.length > 0) {
                this.insertBefore(childNodes[0], referenceNode);
            }
            return newNode;
        }
        // If the referenceNode is null or undefined, then the newNode should be appended to the ancestorNode.
        // According to spec only null is valid, but browsers support undefined as well.
        if (!referenceNode) {
            this.appendChild(newNode);
            return newNode;
        }
        const nodeArray = this[PropertySymbol.nodeArray];
        // We need to check if the referenceNode is a child of this node before removing it from its parent, as the parent may be the same node and the index would be wrong.
        if (!nodeArray.includes(referenceNode)) {
            throw new this[PropertySymbol.window].DOMException("Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.");
        }
        if (newNode[PropertySymbol.parentNode]) {
            newNode[PropertySymbol.parentNode][PropertySymbol.removeChild](newNode);
        }
        newNode[PropertySymbol.parentNode] = this[PropertySymbol.proxy] || this;
        newNode[PropertySymbol.clearCache]();
        const index = nodeArray.indexOf(referenceNode);
        if (newNode[PropertySymbol.nodeType] === NodeTypeEnum.elementNode) {
            const elementArray = this[PropertySymbol.elementArray];
            if (referenceNode[PropertySymbol.nodeType] === NodeTypeEnum.elementNode) {
                elementArray.splice(elementArray.indexOf(referenceNode), 0, newNode);
            }
            else {
                let isInserted = false;
                for (let i = index, max = nodeArray.length; i < max; i++) {
                    if (nodeArray[i][PropertySymbol.nodeType] === NodeTypeEnum.elementNode) {
                        elementArray.splice(elementArray.indexOf(nodeArray[i]), 0, newNode);
                        isInserted = true;
                        break;
                    }
                }
                if (!isInserted) {
                    elementArray.push(newNode);
                }
            }
        }
        nodeArray.splice(index, 0, newNode);
        newNode[PropertySymbol.connectedToNode]();
        // Mutation listeners
        for (const mutationListener of this[PropertySymbol.mutationListeners]) {
            if (mutationListener.options?.subtree && mutationListener.callback.deref()) {
                newNode[PropertySymbol.observeMutations](mutationListener);
            }
        }
        this[PropertySymbol.reportMutation](new MutationRecord({
            target: this,
            type: MutationTypeEnum.childList,
            addedNodes: [newNode]
        }));
        return newNode;
    }
    /**
     * Replaces a node with another.
     *
     * @param newChild New child.
     * @param oldChild Old child.
     * @returns Replaced node.
     */
    [PropertySymbol.replaceChild](newChild, oldChild) {
        this.insertBefore(newChild, oldChild);
        this.removeChild(oldChild);
        return oldChild;
    }
    /**
     * Compares two nodes.
     * Two nodes are equal if they have the same type, defining the same attributes, and so on.
     *
     * @param node  Node to compare.
     * @returns boolean - `true` if two nodes are equal.
     */
    isEqualNode(node) {
        return NodeUtility.isEqualNode(this, node);
    }
    /**
     * Converts the node to a string.
     *
     * @param listener Listener.
     */
    toString() {
        return `[object ${this.constructor.name}]`;
    }
    /**
     * Observeres mutations on the node.
     *
     * Used by MutationObserver and internal logic.
     *
     * @param listener Listener.
     */
    [PropertySymbol.observeMutations](listener) {
        this[PropertySymbol.mutationListeners].push(listener);
        if (listener.options.subtree) {
            for (const node of this[PropertySymbol.nodeArray]) {
                node[PropertySymbol.observeMutations](listener);
            }
        }
    }
    /**
     * Stops observing mutations on the node.
     *
     * Used by MutationObserver and internal logic.
     *
     * @param listener Listener.
     */
    [PropertySymbol.unobserveMutations](listener) {
        const index = this[PropertySymbol.mutationListeners].indexOf(listener);
        if (index !== -1) {
            this[PropertySymbol.mutationListeners].splice(index, 1);
        }
        if (listener.options.subtree) {
            for (const node of this[PropertySymbol.nodeArray]) {
                node[PropertySymbol.unobserveMutations](listener);
            }
        }
    }
    /**
     * Reports a mutation on the node.
     *
     * Used by MutationObserver and internal logic.
     *
     * @param record Mutation record.
     */
    [PropertySymbol.reportMutation](record) {
        this[PropertySymbol.clearCache]();
        const mutationListeners = this[PropertySymbol.mutationListeners];
        if (!mutationListeners.length) {
            return;
        }
        for (let i = 0, max = mutationListeners.length; i < max; i++) {
            const mutationListener = mutationListeners[i];
            const callback = mutationListener.callback.deref();
            if (callback) {
                switch (record.type) {
                    case MutationTypeEnum.childList:
                        if (mutationListener.options.childList) {
                            callback(record);
                        }
                        break;
                    case MutationTypeEnum.attributes:
                        if (mutationListener.options.attributes &&
                            (!mutationListener.options.attributeFilter ||
                                mutationListener.options.attributeFilter.includes(record.attributeName))) {
                            callback(record);
                        }
                        break;
                    case MutationTypeEnum.characterData:
                        if (mutationListener.options?.characterData) {
                            callback(record);
                        }
                        break;
                }
            }
            else {
                mutationListeners.splice(i, 1);
                i--;
                max--;
            }
        }
    }
    /**
     * Clears query selector cache.
     */
    [PropertySymbol.clearCache]() {
        const cache = this[PropertySymbol.cache];
        if (cache.querySelector.size) {
            for (const item of cache.querySelector.values()) {
                if (item.result) {
                    item.result = null;
                }
            }
            cache.querySelector = new Map();
        }
        if (cache.querySelectorAll.size) {
            for (const item of cache.querySelectorAll.values()) {
                if (item.result) {
                    item.result = null;
                }
            }
            cache.querySelectorAll = new Map();
        }
        if (cache.matches.size) {
            for (const item of cache.matches.values()) {
                if (item.result) {
                    item.result = null;
                }
            }
            cache.matches = new Map();
        }
        if (cache.elementsByTagName.size) {
            for (const item of cache.elementsByTagName.values()) {
                if (item.result) {
                    item.result = null;
                }
            }
            cache.elementsByTagName = new Map();
        }
        if (cache.elementsByTagNameNS.size) {
            for (const item of cache.elementsByTagNameNS.values()) {
                if (item.result) {
                    item.result = null;
                }
            }
            cache.elementsByTagNameNS = new Map();
        }
        if (cache.elementByTagName.size) {
            for (const item of cache.elementByTagName.values()) {
                if (item.result) {
                    item.result = null;
                }
            }
            cache.elementByTagName = new Map();
        }
        if (cache.elementById.size) {
            for (const item of cache.elementById.values()) {
                if (item.result) {
                    item.result = null;
                }
            }
            cache.elementById = new Map();
        }
        const affectsCache = this[PropertySymbol.affectsCache];
        if (affectsCache.length) {
            for (const item of affectsCache) {
                item.result = null;
            }
            this[PropertySymbol.affectsCache] = [];
        }
        // Computed style cache is affected by all mutations.
        const document = this[PropertySymbol.ownerDocument];
        if (document && document[PropertySymbol.affectsComputedStyleCache].length) {
            for (const item of document[PropertySymbol.affectsComputedStyleCache]) {
                item.result = null;
            }
            document[PropertySymbol.affectsComputedStyleCache] = [];
        }
    }
    /**
     * Called when connected to a node.
     */
    [PropertySymbol.connectedToNode]() {
        const parentNode = this[PropertySymbol.parentNode];
        const parent = parentNode || this[PropertySymbol.host];
        if (parentNode) {
            if (parentNode[PropertySymbol.styleNode] && this[PropertySymbol.tagName] !== 'STYLE') {
                this[PropertySymbol.styleNode] = parentNode[PropertySymbol.styleNode];
            }
            if (parentNode[PropertySymbol.textAreaNode] && this[PropertySymbol.tagName] !== 'TEXTAREA') {
                this[PropertySymbol.textAreaNode] = parentNode[PropertySymbol.textAreaNode];
            }
            if (parentNode[PropertySymbol.formNode] && this[PropertySymbol.tagName] !== 'FORM') {
                this[PropertySymbol.formNode] = parentNode[PropertySymbol.formNode];
            }
            if (parentNode[PropertySymbol.selectNode] && this[PropertySymbol.tagName] !== 'SELECT') {
                this[PropertySymbol.selectNode] = parentNode[PropertySymbol.selectNode];
            }
        }
        if (!this[PropertySymbol.isConnected] && parent[PropertySymbol.isConnected]) {
            this[PropertySymbol.connectedToDocument]();
        }
        else if (this[PropertySymbol.isConnected] && !parent[PropertySymbol.isConnected]) {
            this[PropertySymbol.disconnectedFromDocument]();
        }
        const childNodes = this[PropertySymbol.nodeArray];
        for (let i = 0, max = childNodes.length; i < max; i++) {
            childNodes[i][PropertySymbol.connectedToNode]();
        }
        // eslint-disable-next-line
        if (this[PropertySymbol.shadowRoot]) {
            // eslint-disable-next-line
            this[PropertySymbol.shadowRoot][PropertySymbol.connectedToNode]();
        }
    }
    /**
     * Called when disconnected from a node.
     */
    [PropertySymbol.disconnectedFromNode]() {
        if (this[PropertySymbol.isConnected]) {
            this[PropertySymbol.disconnectedFromDocument]();
        }
        if (this[PropertySymbol.tagName] !== 'STYLE') {
            this[PropertySymbol.styleNode] = null;
        }
        if (this[PropertySymbol.tagName] !== 'TEXTAREA') {
            this[PropertySymbol.textAreaNode] = null;
        }
        if (this[PropertySymbol.tagName] !== 'FORM') {
            this[PropertySymbol.formNode] = null;
        }
        if (this[PropertySymbol.tagName] !== 'SELECT') {
            this[PropertySymbol.selectNode] = null;
        }
        const childNodes = this[PropertySymbol.nodeArray];
        for (let i = 0, max = childNodes.length; i < max; i++) {
            childNodes[i][PropertySymbol.disconnectedFromNode]();
        }
        // eslint-disable-next-line
        if (this[PropertySymbol.shadowRoot]) {
            // eslint-disable-next-line
            this[PropertySymbol.shadowRoot][PropertySymbol.disconnectedFromNode]();
        }
    }
    /**
     * Called when connected to document.
     */
    [PropertySymbol.connectedToDocument]() {
        this[PropertySymbol.isConnected] = true;
        if (this[PropertySymbol.nodeType] !== NodeTypeEnum.documentFragmentNode) {
            this[PropertySymbol.rootNode] = this[PropertySymbol.parentNode][PropertySymbol.rootNode];
        }
        // eslint-disable-next-line
        if (this[PropertySymbol.shadowRoot]) {
            // eslint-disable-next-line
            this[PropertySymbol.shadowRoot][PropertySymbol.connectedToDocument]();
        }
        if (this.connectedCallback) {
            const result = this.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));
                }
            }
        }
    }
    /**
     * Called when disconnected from document.
     * @param e
     */
    [PropertySymbol.disconnectedFromDocument]() {
        this[PropertySymbol.isConnected] = false;
        if (this[PropertySymbol.nodeType] !== NodeTypeEnum.documentFragmentNode) {
            this[PropertySymbol.rootNode] = null;
        }
        if (this[PropertySymbol.ownerDocument][PropertySymbol.activeElement] === this) {
            this[PropertySymbol.ownerDocument][PropertySymbol.clearCache]();
            this[PropertySymbol.ownerDocument][PropertySymbol.activeElement] = null;
        }
        // eslint-disable-next-line
        if (this[PropertySymbol.shadowRoot]) {
            // eslint-disable-next-line
            this[PropertySymbol.shadowRoot][PropertySymbol.disconnectedFromDocument]();
        }
        if (this.disconnectedCallback) {
            this.disconnectedCallback();
        }
    }
    /**
     * Reports the position of its argument node relative to the node on which it is called.
     *
     * @see https://dom.spec.whatwg.org/#dom-node-comparedocumentposition
     * @param otherNode Other node.
     */
    compareDocumentPosition(otherNode) {
        /**
         * 1. If this is other, then return zero.
         */
        if (this === otherNode) {
            return 0;
        }
        /**
         * 2. Let node1 be other and node2 be this.
         */
        let node1 = otherNode;
        let node2 = this[PropertySymbol.proxy] || this;
        /**
         * 3. Let attr1 and attr2 be null.
         */
        let attr1 = null;
        let attr2 = null;
        /**
         * 4. If node1 is an attribute, then set attr1 to node1 and node1 to attr1’s element.
         */
        if (node1[PropertySymbol.nodeType] === NodeTypeEnum.attributeNode) {
            attr1 = node1;
            node1 = attr1[PropertySymbol.ownerElement];
        }
        /**
         * 5. If node2 is an attribute, then:
         * 5.1. Set attr2 to node2 and node2 to attr2’s element.
         */
        if (node2[PropertySymbol.nodeType] === NodeTypeEnum.attributeNode) {
            attr2 = node2;
            node2 = attr2[PropertySymbol.ownerElement];
            /**
             * 5.2. If attr1 and node1 are non-null, and node2 is node1, then:
             */
            if (attr1 !== null && node1 !== null && node2 === node1) {
                /**
                 * 5.2.1. For each attr in node2’s attribute list:
                 */
                for (const attr of Array.from(node2[PropertySymbol.attributes][PropertySymbol.namedItems].values())) {
                    /**
                     * 5.2.1.1. If attr equals attr1, then return the result of adding DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC and DOCUMENT_POSITION_PRECEDING.
                     */
                    if (NodeUtility.isEqualNode(attr, attr1)) {
                        return (Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | Node.DOCUMENT_POSITION_PRECEDING);
                    }
                    /**
                     * 5.2.1.2. If attr equals attr2, then return the result of adding DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC and DOCUMENT_POSITION_FOLLOWING.
                     */
                    if (NodeUtility.isEqualNode(attr, attr2)) {
                        return (Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | Node.DOCUMENT_POSITION_FOLLOWING);
                    }
                }
            }
        }
        const node2Ancestors = [];
        let node2Ancestor = node2;
        while (node2Ancestor) {
            /**
             * 7. If node1 is an ancestor of node2 […] then return the result of adding DOCUMENT_POSITION_CONTAINS to DOCUMENT_POSITION_PRECEDING.
             */
            if (node2Ancestor === node1) {
                return Node.DOCUMENT_POSITION_CONTAINS | Node.DOCUMENT_POSITION_PRECEDING;
            }
            node2Ancestors.push(node2Ancestor);
            node2Ancestor = node2Ancestor[PropertySymbol.parentNode];
        }
        const node1Ancestors = [];
        let node1Ancestor = node1;
        while (node1Ancestor) {
            /**
             * 8. If node1 is a descendant of node2 […] then return the result of adding DOCUMENT_POSITION_CONTAINED_BY to DOCUMENT_POSITION_FOLLOWING.
             */
            if (node1Ancestor === node2) {
                return Node.DOCUMENT_POSITION_CONTAINED_BY | Node.DOCUMENT_POSITION_FOLLOWING;
            }
            node1Ancestors.push(node1Ancestor);
            node1Ancestor = node1Ancestor[PropertySymbol.parentNode];
        }
        const reverseArrayIndex = (array, reverseIndex) => {
            return array[array.length - 1 - reverseIndex];
        };
        const root = reverseArrayIndex(node2Ancestors, 0);
        /**
         * 6. If node1 or node2 is null, or node1’s root is not node2’s root, then return the result of adding
         * DOCUMENT_POSITION_DISCONNECTED, DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC, and either
         * DOCUMENT_POSITION_PRECEDING or DOCUMENT_POSITION_FOLLOWING, with the constraint that this is to be consistent, together.
         */
        if (!root || root !== reverseArrayIndex(node1Ancestors, 0)) {
            return (Node.DOCUMENT_POSITION_DISCONNECTED |
                Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC |
                Node.DOCUMENT_POSITION_FOLLOWING);
        }
        // Find the lowest common ancestor
        let commonAncestorIndex = 0;
        const ancestorsMinLength = Math.min(node2Ancestors.length, node1Ancestors.length);
        for (let i = 0; i < ancestorsMinLength; ++i) {
            const node2Ancestor = reverseArrayIndex(node2Ancestors, i);
            const node1Ancestor = reverseArrayIndex(node1Ancestors, i);
            if (node2Ancestor !== node1Ancestor) {
                break;
            }
            commonAncestorIndex = i;
        }
        const commonAncestor = reverseArrayIndex(node2Ancestors, commonAncestorIndex);
        // Indexes within the common ancestor
        let indexes = 0;
        let node2Index = -1;
        let node1Index = -1;
        const node2Node = reverseArrayIndex(node2Ancestors, commonAncestorIndex + 1);
        const node1Node = reverseArrayIndex(node1Ancestors, commonAncestorIndex + 1);
        const computeNodeIndexes = (nodes) => {
            for (const childNode of nodes) {
                computeNodeIndexes(childNode[PropertySymbol.nodeArray]);
                if (childNode === node2Node) {
                    node2Index = indexes;
                }
                else if (childNode === node1Node) {
                    node1Index = indexes;
                }
                if (node2Index !== -1 && node1Index !== -1) {
                    break;
                }
                indexes++;
            }
        };
        computeNodeIndexes(commonAncestor[PropertySymbol.nodeArray]);
        /**
         * 9. If node1 is preceding node2, then return DOCUMENT_POSITION_PRECEDING.
         * 10. Return DOCUMENT_POSITION_FOLLOWING.
         */
        return node1Index < node2Index
            ? Node.DOCUMENT_POSITION_PRECEDING
            : Node.DOCUMENT_POSITION_FOLLOWING;
    }
    /**
     * Normalizes the sub-tree of the node, i.e. joins adjacent text nodes, and
     * removes all empty text nodes.
     *
     * @see https://developer.mozilla.org/en-US/docs/Web/API/Node/normalize
     */
    normalize() {
        let child = this.firstChild;
        while (child) {
            if (NodeUtility.isTextNode(child)) {
                // Append text of all following text nodes, and remove them.
                while (NodeUtility.isTextNode(child.nextSibling)) {
                    child.data += child.nextSibling.data;
                    child.nextSibling.remove();
                }
                // Remove text node if it is still empty.
                if (!child.data.length) {
                    const node = child;
                    child = child.nextSibling;
                    node.remove();
                    continue;
                }
            }
            else {
                // Normalize child nodes recursively.
                child.normalize();
            }
            child = child.nextSibling;
        }
    }
    /**
     * Determines whether the given node is equal to the current node.
     *
     * @see https://developer.mozilla.org/en-US/docs/Web/API/Node/isSameNode
     * @param node Node to check.
     * @returns True if the given node is equal to the current node, otherwise false.
     */
    isSameNode(node) {
        return this === node;
    }
}
// According to the spec, these properties should be on the prototype.
Node.prototype.ELEMENT_NODE = NodeTypeEnum.elementNode;
Node.prototype.ATTRIBUTE_NODE = NodeTypeEnum.attributeNode;
Node.prototype.TEXT_NODE = NodeTypeEnum.textNode;
Node.prototype.CDATA_SECTION_NODE = NodeTypeEnum.cdataSectionNode;
Node.prototype.COMMENT_NODE = NodeTypeEnum.commentNode;
Node.prototype.DOCUMENT_NODE = NodeTypeEnum.documentNode;
Node.prototype.DOCUMENT_TYPE_NODE = NodeTypeEnum.documentTypeNode;
Node.prototype.DOCUMENT_FRAGMENT_NODE = NodeTypeEnum.documentFragmentNode;
Node.prototype.PROCESSING_INSTRUCTION_NODE = NodeTypeEnum.processingInstructionNode;
Node.prototype.DOCUMENT_POSITION_CONTAINED_BY =
    NodeDocumentPositionEnum.containedBy;
Node.prototype.DOCUMENT_POSITION_CONTAINS =
    NodeDocumentPositionEnum.contains;
Node.prototype.DOCUMENT_POSITION_DISCONNECTED =
    NodeDocumentPositionEnum.disconnect;
Node.prototype.DOCUMENT_POSITION_FOLLOWING =
    NodeDocumentPositionEnum.following;
Node.prototype.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC =
    NodeDocumentPositionEnum.implementationSpecific;
Node.prototype.DOCUMENT_POSITION_PRECEDING =
    NodeDocumentPositionEnum.preceding;
//# sourceMappingURL=Node.js.map