"""
Frame buffer stage - stores previous frames for temporal effects.

Provides (per-instance, using instance name):
- framebuffer.{name}.history: list of previous buffers (most recent first)
- framebuffer.{name}.intensity_history: list of corresponding intensity maps
- framebuffer.{name}.current_intensity: intensity map for current frame

Capability: "framebuffer.history.{name}"
"""

import threading
from dataclasses import dataclass
from typing import Any

from engine.display import _strip_ansi
from engine.pipeline.core import DataType, PipelineContext, Stage


@dataclass
class FrameBufferConfig:
    """Configuration for FrameBufferStage."""

    history_depth: int = 2  # Number of previous frames to keep
    name: str = "default"  # Unique instance name for capability and context keys


class FrameBufferStage(Stage):
    """Stores frame history and computes intensity maps.

    Supports multiple instances with unique capabilities and context keys.
    """

    name = "framebuffer"
    category = "effect"  # It's an effect that enriches context with frame history

    def __init__(
        self,
        config: FrameBufferConfig | None = None,
        history_depth: int = 2,
        name: str = "default",
    ):
        self.config = config or FrameBufferConfig(
            history_depth=history_depth, name=name
        )
        self._lock = threading.Lock()

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

    @property
    def dependencies(self) -> set[str]:
        # Depends on rendered output (since we want to capture final buffer)
        return {"render.output"}

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

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

    def init(self, ctx: PipelineContext) -> bool:
        """Initialize framebuffer state in context."""
        prefix = f"framebuffer.{self.config.name}"
        ctx.set(f"{prefix}.history", [])
        ctx.set(f"{prefix}.intensity_history", [])
        return True

    def process(self, data: Any, ctx: PipelineContext) -> Any:
        """Store frame in history and compute intensity.

        Args:
            data: Current text buffer (list[str])
            ctx: Pipeline context

        Returns:
            Same buffer (pass-through)
        """
        if not isinstance(data, list):
            return data

        prefix = f"framebuffer.{self.config.name}"

        # Compute intensity map for current buffer (per-row, length = buffer rows)
        intensity_map = self._compute_buffer_intensity(data, len(data))

        # Store in context
        ctx.set(f"{prefix}.current_intensity", intensity_map)

        with self._lock:
            # Get existing histories
            history = ctx.get(f"{prefix}.history", [])
            intensity_hist = ctx.get(f"{prefix}.intensity_history", [])

            # Prepend current frame to history
            history.insert(0, data.copy())
            intensity_hist.insert(0, intensity_map)

            # Trim to configured depth
            max_depth = self.config.history_depth
            ctx.set(f"{prefix}.history", history[:max_depth])
            ctx.set(f"{prefix}.intensity_history", intensity_hist[:max_depth])

        return data

    def _compute_buffer_intensity(
        self, buf: list[str], max_rows: int = 24
    ) -> list[float]:
        """Compute average intensity per row in buffer.

        Uses ANSI color if available; falls back to character density.

        Args:
            buf: Text buffer (list of strings)
            max_rows: Maximum number of rows to process

        Returns:
            List of intensity values (0.0-1.0) per row
        """
        intensities = []
        # Limit to viewport height
        lines = buf[:max_rows]

        for line in lines:
            # Strip ANSI codes for length calc

            plain = _strip_ansi(line)
            if not plain:
                intensities.append(0.0)
                continue

            # Simple heuristic: ratio of non-space characters
            # More sophisticated version could parse ANSI RGB brightness
            filled = sum(1 for c in plain if c not in (" ", "\t"))
            total = len(plain)
            intensity = filled / total if total > 0 else 0.0
            intensities.append(max(0.0, min(1.0, intensity)))

        # Pad to max_rows if needed
        while len(intensities) < max_rows:
            intensities.append(0.0)

        return intensities

    def get_frame(
        self, index: int = 0, ctx: PipelineContext | None = None
    ) -> list[str] | None:
        """Get frame from history by index (0 = current, 1 = previous, etc)."""
        if ctx is None:
            return None
        prefix = f"framebuffer.{self.config.name}"
        history = ctx.get(f"{prefix}.history", [])
        if 0 <= index < len(history):
            return history[index]
        return None

    def get_intensity(
        self, index: int = 0, ctx: PipelineContext | None = None
    ) -> list[float] | None:
        """Get intensity map from history by index."""
        if ctx is None:
            return None
        prefix = f"framebuffer.{self.config.name}"
        intensity_hist = ctx.get(f"{prefix}.intensity_history", [])
        if 0 <= index < len(intensity_hist):
            return intensity_hist[index]
        return None

    def cleanup(self) -> None:
        """Cleanup resources."""
        pass
