"""Generic display renderer for tool outputs.

This module provides a declarative, data-driven approach to displaying tool results.
Tools can define `output_display` in their METADATA to customize how results are shown,
or rely on automatic scalar field discovery for simple cases.
"""

from typing import Any

from rich.console import Console
from rich.table import Table

console = Console()

# Fields to exclude from automatic display (envelope, internal, file fields)
EXCLUDED_FIELDS = {
    # Envelope fields
    "status",
    "message",
    "job_id",
    "execution_time_seconds",
    "error_details",
    "warnings",
    # File fields
    "output_files",
    "signed_urls",
    # Internal fields
    "seed",
    "sample_id",
}

# Suffixes that indicate file fields
FILE_SUFFIXES = ("_filepath", "_path", "_file")


def _get_nested_value(data: dict, key_path: str) -> Any:
    """
    Get a nested value from a dict using dot notation and array indexing.

    Examples:
        _get_nested_value(data, "predictions[0].avg_plddt")
        _get_nested_value(data, "sasa_results.total_sasa")
    """
    current = data
    for part in key_path.replace("[", ".[").split("."):
        if not part:
            continue
        if part.startswith("[") and part.endswith("]"):
            # Array index
            try:
                idx = int(part[1:-1])
                if isinstance(current, list) and 0 <= idx < len(current):
                    current = current[idx]
                else:
                    return None
            except (ValueError, TypeError):
                return None
        else:
            # Dict key
            if isinstance(current, dict):
                current = current.get(part)
            else:
                return None
        if current is None:
            return None
    return current


def _is_file_field(key: str) -> bool:
    """Check if a field name indicates a file path."""
    return any(key.endswith(suffix) for suffix in FILE_SUFFIXES)


def _is_scalar(value: Any) -> bool:
    """Check if value is a displayable scalar (int, float, str, bool)."""
    return isinstance(value, (int, float, str, bool)) and not isinstance(value, type)


def _format_value(value: Any, fmt: str | None = None) -> str:
    """Format a value for display."""
    if value is None:
        return "N/A"
    if fmt:
        try:
            return fmt.format(value)
        except (ValueError, KeyError):
            pass
    if isinstance(value, float):
        # Default float formatting
        if abs(value) < 0.01 or abs(value) >= 1000:
            return f"{value:.3e}"
        return f"{value:.4f}"
    if isinstance(value, bool):
        return "Yes" if value else "No"
    return str(value)


def _display_nested_dict(data: dict, indent: int = 2):
    """Display a nested dictionary with proper indentation."""
    prefix = " " * indent
    for key, value in data.items():
        if isinstance(value, dict):
            console.print(f"{prefix}[dim]{key}:[/dim]")
            _display_nested_dict(value, indent + 2)
        elif isinstance(value, list):
            console.print(f"{prefix}[dim]{key}:[/dim]")
            for i, item in enumerate(value):
                if isinstance(item, dict):
                    console.print(f"{prefix}  [{i}]:")
                    _display_nested_dict(item, indent + 4)
                else:
                    console.print(f"{prefix}  {_format_value(item)}")
        else:
            console.print(f"{prefix}{key}: {_format_value(value)}")


def _render_sections(data: dict, sections: list[dict]):
    """Render output based on section definitions."""
    for section in sections:
        title = section.get("title", "Results")
        fields = section.get("fields", [])

        # Check if section uses table format
        table_format = section.get("table")

        if table_format:
            # Render as table
            _render_table_section(data, section)
        else:
            # Render as simple field list
            displayed_any = False
            field_outputs = []

            for field in fields:
                key = field.get("key", "")
                label = field.get("label", key)
                fmt = field.get("format")

                value = _get_nested_value(data, key)
                if value is not None:
                    if isinstance(value, dict):
                        field_outputs.append((label, value, "dict"))
                    else:
                        formatted = _format_value(value, fmt)
                        field_outputs.append((label, formatted, "scalar"))
                    displayed_any = True

            if displayed_any:
                console.print(f"\n[bold]{title}[/bold]")
                for label, value, value_type in field_outputs:
                    if value_type == "dict":
                        console.print(f"  [dim]{label}:[/dim]")
                        _display_nested_dict(value, indent=4)
                    else:
                        console.print(f"  {label}: {value}")


def _render_table_section(data: dict, section: dict):
    """Render a section as a table (for array data like interfaces)."""
    title = section.get("title", "Results")
    table_config = section.get("table", {})
    data_key = table_config.get("data_key", "")
    columns = table_config.get("columns", [])

    # Get the array data
    items = _get_nested_value(data, data_key)
    if not items or not isinstance(items, list):
        return

    # Create table
    table = Table(title=title, show_header=True, header_style="bold cyan")

    for col in columns:
        table.add_column(
            col.get("header", col.get("key", "")),
            justify=col.get("justify", "left"),
            style=col.get("style", ""),
        )

    # Add rows
    for item in items:
        row_values = []
        for col in columns:
            key = col.get("key", "")
            fmt = col.get("format")
            value = _get_nested_value(item, key) if isinstance(item, dict) else item
            row_values.append(_format_value(value, fmt))
        table.add_row(*row_values)

    console.print()
    console.print(table)


def _auto_display_scalars(data: dict, title: str = "Results"):
    """Automatically display all scalar fields not in exclusion list.

    Only shows scalar values (int, float, str, bool) - skips nested dicts and lists.
    """
    displayed = []

    for key, value in data.items():
        # Skip excluded and file fields
        if key in EXCLUDED_FIELDS or _is_file_field(key):
            continue

        # Only show simple scalar values (match original behavior)
        if _is_scalar(value):
            displayed.append((key, _format_value(value)))

    if displayed:
        console.print(f"\n[bold]{title}[/bold]")
        for key, value in displayed:
            console.print(f"  {key}: {value}")


def render_tool_output(result: dict, metadata: dict | None = None, input_params: dict | None = None):
    """
    Render tool output based on METADATA configuration.

    If metadata contains `output_display`, uses that configuration.
    Otherwise, auto-displays all non-file scalar fields.

    Args:
        result: The tool execution result dict
        metadata: Tool METADATA dict (optional, for output_display config)
        input_params: Input parameters (not used by display, reserved for future)
    """
    # Get the data to display - check both top-level and inside data field
    # (gateway wraps worker responses in data)
    data = result.get("data", {}) or {}

    # Check for output_display configuration
    output_display = metadata.get("output_display") if metadata else None

    if output_display:
        # Merge top-level fields with data fields for configured display
        display_data = {**result, **data}

        # Extract data from configured path if specified
        data_path = output_display.get("data_path")
        if data_path:
            extracted = _get_nested_value(display_data, data_path)
            if extracted and isinstance(extracted, dict):
                display_data = extracted

        # Render configured sections
        sections = output_display.get("sections", [])
        if sections:
            _render_sections(display_data, sections)
    else:
        # Auto-display: prefer data dict if it exists, otherwise use result
        # This avoids duplication when both result and result["data"] have the same fields
        if data:
            _auto_display_scalars(data, title="Output data")
        else:
            # Fall back to top-level result fields (excluding envelope)
            _auto_display_scalars(result, title="Output data")
