#!/usr/bin/env python3
# 20260308 ChatGPT
# $Header$
#
# Example:
#   blender-bin-5.0.0 -b /path/to/colorpie.blend \
#     --python scripts/render_colorpie_glb_to_webp.py -- \
#     /path/to/glb/colorpie_gamut_20260308_1303.glb \
#     --outdir /path/to/out
#
# Optional:
#   --parent ColorpieParent
#   --front-frame 1
#   --back-frame 144
#   --quality 95
#
# This script assumes:
#   1) the .blend already has the correct camera, Eevee, world, lights, compositor, etc.
#   2) ColorpieParent is the animated top object
#   3) the incoming GLB is centered the same way as the original one

import bpy
import os
import sys
import time


def die(msg, code=1):
    print(f"ERROR: {msg}", file=sys.stderr)
    sys.exit(code)


def log(msg):
    print(msg, flush=True)


def parse_args(argv):
    if "--" in argv:
        argv = argv[argv.index("--") + 1:]
    else:
        argv = []

    glb_path = None
    outdir = "."
    parent_name = "ColorpieParent"
    front_frame = 1
    back_frame = 144
    quality = 95

    i = 0
    pos = []
    while i < len(argv):
        a = argv[i]

        if a == "--outdir":
            i += 1
            if i >= len(argv):
                die("Missing value after --outdir")
            outdir = argv[i]

        elif a == "--parent":
            i += 1
            if i >= len(argv):
                die("Missing value after --parent")
            parent_name = argv[i]

        elif a == "--front-frame":
            i += 1
            if i >= len(argv):
                die("Missing value after --front-frame")
            front_frame = int(argv[i])

        elif a == "--back-frame":
            i += 1
            if i >= len(argv):
                die("Missing value after --back-frame")
            back_frame = int(argv[i])

        elif a == "--quality":
            i += 1
            if i >= len(argv):
                die("Missing value after --quality")
            quality = int(argv[i])

        else:
            pos.append(a)

        i += 1

    if not pos:
        die("Usage: blender -b template.blend --python script.py -- input.glb [--outdir DIR]")

    glb_path = pos[0]

    if not os.path.exists(glb_path):
        die(f"GLB not found: {glb_path}")

    os.makedirs(outdir, exist_ok=True)

    return {
        "glb_path": glb_path,
        "outdir": outdir,
        "parent_name": parent_name,
        "front_frame": front_frame,
        "back_frame": back_frame,
        "quality": quality,
    }


def collect_descendants(obj):
    out = []
    stack = list(obj.children)
    while stack:
        cur = stack.pop()
        out.append(cur)
        stack.extend(cur.children)
    return out


def delete_descendants(parent):
    victims = collect_descendants(parent)
    if not victims:
        log("No existing children beneath parent; nothing to delete")
        return

    # Unlink carefully from children upward
    for obj in sorted(victims, key=lambda o: len(o.children), reverse=True):
        try:
            bpy.data.objects.remove(obj, do_unlink=True)
        except Exception as exc:
            log(f"WARNING: failed removing {obj.name}: {exc}")

    log(f"Deleted {len(victims)} descendant object(s) under {parent.name}")


def import_glb_and_get_new_objects(glb_path):
    before = set(bpy.data.objects.keys())

    res = bpy.ops.import_scene.gltf(filepath=glb_path)
    if "FINISHED" not in res:
        die(f"GLB import failed: {glb_path}")

    after = set(bpy.data.objects.keys())
    new_names = sorted(after - before)
    new_objs = [bpy.data.objects[name] for name in new_names]

    if not new_objs:
        die("Import succeeded, but no new objects were detected")

    log(f"Imported {len(new_objs)} object(s) from {glb_path}")
    return new_objs


def get_import_roots(imported_objs):
    imported_set = set(imported_objs)
    roots = [obj for obj in imported_objs if obj.parent not in imported_set]
    if not roots:
        roots = imported_objs[:]
    return roots


def parent_roots_to_target(roots, parent):
    for obj in roots:
        # Preserve current world-space transform on parenting
        world_matrix = obj.matrix_world.copy()
        obj.parent = parent
        obj.matrix_parent_inverse = parent.matrix_world.inverted()
        obj.matrix_world = world_matrix

    log(f"Parented {len(roots)} imported root object(s) to {parent.name}")


