"""Adapters for transform stages (viewport, font, image, canvas)."""

from typing import Any

import sideline.render
from sideline.data_sources import SourceItem
from sideline.pipeline.core import DataType, PipelineContext, Stage


def estimate_simple_height(text: str, width: int) -> int:
    """Estimate height in terminal rows using simple word wrap.

    Uses conservative estimation suitable for headlines.
    Each wrapped line is approximately 6 terminal rows (big block rendering).
    """
    words = text.split()
    if not words:
        return 6

    lines = 1
    current_len = 0
    for word in words:
        word_len = len(word)
        if current_len + word_len + 1 > width - 4:  # -4 for margins
            lines += 1
            current_len = word_len
        else:
            current_len += word_len + 1

    return lines * 6  # 6 rows per line for big block rendering


class ViewportFilterStage(Stage):
    """Filter items to viewport height based on rendered height."""

    def __init__(self, name: str = "viewport-filter"):
        self.name = name
        self.category = "render"
        self.optional = True
        self._layout: list[int] = []

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

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

    @property
    def dependencies(self) -> set[str]:
        # Always requires camera.state for viewport filtering
        # CameraUpdateStage provides this (auto-injected if missing)
        return {"source", "camera.state"}

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

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

    def process(self, data: Any, ctx: PipelineContext) -> Any:
        """Filter items to viewport height based on rendered height."""
        if data is None:
            return data

        if not isinstance(data, list):
            return data

        if not data:
            return []

        # Get viewport parameters from context
        viewport_height = ctx.params.viewport_height if ctx.params else 24
        viewport_width = ctx.params.viewport_width if ctx.params else 80
        camera_y = ctx.get("camera_y", 0)

        # Estimate height for each item and cache layout
        self._layout = []
        cumulative_heights = []
        current_height = 0

        for item in data:
            title = item.content if isinstance(item, SourceItem) else str(item)
            # Use simple height estimation (not PIL-based)
            estimated_height = estimate_simple_height(title, viewport_width)
            self._layout.append(estimated_height)
            current_height += estimated_height
            cumulative_heights.append(current_height)

        # Find visible range based on camera_y and viewport_height
        # camera_y is the scroll offset (how many rows are scrolled up)
        start_y = camera_y
        end_y = camera_y + viewport_height

        # Find start index (first item that intersects with visible range)
        start_idx = 0
        start_item_y = 0  # Y position where the first visible item starts
        for i, total_h in enumerate(cumulative_heights):
            if total_h > start_y:
                start_idx = i
                # Calculate the Y position of the start of this item
                if i > 0:
                    start_item_y = cumulative_heights[i - 1]
                break

        # Find end index (first item that extends beyond visible range)
        end_idx = len(data)
        for i, total_h in enumerate(cumulative_heights):
            if total_h >= end_y:
                end_idx = i + 1
                break

        # Adjust camera_y for the filtered buffer
        # The filtered buffer starts at row 0, but the camera position
        # needs to be relative to where the first visible item starts
        filtered_camera_y = camera_y - start_item_y

        # Update context with the filtered camera position
        # This ensures CameraStage can correctly slice the filtered buffer
        ctx.set_state("camera_y", filtered_camera_y)
        ctx.set_state("camera_x", ctx.get("camera_x", 0))  # Keep camera_x unchanged

        # Return visible items
        return data[start_idx:end_idx]


class FontStage(Stage):
    """Render items using font."""

    def __init__(self, name: str = "font"):
        self.name = name
        self.category = "render"
        self.optional = False

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

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

    @property
    def stage_dependencies(self) -> set[str]:
        # Must connect to viewport_filter stage to get filtered source
        return {"viewport_filter"}

    @property
    def dependencies(self) -> set[str]:
        # Depend on source.filtered (provided by viewport_filter)
        # This ensures we get the filtered/processed source, not raw source
        return {"source.filtered"}

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

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

    def process(self, data: Any, ctx: PipelineContext) -> Any:
        """Render items to text buffer using font."""
        if data is None:
            return []

        if not isinstance(data, list):
            return [str(data)]

        import os

        if os.environ.get("DEBUG_CAMERA"):
            print(f"FontStage: input items={len(data)}")

        viewport_width = ctx.params.viewport_width if ctx.params else 80

        result = []
        for item in data:
            if isinstance(item, SourceItem):
                title = item.content
                src = item.source
                ts = item.timestamp
                content_lines, _, _ = engine.render.make_block(
                    title, src, ts, viewport_width
                )
                result.extend(content_lines)
            elif hasattr(item, "content"):
                title = str(item.content)
                content_lines, _, _ = engine.render.make_block(
                    title, "", "", viewport_width
                )
                result.extend(content_lines)
            else:
                result.append(str(item))
        return result


class ImageToTextStage(Stage):
    """Convert image items to text."""

    def __init__(self, name: str = "image-to-text"):
        self.name = name
        self.category = "render"
        self.optional = True

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

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

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

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

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

    def process(self, data: Any, ctx: PipelineContext) -> Any:
        """Convert image items to text representation."""
        if data is None:
            return []

        if not isinstance(data, list):
            return [str(data)]

        result = []
        for item in data:
            # Check if item is an image
            if hasattr(item, "image_path") or hasattr(item, "image_data"):
                # Placeholder: would normally render image to ASCII art
                result.append(f"[Image: {getattr(item, 'image_path', 'data')}]")
            elif isinstance(item, SourceItem):
                result.extend(item.content.split("\n"))
            else:
                result.append(str(item))
        return result


class CanvasStage(Stage):
    """Render items to canvas."""

    def __init__(self, name: str = "canvas"):
        self.name = name
        self.category = "render"
        self.optional = False

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

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

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

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

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

    def process(self, data: Any, ctx: PipelineContext) -> Any:
        """Render items to canvas."""
        if data is None:
            return []

        if not isinstance(data, list):
            return [str(data)]

        # Simple canvas rendering
        result = []
        for item in data:
            if isinstance(item, SourceItem):
                result.extend(item.content.split("\n"))
            else:
                result.append(str(item))
        return result
