"""
europa.core.load
~~~~~~~~~~~~~~~~
Audio file loading with broad format support.
Supports: WAV, MP3, FLAC, OGG, AIFF, AU, and more via soundfile + audioread fallback.
"""

import numpy as np
from pathlib import Path
from typing import Optional, Tuple, Union

from .audio import Audio


def load(
    path: Union[str, Path],
    sr: Optional[int] = None,
    mono: bool = True,
    offset: float = 0.0,
    duration: Optional[float] = None,
    dtype: np.dtype = np.float32,
) -> Audio:
    """
    Load an audio file from disk.

    Args:
        path        : Path to the audio file.
        sr          : Target sample rate. If None, uses the file's native rate.
        mono        : If True, convert to mono. Default True.
        offset      : Start reading at this time offset (seconds).
        duration    : Only load this many seconds. None = full file.
        dtype       : NumPy dtype for samples. Default float32.

    Returns:
        Audio: Loaded audio object.

    Raises:
        FileNotFoundError : If the file does not exist.
        RuntimeError      : If the file cannot be decoded.

    Examples:
        >>> audio = europa.load("song.mp3")
        >>> audio = europa.load("clip.wav", sr=44100, mono=False)
        >>> audio = europa.load("long.flac", offset=30.0, duration=10.0)
    """
    try:
        import soundfile as sf
    except ImportError:
        raise ImportError(
            "soundfile is required for loading audio. "
            "Install it with: pip install soundfile"
        )

    path = Path(path)
    if not path.exists():
        raise FileNotFoundError(f"Audio file not found: {path}")

    # ── Load via soundfile ──────────────────────────────────────────────────
    try:
        with sf.SoundFile(str(path)) as f:
            native_sr = f.samplerate
            native_channels = f.channels

            # Seek to offset
            if offset > 0:
                f.seek(int(offset * native_sr))

            # Read frames
            frames = -1 if duration is None else int(duration * native_sr)
            data = f.read(frames=frames, dtype=dtype, always_2d=True)  # (samples, channels)

        # Transpose to (channels, samples)
        data = data.T

    except Exception as sf_err:
        # Fallback to audioread for MP3 and other formats
        data, native_sr = _load_audioread(path, offset, duration, dtype)
        native_channels = data.shape[0] if data.ndim > 1 else 1

    # ── Convert to mono if requested ────────────────────────────────────────
    if mono and data.ndim == 2:
        data = np.mean(data, axis=0)
    elif not mono and data.ndim == 1:
        data = np.stack([data, data], axis=0)

    # ── Resample if needed ──────────────────────────────────────────────────
    target_sr = sr if sr is not None else native_sr
    if target_sr != native_sr:
        from .convert import _resample_array
        data = _resample_array(data, native_sr, target_sr)

    return Audio(
        data=data,
        sr=target_sr,
        path=str(path),
        metadata={"original_sr": native_sr, "original_channels": native_channels},
    )


def _load_audioread(
    path: Path,
    offset: float,
    duration: Optional[float],
    dtype: np.dtype,
) -> Tuple[np.ndarray, int]:
    """Fallback loader using audioread (supports MP3, AAC, etc.)."""
    try:
        import audioread
    except ImportError:
        raise ImportError(
            "Could not load audio file. For MP3 support install audioread: "
            "pip install audioread"
        )

    blocks = []
    sr = None

    with audioread.audio_open(str(path)) as f:
        sr = f.samplerate
        channels = f.channels
        offset_frames = int(offset * sr)
        max_frames = int(duration * sr) if duration is not None else None
        frame_count = 0

        for block in f:
            raw = np.frombuffer(block, dtype=np.int16).astype(dtype) / 32768.0
            raw = raw.reshape(-1, channels).T

            if frame_count < offset_frames:
                frame_count += raw.shape[1]
                continue

            if max_frames is not None and frame_count >= offset_frames + max_frames:
                break

            blocks.append(raw)
            frame_count += raw.shape[1]

    if not blocks:
        raise RuntimeError(f"Could not read audio from: {path}")

    data = np.concatenate(blocks, axis=1)
    return data, sr
