import bpy, math
from mathutils import Vector

info = {}
try:
    if bpy.context.object and bpy.context.object.mode != 'OBJECT':
        bpy.ops.object.mode_set(mode='OBJECT')
except Exception:
    pass
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)
for blklist in (bpy.data.meshes, bpy.data.curves, bpy.data.materials, bpy.data.lights,
                bpy.data.cameras, bpy.data.images):
    for blk in list(blklist):
        if blk.users == 0:
            blklist.remove(blk)

scene = bpy.context.scene

def make_coll(name):
    c = bpy.data.collections.get(name) or bpy.data.collections.new(name)
    if c.name not in [ch.name for ch in scene.collection.children]:
        try: scene.collection.children.link(c)
        except RuntimeError: pass
    return c

COLLS = {n: make_coll(n) for n in ["Terrain","Water","Cabin","Chimney","Dock","Trees","Props","Lights","Camera"]}

def link_to(obj, cname):
    for c in list(obj.users_collection): c.objects.unlink(obj)
    COLLS[cname].objects.link(obj)

scene.unit_settings.system = 'METRIC'
scene.unit_settings.length_unit = 'METERS'
scene.render.engine = 'CYCLES'
cy = scene.cycles
cy.samples = 128
cy.use_denoising = True
cy.caustics_reflective = False
cy.caustics_refractive = False
cy.sample_clamp_indirect = 8.0
scene.render.resolution_x = 1536
scene.render.resolution_y = 1536

try:
    prefs = bpy.context.preferences.addons['cycles'].preferences
    gpu = None
    for dt in ('OPTIX','CUDA','HIP','METAL','ONEAPI'):
        try:
            prefs.compute_device_type = dt
            prefs.get_devices()
            if any(d.type == dt for d in prefs.devices):
                gpu = dt; break
        except Exception: pass
    if gpu:
        scene.cycles.device = 'GPU'
        for d in prefs.devices:
            d.use = (d.type == gpu) or (d.type == 'CPU')
except Exception as e:
    gpu = str(e)
info["gpu"] = gpu

vt = scene.view_settings
try: vt.view_transform = 'AgX'
except Exception: vt.view_transform = 'Filmic'
look_set = None
for lk in ('AgX - Punchy','Punchy','AgX - Medium High Contrast','Medium High Contrast'):
    try: vt.look = lk; look_set = lk; break
    except Exception: pass
info["look"] = look_set
vt.exposure = 0.35

# ---------- world ----------
world = bpy.data.worlds.get('World') or bpy.data.worlds.new('World')
scene.world = world
world.use_nodes = True
nt = world.node_tree
nt.nodes.clear()
o = nt.nodes.new('ShaderNodeOutputWorld')
b = nt.nodes.new('ShaderNodeBackground')
sky = nt.nodes.new('ShaderNodeTexSky')
ok_types = [e.identifier for e in sky.bl_rna.properties['sky_type'].enum_items]
for cand in ('SINGLE_SCATTERING','NISHITA','MULTIPLE_SCATTERING','PREETHAM'):
    if cand in ok_types:
        sky.sky_type = cand; info["sky_type"]=cand; break
try:
    sky.sun_disc = False
    sky.sun_elevation = math.radians(38)
    sky.sun_rotation = math.radians(115)
except Exception: pass
try:
    sky.altitude = 60; sky.air_density = 1.1; sky.dust_density = 1.6
except Exception: pass
nt.links.new(sky.outputs['Color'], b.inputs['Color'])
b.inputs['Strength'].default_value = 0.55
nt.links.new(b.outputs['Background'], o.inputs['Surface'])
try:
    vw = nt.nodes.new('ShaderNodeVolumeScatter')
    vw.inputs['Density'].default_value = 0.0012
    vw.inputs['Anisotropy'].default_value = 0.25
    nt.links.new(vw.outputs['Volume'], o.inputs['Volume'])
except Exception: pass

# ---------- warm key sun ----------
ld = bpy.data.lights.new('KeySun', 'SUN')
ld.energy = 5.5
ld.angle = math.radians(1.2)
ld.color = (1.0, 0.92, 0.80)
sun = bpy.data.objects.new('KeySun', ld)
link_to(sun, 'Lights')
Ldir = Vector((-0.72, 0.28, -0.63)).normalized()
sun.rotation_euler = Ldir.to_track_quat('-Z','Y').to_euler()
sun.location = (12,-6,10)

info["objects"] = len(bpy.data.objects)
result = info