"""CLI output formatters and styling utilities."""

import sys
from enum import Enum
from typing import Any, Dict, List, Optional


class Color(str, Enum):
    """ANSI color codes."""

    RESET = "\033[0m"
    BOLD = "\033[1m"
    DIM = "\033[2m"
    UNDERLINE = "\033[4m"

    BLACK = "\033[30m"
    RED = "\033[31m"
    GREEN = "\033[32m"
    YELLOW = "\033[33m"
    BLUE = "\033[34m"
    MAGENTA = "\033[35m"
    CYAN = "\033[36m"
    WHITE = "\033[37m"

    BRIGHT_BLACK = "\033[90m"
    BRIGHT_RED = "\033[91m"
    BRIGHT_GREEN = "\033[92m"
    BRIGHT_YELLOW = "\033[93m"
    BRIGHT_BLUE = "\033[94m"
    BRIGHT_MAGENTA = "\033[95m"
    BRIGHT_CYAN = "\033[96m"
    BRIGHT_WHITE = "\033[97m"


class Symbol(str, Enum):
    """Unicode symbols for output."""

    SUCCESS = "✓"
    ERROR = "✗"
    WARNING = "⚠"
    INFO = "ℹ"
    ARROW = "→"
    BULLET = "•"
    SPINNER = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"


class OutputFormatter:
    """Format CLI output with colors and styling."""

    def __init__(self, use_color: bool = True):
        """Initialize formatter.

        Args:
            use_color: Enable color output
        """
        self.use_color = use_color and sys.stdout.isatty()

    def colorize(self, text: str, color: Color, bold: bool = False) -> str:
        """Apply color to text.

        Args:
            text: Text to colorize
            color: Color to apply
            bold: Make text bold

        Returns:
            Colorized text
        """
        if not self.use_color:
            return text

        result = f"{color}{text}{Color.RESET}"
        if bold:
            result = f"{Color.BOLD}{result}"
        return result

    def success(self, message: str) -> str:
        """Format success message.

        Args:
            message: Message text

        Returns:
            Formatted message
        """
        symbol = self.colorize(Symbol.SUCCESS.value, Color.GREEN, bold=True)
        return f"{symbol} {message}"

    def error(self, message: str) -> str:
        """Format error message.

        Args:
            message: Message text

        Returns:
            Formatted message
        """
        symbol = self.colorize(Symbol.ERROR.value, Color.RED, bold=True)
        return f"{symbol} {message}"

    def warning(self, message: str) -> str:
        """Format warning message.

        Args:
            message: Message text

        Returns:
            Formatted message
        """
        symbol = self.colorize(Symbol.WARNING.value, Color.YELLOW, bold=True)
        return f"{symbol} {message}"

    def info(self, message: str) -> str:
        """Format info message.

        Args:
            message: Message text

        Returns:
            Formatted message
        """
        symbol = self.colorize(Symbol.INFO.value, Color.BLUE, bold=True)
        return f"{symbol} {message}"

    def heading(self, text: str, level: int = 1) -> str:
        """Format heading.

        Args:
            text: Heading text
            level: Heading level (1-3)

        Returns:
            Formatted heading
        """
        if level == 1:
            return self.colorize(text, Color.CYAN, bold=True)
        elif level == 2:
            return self.colorize(text, Color.BLUE, bold=True)
        else:
            return self.colorize(text, Color.WHITE, bold=True)

    def dim(self, text: str) -> str:
        """Format dim text.

        Args:
            text: Text to dim

        Returns:
            Formatted text
        """
        if not self.use_color:
            return text
        return f"{Color.DIM}{text}{Color.RESET}"

    def bold(self, text: str) -> str:
        """Format bold text.

        Args:
            text: Text to make bold

        Returns:
            Formatted text
        """
        if not self.use_color:
            return text
        return f"{Color.BOLD}{text}{Color.RESET}"

    def key_value(self, key: str, value: Any, indent: int = 0) -> str:
        """Format key-value pair.

        Args:
            key: Key text
            value: Value
            indent: Indentation level

        Returns:
            Formatted pair
        """
        spaces = "  " * indent
        key_formatted = self.colorize(key, Color.CYAN)
        return f"{spaces}{key_formatted}: {value}"

    def bullet_list(self, items: List[str], indent: int = 0) -> str:
        """Format bullet list.

        Args:
            items: List items
            indent: Indentation level

        Returns:
            Formatted list
        """
        spaces = "  " * indent
        bullet = self.colorize(Symbol.BULLET.value, Color.BLUE)
        lines = [f"{spaces}{bullet} {item}" for item in items]
        return "\n".join(lines)

    def section(self, title: str, content: str) -> str:
        """Format section with title.

        Args:
            title: Section title
            content: Section content

        Returns:
            Formatted section
        """
        separator = self.dim("─" * 50)
        title_formatted = self.heading(title, level=2)
        return f"\n{title_formatted}\n{separator}\n{content}\n"

    def table(self, headers: List[str], rows: List[List[str]]) -> str:
        """Format simple table.

        Args:
            headers: Table headers
            rows: Table rows

        Returns:
            Formatted table
        """
        if not rows:
            return ""

        col_widths = [len(h) for h in headers]
        for row in rows:
            for i, cell in enumerate(row):
                col_widths[i] = max(col_widths[i], len(str(cell)))

        header_line = "  ".join(
            self.bold(h.ljust(w)) for h, w in zip(headers, col_widths)
        )
        separator = self.dim("─" * (sum(col_widths) + 2 * (len(headers) - 1)))

        row_lines = []
        for row in rows:
            row_line = "  ".join(
                str(cell).ljust(w) for cell, w in zip(row, col_widths)
            )
            row_lines.append(row_line)

        return f"{header_line}\n{separator}\n" + "\n".join(row_lines)

    def json_tree(self, data: Dict[str, Any], indent: int = 0) -> str:
        """Format JSON-like tree structure.

        Args:
            data: Dictionary data
            indent: Current indentation level

        Returns:
            Formatted tree
        """
        lines = []
        spaces = "  " * indent

        for key, value in data.items():
            key_formatted = self.colorize(key, Color.CYAN)

            if isinstance(value, dict):
                lines.append(f"{spaces}{key_formatted}:")
                lines.append(self.json_tree(value, indent + 1))
            elif isinstance(value, list):
                lines.append(f"{spaces}{key_formatted}:")
                for item in value:
                    if isinstance(item, dict):
                        lines.append(self.json_tree(item, indent + 1))
                    else:
                        lines.append(f"{spaces}  {Symbol.BULLET.value} {item}")
            else:
                value_str = self.colorize(str(value), Color.GREEN)
                lines.append(f"{spaces}{key_formatted}: {value_str}")

        return "\n".join(lines)


