"""RendererRegistry and ElementRenderer protocol for the output framework.

Implements the RendererRegistry class (spec §27249-27350) and the
ElementRenderer protocol (spec §26557-26654).  Each format is registered
as a ``(MaterializationStrategy, ElementRenderer)`` pair — the strategy
controls *when* elements are rendered and the renderer controls *how*
each element type is visually formatted.

Based on the Output Rendering Framework specification (v3_spec.md §Output
Rendering Framework / Renderer Registration and Extension).
"""

from __future__ import annotations

from collections.abc import Callable
from io import StringIO
from typing import TYPE_CHECKING, Any, Protocol

from pydantic import BaseModel

from cleveragents.cli.output.handles import (
    ActionHint,
    CodeBlock,
    DiffBlock,
    ElementSnapshot,
    Panel,
    ProgressIndicator,
    Separator,
    StatusMessage,
    Table,
    TextBlock,
    Tree,
)

if TYPE_CHECKING:
    from cleveragents.cli.output.selection import TerminalCapabilities
    from cleveragents.cli.output.session import StructuredOutput


# ---------------------------------------------------------------------------
# ElementRenderer protocol (spec §26557-26654)
# ---------------------------------------------------------------------------


class ElementRenderer(Protocol):
    """Interface for format-specific element rendering.

    An ElementRenderer knows how to paint each element type for a specific
    output format.  It is called by the MaterializationStrategy when it is
    time to render an element.

    Implementations:
    - PlainElementRenderer: ASCII text, no escapes
    - ColorElementRenderer: ANSI-colored text, same layout as plain
    - TableElementRenderer: Unicode box-drawing with color
    - RichElementRenderer: Advanced terminal features (cursor, animation)
    - JsonElementRenderer: JSON serialization
    - YamlElementRenderer: YAML serialization
    """

    format_name: str

    def render_panel(self, panel: Panel) -> str: ...  # pragma: no cover

    def render_table(self, table: Table) -> str: ...  # pragma: no cover

    def render_tree(self, tree: Tree) -> str: ...  # pragma: no cover

    def render_status(self, status: StatusMessage) -> str: ...  # pragma: no cover

    def render_progress(
        self, progress: ProgressIndicator
    ) -> str: ...  # pragma: no cover

    def render_code(self, code: CodeBlock) -> str: ...  # pragma: no cover

    def render_diff(self, diff: DiffBlock) -> str: ...  # pragma: no cover

    def render_text(self, text: TextBlock) -> str: ...  # pragma: no cover

    def render_separator(self, separator: Separator) -> str: ...  # pragma: no cover

    def render_action_hint(self, hint: ActionHint) -> str: ...  # pragma: no cover

    def render_element(self, element: ElementSnapshot) -> str: ...  # pragma: no cover

    def serialize(self, output: StructuredOutput) -> str: ...  # pragma: no cover

    def can_render(
        self, terminal_caps: TerminalCapabilities
    ) -> bool: ...  # pragma: no cover


# ---------------------------------------------------------------------------
# Concrete ElementRenderer implementations
# ---------------------------------------------------------------------------


