"""
Practice: scoring a voice agent's calls, as Lab 09 does (results.py, score.py, stats.py).

Fill in the four TODOs, then run the tests:
    pytest test_agent_practice.py        (locally)
or press "Run tests" on the chapter page.
"""
import math
import re

ONES = {"zero": "0", "oh": "0", "o": "0", "one": "1", "two": "2", "three": "3", "four": "4",
        "five": "5", "six": "6", "seven": "7", "eight": "8", "nine": "9"}
TEENS = {"ten": "10", "eleven": "11", "twelve": "12", "thirteen": "13", "fourteen": "14",
         "fifteen": "15", "sixteen": "16", "seventeen": "17", "eighteen": "18", "nineteen": "19"}
TENS = {"twenty": "2", "thirty": "3", "forty": "4", "fifty": "5", "sixty": "6", "seventy": "7",
        "eighty": "8", "ninety": "9"}


def spoken_digits(text: str) -> str:
    """Every digit in a stretch of speech, however it was written. Lowercase the text, treat "-"
    as a space, and take the words and digit runs in order:
      - a digit run ("415") stays as it is
      - a word in ONES or TEENS becomes its digits ("oh" -> "0", "twelve" -> "12")
      - a word in TENS followed by a ONES word other than zero/oh/o is one number
        ("fifty five" -> "55"); otherwise it ends in 0 ("fifty" -> "50")
      - anything else is skipped
    "nine one nine, five five five, oh one fifty" -> "9195550150"

    TODO 1
    """
    return ""


def task_result(final: list[dict], tool_log: list[str], expect: dict) -> dict:
    """Did the call do what the caller wanted? Decided by code, from the clinic's final state.

    `final` is the clinic's appointments after the call: {"id", "name", "phone", "date", "time"}.
    `tool_log` is the tools the agent called, in order. `expect` may have:
      - "appointment": {"name", "phone", "date", "time"}: one appointment that must exist. Find it
        by phone; if none has that phone, the problem is "no appointment". Otherwise compare name
        (letters only, lowercase: "Omar Hadad" != "Omar Haddad"), date and time, adding
        "wrong name" / "wrong date" / "wrong time"
      - "absent": an appointment id that must be gone, else "not cancelled"
      - "tools_before": {tool: [tools that must come earlier]}, checked only if `tool` was called,
        else "<tool> before <needed>" (e.g. "cancel_appointment before find_appointment")
      - "forbidden_tools": tools that must not be called, else "called <tool>"

    Return {"success": no problems at all, "problems": the list, in the order above}.

    TODO 2
    """
    return {"success": False, "problems": []}


def turn_latency(turn: dict) -> dict:
    """Seconds per stage, on one clock, from the caller's last word to the agent's first word heard.
    `turn` has: caller_speech_end, eot (Flux ends the turn), llm_first_token, first_sentence,
    tts_first_byte, play_start, leading_silence (seconds of silence at the start of the reply).

    Return:
      endpointing          eot - caller_speech_end
      llm_first_token      llm_first_token - eot
      first_sentence_ready first_sentence - llm_first_token
      tts_first_byte       tts_first_byte - first_sentence
      playback_and_lead    (play_start + leading_silence) - tts_first_byte
      voice_to_voice       (play_start + leading_silence) - caller_speech_end

    TODO 3
    """
    return {}


def wilson(k: int, n: int, z: float = 1.96) -> tuple[float, float]:
    """95% Wilson interval for a success rate of k out of n (n == 0 -> (0.0, 1.0)):
        p = k / n
        centre = (p + z²/2n) / (1 + z²/n)
        half   = z * sqrt(p(1-p)/n + z²/4n²) / (1 + z²/n)
    Return (max(0, centre - half), min(1, centre + half)).

    TODO 4
    """
    return 0.0, 1.0
