"""JSON output formatting.

This module owns the "Python object -> JSON-serializable" conversion logic so
other modules don't need to duplicate dataclass handling.
"""

from __future__ import annotations

import json
from dataclasses import asdict, is_dataclass
from typing import Any

from arxivy.models import PaperListMeta, PaperListResult


def _make_serializable(obj: Any) -> Any:
    """Recursively convert dataclass instances to dicts for JSON serialization."""
    if isinstance(obj, PaperListResult):
        # Preserve PaperListResult's omission of empty meta.
        return obj.to_dict()
    if is_dataclass(obj) and not isinstance(obj, type):
        return asdict(obj)
    if isinstance(obj, list):
        return [_make_serializable(item) for item in obj]
    if isinstance(obj, dict):
        return {k: _make_serializable(v) for k, v in obj.items()}
    return obj


def format_json(
    data: Any,
    meta: PaperListMeta | dict[str, Any] | None = None,
    pretty: bool = False,
) -> str:
    """Format data as JSON.

    Args:
        data: Paper data — a single Paper, list of Papers, or
              a dict with "results" and "meta" keys.
        meta: Optional metadata to include.
        pretty: If True, pretty-print with indentation.

    Returns:
        JSON string.
    """
    indent = 2 if pretty else None

    data = _make_serializable(data)

    if isinstance(data, list):
        output: dict[str, Any] = {"results": data}
        if isinstance(meta, PaperListMeta):
            output["meta"] = meta.to_dict()
        elif meta:
            output["meta"] = meta
    elif isinstance(data, dict) and "error" in data:
        # Error payloads are already shaped as {"error": {...}}.
        output = data
        if isinstance(meta, PaperListMeta):
            output.setdefault("meta", {}).update(meta.to_dict())
        elif meta:
            output.setdefault("meta", {}).update(meta)
    elif isinstance(data, dict) and "results" in data:
        output = data
        if isinstance(meta, PaperListMeta):
            output.setdefault("meta", {}).update(meta.to_dict())
        elif meta:
            output.setdefault("meta", {}).update(meta)
    elif isinstance(data, dict):
        output = {"result": data}
        if isinstance(meta, PaperListMeta):
            output["meta"] = meta.to_dict()
        elif meta:
            output["meta"] = meta
    else:
        output = data

    return json.dumps(output, indent=indent, ensure_ascii=False)