# def ensure_render_settings(scene, quality):
#     scene.render.image_settings.file_format = 'WEBP'
#     scene.render.image_settings.quality = quality
#     scene.render.use_file_extension = True

#     # Leave all your scene-specific Eevee/world/compositor settings alone.
#     # The point is to use the .blend as-is.
#     log(f"Render format set to WEBP, quality={quality}")

def ensure_render_settings(scene, quality):
    isettings = scene.render.image_settings

    # Blender 5.x split still vs video by media_type.
    # Do not guess blindly; inspect the enum and choose the still-image mode.
    media_prop = isettings.bl_rna.properties.get("media_type")
    if media_prop is not None:
        media_keys = {item.identifier for item in media_prop.enum_items}

        if "IMAGE" in media_keys:
            isettings.media_type = 'IMAGE'
        elif "STILL" in media_keys:
            isettings.media_type = 'STILL'
        elif "VIDEO" in media_keys and len(media_keys) == 1:
            raise RuntimeError(
                f"Scene is locked to VIDEO media_type only: {sorted(media_keys)}"
            )

    fmt_keys = {
        item.identifier
        for item in isettings.bl_rna.properties["file_format"].enum_items
    }

    # Prefer WEBP if available, otherwise fall back to PNG.
    if "WEBP" in fmt_keys:
        isettings.file_format = 'WEBP'
        if hasattr(isettings, "quality"):
            isettings.quality = quality
        ext = ".webp"
    elif "PNG" in fmt_keys:
        isettings.file_format = 'PNG'
        ext = ".png"
    else:
        raise RuntimeError(
            f"No usable still-image format available. file_format choices: {sorted(fmt_keys)}"
        )

    scene.render.use_file_extension = True

    print(f"media_type = {getattr(isettings, 'media_type', '(none)')}")
    print(f"file_format choices = {sorted(fmt_keys)}")
    print(f"using still format = {isettings.file_format}")

    return ext

def render_frame(scene, frame_no, outfile):
    scene.frame_set(frame_no)
    scene.render.filepath = outfile
    log(f"Rendering frame {frame_no} -> {outfile}")
    bpy.ops.render.render(write_still=True)


def main():
    cfg = parse_args(sys.argv)

    glb_path = cfg["glb_path"]
    outdir = cfg["outdir"]
    parent_name = cfg["parent_name"]
    front_frame = cfg["front_frame"]
    back_frame = cfg["back_frame"]
    quality = cfg["quality"]

    base_name = os.path.splitext(os.path.basename(glb_path))[0]
    timestamp = time.strftime("%Y%m%d_%H%M")

    scene = bpy.context.scene
    parent = bpy.data.objects.get(parent_name)
    if parent is None:
        die(f"Parent object not found: {parent_name}")

    log(f"Using parent: {parent.name}")
    log(f"Using scene: {scene.name}")
    log(f"Input GLB: {glb_path}")

    delete_descendants(parent)
    imported = import_glb_and_get_new_objects(glb_path)
    roots = get_import_roots(imported)
    parent_roots_to_target(roots, parent)

    for obj in imported:
        pname = obj.parent.name if obj.parent else "-"
        print(f"IMPORTED: {obj.name:30s} type={obj.type:8s} parent={pname}")


    # ensure_render_settings(scene, quality)

    # front_out = os.path.join(outdir, f"{base_name}_frame001_{timestamp}.webp")
    # back_out  = os.path.join(outdir, f"{base_name}_frame144_{timestamp}.webp")
    ext = ensure_render_settings(scene, quality)

    front_out = os.path.join(outdir, f"{base_name}_frame001_{timestamp}{ext}")
    back_out  = os.path.join(outdir, f"{base_name}_frame144_{timestamp}{ext}")

    render_frame(scene, front_frame, front_out)
    render_frame(scene, back_frame, back_out)

    log("Done")
    log(f"Wrote: {front_out}")
    log(f"Wrote: {back_out}")


if __name__ == "__main__":
    main()
