import { streamAst } from "../utils/ast-utils.js";
import { tokenToRange } from "../utils/cst-utils.js";
import { findNodeForKeyword, findNodeForProperty } from "../utils/grammar-utils.js";
import { cancellation_exports } from "../utils/cancellation.js";
import { interruptAndCheck, isOperationCancelled } from "../utils/promise-utils.js";
import { diagnosticData } from "./validation-registry.js";
const VALIDATE_EACH_NODE = Object.freeze({
	validateNode: !0,
	validateChildren: !0
});
var DefaultDocumentValidator = class {
	constructor(e) {
		this.validationRegistry = e.validation.ValidationRegistry, this.metadata = e.LanguageMetaData, this.profiler = e.shared.profilers.LangiumProfiler, this.languageId = e.LanguageMetaData.languageId;
	}
	async validateDocument(e, s = {}, c = cancellation_exports.CancellationToken.None) {
		let l = e.parseResult, p = [];
		if (await interruptAndCheck(c), (!s.categories || s.categories.includes("built-in")) && (this.processLexingErrors(l, p, s), s.stopAfterLexingErrors && p.some((e) => e.data?.code === DocumentValidator.LexingError) || (this.processParsingErrors(l, p, s), s.stopAfterParsingErrors && p.some((e) => e.data?.code === DocumentValidator.ParsingError)) || (this.processLinkingErrors(e, p, s), s.stopAfterLinkingErrors && p.some((e) => e.data?.code === DocumentValidator.LinkingError)))) return p;
		try {
			p.push(...await this.validateAst(l.value, s, c));
		} catch (e) {
			if (isOperationCancelled(e)) throw e;
			console.error("An error occurred during validation:", e);
		}
		return await interruptAndCheck(c), p;
	}
	processLexingErrors(e, s, c) {
		let l = [...e.lexerErrors, ...e.lexerReport?.diagnostics ?? []];
		for (let e of l) {
			let c = e.severity ?? "error", l = {
				severity: toDiagnosticSeverity(c),
				range: {
					start: {
						line: e.line - 1,
						character: e.column - 1
					},
					end: {
						line: e.line - 1,
						character: e.column + e.length - 1
					}
				},
				message: e.message,
				data: toDiagnosticData(c),
				source: this.getSource()
			};
			s.push(l);
		}
	}
	processParsingErrors(e, c, l) {
		for (let l of e.parserErrors) {
			let e;
			if (isNaN(l.token.startOffset)) {
				if ("previousToken" in l) {
					let s = l.previousToken;
					if (isNaN(s.startOffset)) {
						let s = {
							line: 0,
							character: 0
						};
						e = {
							start: s,
							end: s
						};
					} else {
						let c = {
							line: s.endLine - 1,
							character: s.endColumn
						};
						e = {
							start: c,
							end: c
						};
					}
				}
			} else e = tokenToRange(l.token);
			if (e) {
				let s = {
					severity: toDiagnosticSeverity("error"),
					range: e,
					message: l.message,
					data: diagnosticData(DocumentValidator.ParsingError),
					source: this.getSource()
				};
				c.push(s);
			}
		}
	}
	processLinkingErrors(e, s, c) {
		for (let c of e.references) {
			let e = c.error;
			if (e) {
				let l = {
					node: e.info.container,
					range: c.$refNode?.range,
					property: e.info.property,
					index: e.info.index,
					data: {
						code: DocumentValidator.LinkingError,
						containerType: e.info.container.$type,
						property: e.info.property,
						refText: e.info.reference.$refText
					}
				};
				s.push(this.toDiagnostic("error", e.message, l));
			}
		}
	}
	async validateAst(e, s, c = cancellation_exports.CancellationToken.None) {
		let l = [], d = (e, s, c) => {
			l.push(this.toDiagnostic(e, s, c));
		};
		return await this.validateAstBefore(e, s, d, c), await this.validateAstNodes(e, s, d, c), await this.validateAstAfter(e, s, d, c), l;
	}
	async validateAstBefore(e, s, c, l = cancellation_exports.CancellationToken.None) {
		let f = this.validationRegistry.checksBefore;
		for (let u of f) await interruptAndCheck(l), await u(e, c, s.categories ?? [], l);
	}
	async validateAstNodes(s, c, l, f = cancellation_exports.CancellationToken.None) {
		if (this.profiler?.isActive("validating")) {
			let u = this.profiler.createTask("validating", this.languageId);
			u.start();
			try {
				let d = streamAst(s).iterator();
				for (let e of d) {
					u.startSubTask(e.$type);
					let s = this.validateSingleNodeOptions(e, c);
					if (s.validateNode) try {
						let s = this.validationRegistry.getChecks(e.$type, c.categories);
						for (let c of s) await c(e, l, f);
					} finally {
						u.stopSubTask(e.$type);
					}
					s.validateChildren || d.prune();
				}
			} finally {
				u.stop();
			}
		} else {
			let u = streamAst(s).iterator();
			for (let e of u) {
				await interruptAndCheck(f);
				let s = this.validateSingleNodeOptions(e, c);
				if (s.validateNode) {
					let s = this.validationRegistry.getChecks(e.$type, c.categories);
					for (let c of s) await c(e, l, f);
				}
				s.validateChildren || u.prune();
			}
		}
	}
	validateSingleNodeOptions(e, s) {
		return VALIDATE_EACH_NODE;
	}
	async validateAstAfter(e, s, c, l = cancellation_exports.CancellationToken.None) {
		let f = this.validationRegistry.checksAfter;
		for (let u of f) await interruptAndCheck(l), await u(e, c, s.categories ?? [], l);
	}
	toDiagnostic(e, s, c) {
		return {
			message: s,
			range: getDiagnosticRange(c),
			severity: toDiagnosticSeverity(e),
			code: c.code,
			codeDescription: c.codeDescription,
			tags: c.tags,
			relatedInformation: c.relatedInformation,
			data: c.data,
			source: this.getSource()
		};
	}
	getSource() {
		return this.metadata.languageId;
	}
};
function getDiagnosticRange(e) {
	if (e.range) return e.range;
	let s;
	return typeof e.property == "string" ? s = findNodeForProperty(e.node.$cstNode, e.property, e.index) : typeof e.keyword == "string" && (s = findNodeForKeyword(e.node.$cstNode, e.keyword, e.index)), s ??= e.node.$cstNode, s ? s.range : {
		start: {
			line: 0,
			character: 0
		},
		end: {
			line: 0,
			character: 0
		}
	};
}
function toDiagnosticSeverity(e) {
	switch (e) {
		case "error": return 1;
		case "warning": return 2;
		case "info": return 3;
		case "hint": return 4;
		default: throw Error("Invalid diagnostic severity: " + e);
	}
}
function toDiagnosticData(e) {
	switch (e) {
		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 Error("Invalid diagnostic severity: " + e);
	}
}
var DocumentValidator;
(function(e) {
	e.LexingError = "lexing-error", e.LexingWarning = "lexing-warning", e.LexingInfo = "lexing-info", e.LexingHint = "lexing-hint", e.ParsingError = "parsing-error", e.LinkingError = "linking-error";
})(DocumentValidator ||= {});
export { DefaultDocumentValidator, DocumentValidator, VALIDATE_EACH_NODE, getDiagnosticRange, toDiagnosticData, toDiagnosticSeverity };
