"""
Display backend system with registry pattern.

Allows swapping output backends via the Display protocol.
Supports auto-discovery of display backends.
"""

from enum import Enum, auto
from typing import Protocol

# Optional backend - requires moderngl package
try:
    from engine.display.backends.moderngl import ModernGLDisplay

    _MODERNGL_AVAILABLE = True
except ImportError:
    ModernGLDisplay = None
    _MODERNGL_AVAILABLE = False

from engine.display.backends.multi import MultiDisplay
from engine.display.backends.null import NullDisplay
from engine.display.backends.pygame import PygameDisplay
from engine.display.backends.replay import ReplayDisplay
from engine.display.backends.terminal import TerminalDisplay
from engine.display.backends.websocket import WebSocketDisplay


class BorderMode(Enum):
    """Border rendering modes for displays."""

    OFF = auto()  # No border
    SIMPLE = auto()  # Traditional border with FPS/frame time
    UI = auto()  # Right-side UI panel with interactive controls


class Display(Protocol):
    """Protocol for display backends.

    Required attributes:
    - width: int
    - height: int

    Required methods (duck typing - actual signatures may vary):
    - init(width, height, reuse=False)
    - show(buffer, border=False)
    - clear()
    - cleanup()
    - get_dimensions() -> (width, height)

    Optional attributes (for UI mode):
    - ui_panel: UIPanel instance (set by app when border=UI)

    Optional methods:
    - is_quit_requested() -> bool
    - clear_quit_request() -> None
    """

    width: int
    height: int


class DisplayRegistry:
    """Registry for display backends with auto-discovery."""

    _backends: dict[str, type[Display]] = {}
    _initialized = False

    @classmethod
    def register(cls, name: str, backend_class: type[Display]) -> None:
        cls._backends[name.lower()] = backend_class

    @classmethod
    def get(cls, name: str) -> type[Display] | None:
        return cls._backends.get(name.lower())

    @classmethod
    def list_backends(cls) -> list[str]:
        return list(cls._backends.keys())

    @classmethod
    def create(cls, name: str, **kwargs) -> Display | None:
        cls.initialize()
        backend_class = cls.get(name)
        if backend_class:
            return backend_class(**kwargs)
        return None

    @classmethod
    def initialize(cls) -> None:
        if cls._initialized:
            return
        cls.register("terminal", TerminalDisplay)
        cls.register("null", NullDisplay)
        cls.register("replay", ReplayDisplay)
        cls.register("websocket", WebSocketDisplay)
        cls.register("pygame", PygameDisplay)
        if _MODERNGL_AVAILABLE:
            cls.register("moderngl", ModernGLDisplay)  # type: ignore[arg-type]
        cls._initialized = True

    @classmethod
    def create_multi(cls, names: list[str]) -> MultiDisplay | None:
        displays = []
        for name in names:
            backend = cls.create(name)
            if backend:
                displays.append(backend)
            else:
                return None
        if not displays:
            return None
        return MultiDisplay(displays)


def get_monitor():
    """Get the performance monitor."""
    try:
        from engine.effects.performance import get_monitor as _get_monitor

        return _get_monitor()
    except Exception:
        return None


def _strip_ansi(s: str) -> str:
    """Strip ANSI escape sequences from string for length calculation."""
    import re

    return re.sub(r"\x1b\[[0-9;]*[a-zA-Z]", "", s)


