/**
 * Essentia analysis worker (child process).
 *
 * Why this exists: essentia.js's WASM module calls `process.abort()` from
 * native code on internal "Request timeout: getStats" — a SIGABRT that
 * bypasses Node's uncaughtException handler and kills the entire process.
 * On a memory-constrained host (Mac mini at 0.1 GB free, V8 cap 1024 MB)
 * we observed this taking down SOUND in a tight launchd respawn loop.
 *
 * Running essentia in a child_process.fork() worker isolates the abort:
 * if KeyExtractor aborts, only this child dies. The parent gets an `exit`
 * event with a non-zero code, rejects the in-flight request, and respawns
 * the worker for the next analysis.
 *
 * Note: worker_threads does NOT provide this isolation — Workers share
 * the parent's V8 process, so process.abort() from a Worker still kills
 * the parent. fork() is the correct primitive here.
 */ import { serverLogger } from "../../utils/serverLogger.js";
let essentia = null;
function send(msg) {
    if (process.send) {
        process.send(msg);
    }
}
async function loadEssentia() {
    try {
        // essentia.js is a CommonJS module that exposes:
        //   { Essentia: function, EssentiaWASM: object, ... }
        // The previous in-process code in djAnalysisService.ts called
        // `Essentia.default()` which was always wrong — it was silently
        // failing forever, so we never actually had key detection. The
        // import side-effect still loaded the WASM module though, which is
        // where the abort behavior came from.
        //
        // Correct pattern (per essentia.js index.js + README): pass the
        // pre-initialized WASM module to the Essentia constructor.
        const mod = await import("essentia.js").catch(()=>null);
        if (!mod || typeof mod.Essentia !== "function" || !mod.EssentiaWASM) {
            return null;
        }
        return new mod.Essentia(mod.EssentiaWASM);
    } catch (error) {
        serverLogger.warn("[essentiaWorker] Failed to load essentia.js", {
            error: error instanceof Error ? error.message : String(error)
        });
        return null;
    }
}
(async function main() {
    essentia = await loadEssentia();
    if (!essentia) {
        // Tell parent we couldn't load — it'll fall back to "key detection
        // unavailable" rather than spawning us again.
        send({
            type: "fatal",
            message: "essentia.js failed to load"
        });
        process.exit(1);
    }
    send({
        type: "ready"
    });
    process.on("message", (msg)=>{
        if (!msg || typeof msg !== "object") return;
        if (msg.type === "ping") {
            send({
                type: "pong",
                id: msg.id
            });
            return;
        }
        if (msg.type === "analyze") {
            // essentia's KeyExtractor needs an embind VectorFloat, not the raw
            // Float32Array we receive over IPC. Convert via arrayToVector, then free
            // the WASM-heap vector afterwards — this worker analyzes whole libraries
            // on a memory-constrained host, so leaking native vectors is not an
            // option.
            let audioVector = null;
            try {
                if (!msg.audioBuffer || msg.audioBuffer.length === 0) {
                    // ffmpeg decode failed upstream (unsupported codec / missing file) →
                    // empty buffer. KeyExtractor throws on empty input; surface it as a
                    // clean soft error so the parent records "no key" without a crash.
                    send({
                        type: "error",
                        id: msg.id,
                        message: "empty audio buffer"
                    });
                    return;
                }
                audioVector = essentia.arrayToVector(msg.audioBuffer);
                // KeyExtractor is the call that has historically aborted. If it
                // does, the abort is now contained to this child process — the
                // parent will get our `exit` event and respawn us.
                const result = essentia.KeyExtractor(audioVector);
                send({
                    type: "result",
                    id: msg.id,
                    key: result.key,
                    scale: result.scale,
                    strength: result.strength
                });
            } catch (error) {
                send({
                    type: "error",
                    id: msg.id,
                    message: error instanceof Error ? error.message : String(error)
                });
            } finally{
                // Free the embind vector regardless of success/failure.
                if (audioVector && typeof audioVector.delete === "function") {
                    audioVector.delete();
                }
            }
        }
    });
    // Belt-and-suspenders: if a promise rejects unhandled inside the worker,
    // log it and exit cleanly so the parent respawns rather than hanging.
    process.on("unhandledRejection", (reason)=>{
        serverLogger.error("[essentiaWorker] unhandledRejection — exiting", {
            reason: reason instanceof Error ? reason.message : String(reason)
        });
        process.exit(2);
    });
})();

//# sourceMappingURL=essentiaWorker.js.map