// Worker pool for offloading large message JSON parse/stringify + encryption.
// Pre-spawns a small number of Workers and dispatches tasks round-robin.
import { Worker } from 'worker_threads';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import tweetnaclUtil from 'tweetnacl-util';
const { encodeBase64 } = tweetnaclUtil;
const POOL_SIZE = 2;
const LARGE_MESSAGE_THRESHOLD = 1_000_000; // 1MB
let pool = null;
let nextId = 0;
let nextWorkerIdx = 0;
function getWorkerPath() {
    const thisFile = fileURLToPath(import.meta.url);
    const thisDir = dirname(thisFile);
    // In dev/test, import.meta.url points to src/*.ts — resolve to dist/*.js
    if (thisFile.endsWith('.ts')) {
        return join(thisDir, '..', 'dist', 'json-worker.js');
    }
    return join(thisDir, 'json-worker.js');
}
function ensurePool() {
    if (pool)
        return pool;
    pool = [];
    const workerPath = getWorkerPath();
    for (let i = 0; i < POOL_SIZE; i++) {
        const worker = new Worker(workerPath);
        const pw = { worker, pending: new Map(), busy: 0 };
        worker.on('message', (msg) => {
            const task = pw.pending.get(msg.id);
            if (!task)
                return;
            pw.pending.delete(msg.id);
            pw.busy--;
            if (msg.error) {
                task.reject(new Error(msg.error));
            }
            else {
                task.resolve(msg.result);
            }
        });
        worker.on('error', (err) => {
            // Reject all pending tasks
            for (const [, task] of pw.pending) {
                task.reject(err instanceof Error ? err : new Error(String(err)));
            }
            pw.pending.clear();
            pw.busy = 0;
        });
        pool.push(pw);
    }
    return pool;
}
function pickWorker() {
    const workers = ensurePool();
    // Round-robin, skip if too many pending (>10)
    for (let i = 0; i < workers.length; i++) {
        const idx = (nextWorkerIdx + i) % workers.length;
        if (workers[idx].busy < 10) {
            nextWorkerIdx = (idx + 1) % workers.length;
            return workers[idx];
        }
    }
    return null; // All workers busy — caller should fall back to inline
}
function dispatch(op, data, key) {
    const pw = pickWorker();
    if (!pw)
        return Promise.reject(new Error('All workers busy'));
    const id = nextId++;
    const keyStr = key ? encodeBase64(key) : null;
    return new Promise((resolve, reject) => {
        pw.pending.set(id, { resolve, reject });
        pw.busy++;
        pw.worker.postMessage({ id, op, data, key: keyStr });
    });
}
/** Check if a raw inbound message is large enough to offload to Worker. */
export function isLargeMessage(raw) {
    return raw.length > LARGE_MESSAGE_THRESHOLD;
}
/** Check if an outbound message object is large enough to offload.
 *  Avoids JSON.stringify by checking known large-payload shapes (file attachments). */
export function isLargeOutbound(msg) {
    if (!msg || typeof msg !== 'object')
        return false;
    const m = msg;
    // Chat messages with file attachments
    if (Array.isArray(m.files)) {
        for (const f of m.files) {
            if (f && typeof f === 'object' && typeof f.data === 'string') {
                if (f.data.length > LARGE_MESSAGE_THRESHOLD)
                    return true;
            }
        }
    }
    // Generic large data/content fields
    if (typeof m.data === 'string' && m.data.length > LARGE_MESSAGE_THRESHOLD)
        return true;
    if (typeof m.content === 'string' && m.content.length > LARGE_MESSAGE_THRESHOLD)
        return true;
    return false;
}
/** Parse a large message in a Worker thread. Falls back to inline on failure. */
export async function workerParseMessage(raw, sessionKey) {
    try {
        return await dispatch('parse', raw, sessionKey);
    }
    catch {
        // Fallback: parse inline
        return null;
    }
}
/** Serialize+encrypt a large message in a Worker thread, then send via ws. */
export async function workerEncryptAndSend(ws, msg, sessionKey) {
    try {
        const serialized = await dispatch('serialize', msg, sessionKey);
        if (ws.readyState === 1) {
            ws.send(serialized);
        }
    }
    catch (err) {
        console.error('[workerEncryptAndSend] Worker failed, falling back to inline:', err.message);
        // Fallback: do it inline
        const { encryptAndSend } = await import('./encryption.js');
        await encryptAndSend(ws, msg, sessionKey);
    }
}
/** Shut down all workers. Call on server shutdown. */
export async function terminateWorkerPool() {
    if (!pool)
        return;
    await Promise.all(pool.map(pw => pw.worker.terminate()));
    pool = null;
}
//# sourceMappingURL=worker-pool.js.map