"""Capability detection for checkpointer implementations."""

from __future__ import annotations

from dataclasses import dataclass
from enum import Enum
from typing import TYPE_CHECKING

from langgraph.checkpoint.base import BaseCheckpointSaver

if TYPE_CHECKING:
    pass


class Capability(str, Enum):
    """Capabilities that a checkpointer may support."""

    PUT = "put"
    PUT_WRITES = "put_writes"
    GET_TUPLE = "get_tuple"
    LIST = "list"
    DELETE_THREAD = "delete_thread"
    DELETE_FOR_RUNS = "delete_for_runs"
    COPY_THREAD = "copy_thread"
    PRUNE = "prune"
    DELTA_CHANNEL_HISTORY = "delta_channel_history"


# Capabilities that every checkpointer must support.
BASE_CAPABILITIES = frozenset(
    {
        Capability.PUT,
        Capability.PUT_WRITES,
        Capability.GET_TUPLE,
        Capability.LIST,
        Capability.DELETE_THREAD,
    }
)

# Capabilities that are optional extensions.
EXTENDED_CAPABILITIES = frozenset(
    {
        Capability.DELETE_FOR_RUNS,
        Capability.COPY_THREAD,
        Capability.PRUNE,
        Capability.DELTA_CHANNEL_HISTORY,
    }
)

ALL_CAPABILITIES = BASE_CAPABILITIES | EXTENDED_CAPABILITIES

# Maps capability to the async method name on BaseCheckpointSaver (or subclass).
_CAPABILITY_METHOD_MAP: dict[Capability, str] = {
    Capability.PUT: "aput",
    Capability.PUT_WRITES: "aput_writes",
    Capability.GET_TUPLE: "aget_tuple",
    Capability.LIST: "alist",
    Capability.DELETE_THREAD: "adelete_thread",
    Capability.DELETE_FOR_RUNS: "adelete_for_runs",
    Capability.COPY_THREAD: "acopy_thread",
    Capability.PRUNE: "aprune",
    Capability.DELTA_CHANNEL_HISTORY: "aget_delta_channel_history",
}


@dataclass(frozen=True)
class DetectedCapabilities:
    """Result of capability detection for a checkpointer type."""

    detected: frozenset[Capability]
    missing: frozenset[Capability]

    @classmethod
    def from_instance(cls, saver: BaseCheckpointSaver) -> DetectedCapabilities:
        """Detect capabilities from a checkpointer instance."""
        inner_type = type(saver)
        detected: set[Capability] = set()

        for cap, method_name in _CAPABILITY_METHOD_MAP.items():
            if _is_overridden(inner_type, method_name):
                detected.add(cap)

        detected_fs = frozenset(detected)
        return cls(
            detected=detected_fs,
            missing=ALL_CAPABILITIES - detected_fs,
        )


def _is_overridden(inner_type: type, method: str) -> bool:
    """Check if *method* on *inner_type* differs from the base class default."""
    base = getattr(BaseCheckpointSaver, method, None)
    impl = getattr(inner_type, method, None)
    if base is None or impl is None:
        return impl is not None
    return impl is not base
