#!/usr/bin/env python3
"""GUTS asset manifest builder (Lane D) — catalogues web/assets/** into manifest.json.

The manifest is a CATALOGUE OF WHAT EXISTS, never a wishlist: it is generated by scanning the
shipped files, so it can't drift from reality and can't promise a texture that isn't there.
That is what makes the runtime law safe (TECH.md): every entry is optional, `assets.get()`
returns null on miss, and the game boots fine with an empty assets/gen/.

    python3 pipeline/build_manifest.py            # scan -> web/assets/manifest.json
    python3 pipeline/build_manifest.py --dry-run  # print what it would write

Stdlib only — runs anywhere, including in qa.sh.
"""
import json, os, sys

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
ASSETS = os.path.join(ROOT, "web", "assets")
GEN = os.path.join(ASSETS, "gen")
AUDIO = os.path.join(ASSETS, "audio")
MODELS = os.path.join(ASSETS, "models")
OUT = os.path.join(ASSETS, "manifest.json")
BATCH_TEX = os.path.join(ROOT, "pipeline", "batch_textures.json")
BATCH_AUD = os.path.join(ROOT, "pipeline", "batch_audio.json")

DEFAULT_TILE = [4, 16]     # [repeats around theta, units of s per repeat] — see LANE_D_NOTES


def ls(d, ext):
    if not os.path.isdir(d):
        return []
    return sorted(f for f in os.listdir(d) if f.endswith(ext))


def build():
    tex_meta = json.load(open(BATCH_TEX)) if os.path.exists(BATCH_TEX) else {}
    aud_meta = json.load(open(BATCH_AUD)) if os.path.exists(BATCH_AUD) else {}
    m = {"textures": {}, "matcaps": {}, "models": {}, "audio": {"beds": {}, "sfx": {}}}

    for f in ls(GEN, ".webp"):
        slug = f[:-5]
        if slug.endswith("_n"):
            continue                                    # normals are attached to their albedo
        if slug.startswith("matcap_"):
            m["matcaps"][slug[len("matcap_"):]] = {"url": f"gen/{f}"}
            continue
        e = {"url": f"gen/{f}"}
        if os.path.exists(os.path.join(GEN, f"{slug}_n.webp")):
            e["normal"] = f"gen/{slug}_n.webp"
        e["tile"] = (tex_meta.get(slug) or {}).get("tile") or DEFAULT_TILE
        m["textures"][slug] = e

    for f in ls(MODELS, ".glb"):
        slug = f[:-4]
        e = {"url": f"models/{f}"}
        tris = (tex_meta.get(slug) or {}).get("tris")
        if tris:
            e["tris"] = tris
        m["models"][slug] = e

    for f in ls(AUDIO, ".ogg"):
        slug = f[:-4]
        meta = aud_meta.get(slug) or {}
        cat = "beds" if meta.get("category", "sfx") in ("ambience", "music", "bed") else "sfx"
        key = meta.get("key") or slug.split("-", 1)[-1]
        e = {"ogg": f"audio/{f}"}
        if os.path.exists(os.path.join(AUDIO, f"{slug}.m4a")):
            e["m4a"] = f"audio/{slug}.m4a"
        for k in ("loop", "gain", "seconds"):
            if k in meta:
                e[k] = meta[k]
        m["audio"][cat][key] = e
    return m


if __name__ == "__main__":
    m = build()
    n = (len(m["textures"]), len(m["matcaps"]), len(m["models"]),
         len(m["audio"]["beds"]), len(m["audio"]["sfx"]))
    print(f"manifest: {n[0]} textures, {n[1]} matcaps, {n[2]} models, "
          f"{n[3]} beds, {n[4]} sfx")
    if "--dry-run" in sys.argv:
        print(json.dumps(m, indent=2, sort_keys=True))
        sys.exit(0)
    os.makedirs(ASSETS, exist_ok=True)
    json.dump(m, open(OUT, "w"), indent=2, sort_keys=True)
    print(f"wrote {OUT}")
