"""ANSI colour renderers for the output rendering framework.

Provides colour-coded rendering using ANSI escape sequences for all 10
element types.  Extracted from ``_renderers.py`` for file-length
compliance (R2-C2 fix).
"""

from __future__ import annotations

from cleveragents.cli.output._renderers import (
    _render_code_plain,
    _render_progress_plain,
    _render_tree_plain,
    _sanitize,
    _sort_table_rows,
    compute_column_widths,
)
from cleveragents.cli.output.handles import (
    ActionHint,
    CodeBlock,
    DiffBlock,
    ElementSnapshot,
    Panel,
    ProgressIndicator,
    Separator,
    StatusMessage,
    Table,
    TextBlock,
    Tree,
)

# ---------------------------------------------------------------------------
# ANSI colour codes
# ---------------------------------------------------------------------------

_BOLD = "\033[1m"
_CYAN = "\033[36m"
_GREEN = "\033[32m"
_YELLOW = "\033[33m"
_RED = "\033[31m"
_RESET = "\033[0m"
_DIM = "\033[2m"


# ---------------------------------------------------------------------------
# Color renderers
# ---------------------------------------------------------------------------


def _render_panel_color(panel: Panel) -> str:
    """Render a panel with ANSI colour codes."""
    title = _sanitize(panel.title)
    lines: list[str] = [f"{_BOLD}{_CYAN}{title}{_RESET}"]
    for entry in panel.entries:
        key = _sanitize(entry.key)
        value = _sanitize(entry.value)
        value_str = value
        if entry.style_hint == "success":
            value_str = f"{_GREEN}{value}{_RESET}"
        elif entry.style_hint == "warning":
            value_str = f"{_YELLOW}{value}{_RESET}"
        elif entry.style_hint == "error":
            value_str = f"{_RED}{value}{_RESET}"
        lines.append(f"  {_BOLD}{key}{_RESET}: {value_str}")
    return "\n".join(lines)


def _render_table_color(table: Table) -> str:
    """Render a table with ANSI colour codes."""
    if not table.columns:
        return f"{_DIM}(empty table){_RESET}"

    col_names = [_sanitize(c.name) for c in table.columns]
    rows = _sort_table_rows(table)
    str_rows: list[list[str]] = []
    for row in rows:
        str_rows.append([_sanitize(str(row.get(c.name, ""))) for c in table.columns])

    widths = compute_column_widths(col_names, str_rows)

    header = "  ".join(
        f"{_BOLD}{_CYAN}{n.ljust(w)}{_RESET}"
        for n, w in zip(col_names, widths, strict=False)
    )
    sep = "  ".join("-" * w for w in widths)

    lines: list[str] = []
    if table.title:
        lines.append(f"{_BOLD}{_sanitize(table.title)}{_RESET}")
    lines.append(header)
    lines.append(sep)
    for row in str_rows:
        lines.append("  ".join(c.ljust(w) for c, w in zip(row, widths, strict=False)))

    if table.summary:
        lines.append(sep)
        summary_parts: list[str] = []
        for key, val in table.summary.items():
            summary_parts.append(
                f"{_BOLD}{_sanitize(str(key))}{_RESET}: {_sanitize(str(val))}"
            )
        lines.append("  ".join(summary_parts))

    return "\n".join(lines)


def _render_status_color(status: StatusMessage) -> str:
    """Render a status message with ANSI colour codes."""
    colour_map = {"ok": _GREEN, "warn": _YELLOW, "error": _RED, "info": _CYAN}
    prefix_map = {
        "ok": "[OK]",
        "warn": "[WARN]",
        "error": "[ERROR]",
        "info": "[INFO]",
    }
    colour = colour_map.get(status.level, "")
    prefix = prefix_map.get(status.level, f"[{status.level.upper()}]")
    msg = _sanitize(status.message)
    line = f"{colour}{_BOLD}{prefix}{_RESET} {msg}"
    if status.detail:
        line += f"\n  {_DIM}{_sanitize(status.detail)}{_RESET}"
    return line


def _render_progress_color(progress: ProgressIndicator) -> str:
    """Render a progress indicator with ANSI colour codes."""
    plain = _render_progress_plain(progress)
    label = _sanitize(progress.label)
    lines = plain.split("\n", 1)
    first_line = lines[0]
    coloured_label = f"{_BOLD}{_CYAN}{label}{_RESET}"
    if first_line.startswith(label):
        first_line = coloured_label + first_line[len(label) :]
    rest = f"\n{lines[1]}" if len(lines) > 1 else ""
    return first_line + rest


