'use strict';

require('../../../../hastscript@7.2.0/node_modules/hastscript/index.js');
var index = require('../../../../property-information@6.5.0/node_modules/property-information/index.js');
require('../../../../vfile-location@4.1.0/node_modules/vfile-location/index.js');
var index$2 = require('../../../../web-namespaces@2.0.1/node_modules/web-namespaces/index.js');
var index$1 = require('../../../../vfile-location@4.1.0/node_modules/vfile-location/lib/index.js');
var svg = require('../../../../hastscript@7.2.0/node_modules/hastscript/lib/svg.js');
var html = require('../../../../hastscript@7.2.0/node_modules/hastscript/lib/html.js');
var find = require('../../../../property-information@6.5.0/node_modules/property-information/lib/find.js');

/**
 * @typedef {import('vfile').VFile} VFile
 * @typedef {import('property-information').Schema} Schema
 * @typedef {import('unist').Position} Position
 * @typedef {import('unist').Point} Point
 * @typedef {import('hast').Element} Element
 * @typedef {import('hast').Root} Root
 * @typedef {import('hast').Content} Content
 * @typedef {import('parse5').DefaultTreeAdapterMap} DefaultTreeAdapterMap
 * @typedef {import('parse5').Token.ElementLocation} P5ElementLocation
 * @typedef {import('parse5').Token.Location} P5Location
 */


const own = {}.hasOwnProperty;
/** @type {unknown} */
// type-coverage:ignore-next-line
const proto = Object.prototype;

/**
 * Transform a `parse5` AST to hast.
 *
 * @param {P5Node} tree
 *   `parse5` tree to transform.
 * @param {Options | VFile | null | undefined} [options]
 *   Configuration.
 * @returns {Node}
 *   hast tree.
 */
function fromParse5(tree, options) {
  const options_ = options || {};
  /** @type {Options} */
  let settings;
  /** @type {VFile | undefined} */
  let file;

  if (isFile(options_)) {
    file = options_;
    settings = {};
  } else {
    file = options_.file || undefined;
    settings = options_;
  }

  return one(
    {
      schema: settings.space === 'svg' ? index.svg : index.html,
      file,
      verbose: settings.verbose,
      location: false
    },
    tree
  )
}

/**
 * Transform a node.
 *
 * @param {State} state
 *   Info passed around about the current state.
 * @param {P5Node} node
 *   p5 node.
 * @returns {Node}
 *   hast node.
 */
function one(state, node) {
  /** @type {Node} */
  let result;

  switch (node.nodeName) {
    case '#comment': {
      const reference = /** @type {P5Comment} */ (node);
      result = {type: 'comment', value: reference.data};
      patch(state, reference, result);
      return result
    }

    case '#document':
    case '#document-fragment': {
      const reference = /** @type {P5Document | P5DocumentFragment} */ (node);
      const quirksMode =
        'mode' in reference
          ? reference.mode === 'quirks' || reference.mode === 'limited-quirks'
          : false;

      result = {
        type: 'root',
        children: all(state, node.childNodes),
        data: {quirksMode}
      };

      if (state.file && state.location) {
        const doc = String(state.file);
        const loc = index$1.location(doc);
        const start = loc.toPoint(0);
        const end = loc.toPoint(doc.length);
        // @ts-expect-error: always defined as we give valid input.
        result.position = {start, end};
      }

      return result
    }

    case '#documentType': {
      const reference = /** @type {P5DocumentType} */ (node);
      // @ts-expect-error Types are out of date.
      result = {type: 'doctype'};
      patch(state, reference, result);
      return result
    }

    case '#text': {
      const reference = /** @type {P5Text} */ (node);
      result = {type: 'text', value: reference.value};
      patch(state, reference, result);
      return result
    }

    // Element.
    default: {
      const reference = /** @type {P5Element} */ (node);
      result = element(state, reference);
      return result
    }
  }
}

/**
 * Transform children.
 *
 * @param {State} state
 *   Info passed around about the current state.
 * @param {Array<P5Node>} nodes
 *   Nodes.
 * @returns {Array<Content>}
 *   hast nodes.
 */
function all(state, nodes) {
  let index = -1;
  /** @type {Array<Content>} */
  const result = [];

  while (++index < nodes.length) {
    // @ts-expect-error Assume no roots in `nodes`.
    result[index] = one(state, nodes[index]);
  }

  return result
}

