"""Transcription audio (dictée R5, ADR-004 §5) : l'audio ne survit JAMAIS à
cet appel — un fichier temporaire le temps de l'inférence, supprimé aussitôt,
quoi qu'il arrive (D5, loi 09-08). Seul le texte transcrit est retourné ; la
relecture humaine reste le seul contenu conservé, dans l'OT (D1).
"""

import os
import tempfile
from typing import Protocol


class Transcripteur(Protocol):
    def transcrire(self, audio: bytes, extension: str) -> str: ...


class TranscripteurLocal:
    """faster-whisper (CTranslate2, CPU, licence MIT) — chargé paresseusement,
    jamais importé en test. Modèle et langue forcée en français : le mélange
    français/darija du terrain reste un point de vigilance non calibré (pas
    de mesure préalable demandée par le référent, 22/07/2026) — à revoir sur
    échantillons réels si la qualité déçoit en recette."""

    def __init__(self, modele: str) -> None:
        from faster_whisper import WhisperModel  # import différé (dépendance optionnelle)

        self._modele = WhisperModel(modele, device="cpu", compute_type="int8")

    def transcrire(self, audio: bytes, extension: str) -> str:
        fd, chemin = tempfile.mkstemp(suffix=f".{extension}")
        try:
            with os.fdopen(fd, "wb") as f:
                f.write(audio)
            segments, _ = self._modele.transcribe(chemin, language="fr", vad_filter=True)
            return " ".join(segment.text.strip() for segment in segments).strip()
        finally:
            os.remove(chemin)  # D5 — l'audio ne doit JAMAIS survivre à l'appel


class TranscripteurDeterministe:
    """Tests/CI : pas de modèle, pas de dépendance audio réelle — renvoie un
    texte stable dérivé de la taille du fichier (même interface)."""

    def transcrire(self, audio: bytes, extension: str) -> str:
        return f"transcription déterministe ({len(audio)} octets, .{extension})"


def construire_transcripteur(mode: str, modele: str) -> Transcripteur | None:
    """None si la dictée n'est pas activée (AI_TRANSCRIPTION=off, défaut) —
    l'appelant doit alors refuser proprement (503), pas planter."""
    if mode == "off":
        return None
    if mode == "deterministe":
        return TranscripteurDeterministe()
    return TranscripteurLocal(modele)
