"""Thread-safe App Mode voice runtime state and control signals."""

from __future__ import annotations

from collections import deque
from threading import Lock
import time
from typing import Any


class VoiceRuntimeController:
    """Shared state between voice loop and Assistant UI routes."""

    def __init__(self) -> None:
        self._lock = Lock()
        self._status: dict[str, Any] = {
            "state": "idle",
            "backend": "unknown",
            "capture_backend": "unknown",
            "input_device_name": "none",
            "input_device_index": None,
            "input_sample_rate": None,
            "input_channels": 1,
            "input_dtype": "int16",
            "microphone_available": False,
            "microphone_error": "No microphone detected / input unavailable",
            "input_level_rms": 0.0,
            "input_level_peak": 0,
            "input_level_norm": 0.0,
            "worker_active": False,
            "worker_started_ts": None,
            "worker_last_seen_ts": None,
            "worker_error": None,
            "updated_ts": time.time(),
        }
        self._mic_retry_requests = 0
        self._is_listening = False
        self._typed_prompts: deque[str] = deque()
        self._active_cycle_id: str | None = None
        self._active_cycle_state = "idle"
        self._stop_response_requested = False
        self._stop_response_request_count = 0

    def reset(self) -> None:
        """Reset transient state for a fresh app-mode run."""
        with self._lock:
            self._status = {
                "state": "idle",
                "backend": "unknown",
                "capture_backend": "unknown",
                "input_device_name": "none",
                "input_device_index": None,
                "input_sample_rate": None,
                "input_channels": 1,
                "input_dtype": "int16",
                "microphone_available": False,
                "microphone_error": "No microphone detected / input unavailable",
                "input_level_rms": 0.0,
                "input_level_peak": 0,
                "input_level_norm": 0.0,
                "worker_active": False,
                "worker_started_ts": None,
                "worker_last_seen_ts": None,
                "worker_error": None,
                "updated_ts": time.time(),
            }
            self._mic_retry_requests = 0
            self._is_listening = False
            self._typed_prompts.clear()
            self._active_cycle_id = None
            self._active_cycle_state = "idle"
            self._stop_response_requested = False
            self._stop_response_request_count = 0

    def update_status(self, **fields: Any) -> dict[str, Any]:
        """Merge status fields and return a snapshot."""
        with self._lock:
            self._status.update(fields)
            self._status["updated_ts"] = time.time()
            if self._status.get("worker_active"):
                self._status["worker_last_seen_ts"] = self._status["updated_ts"]
            return dict(self._status)

    def snapshot(self) -> dict[str, Any]:
        """Return current voice status snapshot."""
        with self._lock:
            return dict(self._status)

    def set_worker_active(self, active: bool, *, error: str | None = None) -> dict[str, Any]:
        """Set voice worker active/inactive state and return a snapshot."""
        with self._lock:
            now = time.time()
            is_active = bool(active)
            self._status["worker_active"] = is_active
            self._status["updated_ts"] = now
            self._status["worker_last_seen_ts"] = now
            if is_active:
                if self._status.get("worker_started_ts") is None:
                    self._status["worker_started_ts"] = now
                self._status["worker_error"] = None
            else:
                self._is_listening = False
                self._active_cycle_id = None
                self._active_cycle_state = "idle"
                self._stop_response_requested = False
                normalized_error = str(error).strip() if error is not None else ""
                self._status["worker_error"] = normalized_error or None
            return dict(self._status)

    def is_worker_active(self) -> bool:
        """Return True when voice worker loop is currently running."""
        with self._lock:
            return bool(self._status.get("worker_active"))

    def enqueue_typed_prompt(self, prompt: str) -> int:
        """Queue one typed prompt for voice-loop processing."""
        text = str(prompt or "").strip()
        if not text:
            return 0
        with self._lock:
            self._typed_prompts.append(text)
            return len(self._typed_prompts)

    def pop_typed_prompt(self) -> str | None:
        """Pop one queued typed prompt."""
        with self._lock:
            if not self._typed_prompts:
                return None
            return self._typed_prompts.popleft()

    def request_microphone_retry(self) -> None:
        """Request immediate microphone probe retry."""
        with self._lock:
            self._mic_retry_requests += 1

    def consume_microphone_retry_request(self) -> bool:
        """Consume one pending microphone retry request."""
        with self._lock:
            if self._mic_retry_requests <= 0:
                return False
            self._mic_retry_requests -= 1
            return True

    def start_listening(self) -> bool:
        """Enable push-to-talk listening mode."""
        with self._lock:
            changed = not self._is_listening
            self._is_listening = True
            return changed

    def stop_listening(self) -> bool:
        """Disable push-to-talk listening mode."""
        with self._lock:
            changed = bool(self._is_listening)
            self._is_listening = False
            return changed

    def is_listening(self) -> bool:
        """Return current push-to-talk listening state."""
        with self._lock:
            return bool(self._is_listening)

    def set_active_cycle(self, cycle_id: str, *, state: str = "thinking") -> None:
        """Mark one assistant response cycle as active."""
        normalized_cycle = str(cycle_id or "").strip()
        normalized_state = str(state or "thinking").strip().lower() or "thinking"
        if not normalized_cycle:
            return
        with self._lock:
            self._active_cycle_id = normalized_cycle
            self._active_cycle_state = normalized_state
            self._stop_response_requested = False

    def update_active_cycle_state(self, *, cycle_id: str | None = None, state: str) -> None:
        """Update current active-cycle state when IDs match."""
        normalized_state = str(state or "").strip().lower()
        if not normalized_state:
            return
        with self._lock:
            if self._active_cycle_id is None:
                return
            if cycle_id is not None and str(cycle_id).strip() and str(cycle_id).strip() != self._active_cycle_id:
                return
            self._active_cycle_state = normalized_state

    def clear_active_cycle(self, *, cycle_id: str | None = None) -> None:
        """Clear active cycle tracking and stop-request flags."""
        with self._lock:
            if self._active_cycle_id is None:
                return
            if cycle_id is not None and str(cycle_id).strip() and str(cycle_id).strip() != self._active_cycle_id:
                return
            self._active_cycle_id = None
            self._active_cycle_state = "idle"
            self._stop_response_requested = False

    def active_cycle_snapshot(self) -> dict[str, Any]:
        """Return active-cycle and stop-request state snapshot."""
        with self._lock:
            return {
                "cycle_id": self._active_cycle_id,
                "state": self._active_cycle_state,
                "stop_requested": bool(self._stop_response_requested),
                "stop_request_count": int(self._stop_response_request_count),
            }

    def request_response_stop(self) -> dict[str, Any]:
        """Request interruption of the currently active response cycle."""
        with self._lock:
            cycle_id = self._active_cycle_id
            cycle_state = self._active_cycle_state

            if not cycle_id or cycle_state in {"idle", "cooldown"}:
                return {
                    "accepted": False,
                    "reason": "no_active_cycle",
                    "cycle_id": cycle_id,
                    "state": cycle_state,
                }
            if self._stop_response_requested:
                return {
                    "accepted": False,
                    "reason": "already_requested",
                    "cycle_id": cycle_id,
                    "state": cycle_state,
                }

            self._stop_response_requested = True
            self._stop_response_request_count += 1
            self._is_listening = False
            return {
                "accepted": True,
                "reason": "accepted",
                "cycle_id": cycle_id,
                "state": cycle_state,
                "request_count": self._stop_response_request_count,
            }

    def should_stop_response(self, *, cycle_id: str | None = None) -> bool:
        """Return True when active response should be interrupted."""
        with self._lock:
            if not self._stop_response_requested:
                return False
            if cycle_id is None:
                return True
            normalized_cycle = str(cycle_id).strip()
            if not normalized_cycle:
                return True
            return self._active_cycle_id == normalized_cycle


voice_runtime = VoiceRuntimeController()