class _BaseElementRenderer:
    """Shared dispatch logic for element renderers."""

    format_name: str = "base"

    def render_panel(self, panel: Panel) -> str:
        return ""  # pragma: no cover

    def render_table(self, table: Table) -> str:
        return ""  # pragma: no cover

    def render_tree(self, tree: Tree) -> str:
        return ""  # pragma: no cover

    def render_status(self, status: StatusMessage) -> str:
        return ""  # pragma: no cover

    def render_progress(self, progress: ProgressIndicator) -> str:
        return ""  # pragma: no cover

    def render_code(self, code: CodeBlock) -> str:
        return ""  # pragma: no cover

    def render_diff(self, diff: DiffBlock) -> str:
        return ""  # pragma: no cover

    def render_text(self, text: TextBlock) -> str:
        return ""  # pragma: no cover

    def render_separator(self, separator: Separator) -> str:
        return ""  # pragma: no cover

    def render_action_hint(self, hint: ActionHint) -> str:
        return ""  # pragma: no cover

    def render_element(self, element: ElementSnapshot) -> str:
        """Dispatch to the appropriate render method based on element type."""
        dispatch: dict[type, Callable[..., str]] = {
            Panel: self.render_panel,
            Table: self.render_table,
            Tree: self.render_tree,
            StatusMessage: self.render_status,
            ProgressIndicator: self.render_progress,
            CodeBlock: self.render_code,
            DiffBlock: self.render_diff,
            TextBlock: self.render_text,
            Separator: self.render_separator,
            ActionHint: self.render_action_hint,
        }
        handler = dispatch.get(type(element))
        if handler:
            return handler(element)
        return str(element)  # pragma: no cover

    def serialize(self, output: StructuredOutput) -> str:
        """Serialize a complete StructuredOutput by rendering each element."""
        buf = StringIO()
        for element in output.elements:
            rendered = self.render_element(element)
            if rendered:
                buf.write(rendered)
                buf.write("\n\n")
        return buf.getvalue().rstrip("\n")

    def can_render(self, terminal_caps: TerminalCapabilities) -> bool:
        """Whether this renderer can operate in the given terminal."""
        return True  # pragma: no cover


class PlainElementRenderer(_BaseElementRenderer):
    """Renders all elements as plain ASCII text."""

    format_name: str = "plain"

    def render_panel(self, panel: Panel) -> str:
        from cleveragents.cli.output._renderers import render_element_plain

        return render_element_plain(panel)

    def render_table(self, table: Table) -> str:
        from cleveragents.cli.output._renderers import render_element_plain

        return render_element_plain(table)

    def render_tree(self, tree: Tree) -> str:
        from cleveragents.cli.output._renderers import render_element_plain

        return render_element_plain(tree)

    def render_status(self, status: StatusMessage) -> str:
        from cleveragents.cli.output._renderers import render_element_plain

        return render_element_plain(status)

    def render_progress(self, progress: ProgressIndicator) -> str:
        from cleveragents.cli.output._renderers import render_element_plain

        return render_element_plain(progress)

    def render_code(self, code: CodeBlock) -> str:
        from cleveragents.cli.output._renderers import render_element_plain

        return render_element_plain(code)

    def render_diff(self, diff: DiffBlock) -> str:
        from cleveragents.cli.output._renderers import render_element_plain

        return render_element_plain(diff)

    def render_text(self, text: TextBlock) -> str:
        from cleveragents.cli.output._renderers import render_element_plain

        return render_element_plain(text)

    def render_separator(self, separator: Separator) -> str:
        from cleveragents.cli.output._renderers import render_element_plain

        return render_element_plain(separator)

    def render_action_hint(self, hint: ActionHint) -> str:
        from cleveragents.cli.output._renderers import render_element_plain

        return render_element_plain(hint)

    def can_render(self, terminal_caps: TerminalCapabilities) -> bool:
        return True  # Plain always works


class ColorElementRenderer(_BaseElementRenderer):
    """Renders all elements with ANSI colour codes."""

    format_name: str = "color"

    def render_panel(self, panel: Panel) -> str:
        from cleveragents.cli.output._color_renderers import render_element_color

        return render_element_color(panel)

    def render_table(self, table: Table) -> str:
        from cleveragents.cli.output._color_renderers import render_element_color

        return render_element_color(table)

    def render_tree(self, tree: Tree) -> str:
        from cleveragents.cli.output._color_renderers import render_element_color

        return render_element_color(tree)

    def render_status(self, status: StatusMessage) -> str:
        from cleveragents.cli.output._color_renderers import render_element_color

        return render_element_color(status)

    def render_progress(self, progress: ProgressIndicator) -> str:
        from cleveragents.cli.output._color_renderers import render_element_color

        return render_element_color(progress)

    def render_code(self, code: CodeBlock) -> str:
        from cleveragents.cli.output._color_renderers import render_element_color

        return render_element_color(code)

    def render_diff(self, diff: DiffBlock) -> str:
        from cleveragents.cli.output._color_renderers import render_element_color

        return render_element_color(diff)

    def render_text(self, text: TextBlock) -> str:
        from cleveragents.cli.output._color_renderers import render_element_color

        return render_element_color(text)

    def render_separator(self, separator: Separator) -> str:
        from cleveragents.cli.output._color_renderers import render_element_color

        return render_element_color(separator)

    def render_action_hint(self, hint: ActionHint) -> str:
        from cleveragents.cli.output._color_renderers import render_element_color

        return render_element_color(hint)

    def can_render(self, terminal_caps: TerminalCapabilities) -> bool:
        return terminal_caps.supports_ansi


