//! Standard materials from [`MaterialDesc`] authoring data.

use bevy::prelude::*;

use crate::{
    asset_server_path, authoring_component_active, AuthoringComponentStates, InspectorOrder,
    LevelObject, MaterialDesc, MaterialShaderKind, Primitive, COMPONENT_MATERIAL_DESC,
};

#[allow(clippy::type_complexity)]
pub fn hydrate_materials(
    mut commands: Commands,
    asset_server: Res<AssetServer>,
    mut materials: ResMut<Assets<StandardMaterial>>,
    targets: Query<
        (
            Entity,
            &MaterialDesc,
            Option<&AuthoringComponentStates>,
            Option<&InspectorOrder>,
        ),
        (
            With<LevelObject>,
            With<Mesh3d>,
            Or<(
                Changed<MaterialDesc>,
                Added<Mesh3d>,
                Changed<Primitive>,
                Changed<AuthoringComponentStates>,
                Changed<InspectorOrder>,
            )>,
        ),
    >,
) {
    for (entity, desc, states, legacy_order) in &targets {
        if !authoring_component_active(states, legacy_order, COMPONENT_MATERIAL_DESC) {
            commands
                .entity(entity)
                .remove::<MeshMaterial3d<StandardMaterial>>();
            continue;
        }
        let material = materials.add(material_from_desc(&asset_server, desc));
        commands.entity(entity).insert(MeshMaterial3d(material));
    }
}

pub fn material_from_desc(asset_server: &AssetServer, desc: &MaterialDesc) -> StandardMaterial {
    StandardMaterial {
        base_color: desc.base_color.to_color(),
        metallic: desc.metallic,
        perceptual_roughness: desc.roughness,
        unlit: matches!(desc.shader.kind, MaterialShaderKind::Unlit),
        base_color_texture: desc
            .base_color_texture
            .as_ref()
            .map(|path| asset_server.load(asset_server_path(path))),
        emissive: LinearRgba::rgb(
            desc.emissive_color.r * desc.emissive_intensity,
            desc.emissive_color.g * desc.emissive_intensity,
            desc.emissive_color.b * desc.emissive_intensity,
        ),
        emissive_texture: desc
            .emissive_texture
            .as_ref()
            .map(|path| asset_server.load(asset_server_path(path))),
        normal_map_texture: desc
            .normal_map_texture
            .as_ref()
            .map(|path| asset_server.load(asset_server_path(path))),
        metallic_roughness_texture: desc
            .metallic_roughness_texture
            .as_ref()
            .map(|path| asset_server.load(asset_server_path(path))),
        ..default()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ColorDesc;

    #[test]
    fn emissive_desc_maps_to_standard_material() {
        let mut app = App::new();
        app.add_plugins((MinimalPlugins, AssetPlugin::default()));
        let asset_server = app.world().resource::<AssetServer>();
        let desc = MaterialDesc {
            emissive_color: ColorDesc::srgb(0.5, 0.25, 1.0),
            emissive_intensity: 1000.0,
            ..Default::default()
        };

        let material = material_from_desc(asset_server, &desc);

        assert_eq!(material.emissive.red, 500.0);
        assert_eq!(material.emissive.green, 250.0);
        assert_eq!(material.emissive.blue, 1000.0);
    }
}
