import { fork } from "node:child_process";
import { existsSync, promises as fs } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const STATUS_FILENAME = "speech-native-status.json";
const PROBE_LOG_FILENAME = "speech-native-probe.log";
const PROBE_TIMEOUT_MS = 30000;
// Returns true when any local provider would eventually touch sherpa-onnx
// native — either synchronously during reconcile (STT/TTS), or lazily when
// a session is created (VAD via `silero-vad-session.ts:107`). All of these
// paths can take down the main daemon on the affected Windows machines, so
// they all participate in the quarantine decision. The trade-off for
// including local VAD: on Windows, an OpenAI-STT/TTS-with-local-VAD config
// loses voice mode entirely (OpenAI runtime doesn't substitute its own
// VAD). Acceptable until the out-of-process speech host lands.
function localSpeechRequested(providers) {
    return ((providers.dictationStt.enabled !== false && providers.dictationStt.provider === "local") ||
        (providers.voiceTurnDetection.enabled !== false &&
            providers.voiceTurnDetection.provider === "local") ||
        (providers.voiceStt.enabled !== false && providers.voiceStt.provider === "local") ||
        (providers.voiceTts.enabled !== false && providers.voiceTts.provider === "local"));
}
function isTruthyEnvFlag(value) {
    if (!value)
        return false;
    const v = value.trim().toLowerCase();
    return v === "1" || v === "true" || v === "yes" || v === "on";
}
export function createSpeechNativeGuard(params) {
    const { seaworkHome, logger: parentLogger } = params;
    const env = params.env ?? process.env;
    const logger = parentLogger.child({ module: "speech-native-guard" });
    const statusPath = path.join(seaworkHome, STATUS_FILENAME);
    const probeLogPath = path.join(seaworkHome, PROBE_LOG_FILENAME);
    let status = { state: "unknown" };
    let probeStarted = false;
    let probeChild = null;
    const writeStatusFile = (next) => {
        void fs.writeFile(statusPath, JSON.stringify(next, null, 2)).catch((err) => {
            logger.warn({ err, statusPath }, "Failed to write speech native status file");
        });
    };
    const setStatus = (next, onChange) => {
        status = next;
        writeStatusFile(next);
        if (onChange) {
            try {
                onChange();
            }
            catch (err) {
                logger.warn({ err }, "Speech native status change handler threw");
            }
        }
    };
    // Resolve probe entry across both layouts:
    //   - packaged / built (this module is the .js sibling next to probe .js)
    //   - dev with tsx (this module is .ts; probe .js doesn't exist yet)
    // Mirrors `resolveWorkerEntry` in supervisor-entrypoint.ts.
    const probeEntryPoint = () => {
        const candidates = [
            fileURLToPath(new URL("./native-runtime-probe.js", import.meta.url)),
            fileURLToPath(new URL("./native-runtime-probe.ts", import.meta.url)),
        ];
        const entry = candidates.find((c) => existsSync(c)) ?? candidates[0];
        const execArgv = entry.endsWith(".ts") ? ["--import", "tsx"] : [];
        return { entry, execArgv };
    };
    // Single source of truth for "should we quarantine local speech native
    // on this platform/config?". Used by both `shouldQuarantineLocalSpeech`
    // (the main daemon's load gate) and `startProbeInBackground` (so we
    // don't fork a probe that loads native modules on configurations that
    // weren't going to load them anyway).
    const isQuarantined = (providers) => {
        if (process.platform !== "win32")
            return false;
        if (!localSpeechRequested(providers))
            return false;
        if (isTruthyEnvFlag(env.SEAWORK_LOCAL_SPEECH_NATIVE_FORCE))
            return false;
        return true;
    };
    return {
        shouldQuarantineLocalSpeech(providers) {
            const quarantined = isQuarantined(providers);
            if (!quarantined &&
                process.platform === "win32" &&
                localSpeechRequested(providers) &&
                isTruthyEnvFlag(env.SEAWORK_LOCAL_SPEECH_NATIVE_FORCE)) {
                logger.info("SEAWORK_LOCAL_SPEECH_NATIVE_FORCE=1 — loading local speech native in main process");
            }
            return quarantined;
        },
        status() {
            return status;
        },
        startProbeInBackground(providers, options) {
            if (probeStarted)
                return;
            probeStarted = true;
            // Short-circuit on every configuration where the main daemon would
            // load native locally anyway, or where local speech isn't requested
            // at all. Probing in those cases would either duplicate work or add
            // pointless native-load noise on machines that never asked for it.
            if (process.platform !== "win32") {
                setStatus({ state: "skipped", reason: "non-windows-platform" }, options?.onStatusChange);
                return;
            }
            if (!localSpeechRequested(providers)) {
                setStatus({ state: "skipped", reason: "local-speech-not-requested" }, options?.onStatusChange);
                return;
            }
            if (isTruthyEnvFlag(env.SEAWORK_LOCAL_SPEECH_NATIVE_FORCE)) {
                setStatus({ state: "skipped", reason: "force-load-in-main-process" }, options?.onStatusChange);
                return;
            }
            const { entry, execArgv } = probeEntryPoint();
            const startedAt = new Date().toISOString();
            setStatus({ state: "running", startedAt }, options?.onStatusChange);
            try {
                probeChild = fork(entry, [probeLogPath], {
                    stdio: ["ignore", "ignore", "ignore", "ipc"],
                    // Fork without inheriting parent env mutations that might affect
                    // module resolution. Native loading depends on PATH for DLL search.
                    env: process.env,
                    // In dev (tsx) the entry is a .ts file; the child needs --import tsx
                    // to handle TS source. In packaged builds this is empty.
                    execArgv,
                });
            }
            catch (err) {
                const reason = err instanceof Error ? err.message : String(err);
                logger.warn({ err }, "Failed to fork speech native probe");
                setStatus({ state: "failed", finishedAt: new Date().toISOString(), reason: `fork: ${reason}` }, options?.onStatusChange);
                return;
            }
            const timer = setTimeout(() => {
                if (probeChild) {
                    try {
                        probeChild.kill("SIGKILL");
                    }
                    catch {
                        // ignore
                    }
                }
            }, PROBE_TIMEOUT_MS);
            probeChild.once("error", (err) => {
                clearTimeout(timer);
                const reason = err instanceof Error ? err.message : String(err);
                logger.warn({ err }, "Speech native probe child errored");
                setStatus({ state: "failed", finishedAt: new Date().toISOString(), reason: `error: ${reason}` }, options?.onStatusChange);
            });
            probeChild.once("exit", (code, signal) => {
                clearTimeout(timer);
                const finishedAt = new Date().toISOString();
                if (code === 0) {
                    logger.info("Speech native probe succeeded");
                    setStatus({ state: "ok", finishedAt }, options?.onStatusChange);
                }
                else {
                    const reason = signal
                        ? `signal: ${signal}`
                        : `exit: ${code} (probe log: ${probeLogPath})`;
                    logger.warn({ code, signal, probeLogPath }, "Speech native probe failed");
                    setStatus({ state: "failed", finishedAt, reason }, options?.onStatusChange);
                }
                probeChild = null;
            });
        },
    };
}
//# sourceMappingURL=native-runtime-guard.js.map