"""Speech-to-text mixin — microphone capture and Whisper transcription."""

from __future__ import annotations

import logging
import sys
import time

import numpy as np

from timmy.voice.audio_io import DEFAULT_CHANNELS, _rms

logger = logging.getLogger(__name__)


class STTMixin:
    """Mixin providing STT methods for :class:`VoiceLoop`."""

    def _load_whisper(self):
        """Load Whisper model (lazy, first use only)."""
        if self._whisper_model is not None:
            return
        import whisper

        logger.info("Loading Whisper model: %s", self.config.whisper_model)
        self._whisper_model = whisper.load_model(self.config.whisper_model)
        logger.info("Whisper model loaded.")

    def _record_utterance(self) -> np.ndarray | None:
        """Record from microphone until silence is detected."""
        import sounddevice as sd

        sr = self.config.sample_rate
        block_size = int(sr * 0.1)
        silence_blocks = int(self.config.silence_duration / 0.1)
        min_blocks = int(self.config.min_utterance / 0.1)
        max_blocks = int(self.config.max_utterance / 0.1)

        sys.stdout.write("\n  🎤 Listening... (speak now)\n")
        sys.stdout.flush()

        with sd.InputStream(
            samplerate=sr,
            channels=DEFAULT_CHANNELS,
            dtype="float32",
            blocksize=block_size,
        ) as stream:
            chunks = self._capture_audio_blocks(stream, block_size, silence_blocks, max_blocks)

        return self._finalize_utterance(chunks, min_blocks, sr)

    def _capture_audio_blocks(
        self,
        stream,
        block_size: int,
        silence_blocks: int,
        max_blocks: int,
    ) -> list[np.ndarray]:
        """Read audio blocks from *stream* until silence or max length."""
        chunks: list[np.ndarray] = []
        silent_count = 0
        recording = False

        while self._running:
            block, overflowed = stream.read(block_size)
            if overflowed:
                logger.debug("Audio buffer overflowed")

            rms = _rms(block)

            if not recording:
                if rms > self.config.silence_threshold:
                    recording = True
                    silent_count = 0
                    chunks.append(block.copy())
                    sys.stdout.write("  📢 Recording...\r")
                    sys.stdout.flush()
            else:
                chunks.append(block.copy())
                if rms < self.config.silence_threshold:
                    silent_count += 1
                else:
                    silent_count = 0
                if silent_count >= silence_blocks:
                    break
                if len(chunks) >= max_blocks:
                    logger.info("Max utterance length reached, stopping.")
                    break

        return chunks

    @staticmethod
    def _finalize_utterance(
        chunks: list[np.ndarray], min_blocks: int, sample_rate: int
    ) -> np.ndarray | None:
        """Concatenate recorded chunks and report duration."""
        if not chunks or len(chunks) < min_blocks:
            return None

        audio = np.concatenate(chunks, axis=0).flatten()
        duration = len(audio) / sample_rate
        sys.stdout.write(f"  ✂️  Captured {duration:.1f}s of audio\n")
        sys.stdout.flush()
        return audio

    def _transcribe(self, audio: np.ndarray) -> str:
        """Transcribe audio using local Whisper model."""
        self._load_whisper()

        sys.stdout.write("  🧠 Transcribing...\r")
        sys.stdout.flush()

        t0 = time.monotonic()
        result = self._whisper_model.transcribe(audio, language="en", fp16=False)
        elapsed = time.monotonic() - t0

        text = result["text"].strip()
        logger.info("Whisper transcribed in %.1fs: '%s'", elapsed, text[:80])
        return text
