"""WidgetTreeSerializer — DOM to text tree for LLM consumption."""

from __future__ import annotations

from textual.app import App
from textual.scrollbar import ScrollBar, ScrollBarCorner
from textual.widget import Widget

from textual_mcp.serializers.widget_state import WidgetStateExtractor


class WidgetTreeSerializer:
    """Serializes a Textual app's widget tree to an indented text representation.

    Interactive widgets (can_focus=True) are assigned sequential [ref=N] markers
    that can be resolved back to Widget instances via resolve_ref().
    """

    def __init__(self) -> None:
        self._ref_counter: int = 0
        self._ref_map: dict[int, Widget] = {}
        self._extractor = WidgetStateExtractor()

    def serialize(
        self,
        app: App,
        *,
        max_depth: int | None = None,
        include_styles: bool = False,
    ) -> str:
        """Serialize the app's widget tree to an indented text string.

        Args:
            app: The Textual App to serialize.
            max_depth: Maximum nesting depth to include (None = unlimited).
            include_styles: Reserved for future use.

        Returns:
            Multi-line string representation of the widget tree.
        """
        self._ref_counter = 0
        self._ref_map.clear()
        lines: list[str] = []
        screen = app.screen
        self._walk(screen, lines, depth=0, max_depth=max_depth)
        return "\n".join(lines)

    def resolve_ref(self, ref: int) -> Widget | None:
        """Resolve a ref number back to the Widget instance.

        Args:
            ref: The reference number assigned during serialization.

        Returns:
            The Widget, or None if the ref is invalid.
        """
        return self._ref_map.get(ref)

    def _is_excluded(self, widget: Widget) -> bool:
        """Check if a widget should be excluded from the tree."""
        if isinstance(widget, (ScrollBar, ScrollBarCorner)):
            return True
        if not widget.display:
            return True
        return False

    def _walk(
        self,
        widget: Widget,
        lines: list[str],
        depth: int,
        max_depth: int | None,
    ) -> None:
        """Recursively walk the widget tree, appending formatted lines."""
        if self._is_excluded(widget):
            return

        lines.append(self._format_widget(widget, depth))

        if max_depth is not None and depth >= max_depth:
            return

        for child in widget.children:
            self._walk(child, lines, depth + 1, max_depth)

    def _format_widget(self, widget: Widget, depth: int) -> str:
        """Format a single widget as a text line."""
        indent = "  " * depth
        parts: list[str] = []

        # Ref for interactive widgets
        if widget.can_focus:
            self._ref_counter += 1
            self._ref_map[self._ref_counter] = widget
            parts.append(f"[ref={self._ref_counter}]")

        # Widget type name
        parts.append(type(widget).__name__)

        # Widget ID
        if widget.id:
            parts[-1] += f"#{widget.id}"

        # CSS classes
        for cls in sorted(widget.classes):
            parts[-1] += f".{cls}"

        # State flags
        if widget.has_focus:
            parts.append("FOCUSED")
        if widget.disabled:
            parts.append("disabled")

        # Type-specific properties from WidgetStateExtractor
        props = WidgetStateExtractor.extract(widget)
        for key, value in props.items():
            parts.append(f"{key}={value!r}")

        return indent + " ".join(parts)
