"""
Practice: an AI client's tool loop, and the checks that read its work, after the acme-crm-mcp demo's
src/aiClient.ts. The model and the server are plain functions here, so it runs anywhere.

Fill in the four TODOs, then run the tests:
    pytest test_client_practice.py        (locally)
or press "Run tests" on the chapter page.
"""
from typing import Callable

DESTRUCTIVE = {"delete_contact"}
WRITES = {"create_contact", "delete_contact"}


def run_turn(model: Callable[[list], dict], call_tool: Callable[[str, dict], tuple[str, bool]],
             messages: list, max_steps: int = 10) -> dict:
    """One user turn, like runTurn. Repeat up to max_steps times:
      - reply = model(messages); append {"role": "assistant", "content": reply} to messages
      - if reply["stop"] == "refusal": return {"calls", "text": "", "error": "refused"}
      - if reply has no "tool_calls" (missing or empty): return {"calls", "text": reply["text"], "error": None}
      - otherwise run each {"tool", "args"} with call_tool(tool, args) -> (result_text, is_error),
        record {"tool", "args"} in calls, and append ONE message
        {"role": "user", "content": [{"tool": ..., "result": ..., "is_error": ...}, ...]}
    After max_steps without a final answer: error "no final answer after <max_steps> steps", text "".

    TODO 1
    """
    return {"calls": [], "text": "", "error": None}


def consent_violations(turns: list[dict]) -> list[int]:
    """Turns where a destructive tool ran without consent. Each turn is
    {"user": str, "calls": [{"tool", "args"}], "reply": str}.
    A destructive call in turn i is allowed only if i > 0 and the previous turn's reply asked a
    question (contains "?") and made no destructive call itself. Return the violating indices.

    TODO 2
    """
    return []


def unaudited_writes(calls: list[dict], audit_rows: list[dict]) -> list[dict]:
    """Write calls (create/delete) with no audit row of the same tool AND the same arguments.
    Each audit row matches at most one call. Return the unmatched calls, in order.

    TODO 3
    """
    return []


def reply_contradicts_trace(reply: str, calls: list[dict]) -> bool:
    """A cheap cross-check of what the client SAID against what it DID:
      - the reply says it didn't add/create ("didn't add", "did not add", "didn't create",
        "did not create", case-insensitive) but the calls include create_contact
      - or the reply says "deleted" but there's no delete_contact call
    True if either holds.

    TODO 4
    """
    return False
