"""
Helpers for approval handling within the run loop. Keep only execution-time utilities that
coordinate approval placeholders and normalization; public APIs should stay in run.py or
peer modules.
"""

from __future__ import annotations

from collections.abc import Sequence
from typing import Any

from openai.types.responses import ResponseFunctionToolCall

from ..agent import Agent
from ..items import ItemHelpers, RunItem, ToolApprovalItem, ToolCallOutputItem, TResponseInputItem
from ..tool import ToolOrigin
from .items import ReasoningItemIdPolicy, run_item_to_input_item

# --------------------------
# Public helpers
# --------------------------


def append_approval_error_output(
    *,
    generated_items: list[RunItem],
    agent: Agent[Any],
    tool_call: Any,
    tool_name: str,
    call_id: str | None,
    message: str,
    tool_origin: ToolOrigin | None = None,
) -> None:
    """Emit a synthetic tool output so users see why an approval failed."""
    error_tool_call = _build_function_tool_call_for_approval_error(tool_call, tool_name, call_id)
    generated_items.append(
        ToolCallOutputItem(
            output=message,
            raw_item=ItemHelpers.tool_call_output_item(error_tool_call, message),
            agent=agent,
            tool_origin=tool_origin,
        )
    )


def filter_tool_approvals(interruptions: Sequence[Any]) -> list[ToolApprovalItem]:
    """Keep only approval items from a mixed interruption payload."""
    return [item for item in interruptions if isinstance(item, ToolApprovalItem)]


def approvals_from_step(step: Any) -> list[ToolApprovalItem]:
    """Return approvals from a step that may or may not contain interruptions."""
    interruptions = getattr(step, "interruptions", None)
    if interruptions is None:
        return []
    return filter_tool_approvals(interruptions)


def append_input_items_excluding_approvals(
    base_input: list[TResponseInputItem],
    items: Sequence[RunItem],
    reasoning_item_id_policy: ReasoningItemIdPolicy | None = None,
) -> None:
    """Append tool outputs to model input while skipping approval placeholders."""
    for item in items:
        converted = run_item_to_input_item(item, reasoning_item_id_policy)
        if converted is None:
            continue
        base_input.append(converted)


# --------------------------
# Private helpers
# --------------------------


def _build_function_tool_call_for_approval_error(
    tool_call: Any, tool_name: str, call_id: str | None
) -> ResponseFunctionToolCall:
    """Coerce raw tool call payloads into a normalized function_call for approval errors."""
    if isinstance(tool_call, ResponseFunctionToolCall):
        return tool_call
    namespace = None
    if isinstance(tool_call, dict):
        candidate = tool_call.get("namespace")
        if isinstance(candidate, str) and candidate:
            namespace = candidate
    else:
        candidate = getattr(tool_call, "namespace", None)
        if isinstance(candidate, str) and candidate:
            namespace = candidate

    kwargs: dict[str, Any] = {
        "type": "function_call",
        "name": tool_name,
        "call_id": call_id or "unknown",
        "status": "completed",
        "arguments": "{}",
    }
    if namespace is not None:
        kwargs["namespace"] = namespace
    return ResponseFunctionToolCall(**kwargs)
