"""Adapter contract for trustless external authority proposal handling."""

from __future__ import annotations

from collections.abc import Mapping
from dataclasses import dataclass, field
from typing import Any, Protocol, runtime_checkable

__all__ = [
    "AuthorityAdapter",
    "AuthorityProposal",
    "normalise_authority_proposal",
]


@dataclass(frozen=True, slots=True)
class AuthorityProposal:
    """Normalised proposal output derived from an external authority response."""

    changes: Mapping[str, Any]
    source: str | None = None
    reference: str | None = None
    details: Mapping[str, Any] = field(default_factory=dict)

    def __post_init__(self) -> None:
        if not isinstance(self.changes, Mapping):
            msg = "changes must be a mapping."
            raise ValueError(msg)
        object.__setattr__(self, "changes", dict(self.changes))
        if self.source is not None:
            object.__setattr__(self, "source", _require_non_empty_string("source", self.source))
        if self.reference is not None:
            object.__setattr__(
                self,
                "reference",
                _require_non_empty_string("reference", self.reference),
            )
        if not isinstance(self.details, Mapping):
            msg = "details must be a mapping."
            raise ValueError(msg)
        object.__setattr__(self, "details", dict(self.details))


@runtime_checkable
class AuthorityAdapter(Protocol):
    """Contract for translating external responses into proposal state changes."""

    def propose(
        self,
        *,
        current_state: Mapping[str, Any],
        authority_response: Mapping[str, Any],
    ) -> AuthorityProposal | Mapping[str, Any]:
        """Return proposal changes derived from external authority input."""


def normalise_authority_proposal(
    value: AuthorityProposal | Mapping[str, Any],
) -> AuthorityProposal:
    """Normalise adapter proposal output into an AuthorityProposal value."""
    if isinstance(value, AuthorityProposal):
        return value
    if not isinstance(value, Mapping):
        msg = "Authority proposal must be an AuthorityProposal or mapping."
        raise ValueError(msg)
    if "changes" in value:
        return AuthorityProposal(
            changes=_extract_mapping(value["changes"], field_name="changes"),
            source=value.get("source"),
            reference=value.get("reference"),
            details=value.get("details", {}),
        )
    return AuthorityProposal(changes=value)


def _extract_mapping(value: Any, *, field_name: str) -> Mapping[str, Any]:
    if not isinstance(value, Mapping):
        msg = f"{field_name} must be a mapping."
        raise ValueError(msg)
    return value


def _require_non_empty_string(field_name: str, value: Any) -> str:
    if not isinstance(value, str) or not value.strip():
        msg = f"{field_name} must be a non-empty string."
        raise ValueError(msg)
    return value.strip()
