"""RunState — per-execution mutable state.

Created fresh for every ``PathwayVM.execute()`` call by ``Agent.turn()``.
Contains per-turn identity, execution defaults, working state mutated
by tools, and ephemeral VM internals.

Execution defaults (default_model, default_tools, etc.) are set by the
Agent before pathway execution. Pathway nodes read these via
``ctx.run.default_model`` etc. — they never reach into AgentDefinition.
"""

from __future__ import annotations

from typing import Any

from pydantic import BaseModel, Field, PrivateAttr

from core.builders.pathway import PathwayBuilder  # noqa: TC001 (Pydantic needs at runtime)
from core.pathways.models.tool import RegisteredTool  # noqa: TC001
from core.pathways.session import PathwaySession  # noqa: TC001
from core.types.agents import ModelRouting  # noqa: TC001
from core.types.state import ThreadState  # noqa: TC001


class RunState(BaseModel):
    """Mutable state scoped to a single pathway execution.

    Nodes and tools access this via ``ctx.run.<field>``.
    A fresh ``RunState`` is created for each ``vm.execute()`` call.
    """

    model_config = {"arbitrary_types_allowed": True}

    # -- Execution defaults (set by Agent before pathway execution) --
    system_prompt: str | None = None
    default_model: str = "reasoning"
    default_tools: list[str] = Field(default_factory=list)
    default_temperature: float = 0.7
    max_steps: int = 10
    model_routing: ModelRouting | None = None

    # -- Per-turn identity (set by Agent) --
    agent_id: str | None = None
    thread_id: str | None = None

    # -- Per-execution identifiers (set by PathwayVM at execute time) --
    pathway_id: str = ""
    execution_id: str = ""

    # -- Policy context --
    policy_approved: bool = False
    policy_actor_id: str | None = None
    agent_skills: list[str] | None = None
    workspace_root: str | None = None
    workspace_id: str | None = None
    workspace_path: str | None = None

    # -- Thread state (tools read/write working memory) --
    state: ThreadState | None = None

    # -- Working state (pathway builder tools) --
    working_builders: dict[str, PathwayBuilder] | None = None
    current_pathway_id: str | None = None
    registered_tools: dict[str, RegisteredTool] | None = None

    # -- VM internals (ephemeral, set/cleared per node) --
    _current_node_id: str | None = PrivateAttr(default=None)
    _session: PathwaySession | None = PrivateAttr(default=None)
    _code_namespace: dict[str, Any] = PrivateAttr(default_factory=dict)
    _tools_wrapped: bool = PrivateAttr(default=False)

    # -- Observability --
    event_buffer: list[object] = Field(default_factory=list)

    # -- Public accessors for private VM internals --

    def get_code_namespace(self) -> dict[str, Any]:
        """Return the mutable code namespace (for ``code.execute`` tool)."""
        return self._code_namespace

    def update_code_namespace(self, updates: dict[str, Any]) -> None:
        """Merge new bindings into the code namespace."""
        self._code_namespace.update(updates)


__all__ = ["RunState"]
