# pyright: reportCallIssue=none
# Mitsuba 3 は variant ベースの動的属性解決を行うため Pylance/Pyright が
# mi.* を object 型と推論し reportCallIssue を誤検知する（GUIDE_06 の方針）．
"""YAML 光源定義 → Mitsuba emitter dict 変換モジュール．"""

from __future__ import annotations

import mitsuba as mi

from sm_tias_lightsim.scene.materials import _to_rgb
from sm_tias_lightsim.scene.schema import (
    AreaLight,
    DirectionalLight,
    LightConfig,
    PointLight,
    SpotLight,
)


def _build_area_light_transform(
    center: tuple[float, float, float],
    size: tuple[float, float],
    direction: tuple[float, float, float],
    position_scale: float,
) -> mi.ScalarTransform4f:
    """AreaLight の中心・サイズ・方向から Mitsuba の to_world Transform を組み立てる．

    Mitsuba の rectangle 形状は XZ 平面上の -1〜+1 の正方形（Y 軸正方向が前面）．
    direction ベクトルが面の法線方向を指すように look_at を構成する．

    Args:
        center: 面中心位置（元単位）．
        size: 面サイズ (width, height)（元単位）．
        direction: 放射方向ベクトル（単位ベクトルでなくても可）．
        position_scale: 座標スケール係数（mm→m なら 0.001）．

    Returns:
        Mitsuba ScalarTransform4f．
    """
    cx, cy, cz = [c * position_scale for c in center]
    sw, sh = size[0] * position_scale, size[1] * position_scale

    dx, dy, dz = direction
    norm = (dx**2 + dy**2 + dz**2) ** 0.5
    if norm == 0.0:
        msg = "AreaLight direction vector must not be zero."
        raise ValueError(msg)
    dx, dy, dz = dx / norm, dy / norm, dz / norm

    # direction が Y 軸に近い場合は up を Z 軸とし，それ以外は Y 軸を up に使う
    # look_at の origin を center + direction（目線は放射方向の逆から見る）として構成
    if abs(dy) < 0.9:
        up = [0.0, 1.0, 0.0]
    else:
        up = [0.0, 0.0, 1.0]

    # look_at: origin=center，target=center + direction，up=up
    origin = [cx, cy, cz]
    target = [cx + dx, cy + dy, cz + dz]

    transform = mi.ScalarTransform4f().look_at(origin=origin, target=target, up=up)

    # サイズに合わせてスケール（rectangle は ±1 なので half-size でスケール）
    scale = mi.ScalarTransform4f().scale([sw / 2.0, sh / 2.0, 1.0])

    return transform @ scale


def build_light_dict(light: LightConfig, position_scale: float) -> dict:
    """1 LightConfig を Mitsuba scene dict の emitter エントリに変換する．

    Args:
        light: SceneConfig.lights の 1 要素（PointLight / AreaLight / DirectionalLight）．
        position_scale: 座標スケール係数（例：mm→m なら 0.001）．
                        位置・サイズに掛けて Mitsuba（m）に揃える．

    Returns:
        Mitsuba scene dict のキーに使える dict．
        - PointLight → {"type": "point", "position": [...], "intensity": {...}}
        - AreaLight → {"type": "rectangle", "to_world": ..., "emitter": {"type": "area", ...}}
        - DirectionalLight → {"type": "directional", "direction": [...], "irradiance": {...}}

    Raises:
        ValueError: 未対応の type が指定された場合．
    """
    if isinstance(light, PointLight):
        pos = [c * position_scale for c in light.position]
        return {
            "type": "point",
            "position": pos,
            "intensity": {
                "type": "rgb",
                "value": _to_rgb(light.intensity),
            },
        }

    if isinstance(light, AreaLight):
        to_world = _build_area_light_transform(
            center=light.center,
            size=light.size,
            direction=light.direction,
            position_scale=position_scale,
        )
        return {
            "type": "rectangle",
            "to_world": to_world,
            "emitter": {
                "type": "area",
                "radiance": {
                    "type": "rgb",
                    "value": _to_rgb(light.radiance),
                },
            },
        }

    if isinstance(light, DirectionalLight):
        return {
            "type": "directional",
            "direction": list(light.direction),
            "irradiance": {
                "type": "rgb",
                "value": _to_rgb(light.irradiance),
            },
        }

    if isinstance(light, SpotLight):
        # spot emitter は to_world で位置と光軸方向を指定する．
        # Mitsuba の spot は to_world @ (origin=(0,0,0), target=(0,0,1)) を基準とし，
        # to_world で世界座標へ変換するので look_at で組み立てる．
        cx, cy, cz = [c * position_scale for c in light.position]
        dx, dy, dz = light.direction
        norm = (dx**2 + dy**2 + dz**2) ** 0.5
        if norm == 0.0:
            msg = "SpotLight direction vector must not be zero."
            raise ValueError(msg)
        dx, dy, dz = dx / norm, dy / norm, dz / norm
        if abs(dy) < 0.9:
            up = [0.0, 1.0, 0.0]
        else:
            up = [0.0, 0.0, 1.0]
        origin = [cx, cy, cz]
        target = [cx + dx, cy + dy, cz + dz]
        to_world = mi.ScalarTransform4f().look_at(origin=origin, target=target, up=up)
        return {
            "type": "spot",
            "to_world": to_world,
            "cutoff_angle": light.cutoff_angle,
            "beam_width": light.beam_width,
            "intensity": {
                "type": "rgb",
                "value": _to_rgb(light.intensity),
            },
        }

    msg = f"Unsupported light type: {type(light).__name__}"
    raise ValueError(msg)
