import collections

import numpy as np
import sounddevice as sd

from .model import SAMPLE_RATE

FRAME_SIZE = 800          # 50ms at 16kHz
PRE_ROLL_FRAMES = 6       # ~0.3s of audio before speech onset
DEFAULT_SILENCE_FRAMES = 16  # ~0.8s of silence to end a segment
SPEECH_ONSET_FRAMES = 3   # ~150ms of speech to trigger
MAX_SPEECH_SECONDS = 30   # force chunk boundary
MIN_SPEECH_SECONDS = 0.3  # discard segments shorter than this (mic bumps, clicks)


def pause_seconds_to_frames(seconds: float) -> int:
    return max(1, round(seconds / (FRAME_SIZE / SAMPLE_RATE)))


def calibrate_silence(duration=0.5):
    print("Calibrating silence threshold...")
    audio = sd.rec(int(duration * SAMPLE_RATE), samplerate=SAMPLE_RATE, channels=1, dtype="float32")
    sd.wait()
    rms = np.sqrt(np.mean(audio ** 2))
    threshold = max(rms * 3, 0.01)
    print(f"  Ambient RMS: {rms:.4f}, threshold: {threshold:.4f}")
    return threshold


class VADStateMachine:
    def __init__(self, threshold, silence_frames=DEFAULT_SILENCE_FRAMES):
        self.threshold = threshold
        self.silence_limit = silence_frames
        self.speaking = False
        self.speech_frames = 0
        self.silence_frames = 0
        self.pre_roll = collections.deque(maxlen=PRE_ROLL_FRAMES)
        self.segment = []
        self.segment_start_time = 0.0

    def process_frame(self, frame, elapsed_time):
        """Process one 50ms frame. Returns a (start_time, audio_array) tuple when a
        complete speech segment is detected, otherwise None."""
        rms = np.sqrt(np.mean(frame ** 2))
        is_loud = rms > self.threshold

        if not self.speaking:
            self.pre_roll.append(frame)

            if is_loud:
                self.speech_frames += 1
                if self.speech_frames >= SPEECH_ONSET_FRAMES:
                    self.speaking = True
                    self.silence_frames = 0
                    self.segment = list(self.pre_roll)
                    self.segment_start_time = max(0.0, elapsed_time - len(self.pre_roll) * FRAME_SIZE / SAMPLE_RATE)
                    self.pre_roll = collections.deque(maxlen=PRE_ROLL_FRAMES)
            else:
                self.speech_frames = 0
            return None

        self.segment.append(frame)

        if is_loud:
            self.silence_frames = 0
        else:
            self.silence_frames += 1

        segment_duration = len(self.segment) * FRAME_SIZE / SAMPLE_RATE
        if self.silence_frames >= self.silence_limit or segment_duration >= MAX_SPEECH_SECONDS:
            speech_duration = segment_duration - self.silence_frames * FRAME_SIZE / SAMPLE_RATE
            result = None
            if speech_duration >= MIN_SPEECH_SECONDS:
                result = (self.segment_start_time, np.concatenate(self.segment))
            self.speaking = False
            self.speech_frames = 0
            self.silence_frames = 0
            self.segment = []
            self.pre_roll = collections.deque(maxlen=PRE_ROLL_FRAMES)
            return result

        return None
