import { ProviderError } from "./providers.js";
export class OffscreenAudioTranscriber {
    tabId;
    streamId;
    send;
    callbacks;
    constructor(tabId, streamId, send = runtimeSend) {
        this.tabId = tabId;
        this.streamId = streamId;
        this.send = send;
    }
    async availability(selection) {
        const result = await this.send({ type: "m1.offscreen.audio.availability", ...(selection ?? {}) });
        if (!record(result) || (result.status !== "available" && result.status !== "unavailable" && result.status !== "unconfigured" && result.status !== "corrupt"))
            return "unavailable";
        return result.status;
    }
    async start(options) {
        this.callbacks = options;
        const result = await this.send({ type: "m1.offscreen.audio.start", tabId: this.tabId, streamId: this.streamId, sourceLanguage: options.sourceLanguage, profile: options.profile ?? "realtime" });
        if (record(result) && result.ok === true)
            return;
        this.callbacks = undefined;
        throw new ProviderError(providerFailure(record(result) ? result.error : undefined));
    }
    receive(message) {
        if (!record(message) || message.tabId !== this.tabId || typeof message.type !== "string")
            return false;
        if ((message.type === "m1.offscreen.audio.partial" || message.type === "m1.offscreen.audio.stable") && typeof message.text === "string" && typeof message.startMs === "number" && typeof message.endMs === "number") {
            if (message.type === "m1.offscreen.audio.partial")
                this.callbacks?.onPartial(message.text, message.startMs, message.endMs);
            else
                this.callbacks?.onStable(message.text, message.startMs, message.endMs);
            return true;
        }
        if (message.type === "m1.offscreen.audio.language" && (message.language === "en" || message.language === "ja")) {
            this.callbacks?.onLanguage?.(message.language);
            return true;
        }
        if (message.type === "m1.offscreen.audio.metrics" && isAsrMetrics(message.metrics)) {
            this.callbacks?.onMetrics?.(message.metrics);
            return true;
        }
        if (message.type === "m1.offscreen.audio.error") {
            this.callbacks?.onError(new ProviderError(providerFailure(message.error)));
            return true;
        }
        return false;
    }
    push(_frame) { }
    setPaused(paused) { void this.send({ type: "m1.offscreen.audio.pause", tabId: this.tabId, paused }).catch(() => undefined); }
    reset(reason = "overload") {
        void this.send({ type: "m1.offscreen.audio.reset", tabId: this.tabId, reason }).catch(() => undefined);
    }
    stop() {
        this.callbacks = undefined;
        void this.send({ type: "m1.offscreen.audio.stop", tabId: this.tabId }).catch(() => undefined);
    }
}
function providerFailure(value) {
    return value === "audio_unavailable" || value === "audio_unconfigured" || value === "asr_cache_corrupt" || value === "capture_failed" || value === "inference_failed" || value === "audio_overloaded" ? value : "inference_failed";
}
function runtimeSend(message) {
    const extension = globalThis;
    return extension.chrome?.runtime?.sendMessage ? extension.chrome.runtime.sendMessage(message) : Promise.reject(new Error("extension_runtime_unavailable"));
}
function record(value) { return typeof value === "object" && value !== null; }
function isAsrMetrics(value) {
    if (!record(value))
        return false;
    const fields = ["audioReceivedMs", "audioProcessedMs", "inferenceMs", "rtf", "audioLagMs", "partialLatencyMs", "finalLatencyMs", "droppedAudioMs", "resetCount"];
    return fields.every((field) => typeof value[field] === "number" && Number.isFinite(value[field]) && value[field] >= 0);
}
