import { LOCAL_ASR_MODEL } from "./model.js";
export function isApprovedManifest(manifest) { return manifest.redistributionReview === "approved" && Boolean(manifest.modelUrl) && manifest.sizeBytes > 0 && /^[a-f0-9]{64}$/i.test(manifest.sha256); }
export async function verifySha256(payload, expected) { const digest = await crypto.subtle.digest("SHA-256", payload); const actual = Array.from(new Uint8Array(digest), (value) => value.toString(16).padStart(2, "0")).join(""); return actual === expected.toLowerCase(); }
export class ModelStore {
    model;
    key;
    storage;
    constructor(storage, model = LOCAL_ASR_MODEL, key = "localAsrModel") {
        this.model = model;
        this.key = key;
        this.storage = storage ?? (typeof localStorage === "undefined" ? new MapStorage() : localStorage);
    }
    manifest() { return this.model; }
    async status() {
        const raw = this.storage.getItem(this.key);
        return raw ? "ready" : "unconfigured";
    }
    async remove() { this.storage.removeItem(this.key); }
    markReady() { this.storage.setItem(this.key, `${this.model.modelId}@${this.model.revision}`); }
    async download(signal, onProgress) {
        if (!isApprovedManifest(this.model))
            throw new Error("unconfigured");
        const response = await fetch(this.model.modelUrl, { signal });
        if (!response.ok || !response.body)
            throw new Error("download_failed");
        const total = Number(response.headers.get("content-length")) || this.model.sizeBytes;
        const reader = response.body.getReader();
        let received = 0;
        for (;;) {
            const part = await reader.read();
            if (part.done)
                break;
            received += part.value.byteLength;
            onProgress?.(received, total);
        }
        throw new Error("model_storage_not_configured");
    }
    selectExecutionPath() {
        const nav = navigator;
        // Prefer the asyncify WASM/C++-quantized path. WebGPU fp16 is only chosen as a
        // fallback: on real devices it has blocked the main thread for minutes per
        // inference (utterances burst out late) and produced single-character transcripts,
        // while the WASM path yields to the event loop and was verified to transcribe
        // correctly. WASM is also the only execution path the translation models expose.
        if (this.model.executionPaths.includes("wasm-cpu"))
            return "wasm-cpu";
        if (nav.gpu && this.model.executionPaths.includes("webgpu"))
            return "webgpu";
        return "unsupported";
    }
}
class MapStorage {
    values = new Map();
    getItem(key) { return this.values.get(key) ?? null; }
    setItem(key, value) { this.values.set(key, value); }
    removeItem(key) { this.values.delete(key); }
}
