text: import { convert } from '../unist-util-is';
  import { color } from './color';
  
  /**
   * Continue traversing as normal
   */
  export const CONTINUE = true;
  /**
   * Do not traverse this node’s children
   */
  export const SKIP = 'skip';
  /**
   * Stop traversing immediately
   */
  export const EXIT = false;
  
  /**
   * Visit children of tree which pass a test
   *
   * @param tree Abstract syntax tree to walk
   * @param test Test node, optional
   * @param visitor Function to run for each node
   * @param reverse Visit the tree in reverse order, defaults to false
   */
  export const visitParents =
    /**
     * @type {(
     *   (<Tree extends Node, Check extends Test>(tree: Tree, test: Check, visitor: import('./complex-types').BuildVisitor<Tree, Check>, reverse?: boolean) => void) &
     *   (<Tree extends Node>(tree: Tree, visitor: import('./complex-types').BuildVisitor<Tree>, reverse?: boolean) => void)
     * )}
     */
    (
      /**
       * @param {Node} tree
       * @param {Test} test
       * @param {import('./complex-types').Visitor<Node>} visitor
       * @param {boolean} [reverse]
       */
      function (tree, test, visitor, reverse) {
        if (typeof test === 'function' && typeof visitor !== 'function') {
          reverse = visitor;
          // @ts-expect-error no visitor given, so `visitor` is test.
          visitor = test;
          test = null;
        }
  
        const is = convert(test);
        const step = reverse ? -1 : 1;
  
        factory(tree, null, [])();
  
        /**
         * @param {Node} node
         * @param {number?} index
         * @param {Array.<Parent>} parents
         */
        function factory(node, index, parents) {
          /** @type {Object.<string, unknown>} */
          // @ts-expect-error: hush
          const value = typeof node === 'object' && node !== null ? node : {};
          /** @type {string|undefined} */
          let name;
  
          if (typeof value.type === 'string') {
            name =
              typeof value.tagName === 'string' ? value.tagName : typeof value.name === 'string' ? value.name : undefined;
  
            Object.defineProperty(visit, 'name', {
              value: `node (${color(value.type + (name ? `<${name}>` : ''))})`,
            });
          }
  
          return visit;
  
          function visit() {
            /** @type {ActionTuple} */
            let result = [];
            /** @type {ActionTuple} */
            let subresult;
            /** @type {number} */
            let offset;
            /** @type {Array.<Parent>} */
            let grandparents;
  
            if (!test || is(node, index, parents[parents.length - 1] || null)) {
              result = toResult(visitor(node, parents));
  
              if (result[0] === EXIT) {
                return result;
              }
            }
  
            // @ts-expect-error looks like a parent.
            if (node.children && result[0] !== SKIP) {
              // @ts-expect-error looks like a parent.
              offset = (reverse ? node.children.length : -1) + step;
              // @ts-expect-error looks like a parent.
              grandparents = parents.concat(node);
  
              // @ts-expect-error looks like a parent.
              while (offset > -1 && offset < node.children.length) {
                // @ts-expect-error looks like a parent.
                subresult = factory(node.children[offset], offset, grandparents)();
  
                if (subresult[0] === EXIT) {
                  return subresult;
                }
  
                offset = typeof subresult[1] === 'number' ? subresult[1] : offset + step;
              }
            }
  
            return result;
          }
        }
      }
    );
  
  /**
   * @param {VisitorResult} value
   * @returns {ActionTuple}
   */
  function toResult(value) {
    if (Array.isArray(value)) {
      return value;
    }
  
    if (typeof value === 'number') {
      return [CONTINUE, value];
    }
  
    return [value];
  }
  