"""sons.py — os efeitos sonoros de interface do Primo, sintetizados (sem arquivo de terceiros).

Curtos (60-300 ms), de volume baixo, notas de uma escala pentatônica com
decaimento suave — nada de bipe de alarme. São gerados uma vez em
~/.primocode/voz-sons/*.wav e tocados com o tocador do sistema. Regra: nunca em
cima da fala (quem chama passa `falando=True`) e desligável (voz.json "sons":
false, ou PRIMOCODE_SEM_SONS=1).
"""
from __future__ import annotations

import math
import os
import shutil
import struct
import subprocess
import sys
import wave
from pathlib import Path
from typing import Dict, List, Tuple

TAXA = 22050
VOLUME = 0.22     # 0..1 do arquivo; o tocador ainda baixa mais

# nome → lista de (frequência Hz, início s, duração s, ganho)
RECEITAS: Dict[str, List[Tuple[float, float, float, float]]] = {
    "boot":        [(261.6, 0.00, 0.30, 0.5), (392.0, 0.18, 0.30, 0.55), (523.3, 0.36, 0.30, 0.6),
                    (784.0, 0.54, 0.30, 0.7), (1046.5, 0.72, 0.60, 0.9)],
    "acordou":     [(523.3, 0.00, 0.12, 0.8), (784.0, 0.09, 0.22, 1.0)],
    "ouvindo":     [(880.0, 0.00, 0.07, 0.6)],
    "entendeu":    [(659.3, 0.00, 0.07, 0.7), (880.0, 0.06, 0.12, 0.8)],
    "pensando":    [(392.0, 0.00, 0.18, 0.35)],
    "pronto":      [(784.0, 0.00, 0.10, 0.7), (1046.5, 0.08, 0.26, 0.9)],
    "erro":        [(311.1, 0.00, 0.14, 0.9), (233.1, 0.12, 0.26, 0.9)],
    "painel_abre": [(587.3, 0.00, 0.06, 0.6), (880.0, 0.05, 0.10, 0.7)],
    "painel_fecha": [(880.0, 0.00, 0.06, 0.6), (587.3, 0.05, 0.10, 0.7)],
}


def sintetizar(nome: str) -> List[int]:
    notas = RECEITAS[nome]
    total = max(i + d for _, i, d, _ in notas) + 0.05
    n = int(total * TAXA)
    amostras = [0.0] * n
    for f, ini, dur, g in notas:
        a = int(ini * TAXA)
        for k in range(int(dur * TAXA)):
            if a + k >= n:
                break
            t = k / TAXA
            ataque = min(1.0, t / 0.006)
            queda = math.exp(-t * (5.0 / dur))
            v = math.sin(math.tau * f * t) + 0.25 * math.sin(math.tau * 2 * f * t)
            amostras[a + k] += v * ataque * queda * g
    pico = max(1e-9, max(abs(x) for x in amostras))
    return [int(32767 * VOLUME * x / max(pico, 1.0)) for x in amostras]


def pasta() -> Path:
    return Path(os.environ.get("PRIMOCODE_SONS_DIR") or (Path.home() / ".primocode" / "voz-sons"))


def arquivo(nome: str) -> Path:
    p = pasta() / f"{nome}.wav"
    if not p.exists():
        p.parent.mkdir(parents=True, exist_ok=True)
        with wave.open(str(p), "wb") as w:
            w.setnchannels(1)
            w.setsampwidth(2)
            w.setframerate(TAXA)
            w.writeframes(b"".join(struct.pack("<h", x) for x in sintetizar(nome)))
    return p


class Sons:
    def __init__(self, ligado: bool = True) -> None:
        self.ligado = ligado and os.environ.get("PRIMOCODE_SEM_SONS") != "1"
        self.cmd = None
        if self.ligado:
            if sys.platform == "darwin":
                self.cmd = ["afplay", "-v", "0.5"]
            else:
                for c in (["paplay"], ["aplay", "-q"], ["ffplay", "-nodisp", "-autoexit", "-loglevel", "quiet"]):
                    if shutil.which(c[0]):
                        self.cmd = c
                        break
            self.ligado = bool(self.cmd)

    def tocar(self, nome: str, falando: bool = False) -> bool:
        """Toca e volta na hora. Em cima da fala (falando=True) não toca."""
        if not self.ligado or falando or nome not in RECEITAS:
            return False
        try:
            subprocess.Popen(self.cmd + [str(arquivo(nome))], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            return True
        except Exception:
            return False
