"""Receiver hierarchy — typed delivery endpoints with locale resolution."""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Callable


@dataclass
class Receiver:
    """Base receiver with locale and raw config."""

    locale: str = ""
    config: dict[str, Any] = field(default_factory=dict)

    def to_dict(self) -> dict[str, Any]:
        """Return raw dict for backward compat (includes locale)."""
        return {**self.config, "locale": self.locale}


@dataclass
class TelegramReceiver(Receiver):
    """Telegram chat receiver."""

    chat_id: str = ""


@dataclass
class WebhookReceiver(Receiver):
    """Webhook URL receiver."""

    url: str = ""
    headers: dict[str, str] = field(default_factory=dict)


@dataclass
class EmailReceiver(Receiver):
    """Email receiver."""

    email: str = ""
    name: str = ""


@dataclass
class DiscordReceiver(Receiver):
    """Discord webhook receiver."""

    webhook_url: str = ""


@dataclass
class SlackReceiver(Receiver):
    """Slack webhook receiver."""

    webhook_url: str = ""


@dataclass
class NtfyReceiver(Receiver):
    """ntfy topic receiver."""

    topic: str = ""
    priority: int = 3


@dataclass
class MatrixReceiver(Receiver):
    """Matrix room receiver."""

    room_id: str = ""


_ReceiverFactory = Callable[[str, dict[str, Any]], Receiver]


def _coerce_int(value: Any, default: int) -> int:
    try:
        return int(value)
    except (TypeError, ValueError):
        return default


_RECEIVER_FACTORIES: dict[str, _ReceiverFactory] = {
    "telegram": lambda locale, config: TelegramReceiver(
        locale=locale, config=config, chat_id=str(config.get("chat_id", "")),
    ),
    "webhook": lambda locale, config: WebhookReceiver(
        locale=locale, config=config,
        url=str(config.get("url", "")),
        headers=dict(config.get("headers", {}) or {}),
    ),
    "email": lambda locale, config: EmailReceiver(
        locale=locale, config=config,
        email=str(config.get("email", "")),
        name=str(config.get("name", "")),
    ),
    "discord": lambda locale, config: DiscordReceiver(
        locale=locale, config=config,
        webhook_url=str(config.get("webhook_url", "")),
    ),
    "slack": lambda locale, config: SlackReceiver(
        locale=locale, config=config,
        webhook_url=str(config.get("webhook_url", "")),
    ),
    "ntfy": lambda locale, config: NtfyReceiver(
        locale=locale, config=config,
        topic=str(config.get("topic", "")),
        priority=_coerce_int(config.get("priority"), 3),
    ),
    "matrix": lambda locale, config: MatrixReceiver(
        locale=locale, config=config,
        room_id=str(config.get("room_id", "")),
    ),
}


def register_receiver_factory(target_type: str, factory: _ReceiverFactory) -> None:
    """Register a receiver factory for an out-of-tree target type."""
    _RECEIVER_FACTORIES[target_type] = factory


def build_receiver(target_type: str, config: dict[str, Any], locale: str = "") -> Receiver:
    """Factory: build typed Receiver from target type and config dict.

    Falls back to a base ``Receiver`` for unknown target types so callers
    that handle types defensively still receive a usable object — but the
    dispatcher rejects them with ``"Unknown target type"`` so a typo can't
    silently route to nowhere.
    """
    factory = _RECEIVER_FACTORIES.get(target_type)
    if factory is None:
        return Receiver(locale=locale, config=config)
    return factory(locale, config)
