#!/usr/bin/env python3
"""tide — the tide-table pipeline driver.

ONE reproducible command regenerates every DOWNSTREAM ARTIFACT from its parsers,
in dependency order. The catalog is not hand-maintained state with an unknowable
"valid as of" — it is generated, every artifact stamped (parsers-over-copy). Run
under system python3 (numpy + pydantic).

    python3 tide.py build               # regenerate all artifacts
    python3 tide.py build catalog_view  # just one step
    python3 tide.py test                # run the pytest suite (the mechanical gate)
    python3 tide.py serve               # serve the triangle viz (load-once, Ctrl-C to stop)
    python3 tide.py list                # show the pipeline

Each step is importable and pure-ish (reads the corpus, writes one artifact), so
the same functions back the test suite. Add a step → add one STEPS entry.
"""
from __future__ import annotations
import subprocess
import sys
from pathlib import Path

HERE = Path(__file__).resolve().parent
ROOT = HERE.parent.parent                         # …/Sound/Tidal
sys.path.insert(0, str(HERE))


def _track_recording_map():
    import build_track_recording_map as m
    m.main()
    return m.OUT


def _catalog_view():
    import build_catalog_view as m
    m.main()                                      # validates against CatalogView on emit
    return m.OUT


def _catalog():
    import build_catalog as m
    m.main()                                      # validates against Catalog on emit
    return m.OUT


def _patterns():
    import pattern_ngrams as m
    m.main()                                      # validates against PatternRegistry on emit
    return m.OUT


def _presence():
    import build_presence as m
    m.main()                                      # validates against Presence on emit
    return m.OUT


def _tokens():
    sys.path.insert(0, str(ROOT / "tools"))
    import gen_tokens as t
    t.main()                                      # validates against DesignTokens on emit
    return t.OUT


def _ts_types():
    sys.path.insert(0, str(ROOT / "tools"))
    import gen_ts_types as g
    g.main()
    return g.OUT


# name → (one-line description, fn → artifact Path). Dependency order top-to-bottom.
STEPS = [
    ("track_recording_map", "L0 metadata map · site tracklist × take_gig_map", _track_recording_map),
    ("catalog_view",        "triangle · A=score ⋈ C=metadata ⋈ B=recording (validated)", _catalog_view),
    ("catalog",             "GENERATED catalog · catalog_view ⊕ authored overlay", _catalog),
    ("patterns",            "pattern registry · phrases-as-things + n-gram clustering", _patterns),
    ("presence",            "distribution SSOT · authored overlay → presence.json (validated)", _presence),
    ("tokens",              "fleet color language · ontology → tokens.css + tokens.json", _tokens),
    ("ts_types",            "pydantic models → generated TypeScript (DRY)", _ts_types),
]
NAMES = [n for n, _, _ in STEPS]


def cmd_build(which):
    todo = which or NAMES
    bad = [w for w in todo if w not in NAMES]
    if bad:
        sys.exit(f"unknown step(s): {', '.join(bad)}\n  available: {', '.join(NAMES)}")
    print(f"⛵ tide build — {len(todo)} step(s)\n")
    for name, desc, fn in STEPS:
        if name not in todo:
            continue
        print(f"▸ {name}  ({desc})")
        try:
            out = fn()
            rel = Path(out).relative_to(ROOT) if Path(out).is_absolute() else out
            print(f"  ✓ {rel}\n")
        except Exception as e:          # surface, don't swallow — a broken parser must fail loud
            print(f"  ✗ FAILED: {type(e).__name__}: {e}\n")
            raise
    print("✓ tide build complete")


def cmd_test():
    print("⛵ tide test — pytest\n")
    r = subprocess.run([sys.executable, "-m", "pytest", str(HERE / "tests"), "-q"])
    sys.exit(r.returncode)


def cmd_serve(args):
    """Serve the tide-table dir so the triangle viz loads with NO path fragility.
    Source is embedded in catalog_view.json (load-once); only audition proxies need
    the server, and they live under HERE, so serving HERE resolves everything."""
    port = 8731
    if args and args[0].isdigit():
        port = int(args[0])
    serve_py = HERE.parent / "serve.py"        # armada/serve.py (Range-capable)
    base = f"http://127.0.0.1:{port}"
    print(f"⛵ tide serve — open:\n    {base}/triangle.html   (catalog)"
          f"\n    {base}/corpus.html     (by-the-numbers dataviz)\n   (Ctrl-C to stop)\n")
    try:
        subprocess.run([sys.executable, str(serve_py), "--dir", str(HERE), "--port", str(port)])
    except KeyboardInterrupt:
        print("\n✓ stopped")


def cmd_corpus(args):
    """Bundle the by-the-numbers dataviz into one self-contained file for deploy."""
    r = subprocess.run([sys.executable, str(HERE / "build_corpus.py"), *args])
    sys.exit(r.returncode)


def cmd_list():
    print("⛵ tide pipeline (dependency order):\n")
    for name, desc, _ in STEPS:
        print(f"  {name:<22} {desc}")
    print("\n  test                   run the pytest suite")
    print("  serve [port]           serve the triangle + corpus viz (default :8731)")
    print("  corpus [-o out.html]   bundle corpus.html → self-contained deploy file")


def main():
    args = sys.argv[1:]
    cmd = args[0] if args else "build"
    if cmd == "build":
        cmd_build(args[1:])
    elif cmd == "test":
        cmd_test()
    elif cmd == "serve":
        cmd_serve(args[1:])
    elif cmd == "corpus":
        cmd_corpus(args[1:])
    elif cmd in ("list", "ls"):
        cmd_list()
    else:
        sys.exit(f"usage: tide.py [build [step…] | test | serve [port] | list]")


if __name__ == "__main__":
    main()
