"""Streaming Voice Activity Detection.

Provides real-time speech detection for audio streams.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Protocol

from noteflow.config.constants import DEFAULT_SAMPLE_RATE
from noteflow.infrastructure.audio import compute_rms

if TYPE_CHECKING:
    import numpy as np
    from numpy.typing import NDArray


class VadEngine(Protocol):
    """Protocol for VAD engine implementations."""

    def process(self, audio: NDArray[np.float32]) -> bool:
        """Process audio chunk and return speech detection result.

        Args:
            audio: Audio samples (float32, mono).

        Returns:
            True if speech detected, False otherwise.
        """
        ...

    def reset(self) -> None:
        """Reset VAD state."""
        ...


@dataclass
class EnergyVadConfig:
    """Configuration for energy-based VAD."""

    # Speech detection threshold (RMS energy)
    speech_threshold: float = 0.01
    # Silence threshold (lower than speech for hysteresis)
    silence_threshold: float = 0.005
    # Minimum consecutive speech frames to confirm speech
    min_speech_frames: int = 2
    # Minimum consecutive silence frames to confirm silence
    min_silence_frames: int = 3


@dataclass
class EnergyVad:
    """Simple energy-based Voice Activity Detection.

    Uses RMS energy with hysteresis for robust detection.
    Suitable for clean audio; use silero-vad for noisy environments.
    """

    config: EnergyVadConfig = field(default_factory=EnergyVadConfig)

    # Internal state
    _is_speech: bool = field(default=False, init=False)
    _speech_frame_count: int = field(default=0, init=False)
    _silence_frame_count: int = field(default=0, init=False)

    def process(self, audio: NDArray[np.float32]) -> bool:
        """Process audio chunk and detect speech.

        Uses RMS energy with hysteresis to detect speech.
        State transitions require consecutive frames above/below threshold.

        Args:
            audio: Audio samples (float32, mono, normalized to [-1, 1]).

        Returns:
            True if speech detected, False for silence.
        """
        energy = compute_rms(audio)

        if self._is_speech:
            # Currently in speech - check for silence
            if energy < self.config.silence_threshold:
                self._silence_frame_count += 1
                self._speech_frame_count = 0
                if self._silence_frame_count >= self.config.min_silence_frames:
                    self._is_speech = False
            else:
                self._silence_frame_count = 0
        elif energy > self.config.speech_threshold:
            self._speech_frame_count += 1
            self._silence_frame_count = 0
            if self._speech_frame_count >= self.config.min_speech_frames:
                self._is_speech = True
        else:
            self._speech_frame_count = 0

        return self._is_speech

    def reset(self) -> None:
        """Reset VAD state to initial values."""
        self._is_speech = False
        self._speech_frame_count = 0
        self._silence_frame_count = 0

    @property
    def is_speech(self) -> bool:
        """Return whether VAD is currently in speech state."""
        return self._is_speech

    @property
    def speech_frame_count(self) -> int:
        """Return the consecutive speech frame count."""
        return self._speech_frame_count

    @property
    def silence_frame_count(self) -> int:
        """Return the consecutive silence frame count."""
        return self._silence_frame_count


@dataclass
class StreamingVad:
    """Streaming VAD wrapper with configurable backend.

    Wraps VAD engines to provide a unified streaming interface.
    """

    engine: VadEngine = field(default_factory=EnergyVad)
    sample_rate: int = DEFAULT_SAMPLE_RATE

    def process_chunk(self, audio: NDArray[np.float32]) -> bool:
        """Process audio chunk through VAD engine.

        Args:
            audio: Audio samples (float32, mono).

        Returns:
            True if speech detected, False otherwise.
        """
        return self.engine.process(audio)

    def reset(self) -> None:
        """Reset VAD state."""
        self.engine.reset()
