//! Outdoor lighting: a single directional sun with cascaded shadow maps.

use bevy::light::CascadeShadowConfig;
use bevy::prelude::*;
use settings::ProjectSettings;
use shared::{
    authoring_component_active, cascade_config_from_rendering, AuthoringComponentStates,
    AuthoringLightKind, InspectorOrder, LevelObject, LightDesc, ProjectSun, COMPONENT_LIGHT_DESC,
};

/// Spawns the directional "sun" from project rendering settings.
#[unsafe(no_mangle)]
pub fn spawn_sun(
    mut commands: Commands,
    settings: Res<ProjectSettings>,
    existing: Query<(), With<DirectionalLight>>,
    scene_suns: Query<
        (
            &LightDesc,
            Option<&AuthoringComponentStates>,
            Option<&InspectorOrder>,
        ),
        With<LevelObject>,
    >,
) {
    if !existing.is_empty() {
        return;
    }

    let rendering = &settings.rendering;
    let cascades = cascade_config_from_rendering(rendering);

    commands.spawn((
        Name::new("Sun"),
        ProjectSun,
        DirectionalLight {
            illuminance: rendering.sun_illuminance,
            shadow_maps_enabled: !has_scene_sun_override(&scene_suns),
            ..default()
        },
        Visibility::default(),
        Transform::from_xyz(0.0, 0.0, 0.0).looking_to(Vec3::new(-0.35, -0.85, -0.4), Vec3::Y),
        cascades,
    ));
}

#[unsafe(no_mangle)]
pub fn sync_project_sun_visibility(
    scene_suns: Query<
        (
            &LightDesc,
            Option<&AuthoringComponentStates>,
            Option<&InspectorOrder>,
        ),
        With<LevelObject>,
    >,
    mut project_suns: Query<(&mut DirectionalLight, &mut Visibility), With<ProjectSun>>,
) {
    let scene_override = has_scene_sun_override(&scene_suns);
    for (mut sun, mut visibility) in &mut project_suns {
        sun.shadow_maps_enabled = !scene_override;
        *visibility = if scene_override {
            Visibility::Hidden
        } else {
            Visibility::Visible
        };
    }
}

fn has_scene_sun_override(
    scene_suns: &Query<
        (
            &LightDesc,
            Option<&AuthoringComponentStates>,
            Option<&InspectorOrder>,
        ),
        With<LevelObject>,
    >,
) -> bool {
    scene_suns.iter().any(|(light, states, legacy_order)| {
        authoring_component_active(states, legacy_order, COMPONENT_LIGHT_DESC)
            && matches!(light.kind, AuthoringLightKind::Directional)
    })
}

/// Keeps the project sun's illuminance in sync with [`ProjectSettings`] when settings change.
#[unsafe(no_mangle)]
pub fn sync_project_sun_illuminance(
    settings: Res<ProjectSettings>,
    mut project_suns: Query<&mut DirectionalLight, With<ProjectSun>>,
) {
    let illuminance = settings.rendering.sun_illuminance;
    for mut sun in &mut project_suns {
        sun.illuminance = illuminance;
    }
}

/// Keeps scene directional (non-[`ProjectSun`]) cascade configs aligned with project shadow settings.
#[unsafe(no_mangle)]
pub fn sync_scene_directional_shadow_cascades(
    settings: Res<ProjectSettings>,
    mut scene_lights: Query<
        &mut CascadeShadowConfig,
        (
            With<LevelObject>,
            With<DirectionalLight>,
            Without<ProjectSun>,
        ),
    >,
) {
    if !settings.is_changed() {
        return;
    }
    let cascades = cascade_config_from_rendering(&settings.rendering);
    for mut config in &mut scene_lights {
        *config = cascades.clone();
    }
}

#[unsafe(no_mangle)]
pub fn sync_project_sun_from_settings(
    settings: Res<ProjectSettings>,
    scene_suns: Query<
        (
            &LightDesc,
            Option<&AuthoringComponentStates>,
            Option<&InspectorOrder>,
        ),
        With<LevelObject>,
    >,
    mut project_suns: Query<(&mut DirectionalLight, &mut Visibility), With<ProjectSun>>,
) {
    let scene_override = has_scene_sun_override(&scene_suns);
    let illuminance = settings.rendering.sun_illuminance;
    for (mut sun, mut visibility) in &mut project_suns {
        sun.illuminance = illuminance;
        sun.shadow_maps_enabled = !scene_override;
        *visibility = if scene_override {
            Visibility::Hidden
        } else {
            Visibility::Visible
        };
    }
}
