# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/what-happens-when-an-instance-advertises-an-a2a-v1-0-agent-card-with-a-tasknegotiation-signing-key-whose-kid-resolves-through-a-cross-vendor-jwks-but.md
# Remnant Warrant:    https://corpusfetch.com/c/what-happens-when-an-instance-advertises-an-a2a-v1-0-agent-card-with-a-tasknegotiation-signing-key-whose-kid-resolves-through-a-cross-vendor-jwks-but.warrant.json
# Markdown SHA-256:   e30aa94d6d5bda639e74854aa64856dd9a057fb33b45fd69356d51cacb43bee3
# Code AST SHA-256:   970947e308a26f9bcef4d774588479de1a902520fecc2c68fca583ac21745b94
# Verified Classes:   MediaTypeNegotiationError, MediaTypeRegistry, ConsumptionProfile
# Defensive Guards:   3 fail-closed invariants (zero external deps)

from __future__ import annotations

import re
from dataclasses import dataclass, field
from typing import Iterable


class MediaTypeNegotiationError(RuntimeError):
    """Raised when a peer card is trusted but its output modes are uninterpretable."""


_MEDIA_TYPE_RE = re.compile(
    r"^(?P<type>[!#$%&'*+\-.^_`|~0-9A-Za-z]+)/(?P<subtype>[!#$%&'*+\-.^_`|~0-9A-Za-z]+)"
    r"(?P<params>(?:\s*;\s*[!#$%&'*+\-.^_`|~0-9A-Za-z]+\s*=\s*(?:\"[^\"]*\"|[^;\s]+))*)$"
)


def parse_media_type(value: str) -> tuple[str, frozenset[tuple[str, str]]]:
    m = _MEDIA_TYPE_RE.match(value.strip())
    if not m:
        raise MediaTypeNegotiationError(f"unparseable media type: {value!r}")
    base = f"{m.group('type').lower()}/{m.group('subtype').lower()}"
    params: set[tuple[str, str]] = set()
    raw = m.group("params") or ""
    for chunk in raw.split(";"):
        chunk = chunk.strip()
        if not chunk:
            continue
        k, _, v = chunk.partition("=")
        v = v.strip().strip('"')
        params.add((k.strip().lower(), v))
    return base, frozenset(params)


@dataclass(frozen=True)
class MediaTypeRegistry:
    # Each entry is (base_type, required_params). A registry entry matches an
    # incoming type only if base matches AND every required param is present
    # with an equal value. Extra params on the incoming type are permitted.
    entries: tuple[tuple[str, frozenset[tuple[str, str]]], ...] = field(default_factory=tuple)

    def supports(self, media_type: str) -> bool:
        base, params = parse_media_type(media_type)
        for reg_base, reg_params in self.entries:
            if reg_base != base:
                continue
            if reg_params.issubset(params):
                return True
        return False


@dataclass(frozen=True)
class ConsumptionProfile:
    """Caller-declared: which output modes it will actually consume."""
    consumes_artifacts: bool = True
    registry: MediaTypeRegistry = field(default_factory=MediaTypeRegistry)


def pre_negotiation_gate(
    card_default_output_modes: Iterable[str],
    skill_output_modes: Iterable[str],
    profile: ConsumptionProfile,
) -> None:
    """
    Must be called after JWS verification succeeds and BEFORE tasks/send.
    Raises MediaTypeNegotiationError if any required output mode is
    uninterpretable. Callers must treat this exception as terminal.
    """
    if not profile.consumes_artifacts:
        return  # status-only consumers are exempt from artifact negotiation

    required = list(card_default_output_modes) + list(skill_output_modes)
    uninterpretable = [mt for mt in required if not profile.registry.supports(mt)]
    if uninterpretable:
        raise MediaTypeNegotiationError(
            "peer card is signed and trusted but advertises output modes the "
            f"caller cannot decode: {uninterpretable!r}. Refusing tasks/send "
            "to avoid orphan-task accumulation under retry."
        )


def decode_artifact(media_type: str, payload: bytes, profile: ConsumptionProfile) -> object:
    if not profile.registry.supports(media_type):
        # Terminal, never transient. Do not let a retry wrapper catch this
        # as a transport error.
        raise MediaTypeNegotiationError(
            f"artifact media type {media_type!r} not in caller registry; "
            "task must be abandoned, not retried"
        )
    import json
    return json.loads(payload)
