"""
Practice: the decisions inside Lab 07's quality gates (gates.py, run_eval.py).

Every function works on numbers or transcripts that were already measured, so there are
no model calls: the gate logic is tested like any other code.

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


def relative_verdict(change: float, lo: float, hi: float, tolerance: float) -> str:
    """A relative WER gate. `change` is candidate minus baseline in points of WER, and
    (lo, hi) is its 95% interval from a paired speaker bootstrap (Chapter 4).

    Return one of:
      - "fail":    the change is bigger than `tolerance` AND the interval is above zero
      - "within":  the interval is above zero, but the change is within tolerance
                   (a real loss the team accepts: reported, never blocks)
      - "better":  the interval is below zero
      - "unclear": the interval includes zero

    The tolerance stops tiny real changes from blocking; the interval stops noise from blocking.

    TODO 1
    """
    return ""


def no_new_failures(current: dict[str, str], baseline_runs: list[dict[str, str]],
                    max_new: int = 0) -> tuple[str, list[str], list[str]]:
    """The invented_text gate. Each dict maps a non-speech clip id to its transcript; the right
    transcript is empty, so any text at all is a failure.

    A clip counts as already failing if it had text in ANY of the baseline's runs: even a
    local model flips a non-speech clip or two between runs.

    Return (status, new, fixed), with `new` and `fixed` sorted:
      - new:    clips with text now that never had text in any baseline run
      - fixed:  clips that had text in some baseline run and are empty now
      - status: "fail" if len(new) > max_new, else "pass"
    Don't net them out: "4 fixed, 2 new" still fails.

    TODO 2
    """
    return "pass", [], []


def normalize(text: str) -> str:
    """Lowercase, drop punctuation except % and decimal points, collapse spaces.
    (A simplified stand-in for the Whisper normalizer the lab uses.)"""
    text = re.sub(r"(?<!\d)\.|\.(?!\d)", " ", text.lower())
    text = re.sub(r"[^\w%.\s]", " ", text)
    return " ".join(text.split())


def must_pass(hypothesis: str, phrase: str) -> bool:
    """Does the transcript contain the required phrase, as whole words, after normalize()?
    "see page 24 of the deck" contains "page 24"; "see pages 24" doesn't.

    TODO 3
    """
    return False


def exit_code(results: list[tuple[str, str]], problems: list[str]) -> int:
    """What run_eval.py returns. `results` holds (status, severity) per gate, with status
    "pass" / "fail" and severity "block" / "warn". `problems` lists anything that stopped a
    valid measurement: a missing key, an API error, audio or an evaluation set that changed
    since the baseline.

      2: couldn't measure (any problem): not a quality verdict, whatever the gates say
      1: measured, and a blocking gate failed
      0: measured, and nothing blocks (a failed warn gate doesn't block)

    TODO 4
    """
    return 0
