import Element from '../element/Element.js';
import * as PropertySymbol from '../../PropertySymbol.js';
import Node from '../node/Node.js';
import NodeIterator from '../../tree-walker/NodeIterator.js';
import TreeWalker from '../../tree-walker/TreeWalker.js';
import DocumentFragment from '../document-fragment/DocumentFragment.js';
import XMLParser from '../../xml-parser/XMLParser.js';
import Event from '../../event/Event.js';
import DOMImplementation from '../../dom-implementation/DOMImplementation.js';
import NamespaceURI from '../../config/NamespaceURI.js';
import DocumentType from '../document-type/DocumentType.js';
import ParentNodeUtility from '../parent-node/ParentNodeUtility.js';
import QuerySelector from '../../query-selector/QuerySelector.js';
import Comment from '../comment/Comment.js';
import Text from '../text/Text.js';
import HTMLCollection from '../element/HTMLCollection.js';
import DocumentReadyStateEnum from './DocumentReadyStateEnum.js';
import Selection from '../../selection/Selection.js';
import Attr from '../attr/Attr.js';
import ProcessingInstruction from '../processing-instruction/ProcessingInstruction.js';
import VisibilityStateEnum from './VisibilityStateEnum.js';
import NodeTypeEnum from '../node/NodeTypeEnum.js';
import CookieStringUtility from '../../cookie/urilities/CookieStringUtility.js';
import { URL } from 'url';
import HTMLElementConfig from '../../config/HTMLElementConfig.js';
import WindowBrowserContext from '../../window/WindowBrowserContext.js';
import NodeFactory from '../NodeFactory.js';
import SVGElementConfig from '../../config/SVGElementConfig.js';
import StringUtility from '../../StringUtility.js';
const PROCESSING_INSTRUCTION_TARGET_REGEXP = /^[a-z][a-z0-9-]+$/;
/**
 * Document.
 */
