"""Clout original volumetric and chiaroscuro scenes
Run in a NEW background process only:
blender --background --factory-startup --python cinema-light-studies.py -- --output-root NEW_DIRECTORY
Writes PNGs and a manifest; never saves a Blender project or edits the foreground application.
"""

import argparse
import json
import sys
from pathlib import Path

import bpy
from mathutils import Vector

if not bpy.app.background:
    raise RuntimeError("Use a new background factory-startup process, not an open project")
p = argparse.ArgumentParser()
p.add_argument("--output-root", required=True)
args = p.parse_args(sys.argv[sys.argv.index("--") + 1 :])
out = Path(args.output_root)
out.mkdir(parents=True, exist_ok=False)


def reset():
    bpy.ops.object.select_all(action="SELECT")
    bpy.ops.object.delete(use_global=False)
    s = bpy.context.scene
    s.render.engine = "CYCLES"
    s.cycles.device = "CPU"
    s.cycles.samples = 48
    s.cycles.use_denoising = True
    s.cycles.seed = 17
    s.render.resolution_x = 900
    s.render.resolution_y = 900
    s.render.resolution_percentage = 100
    s.render.image_settings.file_format = "PNG"
    s.world.color = (0.03, 0.03, 0.03)
    s.view_settings.view_transform = "AgX"
    s.render.film_transparent = False
    return s


def mat(name, color, sss=0, scale=0.12):
    m = bpy.data.materials.new(name)
    m.use_nodes = True
    bs = m.node_tree.nodes.get("Principled BSDF")
    bs.inputs["Base Color"].default_value = (*color, 1)
    bs.inputs["Roughness"].default_value = 0.36
    bs.inputs["Subsurface Weight"].default_value = sss
    bs.inputs["Subsurface Scale"].default_value = scale
    bs.inputs["Subsurface Radius"].default_value = (1, 0.35, 0.15)
    return m


def assign(obj, m):
    obj.data.materials.clear()
    obj.data.materials.append(m)


def camera(pos, target, scale):
    bpy.ops.object.camera_add(location=pos)
    c = bpy.context.object
    c.rotation_euler = (Vector(target) - c.location).to_track_quat("-Z", "Y").to_euler()
    c.data.type = "ORTHO"
    c.data.ortho_scale = scale
    bpy.context.scene.camera = c


def area(pos, target, power, size):
    bpy.ops.object.light_add(type="AREA", location=pos)
    light = bpy.context.object
    light.data.energy = power
    light.data.shape = "DISK"
    light.data.size = size
    light.rotation_euler = (Vector(target) - light.location).to_track_quat("-Z", "Y").to_euler()


def render(name):
    bpy.context.scene.render.filepath = str(out / (name + ".png"))
    bpy.ops.render.render(write_still=True)


def box(pos, scale, material):
    bpy.ops.mesh.primitive_cube_add(size=1, location=pos)
    ob = bpy.context.object
    ob.scale = scale
    assign(ob, material)
    return ob


s = reset()
s.render.resolution_x = 1280
s.render.resolution_y = 720
s.cycles.samples = 64
s.world.color = (0.003, 0.003, 0.003)
stone = mat("Fictional cream sculpture", (0.5, 0.38, 0.24))
dark = mat("Dark studio walls", (0.03, 0.04, 0.045))
bpy.ops.mesh.primitive_monkey_add(location=(0, 0, 1.25))
ob = bpy.context.object
ob.rotation_euler[2] = -0.2
ob.modifiers.new("Smooth study mesh", "SUBSURF").levels = 2
for face in ob.data.polygons:
    face.use_smooth = True
assign(ob, stone)
box((0, 0, -0.12), (14, 14, 0.2), dark)
box((0, 3, 2), (14, 0.15, 6), dark)
camera((4, -7, 3), (0, 0, 1.15), 6)
area((-3, -2, 4), (0, 0, 1), 500, 1.3)
area((3, -3, 2), (0, 0, 1), 450, 2)
fill = bpy.context.object
render("chiaroscuro-open-fill")
fill.data.energy = 15
render("chiaroscuro-low-fill")
# Keep camera, object, surfaces and spotlight fixed in both volume renders
fill.data.energy = 0
for light in [o for o in bpy.context.scene.objects if o.type == "LIGHT"]:
    light.data.energy = 0
bpy.ops.object.light_add(type="SPOT", location=(-3, 1.5, 4))
spot = bpy.context.object
spot.rotation_euler = (Vector((0, -0.5, 0.6)) - spot.location).to_track_quat("-Z", "Y").to_euler()
spot.data.energy = 1800
spot.data.spot_size = 0.7
spot.data.spot_blend = 0.15
render("volume-clear-air")
volume = bpy.data.materials.new("Authored homogeneous scatter volume")
volume.use_nodes = True
nodes = volume.node_tree.nodes
nodes.clear()
output = nodes.new("ShaderNodeOutputMaterial")
scatter = nodes.new("ShaderNodeVolumeScatter")
scatter.inputs["Density"].default_value = 0.06
scatter.inputs["Anisotropy"].default_value = 0.25
volume.node_tree.links.new(scatter.outputs["Volume"], output.inputs["Volume"])
box((0, 0, 2), (12, 12, 8), volume)
render("volume-scattering-air")
manifest = {
    "blenderVersion": bpy.app.version_string,
    "engine": "Cycles CPU",
    "samples": 64,
    "seed": 17,
    "resolution": [1280, 720],
    "viewTransform": "AgX",
    "chiaroscuro": {
        "keyWatts": 500,
        "fillWatts": [450, 15],
        "note": "Not measured subject contrast ratios",
    },
    "volume": {
        "spotWatts": 1800,
        "density": 0.06,
        "anisotropy": 0.25,
        "boxDimensions": [12, 12, 8],
    },
    "note": "Authored 3D scenes with built-in Suzanne mesh, not AI generation or a real-world haze calibration",
}
(out / "render-manifest.json").write_text(json.dumps(manifest, indent=2))