def _render_tree_color(tree: Tree) -> str:
    """Render a tree with ANSI colour codes."""
    plain = _render_tree_plain(tree)
    root_label = _sanitize(tree.root.label)
    coloured_label = f"{_BOLD}{_CYAN}{root_label}{_RESET}"
    lines = plain.split("\n", 1)
    first_line = lines[0]
    idx = first_line.find(root_label)
    if idx >= 0:
        first_line = (
            first_line[:idx] + coloured_label + first_line[idx + len(root_label) :]
        )
    rest = f"\n{lines[1]}" if len(lines) > 1 else ""
    return first_line + rest


def _render_code_color(code: CodeBlock) -> str:
    """Render a code block with ANSI colour codes."""
    lines: list[str] = []
    if code.language:
        lines.append(f"{_DIM}[{_sanitize(code.language)}]{_RESET}")
    lines.append(_render_code_plain(code))
    return "\n".join(lines)


def _render_diff_color(diff: DiffBlock) -> str:
    """Render a diff block with ANSI colour codes."""
    lines: list[str] = []
    if diff.file_a or diff.file_b:
        lines.append(f"{_BOLD}--- {_sanitize(diff.file_a or '/dev/null')}{_RESET}")
        lines.append(f"{_BOLD}+++ {_sanitize(diff.file_b or '/dev/null')}{_RESET}")
    for hunk in diff.hunks:
        lines.append(f"{_CYAN}{_sanitize(hunk.header)}{_RESET}")
        for dline in hunk.lines:
            content = _sanitize(dline.content)
            if dline.line_type == "add":
                lines.append(f"{_GREEN}+{content}{_RESET}")
            elif dline.line_type == "remove":
                lines.append(f"{_RED}-{content}{_RESET}")
            else:
                lines.append(f" {content}")
    if diff.stats:
        ins = diff.stats.get("insertions", 0)
        dels = diff.stats.get("deletions", 0)
        lines.append(
            f"{_GREEN}{ins} insertion(s){_RESET}, {_RED}{dels} deletion(s){_RESET}"
        )
    return "\n".join(lines)


def _render_separator_color(sep: Separator) -> str:
    """Render a separator with ANSI colour codes."""
    if sep.style == "blank":
        return ""
    line_char = "=" if sep.style == "double" else "-"
    return f"{_DIM}{line_char * 40}{_RESET}"


def _render_action_hint_color(hint: ActionHint) -> str:
    """Render an action hint with ANSI colour codes."""
    lines: list[str] = []
    if hint.description:
        lines.append(f"{_DIM}{_sanitize(hint.description)}{_RESET}")
    if hint.commands:
        lines.append(f"{_BOLD}Next steps:{_RESET}")
        for cmd in hint.commands:
            lines.append(f"  $ {_CYAN}{_sanitize(cmd)}{_RESET}")
    return "\n".join(lines)


def _render_text_color(text: TextBlock) -> str:
    """Render a text block with ANSI colour codes.

    P2-2 fix (Luis review): TextBlock now gets colour treatment instead
    of falling through to the plain renderer.  The first line gets bold
    formatting and indent markers use dim styling.
    """
    content = _sanitize(text.content)
    indent_str = " " * text.indent
    if text.wrap:
        import textwrap

        width = max(80 - text.indent, 20)
        wrapped_lines: list[str] = []
        for line in content.split("\n"):
            if line:
                wrapped_lines.append(
                    textwrap.fill(
                        line,
                        width=width,
                        initial_indent=f"{_DIM}{indent_str}{_RESET}",
                        subsequent_indent=f"{_DIM}{indent_str}{_RESET}",
                    )
                )
            else:
                wrapped_lines.append("")
        return "\n".join(wrapped_lines)
    return "\n".join(
        f"{_DIM}{indent_str}{_RESET}{line}" for line in content.split("\n")
    )


def render_element_color(element: ElementSnapshot) -> str:
    """Dispatch to the correct colour renderer."""
    if isinstance(element, Panel):
        return _render_panel_color(element)
    if isinstance(element, Table):
        return _render_table_color(element)
    if isinstance(element, StatusMessage):
        return _render_status_color(element)
    if isinstance(element, ProgressIndicator):
        return _render_progress_color(element)
    if isinstance(element, Tree):
        return _render_tree_color(element)
    if isinstance(element, TextBlock):
        return _render_text_color(element)
    if isinstance(element, CodeBlock):
        return _render_code_color(element)
    if isinstance(element, DiffBlock):
        return _render_diff_color(element)
    if isinstance(element, Separator):
        return _render_separator_color(element)
    if isinstance(element, ActionHint):
        return _render_action_hint_color(element)
    return _sanitize(str(element))  # pragma: no cover
