"""Shared renderers for typer-test output tables.

This module centralizes Rich table rendering so typer-test.py stays lean. It includes
specialized handling for planets plus generic list/dict/object renderers.
"""

from __future__ import annotations

from typing import Any, Dict, List, Optional, Sequence

from rich import box
from rich.console import Console, Group
from rich.panel import Panel
from rich.table import Table

from tarot import Card
from utils.attributes import Color, Planet


class Renderer:
    """Render arbitrary Tarot objects using Rich tables."""

    def __init__(self, console: Console) -> None:
        self.console = console
        self.planet_renderer = PlanetRenderer(self)

    # Public helpers -----------------------------------------------------
    def render_value(self, value: Any) -> None:
        if isinstance(value, Planet):
            self.planet_renderer.render(value)
            return
        if isinstance(value, Card):
            self._render_card(value)
            return
        if isinstance(value, list) and value and isinstance(value[0], Card):
            self.print_cards_table(value)
            return
        if hasattr(value, "upright") and hasattr(value, "reversed"):
            self.print_kv_table(
                "Meaning", {"upright": value.upright, "reversed": value.reversed}
            )
            return
        if isinstance(value, dict):
            self._render_dict(value)
            return
        if isinstance(value, list):
            self._render_list(value)
            return
        if hasattr(value, "__dict__"):
            self._render_object(value)
            return
        self.console.print(value)

    def print_kv_table(self, title: str, data: Dict[str, Any]) -> None:
        table = Table(title=title, show_header=False)
        table.add_column("Field", style="bold")
        table.add_column("Value")
        for key, value in data.items():
            table.add_row(self._humanize_key(str(key)), "" if value is None else str(value))
        self.console.print(table)

    def print_rows_table(self, title: str, columns: List[str], rows: List[List[Any]]) -> None:
        table = Table(title=title, show_header=True, show_lines=True)
        for col in columns:
            table.add_column(col)
        for row in rows:
            str_row = ["" if v is None else str(v) for v in row]
            table.add_row(*str_row)
        self.console.print(table)

    def print_cards_table(self, cards: List[Card]) -> None:
        table = Table(title="Cards", show_lines=True)
        table.add_column("#", justify="right")
        table.add_column("Name")
        table.add_column("Arcana")
        table.add_column("Suit")
        table.add_column("Pip/Court")

        for card in cards:
            suit = getattr(getattr(card, "suit", None), "name", "")
            pip = str(getattr(card, "pip", "")) if getattr(card, "pip", None) else ""
            court = getattr(card, "court_rank", "")
            pip_display = court or pip
            table.add_row(
                str(getattr(card, "number", "")),
                getattr(card, "name", ""),
                getattr(card, "arcana", ""),
                suit,
                pip_display,
            )

        self.console.print(table)

    # Internal renderers -------------------------------------------------
    def _render_card(self, card: Card) -> None:
        """Render a card in a rectangular layout with top/middle/bottom bars."""

        name = getattr(card, "name", "")
        arcana = getattr(card, "arcana", "")
        suit_obj = getattr(card, "suit", None)
        suit = getattr(suit_obj, "name", "") if suit_obj is not None else ""
        pip = getattr(card, "pip", None)
        number = getattr(card, "number", "")
        keywords = getattr(card, "keywords", None) or []
        meaning = getattr(card, "meaning", None)

        top = Table.grid(expand=True)
        for align in ("left", "center", "right"):
            top.add_column(justify=align)
        top_left = suit or arcana
        top_center = name
        top_right = f"#{number}" if number not in (None, "") else ""
        top.add_row(top_left, top_center, top_right)

        body_lines = [f"Arcana: {arcana or '-'}"]
        if suit:
            body_lines.append(f"Suit: {suit}")
        if pip:
            body_lines.append(f"Pip: {pip}")
        if getattr(card, "court_rank", ""):
            body_lines.append(f"Court Rank: {card.court_rank}")
        if keywords:
            body_lines.append(f"Keywords: {', '.join(keywords)}")
        guidance = getattr(card, "guidance", None)
        if guidance:
            body_lines.append(f"Guidance: {guidance}")
        body = Panel("\n".join(body_lines), box=box.MINIMAL, padding=(1, 1))

        bottom = Table(
            show_header=False,
            expand=True,
            box=box.SIMPLE_HEAVY,
            show_edge=True,
            pad_edge=False,
        )
        bottom.add_column(justify="left")
        bottom.add_column(justify="center")
        bottom.add_column(justify="right")
        bottom.add_row(
            self._label_block("Upright", getattr(meaning, "upright", None)),
            self._label_block("Reversed", getattr(meaning, "reversed", None)),
            self._label_block("Keywords", ", ".join(keywords) if keywords else None),
        )

        card_panel = Panel(
            Group(top, body, bottom),
            box=box.SQUARE,
            padding=0,
            title=name,
            expand=False,
        )
        self.console.print(card_panel)

    def _render_object(self, obj: Any) -> None:
        attrs = {k: v for k, v in vars(obj).items() if not k.startswith("_")}
        display = {k: self._format_value(v) for k, v in attrs.items()}
        title = getattr(obj, "name", None) or obj.__class__.__name__
        self.print_kv_table(str(title), display)

    def _render_dict(self, data: Dict[Any, Any], title: str = "Result") -> None:
        if not data:
            self.console.print(f"{title}: {{}}")
            return

        values = list(data.values())

        if all(self._is_scalar(v) for v in values):
            self.print_kv_table(title, data)
            return

        if all(isinstance(v, list) for v in values) and values and values[0]:
            first_item = values[0][0]
            if hasattr(first_item, "__dict__"):
                self._render_dict_of_list_objects(data, title)
                return

        if all(hasattr(v, "__dict__") for v in values):
            rows = []
            sample_attrs = self._simple_attrs(values[0])
            columns = ["Key"] + [self._humanize_key(k) for k in sample_attrs.keys()]
            for key, obj in data.items():
                attrs = self._simple_attrs(obj)
                rows.append([key] + [attrs.get(k, "") for k in sample_attrs.keys()])
            self.print_rows_table(title, columns, rows)
            return

        self.print_kv_table(title, {k: self._short_value(v) for k, v in data.items()})

    def _render_list(self, items: List[Any], title: str = "Result") -> None:
        if not items:
            self.console.print(f"{title}: []")
            return

        first = items[0]

        if isinstance(first, Card):
            self.print_cards_table(items)
            return

        if hasattr(first, "__dict__"):
            sample_attrs = self._simple_attrs(first)
            columns = [self._humanize_key(k) for k in sample_attrs.keys()]
            rows = []
            for obj in items:
                attrs = self._simple_attrs(obj)
                rows.append([attrs.get(k, "") for k in sample_attrs.keys()])
            self.print_rows_table(title, columns, rows)
            return

        if isinstance(first, dict):
            keys = list(first.keys())
            columns = [self._humanize_key(k) for k in keys]
            rows = []
            for obj in items:
                rows.append([obj.get(k, "") for k in keys])
            self.print_rows_table(title, columns, rows)
            return

        self.print_kv_table(title, {i: self._short_value(v) for i, v in enumerate(items)})

    def _render_dict_of_list_objects(self, data: Dict[Any, List[Any]], title: str) -> None:
        first_key = next(iter(data))
        first_list = data[first_key]
        if not first_list:
            self.print_kv_table(title, {k: "[]" for k in data.keys()})
            return
        sample_attrs = self._simple_attrs(first_list[0])
        columns = ["Key"] + [self._humanize_key(k) for k in sample_attrs.keys()]
        rows: List[List[Any]] = []
        for key, lst in data.items():
            for obj in lst:
                attrs = self._simple_attrs(obj)
                rows.append([key] + [attrs.get(k, "") for k in sample_attrs.keys()])
        self.print_rows_table(title, columns, rows)

    # Value helpers ------------------------------------------------------
    @staticmethod
    def _simple_attrs(obj: Any) -> Dict[str, Any]:
        return {k: v for k, v in vars(obj).items() if not k.startswith("_")}

    @staticmethod
    def _is_scalar(val: Any) -> bool:
        return isinstance(val, (str, int, float, bool)) or val is None

    def _format_value(self, val: Any, depth: int = 0) -> str:
        if isinstance(val, list):
            if not val:
                return "[]"
            lines = []
            for item in val:
                lines.append("  " * depth + "- " + self._format_value(item, depth + 1).lstrip())
            return "\n".join(lines)
        if isinstance(val, dict):
            lines = []
            for k, v in val.items():
                lines.append(
                    "  " * depth
                    + f"{self._humanize_key(str(k))}: "
                    + self._format_value(v, depth + 1)
                )
            return "\n".join(lines)
        if hasattr(val, "__dict__"):
            inner = {k: v for k, v in vars(val).items() if not k.startswith("_")}
            if not inner:
                return str(val)
            parts = [
                f"{self._humanize_key(str(k))}={self._short_value(v)}" for k, v in inner.items()
            ]
            name = getattr(val, "name", val.__class__.__name__)
            return f"{name}: " + "; ".join(parts)
        return str(val)

    def _short_value(self, val: Any) -> str:
        if isinstance(val, Card):
            return f"Card {getattr(val, 'number', '?')}: {getattr(val, 'name', '')}"
        if isinstance(val, list):
            if not val:
                return "[]"
            preview = ", ".join(self._short_value(v) for v in val[:4])
            suffix = "..." if len(val) > 4 else ""
            return f"[{preview}{suffix}]"
        if isinstance(val, dict):
            return f"dict({len(val)})"
        return str(val)

    @staticmethod
    def _humanize_key(key: str) -> str:
        return key.replace("_", " ")

    @staticmethod
    def _label_block(label: str, value: Optional[str]) -> str:
        safe_val = value if value not in (None, "") else "—"
        return f"{label}:\n{safe_val}"


