"""
europa.core.audio
~~~~~~~~~~~~~~~~~
The Audio class — the core data structure of Europa.
Wraps a NumPy array with sample rate and metadata.
"""

import numpy as np
from dataclasses import dataclass, field
from typing import Optional, Dict, Any


@dataclass
class Audio:
    """
    Core audio container.

    Attributes:
        data        (np.ndarray) : Audio samples, shape (samples,) mono or (2, samples) stereo
        sr          (int)        : Sample rate in Hz
        path        (str|None)   : Original file path, if loaded from disk
        metadata    (dict)       : Arbitrary metadata (title, artist, duration, etc.)

    Examples:
        >>> import numpy as np
        >>> from europa import Audio
        >>> audio = Audio(data=np.zeros(44100), sr=44100)
        >>> audio.duration
        1.0
        >>> audio.is_stereo
        False
    """

    data: np.ndarray
    sr: int = 22050
    path: Optional[str] = None
    metadata: Dict[str, Any] = field(default_factory=dict)

    def __post_init__(self):
        self.data = np.asarray(self.data, dtype=np.float32)

    # ── Properties ────────────────────────────────────────────────────────────

    @property
    def duration(self) -> float:
        """Duration in seconds."""
        return self.samples / self.sr

    @property
    def samples(self) -> int:
        """Total number of samples (per channel)."""
        if self.data.ndim == 1:
            return len(self.data)
        return self.data.shape[1]

    @property
    def channels(self) -> int:
        """Number of channels (1 = mono, 2 = stereo)."""
        if self.data.ndim == 1:
            return 1
        return self.data.shape[0]

    @property
    def is_mono(self) -> bool:
        return self.channels == 1

    @property
    def is_stereo(self) -> bool:
        return self.channels == 2

    @property
    def rms(self) -> float:
        """Root-mean-square energy."""
        return float(np.sqrt(np.mean(self.data ** 2)))

    @property
    def peak(self) -> float:
        """Peak amplitude."""
        return float(np.max(np.abs(self.data)))

    @property
    def shape(self):
        return self.data.shape

    # ── Dunder helpers ────────────────────────────────────────────────────────

    def __len__(self):
        return self.samples

    def __repr__(self):
        ch = "mono" if self.is_mono else "stereo"
        return (
            f"<Audio | {ch} | sr={self.sr} Hz "
            f"| duration={self.duration:.3f}s "
            f"| samples={self.samples}>"
        )

    def __add__(self, other: "Audio") -> "Audio":
        """Concatenate two Audio objects."""
        if self.sr != other.sr:
            raise ValueError(
                f"Sample rates must match ({self.sr} vs {other.sr}). "
                "Use europa.resample() first."
            )
        new_data = np.concatenate([self.data, other.data], axis=-1)
        return Audio(data=new_data, sr=self.sr)

    def __getitem__(self, s) -> "Audio":
        """Slice audio by sample index: audio[start:end]"""
        return Audio(data=self.data[..., s], sr=self.sr, metadata=self.metadata.copy())

    # ── Convenience methods ───────────────────────────────────────────────────

    def slice_time(self, start: float, end: float) -> "Audio":
        """
        Slice audio by time in seconds.

        Args:
            start: Start time in seconds.
            end:   End time in seconds.

        Returns:
            Audio: Sliced audio segment.
        """
        s = int(start * self.sr)
        e = int(end * self.sr)
        return self[s:e]

    def copy(self) -> "Audio":
        """Return a deep copy of this Audio object."""
        return Audio(
            data=self.data.copy(),
            sr=self.sr,
            path=self.path,
            metadata=self.metadata.copy(),
        )

    def to_numpy(self) -> np.ndarray:
        """Return the raw NumPy array."""
        return self.data.copy()

    def info(self) -> Dict[str, Any]:
        """Return a summary dict of audio properties."""
        return {
            "sr": self.sr,
            "channels": self.channels,
            "samples": self.samples,
            "duration": self.duration,
            "rms": self.rms,
            "peak": self.peak,
            "dtype": str(self.data.dtype),
            "path": self.path,
        }
