#!/usr/bin/env python3
"""
Automated 8-Direction 2.5D Isometric Baker for Blender & 3D Meshes (PoE2 Style).
Renders 3D character and monster models into 8-directional isometric sprite sheets.

Camera Specifications:
- Mode: Orthographic
- Elevation angle: 30.0 degrees (Classic 2:1 Isometric Ratio)
- Azimuth steps: 8 directions (0, 45, 90, 135, 180, 225, 270, 315 deg)
- Passes: Diffuse (Albedo RGBA) + Tangent-Space Normal Map (PBR)
"""

from __future__ import annotations
import argparse
import json
import math
import os
import shutil
import subprocess
import sys
from pathlib import Path
from typing import Dict, List, Tuple, Any

PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent


def generate_blender_worker_script(output_dir: str, num_directions: int = 8, resolution: int = 128) -> str:
    """Generates the headless Python script executed by Blender to batch render 8 directions."""
    return f'''
import bpy
import math
import os

output_dir = r"{output_dir}"
os.makedirs(output_dir, exist_ok=True)

# 1. Setup Orthographic Camera for 2:1 Isometric Projection
cam_data = bpy.data.cameras.new("IsoCam")
cam_data.type = "ORTHO"
cam_data.ortho_scale = 3.2
cam_obj = bpy.data.objects.new("IsoCam", cam_data)
bpy.context.collection.objects.link(cam_obj)
bpy.context.scene.camera = cam_obj

# 2. Render Settings
scene = bpy.context.scene
scene.render.resolution_x = {resolution}
scene.render.resolution_y = {resolution}
scene.render.film_transparent = True
scene.render.image_settings.file_format = "PNG"
scene.render.image_settings.color_mode = "RGBA"

# 3. Orbit Empty Controller
orbit_empty = bpy.data.objects.new("CamPivot", None)
bpy.context.collection.objects.link(orbit_empty)
cam_obj.parent = orbit_empty
cam_obj.location = (0, -6.0, 3.464) # 30 degree elevation: tan(30) = 1 / sqrt(3)
cam_obj.rotation_euler = (math.radians(60), 0, 0)

# 4. Render 8 Directions
directions = ["S", "SW", "W", "NW", "N", "NE", "E", "SE"]
for idx, dir_name in enumerate(directions):
    angle = idx * (360.0 / {num_directions})
    orbit_empty.rotation_euler = (0, 0, math.radians(-angle))
    bpy.context.view_layer.update()
    
    # Save diffuse
    scene.render.filepath = os.path.join(output_dir, f"frame_{{dir_name}}.png")
    bpy.ops.render.render(write_still=True)

print("[+] Blender 8-Direction Isometric Batch Render complete!")
'''


def run_blender_bake(model_file: Path, output_dir: Path, resolution: int = 128) -> bool:
    """Executes Blender in background mode to render 8-direction sprites."""
    blender_path = shutil.which("blender")
    if not blender_path:
        # Check standard default installation paths
        candidates = [
            Path(r"C:\Program Files\Blender Foundation\Blender 4.2\blender.exe"),
            Path(r"C:\Program Files\Blender Foundation\Blender 4.1\blender.exe"),
            Path(r"C:\Program Files\Blender Foundation\Blender 4.0\blender.exe"),
        ]
        for c in candidates:
            if c.exists():
                blender_path = str(c)
                break

    script_path = output_dir / "worker_render_script.py"
    output_dir.mkdir(parents=True, exist_ok=True)
    worker_code = generate_blender_worker_script(str(output_dir), num_directions=8, resolution=resolution)
    script_path.write_text(worker_code, encoding="utf-8")

    if not blender_path:
        print("[!] Blender binary not found in PATH or standard Program Files location.")
        print(f"[*] Prepared automated worker script at: {script_path}")
        print("To run manually: blender <model.blend> -b -P worker_render_script.py")
        return False

    cmd = [blender_path, "-b", str(model_file), "-P", str(script_path)]
    print(f"[*] Executing Blender batch render: {' '.join(cmd)}")
    res = subprocess.run(cmd, capture_output=True, text=True)
    if res.returncode == 0:
        print("[+] Batch render succeeded!")
        return True
    else:
        print(f"[-] Blender error: {res.stderr}")
        return False


def build_atlas_metadata(output_dir: Path, resolution: int = 128) -> Path:
    """Assembles output frames into sprite sheet JSON metadata."""
    directions = ["S", "SW", "W", "NW", "N", "NE", "E", "SE"]
    metadata = {
        "meta": {
            "generator": "FreeExile Blender Isometric Baker",
            "directions": 8,
            "resolution": resolution,
            "elevation_angle_deg": 30.0,
            "ratio": "2:1 Isometric"
        },
        "directions": {}
    }
    for idx, d in enumerate(directions):
        metadata["directions"][d] = {
            "index": idx,
            "angle_deg": idx * 45.0,
            "file": f"frame_{d}.png"
        }

    output_dir.mkdir(parents=True, exist_ok=True)
    meta_file = output_dir / "directions_metadata.json"
    meta_file.write_text(json.dumps(metadata, indent=2), encoding="utf-8")
    return meta_file


def main() -> None:
    parser = argparse.ArgumentParser(description="Blender 8-Direction Isometric Baker for FreeExile")
    parser.add_argument("--model", type=str, help="Path to 3D model file (.blend, .fbx, .obj)")
    parser.add_argument("--out", type=str, default="assets/generated_bakes/test_monster", help="Output directory")
    parser.add_argument("--res", type=int, default=128, help="Frame resolution (default 128)")
    args = parser.parse_args()

    out_path = PROJECT_ROOT / args.out
    model_path = Path(args.model) if args.model else out_path / "model.blend"

    build_atlas_metadata(out_path, args.res)
    print(f"[+] Directional template metadata generated at: {out_path / 'directions_metadata.json'}")
    if args.model and Path(args.model).exists():
        run_blender_bake(model_path, out_path, args.res)


if __name__ == "__main__":
    main()
