"""
RunContext - Runtime context for agent and tool execution.

This module provides the core context types used throughout the Swarm execution pipeline.
RunContext consolidates all dependencies needed for tool execution in a type-safe manner.

This module is decoupled from app - all type hints use protocols.
"""

from dataclasses import dataclass
from typing import Any, Dict, Generic, List, Optional, Protocol, TypeVar

from swarm.config.protocols import (
    MessageStoreProtocol,
    RetryPolicyProtocol,
    StreamManagerProtocol,
)
from swarm.message import SwarmMessage
from swarm.providers.protocols import ModelRouterProtocol


class ContextProtocol(Protocol):
    """
    Protocol for dialogue context used by DialogueRunner.

    This interface defines the minimal contract required for
    any context object to work with the DialogueRunner.
    """

    @property
    def conversation_id(self) -> str:
        """
        Get the conversation ID.

        Returns:
            The unique identifier for this conversation as a string.
        """
        ...

    @property
    def current_context_variables(self) -> Dict[str, Any]:
        """
        Get current context variables.

        Returns:
            Dictionary of context variables for the current conversation state.
        """
        ...

    @property
    def workflow_context(self) -> Dict[str, Any]:
        """
        Mutable workflow context for runtime updates.

        The DialogueRunner updates this after tool execution so subsequent
        tools can see context_variables returned by previous tools.

        Returns:
            Dictionary of workflow context variables.
        """
        ...

    def get_history_for_completion(self) -> List[SwarmMessage]:
        """
        Get message history formatted for completion.

        Returns:
            List of SwarmMessage objects for completion requests.
        """
        ...

    @property
    def designer_fullname(self) -> str:
        """
        Get the designer's display name for message authoring.

        Returns:
            The designer's full name to use as message author.
        """
        ...


# TypeVar for context dependencies - must implement ContextProtocol
DepsT = TypeVar("DepsT", bound=ContextProtocol)


@dataclass(kw_only=True)
class RunContext(Generic[DepsT]):
    """
    Runtime context containing all dependencies for agent and tool execution.

    This consolidates dependencies that tools need access to:
    - deps: The context object (DialogueContext, WorkflowContext, etc.)
    - stream_manager: For streaming responses to the client
    - message_store: For persisting messages (DialogueService, etc.)
    - router: Model router for retries

    The generic type DepsT allows type-safe access to different context types
    while maintaining a consistent interface.
    """

    deps: DepsT
    """The context object (DialogueContext, WorkflowContext, etc.)"""

    stream_manager: StreamManagerProtocol
    """Stream manager for publishing SSE events to the client"""

    message_store: MessageStoreProtocol
    """Service for message persistence (DialogueService, etc.)"""

    router: ModelRouterProtocol
    """Model router for handling retries across models"""

    retry_policy: Optional[RetryPolicyProtocol] = None
    """Retry policy configuration for tool execution (required for tool execution)"""

    @property
    def context_variables(self) -> Dict[str, Any]:
        """
        Get context variables from the deps object.

        Returns the current_context_variables from the context,
        providing easy access to simple key-value pairs that are
        passed between tool calls.
        """
        return self.deps.current_context_variables

    async def publish_status(self, message: str) -> None:
        """
        Publish a status message to the stream manager.

        This is a null-safe convenience method that eliminates boilerplate.
        Tools can call `await ctx.publish_status("...")` instead of:
            stream_manager = ctx.stream_manager
            if stream_manager:
                await stream_manager.publish_status(message)

        Args:
            message: The status message to publish to the client.
        """
        if self.stream_manager:
            await self.stream_manager.publish_status(message)

    async def stream_reasoning(self, reasoning: List[str]) -> None:
        """
        Stream decision reasoning to the user.

        All tools with ToolInput have decision_reasoning. This method streams
        each reasoning step to provide transparency about the LLM's thought process.

        Args:
            reasoning: List of reasoning steps from data.decision_reasoning.
        """
        if self.stream_manager:
            for reason in reasoning:
                await self.stream_manager.publish_status(reason)
