import { findNodeForProperty, findNodeForKeyword } from "./lib-utils-grammar-utils.js";
import { streamAst } from "./lib-utils-ast-utils.js";
import { tokenToRange } from "./lib-utils-cst-utils.js";
import { interruptAndCheck, isOperationCancelled } from "./lib-utils-promise-utils.js";
import { diagnosticData } from "./lib-validation-validation-registry.js";
import { c as cancellationExports } from "../../_virtual/cancellation.js";
class DefaultDocumentValidator {
  constructor(services) {
    this.validationRegistry = services.validation.ValidationRegistry;
    this.metadata = services.LanguageMetaData;
  }
  async validateDocument(document, options = {}, cancelToken = cancellationExports.CancellationToken.None) {
    const parseResult = document.parseResult;
    const diagnostics = [];
    await interruptAndCheck(cancelToken);
    if (!options.categories || options.categories.includes("built-in")) {
      this.processLexingErrors(parseResult, diagnostics, options);
      if (options.stopAfterLexingErrors && diagnostics.some((d) => {
        var _a;
        return ((_a = d.data) === null || _a === void 0 ? void 0 : _a.code) === DocumentValidator.LexingError;
      })) {
        return diagnostics;
      }
      this.processParsingErrors(parseResult, diagnostics, options);
      if (options.stopAfterParsingErrors && diagnostics.some((d) => {
        var _a;
        return ((_a = d.data) === null || _a === void 0 ? void 0 : _a.code) === DocumentValidator.ParsingError;
      })) {
        return diagnostics;
      }
      this.processLinkingErrors(document, diagnostics, options);
      if (options.stopAfterLinkingErrors && diagnostics.some((d) => {
        var _a;
        return ((_a = d.data) === null || _a === void 0 ? void 0 : _a.code) === DocumentValidator.LinkingError;
      })) {
        return diagnostics;
      }
    }
    try {
      diagnostics.push(...await this.validateAst(parseResult.value, options, cancelToken));
    } catch (err) {
      if (isOperationCancelled(err)) {
        throw err;
      }
      console.error("An error occurred during validation:", err);
    }
    await interruptAndCheck(cancelToken);
    return diagnostics;
  }
  processLexingErrors(parseResult, diagnostics, _options) {
    var _a, _b, _c;
    const lexerDiagnostics = [...parseResult.lexerErrors, ...(_b = (_a = parseResult.lexerReport) === null || _a === void 0 ? void 0 : _a.diagnostics) !== null && _b !== void 0 ? _b : []];
    for (const lexerDiagnostic of lexerDiagnostics) {
      const severity = (_c = lexerDiagnostic.severity) !== null && _c !== void 0 ? _c : "error";
      const diagnostic = {
        severity: toDiagnosticSeverity(severity),
        range: {
          start: {
            line: lexerDiagnostic.line - 1,
            character: lexerDiagnostic.column - 1
          },
          end: {
            line: lexerDiagnostic.line - 1,
            character: lexerDiagnostic.column + lexerDiagnostic.length - 1
          }
        },
        message: lexerDiagnostic.message,
        data: toDiagnosticData(severity),
        source: this.getSource()
      };
      diagnostics.push(diagnostic);
    }
  }
  processParsingErrors(parseResult, diagnostics, _options) {
    for (const parserError of parseResult.parserErrors) {
      let range = void 0;
      if (isNaN(parserError.token.startOffset)) {
        if ("previousToken" in parserError) {
          const token = parserError.previousToken;
          if (!isNaN(token.startOffset)) {
            const position = { line: token.endLine - 1, character: token.endColumn };
            range = { start: position, end: position };
          } else {
            const position = { line: 0, character: 0 };
            range = { start: position, end: position };
          }
        }
      } else {
        range = tokenToRange(parserError.token);
      }
      if (range) {
        const diagnostic = {
          severity: toDiagnosticSeverity("error"),
          range,
          message: parserError.message,
          data: diagnosticData(DocumentValidator.ParsingError),
          source: this.getSource()
        };
        diagnostics.push(diagnostic);
      }
    }
  }
  processLinkingErrors(document, diagnostics, _options) {
    for (const reference of document.references) {
      const linkingError = reference.error;
      if (linkingError) {
        const info = {
          node: linkingError.container,
          property: linkingError.property,
          index: linkingError.index,
          data: {
            code: DocumentValidator.LinkingError,
            containerType: linkingError.container.$type,
            property: linkingError.property,
            refText: linkingError.reference.$refText
          }
        };
        diagnostics.push(this.toDiagnostic("error", linkingError.message, info));
      }
    }
  }
  async validateAst(rootNode, options, cancelToken = cancellationExports.CancellationToken.None) {
    const validationItems = [];
    const acceptor = (severity, message, info) => {
      validationItems.push(this.toDiagnostic(severity, message, info));
    };
    await this.validateAstBefore(rootNode, options, acceptor, cancelToken);
    await this.validateAstNodes(rootNode, options, acceptor, cancelToken);
    await this.validateAstAfter(rootNode, options, acceptor, cancelToken);
    return validationItems;
  }
  async validateAstBefore(rootNode, options, acceptor, cancelToken = cancellationExports.CancellationToken.None) {
    var _a;
    const checksBefore = this.validationRegistry.checksBefore;
    for (const checkBefore of checksBefore) {
      await interruptAndCheck(cancelToken);
      await checkBefore(rootNode, acceptor, (_a = options.categories) !== null && _a !== void 0 ? _a : [], cancelToken);
    }
  }
  async validateAstNodes(rootNode, options, acceptor, cancelToken = cancellationExports.CancellationToken.None) {
    await Promise.all(streamAst(rootNode).map(async (node) => {
      await interruptAndCheck(cancelToken);
      const checks = this.validationRegistry.getChecks(node.$type, options.categories);
      for (const check of checks) {
        await check(node, acceptor, cancelToken);
      }
    }));
  }
  async validateAstAfter(rootNode, options, acceptor, cancelToken = cancellationExports.CancellationToken.None) {
    var _a;
    const checksAfter = this.validationRegistry.checksAfter;
    for (const checkAfter of checksAfter) {
      await interruptAndCheck(cancelToken);
      await checkAfter(rootNode, acceptor, (_a = options.categories) !== null && _a !== void 0 ? _a : [], cancelToken);
    }
  }
  toDiagnostic(severity, message, info) {
    return {
      message,
      range: getDiagnosticRange(info),
      severity: toDiagnosticSeverity(severity),
      code: info.code,
      codeDescription: info.codeDescription,
      tags: info.tags,
      relatedInformation: info.relatedInformation,
      data: info.data,
      source: this.getSource()
    };
  }
  getSource() {
    return this.metadata.languageId;
  }
}
function getDiagnosticRange(info) {
  if (info.range) {
    return info.range;
  }
  let cstNode;
  if (typeof info.property === "string") {
    cstNode = findNodeForProperty(info.node.$cstNode, info.property, info.index);
  } else if (typeof info.keyword === "string") {
    cstNode = findNodeForKeyword(info.node.$cstNode, info.keyword, info.index);
  }
  cstNode !== null && cstNode !== void 0 ? cstNode : cstNode = info.node.$cstNode;
  if (!cstNode) {
    return {
      start: { line: 0, character: 0 },
      end: { line: 0, character: 0 }
    };
  }
  return cstNode.range;
}
function toDiagnosticSeverity(severity) {
  switch (severity) {
    case "error":
      return 1;
    case "warning":
      return 2;
    case "info":
      return 3;
    case "hint":
      return 4;
    default:
      throw new Error("Invalid diagnostic severity: " + severity);
  }
}
function toDiagnosticData(severity) {
  switch (severity) {
    case "error":
      return diagnosticData(DocumentValidator.LexingError);
    case "warning":
      return diagnosticData(DocumentValidator.LexingWarning);
    case "info":
      return diagnosticData(DocumentValidator.LexingInfo);
    case "hint":
      return diagnosticData(DocumentValidator.LexingHint);
    default:
      throw new Error("Invalid diagnostic severity: " + severity);
  }
}
var DocumentValidator;
(function(DocumentValidator2) {
  DocumentValidator2.LexingError = "lexing-error";
  DocumentValidator2.LexingWarning = "lexing-warning";
  DocumentValidator2.LexingInfo = "lexing-info";
  DocumentValidator2.LexingHint = "lexing-hint";
  DocumentValidator2.ParsingError = "parsing-error";
  DocumentValidator2.LinkingError = "linking-error";
})(DocumentValidator || (DocumentValidator = {}));
export {
  DefaultDocumentValidator,
  DocumentValidator,
  getDiagnosticRange,
  toDiagnosticData,
  toDiagnosticSeverity
};
//# sourceMappingURL=lib-validation-document-validator.js.map
