from dataclasses import dataclass
from typing import Protocol, TypeVar, Any, Iterable

from frisk_sdk.core.wrap_tool_options import WrapToolOptions
from frisk_sdk.redact import RedactOption, RedactedDictionary, RedactedSerializedObject

ToolCallT = TypeVar("ToolCallT", contravariant=True)

ToolT = TypeVar("ToolT")


@dataclass(frozen=True)
class NormalizedToolCall:
    """Framework-agnostic representation of a single tool call."""

    id: str
    name: str
    args: dict[str, Any] | None


class FrameworkAdapter(Protocol[ToolCallT, ToolT]):
    """Protocol for adapting framework-specific runtime types to Frisk primitives."""

    def serialize_tool_args(
        self,
        tool_args: dict[str, Any] | None,
        *,
        redact: RedactOption | None = None,
    ) -> RedactedSerializedObject: ...
    def serialize_agent_state(
        self,
        agent_state: dict[str, Any] | None,
        *,
        redact: RedactOption | None = None,
    ) -> RedactedSerializedObject: ...
    def normalize_tool_call(self, tool_call: ToolCallT) -> NormalizedToolCall: ...

    def tool_args_to_dict(
        self,
        tool_args: dict[str, Any],
        *,
        redact: RedactOption | None = None,
    ) -> RedactedDictionary: ...

    def agent_state_to_dict(
        self,
        agent_state: dict[str, Any],
        *,
        redact: RedactOption | None = None,
    ) -> RedactedDictionary: ...

    def extract_prompt(self, agent_state: dict[str, Any]) -> str | None: ...

    def wrap_tools(
        self, tools: Iterable[Any], options: WrapToolOptions | None
    ) -> list[ToolT]: ...

    def wrap_tool(self, tool: Any, options: WrapToolOptions | None) -> ToolT: ...
