"""Scripted prelude: tool calls a prompt built-in plays before the turn's first model call.

A prelude is a generator owned by the caller that starts the turn (the desktop onboarding opening is the
intended one). Each step it yields ``(content, tool_name, args)``: the assistant text and the one
tool call that row carries. The loop runs it through the unchanged ``run_tool_round`` (row persisted
before the tool runs, interim text and tool events emitted, tool hooks fired) and sends the tool's
result string back into the generator, so a later step can use an earlier answer.

It runs before any provider call, so the rows sit between this turn's user message and the first
model call: nothing cached is mutated, the system prompt is untouched, no user row is added, and
roles alternate ``user -> assistant(tool_calls) -> tool -> ... -> assistant(model)``. No API call is
counted and no ``pre/post_api_request`` hook fires for a step.
"""

from __future__ import annotations

import json
from typing import Any, Generator, Optional, Tuple

from agent.message_sanitization import deterministic_call_id
from agent.transports.types import NormalizedResponse, build_tool_call
from agent.turn_tool_round import run_tool_round

Prelude = Generator[tuple[str, str, dict], Optional[str], None]


def prelude_call_id(session_id: str, name: str, args: dict, index: int) -> str:
    """Seeded by the session, the call and its place in history: no two chats share an id (a rerun plays
    the same card at the same place), and it stays byte-stable on replay."""
    return deterministic_call_id(name, f"{session_id}:{json.dumps(args, sort_keys=True)}", index)


def run_scripted_prelude(agent: Any, s: Any, prelude: Prelude) -> Any:
    """Play ``prelude`` into the loop state ``s``; returns the last tool round's verdict, or None when
    no step ran. The caller ends the turn on ``return`` and skips the model on ``break``."""
    from agent.conversation_loop import _run_phase

    verdict, result = None, None
    while not agent._interrupt_requested:
        try:
            content, name, args = prelude.send(result)
        except StopIteration:
            break
        s.finish_reason = "tool_calls"
        s.assistant_message = NormalizedResponse(
            content=content, finish_reason="tool_calls",
            tool_calls=[build_tool_call(prelude_call_id(agent.session_id, name, args, len(s.messages)), name, args)])
        verdict = _run_phase(run_tool_round, agent, s)
        if verdict.action != "continue":
            break
        tail = s.messages[-1] if s.messages else {}
        result = tail.get("content") if tail.get("role") == "tool" else None
    prelude.close()
    return verdict


def play_prelude(agent: Any, s: Any, prelude: Optional[Prelude]) -> tuple[str, Any]:
    """``("return", result)`` ends the turn now, ``("break", None)`` skips the model call, ``("run", None)`` runs
    the loop as usual (also when there is no prelude)."""
    verdict = run_scripted_prelude(agent, s, prelude) if prelude is not None else None
    if verdict is not None and verdict.action in ("return", "break"):
        return verdict.action, verdict.result
    return "run", None
