let enginePromise;

const workerUrl = new URL(import.meta.url);
const assetVersion = workerUrl.searchParams.get('ver') || '';

const MAX_INPUT_BYTES = 20 * 1024 * 1024;
const MAX_MARKDOWN_CHARACTERS = 2000000;
const MAX_MARKDOWN_LINES = 50000;
const MAX_PAGE_COUNT = 2000;
const ERROR_CODES = new Set([
	'encrypted',
	'malformed',
	'limit',
	'engine',
	'security',
]);

const ERROR_MESSAGES = {
	encrypted: 'This PDF is password-protected. Enter its password and try again.',
	malformed: 'This PDF appears damaged or incomplete. Re-export it and try again.',
	limit: 'This PDF exceeds the safe conversion limits. Try a smaller document.',
	engine: 'PufferPDF could not process this PDF. Reload the page and try again.',
	security: 'PufferPDF could not start the PDF parser. A Content Security Policy may be blocking the same-origin module Worker or WebAssembly.',
};

function createWorkerError(code) {
	const safeCode = ERROR_CODES.has(code) ? code : 'engine';
	const error = new Error(ERROR_MESSAGES[safeCode]);
	error.code = safeCode;
	return error;
}

export function classifyWorkerError(error) {
	if (error && ERROR_CODES.has(error.code)) {
		return error.code;
	}

	const message = error instanceof Error ? error.message : String(error || '');

	if (/encrypted|password.{0,24}(?:required|incorrect|invalid)|decrypt/i.test(message)) {
		return 'encrypted';
	}

	if (
		/invalid pdf structure|malformed|corrupt|damaged|cross-reference|\bxref\b|\bstartxref\b|invalid trailer|unexpected (?:end|eof)/i.test(
			message
		)
	) {
		return 'malformed';
	}

	if (/safe conversion limit|too (?:large|many)|out of memory|memory allocation/i.test(message)) {
		return 'limit';
	}

	if (
		/content security policy|wasm-unsafe-eval|refused to|dynamically imported module|failed to fetch/i.test(
			message
		)
	) {
		return 'security';
	}

	return 'engine';
}

export function workerErrorResponse(error) {
	const code = classifyWorkerError(error);
	return {
		type: 'error',
		code,
		message: ERROR_MESSAGES[code],
	};
}

export function validateProcessResult(result) {
	if (
		!result ||
		typeof result !== 'object' ||
		(typeof result.markdown !== 'undefined' && typeof result.markdown !== 'string')
	) {
		throw createWorkerError('engine');
	}

	const pageCount = Number(result.pageCount);
	if (!Number.isInteger(pageCount) || pageCount < 1) {
		throw createWorkerError('engine');
	}

	const markdown = typeof result.markdown === 'string' ? result.markdown : '';
	const lineCount = markdown
		? markdown.split('\n', MAX_MARKDOWN_LINES + 1).length
		: 0;

	if (
		markdown.length > MAX_MARKDOWN_CHARACTERS ||
		lineCount > MAX_MARKDOWN_LINES ||
		pageCount > MAX_PAGE_COUNT
	) {
		throw createWorkerError('limit');
	}
}

export function validateInputBytes(byteLength) {
	if (
		!Number.isInteger(byteLength) ||
		byteLength < 1 ||
		byteLength > MAX_INPUT_BYTES
	) {
		throw createWorkerError(byteLength > MAX_INPUT_BYTES ? 'limit' : 'engine');
	}
}

export function workerAssetUrl(relativePath) {
	const url = new URL(relativePath, import.meta.url);
	if (assetVersion) {
		url.searchParams.set('ver', assetVersion);
	}
	return url;
}

function loadEngine() {
	if (!enginePromise) {
		const moduleUrl = workerAssetUrl(
			'../vendor/pdf-inspector/pdf_inspector_wasm.js'
		);
		const wasmUrl = workerAssetUrl(
			'../vendor/pdf-inspector/pdf_inspector_wasm_bg.wasm'
		);

		// WordPress hosts do not consistently map .wasm to application/wasm.
		// Loading bytes explicitly avoids depending on streaming MIME support.
		enginePromise = Promise.all([
			import(moduleUrl.href),
			fetch(wasmUrl, {
				credentials: 'same-origin',
				referrerPolicy: 'no-referrer',
			}).then((response) => {
				if (!response.ok) {
					throw new Error('The PDF engine could not be loaded.');
				}

				return response.arrayBuffer();
			}),
		]).then(async ([engine, wasmBytes]) => {
			await engine.default({ module_or_path: wasmBytes });
			return engine;
		});
	}

	return enginePromise;
}

self.addEventListener('message', async (event) => {
	const message = event.data || {};

	if (message.type !== 'process' || !(message.buffer instanceof ArrayBuffer)) {
		return;
	}

	try {
		validateInputBytes(message.buffer.byteLength);

		const engine = await loadEngine();

		const result = engine.processPdf(
			new Uint8Array(message.buffer),
			message.options || {}
		);
		validateProcessResult(result);

		self.postMessage({
			type: 'result',
			result,
			engineVersion: engine.version(),
		});
	} catch (error) {
		self.postMessage(workerErrorResponse(error));
	}
});
