import { NodeType, NAME, IS_CONNECTED, OWNER_DOCUMENT, HOOKS, NamespaceURI, NS } from './constants.mjs';
import { Event } from './Event.mjs';
import { ParentNode } from './ParentNode.mjs';
import { Element } from './Element.mjs';
import { SVGElement } from './SVGElement.mjs';
import { Text } from './Text.mjs';
import { Comment } from './Comment.mjs';
import { DocumentFragment } from './DocumentFragment.mjs';
import { HTMLTemplateElement } from './HTMLTemplateElement.mjs';
import { cloneNode, isParentNode } from './shared.mjs';
import { HTMLBodyElement } from './HTMLBodyElement.mjs';
import { HTMLHeadElement } from './HTMLHeadElement.mjs';
import { HTMLHtmlElement } from './HTMLHtmlElement.mjs';

class Document extends ParentNode {
  nodeType = (() => NodeType.DOCUMENT_NODE)();
  [NAME] = '#document';
  [IS_CONNECTED] = true;
  constructor(defaultView) {
    super();
    this.defaultView = defaultView;
    this[OWNER_DOCUMENT] = this;
    this.documentElement = setupElement(new HTMLHtmlElement(), this, 'html');
    this.body = setupElement(new HTMLBodyElement(), this, 'body');
    this.head = setupElement(new HTMLHeadElement(), this, 'head');
    this.appendChild(this.documentElement);
    this.documentElement.appendChild(this.head);
    this.documentElement.appendChild(this.body);
  }
  createElement(localName) {
    return createElement(this, localName);
  }
  createElementNS(namespaceURI, localName) {
    return createElement(this, localName, namespaceURI);
  }
  createTextNode(data) {
    const text = createNode(new Text(data), this);
    this[HOOKS].createText?.(text, String(data));
    return text;
  }
  createComment(data) {
    return createNode(new Comment(data), this);
  }
  createDocumentFragment() {
    return createNode(new DocumentFragment(), this);
  }
  createEvent() {
    return new Event('');
  }
  importNode(node, deep) {
    return cloneNode(node, deep, this);
  }
  adoptNode(node) {
    if (node[OWNER_DOCUMENT] === this) return node;
    node.parentNode?.removeChild(node);
    adoptNode(node, this);
    return node;
  }
}
function createNode(node, ownerDocument) {
  Object.defineProperty(node, OWNER_DOCUMENT, {
    value: ownerDocument,
    writable: true,
    enumerable: false
  });
  return node;
}
function createElement(ownerDocument, name, namespace) {
  let element;
  const lowerName = String(name).toLowerCase();
  if (namespace === NamespaceURI.SVG) {
    element = new SVGElement();
  } else if (lowerName === 'template') {
    element = new HTMLTemplateElement();
  } else {
    const CustomElement = ownerDocument.defaultView.customElements.get(name);
    element = CustomElement ? new CustomElement() : new Element();
  }
  return setupElement(element, ownerDocument, name, namespace);
}
function setupElement(element, ownerDocument, name, namespace) {
  createNode(element, ownerDocument);
  Object.defineProperty(element, NAME, {
    value: name
  });
  if (namespace) {
    Object.defineProperty(element, NS, {
      value: namespace
    });
  }
  ownerDocument[HOOKS].createElement?.(element, namespace);
  return element;
}
function adoptNode(node, document) {
  node[OWNER_DOCUMENT] = document;
  if (isParentNode(node)) {
    for (const child of node.childNodes) {
      adoptNode(child, document);
    }
  }
}

export { Document, adoptNode, createElement, createNode, setupElement };
