from collections.abc import Mapping
from typing import Any, cast

from click import Context


def build_help_json(ctx: Context) -> dict[str, Any]:
    command_info: dict[str, Any] = dict(ctx.command.to_info_dict(ctx))
    usage = " ".join([ctx.command_path, *ctx.command.collect_usage_pieces(ctx)]).strip()
    payload = _normalize_command(command_info)
    payload["usage"] = usage
    payload["description"] = payload.pop("help")
    payload.pop("deprecated", None)
    payload.pop("hidden", None)
    return payload


def _normalize_command(info: dict[str, Any]) -> dict[str, Any]:
    option_entries: list[dict[str, Any]] = []
    for raw_parameter in cast(list[Any], info.get("params", [])):
        if not isinstance(raw_parameter, Mapping):
            continue
        parameter = dict(cast(Mapping[str, Any], raw_parameter))
        if bool(parameter.get("hidden", False)):
            continue
        option_entries.append(_normalize_parameter(parameter))

    command_entries: list[dict[str, Any]] = []
    for raw_command in _iter_commands(info.get("commands", {})):
        if bool(raw_command.get("hidden", False)):
            continue
        command_entries.append(_normalize_command(raw_command))

    return {
        "name": str(info["name"]),
        "help": _normalize_help_text(info),
        "deprecated": bool(info.get("deprecated", False)),
        "hidden": bool(info.get("hidden", False)),
        "options": option_entries,
        "commands": command_entries,
    }


def _iter_commands(commands: Any) -> list[dict[str, Any]]:
    if not isinstance(commands, Mapping):
        return []
    command_mapping = cast(Mapping[Any, Any], commands)
    normalized_commands: list[dict[str, Any]] = []
    for raw_command in command_mapping.values():
        if isinstance(raw_command, Mapping):
            normalized_commands.append(dict(cast(Mapping[str, Any], raw_command)))
    return normalized_commands


def _normalize_parameter(parameter: dict[str, Any]) -> dict[str, Any]:
    parameter_type = str(parameter.get("param_type_name", "option"))
    choices = _extract_choices(parameter.get("type"))
    payload: dict[str, Any] = {
        "name": _parameter_name(parameter, parameter_type),
        "kind": parameter_type,
        "required": bool(parameter.get("required", False)),
        "default": _json_scalar(parameter.get("default")),
        "help": _normalize_help_text(parameter),
    }
    envvar = parameter.get("envvar")
    if envvar is not None:
        payload["envvar"] = str(envvar)
    if bool(parameter.get("multiple", False)):
        payload["multiple"] = True
    if choices is not None:
        payload["choices"] = choices
    return payload


def _parameter_name(parameter: dict[str, Any], parameter_type: str) -> str:
    if parameter_type == "argument":
        return str(parameter["name"])
    options = cast(list[Any], parameter.get("opts", []))
    if options:
        return str(options[0])
    return str(parameter["name"])


def _extract_choices(type_info: Any) -> list[Any] | None:
    if not isinstance(type_info, Mapping):
        return None
    type_mapping = cast(Mapping[str, Any], type_info)
    raw_choices = type_mapping.get("choices")
    if not isinstance(raw_choices, (list, tuple)):
        return None
    normalized_choices: list[Any] = []
    for choice in cast(list[Any] | tuple[Any, ...], raw_choices):
        normalized_choices.append(_json_scalar(choice))
    return normalized_choices


def _normalize_help_text(info: Mapping[str, Any]) -> str | None:
    help_text = cast(Any, info.get("help")) or cast(Any, info.get("short_help"))
    if help_text is None:
        return None
    return str(help_text)


def _json_scalar(value: Any) -> Any:
    if value is None or isinstance(value, (str, int, float, bool)):
        return value
    if isinstance(value, tuple):
        tuple_items = cast(tuple[Any, ...], value)
        return [_json_scalar(item) for item in tuple_items]
    if isinstance(value, list):
        list_items = cast(list[Any], value)
        return [_json_scalar(item) for item in list_items]
    if isinstance(value, dict):
        dict_items = cast(dict[Any, Any], value)
        return {str(key): _json_scalar(item) for key, item in dict_items.items()}
    return str(value)
