"""Adapter for camera stage."""

import time
from typing import Any

from sideline.pipeline.core import DataType, PipelineContext, Stage


class CameraClockStage(Stage):
    """Per-frame clock stage that updates camera state.

    This stage runs once per frame and updates the camera's internal state
    (position, time). It makes camera_y/camera_x available to subsequent
    stages via the pipeline context.

    Unlike other stages, this is a pure clock stage and doesn't process
    data - it just updates camera state and passes data through unchanged.
    """

    def __init__(self, camera, name: str = "camera-clock"):
        self._camera = camera
        self.name = name
        self.category = "camera"
        self.optional = False
        self._last_frame_time: float | None = None

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

    @property
    def capabilities(self) -> set[str]:
        # Provides camera state info only
        # NOTE: Do NOT provide "source" as it conflicts with viewport_filter's "source.filtered"
        return {"camera.state"}

    @property
    def dependencies(self) -> set[str]:
        # Clock stage - no dependencies (updates every frame regardless of data flow)
        return set()

    @property
    def inlet_types(self) -> set:
        # Accept any data type - this is a pass-through stage
        return {DataType.ANY}

    @property
    def outlet_types(self) -> set:
        # Pass through whatever was received
        return {DataType.ANY}

    def process(self, data: Any, ctx: PipelineContext) -> Any:
        """Update camera state and pass data through.

        This stage updates the camera's internal state (position, time) and
        makes the updated camera_y/camera_x available to subsequent stages
        via the pipeline context.

        The data is passed through unchanged - this stage only updates
        camera state, it doesn't transform the data.
        """
        if data is None:
            return data

        # Update camera speed from params if explicitly set (for dynamic modulation)
        # Only update if camera_speed in params differs from the default (1.0)
        # This preserves camera speed set during construction
        if (
            ctx.params
            and hasattr(ctx.params, "camera_speed")
            and ctx.params.camera_speed != 1.0
        ):
            self._camera.set_speed(ctx.params.camera_speed)

        current_time = time.perf_counter()
        dt = 0.0
        if self._last_frame_time is not None:
            dt = current_time - self._last_frame_time
            self._camera.update(dt)
        self._last_frame_time = current_time

        # Update context with current camera position
        ctx.set_state("camera_y", self._camera.y)
        ctx.set_state("camera_x", self._camera.x)

        # Pass data through unchanged
        return data


class CameraStage(Stage):
    """Adapter wrapping Camera as a Stage.

    This stage applies camera viewport transformation to the rendered buffer.
    Camera state updates are handled by CameraClockStage.
    """

    def __init__(self, camera, name: str = "vertical"):
        self._camera = camera
        self.name = name
        self.category = "camera"
        self.optional = True
        self._last_frame_time: float | None = None

    def save_state(self) -> dict[str, Any]:
        """Save camera state for restoration after pipeline rebuild.

        Returns:
            Dictionary containing camera state that can be restored
        """
        state = {
            "x": self._camera.x,
            "y": self._camera.y,
            "mode": self._camera.mode.value
            if hasattr(self._camera.mode, "value")
            else self._camera.mode,
            "speed": self._camera.speed,
            "zoom": self._camera.zoom,
            "canvas_width": self._camera.canvas_width,
            "canvas_height": self._camera.canvas_height,
            "_x_float": getattr(self._camera, "_x_float", 0.0),
            "_y_float": getattr(self._camera, "_y_float", 0.0),
            "_time": getattr(self._camera, "_time", 0.0),
        }
        # Save radial camera state if present
        if hasattr(self._camera, "_r_float"):
            state["_r_float"] = self._camera._r_float
        if hasattr(self._camera, "_theta_float"):
            state["_theta_float"] = self._camera._theta_float
        if hasattr(self._camera, "_radial_input"):
            state["_radial_input"] = self._camera._radial_input
        return state

    def restore_state(self, state: dict[str, Any]) -> None:
        """Restore camera state from saved state.

        Args:
            state: Dictionary containing camera state from save_state()
        """
        from sideline.camera import CameraMode

        self._camera.x = state.get("x", 0)
        self._camera.y = state.get("y", 0)

        # Restore mode - handle both enum value and direct enum
        mode_value = state.get("mode", 0)
        if isinstance(mode_value, int):
            self._camera.mode = CameraMode(mode_value)
        else:
            self._camera.mode = mode_value

        self._camera.speed = state.get("speed", 1.0)
        self._camera.zoom = state.get("zoom", 1.0)
        self._camera.canvas_width = state.get("canvas_width", 200)
        self._camera.canvas_height = state.get("canvas_height", 200)

        # Restore internal state
        if hasattr(self._camera, "_x_float"):
            self._camera._x_float = state.get("_x_float", 0.0)
        if hasattr(self._camera, "_y_float"):
            self._camera._y_float = state.get("_y_float", 0.0)
        if hasattr(self._camera, "_time"):
            self._camera._time = state.get("_time", 0.0)

        # Restore radial camera state if present
        if hasattr(self._camera, "_r_float"):
            self._camera._r_float = state.get("_r_float", 0.0)
        if hasattr(self._camera, "_theta_float"):
            self._camera._theta_float = state.get("_theta_float", 0.0)
        if hasattr(self._camera, "_radial_input"):
            self._camera._radial_input = state.get("_radial_input", 0.0)

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

    @property
    def capabilities(self) -> set[str]:
        return {"camera"}

    @property
    def dependencies(self) -> set[str]:
        return {"render.output", "camera.state"}

    @property
    def inlet_types(self) -> set:
        return {DataType.TEXT_BUFFER}

    @property
    def outlet_types(self) -> set:
        return {DataType.TEXT_BUFFER}

    def process(self, data: Any, ctx: PipelineContext) -> Any:
        """Apply camera transformation to items."""
        if data is None:
            return data

        # Camera state is updated by CameraClockStage
        # We only apply the viewport transformation here

        if hasattr(self._camera, "apply"):
            viewport_width = ctx.params.viewport_width if ctx.params else 80
            viewport_height = ctx.params.viewport_height if ctx.params else 24

            # Use filtered camera position if available (from ViewportFilterStage)
            # This handles the case where the buffer has been filtered and starts at row 0
            filtered_camera_y = ctx.get("camera_y", self._camera.y)

            # Temporarily adjust camera position for filtering
            original_y = self._camera.y
            self._camera.y = filtered_camera_y

            try:
                result = self._camera.apply(data, viewport_width, viewport_height)
            finally:
                # Restore original camera position
                self._camera.y = original_y

            return result
        return data
