from __future__ import annotations

import threading
from dataclasses import dataclass
from typing import List, Optional, Tuple

import numpy as np
import sounddevice as sd


@dataclass
class RecordingConfig:
    samplerate: int = 16000
    channels: int = 1
    dtype: str = "float32"


class RecordingSession:
    def __init__(self, cfg: RecordingConfig | None = None) -> None:
        self.cfg = cfg or RecordingConfig()
        self._lock = threading.Lock()
        self._buffers: List[np.ndarray] = []
        self._stream: Optional[sd.InputStream] = None
        self._is_recording = False
        self._is_paused = False

    @property
    def is_recording(self) -> bool:
        return self._is_recording

    @property
    def is_paused(self) -> bool:
        return self._is_paused

    def _callback(self, indata: np.ndarray, frames: int, time, status) -> None:
        with self._lock:
            self._buffers.append(indata.copy())

    def start(self) -> None:
        if self._is_recording:
            return
        self._buffers = []
        self._stream = sd.InputStream(
            samplerate=self.cfg.samplerate,
            channels=self.cfg.channels,
            dtype=self.cfg.dtype,
            callback=self._callback,
        )
        self._stream.start()
        self._is_recording = True
        self._is_paused = False

    def pause(self) -> None:
        if not self._is_recording or self._is_paused:
            return
        if self._stream is not None:
            self._stream.stop()
            self._stream.close()
            self._stream = None
        self._is_paused = True

    def resume(self) -> None:
        if not self._is_recording or not self._is_paused:
            return
        self._stream = sd.InputStream(
            samplerate=self.cfg.samplerate,
            channels=self.cfg.channels,
            dtype=self.cfg.dtype,
            callback=self._callback,
        )
        self._stream.start()
        self._is_paused = False

    def stop(self) -> Tuple[np.ndarray, int]:
        if not self._is_recording:
            return np.zeros((0, self.cfg.channels), dtype=self.cfg.dtype), self.cfg.samplerate

        if self._stream is not None:
            self._stream.stop()
            self._stream.close()
            self._stream = None

        self._is_recording = False
        self._is_paused = False

        with self._lock:
            data = np.concatenate(self._buffers, axis=0) if self._buffers else np.zeros((0, self.cfg.channels), dtype=self.cfg.dtype)
        return data, self.cfg.samplerate
