"""
Single source of truth for list/detail display values.

The web templates are intentionally thin (see
``templates/aa_discord_audit/AGENTS.md``): views pre-compute display
values and the templates render ``{{ value }}``. This module is that
pre-compute layer for the two derived presentation concerns shared
across every list/detail view -- semantic badges keyed on enum values
and relative timestamps -- so a colour map or a phrasing lives in one
place instead of drifting across six templates.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from django.utils.timesince import timesince
from django.utils.translation import gettext_lazy as _

from .models import (
    AuditFindingState,
    AuditRunState,
    ConfigAction,
    InvocationRefusal,
    OrphanAction,
    OrphanCategory,
    RunTrigger,
)

if TYPE_CHECKING:  # pragma: no cover
    from datetime import datetime

    from django.db.models import TextChoices

_EM_DASH = "—"

_BADGE: dict[str, tuple[type[TextChoices], dict[str, str]]] = {
    "run_state": (
        AuditRunState,
        {
            "succeeded": "bg-success",
            "failed": "bg-danger",
            "budget_exceeded": "bg-warning text-dark",
            "interrupted": "bg-warning text-dark",
            "abandoned": "bg-secondary",
            "running": "bg-info text-dark",
            "pending": "bg-secondary",
        },
    ),
    "finding_category": (
        OrphanCategory,
        {
            "unknown_guest": "bg-warning text-dark",
            "linked_no_perm": "bg-danger",
            "bot_filtered": "bg-secondary",
        },
    ),
    "finding_state": (
        AuditFindingState,
        {
            "applied": "bg-success",
            "skipped": "bg-secondary",
            "failed": "bg-danger",
            "pending": "bg-info text-dark",
        },
    ),
    "action": (
        OrphanAction,
        {
            "report": "bg-info text-dark",
            "strip": "bg-warning text-dark",
            "strip_kick": "bg-danger",
        },
    ),
    "trigger": (
        RunTrigger,
        {
            "cli": "bg-secondary",
            "beat": "bg-info text-dark",
            "manual": "bg-primary",
        },
    ),
    "refusal": (
        InvocationRefusal,
        {
            "permission_denied": "bg-danger",
            "rate_limited": "bg-warning text-dark",
            "confirmation_declined": "bg-secondary",
        },
    ),
    "config_action": (
        ConfigAction,
        {
            "create": "bg-success",
            "update": "bg-info text-dark",
            "delete": "bg-danger",
        },
    ),
}

_FLAG_CSS: dict[str, str] = {
    "dry_run": "bg-info text-dark",
    "was_kicked": "bg-danger",
    "first_run_lock": "bg-warning text-dark",
    "was_confirmation_bypassed": "bg-danger",
}


def build_badge(kind: str, value: str) -> dict[str, str]:
    """
    Map an enum ``value`` to ``{"css", "label"}`` for a BS5 badge.

    ``kind`` must be a registered key in ``_BADGE`` -- an unknown kind
    raises ``KeyError`` (a programming error). An unknown ``value``
    falls back to ``bg-secondary`` with the raw string as the label.
    """
    enum_cls, colours = _BADGE[kind]
    css = colours.get(value, "bg-secondary")
    try:
        label = str(enum_cls(value).label)
    except ValueError:
        label = value
    return {"css": css, "label": label}


def flag_badge(kind: str, *, value: bool) -> dict[str, str] | None:
    """Risk badge for a boolean flag; ``None`` (no badge) when false."""
    if not value:
        return None
    return {"css": _FLAG_CSS[kind], "label": str(_("yes"))}


def relative_since(dt: datetime | None) -> str:
    """Human "3 hours ago"; em-dash for ``None`` (unfinished/unset)."""
    if dt is None:
        return _EM_DASH
    return f"{timesince(dt)} ago"
