"""Audio protocols defining contracts for audio components.

Define Protocol interfaces for audio capture, level metering, and buffering.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Protocol

from noteflow.config.constants import DEFAULT_SAMPLE_RATE

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

    from noteflow.infrastructure.audio.dto import (
        AudioDeviceInfo,
        AudioFrameCallback,
        TimestampedAudio,
    )


class AudioCapture(Protocol):
    """Protocol for audio input capture.

    Implementations should handle device enumeration, stream management,
    and device change detection.
    """

    def list_devices(self) -> list[AudioDeviceInfo]:
        """List available audio input devices.

        Returns:
            List of AudioDeviceInfo for all available input devices.
        """
        ...

    def start(
        self,
        device_id: int | None,
        on_frames: AudioFrameCallback,
        sample_rate: int = DEFAULT_SAMPLE_RATE,
        channels: int = 1,
        chunk_duration_ms: int = 100,
    ) -> None:
        """Start capturing audio from the specified device.

        Args:
            device_id: Device ID to capture from, or None for default device.
            on_frames: Callback receiving (frames, timestamp) for each chunk.
            sample_rate: Sample rate in Hz (default 16kHz for ASR).
            channels: Number of channels (default 1 for mono).
            chunk_duration_ms: Duration of each audio chunk in milliseconds.

        Raises:
            RuntimeError: If already capturing.
            ValueError: If device_id is invalid.
        """
        ...

    def stop(self) -> None:
        """Stop audio capture.

        Safe to call even if not capturing.
        """
        ...

    def is_capturing(self) -> bool:
        """Check if currently capturing audio.

        Returns:
            True if capture is active.
        """
        ...


class AudioLevelProvider(Protocol):
    """Protocol for computing audio levels (VU meter data)."""

    def get_rms(self, frames: NDArray[np.float32]) -> float:
        """Calculate RMS level from audio frames.

        Args:
            frames: Audio samples as float32 array (normalized -1.0 to 1.0).

        Returns:
            RMS level normalized to 0.0-1.0 range.
        """
        ...

    def get_db(self, frames: NDArray[np.float32]) -> float:
        """Calculate dB level from audio frames.

        Args:
            frames: Audio samples as float32 array (normalized -1.0 to 1.0).

        Returns:
            Level in dB (typically -60 to 0 range).
        """
        ...


class RingBuffer(Protocol):
    """Protocol for timestamped audio ring buffer.

    Ring buffers store recent audio with timestamps for ASR processing
    and playback sync.
    """

    def push(self, audio: TimestampedAudio) -> None:
        """Add audio to the buffer.

        Old audio is discarded if buffer exceeds max_duration.

        Args:
            audio: Timestamped audio chunk to add.
        """
        ...

    def get_window(self, duration_seconds: float) -> list[TimestampedAudio]:
        """Get the last N seconds of audio.

        Args:
            duration_seconds: How many seconds of audio to retrieve.

        Returns:
            List of TimestampedAudio chunks, ordered oldest to newest.
        """
        ...

    def clear(self) -> None:
        """Clear all audio from the buffer."""
        ...

    @property
    def duration(self) -> float:
        """Total duration of buffered audio in seconds."""
        ...

    @property
    def max_duration(self) -> float:
        """Maximum buffer duration in seconds."""
        ...


class AudioPlayback(Protocol):
    """Protocol for audio output playback.

    Implementations should handle output device management, playback state,
    and position tracking for sync with UI.
    """

    def play(self, audio: list[TimestampedAudio]) -> None:
        """Start playback of audio chunks.

        Args:
            audio: List of TimestampedAudio chunks to play, ordered oldest to newest.
        """
        ...

    def pause(self) -> None:
        """Pause playback.

        Safe to call even if not playing.
        """
        ...

    def resume(self) -> None:
        """Resume paused playback.

        No-op if not paused.
        """
        ...

    def stop(self) -> None:
        """Stop playback and reset position.

        Safe to call even if not playing.
        """
        ...

    def is_playing(self) -> bool:
        """Check if currently playing audio.

        Returns:
            True if playback is active (not paused or stopped).
        """
        ...

    @property
    def current_position(self) -> float:
        """Current playback position in seconds from start of loaded audio."""
        ...

    @property
    def total_duration(self) -> float:
        """Total duration of loaded audio in seconds."""
        ...
