import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { writeFile, unlink, access } from "node:fs/promises";
import { randomUUID } from "node:crypto";
import { tmpdir, homedir } from "node:os";
import { join } from "node:path";
import { logger } from "../logger.js";
const execFileAsync = promisify(execFile);
const WHISPER_MODEL_PATH = process.platform === "win32"
    ? join(process.env.LOCALAPPDATA ?? join(homedir(), "AppData", "Local"), "whisper-cpp", "ggml-large-v3-turbo.bin")
    : join(homedir(), ".local/share/whisper-cpp/ggml-large-v3-turbo.bin");
const TRANSCRIBE_TIMEOUT_MS = 30_000;
async function fileExists(path) {
    try {
        await access(path);
        return true;
    }
    catch {
        return false;
    }
}
async function which(bin) {
    const cmd = process.platform === "win32" ? "where" : "which";
    try {
        await execFileAsync(cmd, [bin]);
        return true;
    }
    catch {
        return false;
    }
}
/**
 * Downloads audio from a URL and returns the local temp path.
 * Caller is responsible for cleanup.
 */
async function downloadAudio(url) {
    const ext = url.includes(".ogg") ? ".ogg" : url.includes(".mp3") ? ".mp3" : ".audio";
    const dest = join(tmpdir(), `vc-audio-${randomUUID()}${ext}`);
    const response = await fetch(url, { signal: AbortSignal.timeout(TRANSCRIBE_TIMEOUT_MS) });
    if (!response.ok) {
        throw new Error(`Failed to download audio: HTTP ${response.status}`);
    }
    const buffer = Buffer.from(await response.arrayBuffer());
    await writeFile(dest, buffer);
    return dest;
}
/**
 * Transcribe a single audio file (URL, local path, or base64) to text
 * using whisper-cli. Returns the transcription or null on failure.
 */
export async function transcribeAudioAttachment(att) {
    if (att.type !== "audio")
        return null;
    const hasWhisper = await which("whisper-cli");
    const hasFfmpeg = await which("ffmpeg");
    const hasModel = await fileExists(WHISPER_MODEL_PATH);
    if (!hasWhisper || !hasFfmpeg || !hasModel) {
        logger.warn(`Transcription unavailable: whisper-cli=${hasWhisper} ffmpeg=${hasFfmpeg} model=${hasModel}`);
        return null;
    }
    const tempFiles = [];
    try {
        let inputPath;
        if (att.localPath) {
            inputPath = att.localPath;
        }
        else if (att.url) {
            inputPath = await downloadAudio(att.url);
            tempFiles.push(inputPath);
        }
        else if (att.base64) {
            inputPath = join(tmpdir(), `vc-audio-${randomUUID()}.ogg`);
            await writeFile(inputPath, Buffer.from(att.base64, "base64"));
            tempFiles.push(inputPath);
        }
        else {
            return null;
        }
        const wavPath = join(tmpdir(), `vc-audio-${randomUUID()}.wav`);
        tempFiles.push(wavPath);
        await execFileAsync("ffmpeg", ["-y", "-i", inputPath, "-ar", "16000", "-ac", "1", wavPath], { timeout: TRANSCRIBE_TIMEOUT_MS });
        const { stdout } = await execFileAsync("whisper-cli", ["-m", WHISPER_MODEL_PATH, "-f", wavPath, "--no-timestamps", "-l", "auto"], { timeout: TRANSCRIBE_TIMEOUT_MS });
        const text = stdout.trim();
        return text || null;
    }
    catch (err) {
        logger.warn(`Audio transcription failed: ${err instanceof Error ? err.message : String(err)}`);
        return null;
    }
    finally {
        for (const f of tempFiles) {
            unlink(f).catch((_err) => { });
        }
    }
}
/**
 * Pre-process a batch of messages: for any message that has audio
 * attachments and empty/missing text, transcribe the audio and
 * populate the text field. Mutates messages in-place for efficiency.
 */
export async function transcribeVoiceMessages(messages) {
    const tasks = [];
    for (const msg of messages) {
        const audioAttachments = msg.attachments.filter((a) => a.type === "audio");
        if (audioAttachments.length === 0)
            continue;
        for (const att of audioAttachments) {
            tasks.push(transcribeAudioAttachment(att).then((transcript) => {
                if (transcript) {
                    const label = msg.text ? `${msg.text}\n\n[Voice message transcription]: ${transcript}` : `[Voice message transcription]: ${transcript}`;
                    msg.text = label;
                    logger.log("info", "transcribe", `Transcribed voice message from ${msg.sender}`, {
                        channel: msg.channel,
                        sender: msg.sender,
                        length: transcript.length,
                    });
                }
            }));
        }
    }
    await Promise.all(tasks);
}
//# sourceMappingURL=transcribe.js.map