r"""
Synthesize neural-TTS commentary clips for selected races in the demo manifest (self-contained
`data/races/manifest.json`, the GSYS flights) — or for every race plus `real.json` in one pass.

`live/tts/synth.py` renders the clips for a single feed (one top-level `events` list). The GSYS demo is
a manifest of 12 races, each carrying its own inlined feed under `data`, so this driver flattens every
race's `events` (and `teams`, for the foreign-name pronunciation pass) into ONE merged feed and hands
that to synth.py. Including `real.json` keeps its existing clips in the shared `index.json` (synth
rebuilds the rendered language's index from the feed it's given).

Idempotent: synth.py hashes each clip by (voice, rate, text), so only new/changed lines are sent to the
cloud — re-running after a commentary edit just fills the gaps.

  ..\..\..\.venv-demo\Scripts\python.exe export\synth_races.py --lang en
  Auth: gcloud auth application-default login   (same ADC the rest of the demo uses)
"""
from __future__ import annotations

import argparse
import json
import subprocess
import sys
import tempfile
from pathlib import Path

HERE = Path(__file__).resolve().parent
ROOT = HERE.parent.parent                                  # v3/broadcast
MANIFEST = ROOT / "demo" / "data" / "races" / "manifest.json"
REAL = ROOT / "demo" / "data" / "races" / "real.json"
SYNTH = ROOT / "live" / "tts" / "synth.py"


def merged_feed(race_ids: set[str] | None = None) -> dict:
    """Flatten selected manifest races, or all races plus real.json when no filter is supplied."""
    events: list[dict] = []
    teams_by_id: dict[str, dict] = {}

    def absorb(feed: dict) -> None:
        events.extend(feed.get("events") or [])
        for t in feed.get("teams") or []:
            teams_by_id.setdefault(t.get("teamId") or t.get("name") or id(t), t)

    if MANIFEST.exists():
        for r in json.loads(MANIFEST.read_text(encoding="utf-8")).get("races", []):
            if r.get("data") and (race_ids is None or r.get("id") in race_ids):
                absorb(r["data"])
    if REAL.exists() and race_ids is None:
        absorb(json.loads(REAL.read_text(encoding="utf-8")))
    return {"events": events, "teams": list(teams_by_id.values())}


def main() -> int:
    ap = argparse.ArgumentParser(description=__doc__,
                                 formatter_class=argparse.RawDescriptionHelpFormatter)
    ap.add_argument("--lang", default="en", help="comma-separated language(s), e.g. en or en,sv")
    ap.add_argument("--race", action="append", dest="races",
                    help="manifest race id to synthesize; repeat to select a small batch")
    args = ap.parse_args()

    selected = set(args.races) if args.races else None
    feed = merged_feed(selected)
    langs = sorted({lng for e in feed["events"] for lng in (e.get("i18n") or {})})
    want = {x.strip() for x in args.lang.split(",") if x.strip()}
    scope = ",".join(sorted(selected)) if selected else "all manifest races + real.json"
    print(f"merged feed ({scope}): {len(feed['events'])} events, {len(feed['teams'])} teams; "
          f"languages present: {langs or '—'}; synthesizing: {sorted(want)}")

    with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8") as tf:
        json.dump(feed, tf, ensure_ascii=False)
        # The child process opens this file before the parent context closes it. Flush the Python
        # buffer explicitly, otherwise a fast child can see an empty feed and silently do no work.
        tf.flush()
        tmp = tf.name
    try:
        return subprocess.run([sys.executable, str(SYNTH), tmp, "--lang", args.lang]).returncode
    finally:
        Path(tmp).unlink(missing_ok=True)


if __name__ == "__main__":
    sys.exit(main())
