//! Derived-processing classification for authored document saves.

use shared::{
    EditorAssetRef, MaterialAsset, MaterialInputSet, MaterialInstanceAsset, TextureChannel,
};

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum DerivedProcessingImpact {
    #[default]
    None,
    MaterialPacking,
    TextureAndDependents,
    ModelArtifacts,
    CatalogDefaults,
}

pub fn material_impact(clean: &MaterialAsset, current: &MaterialAsset) -> DerivedProcessingImpact {
    if packing_bindings(&clean.inputs) != packing_bindings(&current.inputs) {
        DerivedProcessingImpact::MaterialPacking
    } else {
        DerivedProcessingImpact::None
    }
}

pub fn instance_impact(
    clean: &MaterialInstanceAsset,
    current: &MaterialInstanceAsset,
) -> DerivedProcessingImpact {
    if clean.base != current.base
        || packing_bindings(&clean.overrides) != packing_bindings(&current.overrides)
    {
        DerivedProcessingImpact::MaterialPacking
    } else {
        DerivedProcessingImpact::None
    }
}

fn packing_bindings(
    inputs: &MaterialInputSet,
) -> Vec<(String, Option<EditorAssetRef>, TextureChannel)> {
    ["occlusion", "roughness", "metallic"]
        .into_iter()
        .map(|name| {
            let binding = inputs.texture(name);
            (
                name.to_string(),
                binding.and_then(|binding| binding.texture.clone()),
                binding.map(|binding| binding.channel).unwrap_or_default(),
            )
        })
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;
    use shared::{MaterialParameter, MaterialParameterValue, MaterialTextureBinding};

    #[test]
    fn scalar_values_do_not_require_material_repacking() {
        let clean = MaterialAsset {
            schema_version: shared::MATERIAL_ASSET_SCHEMA_VERSION,
            label: "Test".into(),
            shader: Default::default(),
            shader_ref: None,
            render_state: Default::default(),
            provenance: None,
            inputs: Default::default(),
        };
        let mut current = clean.clone();
        current.inputs.values.push(MaterialParameter {
            name: "roughness".into(),
            value: MaterialParameterValue::Float(0.25),
        });
        assert_eq!(
            material_impact(&clean, &current),
            DerivedProcessingImpact::None
        );
    }

    #[test]
    fn render_state_and_color_changes_do_not_require_material_repacking() {
        let clean = MaterialAsset {
            schema_version: shared::MATERIAL_ASSET_SCHEMA_VERSION,
            label: "Test".into(),
            shader: Default::default(),
            shader_ref: None,
            render_state: Default::default(),
            provenance: None,
            inputs: Default::default(),
        };
        let mut current = clean.clone();
        current.render_state.double_sided = true;
        current.inputs.values.push(MaterialParameter {
            name: "base_color".into(),
            value: MaterialParameterValue::Color(shared::ColorDesc::srgb(0.2, 0.3, 0.4)),
        });

        assert_eq!(
            material_impact(&clean, &current),
            DerivedProcessingImpact::None
        );
    }

    #[test]
    fn arm_texture_or_channel_changes_require_one_material_pack() {
        let clean = MaterialAsset {
            schema_version: shared::MATERIAL_ASSET_SCHEMA_VERSION,
            label: "Test".into(),
            shader: Default::default(),
            shader_ref: None,
            render_state: Default::default(),
            provenance: None,
            inputs: Default::default(),
        };
        let mut current = clean.clone();
        current.inputs.textures.push(MaterialTextureBinding {
            name: "roughness".into(),
            texture: Some(EditorAssetRef::new("arm", "texture:source", "ARM")),
            channel: TextureChannel::G,
        });
        assert_eq!(
            material_impact(&clean, &current),
            DerivedProcessingImpact::MaterialPacking
        );

        let mut channel_change = current.clone();
        channel_change.inputs.textures[0].channel = TextureChannel::R;
        assert_eq!(
            material_impact(&current, &channel_change),
            DerivedProcessingImpact::MaterialPacking
        );
    }
}
