"""
Webcam eye tracking — gaze from the laptop camera, no dedicated tracker.

The actual estimation runs in the browser (WebGazer-style: face/eye landmarks +
a calibration regression to screen coordinates), because that's where the camera
and the rendering live. The browser POSTs normalized gaze samples to the app, which
feeds them into THIS Source. From there it's an ordinary "EyeTracker"/Gaze LSL
stream — so the recorder, calibration, AOI, heatmap, and live-cursor screens all
treat webcam gaze exactly like a Tobii or Smart Eye.

This is the open replacement for iMotions' Webcam Eye Tracking module.
"""

from __future__ import annotations

import queue

from ..core.source import Source, StreamSpec


class WebcamEyeSource(Source):
    """Bridges browser-estimated gaze into LSL. Push samples with `push(x, y, conf)`."""

    def __init__(self, srate: float = 30.0, maxlen: int = 600):
        super().__init__(StreamSpec(
            name="EyeTracker", stype="Gaze",
            channels=["gaze_x", "gaze_y", "confidence"],
            nominal_srate=srate, source_id="webcam-eye-0",
        ))
        self._q: queue.Queue = queue.Queue(maxsize=maxlen)

    def push(self, x: float, y: float, conf: float = 1.0) -> None:
        """Called by the app for each browser gaze sample (drops if backed up)."""
        try:
            self._q.put_nowait((float(x), float(y), float(conf)))
        except queue.Full:
            pass

    def push_batch(self, samples) -> int:
        n = 0
        for s in samples:
            x = s.get("x"); y = s.get("y")
            if x is None or y is None:
                continue
            self.push(x, y, s.get("conf", 1.0))
            n += 1
        return n

    def read(self):
        # stamp at arrival on the LSL clock (None) — webcam timing is approximate
        while not self.stopping:
            try:
                x, y, conf = self._q.get(timeout=0.2)
            except queue.Empty:
                continue
            yield [x, y, conf], None
