from typing import Any, Callable, Iterable, Mapping, Optional, Protocol, Sequence, TypeVar

T = TypeVar("T")


class CachePort(Protocol):
    def get(self, key: str) -> Optional[bytes]: ...
    def set(
        self, key: str, value: bytes, *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None
    ) -> None: ...
    def delete(self, key: str) -> None: ...
    def exists(self, key: str) -> bool: ...
    def ttl(self, key: str) -> Optional[int]: ...

    def mget(self, keys: Sequence[str]) -> list[Optional[bytes]]: ...
    def mset(
        self, items: Mapping[str, bytes], *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None
    ) -> None: ...

    def get_json(self, key: str) -> Optional[Any]: ...
    def set_json(
        self, key: str, value: Any, *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None
    ) -> None: ...

    def invalidate_tags(self, tags: Iterable[str]) -> int: ...
    def bump_namespace(self, namespace: str) -> int: ...  # versioned keys
    def clear_namespace(self, namespace: str) -> int: ...

    def get_or_set(
        self, key: str, producer: Callable[[], bytes], *, ttl: int, tags: Optional[Iterable[str]] = None
    ) -> bytes: ...


class AsyncCachePort(Protocol):
    async def get(self, key: str) -> Optional[bytes]: ...
    async def set(
        self, key: str, value: bytes, *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None
    ) -> None: ...
    async def delete(self, key: str) -> None: ...
    async def exists(self, key: str) -> bool: ...
    async def ttl(self, key: str) -> Optional[int]: ...
    async def mget(self, keys: Sequence[str]) -> list[Optional[bytes]]: ...
    async def mset(
        self, items: Mapping[str, bytes], *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None
    ) -> None: ...
    async def get_json(self, key: str) -> Optional[Any]: ...
    async def set_json(
        self, key: str, value: Any, *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None
    ) -> None: ...
    async def invalidate_tags(self, tags: Iterable[str]) -> int: ...
    async def bump_namespace(self, namespace: str) -> int: ...
    async def clear_namespace(self, namespace: str) -> int: ...
    async def get_or_set(self, key: str, producer, *, ttl: int, tags: Optional[Iterable[str]] = None) -> bytes: ...
