"""Generic stats-provider abstraction.

A service type that exposes a set of named numeric metrics (Jellyseerr request
stats today; Sonarr/Radarr later) registers a :class:`StatsProvider`. The
widget layer renders the provider's output as either a single-stat widget (a
selector picks one metric) or a stats-overview grid. Keeping this behind a
small interface means future stats services reuse the same widgets + tab
without per-service widget kinds.

Providers are synchronous (they do blocking HTTP) and are run in a thread by
the widget source / router. A provider should cache/de-duplicate fetches so
that several widgets + the tab don't each hit the upstream service.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Protocol, runtime_checkable


@dataclass(frozen=True)
class StatValue:
    """One named metric."""

    key: str
    label: str
    value: int | float


@dataclass
class StatsResult:
    """Normalized output of a stats provider."""

    stats: list[StatValue]
    recent: list[dict[str, Any]] = field(default_factory=list)
    # Optional human note (e.g. "not configured"); surfaced as an error when
    # there are no stats.
    detail: str | None = None


@runtime_checkable
class StatsProvider(Protocol):
    """Return the current stats for a service instance.

    ``service`` is a duck-typed record with ``id``, ``service_type``,
    ``config`` and ``secrets`` (see widgets.sources.ServiceRecord).
    """

    def fetch_stats(self, service: Any) -> StatsResult: ...


# Registry keyed by service_type. A service type with no provider simply has no
# stat widgets available.
STATS_PROVIDERS: dict[str, StatsProvider] = {}


def register_stats_provider(service_type: str, provider: StatsProvider) -> None:
    STATS_PROVIDERS[service_type] = provider


def get_stats_provider(service_type: str) -> StatsProvider | None:
    return STATS_PROVIDERS.get(service_type)
