"""Output formatters for CLI display.

This module provides formatter classes for tool requests and responses.
Formatters are composable - they accept custom filter pipelines, extractors,
and tool-specific handlers.

Example:
    formatter = ToolOutputFormatter(verbose=True)
    output = formatter.format(tool_output)

    # With custom handler for a specific tool
    from .handlers import TaskOutputHandler
    formatter = ToolOutputFormatter(
        output_handlers={"Task": TaskOutputHandler(max_lines=20)}
    )
"""

from __future__ import annotations

import json
from dataclasses import dataclass, field
from typing import Any, Callable

from .defaults import CLI_TOOL_RESPONSE_FILTERS
from .extractors import extract_text
from .filters import FilterPipeline
from .handlers import (
    DefaultOutputHandler,
    OutputHandler,
    RequestHandler,
    default_output_handlers,
    default_request_handlers,
)


def clean_lines(text: str) -> list[str]:
    """Strip trailing whitespace and remove leading/trailing empty lines.

    This is a pure utility function for text processing.

    Args:
        text: Raw text with potential whitespace issues.

    Returns:
        List of cleaned lines.
    """
    lines = [line.rstrip() for line in text.split("\n")]

    # Remove leading empty lines
    while lines and not lines[0].strip():
        lines.pop(0)

    # Remove trailing empty lines
    while lines and not lines[-1].strip():
        lines.pop()

    return lines


@dataclass
class ToolOutputFormatter:
    """Format tool outputs for CLI display.

    This formatter orchestrates:
    1. Text extraction from polymorphic outputs
    2. Filtering via pipeline to remove metadata
    3. Line cleaning (whitespace normalization)
    4. Rendering via output handlers

    The separation of concerns ensures:
    - Extraction/filtering logic is reusable across all tools
    - Handlers focus only on rendering cleaned content
    - Tool-specific behavior is composable and testable

    Args:
        filter_pipeline: Pipeline to apply. Defaults to CLI_TOOL_RESPONSE_FILTERS.
        text_extractor: Function to extract text. Defaults to extract_text.
        max_lines: Maximum lines to show in non-verbose mode.
        verbose: If True, show all lines without truncation.
        output_handlers: Registry of tool-specific output handlers.
    """

    filter_pipeline: FilterPipeline = field(default_factory=lambda: CLI_TOOL_RESPONSE_FILTERS)
    text_extractor: Callable[[Any], str] = field(default=extract_text)
    max_lines: int = 15
    verbose: bool = False
    output_handlers: dict[str, OutputHandler] = field(init=False)

    def __post_init__(self) -> None:
        """Initialize output handlers with current settings."""
        self.output_handlers = default_output_handlers(max_lines=self.max_lines, verbose=self.verbose)
        # Also create default handler with same settings
        self._default_handler = DefaultOutputHandler(max_lines=self.max_lines, verbose=self.verbose)

    def format(self, output: Any, tool_name: str | None = None) -> str:
        """Format tool output for display.

        Args:
            output: Raw tool output (str, list, dict, or other).
            tool_name: Optional tool name for tool-specific rendering.

        Returns:
            Formatted string with Rich markup.
        """
        if output is None:
            # Use handler for empty output if available
            handler = self.output_handlers.get(tool_name) if tool_name else None
            if handler:
                return handler.render([])
            return self._default_handler.render([])

        # Extract text from polymorphic output
        raw_text = self.text_extractor(output)

        # Apply filter pipeline
        filtered_text = self.filter_pipeline.apply(raw_text)

        # Clean up whitespace
        lines = clean_lines(filtered_text)

        # Render with appropriate handler
        handler = self.output_handlers.get(tool_name) if tool_name else None
        if handler:
            return handler.render(lines)

        return self._default_handler.render(lines)


@dataclass
class ToolRequestFormatter:
    """Format tool requests for CLI display.

    This formatter renders tool names and arguments with Rich markup.
    Supports tool-specific handlers for custom rendering (e.g., Task tool).

    Args:
        verbose: If True, show full argument values without truncation.
        max_value_length: Maximum length for argument values in non-verbose mode.
        request_handlers: Registry of tool-specific request handlers.
    """

    verbose: bool = False
    max_value_length: int = 70
    request_handlers: dict[str, RequestHandler] = field(default_factory=default_request_handlers)

    def format(self, tool_name: str, input_json: str) -> str:
        """Format a tool request for display.

        Args:
            tool_name: Name of the tool being called.
            input_json: JSON string of tool arguments.

        Returns:
            Formatted string with Rich markup.
        """
        # Check for tool-specific handler
        if tool_name in self.request_handlers:
            try:
                args = json.loads(input_json)
                return self.request_handlers[tool_name].format(args)
            except json.JSONDecodeError:
                pass  # Fall through to default formatting

        return self._format_default(tool_name, input_json)

    def _format_default(self, tool_name: str, input_json: str) -> str:
        """Default formatting for tool requests."""
        lines = [f"\n[yellow bold][{tool_name}][/yellow bold]"]

        try:
            args = json.loads(input_json)
            for key, value in args.items():
                formatted = self._format_arg(key, value)
                lines.append(formatted)
        except json.JSONDecodeError:
            # Fallback for invalid JSON
            truncated = input_json[: self.max_value_length]
            if len(input_json) > self.max_value_length:
                truncated += "..."
            lines.append(f"  | {truncated}")

        return "\n".join(lines)

    def _format_arg(self, key: str, value: Any) -> str:
        """Format a single argument key-value pair."""
        if isinstance(value, str):
            return self._format_string_arg(key, value)

        # Non-string values: JSON serialize
        display = json.dumps(value)
        if len(display) > self.max_value_length and not self.verbose:
            display = display[: self.max_value_length - 3] + "..."
        return f"  | {key}: {display}"

    def _format_string_arg(self, key: str, value: str) -> str:
        """Format a string argument with special handling for multiline/long values."""
        is_multiline = "\n" in value
        is_long = len(value) > self.max_value_length

        if is_multiline or is_long:
            if self.verbose:
                # Show full value with each line indented
                lines = [f"  | {key}:"]
                for line in value.split("\n"):
                    lines.append(f"  |   {line}")
                return "\n".join(lines)
            else:
                # Truncate to single line
                display = value.replace("\n", " ")
                if len(display) > self.max_value_length:
                    display = display[: self.max_value_length - 3] + "..."
                return f"  | {key}: {display}"

        return f"  | {key}: {value}"
