"""
Live signal-quality monitor.

Taps every LSL stream currently on the network (the same way the recorder does),
keeps a short rolling buffer per stream, and computes quality each poll so the UI
can show a good/warn/bad light per sensor DURING a recording. Run it next to a
recording to catch a dead electrode or lost tracker before the session is wasted.
"""

from __future__ import annotations

import threading
import time
from collections import deque

from ..analysis import quality as Q


class LiveMonitor:
    def __init__(self, window: float = 3.0):
        self.window = window
        self._lock = threading.Lock()
        self._buffers: dict = {}            # name -> {"type","srate","t":deque,"x":deque}
        self._open = set()
        self._threads: list = []
        self._stop = threading.Event()
        self.running = False

    def start(self) -> dict:
        if self.running:
            return {"running": True}
        self._stop.clear()
        self.running = True
        threading.Thread(target=self._resolver, daemon=True).start()
        return {"running": True}

    def _resolver(self):
        from pylsl import resolve_streams, StreamInlet
        while not self._stop.is_set():
            try:
                streams = resolve_streams(1.0)
            except Exception:
                streams = []
            for info in streams:
                uid = info.source_id() or f"{info.name()}|{info.uid()}"
                if uid in self._open:
                    continue
                self._open.add(uid)
                inlet = StreamInlet(info, max_buflen=int(self.window) + 1, recover=True)
                name = info.name()
                with self._lock:
                    self._buffers[name] = {"type": info.type(),
                                           "srate": info.nominal_srate(),
                                           "t": deque(), "x": deque()}
                threading.Thread(target=self._pull, args=(inlet, name), daemon=True).start()
            self._stop.wait(0.8)

    def _pull(self, inlet, name):
        is_str = inlet.info().channel_format() == 3
        while not self._stop.is_set():
            try:
                chunk, stamps = inlet.pull_chunk(timeout=0.2, max_samples=128)
            except Exception:
                time.sleep(0.05); continue
            if not stamps:
                continue
            b = self._buffers.get(name)
            if b is None:
                continue
            now = time.time()
            with self._lock:
                for s, ts in zip(chunk, stamps):
                    b["t"].append(now)
                    b["x"].append(s[0] if is_str else (s if len(s) > 1 else s[0]))
                # trim to the rolling window
                cut = now - self.window
                while b["t"] and b["t"][0] < cut:
                    b["t"].popleft(); b["x"].popleft()

    def quality(self) -> dict:
        import numpy as np
        out = {}
        with self._lock:
            snap = {n: {"type": b["type"], "srate": b["srate"],
                        "t": list(b["t"]), "x": list(b["x"])}
                    for n, b in self._buffers.items()}
        for name, s in snap.items():
            if s["type"] == "Markers" or not s["t"]:
                continue
            x = np.asarray(s["x"], dtype=object)
            try:
                xx = np.asarray(s["x"], dtype="float64")
            except Exception:
                continue
            out[name] = Q.stream_quality(np.asarray(s["t"]), xx, stype=s["type"],
                                         srate=s["srate"], window=self.window)
        return out

    def stop(self) -> dict:
        self._stop.set()
        self.running = False
        with self._lock:
            self._buffers.clear(); self._open.clear()
        return {"running": False}
