"""Cursor codec abstractions for compound (multi-column) keyset cursors.

Single-column cursors are plain ``str(value)`` — no encoding needed.
Compound cursors use ``Base64JsonCodec`` by default: a URL-safe base64
encoding of a compact JSON object mapping column keys to string values.
"""

import base64
import json
from typing import Any, runtime_checkable

from typing_extensions import Protocol

from sqlalchemy_relay_pagination.exceptions import InvalidCursorError


@runtime_checkable
class CursorCodec(Protocol):
    """Protocol for encoding/decoding compound cursor payloads."""

    def encode(self, values: dict[str, Any]) -> str: ...
    def decode(self, cursor: str) -> dict[str, Any]: ...


class Base64JsonCodec:
    """Default compound cursor codec: base64url(json({col: str_val, ...}))."""

    def encode(self, values: dict[str, Any]) -> str:
        payload = {k: str(v) for k, v in values.items()}
        return base64.urlsafe_b64encode(json.dumps(payload, separators=(",", ":")).encode()).decode()

    def decode(self, cursor: str) -> dict[str, Any]:
        try:
            return json.loads(base64.urlsafe_b64decode(cursor.encode()))
        except Exception as e:
            raise InvalidCursorError(f"Cannot decode compound cursor {cursor!r}: {e}") from e
