"""
Live gaze provider for the calibration & gaze-cursor screens.

Runs a gaze Source (synthetic, or any tracker via `drivers.make`) and taps its LSL
outlet with an inlet — the same path the recorder uses — so the browser can read
the latest gaze sample in real time. One instance is shared by the app.

Endpoints in app/server.py expose:
  * /api/gaze/latest  — newest sample (JSON) for high-rate polling / tests,
  * /api/gaze/stream  — Server-Sent Events for a smooth cursor,
and the screens collect per-target gaze for calibration.
"""

from __future__ import annotations

import threading
import time


class LiveGaze:
    def __init__(self):
        self._lock = threading.Lock()
        self._source = None
        self._inlet = None
        self._thread = None
        self._stop = threading.Event()
        self.running = False
        self.latest = {"x": 0.5, "y": 0.5, "valid": False, "t": 0.0, "fps": 0.0}

    def start(self, slug: str | None = None) -> dict:
        with self._lock:
            if self.running:
                self.stop_locked()
            self._source = self._make_source(slug)
            self._source.start()
            self._open_inlet()
            self._stop.clear()
            self._thread = threading.Thread(target=self._pull, daemon=True)
            self._thread.start()
            self.running = True
            return {"running": True, "source": slug or "synthetic"}

    def _make_source(self, slug):
        if slug:
            from .. import drivers
            return drivers.make(slug)
        from ..sources.synthetic import SyntheticGaze
        return SyntheticGaze()

    def _open_inlet(self):
        from pylsl import resolve_byprop, StreamInlet
        streams = resolve_byprop("type", "Gaze", timeout=5)
        if not streams:
            streams = resolve_byprop("name", "EyeTracker", timeout=3)
        if not streams:
            raise RuntimeError("no gaze stream found to tap")
        self._inlet = StreamInlet(streams[0], max_buflen=8, recover=True)

    def _pull(self):
        last_t, frames, t_win = time.time(), 0, time.time()
        while not self._stop.is_set():
            try:
                sample, ts = self._inlet.pull_sample(timeout=0.2)
            except Exception:
                sample = None
            if sample is None:
                continue
            x, y = float(sample[0]), float(sample[1])
            valid = (x == x and y == y)             # not NaN
            frames += 1
            now = time.time()
            fps = frames / (now - t_win) if now - t_win > 0.5 else self.latest["fps"]
            if now - t_win > 0.5:
                frames, t_win = 0, now
            with self._lock:
                self.latest = {"x": x, "y": y, "valid": valid, "t": now, "fps": round(fps, 1)}

    def stop(self) -> dict:
        with self._lock:
            self.stop_locked()
        return {"running": False}

    def stop_locked(self):
        self._stop.set()
        if self._source:
            try:
                self._source.stop()
            except Exception:
                pass
        self._source = self._inlet = None
        self.running = False