class ProgressBar:
    """Simple progress bar for CLI."""

    def __init__(
        self,
        total: int,
        width: int = 40,
        formatter: Optional[OutputFormatter] = None
    ):
        """Initialize progress bar.

        Args:
            total: Total number of items
            width: Width of progress bar
            formatter: Output formatter
        """
        self.total = total
        self.width = width
        self.current = 0
        self.formatter = formatter or OutputFormatter()

    def update(self, n: int = 1) -> None:
        """Update progress.

        Args:
            n: Number of items completed
        """
        self.current += n
        self._render()

    def _render(self) -> None:
        """Render progress bar."""
        if not sys.stdout.isatty():
            return

        percent = self.current / self.total if self.total > 0 else 0
        filled = int(self.width * percent)
        bar = "█" * filled + "░" * (self.width - filled)

        bar_colored = self.formatter.colorize(bar, Color.GREEN)
        percent_str = f"{percent * 100:.0f}%"

        line = f"\r[{bar_colored}] {percent_str} ({self.current}/{self.total})"
        sys.stdout.write(line)
        sys.stdout.flush()

        if self.current >= self.total:
            sys.stdout.write("\n")
            sys.stdout.flush()

    def finish(self) -> None:
        """Complete progress bar."""
        self.current = self.total
        self._render()


class Spinner:
    """Animated spinner for CLI."""

    def __init__(self, message: str = "", formatter: Optional[OutputFormatter] = None):
        """Initialize spinner.

        Args:
            message: Message to display
            formatter: Output formatter
        """
        self.message = message
        self.formatter = formatter or OutputFormatter()
        self.frames = list(Symbol.SPINNER)
        self.current_frame = 0
        self.running = False

    def start(self) -> None:
        """Start spinner."""
        self.running = True
        self._render()

    def update(self, message: str) -> None:
        """Update spinner message.

        Args:
            message: New message
        """
        self.message = message
        self._render()

    def _render(self) -> None:
        """Render spinner frame."""
        if not sys.stdout.isatty():
            return

        frame = self.frames[self.current_frame]
        frame_colored = self.formatter.colorize(frame, Color.CYAN)

        line = f"\r{frame_colored} {self.message}"
        sys.stdout.write(line)
        sys.stdout.flush()

        self.current_frame = (self.current_frame + 1) % len(self.frames)

    def stop(self, final_message: Optional[str] = None) -> None:
        """Stop spinner.

        Args:
            final_message: Final message to display
        """
        self.running = False

        if final_message:
            sys.stdout.write(f"\r{final_message}\n")
        else:
            sys.stdout.write("\r" + " " * (len(self.message) + 2) + "\r")

        sys.stdout.flush()


def format_error_details(
    error: Exception,
    formatter: Optional[OutputFormatter] = None
) -> str:
    """Format error with details.

    Args:
        error: Exception to format
        formatter: Output formatter

    Returns:
        Formatted error message
    """
    fmt = formatter or OutputFormatter()

    error_type = fmt.colorize(type(error).__name__, Color.RED, bold=True)
    error_msg = str(error)

    result = f"{error_type}: {error_msg}"

    if hasattr(error, "__dict__"):
        details = {k: v for k, v in error.__dict__.items() if not k.startswith("_")}
        if details:
            result += "\n\n" + fmt.heading("Details:", level=3)
            result += "\n" + fmt.json_tree(details, indent=1)

    return result
