“use strict”; const attributes = require(“./attributes”); const cloningSteps = require(“./helpers/internal-constants”).cloningSteps; const domSymbolTree = require(“./helpers/internal-constants”).domSymbolTree; const NODE_TYPE = require(“./node-type”); const orderedSetParser = require(“./helpers/ordered-set-parser”); const createHTMLCollection = require(“./html-collection”).create; const domTokenListContains = require(“./dom-token-list”).contains;

module.exports.clone = function (core, node, document, cloneChildren) {

if (document === undefined) {
  document = node._ownerDocument;
}

let copy;
switch (node.nodeType) {
  case NODE_TYPE.DOCUMENT_NODE:
    // TODO: just use Document when we eliminate the difference between Document and HTMLDocument.
    if (node.contentType === "text/html") { // need to differentiate due to parsing mode
      copy = document.implementation.createHTMLDocument();
      copy.removeChild(copy.documentElement); // ;_;
    } else {
      copy = document.implementation.createDocument("", "", null);
    }
    document = copy;
    break;

  case NODE_TYPE.DOCUMENT_TYPE_NODE:
    copy = document.implementation.createDocumentType(node.name, node.publicId, node.systemId);
    break;

  case NODE_TYPE.ELEMENT_NODE:
    copy = document._createElementWithCorrectElementInterface(node._localName, node._namespaceURI);
    copy._namespaceURI = node._namespaceURI;
    copy._prefix = node._prefix;
    copy._localName = node._localName;
    attributes.copyAttributeList(node, copy);
    break;

  case NODE_TYPE.TEXT_NODE:
    copy = document.createTextNode(node._data);
    break;

  case NODE_TYPE.CDATA_SECTION_NODE:
    copy = document.createCDATASection(node._data);
    break;

  case NODE_TYPE.COMMENT_NODE:
    copy = document.createComment(node._data);
    break;

  case NODE_TYPE.PROCESSING_INSTRUCTION_NODE:
    copy = document.createProcessingInstruction(node.target, node._data);
    break;

  case NODE_TYPE.DOCUMENT_FRAGMENT_NODE:
    copy = document.createDocumentFragment();
    break;
}

if (node[cloningSteps]) {
  node[cloningSteps](copy, node, document, cloneChildren);
}

if (cloneChildren) {
  for (const child of domSymbolTree.childrenIterator(node)) {
    const childCopy = module.exports.clone(core, child, document, true);
    copy.appendChild(childCopy);
  }
}

return copy;

};

// For the following, memoization is not applied here since the memoized results are stored on `this`.

module.exports.listOfElementsWithClassNames = (classNames, root) => {

// https://dom.spec.whatwg.org/#concept-getElementsByClassName

const classes = orderedSetParser(classNames);

if (classes.size === 0) {
  return createHTMLCollection(root, () => []);
}

return createHTMLCollection(root, () => {
  const isQuirksMode = root._ownerDocument.compatMode === "BackCompat";

  return domSymbolTree.treeToArray(root, { filter(node) {
    if (node.nodeType !== NODE_TYPE.ELEMENT_NODE || node === root) {
      return false;
    }

    for (const className of classes) {
      if (!domTokenListContains(node.classList, className, { caseInsensitive: isQuirksMode })) {
        return false;
      }
    }

    return true;
  } });
});

};

module.exports.listOfElementsWithQualifiedName = (qualifiedName, root) => {

// https://dom.spec.whatwg.org/#concept-getelementsbytagname

if (qualifiedName === "*") {
  return createHTMLCollection(root, () => {
    return domSymbolTree.treeToArray(root, { filter(node) {
      return node.nodeType === NODE_TYPE.ELEMENT_NODE && node !== root;
    } });
  });
}

if (root._ownerDocument._parsingMode === "html") {
  const lowerQualifiedName = qualifiedName.toLowerCase();

  return createHTMLCollection(root, () => {
    return domSymbolTree.treeToArray(root, { filter(node) {
      if (node.nodeType !== NODE_TYPE.ELEMENT_NODE || node === root) {
        return false;
      }

      if (node._namespaceURI === "http://www.w3.org/1999/xhtml") {
        return node._qualifiedName === lowerQualifiedName;
      }

      return node._qualifiedName === qualifiedName;
    } });
  });
}

return createHTMLCollection(root, () => {
  return domSymbolTree.treeToArray(root, { filter(node) {
    if (node.nodeType !== NODE_TYPE.ELEMENT_NODE || node === root) {
      return false;
    }

    return node._qualifiedName === qualifiedName;
  } });
});

};

module.exports.listOfElementsWithNamespaceAndLocalName = (namespace, localName, root) => {

// https://dom.spec.whatwg.org/#concept-getelementsbytagnamens

if (namespace === "") {
  namespace = null;
}

if (namespace === "*" && localName === "*") {
  return createHTMLCollection(root, () => {
    return domSymbolTree.treeToArray(root, { filter(node) {
      return node.nodeType === NODE_TYPE.ELEMENT_NODE && node !== root;
    } });
  });
}

if (namespace === "*") {
  return createHTMLCollection(root, () => {
    return domSymbolTree.treeToArray(root, { filter(node) {
      if (node.nodeType !== NODE_TYPE.ELEMENT_NODE || node === root) {
        return false;
      }

      return node._localName === localName;
    } });
  });
}

if (localName === "*") {
  return createHTMLCollection(root, () => {
    return domSymbolTree.treeToArray(root, { filter(node) {
      if (node.nodeType !== NODE_TYPE.ELEMENT_NODE || node === root) {
        return false;
      }

      return node._namespaceURI === namespace;
    } });
  });
}

return createHTMLCollection(root, () => {
  return domSymbolTree.treeToArray(root, { filter(node) {
    if (node.nodeType !== NODE_TYPE.ELEMENT_NODE || node === root) {
      return false;
    }

    return node._localName === localName && node._namespaceURI === namespace;
  } });
});

};