"""Audio processing helper functions for streaming transcription.

Extracted from StreamingMixin to reduce class size and improve testability.
These are pure functions that operate on audio data without state.
"""

from __future__ import annotations

import struct

import numpy as np
from numpy.typing import NDArray

from noteflow.infrastructure.logging import get_logger

logger = get_logger(__name__)


def resample_audio(
    audio: NDArray[np.float32],
    src_rate: int,
    dst_rate: int,
) -> NDArray[np.float32]:
    """Resample audio using linear interpolation.

    Args:
        audio: Input audio samples.
        src_rate: Source sample rate in Hz.
        dst_rate: Target sample rate in Hz.

    Returns:
        Resampled audio array.
    """
    if src_rate == dst_rate or audio.size == 0:
        return audio

    ratio = dst_rate / src_rate
    new_length = round(audio.shape[0] * ratio)
    if new_length <= 0:
        return np.array([], dtype=np.float32)

    old_indices = np.arange(audio.shape[0])
    new_indices = np.arange(new_length) / ratio
    return np.interp(new_indices, old_indices, audio).astype(np.float32)


def decode_audio_chunk(audio_data: bytes) -> NDArray[np.float32] | None:
    """Decode audio data bytes to numpy array.

    Args:
        audio_data: Raw audio bytes in float32 format.

    Returns:
        Decoded audio array, or None if decoding fails.
    """
    if not audio_data:
        return None
    try:
        return np.frombuffer(audio_data, dtype=np.float32)
    except (ValueError, struct.error) as e:
        logger.warning("Failed to decode audio chunk: %s", e)
        return None


def convert_audio_format(
    audio: NDArray[np.float32],
    sample_rate: int,
    channels: int,
    target_sample_rate: int,
) -> NDArray[np.float32]:
    """Downmix and resample audio to target format.

    Args:
        audio: Input audio samples.
        sample_rate: Source sample rate in Hz.
        channels: Number of audio channels.
        target_sample_rate: Target sample rate in Hz.

    Returns:
        Converted audio array.

    Raises:
        ValueError: If buffer size is not divisible by channel count.
    """
    if channels > 1:
        if audio.size % channels != 0:
            raise ValueError("Audio buffer size is not divisible by channel count")
        audio = audio.reshape(-1, channels).mean(axis=1)

    if sample_rate != target_sample_rate:
        audio = resample_audio(audio, sample_rate, target_sample_rate)

    return audio


def validate_stream_format(
    sample_rate: int,
    channels: int,
    default_sample_rate: int,
    supported_sample_rates: frozenset[int],
    existing_format: tuple[int, int] | None,
) -> tuple[int, int]:
    """Validate and normalize stream audio format.

    Args:
        sample_rate: Requested sample rate (0 means use default).
        channels: Number of audio channels (0 means mono).
        default_sample_rate: Default sample rate if none specified.
        supported_sample_rates: Set of supported sample rates.
        existing_format: Previously set format for this stream, if any.

    Returns:
        Tuple of (normalized_rate, normalized_channels).

    Raises:
        ValueError: If sample rate is unsupported or format changes mid-stream.
    """
    normalized_rate = sample_rate or default_sample_rate
    normalized_channels = channels or 1

    if normalized_rate not in supported_sample_rates:
        raise ValueError(
            f"Unsupported sample_rate {normalized_rate}; "
            f"supported: {supported_sample_rates}"
        )
    if normalized_channels < 1:
        raise ValueError("channels must be >= 1")

    if existing_format and existing_format != (normalized_rate, normalized_channels):
        raise ValueError("Stream audio format cannot change mid-stream")

    return normalized_rate, normalized_channels
