//! Material asset loading and inspector/browser integration.

use bevy::prelude::*;
use bevy_egui::egui;
use shared::{
    ComponentInstanceId, LevelObject, MaterialAsset, MaterialDesc, MaterialRef,
    RendererMaterialSlot,
};

use crate::assets::{EditorAsset, EditorAssetKind, EditorAssets};
use crate::history::{reflected_component_transaction, set_material_group_with_history};
use crate::ui::inspector::property_row;

pub fn load_material_asset(catalog_path: &str) -> Result<MaterialAsset, String> {
    MaterialAsset::load_from_path(catalog_path)
}

pub fn material_desc_from_asset(catalog_path: &str) -> Result<MaterialDesc, String> {
    shared::load_resolved_material_from_path(catalog_path).map(|(desc, _)| desc)
}

pub fn apply_material_asset_to_selection(
    world: &mut World,
    asset: &EditorAsset,
    selected: &bevy_inspector_egui::bevy_inspector::hierarchy::SelectedEntities,
) -> Result<(), String> {
    let path = asset
        .path
        .clone()
        .ok_or_else(|| format!("Material has no source path: {}", asset.label))?;
    let record = world
        .get_resource::<crate::asset_db::AssetRegistry>()
        .and_then(|registry| crate::asset_db::find_asset_by_path(registry, &path))
        .ok_or_else(|| format!("Material is missing from the asset registry: {path}"))?;
    let sub_asset_id = if shared::MaterialInstanceAsset::load_from_path(&path).is_ok() {
        "material:instance"
    } else {
        "material:source"
    };
    let material_ref = MaterialRef::new(
        shared::EditorAssetRef::new(record.id.as_string(), sub_asset_id, asset.label.clone())
            .with_source_path(path.clone()),
    );
    let desc = material_desc_from_asset(&path).ok();
    let entities = crate::ui::helpers::selected_level_entities(world, selected);
    let mut renderer_changes = 0usize;
    let mut legacy_changes = Vec::new();
    for entity in entities {
        if world.get::<LevelObject>(entity).is_none() {
            continue;
        }
        if let Some(mut renderer) = world.get::<shared::StaticMeshRenderer>(entity).cloned() {
            ensure_static_material_slots(&mut renderer);
            for slot in &mut renderer.materials.slots {
                slot.material = Some(material_ref.clone());
            }
            reflected_component_transaction(
                world,
                entity,
                "Assign Material Slots",
                shared::AUTHORING_COMPONENT_STATIC_MESH_RENDERER,
                shared::COMPONENT_STATIC_MESH_RENDERER,
                move |world, entity| {
                    world.entity_mut(entity).insert(renderer);
                    Ok(())
                },
            )?;
            renderer_changes += 1;
            continue;
        }
        if let Some(mut renderer) = world.get::<shared::SkinnedMeshRenderer>(entity).cloned() {
            if renderer.materials.slots.is_empty() {
                return Err(format!(
                    "Skinned renderer `{}` has no imported material slots; reimport its model",
                    renderer.path
                ));
            }
            for slot in &mut renderer.materials.slots {
                slot.material = Some(material_ref.clone());
            }
            reflected_component_transaction(
                world,
                entity,
                "Assign Material Slots",
                shared::AUTHORING_COMPONENT_SKINNED_MESH_RENDERER,
                shared::COMPONENT_SKINNED_MESH_RENDERER,
                move |world, entity| {
                    world.entity_mut(entity).insert(renderer);
                    Ok(())
                },
            )?;
            renderer_changes += 1;
            continue;
        }
        if let Some(desc) = desc.as_ref() {
            legacy_changes.push((entity, desc.clone()));
        }
    }
    if !legacy_changes.is_empty() {
        set_material_group_with_history(world, legacy_changes);
    }
    if renderer_changes == 0 && desc.is_none() {
        return Err("No selected renderable actor accepts this material".into());
    }
    world.resource_mut::<crate::scene_io::SceneIo>().status =
        format!("Applied shared material {}", asset.label);
    Ok(())
}

pub(crate) fn ensure_static_material_slots(renderer: &mut shared::StaticMeshRenderer) {
    for part in &mut renderer.slots {
        if part.material_slot_id.0.trim().is_empty() {
            part.material_slot_id = if part.id.0.trim().is_empty() {
                ComponentInstanceId::new("slot:legacy")
            } else {
                ComponentInstanceId::new(format!("slot:{}", part.id.0))
            };
        }
        if renderer.materials.slot(&part.material_slot_id).is_none() {
            renderer.materials.slots.push(RendererMaterialSlot {
                id: part.material_slot_id.clone(),
                name: part.name.clone(),
                source_material: part.material.clone().map(MaterialRef::new),
                material: None,
            });
        }
    }
}

pub fn material_asset_picker_ui(
    world: &mut World,
    ui: &mut egui::Ui,
    _entity: Entity,
    material: &mut MaterialDesc,
    _original: &MaterialDesc,
) -> bool {
    let assets: Vec<(String, String)> = world
        .resource::<EditorAssets>()
        .assets
        .iter()
        .filter(|a| matches!(a.kind, EditorAssetKind::Material))
        .filter_map(|a| Some((a.label.clone(), a.path.clone()?)))
        .collect();

    let current = material.material_asset_path.clone().unwrap_or_default();
    let mut selected_label = assets
        .iter()
        .find(|(_, path)| path == &current)
        .map(|(label, _)| label.clone())
        .unwrap_or_else(|| "(none)".to_string());

    let mut changed = false;
    property_row(ui, "Material asset", |ui| {
        egui::ComboBox::from_id_salt("material_asset_picker")
            .selected_text(&selected_label)
            .show_ui(ui, |ui| {
                if ui.selectable_label(current.is_empty(), "(none)").clicked() {
                    material.material_asset_path = None;
                    selected_label = "(none)".into();
                    changed = true;
                }
                for (label, path) in &assets {
                    if ui
                        .selectable_label(&selected_label == label, label)
                        .clicked()
                    {
                        if let Ok(desc) = material_desc_from_asset(path) {
                            *material = desc;
                            selected_label = label.clone();
                            changed = true;
                        } else {
                            world.resource_mut::<crate::scene_io::SceneIo>().status =
                                format!("Could not load material {path}");
                        }
                    }
                }
            });
    });

    changed
}
