import { openAsBlob } from "node:fs";
import { setTimeout as delay } from "node:timers/promises";
import { TRANSCRIPTION_TIMEOUT_MS } from "./constants.js";
import { toArrayBuffer, wrapError } from "./utils.js";
const ASSEMBLYAI_BASE_URL = "https://api.assemblyai.com/v2";
export const ASSEMBLYAI_DEFAULT_SPEECH_MODEL = "universal-2";
export const ASSEMBLYAI_DEFAULT_MODELS = [ASSEMBLYAI_DEFAULT_SPEECH_MODEL];
export const ASSEMBLYAI_TRANSCRIPTION_MODEL_ID = `assemblyai/${ASSEMBLYAI_DEFAULT_SPEECH_MODEL}`;
const ASSEMBLYAI_POLL_INTERVAL_MS = 1_500;
function resolveFetch(fetchImpl) {
    return fetchImpl ?? globalThis.fetch;
}
function buildHeaders(apiKey, contentType) {
    const headers = new Headers({ authorization: apiKey });
    if (contentType?.trim())
        headers.set("content-type", contentType.trim());
    return headers;
}
function requireString(value, label) {
    if (typeof value === "string" && value.trim().length > 0)
        return value.trim();
    throw new Error(`AssemblyAI ${label} missing from response`);
}
async function uploadViaAssemblyAi(body, apiKey, { fetchImpl, baseUrl = ASSEMBLYAI_BASE_URL, mediaType, }) {
    const res = await resolveFetch(fetchImpl)(`${baseUrl}/upload`, {
        method: "POST",
        headers: buildHeaders(apiKey, mediaType ?? "application/octet-stream"),
        body,
        duplex: "half",
    });
    if (!res.ok) {
        const text = await res.text().catch(() => "");
        throw new Error(`upload failed (${res.status}): ${text.trim() || res.statusText}`);
    }
    const payload = (await res.json().catch(() => null));
    return requireString(payload?.upload_url, "upload_url");
}
async function submitAssemblyAiTranscript(audioUrl, apiKey, { fetchImpl, baseUrl = ASSEMBLYAI_BASE_URL }) {
    const res = await resolveFetch(fetchImpl)(`${baseUrl}/transcript`, {
        method: "POST",
        headers: buildHeaders(apiKey, "application/json"),
        body: JSON.stringify({
            audio_url: audioUrl,
            speech_models: [...ASSEMBLYAI_DEFAULT_MODELS],
            punctuate: true,
            format_text: true,
        }),
    });
    if (!res.ok) {
        const text = await res.text().catch(() => "");
        throw new Error(`transcript create failed (${res.status}): ${text.trim() || res.statusText}`);
    }
    const payload = (await res.json().catch(() => null));
    return {
        id: requireString(payload?.id, "transcript id"),
        status: typeof payload?.status === "string" ? payload.status : "queued",
        text: typeof payload?.text === "string" && payload.text.trim() ? payload.text.trim() : null,
    };
}
async function pollAssemblyAiTranscript(id, apiKey, { fetchImpl, baseUrl = ASSEMBLYAI_BASE_URL, pollIntervalMs = ASSEMBLYAI_POLL_INTERVAL_MS, timeoutMs = TRANSCRIPTION_TIMEOUT_MS, }) {
    const startedAt = Date.now();
    for (;;) {
        if (Date.now() - startedAt > timeoutMs) {
            throw new Error(`transcript polling timed out after ${Math.round(timeoutMs / 1000)}s`);
        }
        const res = await resolveFetch(fetchImpl)(`${baseUrl}/transcript/${encodeURIComponent(id)}`, {
            method: "GET",
            headers: buildHeaders(apiKey),
        });
        if (!res.ok) {
            const text = await res.text().catch(() => "");
            throw new Error(`transcript poll failed (${res.status}): ${text.trim() || res.statusText}`);
        }
        const payload = (await res.json().catch(() => null));
        const status = typeof payload?.status === "string" ? payload.status.trim().toLowerCase() : "";
        if (status === "completed") {
            return typeof payload?.text === "string" && payload.text.trim() ? payload.text.trim() : null;
        }
        if (status === "error") {
            const message = typeof payload?.error === "string" && payload.error.trim()
                ? payload.error.trim()
                : "AssemblyAI transcription failed";
            throw new Error(message);
        }
        await delay(pollIntervalMs);
    }
}
async function transcribeUploadedAudioWithAssemblyAi(body, apiKey, { fetchImpl, baseUrl, mediaType, pollIntervalMs, timeoutMs, }) {
    const uploadUrl = await uploadViaAssemblyAi(body, apiKey, {
        fetchImpl,
        baseUrl,
        mediaType,
    });
    const started = await submitAssemblyAiTranscript(uploadUrl, apiKey, {
        fetchImpl,
        baseUrl,
    });
    if (started.status.trim().toLowerCase() === "completed")
        return started.text;
    return await pollAssemblyAiTranscript(started.id, apiKey, {
        fetchImpl,
        baseUrl,
        pollIntervalMs,
        timeoutMs,
    });
}
export async function transcribeWithAssemblyAi(bytes, mediaType, apiKey, options = {}) {
    try {
        return await transcribeUploadedAudioWithAssemblyAi(new Blob([toArrayBuffer(bytes)], { type: mediaType }), apiKey, {
            mediaType,
            ...options,
        });
    }
    catch (error) {
        throw wrapError("AssemblyAI transcription failed", error);
    }
}
export async function transcribeFileWithAssemblyAi({ filePath, mediaType, apiKey, fetchImpl, baseUrl, pollIntervalMs, timeoutMs, }) {
    try {
        return await transcribeUploadedAudioWithAssemblyAi(await openAsBlob(filePath, { type: mediaType }), apiKey, {
            mediaType,
            fetchImpl,
            baseUrl,
            pollIntervalMs,
            timeoutMs,
        });
    }
    catch (error) {
        throw wrapError("AssemblyAI transcription failed", error);
    }
}
//# sourceMappingURL=assemblyai.js.map