/**
 * Worker thread for ONNX embedding model.
 * Runs model loading and inference off the main thread so the event loop
 * stays responsive for MCP JSON-RPC messages.
 *
 * The ONNX runtime does synchronous C++ work during model loading and
 * inference that blocks the Node.js event loop. setTimeout/Promise.race
 * timeouts can't fire while the event loop is frozen. Running in a
 * worker_threads Worker solves this — the main thread stays free.
 */
import { parentPort } from 'worker_threads';
import { join } from 'path';
import { homedir } from 'os';
// Lazy-loaded to catch import failures gracefully
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let pipelineFn = null;
let loadError = null;
async function initTransformers() {
    if (pipelineFn)
        return;
    if (loadError)
        throw new Error(loadError);
    try {
        const mod = await import('@huggingface/transformers');
        pipelineFn = mod.pipeline;
        mod.env.allowRemoteModels = true;
        mod.env.allowLocalModels = true;
        mod.env.cacheDir = join(homedir(), '.cache', 'shieldcortex', 'models');
    }
    catch (err) {
        loadError = err instanceof Error ? err.message : String(err);
        throw new Error(`Failed to load @huggingface/transformers: ${loadError}`);
    }
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let extractor = null;
async function loadModel() {
    if (extractor)
        return;
    await initTransformers();
    extractor = await pipelineFn('feature-extraction', 'Xenova/all-MiniLM-L6-v2');
}
async function embed(text) {
    if (!extractor)
        await loadModel();
    const truncated = text.slice(0, 2000);
    const output = await extractor(truncated, {
        pooling: 'mean',
        normalize: true,
    });
    let result;
    if (output.data && output.data.length > 0) {
        result = Array.from(output.data);
    }
    else if (typeof output.tolist === 'function') {
        result = output.tolist().flat(Infinity);
    }
    else {
        throw new Error('Cannot extract embedding from tensor');
    }
    // Release ONNX native memory
    if (output && typeof output.dispose === 'function') {
        output.dispose();
    }
    return result;
}
// Catch uncaught errors so the worker doesn't exit silently with code 0
process.on('uncaughtException', (err) => {
    parentPort?.postMessage({ type: 'error', error: `Uncaught: ${err.message}` });
});
process.on('unhandledRejection', (reason) => {
    const msg = reason instanceof Error ? reason.message : String(reason);
    parentPort?.postMessage({ type: 'error', error: `Unhandled rejection: ${msg}` });
});
parentPort.on('message', async (msg) => {
    try {
        if (msg.type === 'load') {
            await loadModel();
            parentPort.postMessage({ id: msg.id, ok: true });
        }
        else if (msg.type === 'embed') {
            const data = await embed(msg.text);
            parentPort.postMessage({ id: msg.id, ok: true, data });
        }
        else if (msg.type === 'ping') {
            parentPort.postMessage({ id: msg.id, ok: true });
        }
    }
    catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        parentPort.postMessage({ id: msg.id, ok: false, error: message });
    }
});
// Signal ready — worker is alive and listening for messages
parentPort.postMessage({ type: 'ready' });
