"""Execution Context — the single object nodes receive during execution.

Nodes access capabilities via:
    result = await ctx.tools["workspace.read_file"](args, ctx)

Boot-time services:  ctx.services.<name>   (frozen ServiceContainer)
Per-turn state:      ctx.run.<name>         (mutable RunState)

ALL PYDANTIC — ToolArgs model for inputs, ToolOutput for outputs.
"""

from collections.abc import Awaitable, Callable

from pydantic import BaseModel, Field

from core.pathways.models.tool import ToolArgs, ToolOutput
from core.pathways.run_state import RunState
from core.pathways.service_container import ServiceContainer

# Tool handler signature: async (args: ToolArgs, ctx: Context) -> ToolOutput
ToolHandler = Callable[[ToolArgs, "Context"], Awaitable[ToolOutput]]


class Context(BaseModel):
    """Services and state available during graph execution.

    Three concerns only:
    - ``tools``: callable tool handlers (flat dict)
    - ``services``: boot-time services (frozen ServiceContainer)
    - ``run``: per-execution mutable state (RunState)

    Per-execution identifiers (pathway_id, execution_id) and policy
    context live on ``run``.
    """

    model_config = {"arbitrary_types_allowed": True}

    # Core: tools as flat dict
    tools: dict[str, ToolHandler] = Field(default_factory=dict)

    # Boot-time services (frozen, set once at startup)
    services: ServiceContainer = Field(default_factory=ServiceContainer)

    # Per-execution mutable state (created fresh each vm.execute())
    run: RunState = Field(default_factory=RunState)

    def get_tool(self, name: str) -> ToolHandler | None:
        """Get a tool handler by name."""
        return self.tools.get(name)

    def has_tool(self, name: str) -> bool:
        """Check if a tool is available."""
        return name in self.tools

    def list_tools(self) -> list[str]:
        """List all available tool names."""
        return list(self.tools.keys())

    # -- Convenience accessors for backward compat with ctx.pathway_id etc. --

    @property
    def pathway_id(self) -> str:
        """Pathway ID for the current execution (delegates to run)."""
        return self.run.pathway_id

    @pathway_id.setter
    def pathway_id(self, value: str) -> None:
        self.run.pathway_id = value

    @property
    def execution_id(self) -> str:
        """Execution ID for the current run (delegates to run)."""
        return self.run.execution_id

    @execution_id.setter
    def execution_id(self, value: str) -> None:
        self.run.execution_id = value

    @property
    def policy_approved(self) -> bool:
        """Whether a policy approval is in effect (delegates to run)."""
        return self.run.policy_approved

    @policy_approved.setter
    def policy_approved(self, value: bool) -> None:
        self.run.policy_approved = value

    @property
    def policy_actor_id(self) -> str | None:
        """Actor who approved the policy (delegates to run)."""
        return self.run.policy_actor_id

    @policy_actor_id.setter
    def policy_actor_id(self, value: str | None) -> None:
        self.run.policy_actor_id = value


__all__ = [
    "Context",
    "RunState",
    "ServiceContainer",
    "ToolHandler",
]
