# aiel_sdk/memory/saver.py
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Any, Dict, List, Optional, Sequence

from .types import (
    Checkpoint,
    RecallItem,
    RecallQuery,
    Thread,
    ThreadMessage,
    ThreadScope,
    ThreadState,
    DEFAULT_CAPABILITIES,
    SaverCapabilities
)


class Saver(ABC):
    """
    Framework-agnostic Saver (THREAD_KEY-first).

    Backend derives thread_id; SDK passes thread_key.
    """
    @property
    def capabilities(self) -> SaverCapabilities:
        return DEFAULT_CAPABILITIES


    # ---- Thread (short-term) ----
    @abstractmethod
    def thread_read(self, *, thread_key: str, limit: int = 50) -> Thread:
        raise NotImplementedError

    @abstractmethod
    def thread_write(
        self,
        *,
        thread_key: str,
        messages: Sequence[ThreadMessage],
        scope: ThreadScope | None = None,
        ttl_seconds: int | None = None,
    ) -> Thread:
        raise NotImplementedError

    # ---- State (working state) ----
    @abstractmethod
    def state_read(self, *, thread_key: str) -> ThreadState:
        raise NotImplementedError

    @abstractmethod
    def state_patch(
        self,
        *,
        thread_key: str,
        patch: Dict[str, Any],
        scope: ThreadScope | None = None,
        ttl_seconds: int | None = None,
    ) -> ThreadState:
        raise NotImplementedError

    # ---- Checkpoints ----
    @abstractmethod
    def checkpoint_read_latest(self, *, thread_key: str) -> Optional[Checkpoint]:
        raise NotImplementedError

    @abstractmethod
    def checkpoint_write(
        self,
        *,
        thread_key: str,
        state: Dict[str, Any],
        metadata: Dict[str, Any] | None = None,
        scope: ThreadScope | None = None,
        ttl_seconds: int | None = None,
    ) -> Checkpoint:
        raise NotImplementedError

    # ---- Recall ----
    @abstractmethod
    def recall_put(
        self,
        *,
        namespace: List[str],
        key: str,
        value: Dict[str, Any],
        tags: Dict[str, Any] | None = None,
        ttl_seconds: int | None = None,
    ) -> None:
        raise NotImplementedError

    @abstractmethod
    def recall_get(self, *, namespace: List[str], key: str) -> Optional[RecallItem]:
        raise NotImplementedError

    @abstractmethod
    def recall_search(self, *, query: RecallQuery) -> List[RecallItem]:
        raise NotImplementedError

    @abstractmethod
    def recall_forget(self, *, namespace: List[str], selector: Dict[str, Any]) -> None:
        raise NotImplementedError

    def recall_forget_key(self, *, namespace: List[str], key: str) -> None:
        self.recall_forget(namespace=namespace, selector={"key": key})