class TableElementRenderer(_BaseElementRenderer):
    """Renders tables with Unicode box-drawing, other elements with colour."""

    format_name: str = "table"

    def render_panel(self, panel: Panel) -> str:
        from cleveragents.cli.output._boxdraw import render_element_table

        return render_element_table(panel)

    def render_table(self, table: Table) -> str:
        from cleveragents.cli.output._boxdraw import render_element_table

        return render_element_table(table)

    def render_tree(self, tree: Tree) -> str:
        from cleveragents.cli.output._boxdraw import render_element_table

        return render_element_table(tree)

    def render_status(self, status: StatusMessage) -> str:
        from cleveragents.cli.output._boxdraw import render_element_table

        return render_element_table(status)

    def render_progress(self, progress: ProgressIndicator) -> str:
        from cleveragents.cli.output._boxdraw import render_element_table

        return render_element_table(progress)

    def render_code(self, code: CodeBlock) -> str:
        from cleveragents.cli.output._boxdraw import render_element_table

        return render_element_table(code)

    def render_diff(self, diff: DiffBlock) -> str:
        from cleveragents.cli.output._boxdraw import render_element_table

        return render_element_table(diff)

    def render_text(self, text: TextBlock) -> str:
        from cleveragents.cli.output._boxdraw import render_element_table

        return render_element_table(text)

    def render_separator(self, separator: Separator) -> str:
        from cleveragents.cli.output._boxdraw import render_element_table

        return render_element_table(separator)

    def render_action_hint(self, hint: ActionHint) -> str:
        from cleveragents.cli.output._boxdraw import render_element_table

        return render_element_table(hint)

    def can_render(self, terminal_caps: TerminalCapabilities) -> bool:
        return terminal_caps.is_tty


class RichElementRenderer(_BaseElementRenderer):
    """Advanced terminal renderer (delegates to colour for now — SD-2)."""

    format_name: str = "rich"

    def render_panel(self, panel: Panel) -> str:
        from cleveragents.cli.output._color_renderers import render_element_color

        return render_element_color(panel)

    def render_table(self, table: Table) -> str:
        from cleveragents.cli.output._color_renderers import render_element_color

        return render_element_color(table)

    def render_tree(self, tree: Tree) -> str:
        from cleveragents.cli.output._color_renderers import render_element_color

        return render_element_color(tree)

    def render_status(self, status: StatusMessage) -> str:
        from cleveragents.cli.output._color_renderers import render_element_color

        return render_element_color(status)

    def render_progress(self, progress: ProgressIndicator) -> str:
        from cleveragents.cli.output._color_renderers import render_element_color

        return render_element_color(progress)

    def render_code(self, code: CodeBlock) -> str:
        from cleveragents.cli.output._color_renderers import render_element_color

        return render_element_color(code)

    def render_diff(self, diff: DiffBlock) -> str:
        from cleveragents.cli.output._color_renderers import render_element_color

        return render_element_color(diff)

    def render_text(self, text: TextBlock) -> str:
        from cleveragents.cli.output._color_renderers import render_element_color

        return render_element_color(text)

    def render_separator(self, separator: Separator) -> str:
        from cleveragents.cli.output._color_renderers import render_element_color

        return render_element_color(separator)

    def render_action_hint(self, hint: ActionHint) -> str:
        from cleveragents.cli.output._color_renderers import render_element_color

        return render_element_color(hint)

    def can_render(self, terminal_caps: TerminalCapabilities) -> bool:
        return terminal_caps.supports_cursor_movement


