"""CLI output helpers built on Rich."""

from __future__ import annotations

import shlex
from collections.abc import Iterator
from contextlib import contextmanager
from typing import TYPE_CHECKING, Any

from rich.console import Console
from rich.panel import Panel
from rich.prompt import Confirm, Prompt
from rich.table import Table

from ilum.errors import IlumError

if TYPE_CHECKING:
    from ilum.core.safety import ValuesDiff


class SpinnerHandle:
    """Handle returned by :meth:`IlumConsole.progress_spinner` for updating text."""

    def __init__(self, status: Any) -> None:
        self._status = status

    def update(self, message: str) -> None:
        """Update the spinner text."""
        if self._status is not None:
            self._status.update(message)


class IlumConsole:
    """Thin wrapper around :class:`rich.console.Console` for consistent CLI output."""

    def __init__(self, verbose: bool = False) -> None:
        self._console = Console()
        self._err_console = Console(stderr=True)
        self.verbose = verbose
        self._quiet = False
        self._output_format = "table"
        self._no_headers = False
        self._field_selector: list[str] | None = None

    @property
    def quiet(self) -> bool:
        return self._quiet

    @quiet.setter
    def quiet(self, value: bool) -> None:
        self._quiet = value

    @property
    def output_format(self) -> str:
        return self._output_format

    @output_format.setter
    def output_format(self, value: str) -> None:
        self._output_format = value

    @property
    def no_headers(self) -> bool:
        return self._no_headers

    @no_headers.setter
    def no_headers(self, value: bool) -> None:
        self._no_headers = value

    @property
    def field_selector(self) -> list[str] | None:
        return self._field_selector

    @field_selector.setter
    def field_selector(self, value: list[str] | None) -> None:
        self._field_selector = value

    # ------------------------------------------------------------------
    # Status messages
    # ------------------------------------------------------------------

    def success(self, message: str) -> None:
        """Print a green checkmark followed by *message*."""
        if self._quiet:
            return
        self._console.print(f"[green]\u2713[/green] {message}")

    def warning(self, message: str) -> None:
        """Print a yellow warning marker followed by *message*."""
        if self._quiet:
            return
        self._console.print(f"[yellow]![/yellow] {message}")

    def error(self, message: str) -> None:
        """Print a red X followed by *message* to stderr."""
        self._err_console.print(f"[red]\u2717[/red] {message}")

    def info(self, message: str) -> None:
        """Print a blue info marker followed by *message*."""
        if self._quiet:
            return
        self._console.print(f"[blue]\u2139[/blue] {message}")

    def debug(self, message: str) -> None:
        """Print *message* in dim text only when verbose mode is enabled."""
        if self.verbose:
            self._console.print(f"[dim]{message}[/dim]")

    # ------------------------------------------------------------------
    # Rich widgets
    # ------------------------------------------------------------------

    def table(self, title: str, columns: list[str], rows: list[list[str]]) -> None:
        """Render a :class:`rich.table.Table` with *title*, *columns*, and *rows*."""
        if self._quiet:
            return
        tbl = Table(title=title, show_header=not self._no_headers)
        for column in columns:
            tbl.add_column(column)
        for row in rows:
            tbl.add_row(*row)
        self._console.print(tbl)

    def panel(self, content: str, title: str = "") -> None:
        """Render a :class:`rich.panel.Panel` with optional *title*."""
        if self._quiet:
            return
        self._console.print(Panel(content, title=title or None))

    @contextmanager
    def status_spinner(self, message: str) -> Iterator[None]:
        """Display a spinner with *message* while the body executes."""
        with self._console.status(message):
            yield

    @contextmanager
    def progress_spinner(self, message: str) -> Iterator[SpinnerHandle]:
        """Display a spinner with updateable text. Suppressed in quiet mode."""
        if self._quiet:
            yield SpinnerHandle(None)
            return
        with self._console.status(message) as status:
            yield SpinnerHandle(status)

    def prompt_text(self, message: str) -> str:
        """Prompt the user for free-form text input."""
        return Prompt.ask(message, console=self._console)

    def operation_summary(self, rows: list[list[str]], title: str = "Operation Summary") -> None:
        """Render a two-column summary table (Setting | Value)."""
        if self._quiet:
            return
        tbl = Table(title=title)
        tbl.add_column("Setting", style="bold")
        tbl.add_column("Value")
        for row in rows:
            tbl.add_row(*row)
        self._console.print(tbl)

    def diff_table(self, diff: ValuesDiff, title: str = "Values Changes") -> None:
        """Render a key-path diff table for Helm values changes."""
        if self._quiet:
            return
        tbl = Table(title=title)
        tbl.add_column("Action", style="bold")
        tbl.add_column("Key")
        tbl.add_column("Current")
        tbl.add_column("New")

        for key, value in sorted(diff.added.items()):
            tbl.add_row("[green]+ added[/green]", key, "\u2014", str(value))
        for key, (old, new) in sorted(diff.changed.items()):
            tbl.add_row("[yellow]~ changed[/yellow]", key, str(old), str(new))
        for key, value in sorted(diff.removed.items()):
            tbl.add_row("[red]- removed[/red]", key, str(value), "\u2014")

        self._console.print(tbl)

    # ------------------------------------------------------------------
    # Command preview
    # ------------------------------------------------------------------

    def command_preview(self, cmd: list[str]) -> None:
        """Display a shell command that is about to be (or would be) executed."""
        if self._quiet:
            return
        self._console.print(
            f"[dim]Command:[/dim] [bold]{shlex.join(cmd)}[/bold]",
            soft_wrap=True,
        )

    # ------------------------------------------------------------------
    # Interactive
    # ------------------------------------------------------------------

    def confirm(self, message: str, default: bool = False) -> bool:
        """Prompt the user for a yes/no answer via :meth:`rich.prompt.Confirm.ask`."""
        return Confirm.ask(message, default=default, console=self._console)

    # ------------------------------------------------------------------
    # Error handling
    # ------------------------------------------------------------------

    def handle_error(self, error: IlumError) -> None:
        """Display an :class:`IlumError`, including its suggestion when present."""
        prefix = f"[{error.error_code}] " if error.error_code else ""
        self.error(f"{prefix}{error}")
        if error.suggestion:
            self.info(error.suggestion)
        if error.recovery_steps:
            self.info("Recovery steps:")
            for i, step in enumerate(error.recovery_steps, 1):
                self.info(f"  {i}. {step}")


# Module-level singleton for convenient access.
console = IlumConsole()
