'use strict';

var index$3 = require('../../../../comma-separated-tokens@2.0.3/node_modules/comma-separated-tokens/index.js');
var index$1 = require('../../../../property-information@6.5.0/node_modules/property-information/index.js');
var index$4 = require('../../../../space-separated-tokens@2.0.2/node_modules/space-separated-tokens/index.js');
var index$2 = require('../../../../web-namespaces@2.0.1/node_modules/web-namespaces/index.js');
var index = require('../../../../zwitch@2.0.4/node_modules/zwitch/index.js');
var find = require('../../../../property-information@6.5.0/node_modules/property-information/lib/find.js');

/**
 * @typedef {import('parse5').DefaultTreeAdapterMap} DefaultTreeAdapterMap
 * @typedef {DefaultTreeAdapterMap['document']} P5Document
 * @typedef {DefaultTreeAdapterMap['documentFragment']} P5Fragment
 * @typedef {DefaultTreeAdapterMap['element']} P5Element
 * @typedef {DefaultTreeAdapterMap['node']} P5Node
 * @typedef {DefaultTreeAdapterMap['documentType']} P5Doctype
 * @typedef {DefaultTreeAdapterMap['commentNode']} P5Comment
 * @typedef {DefaultTreeAdapterMap['textNode']} P5Text
 * @typedef {DefaultTreeAdapterMap['parentNode']} P5Parent
 * @typedef {import('parse5').Token.Attribute} P5Attribute
 * @typedef {Exclude<P5Node, P5Document | P5Fragment>} P5Child
 * @typedef {import('property-information').Schema} Schema
 * @typedef {import('hast').Root} Root
 * @typedef {import('hast').DocType} Doctype
 * @typedef {import('hast').Element} Element
 * @typedef {import('hast').Text} Text
 * @typedef {import('hast').Comment} Comment
 * @typedef {import('hast').Content} Content
 */


const own = {}.hasOwnProperty;

/** @type {(from: Node, schema: Schema) => P5Node} */
const one = index.zwitch('type', {handlers: {root, element, text, comment, doctype}});

/**
 * Transform a hast tree to Parse5’s AST.
 *
 * @param {Node} tree
 *   Tree to transform.
 * @param {Options | Space | null | undefined} [options]
 *   Configuration.
 * @returns {P5Node}
 *   `parse5` node.
 */
function toParse5(tree, options) {
  const space = options && typeof options === 'object' ? options.space : options;
  return one(tree, space === 'svg' ? index$1.svg : index$1.html)
}

/**
 * @param {Root} node
 * @param {Schema} schema
 *   Current schema.
 * @returns {P5Document}
 */
function root(node, schema) {
  /** @type {P5Document} */
  const result = {
    nodeName: '#document',
    // @ts-expect-error: `parse5` uses enums, which are actually strings.
    mode: (node.data || {}).quirksMode ? 'quirks' : 'no-quirks',
    childNodes: []
  };
  result.childNodes = all(node.children, result, schema);
  patch(node, result);
  return result
}

/**
 * @param {Root} node
 * @param {Schema} schema
 *   Current schema.
 * @returns {P5Fragment}
 */
function fragment(node, schema) {
  /** @type {P5Fragment} */
  const result = {nodeName: '#document-fragment', childNodes: []};
  result.childNodes = all(node.children, result, schema);
  patch(node, result);
  return result
}

/**
 * @param {Doctype} node
 * @returns {P5Doctype}
 */
function doctype(node) {
  /** @type {P5Doctype} */
  const result = {
    nodeName: '#documentType',
    name: 'html',
    publicId: '',
    systemId: '',
    // @ts-expect-error: change to `null` in a major?
    parentNode: undefined
  };

  patch(node, result);
  return result
}

/**
 * @param {Text} node
 * @returns {P5Text}
 */
function text(node) {
  /** @type {P5Text} */
  const result = {
    nodeName: '#text',
    value: node.value,
    // @ts-expect-error: no `parentNode`
    parentNode: undefined
  };
  patch(node, result);
  return result
}

/**
 * @param {Comment} node
 * @returns {P5Comment}
 */
function comment(node) {
  /** @type {P5Comment} */
  const result = {
    nodeName: '#comment',
    data: node.value,
    // @ts-expect-error: no `parentNode`
    parentNode: undefined
  };

  patch(node, result);

  return result
}