class JsonElementRenderer(_BaseElementRenderer):
    """JSON serialization renderer."""

    format_name: str = "json"

    def render_panel(self, panel: Panel) -> str:
        return ""  # JSON renders via serialize()

    def render_table(self, table: Table) -> str:
        return ""

    def render_tree(self, tree: Tree) -> str:
        return ""

    def render_status(self, status: StatusMessage) -> str:
        return ""

    def render_progress(self, progress: ProgressIndicator) -> str:
        return ""

    def render_code(self, code: CodeBlock) -> str:
        return ""

    def render_diff(self, diff: DiffBlock) -> str:
        return ""

    def render_text(self, text: TextBlock) -> str:
        return ""

    def render_separator(self, separator: Separator) -> str:
        return ""

    def render_action_hint(self, hint: ActionHint) -> str:
        return ""

    def serialize(self, output: StructuredOutput) -> str:
        import json

        from cleveragents.cli.output.materializers import _snapshot_to_dict

        return json.dumps(_snapshot_to_dict(output), indent=2, default=str)

    def can_render(self, terminal_caps: TerminalCapabilities) -> bool:
        return True  # JSON works everywhere


class YamlElementRenderer(_BaseElementRenderer):
    """YAML serialization renderer."""

    format_name: str = "yaml"

    def render_panel(self, panel: Panel) -> str:
        return ""  # YAML renders via serialize()

    def render_table(self, table: Table) -> str:
        return ""

    def render_tree(self, tree: Tree) -> str:
        return ""

    def render_status(self, status: StatusMessage) -> str:
        return ""

    def render_progress(self, progress: ProgressIndicator) -> str:
        return ""

    def render_code(self, code: CodeBlock) -> str:
        return ""

    def render_diff(self, diff: DiffBlock) -> str:
        return ""

    def render_text(self, text: TextBlock) -> str:
        return ""

    def render_separator(self, separator: Separator) -> str:
        return ""

    def render_action_hint(self, hint: ActionHint) -> str:
        return ""

    def serialize(self, output: StructuredOutput) -> str:
        import yaml

        from cleveragents.cli.output.materializers import _snapshot_to_dict

        return yaml.safe_dump(
            _snapshot_to_dict(output),
            default_flow_style=False,
            sort_keys=True,
            allow_unicode=True,
        ).rstrip("\n")

    def can_render(self, terminal_caps: TerminalCapabilities) -> bool:
        return True  # YAML works everywhere


# ---------------------------------------------------------------------------
# FormatRegistration and RendererRegistry (spec §27249-27350)
# ---------------------------------------------------------------------------


class FormatRegistration(BaseModel):
    """A registered format: its strategy factory, renderer factory, and fallback.

    Uses Pydantic ``BaseModel`` rather than ``dataclasses.dataclass``
    to satisfy the project's architectural constraint that all structured
    data classes inherit from ``BaseModel``.
    """

    model_config = {"arbitrary_types_allowed": True}

    strategy_factory: Callable[..., Any]
    renderer_factory: Callable[..., Any]
    fallback: str | None


