"""Typed event vocabulary for `subscriptions/listen` (2026-07-28, SEP-2575), shared by server and client.

Every event is a level trigger ("this changed, refetch if you care"), so both sides bound buffers by dedupe.
"""

from __future__ import annotations

from collections.abc import Mapping
from dataclasses import dataclass
from typing import Any

from mcp_types import (
    NotificationParams,
    PromptListChangedNotification,
    ResourceListChangedNotification,
    ResourceUpdatedNotification,
    ResourceUpdatedNotificationParams,
    ServerNotification,
    SubscriptionFilter,
    ToolListChangedNotification,
)

__all__ = [
    "LISTEN_STREAM_METHODS",
    "SUBSCRIPTION_ID_META_KEY",
    "PromptsListChanged",
    "ResourceUpdated",
    "ResourcesListChanged",
    "ServerEvent",
    "ToolsListChanged",
    "event_from_wire",
    "event_matches",
    "event_to_notification",
]

SUBSCRIPTION_ID_META_KEY = "io.modelcontextprotocol/subscriptionId"
"""The `_meta` key on every listen-stream frame; the value is the `subscriptions/listen` request's JSON-RPC id."""


@dataclass(frozen=True)
class ToolsListChanged:
    """The server's tool list changed."""


@dataclass(frozen=True)
class PromptsListChanged:
    """The server's prompt list changed."""


@dataclass(frozen=True)
class ResourcesListChanged:
    """The server's resource list changed."""


@dataclass(frozen=True)
class ResourceUpdated:
    """The resource at `uri` changed and may need to be read again."""

    uri: str


ServerEvent = ToolsListChanged | PromptsListChanged | ResourcesListChanged | ResourceUpdated
"""An event a server publishes for delivery to listen subscribers."""


def event_to_notification(event: ServerEvent, meta: dict[str, Any]) -> ServerNotification:
    """Build the stamped wire notification for `event` (the server's direction)."""
    if isinstance(event, ToolsListChanged):
        return ToolListChangedNotification(params=NotificationParams(_meta=meta))
    if isinstance(event, PromptsListChanged):
        return PromptListChangedNotification(params=NotificationParams(_meta=meta))
    if isinstance(event, ResourcesListChanged):
        return ResourceListChangedNotification(params=NotificationParams(_meta=meta))
    return ResourceUpdatedNotification(params=ResourceUpdatedNotificationParams(uri=event.uri, _meta=meta))


_LIST_CHANGED_EVENTS: dict[str, ServerEvent] = {
    "notifications/tools/list_changed": ToolsListChanged(),
    "notifications/prompts/list_changed": PromptsListChanged(),
    "notifications/resources/list_changed": ResourcesListChanged(),
}

LISTEN_STREAM_METHODS: frozenset[str] = frozenset({*_LIST_CHANGED_EVENTS, "notifications/resources/updated"})
"""The notification methods that ride `subscriptions/listen` streams at 2026-07-28
(and, at that era, nowhere else): the change-notification vocabulary."""


def event_from_wire(method: str, params: Mapping[str, Any] | None) -> ServerEvent | None:
    """The event a raw listen-stream frame announces, or None if it carries none.

    Takes the raw wire dict: the client demultiplexes before the typed notification parse."""
    if (event := _LIST_CHANGED_EVENTS.get(method)) is not None:
        return event
    if method == "notifications/resources/updated":
        uri = (params or {}).get("uri")
        if isinstance(uri, str):
            return ResourceUpdated(uri=uri)
    return None


def event_matches(honored: SubscriptionFilter, uris: frozenset[str], event: ServerEvent) -> bool:
    """Whether `event` is within the stream's honored filter (`uris`: the honored resource subscriptions as a set).

    The admission predicate both sides share: server delivery and client intake honor only what was acknowledged."""
    if isinstance(event, ToolsListChanged):
        return honored.tools_list_changed is True
    if isinstance(event, PromptsListChanged):
        return honored.prompts_list_changed is True
    if isinstance(event, ResourcesListChanged):
        return honored.resources_list_changed is True
    return event.uri in uris