/**
 * @param {Element} node
 * @param {Schema} schema
 *   Current schema.
 * @returns {P5Element}
 *   `parse5` node.
 */
function element(node, schema) {
  const parentSchema = schema;
  let currentSchema = parentSchema;

  if (
    node.type === 'element' &&
    node.tagName.toLowerCase() === 'svg' &&
    parentSchema.space === 'html'
  ) {
    currentSchema = index$1.svg;
  }

  /** @type {Array<P5Attribute>} */
  const attrs = [];
  /** @type {string} */
  let prop;

  if (node.properties) {
    for (prop in node.properties) {
      if (prop !== 'children' && own.call(node.properties, prop)) {
        const result = createProperty(
          currentSchema,
          prop,
          node.properties[prop]
        );

        if (result) {
          attrs.push(result);
        }
      }
    }
  }

  /** @type {P5Element} */
  const result = {
    nodeName: node.tagName,
    tagName: node.tagName,
    attrs,
    // @ts-expect-error: html and svg both have a space.
    namespaceURI: index$2.webNamespaces[currentSchema.space],
    childNodes: [],
    // @ts-expect-error: no `parentNode`
    parentNode: undefined
  };
  result.childNodes = all(node.children, result, currentSchema);
  patch(node, result);

  if (node.tagName === 'template' && node.content) {
    // @ts-expect-error: `parse5` types are wrong.
    result.content = fragment(node.content, currentSchema);
  }

  return result
}

/**
 * Handle a property.
 *
 * @param {Schema} schema
 *   Current schema.
 * @param {string} prop
 *   Key.
 * @param {Array<string | number> | string | number | boolean | null | undefined} value
 *   hast property value.
 * @returns {P5Attribute | void}
 *   Field for runtime, optional.
 */
function createProperty(schema, prop, value) {
  const info = find.find(schema, prop);

  // Ignore nullish and `NaN` values.
  if (
    value === undefined ||
    value === null ||
    value === false ||
    (typeof value === 'number' && Number.isNaN(value)) ||
    (!value && info.boolean)
  ) {
    return
  }

  if (Array.isArray(value)) {
    // Accept `array`.
    // Most props are space-separated.
    value = info.commaSeparated ? index$3.stringify(value) : index$4.stringify(value);
  }

  /** @type {P5Attribute} */
  const attribute = {
    name: info.attribute,
    value: value === true ? '' : String(value)
  };

  if (info.space && info.space !== 'html' && info.space !== 'svg') {
    const index = attribute.name.indexOf(':');

    if (index < 0) {
      attribute.prefix = '';
    } else {
      attribute.name = attribute.name.slice(index + 1);
      attribute.prefix = info.attribute.slice(0, index);
    }

    attribute.namespace = index$2.webNamespaces[info.space];
  }

  return attribute
}

/**
 * Transform all hast nodes.
 *
 * @param {Array<Content>} children
 *   List of children.
 * @param {P5Parent} parentNode
 *   `parse5` parent node.
 * @param {Schema} schema
 *   Current schema.
 * @returns {Array<P5Child>}
 *   Transformed children.
 */
function all(children, parentNode, schema) {
  let index = -1;
  /** @type {Array<P5Child>} */
  const results = [];

  if (children) {
    while (++index < children.length) {
      /** @type {P5Child} */
      // @ts-expect-error assume no document.
      const child = one(children[index], schema);

      child.parentNode = parentNode;

      results.push(child);
    }
  }

  return results
}

/**
 * Add position info from `from` to `to`.
 *
 * @param {Node} from
 *   hast node.
 * @param {P5Node} to
 *   `parse5` node.
 * @returns {void}
 *   Nothing.
 */
function patch(from, to) {
  const position = from.position;

  if (position && position.start && position.end) {
    to.sourceCodeLocation = {
      startLine: position.start.line,
      startCol: position.start.column,
      // @ts-expect-error assume this is set.
      startOffset: position.start.offset,
      endLine: position.end.line,
      endCol: position.end.column,
      // @ts-expect-error assume this is set.
      endOffset: position.end.offset
    };
  }
}

exports.toParse5 = toParse5;
//# sourceMappingURL=index.js.map