class RendererRegistry:
    """Central registry for format (strategy, renderer) pairs.

    Formats are registered by name and resolved at runtime based on the
    active format and terminal capabilities.  The registry supports dynamic
    registration, enabling plugins to add custom formats (e.g., 'html',
    'csv', 'markdown').

    Built-in registrations:
        "rich"  → (RichMaterializer, RichElementRenderer), fallback="table"
        "table" → (TableMaterializer, TableElementRenderer), fallback="color"
        "color" → (ColorMaterializer, ColorElementRenderer), fallback="plain"
        "plain" → (PlainMaterializer, PlainElementRenderer), fallback=None
        "json"  → (JsonMaterializer, JsonElementRenderer), fallback=None
        "yaml"  → (YamlMaterializer, YamlElementRenderer), fallback=None
    """

    def __init__(self) -> None:
        self._formats: dict[str, FormatRegistration] = {}

    def register(
        self,
        format_name: str,
        strategy_factory: Callable[..., Any],
        renderer_factory: Callable[..., ElementRenderer],
        fallback: str | None = None,
    ) -> None:
        """Register a format.

        Args:
            format_name: The format identifier (e.g., 'rich', 'json').
            strategy_factory: Callable that creates a MaterializationStrategy,
                given terminal capabilities.
            renderer_factory: Callable that creates an ElementRenderer,
                given terminal capabilities.
            fallback: Optional fallback format name if this format cannot
                operate in the current terminal environment.
        """
        self._formats[format_name] = FormatRegistration(
            strategy_factory=strategy_factory,
            renderer_factory=renderer_factory,
            fallback=fallback,
        )

    def resolve(
        self,
        format_name: str,
        terminal_caps: TerminalCapabilities,
    ) -> tuple[Any, ElementRenderer]:
        """Resolve the best (strategy, renderer) pair for the given format.

        Walks the fallback chain if the requested format's renderer cannot
        operate in the current terminal environment.  Returns the first pair
        where the renderer reports ``can_render(terminal_caps) == True``.

        Raises:
            ValueError: If no usable format is found (should never happen
                since 'plain' has no fallback and always works).
        """
        current: str | None = format_name
        visited: set[str] = set()

        while current and current not in visited:
            visited.add(current)
            registration = self._formats.get(current)
            if registration is None:
                break

            renderer = registration.renderer_factory(terminal_caps)
            if renderer.can_render(terminal_caps):
                strategy = registration.strategy_factory(terminal_caps)
                strategy.bind(renderer, terminal_caps=terminal_caps)
                return strategy, renderer

            current = registration.fallback

        # Ultimate fallback is always plain
        if "plain" in self._formats:
            plain = self._formats["plain"]
            renderer = plain.renderer_factory(terminal_caps)
            strategy = plain.strategy_factory(terminal_caps)
            strategy.bind(renderer, terminal_caps=terminal_caps)
            return strategy, renderer

        raise ValueError(
            f"No usable format found for {format_name!r} "
            f"and no 'plain' fallback registered"
        )

    def available_formats(self) -> list[str]:
        """Return all registered format names."""
        return sorted(self._formats.keys())

    def is_registered(self, format_name: str) -> bool:
        """Check if a format is registered."""
        return format_name in self._formats


# ---------------------------------------------------------------------------
# Default registry with built-in formats
# ---------------------------------------------------------------------------


def _create_default_registry() -> RendererRegistry:
    """Create a RendererRegistry with all built-in formats registered."""
    from cleveragents.cli.output.materializers import (
        ColorMaterializer,
        JsonMaterializer,
        PlainMaterializer,
        RichMaterializer,
        TableMaterializer,
        YamlMaterializer,
    )

    registry = RendererRegistry()

    registry.register(
        "plain",
        strategy_factory=lambda caps: PlainMaterializer(),
        renderer_factory=lambda caps: PlainElementRenderer(),
        fallback=None,
    )
    registry.register(
        "color",
        strategy_factory=lambda caps: ColorMaterializer(),
        renderer_factory=lambda caps: ColorElementRenderer(),
        fallback="plain",
    )
    registry.register(
        "table",
        strategy_factory=lambda caps: TableMaterializer(),
        renderer_factory=lambda caps: TableElementRenderer(),
        fallback="color",
    )
    registry.register(
        "rich",
        strategy_factory=lambda caps: RichMaterializer(),
        renderer_factory=lambda caps: RichElementRenderer(),
        fallback="table",
    )
    registry.register(
        "json",
        strategy_factory=lambda caps: JsonMaterializer(),
        renderer_factory=lambda caps: JsonElementRenderer(),
        fallback=None,
    )
    registry.register(
        "yaml",
        strategy_factory=lambda caps: YamlMaterializer(),
        renderer_factory=lambda caps: YamlElementRenderer(),
        fallback=None,
    )

    return registry


#: The default global registry instance with built-in formats.
default_registry: RendererRegistry = _create_default_registry()
