import { anchorIsValid } from '../doc/anchors.mjs';
import { visit } from '../visit.mjs';
import { ALIAS, isAlias, isCollection, isPair } from './identity.mjs';
import { NodeBase } from './Node.mjs';
import { toJS } from './toJS.mjs';

class Alias extends NodeBase {
  constructor(source) {
    super(ALIAS);
    this.source = source;
    Object.defineProperty(this, 'tag', {
      set() {
        throw new Error('Alias nodes cannot have tags');
      }
    });
  }
  /**
   * Resolve the value of this alias within `doc`, finding the last
   * instance of the `source` anchor before this node.
   */
  resolve(doc) {
    let found = undefined;
    visit(doc, {
      Node: (_key, node) => {
        if (node === this) return visit.BREAK;
        if (node.anchor === this.source) found = node;
      }
    });
    return found;
  }
  toJSON(_arg, ctx) {
    if (!ctx) return {
      source: this.source
    };
    const {
      anchors,
      doc,
      maxAliasCount
    } = ctx;
    const source = this.resolve(doc);
    if (!source) {
      const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`;
      throw new ReferenceError(msg);
    }
    let data = anchors.get(source);
    if (!data) {
      // Resolve anchors for Node.prototype.toJS()
      toJS(source, null, ctx);
      data = anchors.get(source);
    }
    /* istanbul ignore if */
    if (!data || data.res === undefined) {
      const msg = 'This should not happen: Alias anchor was not resolved?';
      throw new ReferenceError(msg);
    }
    if (maxAliasCount >= 0) {
      data.count += 1;
      if (data.aliasCount === 0) data.aliasCount = getAliasCount(doc, source, anchors);
      if (data.count * data.aliasCount > maxAliasCount) {
        const msg = 'Excessive alias count indicates a resource exhaustion attack';
        throw new ReferenceError(msg);
      }
    }
    return data.res;
  }
  toString(ctx, _onComment, _onChompKeep) {
    const src = `*${this.source}`;
    if (ctx) {
      anchorIsValid(this.source);
      if (ctx.options.verifyAliasOrder && !ctx.anchors.has(this.source)) {
        const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`;
        throw new Error(msg);
      }
      if (ctx.implicitKey) return `${src} `;
    }
    return src;
  }
}
function getAliasCount(doc, node, anchors) {
  if (isAlias(node)) {
    const source = node.resolve(doc);
    const anchor = anchors && source && anchors.get(source);
    return anchor ? anchor.count * anchor.aliasCount : 0;
  } else if (isCollection(node)) {
    let count = 0;
    for (const item of node.items) {
      const c = getAliasCount(doc, item, anchors);
      if (c > count) count = c;
    }
    return count;
  } else if (isPair(node)) {
    const kc = getAliasCount(doc, node.key, anchors);
    const vc = getAliasCount(doc, node.value, anchors);
    return Math.max(kc, vc);
  }
  return 1;
}

export { Alias };
