#!/usr/bin/env python3
"""autotune — search loop-finder settings for max grade quality (#19).

    python3 autotune.py --stems ~/Downloads/separated/htdemucs/*/drums.wav --n 24
    python3 autotune.py --stems <dir>  --method grid --max-stems 4 --role drums

Reports the current finder defaults vs the best-found profile (the quality lift)
and the top configs. Output is aggregated (counts, not filenames).
"""
import argparse
import glob
import sys
from pathlib import Path

HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(HERE))

from engine import autotune as AT  # noqa: E402


def _collect(patterns, max_stems):
    paths = []
    for pat in patterns:
        p = Path(pat)
        if p.is_dir():
            paths += [q for q in sorted(p.rglob("*.wav"))]
        else:
            paths += [Path(x) for x in sorted(glob.glob(pat))]
    # de-dup, cap
    seen, out = set(), []
    for q in paths:
        if q not in seen and q.is_file():
            seen.add(q); out.append(q)
    return out[:max_stems]


def _fmt(r):
    s = r["settings"]
    w = " ".join(f"{k[:4]}={s[k]}" for k in ("struct", "novel", "seam", "zc", "tempo_penalty") if k in s)
    tag = " (baseline)" if r.get("is_baseline") else ""
    return (f"obj {r['objective']:.3f}  grade {r['mean_grade']:.3f}  "
            f"cov {r['coverage']:.2f}  n={r['n_candidates']:3d}  "
            f"bars={s.get('bars')} top={s.get('top_n')}  {w}{tag}")


def main(argv=None):
    ap = argparse.ArgumentParser(description=__doc__,
                                 formatter_class=argparse.RawDescriptionHelpFormatter)
    ap.add_argument("--stems", nargs="+", required=True, help="dirs / globs of stem .wav")
    ap.add_argument("--method", choices=["random", "grid"], default="random")
    ap.add_argument("--n", type=int, default=24, help="configs to try")
    ap.add_argument("--max-stems", type=int, default=4, help="cap stems loaded (speed)")
    ap.add_argument("--seed", type=int, default=0)
    ap.add_argument("--top", type=int, default=8, help="leaderboard rows to print")
    a = ap.parse_args(argv)

    paths = _collect(a.stems, a.max_stems)
    if not paths:
        raise SystemExit("no stem .wav found for the given --stems")
    print(f"loading {len(paths)} stem(s)…", flush=True)
    stems = []
    for p in paths:
        try:
            stems.append(AT.load_stem(p))
        except Exception as e:  # noqa: BLE001
            print(f"  skip 1 stem ({type(e).__name__})")
    if not stems:
        raise SystemExit("no stems loaded")

    print(f"searching {a.n} configs ({a.method}) over {len(stems)} stem(s)…\n", flush=True)
    results = AT.search(stems, method=a.method, n=a.n, seed=a.seed)

    base = next(r for r in results if r.get("is_baseline"))
    best = results[0]
    print("rank  " + "-" * 70)
    for i, r in enumerate(results[:a.top], 1):
        print(f"{i:>2}.  {_fmt(r)}")
    lift = best["objective"] - base["objective"]
    print("\n" + "=" * 76)
    print(f"baseline objective {base['objective']:.3f}  →  best {best['objective']:.3f}"
          f"   (lift {lift:+.3f}, {100 * lift / max(base['objective'], 1e-6):+.1f}%)")
    if best.get("is_baseline"):
        print("→ defaults already win on this sample; widen the space or add stems.")
    else:
        print(f"→ winning profile: {best['settings']}")


if __name__ == "__main__":
    main()
