"""Camera and lighting helpers for Blender headless renders."""
from __future__ import annotations

import math

ELEVATION_DEG   = 28.0
AZIMUTH_DEG     = 40.0
LENS_MM         = 50.0
SENSOR_WIDTH_MM = 36.0
FILL_FACTOR     = 0.85


def setup_auto_camera(parts: list, width: int, height: int,
                      lens_mm: float | None = None,
                      sensor_width_mm: float | None = None):
    """Compute bounding sphere and place an isometric auto-camera.

    Returns (bbox_center, bsphere_radius) as a tuple so the caller can
    pass them to setup_auto_lights().
    """
    import bpy  # type: ignore[import]
    from mathutils import Vector, Matrix  # type: ignore[import]

    _lens = lens_mm if lens_mm is not None else LENS_MM
    _sensor = sensor_width_mm if sensor_width_mm is not None else SENSOR_WIDTH_MM

    all_corners = []
    for part in parts:
        all_corners.extend(part.matrix_world @ Vector(c) for c in part.bound_box)

    bbox_min = Vector((
        min(v.x for v in all_corners),
        min(v.y for v in all_corners),
        min(v.z for v in all_corners),
    ))
    bbox_max = Vector((
        max(v.x for v in all_corners),
        max(v.y for v in all_corners),
        max(v.z for v in all_corners),
    ))

    bbox_center    = (bbox_min + bbox_max) * 0.5
    bbox_dims      = bbox_max - bbox_min
    bsphere_radius = max(bbox_dims.length * 0.5, 0.001)

    print(f"[blender_render] bbox_dims={tuple(round(d,4) for d in bbox_dims)}, "
          f"bsphere_radius={bsphere_radius:.4f}, center={tuple(round(c,4) for c in bbox_center)}")

    elevation_rad = math.radians(ELEVATION_DEG)
    azimuth_rad   = math.radians(AZIMUTH_DEG)

    cam_dir = Vector((
        math.cos(elevation_rad) * math.cos(azimuth_rad),
        math.cos(elevation_rad) * math.sin(azimuth_rad),
        math.sin(elevation_rad),
    )).normalized()

    fov_h = math.atan(_sensor / (2.0 * _lens))
    fov_v = math.atan(_sensor * (height / width) / (2.0 * _lens))
    fov_used = min(fov_h, fov_v)

    dist = (bsphere_radius / math.tan(fov_used)) / FILL_FACTOR
    # Minimum distance: prevent camera from being inside the bounding sphere,
    # but scale with FOV so wide-angle lenses can still frame correctly.
    min_dist = bsphere_radius * 1.05  # just outside the sphere surface
    dist = max(dist, min_dist)
    print(f"[blender_render] camera dist={dist:.4f}, fov={math.degrees(fov_used):.2f}°, lens={_lens}mm")

    cam_location = bbox_center + cam_dir * dist
    bpy.ops.object.camera_add(location=cam_location)
    cam_obj = bpy.context.active_object
    cam_obj.data.lens = _lens
    bpy.context.scene.camera = cam_obj

    look_dir = (bbox_center - cam_location).normalized()
    up_world = Vector((0.0, 0.0, 1.0))
    right    = look_dir.cross(up_world)
    if right.length < 1e-6:
        right = Vector((1.0, 0.0, 0.0))
    right.normalize()
    cam_up = right.cross(look_dir).normalized()

    rot_mat = Matrix((
        ( right.x,     right.y,     right.z),
        ( cam_up.x,    cam_up.y,    cam_up.z),
        (-look_dir.x, -look_dir.y, -look_dir.z),
    )).transposed()
    cam_obj.rotation_euler = rot_mat.to_euler('XYZ')

    cam_obj.data.clip_start = max(dist * 0.001, 0.0001)
    cam_obj.data.clip_end   = dist + bsphere_radius * 3.0
    print(f"[blender_render] clip {cam_obj.data.clip_start:.6f} … {cam_obj.data.clip_end:.4f}")

    return bbox_center, bsphere_radius


def setup_auto_lights(bbox_center, bsphere_radius: float) -> None:
    """Add a sun + area fill light positioned relative to the bounding sphere."""
    import bpy  # type: ignore[import]

    light_dist = bsphere_radius * 6.0

    bpy.ops.object.light_add(type='SUN', location=(
        bbox_center.x + light_dist * 0.5,
        bbox_center.y - light_dist * 0.35,
        bbox_center.z + light_dist,
    ))
    sun = bpy.context.active_object
    sun.data.energy = 4.0
    sun.rotation_euler = (math.radians(45), 0, math.radians(30))

    bpy.ops.object.light_add(type='AREA', location=(
        bbox_center.x - light_dist * 0.4,
        bbox_center.y + light_dist * 0.4,
        bbox_center.z + light_dist * 0.7,
    ))
    fill = bpy.context.active_object
    fill.data.energy = max(800.0, bsphere_radius ** 2 * 2000.0)
    fill.data.size   = max(4.0,   bsphere_radius * 4.0)
