#!/usr/bin/env python3
"""gig-metadata — build a gig's tracks.json from the parsers, never by hand.

Sources, all of them canonical (nothing here is typed twice):
  backlog.md          via tools/setlist.py    -> membership, ORDER, name, bpm, section
  the .tidal files    via tools/setlist_samples.py -> sample packs actually referenced
  a segments file     (JSON: [{name,start_s,end_s}]) -> measured timecodes from the take

The timecodes matter beyond bookkeeping: they are what lets a visualist
(Slopmotion) cut to the set without re-deriving boundaries by ear.

Usage:
  tools/gig-metadata.py --gig opal-festival-2026 --title "Sunset Forest" \
      --date 2026-08-08 --segments segments.json --out tracks.json
"""
from __future__ import annotations

import argparse
import json
import re
import subprocess
import sys
from pathlib import Path

REPO = Path(__file__).resolve().parent.parent

STYLE_BY_PATH = [
    ("live/midi/nova/ambient/", "ambient"),
    ("live/midi/nova/techno/", "techno"),
    ("live/midi/nova/dnb/", "dnb"),
    ("live/midi/nova/lounge/", "lounge"),
    ("live/midi/nova/acid/", "acid"),
    ("live/midi/nova/jazz/", "nujazz"),
    ("live/midi/nova/remix/", "remix"),
    ("live/midi/nova/breaks/", "breaks"),
    ("live/techno/", "techno"),
    ("live/collab/", "collab"),
    ("live/boeuf/", "jam"),
]


def style_of(path: str) -> str:
    for prefix, style in STYLE_BY_PATH:
        if path.startswith(prefix):
            return style
    return "other"


def read_setlist() -> list[dict]:
    """tools/setlist.py --long is THE parser for what a set is and in what order."""
    out = subprocess.run([sys.executable, str(REPO / "tools/setlist.py"), "--long"],
                         capture_output=True, text=True, timeout=120)
    if out.returncode != 0:
        raise SystemExit(f"setlist.py failed:\n{out.stderr}")
    rows = []
    for line in out.stdout.splitlines():
        m = re.match(r"\s*(\S+)\s+(.{1,20}?)\s\s+(\S+\.tidal)\s+(.*)$", line)
        if not m:
            continue
        bpm, section, path, name = m.groups()
        rows.append({
            "name": name.strip().rstrip("]"),
            "file": path.strip(),
            "bpm": int(bpm) if bpm.isdigit() else None,
            "section": section.strip(),
            "style": style_of(path.strip()),
        })
    return rows


def read_samples(files: list[str]) -> dict[str, list[str]]:
    """Which sample folders each track references (setlist_samples is the parser)."""
    per_track: dict[str, list[str]] = {}
    for f in files:
        p = REPO / f
        if not p.exists():
            per_track[f] = []
            continue
        out = subprocess.run([sys.executable, str(REPO / "tools/setlist_samples.py"), str(p)],
                             capture_output=True, text=True, timeout=120)
        names = []
        for line in out.stdout.splitlines():
            m = re.match(r"\s{2}(\S+)\s+/", line)
            if m:
                names.append(m.group(1))
        per_track[f] = sorted(set(names))
    return per_track


def hms(x: float) -> str:
    x = max(0.0, float(x))
    return f"{int(x // 3600)}:{int(x % 3600) // 60:02d}:{int(x % 60):02d}"


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--gig", required=True)
    ap.add_argument("--title", required=True)
    ap.add_argument("--date", required=True)
    ap.add_argument("--venue")
    ap.add_argument("--stage")
    ap.add_argument("--segments", help="JSON [{name,start_s,end_s}] measured from the take")
    ap.add_argument("--only-played", action="store_true",
                    help="keep only tracks present in --segments (drops soundcheck/unplayed)")
    ap.add_argument("--out", required=True)
    a = ap.parse_args()

    rows = read_setlist()
    segs = {}
    if a.segments:
        for s in json.loads(Path(a.segments).read_text()):
            segs[s["name"]] = s

    tracks = []
    for r in rows:
        stem = Path(r["file"]).stem
        seg = segs.get(stem)
        if a.only_played and not seg:
            continue
        entry = dict(r)
        if seg:
            entry["start_s"] = round(seg["start_s"], 2)
            entry["end_s"] = round(seg["end_s"], 2)
            entry["duration_s"] = round(seg["end_s"] - seg["start_s"], 2)
            entry["start"] = hms(seg["start_s"])
            entry["end"] = hms(seg["end_s"])
        tracks.append(entry)

    samples = read_samples([t["file"] for t in tracks])
    for t in tracks:
        t["samples"] = samples.get(t["file"], [])

    bpms = [t["bpm"] for t in tracks if t.get("bpm")]
    styles: dict[str, int] = {}
    for t in tracks:
        styles[t["style"]] = styles.get(t["style"], 0) + 1

    doc = {
        "gig": a.gig,
        "title": a.title,
        "date": a.date,
        "venue": a.venue,
        "stage": a.stage,
        "bpmRange": [min(bpms), max(bpms)] if bpms else None,
        "styleDistribution": dict(sorted(styles.items(), key=lambda kv: -kv[1])),
        "trackCount": len(tracks),
        "totalDuration_s": round(sum(t.get("duration_s", 0) for t in tracks), 2),
        "samplePacks": sorted({s for t in tracks for s in t["samples"]}),
        "tracks": tracks,
    }
    doc = {k: v for k, v in doc.items() if v is not None}
    Path(a.out).parent.mkdir(parents=True, exist_ok=True)
    Path(a.out).write_text(json.dumps(doc, indent=2, ensure_ascii=False) + "\n")
    print(f"wrote {a.out}: {len(tracks)} tracks, "
          f"{doc.get('bpmRange')} bpm, {len(doc['samplePacks'])} sample packs, "
          f"{hms(doc['totalDuration_s'])} total")


if __name__ == "__main__":
    main()
