import { spawnSync } from "child_process";
import { existsSync } from "fs";
import path from "path";
import { fileURLToPath } from "url";
/**
 * Headless BPM detection helper.
 *
 * History: DJ analysis used `realtime-bpm-analyzer`, a browser library that
 * needs `OfflineAudioContext`/`AudioWorklet`. That worked while SOUND was
 * Electron (Chromium renderer had Web Audio), but since the headless
 * migration (`fc90c6d`) those APIs are `undefined` in Node, so every BPM
 * analysis silently returned 0 (verified: 0% of the library analyzed since).
 *
 * This replaces it with a shell-out to `bpm_detector_simple.py` — pure
 * Python stdlib + ffmpeg (already in the Docker image), no Web Audio, no
 * pip deps. This module holds the pure, unit-testable pieces (path + output
 * parsing); the execFile invocation lives in djAnalysisService.
 */ const __dirname = path.dirname(fileURLToPath(import.meta.url));
// bpm_detector_simple.py lives in src/server/scripts and is copied to
// dist/server/scripts by the build (`swc src -d dist --copy-files`). From
// this module (dist/server/dj/bpmDetector.js) the script is at ../scripts.
export const BPM_DETECTOR_SCRIPT = path.join(__dirname, "..", "scripts", "bpm_detector_simple.py");
/** Timeout for a single BPM detection (ffmpeg decode + analysis). */ export const BPM_DETECTOR_TIMEOUT_MS = 30000;
// Only the Windows PATH probe (spawnSync) is cached — darwin/linux resolution is
// cheap + deterministic, so it recomputes each call.
let cachedWinPython = null;
/**
 * Resolve the python3 executable that runs bpm_detector_simple.py (pure stdlib).
 *
 * - `SOUND_PYTHON3` env always wins (escape hatch for non-standard installs,
 *   e.g. Windows users who only have the `py` launcher: set `SOUND_PYTHON3=py`
 *   — note `py` alone selects the newest Python, usually 3.x).
 * - macOS: Homebrew python3 isn't always on the launchd PATH. Probe the
 *   Apple-Silicon (`/opt/homebrew`) then Intel (`/usr/local`) Homebrew paths,
 *   falling back to PATH `python3`. (The old code hardcoded /opt/homebrew,
 *   which is wrong on Intel Macs.)
 * - Windows: python.org installs `python.exe` (NOT `python3`); the Microsoft
 *   Store build installs `python3.exe`. Probe `python3` → `python` and cache
 *   the first that answers `--version`. Without this, BPM analysis silently
 *   failed on native Windows because `python3` isn't on PATH there.
 * - Docker (alpine) and Linux: `python3` is on PATH.
 */ export function resolvePython3Path() {
    if (process.env.SOUND_PYTHON3) return process.env.SOUND_PYTHON3;
    if (process.platform === "darwin") {
        for (const p of [
            "/opt/homebrew/bin/python3",
            "/usr/local/bin/python3"
        ]){
            if (existsSync(p)) return p;
        }
        return "python3";
    }
    if (process.platform === "win32") {
        if (cachedWinPython) return cachedWinPython;
        for (const cmd of [
            "python3",
            "python"
        ]){
            try {
                if (spawnSync(cmd, [
                    "--version"
                ], {
                    stdio: "ignore",
                    timeout: 5000
                }).status === 0) {
                    return cachedWinPython = cmd;
                }
            } catch  {
            // not found on PATH — try the next candidate
            }
        }
        // Best-effort default; user can override with SOUND_PYTHON3 (e.g. "py").
        return cachedWinPython = "python";
    }
    return "python3"; // linux / other unix
}
/**
 * Build the PATH for the BPM-detector child process so its bare `ffmpeg`
 * shell-out resolves on every platform.
 *
 * bpm_detector_simple.py runs `subprocess.run(["ffmpeg", ...])` with a bare
 * `ffmpeg`, so ffmpeg must be discoverable on the child's PATH. We prepend,
 * highest priority first:
 *   - the app's bundled binary dir `bin/<platform>` (the daemon cwd in prod,
 *     repo root in dev) — this is where ffmpeg.exe/fpcalc.exe ship on Windows;
 *   - on darwin/linux only, the Homebrew + /usr/local/bin + /usr/bin dirs,
 *     since ffmpeg there isn't always on a launchd/systemd service PATH;
 *   - the inherited PATH.
 *
 * Entries are joined with the platform's OWN delimiter (`;` on Windows, `:` on
 * POSIX) via path.{win32,posix}. The old code hardcoded `:`, which on Windows
 * fused the first entry (the bundled `bin\win32` dir) with the next into one
 * garbage token — so the bare `ffmpeg` resolved to nothing and every BPM
 * compute died with `FileNotFoundError: [WinError 2]` on the Windows worker.
 *
 * platform/cwd/PATH are injectable so the Windows `;` join can be unit-tested
 * from a POSIX CI host.
 */ export function buildDetectorPath(platform = process.platform, cwd = process.cwd(), existingPath = process.env.PATH || "") {
    const p = platform === "win32" ? path.win32 : path.posix;
    const dirs = [
        p.join(cwd, "bin", platform)
    ];
    if (platform === "darwin" || platform === "linux") {
        dirs.push("/opt/homebrew/bin", "/usr/local/bin", "/usr/bin");
    }
    if (existingPath) dirs.push(existingPath);
    return dirs.join(p.delimiter);
}
/**
 * Parse `bpm_detector_simple.py` stdout — `{"bpm": number, "confidence":
 * number, "method": string}`. Returns `{bpm:0, confidence:0}` for any
 * malformed / empty / non-positive / non-finite output (never throws), so a
 * single bad track can't break a library scan.
 */ export function parseBpmDetectorOutput(stdout) {
    if (!stdout || !stdout.trim()) return {
        bpm: 0,
        confidence: 0
    };
    try {
        const parsed = JSON.parse(stdout.trim());
        const bpm = Number(parsed?.bpm);
        const confidence = Number(parsed?.confidence);
        if (!Number.isFinite(bpm) || bpm <= 0) return {
            bpm: 0,
            confidence: 0
        };
        return {
            bpm: Math.round(bpm),
            confidence: Number.isFinite(confidence) && confidence > 0 ? confidence : 0
        };
    } catch  {
        return {
            bpm: 0,
            confidence: 0
        };
    }
}

//# sourceMappingURL=bpmDetector.js.map