#!/usr/bin/env python3
"""MIRPAMO — local Mixamo-style character pipeline.

Orchestrates headless Blender. Subcommands:

  mirpamo rig   <mesh> -o rigged.glb [--markers m.json] [--name N]
  mirpamo anim  <rigged.glb> <clip> [<clip> ...] -o out.glb
                [--bonemap map.json] [--inplace] [--fps 30]
  mirpamo batch <rigged.glb> <clips_dir> -o out_dir [--bonemap ...] [--inplace]
  mirpamo verify <file.glb> [--expect-anim]
  mirpamo smoke [--keep]

Blender binary resolution: $MIRPAMO_BLENDER, then
/Applications/Blender.app/Contents/MacOS/Blender, then `blender` on PATH.
"""
import argparse
import glob
import os
import shutil
import subprocess
import sys

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
BPY = os.path.join(ROOT, "bpy")


def blender_bin():
    cand = os.environ.get("MIRPAMO_BLENDER")
    if cand and os.path.exists(cand):
        return cand
    mac_default = "/Applications/Blender.app/Contents/MacOS/Blender"
    if os.path.exists(mac_default):
        return mac_default
    path = shutil.which("blender")
    if path:
        return path
    sys.exit("mirpamo: Blender not found. Install Blender or set MIRPAMO_BLENDER.")


def run_stage(script, stage_argv):
    cmd = [blender_bin(), "-b", "--python", os.path.join(BPY, script),
           "--", *stage_argv]
    proc = subprocess.run(cmd)
    if proc.returncode != 0:
        sys.exit(f"mirpamo: stage {script} failed (exit {proc.returncode})")


def cmd_rig(a):
    argv = ["--in", a.mesh, "--out", a.out]
    if a.markers:
        argv += ["--markers", a.markers]
    if a.name:
        argv += ["--name", a.name]
    run_stage("autorig.py", argv)


def anim_argv(rig, clips, out, a):
    argv = ["--rig", rig, "--out", out]
    for c in clips:
        argv += ["--clip", c]
    if a.bonemap:
        argv += ["--bonemap", a.bonemap]
    if a.inplace:
        argv += ["--inplace"]
    if getattr(a, "fps", None):
        argv += ["--fps", str(a.fps)]
    return argv


def cmd_anim(a):
    run_stage("retarget.py", anim_argv(a.rig, a.clips, a.out, a))


def cmd_batch(a):
    clips = sorted(
        p for ext in ("*.bvh", "*.fbx", "*.glb")
        for p in glob.glob(os.path.join(a.clips_dir, ext)))
    if not clips:
        sys.exit(f"mirpamo: no clips (.bvh/.fbx/.glb) in {a.clips_dir}")
    os.makedirs(a.out, exist_ok=True)
    base = os.path.splitext(os.path.basename(a.rig))[0]
    for c in clips:
        cn = os.path.splitext(os.path.basename(c))[0]
        out = os.path.join(a.out, f"{base}@{cn}.glb")
        print(f"== {c} -> {out}")
        run_stage("retarget.py", anim_argv(a.rig, [c], out, a))


def cmd_verify(a):
    argv = ["--in", a.file]
    if a.expect_anim:
        argv += ["--expect-anim"]
    run_stage("verify_glb.py", argv)


def cmd_smoke(a):
    out = os.path.join(ROOT, "out", "smoke")
    os.makedirs(out, exist_ok=True)
    mesh = os.path.join(out, "test_human.glb")
    clip = os.path.join(out, "test_clip.bvh")
    rigged = os.path.join(out, "test_human_rigged.glb")
    final = os.path.join(out, "test_human_walking.glb")

    print("== smoke 1/5: generate test mesh")
    run_stage("smoke_mesh.py", ["--out", mesh])
    print("== smoke 2/5: generate test clip")
    subprocess.run([sys.executable,
                    os.path.join(ROOT, "tests", "gen_test_bvh.py"), clip],
                   check=True)
    print("== smoke 3/5: autorig")
    run_stage("autorig.py", ["--in", mesh, "--out", rigged])
    print("== smoke 4/5: retarget")
    run_stage("retarget.py", ["--rig", rigged, "--clip", clip, "--out", final])
    print("== smoke 5/5: verify")
    run_stage("verify_glb.py", ["--in", final, "--expect-anim",
                                "--min-frames", "60"])
    if not a.keep:
        pass  # outputs stay in out/smoke for inspection; gitignored
    print("\nMIRPAMO SMOKE TEST PASSED")
    print(f"  inspect: {final}")


def main():
    ap = argparse.ArgumentParser(prog="mirpamo")
    sub = ap.add_subparsers(dest="cmd", required=True)

    p = sub.add_parser("rig", help="auto-rig a raw mesh")
    p.add_argument("mesh")
    p.add_argument("-o", "--out", required=True)
    p.add_argument("--markers")
    p.add_argument("--name")
    p.set_defaults(fn=cmd_rig)

    p = sub.add_parser("anim", help="retarget clip(s) onto a rig")
    p.add_argument("rig")
    p.add_argument("clips", nargs="+")
    p.add_argument("-o", "--out", required=True)
    p.add_argument("--bonemap")
    p.add_argument("--inplace", action="store_true")
    p.add_argument("--fps", type=int)
    p.set_defaults(fn=cmd_anim)

    p = sub.add_parser("batch", help="retarget every clip in a dir")
    p.add_argument("rig")
    p.add_argument("clips_dir")
    p.add_argument("-o", "--out", required=True)
    p.add_argument("--bonemap")
    p.add_argument("--inplace", action="store_true")
    p.add_argument("--fps", type=int)
    p.set_defaults(fn=cmd_batch)

    p = sub.add_parser("verify", help="verify an output GLB")
    p.add_argument("file")
    p.add_argument("--expect-anim", action="store_true")
    p.set_defaults(fn=cmd_verify)

    p = sub.add_parser("smoke", help="end-to-end self test")
    p.add_argument("--keep", action="store_true")
    p.set_defaults(fn=cmd_smoke)

    a = ap.parse_args()
    a.fn(a)


if __name__ == "__main__":
    main()