export default class Document extends Node {
    // Internal properties
    [PropertySymbol.children] = null;
    [PropertySymbol.activeElement] = null;
    [PropertySymbol.nextActiveElement] = null;
    [PropertySymbol.currentScript] = null;
    [PropertySymbol.rootNode] = this;
    [PropertySymbol.isFirstWrite] = true;
    [PropertySymbol.isFirstWriteAfterOpen] = false;
    [PropertySymbol.nodeType] = NodeTypeEnum.documentNode;
    [PropertySymbol.isConnected] = true;
    [PropertySymbol.adoptedStyleSheets] = [];
    [PropertySymbol.implementation] = new DOMImplementation(this);
    [PropertySymbol.readyState] = DocumentReadyStateEnum.interactive;
    [PropertySymbol.referrer] = '';
    [PropertySymbol.defaultView] = null;
    [PropertySymbol.forms] = null;
    [PropertySymbol.affectsComputedStyleCache] = [];
    [PropertySymbol.ownerDocument] = null;
    [PropertySymbol.elementIdMap] = new Map();
    // Private properties
    #selection = null;
    // Events
    onreadystatechange = null;
    onpointerlockchange = null;
    onpointerlockerror = null;
    onbeforecopy = null;
    onbeforecut = null;
    onbeforepaste = null;
    onfreeze = null;
    onresume = null;
    onsearch = null;
    onvisibilitychange = null;
    onfullscreenchange = null;
    onfullscreenerror = null;
    onwebkitfullscreenchange = null;
    onwebkitfullscreenerror = null;
    onbeforexrselect = null;
    onabort = null;
    onbeforeinput = null;
    onblur = null;
    oncancel = null;
    oncanplay = null;
    oncanplaythrough = null;
    onchange = null;
    onclick = null;
    onclose = null;
    oncontextlost = null;
    oncontextmenu = null;
    oncontextrestored = null;
    oncuechange = null;
    ondblclick = null;
    ondrag = null;
    ondragend = null;
    ondragenter = null;
    ondragleave = null;
    ondragover = null;
    ondragstart = null;
    ondrop = null;
    ondurationchange = null;
    onemptied = null;
    onended = null;
    onerror = null;
    onfocus = null;
    onformdata = null;
    oninput = null;
    oninvalid = null;
    onkeydown = null;
    onkeypress = null;
    onkeyup = null;
    onload = null;
    onloadeddata = null;
    onloadedmetadata = null;
    onloadstart = null;
    onmousedown = null;
    onmouseenter = null;
    onmouseleave = null;
    onmousemove = null;
    onmouseout = null;
    onmouseover = null;
    onmouseup = null;
    onmousewheel = null;
    onpause = null;
    onplay = null;
    onplaying = null;
    onprogress = null;
    onratechange = null;
    onreset = null;
    onresize = null;
    onscroll = null;
    onsecuritypolicyviolation = null;
    onseeked = null;
    onseeking = null;
    onselect = null;
    onslotchange = null;
    onstalled = null;
    onsubmit = null;
    onsuspend = null;
    ontimeupdate = null;
    ontoggle = null;
    onvolumechange = null;
    onwaiting = null;
    onwebkitanimationend = null;
    onwebkitanimationiteration = null;
    onwebkitanimationstart = null;
    onwebkittransitionend = null;
    onwheel = null;
    onauxclick = null;
    ongotpointercapture = null;
    onlostpointercapture = null;
    onpointerdown = null;
    onpointermove = null;
    onpointerrawupdate = null;
    onpointerup = null;
    onpointercancel = null;
    onpointerover = null;
    onpointerout = null;
    onpointerenter = null;
    onpointerleave = null;
    onselectstart = null;
    onselectionchange = null;
    onanimationend = null;
    onanimationiteration = null;
    onanimationstart = null;
    ontransitionrun = null;
    ontransitionstart = null;
    ontransitionend = null;
    ontransitioncancel = null;
    oncopy = null;
    oncut = null;
    onpaste = null;
    onbeforematch = null;
    /**
     * Returns adopted style sheets.
     *
     * @returns Adopted style sheets.
     */
    get adoptedStyleSheets() {
        return this[PropertySymbol.adoptedStyleSheets];
    }
    /**
     * Sets adopted style sheets.
     *
     * @param value Adopted style sheets.
     */
    set adoptedStyleSheets(value) {
        this[PropertySymbol.adoptedStyleSheets] = value;
    }
    /**
     * Returns DOM implementation.
     *
     * @returns DOM implementation.
     */
    get implementation() {
        return this[PropertySymbol.implementation];
    }
    /**
     * Returns document ready state.
     *
     * @returns Document ready state.
     */
    get readyState() {
        return this[PropertySymbol.readyState];
    }
    /**
     * Returns referrer.
     *
     * @returns Referrer.
     */
    get referrer() {
        return this[PropertySymbol.referrer];
    }
    /**
     * Returns default view.
     *
     * @returns Default view.
     */
    get defaultView() {
        return this[PropertySymbol.defaultView];
    }
    /**
     * Returns document children.
     */
    get children() {
        if (!this[PropertySymbol.children]) {
            const elements = this[PropertySymbol.elementArray];
            this[PropertySymbol.children] = new HTMLCollection(PropertySymbol.illegalConstructor, () => elements);
        }
        return this[PropertySymbol.children];
    }
    /**
     * Returns character set.
     *
     * @deprecated
     * @returns Character set.
     */
    get charset() {
        return this.characterSet;
    }
    /**
     * Returns character set.
     *
     * @returns Character set.
     */
    get characterSet() {
        const charset = QuerySelector.querySelector(this, 'meta[charset]')?.getAttributeNS(null, 'charset');
        return charset ? charset : 'UTF-8';
    }
    /**
     * Returns title.
     *
     * @returns Title.
     */
    get title() {
        const element = ParentNodeUtility.getElementByTagName(this, 'title');
        if (element) {
            return element.text.trim();
        }
        return '';
    }
    /**
     * Returns set title.
     *
     */
    set title(title) {
        const element = ParentNodeUtility.getElementByTagName(this, 'title');
        if (element) {
            element.textContent = title;
        }
        else {
            const newElement = this.createElement('title');
            newElement.textContent = title;
            this.head.appendChild(newElement);
        }
    }
    /**
     * Returns a collection of all area elements and a elements in a document with a value for the href attribute.
     */
    get links() {
        return QuerySelector.querySelectorAll(this, 'a[href],area[href]');
    }
    /**
     * Returns a collection of all form elements in a document.
     */
    get forms() {
        if (!this[PropertySymbol.forms]) {
            this[PropertySymbol.forms] = (ParentNodeUtility.getElementsByTagName(this, 'form'));
        }
        return this[PropertySymbol.forms];
    }
    /**
     * Last element child.
     *
     * @returns Element.
     */
    get childElementCount() {
        return this[PropertySymbol.elementArray].length;
    }
    /**
     * First element child.
     *
     * @returns Element.
     */
    get firstElementChild() {
        return this[PropertySymbol.elementArray][0] ?? null;
    }
    /**
     * Last element child.
     *
     * @returns Element.
     */
    get lastElementChild() {
        const children = this[PropertySymbol.elementArray];
        return children[children.length - 1] ?? null;
    }
    /**
     * Returns cookie string.
     *
     * @returns Cookie.
     */
    get cookie() {
        const browserFrame = new WindowBrowserContext(this[PropertySymbol.window]).getBrowserFrame();
        if (!browserFrame) {
            return '';
        }
        return CookieStringUtility.cookiesToString(browserFrame.page.context.cookieContainer.getCookies(new URL(this[PropertySymbol.window].location.href), true));
    }
    /**
     * Sets a cookie string.
     *
     * @param cookie Cookie string.
     */
    set cookie(cookie) {
        const browserFrame = new WindowBrowserContext(this[PropertySymbol.window]).getBrowserFrame();
        if (!browserFrame) {
            return;
        }
        browserFrame.page.context.cookieContainer.addCookies([
            CookieStringUtility.stringToCookie(new URL(this[PropertySymbol.window].location.href), cookie)
        ]);
    }
    /**
     * Node name.
     *
     * @returns Node name.
     */
    get nodeName() {
        return '#document';
    }
    /**
     * Returns <html> element.
     *
     * @returns Element.
     */
    get documentElement() {
        return ParentNodeUtility.getElementByTagName(this, 'html');
    }
    /**
     * Returns document type element.
     *
     * @returns Document type.
     */
    get doctype() {
        for (const node of this[PropertySymbol.nodeArray]) {
            if (node instanceof DocumentType) {
                return node;
            }
        }
        return null;
    }
    /**
     * Returns <body> element.
     *
     * @returns Element.
     */
    get body() {
        return ParentNodeUtility.getElementByTagName(this, 'body');
    }
    /**
     * Returns <head> element.
     *
     * @returns Element.
     */
    get head() {
        return ParentNodeUtility.getElementByTagName(this, 'head');
    }
    /**
     * Returns CSS style sheets.
     *
     * @returns CSS style sheets.
     */
    get styleSheets() {
        const styles = (QuerySelector.querySelectorAll(this, 'link[rel="stylesheet"][href],style'));
        const styleSheets = [];
        for (const style of styles) {
            const sheet = style.sheet;
            if (sheet) {
                styleSheets.push(sheet);
            }
        }
        return styleSheets;
    }
    /**
     * Returns active element.
     *
     * @returns Active element.
     */
    get activeElement() {
        if (this[PropertySymbol.activeElement] &&
            !this[PropertySymbol.activeElement][PropertySymbol.isConnected]) {
            this[PropertySymbol.activeElement] = null;
        }
        if (this[PropertySymbol.activeElement] &&
            this[PropertySymbol.activeElement] instanceof Element) {
            let rootNode = (this[PropertySymbol.activeElement].getRootNode());
            let activeElement = this[PropertySymbol.activeElement];
            while (rootNode !== this) {
                activeElement = rootNode.host;
                rootNode = activeElement ? activeElement.getRootNode() : this;
            }
            return activeElement;
        }
        return this[PropertySymbol.activeElement] || this.body || this.documentElement || null;
    }
    /**
     * Returns scrolling element.
     *
     * @returns Scrolling element.
     */
    get scrollingElement() {
        return this.documentElement;
    }
    /**
     * Returns location.
     *
     * @returns Location.
     */
    get location() {
        return this[PropertySymbol.window].location;
    }
    /**
     * Returns scripts.
     *
     * @returns Scripts.
     */
    get scripts() {
        return this.getElementsByTagName('script');
    }
    /**
     * Returns base URI.
     *
     * @override
     * @returns Base URI.
     */
    get baseURI() {
        const element = ParentNodeUtility.getElementByTagName(this, 'base');
        if (element) {
            return element.href;
        }
        return this[PropertySymbol.window].location.href;
    }
    /**
     * Returns URL.
     *
     * @returns URL of the current document.
     * */
    get URL() {
        return this[PropertySymbol.window].location.href;
    }
    /**
     * Returns document URI.
     *
     * @returns URL of the current document.
     * */
    get documentURI() {
        return this.URL;
    }
    /**
     * Returns domain.
     *
     * @returns Domain.
     * */
    get domain() {
        return this[PropertySymbol.window].location.hostname;
    }
    /**
     * Returns document visibility state.
     *
     * @returns the visibility state of the current document.
     * */
    get visibilityState() {
        if (this.defaultView) {
            return VisibilityStateEnum.visible;
        }
        return VisibilityStateEnum.hidden;
    }
    /**
     * Returns document hidden state.
     *
     * @returns the hidden state of the current document.
     * */
    get hidden() {
        if (this.defaultView) {
            return false;
        }
        return true;
    }
    /**
     * Gets the currently executing script element.
     *
     * @returns the currently executing script element.
     */
    get currentScript() {
        return this[PropertySymbol.currentScript];
    }
    /**
     * Inserts a set of Node objects or DOMString objects after the last child of the ParentNode. DOMString objects are inserted as equivalent Text nodes.
     *
     * @param nodes List of Node or DOMString.
     */
    append(...nodes) {
        ParentNodeUtility.append(this, ...nodes);
    }
    /**
     * Inserts a set of Node objects or DOMString objects before the first child of the ParentNode. DOMString objects are inserted as equivalent Text nodes.
     *
     * @param nodes List of Node or DOMString.
     */
    prepend(...nodes) {
        ParentNodeUtility.prepend(this, ...nodes);
    }
    /**
     * Replaces the existing children of a node with a specified new set of children.
     *
     * @param nodes List of Node or DOMString.
     */
    replaceChildren(...nodes) {
        ParentNodeUtility.replaceChildren(this, ...nodes);
    }
    /**
     * Query CSS selector to find matching elments.
     *
     * @param selector CSS selector.
     * @returns Matching elements.
     */
    querySelectorAll(selector) {
        return QuerySelector.querySelectorAll(this, selector);
    }
    /**
     * Query CSS Selector to find matching node.
     *
     * @param selector CSS selector.
     * @returns Matching element.
     */
    querySelector(selector) {
        return QuerySelector.querySelector(this, selector);
    }
    /**
     * Returns true if the command is supported.
     * @deprecated
     * @param _ Command.
     * @returns True if the command is supported, false otherwise.
     */
    queryCommandSupported(_) {
        if (!arguments.length) {
            throw new this[PropertySymbol.window].TypeError("Failed to execute 'queryCommandSupported' on 'Document': 1 argument required, but only 0 present.");
        }
        return true;
    }
    /**
     * Returns an elements by class name.
     *
     * @param className Tag name.
     * @returns Matching element.
     */
    getElementsByClassName(className) {
        return ParentNodeUtility.getElementsByClassName(this, className);
    }
    /**
     * Returns an elements by tag name.
     *
     * @param tagName Tag name.
     * @returns Matching element.
     */
    getElementsByTagName(tagName) {
        return ParentNodeUtility.getElementsByTagName(this, tagName);
    }
    /**
     * Returns an elements by tag name and namespace.
     *
     * @param namespaceURI Namespace URI.
     * @param tagName Tag name.
     * @returns Matching element.
     */
    getElementsByTagNameNS(namespaceURI, tagName) {
        return ParentNodeUtility.getElementsByTagNameNS(this, namespaceURI, tagName);
    }
    /**
     * Returns an element by ID.
     *
     * @param id ID.
     * @returns Matching element.
     */
    getElementById(id) {
        return ParentNodeUtility.getElementById(this, id);
    }
    /**
     * Returns an element by Name.
     *
     * @returns Matching element.
     * @param name
     */
    getElementsByName(name) {
        return QuerySelector.querySelectorAll(this, `[name="${name}"]`);
    }
    /**
     * Replaces the document HTML with new HTML.
     *
     * @param html HTML.
     */
    write(html) {
        const root = XMLParser.parse(this, html, { evaluateScripts: true });
        if (this[PropertySymbol.isFirstWrite] || this[PropertySymbol.isFirstWriteAfterOpen]) {
            if (this[PropertySymbol.isFirstWrite]) {
                if (!this[PropertySymbol.isFirstWriteAfterOpen]) {
                    this.open();
                }
                this[PropertySymbol.isFirstWrite] = false;
            }
            this[PropertySymbol.isFirstWriteAfterOpen] = false;
            let documentElement = null;
            let documentTypeNode = null;
            for (const node of root[PropertySymbol.nodeArray]) {
                if (node['tagName'] === 'HTML') {
                    documentElement = node;
                }
                else if (node[PropertySymbol.nodeType] === NodeTypeEnum.documentTypeNode) {
                    documentTypeNode = node;
                }
                if (documentElement && documentTypeNode) {
                    break;
                }
            }
            if (documentElement) {
                if (!this.documentElement) {
                    if (documentTypeNode) {
                        this.appendChild(documentTypeNode);
                    }
                    this.appendChild(documentElement);
                    const head = ParentNodeUtility.getElementByTagName(this, 'head');
                    let body = ParentNodeUtility.getElementByTagName(this, 'body');
                    if (!body) {
                        body = this.createElement('body');
                        documentElement.appendChild(this.createElement('body'));
                    }
                    if (!head) {
                        documentElement.insertBefore(this.createElement('head'), body);
                    }
                }
                else {
                    const rootBody = ParentNodeUtility.getElementByTagName(root, 'body');
                    const body = ParentNodeUtility.getElementByTagName(this, 'body');
                    if (rootBody && body) {
                        const childNodes = rootBody[PropertySymbol.nodeArray];
                        while (childNodes.length) {
                            body.appendChild(childNodes[0]);
                        }
                    }
                }
                // Remaining nodes outside the <html> element are added to the <body> element.
                const body = ParentNodeUtility.getElementByTagName(this, 'body');
                if (body) {
                    const childNodes = root[PropertySymbol.nodeArray];
                    while (childNodes.length) {
                        const child = childNodes[0];
                        if (child['tagName'] !== 'HTML' &&
                            child[PropertySymbol.nodeType] !== NodeTypeEnum.documentTypeNode) {
                            body.appendChild(child);
                        }
                    }
                }
            }
            else {
                const documentElement = this.createElement('html');
                const bodyElement = this.createElement('body');
                const headElement = this.createElement('head');
                const childNodes = root[PropertySymbol.nodeArray];
                while (childNodes.length) {
                    bodyElement.appendChild(childNodes[0]);
                }
                documentElement.appendChild(headElement);
                documentElement.appendChild(bodyElement);
                this.appendChild(documentElement);
            }
        }
        else {
            const bodyNode = ParentNodeUtility.getElementByTagName(root, 'body');
            const body = ParentNodeUtility.getElementByTagName(this, 'body');
            const childNodes = (bodyNode || root)[PropertySymbol.nodeArray];
            while (childNodes.length) {
                body.appendChild(childNodes[0]);
            }
        }
    }
    /**
     * Opens the document.
     *
     * @returns Document.
     */
    open() {
        this[PropertySymbol.isFirstWriteAfterOpen] = true;
        for (const eventType of this[PropertySymbol.listeners].bubbling.keys()) {
            const listeners = this[PropertySymbol.listeners].bubbling.get(eventType);
            if (listeners) {
                for (const listener of listeners) {
                    this.removeEventListener(eventType, listener);
                }
            }
        }
        for (const eventType of this[PropertySymbol.listeners].capturing.keys()) {
            const listeners = this[PropertySymbol.listeners].capturing.get(eventType);
            if (listeners) {
                for (const listener of listeners) {
                    this.removeEventListener(eventType, listener);
                }
            }
        }
        const childNodes = this[PropertySymbol.nodeArray];
        while (childNodes.length) {
            this.removeChild(childNodes[0]);
        }
        return this;
    }
    /**
     * Closes the document.
     */
    close() { }
    /**
     * Creates an element.
     *
     * @param qualifiedName Tag name.
     * @param [options] Options.
     * @param [options.is] Tag name of a custom element previously defined via customElements.define().
     * @returns Element.
     */
    createElement(qualifiedName, options) {
        return (this.createElementNS(NamespaceURI.html, StringUtility.asciiLowerCase(String(qualifiedName)), options));
    }
    /**
     * Creates an element with the specified namespace URI and qualified name.
     *
     * @param namespaceURI Namespace URI.
     * @param qualifiedName Tag name.
     * @param [options] Options.
     * @param [options.is] Tag name of a custom element previously defined via customElements.define().
     * @returns Element.
     */
    createElementNS(namespaceURI, qualifiedName, options) {
        const window = this[PropertySymbol.window];
        qualifiedName = String(qualifiedName);
        if (!qualifiedName) {
            throw new window.DOMException("Failed to execute 'createElementNS' on 'Document': The qualified name provided is empty.");
        }
        // SVG element
        if (namespaceURI === NamespaceURI.svg) {
            const config = SVGElementConfig[qualifiedName.toLowerCase()];
            const elementClass = config && config.localName === qualifiedName ? window[config.className] : window.SVGElement;
            const element = NodeFactory.createNode(this, elementClass);
            element[PropertySymbol.tagName] = qualifiedName;
            element[PropertySymbol.localName] = qualifiedName;
            element[PropertySymbol.namespaceURI] = namespaceURI;
            element[PropertySymbol.isValue] = options && options.is ? String(options.is) : null;
            return element;
        }
        // Custom HTML element
        const customElement = window.customElements[PropertySymbol.registry]?.[options && options.is ? String(options.is) : qualifiedName];
        if (customElement) {
            const element = new customElement.elementClass();
            element[PropertySymbol.tagName] = qualifiedName.toUpperCase();
            element[PropertySymbol.localName] = qualifiedName;
            element[PropertySymbol.namespaceURI] = namespaceURI;
            element[PropertySymbol.isValue] = options && options.is ? String(options.is) : null;
            return element;
        }
        const elementClass = HTMLElementConfig[qualifiedName]
            ? window[HTMLElementConfig[qualifiedName].className]
            : null;
        // Known HTML element
        if (elementClass) {
            const element = NodeFactory.createNode(this, elementClass);
            element[PropertySymbol.tagName] = qualifiedName.toUpperCase();
            element[PropertySymbol.localName] = qualifiedName;
            element[PropertySymbol.namespaceURI] = namespaceURI;
            element[PropertySymbol.isValue] = options && options.is ? String(options.is) : null;
            return element;
        }
        // Unknown HTML element (if it has an hyphen in the name, it is a custom element that hasn't been defined yet)
        const unknownElementClass = qualifiedName.includes('-')
            ? window.HTMLElement
            : window.HTMLUnknownElement;
        const element = NodeFactory.createNode(this, unknownElementClass);
        element[PropertySymbol.tagName] = qualifiedName.toUpperCase();
        element[PropertySymbol.localName] = qualifiedName;
        element[PropertySymbol.namespaceURI] = namespaceURI;
        element[PropertySymbol.isValue] = options && options.is ? String(options.is) : null;
        return element;
    }
    /* eslint-enable jsdoc/valid-types */
    /**
     * Creates a text node.
     *
     * @param [data] Text data.
     * @returns Text node.
     */
    createTextNode(data) {
        if (arguments.length < 1) {
            throw new this[PropertySymbol.window].TypeError(`Failed to execute 'createTextNode' on 'Document': 1 argument required, but only ${arguments.length} present.`);
        }
        // We should use the NodeFactory and not the class constructor, so that owner document will be this document
        return NodeFactory.createNode(this, Text, String(data));
    }
    /**
     * Creates a comment node.
     *
     * @param [data] Text data.
     * @returns Text node.
     */
    createComment(data) {
        if (arguments.length < 1) {
            throw new this[PropertySymbol.window].TypeError(`Failed to execute 'createComment' on 'Document': 1 argument required, but only ${arguments.length} present.`);
        }
        // We should use the NodeFactory and not the class constructor, so that owner document will be this document
        return NodeFactory.createNode(this, Comment, String(data));
    }
    /**
     * Creates a document fragment.
     *
     * @returns Document fragment.
     */
    createDocumentFragment() {
        // We should use the NodeFactory and not the class constructor, so that owner document will be this document
        return NodeFactory.createNode(this, DocumentFragment);
    }
    /**
     * Creates a node iterator.
     *
     * @param root Root.
     * @param [whatToShow] What to show.
     * @param [filter] Filter.
     */
    createNodeIterator(root, whatToShow = -1, filter = null) {
        return new NodeIterator(root, whatToShow, filter);
    }
    /**
     * Creates a Tree Walker.
     *
     * @param root Root.
     * @param [whatToShow] What to show.
     * @param [filter] Filter.
     */
    createTreeWalker(root, whatToShow = -1, filter = null) {
        return new TreeWalker(root, whatToShow, filter);
    }
    /**
     * Creates an event.
     *
     * @deprecated
     * @param type Type.
     * @returns Event.
     */
    createEvent(type) {
        if (typeof this[PropertySymbol.window][type] === 'function') {
            return new this[PropertySymbol.window][type]('init');
        }
        return new Event('init');
    }
    /**
     * Creates an Attr node.
     *
     * @param qualifiedName Name.
     * @returns Attribute.
     */
    createAttribute(qualifiedName) {
        return this.createAttributeNS(null, qualifiedName.toLowerCase());
    }
    /**
     * Creates a namespaced Attr node.
     *
     * @param namespaceURI Namespace URI.
     * @param qualifiedName Qualified name.
     * @returns Element.
     */
    createAttributeNS(namespaceURI, qualifiedName) {
        // We should use the NodeFactory and not the class constructor, so that owner document will be this document
        const attribute = NodeFactory.createNode(this, Attr);
        const parts = qualifiedName.split(':');
        attribute[PropertySymbol.namespaceURI] = namespaceURI;
        attribute[PropertySymbol.name] = qualifiedName;
        attribute[PropertySymbol.localName] = parts[1] ?? qualifiedName;
        attribute[PropertySymbol.prefix] = parts[0] ?? null;
        return attribute;
    }
    /**
     * Imports a node.
     *
     * @param node Node to import.
     * @param [deep=false] Set to "true" if the clone should be deep.
     */
    importNode(node, deep = false) {
        if (!(node instanceof Node)) {
            throw new this[PropertySymbol.window].DOMException('Parameter 1 was not of type Node.');
        }
        const clone = node.cloneNode(deep);
        this.#importNode(clone);
        return clone;
    }
    /**
     * Creates a range.
     *
     * @returns Range.
     */
    createRange() {
        return new this[PropertySymbol.window].Range();
    }
    /**
     * Adopts a node.
     *
     * @param node Node to adopt.
     * @returns Adopted node.
     */
    adoptNode(node) {
        if (!(node instanceof Node)) {
            throw new this[PropertySymbol.window].DOMException('Parameter 1 was not of type Node.');
        }
        const adopted = node[PropertySymbol.parentNode]
            ? node[PropertySymbol.parentNode].removeChild(node)
            : node;
        const document = this;
        Object.defineProperty(adopted, 'ownerDocument', { value: document });
        return adopted;
    }
    /**
     * Returns selection.
     *
     * @returns Selection.
     */
    getSelection() {
        if (!this.#selection) {
            this.#selection = new Selection(this);
        }
        return this.#selection;
    }
    /**
     * Returns a boolean value indicating whether the document or any element inside the document has focus.
     *
     * @returns "true" if the document has focus.
     */
    hasFocus() {
        return !!this.activeElement;
    }
    /**
     * Creates a Processing Instruction node.
     *
     * @param target Target.
     * @param data Data.
     * @returns ProcessingInstruction.
     */
    createProcessingInstruction(target, data) {
        if (arguments.length < 2) {
            throw new this[PropertySymbol.window].TypeError(`Failed to execute 'createProcessingInstruction' on 'Document': 2 arguments required, but only ${arguments.length} present.`);
        }
        target = String(target);
        data = String(data);
        if (!target || !PROCESSING_INSTRUCTION_TARGET_REGEXP.test(target)) {
            throw new this[PropertySymbol.window].DOMException(`Failed to execute 'createProcessingInstruction' on 'Document': The target provided ('${target}') is not a valid name.`);
        }
        if (data.includes('?>')) {
            throw new this[PropertySymbol.window].DOMException(`Failed to execute 'createProcessingInstruction' on 'Document': The data provided ('?>') contains '?>'`);
        }
        // We should use the NodeFactory and not the class constructor, so that owner document will be this document
        const element = NodeFactory.createNode(this, ProcessingInstruction);
        element[PropertySymbol.data] = data;
        element[PropertySymbol.target] = target;
        return element;
    }
    /**
     * Get element at a given point.
     *
     * @param _x horizontal coordinate
     * @param _y vertical coordinate
     * @returns Always returns null since Happy DOM does not render elements.
     */
    elementFromPoint(_x, _y) {
        return null;
    }
    /**
     * Imports a node.
     *
     * @param node Node.
     */
    #importNode(node) {
        node[PropertySymbol.ownerDocument] = this;
        for (const child of node[PropertySymbol.nodeArray]) {
            this.#importNode(child);
        }
    }
}
//# sourceMappingURL=Document.js.map