"""
Audio Codec Utilities

Converts between DVGateway's native slin16 format and float32 PCM
used by most AI STT/TTS APIs.

DVGateway audio format:
  - Sample rate: 16000 Hz
  - Bit depth: 16-bit signed integer
  - Endianness: little-endian
  - Frame size: 640 bytes = 320 samples = 20ms
  - Channels: 1 (mono)
"""

from __future__ import annotations

import math
import struct


def slin16_to_float32(buf: bytes) -> list[float]:
    """Convert slin16 bytes to normalized float32 list [-1.0, 1.0]."""
    num_samples = len(buf) // 2
    samples = struct.unpack(f"<{num_samples}h", buf[:num_samples * 2])
    return [s / 32768.0 for s in samples]


def float32_to_slin16(samples: list[float]) -> bytes:
    """Convert normalized float32 list [-1.0, 1.0] to slin16 bytes."""
    clamped = [max(-1.0, min(1.0, s)) for s in samples]
    int16_samples = [round(s * 32767) for s in clamped]
    return struct.pack(f"<{len(int16_samples)}h", *int16_samples)


# ─── μ-law lookup table ──────────────────────────────────────────────────

def _build_ulaw_to_linear_table() -> list[int]:
    """Build μ-law to linear 16-bit PCM lookup table (256 entries)."""
    table: list[int] = []
    for i in range(256):
        u = ~i & 0xFF
        sign = u & 0x80
        exponent = (u >> 4) & 0x07
        mantissa = u & 0x0F
        sample = ((mantissa << 3) + 0x84) << exponent
        sample -= 0x84
        table.append(-sample if sign != 0 else sample)
    return table


_ULAW_TO_LINEAR = _build_ulaw_to_linear_table()


def _linear_to_ulaw(sample: int) -> int:
    """Convert a single linear 16-bit sample to μ-law."""
    BIAS = 0x84
    CLIP = 32635

    sign = 0x80 if sample < 0 else 0
    if sample < 0:
        sample = -sample
    if sample > CLIP:
        sample = CLIP

    sample += BIAS

    exponent = 7
    exp_mask = 0x4000
    while (sample & exp_mask) == 0 and exponent > 0:
        exponent -= 1
        exp_mask >>= 1

    mantissa = (sample >> (exponent + 3)) & 0x0F
    result = ~(sign | (exponent << 4) | mantissa)
    return result & 0xFF


def ulaw_to_slin16(ulaw: bytes) -> bytes:
    """Convert μ-law (ulaw) bytes to slin16 bytes."""
    int16_samples = [_ULAW_TO_LINEAR[b] for b in ulaw]
    return struct.pack(f"<{len(int16_samples)}h", *int16_samples)


def slin16_to_ulaw(slin16: bytes) -> bytes:
    """Convert slin16 bytes to μ-law (ulaw) bytes."""
    num_samples = len(slin16) // 2
    samples = struct.unpack(f"<{num_samples}h", slin16[:num_samples * 2])
    return bytes(_linear_to_ulaw(s) for s in samples)


def resample(samples: list[float], from_rate: int, to_rate: int) -> list[float]:
    """
    Resample float32 samples using linear interpolation.

    Args:
        samples: Input samples
        from_rate: Source sample rate (e.g. 16000)
        to_rate: Target sample rate (e.g. 24000)
    """
    if from_rate == to_rate:
        return samples

    ratio = from_rate / to_rate
    out_length = round(len(samples) / ratio)
    out: list[float] = []

    for i in range(out_length):
        src_pos = i * ratio
        src_idx = int(src_pos)
        frac = src_pos - src_idx

        s0 = samples[src_idx] if src_idx < len(samples) else 0.0
        s1 = samples[min(src_idx + 1, len(samples) - 1)] if src_idx + 1 <= len(samples) - 1 else s0
        out.append(s0 + frac * (s1 - s0))

    return out


def calculate_rms(samples: list[float]) -> float:
    """Calculate RMS (Root Mean Square) amplitude. Returns [0.0, 1.0]."""
    if not samples:
        return 0.0
    sum_sq = sum(s * s for s in samples)
    return math.sqrt(sum_sq / len(samples))


def detect_voice_activity(samples: list[float], threshold: float = 0.01) -> bool:
    """
    Simple Voice Activity Detection based on RMS threshold.

    Args:
        samples: Float32 samples
        threshold: RMS threshold (default: 0.01, i.e. -40 dBFS)
    """
    return calculate_rms(samples) > threshold
