import { DEFAULT_THEMES } from "../constants.js";
import { getResolvedLanguages, hasResolvedLanguages, resolveLanguages } from "../highlighter/languages.js";
import { getThemes } from "../utils/getThemes.js";
import { attachResolvedThemes, getResolvedThemes, hasResolvedThemes, resolveThemes } from "../highlighter/themes.js";
import { getSharedHighlighter } from "../highlighter/shared_highlighter.js";
import { areThemesEqual } from "../utils/areThemesEqual.js";
import { getFiletypeFromFileName } from "../utils/getFiletypeFromFileName.js";
import { renderDiffWithHighlighter } from "../utils/renderDiffWithHighlighter.js";
import { renderFileWithHighlighter } from "../utils/renderFileWithHighlighter.js";
import { LRUMap } from "lru_map";

//#region src/worker/WorkerPoolManager.ts
const IGNORE_RESPONSE = Symbol("IGNORE_RESPONSE");
var WorkerPoolManager = class {
	highlighter;
	renderOptions;
	initialized = false;
	workers = [];
	taskQueue = [];
	pendingTasks = /* @__PURE__ */ new Map();
	nextRequestId = 0;
	themeSubscribers = /* @__PURE__ */ new Set();
	workersFailed = false;
	instanceRequestMap = /* @__PURE__ */ new Map();
	fileCache;
	diffCache;
	constructor(options, { langs, theme = DEFAULT_THEMES, lineDiffType = "word-alt", tokenizeMaxLineLength = 1e3 }) {
		this.options = options;
		this.renderOptions = {
			theme,
			lineDiffType,
			tokenizeMaxLineLength
		};
		this.fileCache = new LRUMap(options.totalASTLRUCacheSize ?? 100);
		this.diffCache = new LRUMap(options.totalASTLRUCacheSize ?? 100);
		this.initialize(langs);
	}
	isWorkingPool() {
		return !this.workersFailed;
	}
	getFileResultCache(file) {
		return file.cacheKey != null ? this.fileCache.get(file.cacheKey) : void 0;
	}
	getDiffResultCache(diff) {
		return diff.cacheKey != null ? this.diffCache.get(diff.cacheKey) : void 0;
	}
	inspectCaches() {
		const { fileCache, diffCache } = this;
		return {
			fileCache,
			diffCache
		};
	}
	evictFileFromCache(cacheKey) {
		return this.fileCache.delete(cacheKey) !== void 0;
	}
	evictDiffFromCache(cacheKey) {
		return this.diffCache.delete(cacheKey) !== void 0;
	}
	async setTheme(theme) {
		if (!this.isInitialized()) await this.initialize();
		if (areThemesEqual(theme, this.renderOptions.theme)) return;
		const themeNames = getThemes(theme);
		let resolvedThemes = [];
		if (hasResolvedThemes(themeNames)) resolvedThemes = getResolvedThemes(themeNames);
		else resolvedThemes = await resolveThemes(themeNames);
		if (this.highlighter != null) {
			attachResolvedThemes(resolvedThemes, this.highlighter);
			await this.registerThemesOnWorkers(theme, resolvedThemes);
			this.renderOptions.theme = theme;
		} else {
			const [highlighter] = await Promise.all([getSharedHighlighter({
				themes: themeNames,
				langs: ["text"]
			}), this.registerThemesOnWorkers(theme, resolvedThemes)]);
			this.highlighter = highlighter;
			this.renderOptions.theme = theme;
		}
		this.diffCache.clear();
		this.fileCache.clear();
		for (const instance of this.themeSubscribers) instance.rerender();
	}
	getFileRenderOptions() {
		const { tokenizeMaxLineLength, theme } = this.renderOptions;
		return {
			theme,
			tokenizeMaxLineLength
		};
	}
	getDiffRenderOptions() {
		return { ...this.renderOptions };
	}
	async registerThemesOnWorkers(theme, resolvedThemes) {
		if (resolvedThemes.length === 0 || this.workersFailed) return;
		if (!this.isInitialized()) await this.initialize();
		const registerPromises = [];
		for (const managedWorker of this.workers) {
			if (!managedWorker.initialized) {
				console.log({ managedWorker });
				throw new Error("registerThemesOnWorkers: Somehow we have an uninitialized worker");
			}
			registerPromises.push(new Promise((resolve, reject) => {
				const id = this.generateRequestId();
				const task = {
					type: "register-theme",
					id,
					request: {
						type: "register-theme",
						id,
						theme,
						resolvedThemes
					},
					resolve,
					reject,
					requestStart: Date.now()
				};
				this.pendingTasks.set(id, task);
				managedWorker.worker.postMessage(task.request);
			}));
		}
		await Promise.all(registerPromises);
	}
	subscribeToThemeChanges(instance) {
		this.themeSubscribers.add(instance);
		return () => {
			this.unsubscribeToThemeChanges(instance);
		};
	}
	unsubscribeToThemeChanges(instance) {
		this.themeSubscribers.delete(instance);
	}
	isInitialized() {
		return this.initialized === true;
	}
	async initialize(languages = []) {
		if (this.initialized === true) return;
		else if (this.initialized === false) this.initialized = new Promise((resolve, reject) => {
			(async () => {
				try {
					const themes = getThemes(this.renderOptions.theme);
					let resolvedThemes = [];
					if (hasResolvedThemes(themes)) resolvedThemes = getResolvedThemes(themes);
					else resolvedThemes = await resolveThemes(themes);
					let resolvedLanguages = [];
					if (hasResolvedLanguages(languages)) resolvedLanguages = getResolvedLanguages(languages);
					else resolvedLanguages = await resolveLanguages(languages);
					const [highlighter] = await Promise.all([getSharedHighlighter({
						themes,
						langs: ["text", ...languages]
					}), this.initializeWorkers(resolvedThemes, resolvedLanguages)]);
					if (this.initialized === false) {
						this.terminateWorkers();
						reject();
						return;
					}
					this.highlighter = highlighter;
					this.initialized = true;
					this.diffCache.clear();
					this.fileCache.clear();
					this.drainQueue();
					resolve();
				} catch (e) {
					this.initialized = false;
					this.workersFailed = true;
					reject(e);
				}
			})();
		});
		else return this.initialized;
	}
	async initializeWorkers(resolvedThemes, resolvedLanguages) {
		this.workersFailed = false;
		const initPromises = [];
		if (this.workers.length > 0) this.terminateWorkers();
		for (let i = 0; i < (this.options.poolSize ?? 8); i++) {
			const worker = this.options.workerFactory();
			const managedWorker = {
				worker,
				busy: false,
				initialized: false,
				langs: new Set(["text", ...resolvedLanguages.map(({ name }) => name)])
			};
			worker.addEventListener("message", (event) => {
				this.handleWorkerMessage(managedWorker, event.data);
			});
			worker.addEventListener("error", (error) => console.error("Worker error:", error, managedWorker));
			this.workers.push(managedWorker);
			initPromises.push(new Promise((resolve, reject) => {
				const id = this.generateRequestId();
				const task = {
					type: "initialize",
					id,
					request: {
						type: "initialize",
						id,
						renderOptions: this.renderOptions,
						resolvedThemes,
						resolvedLanguages
					},
					resolve() {
						managedWorker.initialized = true;
						resolve();
					},
					reject,
					requestStart: Date.now()
				};
				this.pendingTasks.set(id, task);
				this.executeTask(managedWorker, task);
			}));
		}
		await Promise.all(initPromises);
	}
	drainQueue = () => {
		this._queuedDrain = void 0;
		if (this.initialized !== true || this.taskQueue.length === 0) return;
		while (this.taskQueue.length > 0) {
			const task = this.taskQueue[0];
			const langs = getLangsFromTask(task);
			const availableWorker = this.getAvailableWorker(langs);
			if (availableWorker == null) break;
			this.taskQueue.shift();
			this.resolveLanguagesAndExecuteTask(availableWorker, task, langs);
		}
	};
	highlightFileAST(instance, file) {
		this.submitTask(instance, {
			type: "file",
			file
		});
	}
	getPlainFileAST(file) {
		if (this.highlighter == null) {
			this.initialize();
			return;
		}
		return renderFileWithHighlighter(file, this.highlighter, this.renderOptions, true);
	}
	highlightDiffAST(instance, diff) {
		this.submitTask(instance, {
			type: "diff",
			diff
		});
	}
	getPlainDiffAST(diff) {
		return this.highlighter != null ? renderDiffWithHighlighter(diff, this.highlighter, this.renderOptions, true) : void 0;
	}
	terminate() {
		this.terminateWorkers();
		this.fileCache.clear();
		this.diffCache.clear();
		this.instanceRequestMap.clear();
		this.taskQueue.length = 0;
		this.pendingTasks.clear();
		this.highlighter = void 0;
		this.initialized = false;
		this.workersFailed = false;
	}
	terminateWorkers() {
		for (const managedWorker of this.workers) managedWorker.worker.terminate();
		this.workers.length = 0;
	}
	getStats() {
		return {
			totalWorkers: this.workers.length,
			busyWorkers: this.workers.filter((w) => w.busy).length,
			queuedTasks: this.taskQueue.length,
			pendingTasks: this.pendingTasks.size
		};
	}
	submitTask(instance, request) {
		if (this.initialized === false) this.initialize();
		const id = this.generateRequestId();
		const requestStart = Date.now();
		const task = (() => {
			switch (request.type) {
				case "file": return {
					type: "file",
					id,
					request: {
						...request,
						id
					},
					instance,
					requestStart
				};
				case "diff": return {
					type: "diff",
					id,
					request: {
						...request,
						id
					},
					instance,
					requestStart
				};
			}
		})();
		this.instanceRequestMap.set(instance, id);
		this.taskQueue.push(task);
		this.queueDrain();
	}
	async resolveLanguagesAndExecuteTask(availableWorker, task, langs) {
		if (task.type === "file" || task.type === "diff") {
			const workerMissingLangs = langs.filter((lang) => !availableWorker.langs.has(lang));
			if (workerMissingLangs.length > 0) if (hasResolvedLanguages(workerMissingLangs)) task.request.resolvedLanguages = getResolvedLanguages(workerMissingLangs);
			else task.request.resolvedLanguages = await resolveLanguages(workerMissingLangs);
		}
		this.executeTask(availableWorker, task);
	}
	handleWorkerMessage(managedWorker, response) {
		const task = this.pendingTasks.get(response.id);
		try {
			if (task == null) throw new Error("handleWorkerMessage: Received response for unknown task");
			else if (response.type === "error") {
				const error = new Error(response.error);
				if (response.stack) error.stack = response.stack;
				if ("reject" in task) task.reject(error);
				else task.instance.onHighlightError(error);
				throw error;
			} else {
				if ("instance" in task && this.instanceRequestMap.get(task.instance) !== response.id) throw IGNORE_RESPONSE;
				switch (response.requestType) {
					case "initialize":
						if (task.type !== "initialize") throw new Error("handleWorkerMessage: task/response dont match");
						task.resolve();
						break;
					case "register-theme":
						if (task.type !== "register-theme") throw new Error("handleWorkerMessage: task/response dont match");
						task.resolve();
						break;
					case "file": {
						if (task.type !== "file") throw new Error("handleWorkerMessage: task/response dont match");
						const { result, options } = response;
						const { instance, request } = task;
						if (request.file.cacheKey != null) this.fileCache.set(request.file.cacheKey, {
							result,
							options
						});
						instance.onHighlightSuccess(request.file, result, options);
						break;
					}
					case "diff": {
						if (task.type !== "diff") throw new Error("handleWorkerMessage: task/response dont match");
						const { result, options } = response;
						const { instance, request } = task;
						if (request.diff.cacheKey != null) this.diffCache.set(request.diff.cacheKey, {
							result,
							options
						});
						instance.onHighlightSuccess(request.diff, result, options);
						break;
					}
				}
			}
		} catch (error) {
			if (error !== IGNORE_RESPONSE) console.error(error, task, response);
		}
		if (task != null && "instance" in task && this.instanceRequestMap.get(task.instance) === response.id) this.instanceRequestMap.delete(task.instance);
		this.pendingTasks.delete(response.id);
		managedWorker.busy = false;
		if (this.taskQueue.length > 0) this.queueDrain();
	}
	_queuedDrain;
	queueDrain() {
		if (this._queuedDrain != null) return;
		this._queuedDrain = Promise.resolve().then(this.drainQueue);
	}
	executeTask(managedWorker, task) {
		managedWorker.busy = true;
		this.pendingTasks.set(task.id, task);
		for (const lang of getLangsFromTask(task)) managedWorker.langs.add(lang);
		managedWorker.worker.postMessage(task.request);
	}
	getAvailableWorker(langs) {
		let worker;
		for (const managedWorker of this.workers) {
			if (managedWorker.busy || !managedWorker.initialized) continue;
			worker = managedWorker;
			if (langs.length === 0) break;
			let hasEveryLang = true;
			for (const lang of langs) if (!managedWorker.langs.has(lang)) {
				hasEveryLang = false;
				break;
			}
			if (hasEveryLang) break;
		}
		return worker;
	}
	generateRequestId() {
		return `req_${++this.nextRequestId}`;
	}
};
function getLangsFromTask(task) {
	const langs = /* @__PURE__ */ new Set();
	if (task.type === "initialize" || task.type === "register-theme") return [];
	switch (task.type) {
		case "file":
			langs.add(task.request.file.lang ?? getFiletypeFromFileName(task.request.file.name));
			break;
		case "diff":
			langs.add(task.request.diff.lang ?? getFiletypeFromFileName(task.request.diff.name));
			langs.add(task.request.diff.lang ?? getFiletypeFromFileName(task.request.diff.prevName ?? "-"));
			break;
	}
	langs.delete("text");
	return Array.from(langs);
}

//#endregion
export { WorkerPoolManager };
//# sourceMappingURL=WorkerPoolManager.js.map