class PlanetRenderer:
    """Type-specific table rendering for planets."""

    def __init__(self, renderer: Renderer) -> None:
        self.renderer = renderer

    @property
    def console(self) -> Console:
        return self.renderer.console

    def render(self, planet: Planet) -> None:
        base_fields = {
            "Symbol": planet.symbol,
            "Element": planet.element,
            "Ruling Zodiac": self._join_list(planet.ruling_zodiac),
            "Associated Letters": self._join_list(planet.associated_letters),
            "Keywords": self._join_list(planet.keywords),
            "Description": planet.description,
        }
        self.renderer.print_kv_table(f"Planet: {planet.name}", self._compact(base_fields))

        if planet.associated_numbers:
            rows = []
            for number in planet.associated_numbers:
                color_label = self._color_label(number.color)
                rows.append(
                    [
                        getattr(number, "value", ""),
                        getattr(number, "sephera", ""),
                        getattr(number, "element", ""),
                        getattr(number, "compliment", ""),
                        color_label,
                    ]
                )
            self.renderer.print_rows_table(
                "Associated Numbers",
                ["Value", "Sephera", "Element", "Compliment", "Color"],
                rows,
            )

        if planet.color:
            color = planet.color
            color_fields = {
                "Name": color.name,
                "Hex": color.hex_value,
                "Element": color.element,
                "Scale": color.scale,
                "Meaning": color.meaning,
                "Tarot Associations": self._join_list(getattr(color, "tarot_associations", [])),
                "Description": color.description,
            }
            self.renderer.print_kv_table("Planetary Color", self._compact(color_fields))

    @staticmethod
    def _join_list(items: Sequence[Any]) -> str:
        return ", ".join(str(item) for item in items) if items else ""

    @staticmethod
    def _compact(data: Dict[str, Any]) -> Dict[str, Any]:
        return {k: v for k, v in data.items() if v not in (None, "", [], {})}

    @staticmethod
    def _color_label(color: Optional[Color]) -> str:
        if isinstance(color, Color):
            hex_part = f" ({color.hex_value})" if getattr(color, "hex_value", "") else ""
            return f"{color.name}{hex_part}"
        return ""
