#!/usr/bin/env python3
import argparse, os, subprocess, sys
from pathlib import Path
from shutil import which

SHADER_EXTS = {
    ".frag", ".vert", ".comp", ".geom", ".tesc", ".tese",
    ".mesh", ".task",
    ".rgen", ".rint", ".rahit", ".rchit", ".rmiss", ".rcall",
}

PROFILE_FLAGS = {
    "Debug": ["-g"],
    "Release": ["-O"],
    # Matches the old `compile_shaders.ps1` defaults (-O + -g).
    "RelWithDebInfo": ["-O", "-g"],
}

STAGE_FLAGS_BY_EXT = {
    ".mesh": ["-fshader-stage=mesh"],
    ".task": ["-fshader-stage=task"],
}


def find_glslc(user_path):
    if user_path:
        return user_path

    env_glslc = os.environ.get("GLSLC")
    if env_glslc:
        return env_glslc

    vulkan_sdk = os.environ.get("VULKAN_SDK")
    if vulkan_sdk:
        sdk = Path(vulkan_sdk)
        candidates = [
            sdk / "Bin" / "glslc.exe",
            sdk / "Bin" / "glslc",
            sdk / "bin" / "glslc.exe",
            sdk / "bin" / "glslc",
        ]
        for c in candidates:
            if c.exists():
                return str(c)

    for name in ("glslc", "glslc.exe"):
        p = which(name)
        if p:
            return p

    sys.exit(
        "ERROR: `glslc` not found. Install the Vulkan SDK and ensure `glslc` is on PATH,\n"
        "       or set `VULKAN_SDK`, `GLSLC`, or pass `--glslc /path/to/glslc`."
    )


def parse_args():
    p = argparse.ArgumentParser(description="Compile GLSL shaders under shaders/ to .spv next to sources.")
    cfg = p.add_mutually_exclusive_group()
    cfg.add_argument("--debug", action="store_true", help="Debug profile (adds -g, no -O).")
    cfg.add_argument("--release", action="store_true", help="Release profile (adds -O, no -g).")
    p.add_argument(
        "--config",
        choices=list(PROFILE_FLAGS.keys()),
        default="RelWithDebInfo",
        help="Compilation profile (default: RelWithDebInfo).",
    )
    p.add_argument("--shader-dir", default="shaders", help="Shader source directory (default: shaders).")
    p.add_argument("--glslc", default=None, help="Path to glslc (default: auto-detect).")
    p.add_argument("--target-env", default="vulkan1.3", help="Vulkan target env for glslc (default: vulkan1.3).")
    p.add_argument("--no-werror", action="store_true", help="Do not pass -Werror.")
    p.add_argument("--quiet", action="store_true", help="Only print errors.")
    return p.parse_args()


def collect_sources(shader_dir: Path):
    return sorted([p for p in shader_dir.rglob("*") if p.is_file() and p.suffix.lower() in SHADER_EXTS])


def compile_one(glslc: str, src: Path, shader_dir: Path, args) -> int:
    out_path = src.parent / f"{src.name}.spv"
    stage_flags = STAGE_FLAGS_BY_EXT.get(src.suffix.lower(), [])

    cmd = [
        glslc,
        str(src),
        f"--target-env={args.target_env}",
        "-I", str(shader_dir),
        *PROFILE_FLAGS[args.config],
        *stage_flags,
    ]
    if not args.no_werror:
        cmd.append("-Werror")
    cmd += ["-o", str(out_path)]

    if not args.quiet:
        print(subprocess.list2cmdline(cmd))

    proc = subprocess.run(cmd, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    if proc.stdout and not args.quiet:
        sys.stdout.write(proc.stdout)
    if proc.returncode != 0:
        if proc.stderr:
            sys.stderr.write(proc.stderr)
    elif proc.stderr and not args.quiet:
        sys.stderr.write(proc.stderr)
    return proc.returncode


def main() -> int:
    args = parse_args()

    if args.debug:
        args.config = "Debug"
    elif args.release:
        args.config = "Release"

    repo_root = Path(__file__).resolve().parent
    shader_dir = (repo_root / args.shader_dir).resolve()
    if not shader_dir.exists():
        sys.stderr.write(f"ERROR: shader dir not found: {shader_dir}\n")
        return 2

    glslc = find_glslc(args.glslc)

    sources = collect_sources(shader_dir)
    if not sources:
        if not args.quiet:
            print(f"No shaders found under {shader_dir}")
        return 0

    failed = 0
    for src in sources:
        rc = compile_one(glslc, src, shader_dir, args)
        if rc != 0:
            failed += 1

    if failed:
        sys.stderr.write(f"ERROR: {failed} shader(s) failed to compile.\n")
        return 1
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
