"""First-class epistemic-state and normative-clearance enums.

Phase 3 of the epistemic-state program makes the ratified taxonomy
queryable from runtime artifacts without entangling callers with the
scope document.  The values are intentionally lower_snake_case so they
serialize stably into JSONL, metadata dictionaries, and test fixtures.
"""

from __future__ import annotations

from enum import Enum, unique
from typing import Any, Literal


GroundingSource = Literal["pack", "teaching", "vault", "partial", "oov", "none"]
"""Ratified grounding-source labels.

Single source of truth for the values ``epistemic_state_for_grounding_source``
maps, the cold-start-grounding lane validates, and the Workbench UI badges
bind to (ADR-0162 §3d).  Adding a value here without adding a corresponding
badge fails the build-time enum coverage test under ``workbench-ui/``.
"""


@unique
class EpistemicState(str, Enum):
    PERCEIVED = "perceived"
    EVIDENCED = "evidenced"
    EVIDENCED_INCOMPLETE = "evidenced_incomplete"
    VERIFIED = "verified"
    DECODED = "decoded"
    DECODED_UNARTICULATED = "decoded_unarticulated"
    INFERRED = "inferred"
    UNVERIFIED_POSSIBLE = "unverified_possible"
    UNVERIFIED_NOVEL = "unverified_novel"
    CONTRADICTED = "contradicted"
    AMBIGUOUS = "ambiguous"
    UNDETERMINED = "undetermined"
    SCOPE_BOUNDARY = "scope_boundary"
    COMPUTATIONALLY_BOUNDED = "computationally_bounded"
    EPISTEMIC_STATE_NEEDED = "epistemic_state_needed"


@unique
class NormativeClearance(str, Enum):
    CLEARED = "cleared"
    VIOLATED = "violated"
    UNASSESSABLE = "unassessable"
    SUPPRESSED = "suppressed"


def coerce_epistemic_state(value: object | None, *, default: EpistemicState = EpistemicState.UNDETERMINED) -> EpistemicState:
    if isinstance(value, EpistemicState):
        return value
    if isinstance(value, str):
        normalized = value.strip().lower().replace("-", "_")
        for state in EpistemicState:
            if normalized in {state.value, state.name.lower()}:
                return state
    return default


def coerce_normative_clearance(value: object | None, *, default: NormativeClearance = NormativeClearance.UNASSESSABLE) -> NormativeClearance:
    if isinstance(value, NormativeClearance):
        return value
    if isinstance(value, str):
        normalized = value.strip().lower().replace("-", "_")
        for clearance in NormativeClearance:
            if normalized in {clearance.value, clearance.name.lower()}:
                return clearance
    return default


def clearance_from_verdicts(verdicts: Any = None, *, safety_verdict: Any = None, ethics_verdict: Any = None) -> NormativeClearance:
    """Derive the orthogonal normative-clearance state for a turn.

    Refusal replacement is a SUPPRESSED surface.  Otherwise any failed
    safety/ethics verdict is VIOLATED.  If every available verdict is
    upheld but at least one result was not runtime-checkable, the turn is
    UNASSESSABLE rather than positively CLEARED.  Only fully upheld and
    fully runtime-checkable verdicts are CLEARED.
    """
    if verdicts is not None:
        if bool(getattr(verdicts, "refusal_emitted", False)):
            return NormativeClearance.SUPPRESSED
        safety_verdict = safety_verdict if safety_verdict is not None else getattr(verdicts, "safety_verdict", None)
        ethics_verdict = ethics_verdict if ethics_verdict is not None else getattr(verdicts, "ethics_verdict", None)

    saw_unassessable = False
    saw_verdict = False
    for verdict in (safety_verdict, ethics_verdict):
        if verdict is None:
            continue
        saw_verdict = True
        if not bool(getattr(verdict, "upheld", True)):
            return NormativeClearance.VIOLATED
        results = tuple(getattr(verdict, "results", ()) or ())
        if any(not bool(getattr(result, "runtime_checkable", True)) for result in results):
            saw_unassessable = True
        elif int(getattr(verdict, "runtime_checkable_count", 0) or 0) == 0:
            saw_unassessable = True
    if not saw_verdict or saw_unassessable:
        return NormativeClearance.UNASSESSABLE
    return NormativeClearance.CLEARED


def normative_detail_from_verdicts(verdicts: Any = None, *, safety_verdict: Any = None, ethics_verdict: Any = None) -> str:
    """Return a sorted, comma-separated string of violated boundary/commitment IDs.

    Non-empty only when normative clearance is VIOLATED or SUPPRESSED —
    i.e. when at least one runtime-checkable boundary or commitment failed.
    Returns "" when CLEARED or UNASSESSABLE so callers can test truthiness.
    Uses the same duck-typing pattern as ``clearance_from_verdicts``.
    """
    if verdicts is not None:
        safety_verdict = safety_verdict if safety_verdict is not None else getattr(verdicts, "safety_verdict", None)
        ethics_verdict = ethics_verdict if ethics_verdict is not None else getattr(verdicts, "ethics_verdict", None)

    ids: list[str] = []
    for r in getattr(safety_verdict, "results", []):
        if getattr(r, "runtime_checkable", False) and not getattr(r, "upheld", True):
            bid = getattr(r, "boundary_id", None)
            if bid:
                ids.append(str(bid))
    for r in getattr(ethics_verdict, "results", []):
        if getattr(r, "runtime_checkable", False) and not getattr(r, "upheld", True):
            cid = getattr(r, "commitment_id", None)
            if cid:
                ids.append(str(cid))
    return ",".join(sorted(ids))


def epistemic_state_for_grounding_source(source: str | None) -> EpistemicState:
    """Default runtime mapping for existing grounding-source labels."""
    normalized = (source or "none").strip().lower()
    if normalized in {"pack", "teaching", "vault"}:
        return EpistemicState.DECODED
    if normalized == "partial":
        return EpistemicState.EVIDENCED_INCOMPLETE
    if normalized == "oov":
        return EpistemicState.UNVERIFIED_NOVEL
    if normalized == "none":
        return EpistemicState.UNDETERMINED
    return EpistemicState.EPISTEMIC_STATE_NEEDED


__all__ = [
    "EpistemicState",
    "GroundingSource",
    "NormativeClearance",
    "clearance_from_verdicts",
    "coerce_epistemic_state",
    "coerce_normative_clearance",
    "epistemic_state_for_grounding_source",
    "normative_detail_from_verdicts",
]
