"""Display helpers for the ``agents config get`` command.

Provides spec-required formatting utilities for converting internal
``ConfigService`` data structures to the output format defined in
``docs/specification.md`` (section "agents config get").

Separated from ``config.py`` to keep that module under the 500-line limit.
"""

from __future__ import annotations

from typing import Any

from cleveragents.application.services.config_service import ConfigLevel

# ---------------------------------------------------------------------------
# Source name mappings
# ---------------------------------------------------------------------------

# Mapping from internal ConfigLevel values to spec-required human-readable
# names used in the resolution chain and winner objects.
_SOURCE_DISPLAY_NAMES: dict[str, str] = {
    ConfigLevel.CLI_FLAG.value: "CLI flag",
    ConfigLevel.ENV_VAR.value: "Env var",
    ConfigLevel.LOCAL.value: "Local file",
    ConfigLevel.PROJECT.value: "Project file",
    ConfigLevel.GLOBAL.value: "Config file",
    ConfigLevel.DEFAULT.value: "Default",
}

# Short source names used in the top-level ``data.source`` field and the
# Rich ``Source:`` line.  These match the spec examples exactly.
_SOURCE_SHORT_NAMES: dict[str, str] = {
    ConfigLevel.CLI_FLAG.value: "cli",
    ConfigLevel.ENV_VAR.value: "env",
    ConfigLevel.LOCAL.value: "local",
    ConfigLevel.PROJECT.value: "project",
    ConfigLevel.GLOBAL.value: "config",
    ConfigLevel.DEFAULT.value: "default",
}

# Mapping from Python type names to spec-required type strings.
_PYTHON_TYPE_TO_SPEC: dict[str, str] = {
    "str": "string",
    "bool": "boolean",
    "int": "integer",
    "float": "number",
    "NoneType": "null",
    "None": "null",
}

# ---------------------------------------------------------------------------
# Public helpers
# ---------------------------------------------------------------------------


def spec_type_name(value: Any) -> str:
    """Return the spec-required type string for *value*.

    Maps Python type names to the spec-required strings:
    ``str`` → ``string``, ``bool`` → ``boolean``, ``int`` → ``integer``,
    ``float`` → ``number``, ``None`` → ``null``.
    """
    if value is None:
        return "null"
    python_name = type(value).__name__
    return _PYTHON_TYPE_TO_SPEC.get(python_name, python_name)


def spec_source_short(internal_source: str) -> str:
    """Return the short source name for the ``data.source`` field.

    Used in the top-level ``data.source`` JSON field and the Rich
    ``Source:`` line.  Matches the spec examples (e.g. ``"config"``,
    ``"default"``, ``"env"``).
    """
    return _SOURCE_SHORT_NAMES.get(internal_source, internal_source)


def spec_source_display(internal_source: str) -> str:
    """Return the human-readable source name for resolution chain entries.

    Used inside ``resolution_chain[].source`` and ``winner.source``.
    Returns names like ``"CLI flag"``, ``"Config file"``, ``"Default"``.
    """
    return _SOURCE_DISPLAY_NAMES.get(internal_source, internal_source)


def build_spec_resolution_chain(
    chain: list[dict[str, Any]],
) -> list[dict[str, Any]]:
    """Convert internal chain entries to spec-required format.

    The spec requires:
    ``[{"level": 1, "source": "CLI flag", "value": null}, ...]``

    Human-readable source names are used inside the chain per spec.
    """
    result: list[dict[str, Any]] = []
    for idx, entry in enumerate(chain, start=1):
        result.append(
            {
                "level": idx,
                "source": spec_source_display(entry.get("source", "")),
                "value": entry.get("value"),
            }
        )
    return result


def find_winner_in_chain(
    chain: list[dict[str, Any]],
    winning_source: str,
) -> dict[str, Any]:
    """Return the winner dict ``{"source": ..., "level": ...}`` for the chain.

    *winning_source* is the internal ``ConfigLevel.value`` string.
    The spec shows ``winner.source`` in lowercase (e.g. ``"config file"``).
    """
    # Use lowercase human-readable name per spec example
    display_name = spec_source_display(winning_source).lower()
    for idx, entry in enumerate(chain, start=1):
        if entry.get("source") == winning_source and entry.get("value") is not None:
            return {"source": display_name, "level": idx}
    # Fallback: last entry (default)
    return {"source": display_name, "level": len(chain)}


def build_origin(
    chain: list[dict[str, Any]],
    winning_source: str,
    default_value: Any,
) -> dict[str, Any]:
    """Build the ``origin`` object for the JSON envelope.

    The origin shows where the winning value came from (file path, line,
    and the registered default).  Line numbers are not tracked by the
    service, so ``line`` is always ``null``.

    TODO: Track line numbers in ConfigService to populate ``line`` field.
    """
    file_path: str | None = None
    line: int | None = None  # ConfigService does not track line numbers

    # Find the winning chain entry to extract path info
    for entry in chain:
        if entry.get("source") == winning_source and entry.get("value") is not None:
            raw_path = entry.get("path")
            if raw_path:
                file_path = str(raw_path)
            break

    # If no file path found, use a descriptive label based on source
    if file_path is None:
        if winning_source == ConfigLevel.ENV_VAR.value:
            for entry in chain:
                if entry.get("source") == winning_source:
                    env_name = entry.get("env_name")
                    if env_name:
                        file_path = f"${env_name}"
                    break
        elif winning_source == ConfigLevel.CLI_FLAG.value:
            file_path = "(CLI flag)"
        elif winning_source == ConfigLevel.DEFAULT.value:
            file_path = "(built-in default)"

    return {
        "file": file_path,
        "line": line,
        "default": default_value,
    }
