from __future__ import annotations

from dataclasses import dataclass
from enum import Enum
from typing import Iterable, List, Mapping, Optional, Sequence


class RefusalType(str, Enum):
    INSUFFICIENT_CONTEXT = "insufficient_context"
    ETHICAL_BOUNDARY = "ethical_boundary"
    CAPABILITY_LIMIT = "capability_limit"
    POLICY_VIOLATION = "policy_violation"


_RECOURSE_HINTS = {
    RefusalType.INSUFFICIENT_CONTEXT: "Provide missing documentation or clarify the request.",
    RefusalType.ETHICAL_BOUNDARY: "Escalate to Guardian override or human review.",
    RefusalType.CAPABILITY_LIMIT: "Route through fallback model or widen confidence interval.",
    RefusalType.POLICY_VIOLATION: "Update policy inputs or adjust gate configuration.",
}

_ETHICAL_TOKENS = ("dignity", "ethic", "privacy", "consent", "phi")
_POLICY_TOKENS = ("policy", "compliance", "clause", "regulation")
_CAPABILITY_TOKENS = ("calibration", "ec", "evhd", "coherence", "monotonicity", "model", "capacity")
_CONTEXT_TOKENS = ("context", "information", "evidence", "missing", "insufficient", "data", "council", "disagreement")


@dataclass(frozen=True)
class RefusalClassification:
    refusal_type: RefusalType
    recourse_hint: str
    signals: Sequence[str]

    def to_payload(self) -> Mapping[str, object]:
        return {
            "refusal_type": self.refusal_type.value,
            "recourse_hint": self.recourse_hint,
            "signals": list(self.signals),
        }


def _normalise_tokens(values: Iterable[str]) -> List[str]:
    tokens: List[str] = []
    for value in values:
        if not value:
            continue
        token = value.lower()
        tokens.append(token)
    return tokens


def _contains_any(tokens: Sequence[str], needles: Iterable[str]) -> bool:
    for needle in needles:
        lowered = needle.lower()
        for token in tokens:
            if lowered in token:
                return True
    return False


def classify_refusal_type(
    reasons: Sequence[str],
    *,
    failed_policies: Sequence[str] | None = None,
    council_disagreement: Optional[float] = None,
) -> RefusalClassification:
    """
    Collapse guardian refusal reasons into a coarse taxonomy aligned with
    downstream UX surfaces.
    """

    tokens = _normalise_tokens(reasons)
    failed_tokens = _normalise_tokens(failed_policies or ())
    signals: List[str] = []

    if council_disagreement is not None and council_disagreement > 0.15:
        signals.append("high_council_disagreement")
        refusal_type = RefusalType.INSUFFICIENT_CONTEXT
    elif _contains_any(tokens, _ETHICAL_TOKENS):
        signals.append("ethical_boundary")
        refusal_type = RefusalType.ETHICAL_BOUNDARY
    elif _contains_any(tokens, _POLICY_TOKENS) or _contains_any(failed_tokens, _POLICY_TOKENS):
        signals.append("policy_violation")
        refusal_type = RefusalType.POLICY_VIOLATION
    elif _contains_any(tokens, _CONTEXT_TOKENS):
        signals.append("insufficient_context")
        refusal_type = RefusalType.INSUFFICIENT_CONTEXT
    elif _contains_any(tokens, _CAPABILITY_TOKENS):
        signals.append("capability_limit")
        refusal_type = RefusalType.CAPABILITY_LIMIT
    else:
        refusal_type = RefusalType.CAPABILITY_LIMIT if reasons else RefusalType.INSUFFICIENT_CONTEXT

    hint = _RECOURSE_HINTS.get(refusal_type, _RECOURSE_HINTS[RefusalType.CAPABILITY_LIMIT])
    return RefusalClassification(refusal_type=refusal_type, recourse_hint=hint, signals=signals)


__all__ = ["RefusalClassification", "RefusalType", "classify_refusal_type"]
