"""fala.py — a voz do Primo: Edge TTS (neural pt-BR) dentro do processo, sem subprocesso por frase.

Ordem: Edge TTS (precisa de internet) → ElevenLabs só se a pessoa pediu (`"tts": "elevenlabs"` em
~/.primocode/voz.json) → voz do sistema (`say`) offline. As vozes: francisca (padrão), antonio, thalita.
O event loop e o import do edge_tts ficam vivos na thread `Edge`; cada frase é uma tarefa nele.
"""
from __future__ import annotations

import asyncio
import threading
import unicodedata
from pathlib import Path
from typing import Optional

VOZES = {"francisca": "pt-BR-FranciscaNeural", "antonio": "pt-BR-AntonioNeural", "thalita": "pt-BR-ThalitaMultilingualNeural"}
ORDEM = ["francisca", "antonio", "thalita"]


def _s(t: str) -> str:
    return "".join(c for c in unicodedata.normalize("NFD", t.lower()) if unicodedata.category(c) != "Mn")


def voz_pedida(texto: str, atual: str) -> Optional[str]:
    """"muda a voz" / "troca a voz para antonio" → o id da nova voz (a próxima da lista, se não disse qual); None se não era isso."""
    import re
    m = re.search(r"\b(?:muda|mudar|troca|trocar|mude|troque)\s+(?:a\s+)?voz(?:\s+(?:para|pra|pro|do|da|de)\s+(?:a\s+|o\s+)?(\w+))?", _s(texto))
    if not m:
        return None
    pedida = m.group(1)
    if pedida and pedida in VOZES:
        return pedida
    i = ORDEM.index(atual) if atual in ORDEM else -1
    return ORDEM[(i + 1) % len(ORDEM)]


class Edge:
    def __init__(self) -> None:
        self.ok = False
        self._loop: "asyncio.AbstractEventLoop | None" = None
        self._pronto = threading.Event()
        threading.Thread(target=self._rodar, daemon=True).start()

    def _rodar(self) -> None:
        try:
            import edge_tts  # noqa: F401
            self.ok = True
        except ImportError:
            self._pronto.set()
            return
        self._loop = asyncio.new_event_loop()
        self._pronto.set()
        self._loop.run_forever()

    async def _tarefa(self, texto: str, voz: str, saida: Path) -> bool:
        import edge_tts
        n = 0
        with open(saida, "wb") as f:
            async for c in edge_tts.Communicate(texto, voz, rate="+8%").stream():
                if c["type"] == "audio":
                    f.write(c["data"])
                    n += len(c["data"])
        return n > 0

    def sintetizar(self, texto: str, voz_id: str, saida: Path, limite: float = 15.0) -> bool:
        self._pronto.wait(5)
        if not self.ok or not self._loop:
            return False
        try:
            fut = asyncio.run_coroutine_threadsafe(self._tarefa(texto, VOZES.get(voz_id, VOZES["francisca"]), saida), self._loop)
            return bool(fut.result(limite))
        except Exception:
            return False

    def aquecer(self) -> None:
        """Uma frase curta descartada: abre DNS, TLS e o módulo antes da primeira fala de verdade."""
        import tempfile
        p = Path(tempfile.gettempdir()) / "primo-voz-aquece.mp3"
        self.sintetizar("oi", "francisca", p)
        try:
            p.unlink()
        except OSError:
            pass


def hora_falada(t) -> str:
    """"São 19 e 05." — o mesmo texto no app.py (resposta) e no motor (pré-voz do próximo minuto)."""
    return f"São {t.tm_hour} e {t.tm_min:02d}." if t.tm_min else f"São {t.tm_hour} horas em ponto."
