"""Step executor – runs a single LLM → tool-call → result cycle."""

from __future__ import annotations

from typing import Any

from thryve.core.agent.models import StepOutput
from thryve.core.backends.adapter import BackendAdapter
from thryve.core.context.models import ChatResponse, Message, ToolCall, ToolResult
from thryve.core.tools.executor import ToolExecutor
from thryve.utils import get_logger

logger = get_logger("agent.executor")


class StepExecutor:
    """Execute one step of the agent loop.

    A *step* is:
    1. Call the LLM with the current message context.
    2. If the response contains tool calls, execute them all.
    3. Return the combined output as a :class:`StepOutput`.
    """

    def __init__(self, llm: BackendAdapter, tool_executor: ToolExecutor) -> None:
        self._llm = llm
        self._tool_executor = tool_executor

    async def execute(
        self,
        messages: list[Message],
        turn: int = 0,
        **llm_kwargs: Any,
    ) -> tuple[ChatResponse, StepOutput]:
        """Run one step and return (raw_response, step_output).

        The caller is responsible for appending the assistant / tool messages
        back into the conversation context.
        """
        # 1. Call LLM
        response: ChatResponse = await self._llm.generate_async(messages, **llm_kwargs)

        # 2. Execute tool calls (if any)
        tool_calls: list[ToolCall] = response.tool_calls or []
        tool_results: list[ToolResult] = []
        if tool_calls:
            tool_results = await self._tool_executor.execute_batch(tool_calls)

        step = StepOutput(
            turn=turn,
            tool_calls=tool_calls,
            tool_results=tool_results,
            llm_text=response.content,
        )

        return response, step
