"""
Sensor framework - PureData-style real-time input system.

Sensors are data sources that emit values over time, similar to how
PureData objects emit signals. Effects can bind to sensors to modulate
their parameters dynamically.

Architecture:
- Sensor: Base class for all sensors (mic, camera, ntfy, OSC, etc.)
- SensorRegistry: Global registry for sensor discovery
- SensorStage: Pipeline stage wrapper for sensors
- Effect param_bindings: Declarative sensor-to-param routing

Example:
    class GlitchEffect(EffectPlugin):
        param_bindings = {
            "intensity": {"sensor": "mic", "transform": "linear"},
        }

This binds the mic sensor to the glitch intensity parameter.
"""

from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from engine.pipeline.core import PipelineContext


@dataclass
class SensorValue:
    """A sensor reading with metadata."""

    sensor_name: str
    value: float
    timestamp: float
    unit: str = ""


class Sensor(ABC):
    """Abstract base class for sensors.

    Sensors are real-time data sources that emit values. They can be:
    - Physical: mic, camera, joystick, MIDI, OSC
    - Virtual: ntfy, timer, random, noise

    Each sensor has a name and emits SensorValue objects.
    """

    name: str
    unit: str = ""

    @property
    def available(self) -> bool:
        """Whether the sensor is currently available."""
        return True

    @abstractmethod
    def read(self) -> SensorValue | None:
        """Read current sensor value.

        Returns:
            SensorValue if available, None if sensor is not ready.
        """
        ...

    @abstractmethod
    def start(self) -> bool:
        """Start the sensor.

        Returns:
            True if started successfully.
        """
        ...

    @abstractmethod
    def stop(self) -> None:
        """Stop the sensor and release resources."""
        ...


class SensorRegistry:
    """Global registry for sensors.

    Provides:
    - Registration of sensor instances
    - Lookup by name
    - Global start/stop
    """

    _sensors: dict[str, Sensor] = {}
    _started: bool = False

    @classmethod
    def register(cls, sensor: Sensor) -> None:
        """Register a sensor instance."""
        cls._sensors[sensor.name] = sensor

    @classmethod
    def get(cls, name: str) -> Sensor | None:
        """Get a sensor by name."""
        return cls._sensors.get(name)

    @classmethod
    def list_sensors(cls) -> list[str]:
        """List all registered sensor names."""
        return list(cls._sensors.keys())

    @classmethod
    def start_all(cls) -> bool:
        """Start all sensors.

        Returns:
            True if all sensors started successfully.
        """
        if cls._started:
            return True

        all_started = True
        for sensor in cls._sensors.values():
            if sensor.available and not sensor.start():
                all_started = False

        cls._started = all_started
        return all_started

    @classmethod
    def stop_all(cls) -> None:
        """Stop all sensors."""
        for sensor in cls._sensors.values():
            sensor.stop()
        cls._started = False

    @classmethod
    def read_all(cls) -> dict[str, float]:
        """Read all sensor values.

        Returns:
            Dict mapping sensor name to current value.
        """
        result = {}
        for name, sensor in cls._sensors.items():
            value = sensor.read()
            if value:
                result[name] = value.value
        return result


class SensorStage:
    """Pipeline stage wrapper for sensors.

    Provides sensor data to the pipeline context.
    Sensors don't transform data - they inject sensor values into context.
    """

    def __init__(self, sensor: Sensor, name: str | None = None):
        self._sensor = sensor
        self.name = name or sensor.name
        self.category = "sensor"
        self.optional = True

    @property
    def stage_type(self) -> str:
        return "sensor"

    @property
    def inlet_types(self) -> set:
        from engine.pipeline.core import DataType

        return {DataType.ANY}

    @property
    def outlet_types(self) -> set:
        from engine.pipeline.core import DataType

        return {DataType.ANY}

    @property
    def capabilities(self) -> set[str]:
        return {f"sensor.{self.name}"}

    @property
    def dependencies(self) -> set[str]:
        return set()

    def init(self, ctx: "PipelineContext") -> bool:
        return self._sensor.start()

    def process(self, data: Any, ctx: "PipelineContext") -> Any:
        value = self._sensor.read()
        if value:
            ctx.set_state(f"sensor.{self.name}", value.value)
            ctx.set_state(f"sensor.{self.name}.full", value)
        return data

    def cleanup(self) -> None:
        self._sensor.stop()


def create_sensor_stage(sensor: Sensor, name: str | None = None) -> SensorStage:
    """Create a pipeline stage from a sensor."""
    return SensorStage(sensor, name)
