import { stream } from "../utils/stream.js";
import { cancellation_exports } from "../utils/cancellation.js";
import { OperationCancelled, interruptAndCheck, isOperationCancelled } from "../utils/promise-utils.js";
import { UriUtils } from "../utils/uri-utils.js";
import { DocumentState } from "./documents.js";
import { MultiMap } from "../utils/collections.js";
import { require_main } from "../../../vscode-languageserver-protocol/lib/browser/main.js";
import { Disposable } from "../utils/disposable.js";
var import_main = require_main(), DefaultDocumentBuilder = class {
	constructor(e) {
		this.updateBuildOptions = { validation: { categories: ["built-in", "fast"] } }, this.updateListeners = [], this.buildPhaseListeners = new MultiMap(), this.documentPhaseListeners = new MultiMap(), this.buildState = /* @__PURE__ */ new Map(), this.documentBuildWaiters = /* @__PURE__ */ new Map(), this.currentState = DocumentState.Changed, this.langiumDocuments = e.workspace.LangiumDocuments, this.langiumDocumentFactory = e.workspace.LangiumDocumentFactory, this.textDocuments = e.workspace.TextDocuments, this.indexManager = e.workspace.IndexManager, this.fileSystemProvider = e.workspace.FileSystemProvider, this.workspaceManager = () => e.workspace.WorkspaceManager, this.serviceRegistry = e.ServiceRegistry;
	}
	async build(e, r = {}, i = cancellation_exports.CancellationToken.None) {
		for (let n of e) {
			let e = n.uri.toString();
			if (n.state === DocumentState.Validated) {
				if (typeof r.validation == "boolean" && r.validation) this.resetToState(n, DocumentState.IndexedReferences);
				else if (typeof r.validation == "object") {
					let i = this.findMissingValidationCategories(n, r);
					i.length > 0 && (this.buildState.set(e, {
						completed: !1,
						options: { validation: { categories: i } },
						result: this.buildState.get(e)?.result
					}), n.state = DocumentState.IndexedReferences);
				}
			} else this.buildState.delete(e);
		}
		this.currentState = DocumentState.Changed, await this.emitUpdate(e.map((e) => e.uri), []), await this.buildDocuments(e, r, i);
	}
	async update(r, a, o = cancellation_exports.CancellationToken.None) {
		this.currentState = DocumentState.Changed;
		let c = [];
		for (let e of a) {
			let n = this.langiumDocuments.deleteDocuments(e);
			for (let e of n) c.push(e.uri), this.cleanUpDeleted(e);
		}
		let l = (await Promise.all(r.map((e) => this.findChangedUris(e)))).flat();
		for (let e of l) {
			let n = this.langiumDocuments.getDocument(e);
			n === void 0 && (n = this.langiumDocumentFactory.fromModel({ $type: "INVALID" }, e), n.state = DocumentState.Changed, this.langiumDocuments.addDocument(n)), this.resetToState(n, DocumentState.Changed);
		}
		let u = stream(l).concat(c).map((e) => e.toString()).toSet();
		this.langiumDocuments.all.filter((e) => !u.has(e.uri.toString()) && this.shouldRelink(e, u)).forEach((e) => this.resetToState(e, DocumentState.ComputedScopes)), await this.emitUpdate(l, c), await interruptAndCheck(o);
		let d = this.sortDocuments(this.langiumDocuments.all.filter((e) => e.state < DocumentState.Validated || !this.buildState.get(e.uri.toString())?.completed || this.resultsAreIncomplete(e, this.updateBuildOptions)).toArray());
		await this.buildDocuments(d, this.updateBuildOptions, o);
	}
	resultsAreIncomplete(e, n) {
		return this.findMissingValidationCategories(e, n).length >= 1;
	}
	findMissingValidationCategories(n, r) {
		let i = this.buildState.get(n.uri.toString()), a = this.serviceRegistry.getServices(n.uri).validation.ValidationRegistry.getAllValidationCategories(n), o = i?.result?.validationChecks ? new Set(i?.result?.validationChecks) : i?.completed ? a : /* @__PURE__ */ new Set();
		return stream(r === void 0 || r.validation === !0 ? a : typeof r.validation == "object" ? r.validation.categories ?? a : []).filter((e) => !o.has(e)).toArray();
	}
	async findChangedUris(e) {
		if (this.langiumDocuments.getDocument(e) ?? this.textDocuments?.get(e)) return [e];
		try {
			let n = await this.fileSystemProvider.stat(e);
			if (n.isDirectory) return await this.workspaceManager().searchFolder(e);
			if (this.workspaceManager().shouldIncludeEntry(n)) return [e];
		} catch {}
		return [];
	}
	async emitUpdate(e, n) {
		await Promise.all(this.updateListeners.map((r) => r(e, n)));
	}
	sortDocuments(e) {
		let n = 0, r = e.length - 1;
		for (; n < r;) {
			for (; n < e.length && this.hasTextDocument(e[n]);) n++;
			for (; r >= 0 && !this.hasTextDocument(e[r]);) r--;
			n < r && ([e[n], e[r]] = [e[r], e[n]]);
		}
		return e;
	}
	hasTextDocument(e) {
		return !!this.textDocuments?.get(e.uri);
	}
	shouldRelink(e, n) {
		return e.references.some((e) => e.error !== void 0) ? !0 : this.indexManager.isAffected(e, n);
	}
	onUpdate(e) {
		return this.updateListeners.push(e), Disposable.create(() => {
			let n = this.updateListeners.indexOf(e);
			n >= 0 && this.updateListeners.splice(n, 1);
		});
	}
	resetToState(e, n) {
		switch (n) {
			case DocumentState.Changed:
			case DocumentState.Parsed: this.indexManager.removeContent(e.uri);
			case DocumentState.IndexedContent: e.localSymbols = void 0;
			case DocumentState.ComputedScopes: this.serviceRegistry.getServices(e.uri).references.Linker.unlink(e);
			case DocumentState.Linked: this.indexManager.removeReferences(e.uri);
			case DocumentState.IndexedReferences: e.diagnostics = void 0, this.buildState.delete(e.uri.toString());
			case DocumentState.Validated:
		}
		e.state > n && (e.state = n);
	}
	cleanUpDeleted(e) {
		this.buildState.delete(e.uri.toString()), this.indexManager.remove(e.uri), e.state = DocumentState.Changed;
	}
	async buildDocuments(e, n, r) {
		this.prepareBuild(e, n), await this.runCancelable(e, DocumentState.Parsed, r, (e) => this.langiumDocumentFactory.update(e, r)), await this.runCancelable(e, DocumentState.IndexedContent, r, (e) => this.indexManager.updateContent(e, r)), await this.runCancelable(e, DocumentState.ComputedScopes, r, async (e) => {
			e.localSymbols = await this.serviceRegistry.getServices(e.uri).references.ScopeComputation.collectLocalSymbols(e, r);
		});
		let i = e.filter((e) => this.shouldLink(e));
		await this.runCancelable(i, DocumentState.Linked, r, (e) => this.serviceRegistry.getServices(e.uri).references.Linker.link(e, r)), await this.runCancelable(i, DocumentState.IndexedReferences, r, (e) => this.indexManager.updateReferences(e, r));
		let a = e.filter((e) => this.shouldValidate(e) ? !0 : (this.markAsCompleted(e), !1));
		await this.runCancelable(a, DocumentState.Validated, r, async (e) => {
			await this.validate(e, r), this.markAsCompleted(e);
		});
	}
	markAsCompleted(e) {
		let n = this.buildState.get(e.uri.toString());
		n && (n.completed = !0);
	}
	prepareBuild(e, n) {
		for (let r of e) {
			let e = r.uri.toString(), i = this.buildState.get(e);
			(!i || i.completed) && this.buildState.set(e, {
				completed: !1,
				options: n,
				result: i?.result
			});
		}
	}
	async runCancelable(e, n, r, a) {
		for (let o of e) o.state < n && (await interruptAndCheck(r), await a(o), o.state = n, await this.notifyDocumentPhase(o, n, r));
		let o = e.filter((e) => e.state === n);
		await this.notifyBuildPhase(o, n, r), this.currentState = n;
	}
	onBuildPhase(e, n) {
		return this.buildPhaseListeners.add(e, n), Disposable.create(() => {
			this.buildPhaseListeners.delete(e, n);
		});
	}
	onDocumentPhase(e, n) {
		return this.documentPhaseListeners.add(e, n), Disposable.create(() => {
			this.documentPhaseListeners.delete(e, n);
		});
	}
	waitUntil(e, r, i) {
		let a;
		return r && "path" in r ? a = r : i = r, i ??= cancellation_exports.CancellationToken.None, a ? this.awaitDocumentState(e, a, i) : this.awaitBuilderState(e, i);
	}
	awaitDocumentState(e, n, i) {
		let a = this.langiumDocuments.getDocument(n);
		if (a) {
			if (a.state >= e) return Promise.resolve(n);
			if (i.isCancellationRequested) return Promise.reject(OperationCancelled);
			if (this.currentState >= e && e > a.state) return Promise.reject(new import_main.ResponseError(import_main.LSPErrorCodes.RequestFailed, `Document state of ${n.toString()} is ${DocumentState[a.state]}, requiring ${DocumentState[e]}, but workspace state is already ${DocumentState[this.currentState]}. Returning undefined.`));
		} else return Promise.reject(new import_main.ResponseError(import_main.LSPErrorCodes.ServerCancelled, `No document found for URI: ${n.toString()}`));
		return new Promise((a, s) => {
			let c = this.onDocumentPhase(e, (e) => {
				UriUtils.equals(e.uri, n) && (c.dispose(), l.dispose(), a(e.uri));
			}), l = i.onCancellationRequested(() => {
				c.dispose(), l.dispose(), s(OperationCancelled);
			});
		});
	}
	awaitBuilderState(e, n) {
		return this.currentState >= e ? Promise.resolve() : n.isCancellationRequested ? Promise.reject(OperationCancelled) : new Promise((i, a) => {
			let o = this.onBuildPhase(e, () => {
				o.dispose(), s.dispose(), i();
			}), s = n.onCancellationRequested(() => {
				o.dispose(), s.dispose(), a(OperationCancelled);
			});
		});
	}
	async notifyDocumentPhase(e, n, r) {
		let o = this.documentPhaseListeners.get(n).slice();
		for (let n of o) try {
			await interruptAndCheck(r), await n(e, r);
		} catch (e) {
			if (!isOperationCancelled(e)) throw e;
		}
	}
	async notifyBuildPhase(e, n, r) {
		if (e.length === 0) return;
		let a = this.buildPhaseListeners.get(n).slice();
		for (let n of a) await interruptAndCheck(r), await n(e, r);
	}
	shouldLink(e) {
		return this.getBuildOptions(e).eagerLinking ?? !0;
	}
	shouldValidate(e) {
		return !!this.getBuildOptions(e).validation;
	}
	async validate(n, r) {
		let i = this.serviceRegistry.getServices(n.uri).validation.DocumentValidator, a = this.getBuildOptions(n), o = typeof a.validation == "object" ? { ...a.validation } : {};
		o.categories = this.findMissingValidationCategories(n, a);
		let s = await i.validateDocument(n, o, r);
		n.diagnostics ? n.diagnostics.push(...s) : n.diagnostics = s;
		let c = this.buildState.get(n.uri.toString());
		c && (c.result ??= {}, c.result.validationChecks ? c.result.validationChecks = stream(c.result.validationChecks).concat(o.categories).distinct().toArray() : c.result.validationChecks = [...o.categories]);
	}
	getBuildOptions(e) {
		return this.buildState.get(e.uri.toString())?.options ?? {};
	}
};
export { DefaultDocumentBuilder };
