"""Correção de nível (loudness) na saída do TTS, no worker.

O TTS costuma sair num nível baixo demais para telefonia. Em vez de multiplicar
o áudio às cegas no fim do pipeline (o que satura/clipa nos picos), aqui aplica-se
um makeup gain com um limiter soft-knee (tanh) já na saída do TTS — antes de
publicar no room. Para níveis normais de fala o ganho é praticamente linear; nos
picos ele comprime suavemente em direção ao teto, sem hard clipping.

    y = ceiling * tanh(gain * x / ceiling)

Config por ambiente:
    TTS_OUTPUT_GAIN         makeup linear (1.0 = desligado; default 1.0)
    TTS_OUTPUT_CEILING_DBFS teto do limiter em dBFS (default -1.0)
"""

from __future__ import annotations

import math
import os
from typing import Any, Optional

import numpy as np


def _safe_float(value: Optional[str], default: float) -> float:
    if value is None or not str(value).strip():
        return default
    try:
        parsed = float(value)
    except (TypeError, ValueError):
        return default

    if not math.isfinite(parsed):
        return default

    return parsed


def _dbfs_to_linear(dbfs: float) -> float:
    return float(10.0 ** (dbfs / 20.0))


class SoftClipGain:
    """Makeup gain + limiter soft-knee (tanh) para PCM16 mono little-endian.

    ``gain`` é o ganho linear aplicado à fala em nível normal; ``ceiling`` é o
    teto (0..1 do fundo de escala) que os picos nunca ultrapassam.
    """

    def __init__(self, gain: float, ceiling: float = 0.891) -> None:
        try:
            normalized_gain = float(gain)
        except (TypeError, ValueError):
            normalized_gain = 1.0
        self.gain = (
            min(10.0, max(0.0, normalized_gain))
            if math.isfinite(normalized_gain)
            else 1.0
        )
        self.ceiling = float(min(1.0, max(0.05, ceiling)))

    @property
    def enabled(self) -> bool:
        return self.gain != 1.0

    def process(self, pcm: bytes) -> bytes:
        if not self.enabled or not pcm:
            return pcm

        count = len(pcm) // 2
        if count == 0:
            return pcm

        x = np.frombuffer(pcm, dtype="<i2", count=count).astype(np.float32) / 32768.0
        y = self.ceiling * np.tanh((self.gain / self.ceiling) * x)
        out = np.clip(np.rint(y * 32768.0), -32768.0, 32767.0).astype("<i2")

        # preserva um eventual byte ímpar solto (não deveria ocorrer com PCM16)
        return out.tobytes() + pcm[count * 2:]


def tts_output_gain_from_env() -> SoftClipGain:
    gain = min(10.0, max(0.0, _safe_float(os.getenv("TTS_OUTPUT_GAIN"), 1.0)))
    ceiling_dbfs = min(
        0.0, max(-60.0, _safe_float(os.getenv("TTS_OUTPUT_CEILING_DBFS"), -1.0))
    )
    return SoftClipGain(gain=gain, ceiling=_dbfs_to_linear(ceiling_dbfs))


class GainEmitter:
    """Proxy sobre um ``tts.AudioEmitter`` que aplica ``SoftClipGain`` em cada
    ``push()``. Todo o resto (initialize/flush/start_segment/end_segment/...) é
    encaminhado sem alteração para o emitter real.
    """

    def __init__(self, inner: Any, gain: SoftClipGain) -> None:
        self._inner = inner
        self._gain = gain

    def push(self, data: bytes) -> None:
        self._inner.push(self._gain.process(data))

    def __getattr__(self, name: str) -> Any:
        return getattr(self._inner, name)
