import { MultiMap as l } from "../utils/collections.js";
import { isOperationCancelled as f } from "../utils/promise-utils.js";
import { stream as d } from "../utils/stream.js";
import { assertUnreachable as p } from "../utils/errors.js";
function A(a) {
  return { code: a };
}
var c;
(function(a) {
  a.all = ["fast", "slow", "built-in"];
})(c || (c = {}));
class E {
  constructor(r) {
    this.entries = new l(), this.entriesBefore = [], this.entriesAfter = [], this.reflection = r.shared.AstReflection;
  }
  /**
   * Register a set of validation checks. Each value in the record can be either a single validation check (i.e. a function)
   * or an array of validation checks.
   *
   * @param checksRecord Set of validation checks to register.
   * @param category Optional category for the validation checks (defaults to `'fast'`).
   * @param thisObj Optional object to be used as `this` when calling the validation check functions.
   */
  register(r, e = this, t = "fast") {
    if (t === "built-in")
      throw new Error("The 'built-in' category is reserved for lexer, parser, and linker errors.");
    for (const [n, i] of Object.entries(r)) {
      const s = i;
      if (Array.isArray(s))
        for (const o of s) {
          const h = {
            check: this.wrapValidationException(o, e),
            category: t
          };
          this.addEntry(n, h);
        }
      else if (typeof s == "function") {
        const o = {
          check: this.wrapValidationException(s, e),
          category: t
        };
        this.addEntry(n, o);
      } else
        p();
    }
  }
  wrapValidationException(r, e) {
    return async (t, n, i) => {
      await this.handleException(() => r.call(e, t, n, i), "An error occurred during validation", n, t);
    };
  }
  async handleException(r, e, t, n) {
    try {
      await r();
    } catch (i) {
      if (f(i))
        throw i;
      console.error(`${e}:`, i), i instanceof Error && i.stack && console.error(i.stack);
      const s = i instanceof Error ? i.message : String(i);
      t("error", `${e}: ${s}`, { node: n });
    }
  }
  addEntry(r, e) {
    if (r === "AstNode") {
      this.entries.add("AstNode", e);
      return;
    }
    for (const t of this.reflection.getAllSubTypes(r))
      this.entries.add(t, e);
  }
  getChecks(r, e) {
    let t = d(this.entries.get(r)).concat(this.entries.get("AstNode"));
    return e && (t = t.filter((n) => e.includes(n.category))), t.map((n) => n.check);
  }
  /**
   * Register logic which will be executed once before validating all the nodes of an AST/Langium document.
   * This helps to prepare or initialize some information which are required or reusable for the following checks on the AstNodes.
   *
   * As an example, for validating unique fully-qualified names of nodes in the AST,
   * here the map for mapping names to nodes could be established.
   * During the usual checks on the nodes, they are put into this map with their name.
   *
   * Note that this approach makes validations stateful, which is relevant e.g. when cancelling the validation.
   * Therefore it is recommended to clear stored information
   * _before_ validating an AST to validate each AST unaffected from other ASTs
   * AND _after_ validating the AST to free memory by information which are no longer used.
   *
   * @param checkBefore a set-up function which will be called once before actually validating an AST
   * @param thisObj Optional object to be used as `this` when calling the validation check functions.
   */
  registerBeforeDocument(r, e = this) {
    this.entriesBefore.push(this.wrapPreparationException(r, "An error occurred during set-up of the validation", e));
  }
  /**
   * Register logic which will be executed once after validating all the nodes of an AST/Langium document.
   * This helps to finally evaluate information which are collected during the checks on the AstNodes.
   *
   * As an example, for validating unique fully-qualified names of nodes in the AST,
   * here the map with all the collected nodes and their names is checked
   * and validation hints are created for all nodes with the same name.
   *
   * Note that this approach makes validations stateful, which is relevant e.g. when cancelling the validation.
   * Therefore it is recommended to clear stored information
   * _before_ validating an AST to validate each AST unaffected from other ASTs
   * AND _after_ validating the AST to free memory by information which are no longer used.
   *
   * @param checkBefore a set-up function which will be called once before actually validating an AST
   * @param thisObj Optional object to be used as `this` when calling the validation check functions.
   */
  registerAfterDocument(r, e = this) {
    this.entriesAfter.push(this.wrapPreparationException(r, "An error occurred during tear-down of the validation", e));
  }
  wrapPreparationException(r, e, t) {
    return async (n, i, s, o) => {
      await this.handleException(() => r.call(t, n, i, s, o), e, i, n);
    };
  }
  get checksBefore() {
    return this.entriesBefore;
  }
  get checksAfter() {
    return this.entriesAfter;
  }
}
export {
  c as ValidationCategory,
  E as ValidationRegistry,
  A as diagnosticData
};
