"""Shared output formatting utilities for cross-layer use.

Provides lightweight serialisation helpers (JSON, YAML, plain text, ASCII
table) that can be used by any layer without importing from the CLI
presentation layer.

This module intentionally has **no** dependencies on ``cleveragents.cli.*``
so that Application and Domain layer code can format data for output without
creating a reverse dependency on the Presentation layer.

For full-featured CLI output (Rich console, colour, envelope wrapping, etc.)
use ``cleveragents.cli.formatting`` instead.
"""

from __future__ import annotations

import json
from datetime import datetime
from enum import Enum
from io import StringIO
from typing import Any

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


def _serialize_value(value: Any) -> Any:
    """Recursively normalise values for JSON/YAML serialisation.

    * ``datetime`` -> ISO-8601 string
    * ``Enum``     -> ``.value``
    * ``dict``     -> recursed
    * ``list``     -> recursed
    * everything else -> unchanged
    """
    if isinstance(value, datetime):
        return value.isoformat()
    if isinstance(value, Enum):
        return value.value
    if isinstance(value, dict):
        return {k: _serialize_value(v) for k, v in value.items()}
    if isinstance(value, list):
        return [_serialize_value(item) for item in value]
    return value


def format_as_json(data: dict[str, Any] | list[dict[str, Any]]) -> str:
    """Render *data* as indented JSON."""
    return json.dumps(_serialize_value(data), indent=2, default=str)


def format_as_yaml(data: dict[str, Any] | list[dict[str, Any]]) -> str:
    """Render *data* as YAML."""
    return yaml.dump(
        _serialize_value(data),
        default_flow_style=False,
        sort_keys=False,
        allow_unicode=True,
    ).rstrip("\n")


def format_as_plain(data: dict[str, Any] | list[dict[str, Any]]) -> str:
    """Render *data* as plain ``key: value`` lines."""
    if isinstance(data, list):
        parts: list[str] = []
        for idx, item in enumerate(data):
            if idx > 0:
                parts.append("---")
            parts.append(_format_plain_dict(item))
        return "\n".join(parts)
    return _format_plain_dict(data)


def _format_plain_dict(data: dict[str, Any]) -> str:
    lines: list[str] = []
    for key, value in data.items():
        serialised = _serialize_value(value)
        if isinstance(serialised, dict):
            lines.append(f"{key}:")
            for k2, v2 in serialised.items():
                lines.append(f"  {k2}: {v2}")
        elif isinstance(serialised, list):
            lines.append(f"{key}:")
            for item in serialised:
                if isinstance(item, dict):
                    lines.append(f"  - {json.dumps(item, default=str)}")
                else:
                    lines.append(f"  - {item}")
        else:
            lines.append(f"{key}: {serialised}")
    return "\n".join(lines)


def format_as_table(data: dict[str, Any] | list[dict[str, Any]]) -> str:
    """Render *data* as an ASCII table (no Rich styling)."""
    rows: list[dict[str, Any]] = [data] if isinstance(data, dict) else data

    if not rows:
        return "(empty)"

    columns: list[str] = list(rows[0].keys())
    for row in rows[1:]:
        for key in row:
            if key not in columns:
                columns.append(key)

    table = Table(show_header=True, show_edge=True)
    for col in columns:
        table.add_column(col)
    for row in rows:
        cells: list[str] = []
        for col in columns:
            val = row.get(col, "")
            serialised = _serialize_value(val)
            if isinstance(serialised, (dict, list)):
                cells.append(json.dumps(serialised, default=str))
            else:
                cells.append(str(serialised))
        table.add_row(*cells)

    buf = StringIO()
    console = Console(file=buf, width=200, no_color=True)
    console.print(table)
    return buf.getvalue().rstrip("\n")


def format_data(
    data: dict[str, Any] | list[dict[str, Any]],
    format_type: str,
) -> str:
    """Format *data* according to *format_type*.

    Supported format types: ``json``, ``yaml``, ``plain``, ``table``.
    Any other value falls back to JSON.

    This function is intentionally minimal -- it does **not** wrap output in
    the CLI envelope (``command``, ``status``, ``exit_code``, etc.) and does
    **not** write to ``sys.stdout`` directly.  It simply returns the rendered
    string so the caller can decide what to do with it.

    For full CLI output with envelope and Rich console support use
    ``cleveragents.cli.formatting.format_output`` instead.
    """
    fmt = format_type.lower()
    if fmt == "json":
        return format_as_json(data)
    if fmt == "yaml":
        return format_as_yaml(data)
    if fmt == "plain":
        return format_as_plain(data)
    if fmt == "table":
        return format_as_table(data)
    # Fallback: JSON
    return format_as_json(data)


__all__ = [
    "format_as_json",
    "format_as_plain",
    "format_as_table",
    "format_as_yaml",
    "format_data",
]
