"""
Practice: a voice agent's one-second budget, measured. Lab 08's timing code for a three-model voice
agent (06_voice_agent.py): where a call's silence goes, the median vs. the slowest call, a backup
request for a stalled voice (mm_utils.tts_hedged), and when to decide the caller has finished
(metrics.end_of_turn).

Fill in the TODOs, then run the tests:
    pytest test_voice_practice.py        (locally)
or press "Run tests" on the chapter page.
"""
import statistics

FRAME_MS = 20  # the lab's voice activity detector marks speech in 20 ms frames


def turn_gap(stt, first_sentence, first_audio, wait=0.5):
    """Seconds of silence the caller hears: the wait for silence, then speech-to-text, then the
    language model's first sentence, then the voice's first sound. `first_audio` is None when
    the voice stalled (no sound before the timeout): return None then."""
    # TODO
    return 0.0


def summary(gaps, budget=1.0):
    """Summarise a list of gaps (None = a stalled call) as a dict:
    - "under": how many calls were within the budget,
    - "stalled": how many were None,
    - "median": the median, counting a stalled call as infinitely slow (float("inf")),
    - "slowest": the slowest gap (float("inf") if any call stalled)."""
    # TODO
    return {"under": -1, "stalled": -1, "median": 0.0, "slowest": 0.0}


def hedged(primary, backup, after=1.0):
    """A backup ("hedged") request. The first voice service starts at 0 and sends its first sound
    after `primary` seconds (None = never). If it has sent nothing by `after` seconds, a second
    service is asked too; its first sound comes `backup` seconds after it was asked (None = never).
    Whichever sounds first plays. Return (seconds to first sound, "primary" or "backup"), or
    (None, None) if neither ever sounds."""
    # TODO
    return (None, None)


def end_of_turn(is_speech, silence_ms, frame_ms=FRAME_MS):
    """The lab's endpointer. `is_speech` is a list of booleans, one per frame. Return the index of
    the first frame at which `silence_ms` of silence has followed some speech, or None if that
    never happens."""
    # TODO
    return None


def words_turn(is_speech, second_start, last, mid_done, end_done, short=500, long=1200):
    """The lab's "check the words" rule. A model has judged the words at the pause (`mid_done`)
    and at the end (`end_done`): True = they sound finished. Wait `short` ms of silence if
    finished, `long` ms if not.
    - Part one: if the endpointer fires before frame `second_start`, the caller was cut off.
    - Part two: run the endpointer on is_speech[second_start:]; if it fires before frame `last`
      (the last speech frame), the caller was cut off too.
    Return None if cut off, else the wait in ms after the last speech frame."""
    # TODO
    return 0
