"""PositionStage - Configurable positioning mode for terminal rendering.

This module provides positioning stages that allow choosing between
different ANSI positioning approaches:
- ABSOLUTE: Use cursor positioning codes (\\033[row;colH) for all lines
- RELATIVE: Use newlines for all lines
- MIXED: Base content uses newlines, effects use cursor positioning (default)
"""

from enum import Enum
from typing import Any

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


class PositioningMode(Enum):
    """Positioning mode for terminal rendering."""

    ABSOLUTE = "absolute"  # All lines have cursor positioning codes
    RELATIVE = "relative"  # Lines use newlines (no cursor codes)
    MIXED = "mixed"  # Mixed: newlines for base, cursor codes for overlays (default)


class PositionStage(Stage):
    """Applies positioning mode to buffer before display.

    This stage allows configuring how lines are positioned in the terminal:
    - ABSOLUTE: Each line has \\033[row;colH prefix (precise control)
    - RELATIVE: Lines are joined with \\n (natural flow)
    - MIXED: Leaves buffer as-is (effects add their own positioning)
    """

    def __init__(
        self, mode: PositioningMode = PositioningMode.RELATIVE, name: str = "position"
    ):
        self.mode = mode
        self.name = name
        self.category = "position"
        self._mode_str = mode.value

    def save_state(self) -> dict[str, Any]:
        """Save positioning mode for restoration."""
        return {"mode": self.mode.value}

    def restore_state(self, state: dict[str, Any]) -> None:
        """Restore positioning mode from saved state."""
        mode_value = state.get("mode", "relative")
        self.mode = PositioningMode(mode_value)

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

    @property
    def dependencies(self) -> set[str]:
        # Position stage typically runs after render but before effects
        # Effects may add their own positioning codes
        return {"render.output"}

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

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

    def init(self, ctx: PipelineContext) -> bool:
        """Initialize the positioning stage."""
        return True

    def process(self, data: Any, ctx: PipelineContext) -> Any:
        """Apply positioning mode to the buffer.

        Args:
            data: List of strings (buffer lines)
            ctx: Pipeline context

        Returns:
            Buffer with applied positioning mode
        """
        if data is None:
            return data

        if not isinstance(data, list):
            return data

        if self.mode == PositioningMode.ABSOLUTE:
            return self._to_absolute(data, ctx)
        elif self.mode == PositioningMode.RELATIVE:
            return self._to_relative(data, ctx)
        else:  # MIXED
            return data  # No transformation

    def _to_absolute(self, data: list[str], ctx: PipelineContext) -> list[str]:
        """Convert buffer to absolute positioning (all lines have cursor codes).

        This mode prefixes each line with \\033[row;colH to move cursor
        to the exact position before writing the line.

        Args:
            data: List of buffer lines
            ctx: Pipeline context (provides terminal dimensions)

        Returns:
            Buffer with cursor positioning codes for each line
        """
        result = []
        viewport_height = ctx.params.viewport_height if ctx.params else 24

        for i, line in enumerate(data):
            if i >= viewport_height:
                break  # Don't exceed viewport

            # Check if line already has cursor positioning
            if "\033[" in line and "H" in line:
                # Already has cursor positioning - leave as-is
                result.append(line)
            else:
                # Add cursor positioning for this line
                # Row is 1-indexed
                result.append(f"\033[{i + 1};1H{line}")

        return result

    def _to_relative(self, data: list[str], ctx: PipelineContext) -> list[str]:
        """Convert buffer to relative positioning (use newlines).

        This mode removes explicit cursor positioning codes from lines
        (except for effects that specifically add them).

        Note: Effects like HUD add their own cursor positioning codes,
        so we can't simply remove all of them. We rely on the terminal
        display to join lines with newlines.

        Args:
            data: List of buffer lines
            ctx: Pipeline context (unused)

        Returns:
            Buffer with minimal cursor positioning (only for overlays)
        """
        # For relative mode, we leave the buffer as-is
        # The terminal display handles joining with newlines
        # Effects that need absolute positioning will add their own codes

        # Filter out lines that would cause double-positioning
        result = []
        for i, line in enumerate(data):
            # Check if this line looks like base content (no cursor code at start)
            # vs an effect line (has cursor code at start)
            if line.startswith("\033[") and "H" in line[:20]:
                # This is an effect with positioning - keep it
                result.append(line)
            else:
                # Base content - strip any inline cursor codes (rare)
                # but keep color codes
                result.append(line)

        return result

    def cleanup(self) -> None:
        """Clean up positioning stage."""
        pass


# Convenience function to create positioning stage
def create_position_stage(
    mode: str = "relative", name: str = "position"
) -> PositionStage:
    """Create a positioning stage with the specified mode.

    Args:
        mode: Positioning mode ("absolute", "relative", or "mixed")
        name: Name for the stage

    Returns:
        PositionStage instance
    """
    try:
        positioning_mode = PositioningMode(mode)
    except ValueError:
        positioning_mode = PositioningMode.RELATIVE

    return PositionStage(mode=positioning_mode, name=name)