def _render_simple_border(
    buf: list[str], width: int, height: int, fps: float = 0.0, frame_time: float = 0.0
) -> list[str]:
    """Render a traditional border around the buffer."""
    if not buf or width < 3 or height < 3:
        return buf

    inner_w = width - 2
    inner_h = height - 2

    cropped = []
    for i in range(min(inner_h, len(buf))):
        line = buf[i]
        visible_len = len(_strip_ansi(line))
        if visible_len > inner_w:
            cropped.append(line[:inner_w])
        else:
            cropped.append(line + " " * (inner_w - visible_len))

    while len(cropped) < inner_h:
        cropped.append(" " * inner_w)

    if fps > 0:
        fps_str = f" FPS:{fps:.0f}"
        if len(fps_str) < inner_w:
            right_len = inner_w - len(fps_str)
            top_border = "┌" + "─" * right_len + fps_str + "┐"
        else:
            top_border = "┌" + "─" * inner_w + "┐"
    else:
        top_border = "┌" + "─" * inner_w + "┐"

    if frame_time > 0:
        ft_str = f" {frame_time:.1f}ms"
        if len(ft_str) < inner_w:
            right_len = inner_w - len(ft_str)
            bottom_border = "└" + "─" * right_len + ft_str + "┘"
        else:
            bottom_border = "└" + "─" * inner_w + "┘"
    else:
        bottom_border = "└" + "─" * inner_w + "┘"

    result = [top_border]
    for line in cropped:
        if len(line) < inner_w:
            line = line + " " * (inner_w - len(line))
        elif len(line) > inner_w:
            line = line[:inner_w]
        result.append("│" + line + "│")
    result.append(bottom_border)

    return result


def render_ui_panel(
    buf: list[str],
    width: int,
    height: int,
    ui_panel,
    fps: float = 0.0,
    frame_time: float = 0.0,
) -> list[str]:
    """Render buffer with a right-side UI panel."""
    from engine.pipeline.ui import UIPanel

    if not isinstance(ui_panel, UIPanel):
        return _render_simple_border(buf, width, height, fps, frame_time)

    panel_width = min(ui_panel.config.panel_width, width - 4)
    main_width = width - panel_width - 1

    panel_lines = ui_panel.render(panel_width, height)

    main_buf = buf[: height - 2]
    main_result = _render_simple_border(
        main_buf, main_width + 2, height, fps, frame_time
    )

    combined = []
    for i in range(height):
        if i < len(main_result):
            main_line = main_result[i]
            if len(main_line) >= 2:
                main_content = (
                    main_line[1:-1] if main_line[-1] in "│┌┐└┘" else main_line[1:]
                )
                main_content = main_content.ljust(main_width)[:main_width]
            else:
                main_content = " " * main_width
        else:
            main_content = " " * main_width

        panel_idx = i
        panel_line = (
            panel_lines[panel_idx][:panel_width].ljust(panel_width)
            if panel_idx < len(panel_lines)
            else " " * panel_width
        )

        separator = "│" if 0 < i < height - 1 else "┼" if i == 0 else "┴"
        combined.append(main_content + separator + panel_line)

    return combined


def render_border(
    buf: list[str],
    width: int,
    height: int,
    fps: float = 0.0,
    frame_time: float = 0.0,
    border_mode: BorderMode | bool = BorderMode.SIMPLE,
) -> list[str]:
    """Render a border or UI panel around the buffer.

    Args:
        buf: Input buffer
        width: Display width
        height: Display height
        fps: FPS for top border
        frame_time: Frame time for bottom border
        border_mode: Border rendering mode

    Returns:
        Buffer with border/panel applied
    """
    # Normalize border_mode to BorderMode enum
    if isinstance(border_mode, bool):
        border_mode = BorderMode.SIMPLE if border_mode else BorderMode.OFF

    if border_mode == BorderMode.UI:
        # UI panel requires a UIPanel instance (injected separately)
        # For now, this will be called by displays that have a ui_panel attribute
        # This function signature doesn't include ui_panel, so we'll handle it in render_ui_panel
        # Fall back to simple border if no panel available
        return _render_simple_border(buf, width, height, fps, frame_time)
    elif border_mode == BorderMode.SIMPLE:
        return _render_simple_border(buf, width, height, fps, frame_time)
    else:
        return buf


__all__ = [
    "Display",
    "DisplayRegistry",
    "get_monitor",
    "render_border",
    "render_ui_panel",
    "BorderMode",
    "TerminalDisplay",
    "NullDisplay",
    "ReplayDisplay",
    "WebSocketDisplay",
    "MultiDisplay",
    "PygameDisplay",
]

if _MODERNGL_AVAILABLE:
    __all__.append("ModernGLDisplay")
