"""ToolNode — tool invocation node.

Calls any registered tool by name, resolving template arguments
from upstream inputs.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Literal

from pydantic import Field

from core.common import JsonObject
from core.pathways.models.io import NodeInputs, NodeOutputs
from core.pathways.models.tool import ToolArgs, ToolOutput
from core.pathways.nodes.base import NodeBase

if TYPE_CHECKING:
    from core.pathways.context import Context


class ToolNode(NodeBase):
    """Tool invocation — the primary node type.

    Calls any tool from ctx.tools by name. Resolves ``{{var}}`` templates
    in arguments from upstream node outputs.

    Attributes
    ----------
    tool : str
        Fully qualified tool name (e.g., ``"web.search"``, ``"workspace.read_file"``).
    args : JsonObject
        Static arguments with optional ``{{var}}`` template placeholders.
    """

    type: Literal["tool"] = "tool"
    tool: str
    args: JsonObject = Field(default_factory=dict)

    async def compute(self, inputs: NodeInputs, ctx: Context) -> NodeOutputs:
        """Execute tool call, resolving templates and merging inputs."""
        template_data = inputs.to_dict()
        handler = ctx.tools.get(self.tool)
        if not handler:
            msg = f"Tool not found: {self.tool}"
            raise RuntimeError(msg)

        resolved = self._resolve_templates(dict(self.args), template_data)
        call_args: dict[str, object] = resolved if isinstance(resolved, dict) else {}

        for key, value in inputs.items():
            if key not in call_args:
                call_args[key] = value

        raw = await handler(ToolArgs(**call_args), ctx)
        tool_out = raw if isinstance(raw, ToolOutput) else ToolOutput.model_validate(raw)
        output_dict = tool_out.model_dump()

        # Promote data fields so downstream templates can use
        # {{create_job.job_uuid}} instead of {{create_job.output.data.job_uuid}}.
        if tool_out.success and isinstance(tool_out.data, dict):
            output_dict.update(tool_out.data)

        return NodeOutputs(output=output_dict)


__all__ = ["ToolNode"]
