"""Base classes for audio capture engines."""

from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Any, Dict, List, Optional

import numpy as np


@dataclass
class AudioDeviceInfo:
    """Information about an audio device."""

    index: int
    name: str
    is_input: bool
    is_loopback: bool
    channels: int
    default_samplerate: float


class AudioCaptureStreamBase(ABC):
    """Abstract base class for an audio capture session.

    An AudioCaptureStreamBase is a stateful session bound to a specific
    audio device. It holds device-specific resources and provides raw
    audio chunk reading.

    Created by AudioCaptureEngine.create_stream().

    Lifecycle:
        stream = engine.create_stream(device_index, is_loopback, config)
        stream.initialize()
        chunk = stream.read_chunk()
        stream.cleanup()

    Or via context manager:
        with engine.create_stream(device_index, is_loopback, config) as stream:
            chunk = stream.read_chunk()
    """

    def __init__(
        self,
        device_index: int,
        is_loopback: bool,
        config: Dict[str, Any],
    ):
        self.device_index = device_index
        self.is_loopback = is_loopback
        self.config = config
        self._initialized = False

    @property
    @abstractmethod
    def channels(self) -> int:
        """Number of audio channels in the stream."""
        pass

    @property
    @abstractmethod
    def sample_rate(self) -> int:
        """Sample rate of the audio stream."""
        pass

    @property
    @abstractmethod
    def chunk_size(self) -> int:
        """Number of frames per read_chunk() call."""
        pass

    @abstractmethod
    def initialize(self) -> None:
        """Initialize audio capture resources.

        Raises:
            RuntimeError: If initialization fails
        """
        pass

    @abstractmethod
    def cleanup(self) -> None:
        """Release all audio capture resources."""
        pass

    @abstractmethod
    def read_chunk(self) -> Optional[np.ndarray]:
        """Read one chunk of raw audio data.

        Returns:
            1-D float32 ndarray of interleaved samples (length = chunk_size * channels),
            or None if no data available.

        Raises:
            RuntimeError: If read fails
        """
        pass

    def __enter__(self):
        """Context manager entry — initialize stream."""
        self.initialize()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Context manager exit — cleanup stream."""
        self.cleanup()


class AudioCaptureEngine(ABC):
    """Abstract base class for audio capture engines.

    An AudioCaptureEngine is a stateless factory that knows about an audio
    capture technology. It can enumerate devices, check availability, and
    create AudioCaptureStreamBase instances.

    All methods are classmethods — no instance creation needed.
    """

    ENGINE_TYPE: str = "base"
    ENGINE_PRIORITY: int = 0

    @classmethod
    @abstractmethod
    def is_available(cls) -> bool:
        """Check if this engine is available on the current system."""
        pass

    @classmethod
    @abstractmethod
    def get_default_config(cls) -> Dict[str, Any]:
        """Get default configuration for this engine."""
        pass

    @classmethod
    @abstractmethod
    def enumerate_devices(cls) -> List[AudioDeviceInfo]:
        """Get list of available audio devices.

        Returns:
            List of AudioDeviceInfo objects

        Raises:
            RuntimeError: If unable to detect devices
        """
        pass

    @classmethod
    @abstractmethod
    def create_stream(
        cls,
        device_index: int,
        is_loopback: bool,
        config: Dict[str, Any],
    ) -> AudioCaptureStreamBase:
        """Create a capture stream for the specified device.

        Args:
            device_index: Index of audio device
            is_loopback: Whether to capture loopback audio
            config: Engine-specific configuration dict

        Returns:
            Uninitialized AudioCaptureStreamBase. Caller must call
            initialize() or use as context manager.
        """
        pass