/**
 * Transform an element.
 *
 * @param {State} state
 *   Info passed around about the current state.
 * @param {P5Element} node
 *   `parse5` node to transform.
 * @returns {Element}
 *   hast node.
 */
function element(state, node) {
  const schema = state.schema;

  state.schema = node.namespaceURI === index$2.webNamespaces.svg ? index.svg : index.html;

  // Props.
  let index$1 = -1;
  /** @type {Record<string, string>} */
  const props = {};

  while (++index$1 < node.attrs.length) {
    const attribute = node.attrs[index$1];
    const name =
      (attribute.prefix ? attribute.prefix + ':' : '') + attribute.name;
    if (!own.call(proto, name)) {
      props[name] = attribute.value;
    }
  }

  // Build.
  const fn = state.schema.space === 'svg' ? svg.s : html.h;
  const result = fn(node.tagName, props, all(state, node.childNodes));
  patch(state, node, result);

  // Switch content.
  if (result.tagName === 'template') {
    const reference = /** @type {P5Template} */ (node);
    const pos = reference.sourceCodeLocation;
    const startTag = pos && pos.startTag && position(pos.startTag);
    const endTag = pos && pos.endTag && position(pos.endTag);

    /** @type {Root} */
    // @ts-expect-error Types are wrong.
    const content = one(state, reference.content);

    if (startTag && endTag && state.file) {
      content.position = {start: startTag.end, end: endTag.start};
    }

    result.content = content;
  }

  state.schema = schema;

  return result
}

/**
 * Patch positional info from `from` onto `to`.
 *
 * @param {State} state
 *   Info passed around about the current state.
 * @param {P5Node} from
 *   p5 node.
 * @param {Node} to
 *   hast node.
 * @returns {void}
 *   Nothing.
 */
function patch(state, from, to) {
  if ('sourceCodeLocation' in from && from.sourceCodeLocation && state.file) {
    const position = createLocation(state, to, from.sourceCodeLocation);

    if (position) {
      state.location = true;
      to.position = position;
    }
  }
}

/**
 * Create clean positional information.
 *
 * @param {State} state
 *   Info passed around about the current state.
 * @param {Node} node
 *   hast node.
 * @param {P5ElementLocation} location
 *   p5 location info.
 * @returns {Position | undefined}
 *   Position, or nothing.
 */
function createLocation(state, node, location) {
  const result = position(location);

  if (node.type === 'element') {
    const tail = node.children[node.children.length - 1];

    // Bug for unclosed with children.
    // See: <https://github.com/inikulin/parse5/issues/109>.
    if (
      result &&
      !location.endTag &&
      tail &&
      tail.position &&
      tail.position.end
    ) {
      result.end = Object.assign({}, tail.position.end);
    }

    if (state.verbose) {
      /** @type {Record<string, Position | undefined>} */
      const props = {};
      /** @type {string} */
      let key;

      if (location.attrs) {
        for (key in location.attrs) {
          if (own.call(location.attrs, key)) {
            props[find.find(state.schema, key).property] = position(
              location.attrs[key]
            );
          }
        }
      }

      node.data = {
        position: {
          // @ts-expect-error: assume not `undefined`.
          opening: position(location.startTag),
          closing: location.endTag ? position(location.endTag) : null,
          properties: props
        }
      };
    }
  }

  return result
}

/**
 * Turn a p5 location into a position.
 *
 * @param {P5Location} loc
 *   Location.
 * @returns {Position | undefined}
 *   Position or nothing.
 */
function position(loc) {
  const start = point({
    line: loc.startLine,
    column: loc.startCol,
    offset: loc.startOffset
  });
  const end = point({
    line: loc.endLine,
    column: loc.endCol,
    offset: loc.endOffset
  });
  // @ts-expect-error `undefined` is fine.
  return start || end ? {start, end} : undefined
}

/**
 * Filter out invalid points.
 *
 * @param {Point} point
 *   Point with potentially `undefined` values.
 * @returns {Point | undefined}
 *   Point or nothing.
 */
function point(point) {
  return point.line && point.column ? point : undefined
}

/**
 * Check if something is a file.
 *
 * @param {VFile | Options} value
 *   File or options.
 * @returns {value is VFile}
 *   Whether `value` is a file.
 */
function isFile(value) {
  return 'messages' in value
}

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