"""Every silence that is still in a rendered video: the André rule is no breath of 0.25 s or more.

Measures the mono mix in 10 ms windows (the same measure as corte_por_onda.py) and lists every run below the
pause threshold that lasts MIN_S or more, with its time in the video. The threshold is the one corte_por_onda
uses (floor + 14 dB, never lower than 28 dB under the loud speech); `--limiar` forces it.

The first and the last silence count: a cut that starts 0.5 s before the first word, or ends 0.5 s after the
last one, feels slow. On a final video with a still end on purpose (`segura_fim` of the project), pass
`--fim-parado`: the silence that reaches the last frame is listed apart and does not count. Ranges that were kept
whole on purpose ("dry": false: a video playing on screen) show up as well: skip them by their time.

Usage:
    uv run python helpers/respiros.py <video> [--min 0.25] [--limiar -36] [--fim-parado] [--json]
Exit code 0 when nothing is found, 1 when there is a breath to look at, 2 on error.
"""

from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))
import corte_por_onda as onda  # noqa: E402
from common import fmt_time  # noqa: E402

MIN_S = 0.25


def find(db, th: float, min_s: float) -> list[tuple[float, float]]:
    out, run = [], None
    for i, v in enumerate(db):
        quiet = v < th
        if quiet and run is None:
            run = i
        if not quiet and run is not None:
            if (i - run) * onda.HOP_S >= min_s:
                out.append((run * onda.HOP_S, i * onda.HOP_S))
            run = None
    if run is not None and (len(db) - run) * onda.HOP_S >= min_s:
        out.append((run * onda.HOP_S, len(db) * onda.HOP_S))
    return out


def main(argv: list[str] | None = None) -> int:
    ap = argparse.ArgumentParser(description="Silêncios que sobraram num vídeo pronto (regra: nenhum de 0,25 s ou mais).")
    ap.add_argument("video", type=Path)
    ap.add_argument("--min", type=float, default=MIN_S, help="duração mínima do silêncio em segundos (0,25)")
    ap.add_argument("--limiar", type=float, help="dB abaixo do qual é silêncio (padrão: medido)")
    ap.add_argument("--fim-parado", action="store_true",
                    help="vídeo final com fim parado de propósito (segura_fim): o silêncio do fim não conta")
    ap.add_argument("--json", action="store_true")
    a = ap.parse_args(argv)
    if not a.video.is_file():
        print(f"ERRO: não achei {a.video}", file=sys.stderr)
        return 2
    db = onda.envelope(a.video, None)
    th = a.limiar if a.limiar is not None else onda.threshold(db)
    found = find(db, th, a.min)
    end = len(db) * onda.HOP_S
    tail = None
    if a.fim_parado and found and found[-1][1] >= end - 0.05:        # silence up to the last frame
        tail, found = found[-1], found[:-1]
    if a.json:
        print(json.dumps({"limiar_db": th, "min_s": a.min,
                          "respiros": [{"de": round(x, 2), "ate": round(y, 2), "dur": round(y - x, 2)} for x, y in found],
                          "fim_parado": ({"de": round(tail[0], 2), "dur": round(tail[1] - tail[0], 2)} if tail else None)},
                         ensure_ascii=False))
        return 1 if found else 0
    tail_note = (f"  fim parado: {fmt_time(tail[0])} até o fim ({tail[1] - tail[0]:.2f} s), não conta "
                 "(confira se bate com o segura_fim do projeto)") if tail else ""
    if not found:
        print(f"respiros: nenhum silêncio de {a.min:.2f} s ou mais (abaixo de {th:.0f} dB)")
        if tail_note:
            print(tail_note)
        return 0
    print(f"respiros: {len(found)} silêncio(s) de {a.min:.2f} s ou mais (abaixo de {th:.0f} dB):")
    for x, y in found:
        print(f"  {fmt_time(x)} a {fmt_time(y)}  ({y - x:.2f} s)")
    if tail_note:
        print(tail_note)
    print("  cada um é respiro que sobrou: parta o trecho ali no edl_manual.json ou rode o corte pela onda com "
          "--limiar uns 3 dB mais alto, ou no limiar medido acima (references/corte.md)")
    return 1


if __name__ == "__main__":
    raise SystemExit(main())
