use super::imported_material_preview::imported_material_parameters_ui;
use super::*;

pub(super) fn ensure_slot_id(entry: &mut StaticMeshRendererEntry, index: usize) {
    if entry.id.is_empty() {
        entry.id = ComponentInstanceId::new(format!("slot:{index}"));
    }
    if entry.material_slot_id.is_empty() {
        entry.material_slot_id = ComponentInstanceId::new(format!("slot:{}", entry.id.0));
    }
}

pub(super) fn material_drop_invalid_reason(
    world: &World,
    valid_material_candidate: bool,
) -> Option<&'static str> {
    if valid_material_candidate {
        return None;
    }
    let assets = world.get_resource::<EditorAssets>()?;
    let selection = assets.dragging_selection()?;
    let texture = matches!(
        selection,
        AssetSelection::SubAsset {
            kind: AssetSubAssetKind::Texture,
            ..
        }
    ) || assets
        .asset_for_selection(selection)
        .is_some_and(|asset| matches!(asset.kind, EditorAssetKind::Texture));
    Some(if texture {
        "Textures cannot be assigned directly; create or edit a Material"
    } else {
        "Only a project Material or Material Instance can be assigned"
    })
}

#[derive(Default)]
pub(super) struct MaterialSlotWidgetResponse {
    pub(super) changed: bool,
    pub(super) accepted_drop: bool,
}

pub(super) struct MaterialSlotWidgetContext<'a> {
    pub(super) candidates: &'a [AssetRefCandidate],
    pub(super) drop_candidate: Option<&'a AssetRefCandidate>,
    pub(super) invalid_drop_reason: Option<&'static str>,
    pub(super) expanded_header_when_parameters_closed: bool,
}

pub(super) fn material_slot_widget_ui(
    world: &mut World,
    ui: &mut egui::Ui,
    entity: Entity,
    slot: &mut shared::MaterialSlot,
    context: MaterialSlotWidgetContext<'_>,
) -> MaterialSlotWidgetResponse {
    let MaterialSlotWidgetContext {
        candidates,
        drop_candidate,
        invalid_drop_reason,
        expanded_header_when_parameters_closed,
    } = context;
    let mut response = MaterialSlotWidgetResponse::default();
    let actor_widget_id = stable_actor_widget_id(world, entity);
    let project_default = world
        .get_resource::<shared::ProjectContentDefaults>()
        .and_then(|defaults| defaults.default_material.clone());
    let model_material = model_material_for_actor_slot(world, entity, &slot.id);
    let (effective, inherited_badge) = if let Some(reference) = slot.material.clone() {
        (Some(reference), None)
    } else if let Some((reference, badge)) = model_material {
        (Some(reference), Some(badge))
    } else if let Some(reference) = project_default {
        (
            Some(reference),
            Some(crate::ui::materials::MaterialLayerBadge::ProjectDefault),
        )
    } else {
        (
            None,
            Some(crate::ui::materials::MaterialLayerBadge::DefaultGrid),
        )
    };
    let texture_id = effective.as_ref().and_then(|reference| {
        candidates
            .iter()
            .find(|candidate| {
                candidate.reference.asset_id == reference.0.asset_id
                    && candidate.reference.sub_asset_id == reference.0.sub_asset_id
            })
            .and_then(|candidate| candidate.texture_id)
    });
    let source_layer =
        inherited_badge == Some(crate::ui::materials::MaterialLayerBadge::ModelSource);
    let built_in_layer =
        inherited_badge == Some(crate::ui::materials::MaterialLayerBadge::DefaultGrid);
    let effective_path = effective
        .as_ref()
        .and_then(|reference| reference.0.source_path.as_deref());
    let is_project_material = !source_layer
        && effective_path.is_some_and(|path| MaterialAsset::load_from_path(path).is_ok());
    let is_project_instance = !source_layer
        && effective_path.is_some_and(|path| MaterialInstanceAsset::load_from_path(path).is_ok());
    let broken_project_reference = effective.is_some()
        && !source_layer
        && !built_in_layer
        && !is_project_material
        && !is_project_instance;
    let mut health = effective
        .as_ref()
        .and_then(|reference| reference.0.source_path.as_deref())
        .and_then(|path| {
            world
                .get_resource::<crate::asset_documents::AuthoredAssetDocumentStore>()
                .and_then(|store| store.snapshot_for_path(path))
        })
        .map_or_else(
            || {
                if broken_project_reference {
                    crate::ui::materials::MaterialHealth::Broken
                } else {
                    crate::ui::materials::MaterialHealth::Healthy
                }
            },
            |snapshot| {
                use crate::asset_documents::{AuthoredDocumentState, DerivedProcessingState};
                if matches!(
                    snapshot.state,
                    AuthoredDocumentState::ExternalConflict | AuthoredDocumentState::SaveFailed
                ) || snapshot.processing == DerivedProcessingState::Failed
                    || (broken_project_reference && !snapshot.dirty)
                {
                    crate::ui::materials::MaterialHealth::Broken
                } else if snapshot.dirty {
                    crate::ui::materials::MaterialHealth::Dirty
                } else if matches!(
                    snapshot.processing,
                    DerivedProcessingState::Queued | DerivedProcessingState::Processing
                ) {
                    crate::ui::materials::MaterialHealth::Processing
                } else {
                    crate::ui::materials::MaterialHealth::Healthy
                }
            },
        );
    let is_imported_source = effective.is_some() && source_layer;
    if health == crate::ui::materials::MaterialHealth::Healthy
        && (is_imported_source || built_in_layer)
    {
        health = crate::ui::materials::MaterialHealth::ReadOnly;
    }
    let shader_kind = crate::ui::materials::material_shader_kind(world, effective_path);
    let thumbnail = if broken_project_reference {
        crate::ui::materials::MaterialThumbnailPresentation::Failed
    } else if let Some(texture) = texture_id {
        crate::ui::materials::MaterialThumbnailPresentation::Ready(texture)
    } else if effective.is_none() {
        crate::ui::materials::MaterialThumbnailPresentation::Empty
    } else {
        crate::ui::materials::MaterialThumbnailPresentation::Pending
    };
    let picker_candidates = candidates
        .iter()
        .filter(|candidate| {
            candidate
                .reference
                .source_path
                .as_deref()
                .is_some_and(|path| {
                    MaterialAsset::load_from_path(path).is_ok()
                        || MaterialInstanceAsset::load_from_path(path).is_ok()
                })
        })
        .map(|candidate| crate::ui::materials::MaterialPickerCandidate {
            reference: candidate.reference.clone(),
            label: candidate.label.clone(),
            detail: candidate.detail.clone(),
            thumbnail: candidate.texture_id.map_or(
                crate::ui::materials::MaterialThumbnailPresentation::Pending,
                crate::ui::materials::MaterialThumbnailPresentation::Ready,
            ),
        })
        .collect::<Vec<_>>();
    let picker_drop_candidate =
        drop_candidate.map(|candidate| crate::ui::materials::MaterialPickerCandidate {
            reference: candidate.reference.clone(),
            label: candidate.label.clone(),
            detail: candidate.detail.clone(),
            thumbnail: candidate.texture_id.map_or(
                crate::ui::materials::MaterialThumbnailPresentation::Pending,
                crate::ui::materials::MaterialThumbnailPresentation::Ready,
            ),
        });
    let view = crate::ui::materials::MaterialSlotPanelViewModel {
        slot_id: slot.id.0.clone(),
        label: effective
            .as_ref()
            .map(|reference| reference.0.label.clone())
            .unwrap_or_else(|| "Default Grid".into()),
        path: effective_path.map(ToOwned::to_owned),
        shader: crate::ui::materials::material_shader_label(world, effective_path),
        shader_kind,
        can_edit_shader: is_project_material,
        thumbnail,
        inherited: inherited_badge,
        health,
        assigned: slot.material.as_ref().map(|reference| reference.0.clone()),
        effective: effective.as_ref().map(|reference| reference.0.clone()),
        candidates: picker_candidates,
        drop_candidate: picker_drop_candidate,
        invalid_drop_reason: invalid_drop_reason.map(ToOwned::to_owned),
        can_locate: effective.as_ref().is_some_and(|reference| {
            candidates.iter().any(|candidate| {
                candidate.reference.asset_id == reference.0.asset_id
                    && candidate.reference.sub_asset_id == reference.0.sub_asset_id
            })
        }),
        can_clear: slot.material.is_some(),
        can_extract: is_imported_source,
        can_create_instance: is_project_material || is_project_instance,
        parameters_initially_open: true,
        expanded_header_when_parameters_closed,
    };

    let widget_slot_id = slot.id.0.clone();
    ui.push_id(
        (
            "material_slot_widget",
            actor_widget_id.as_str(),
            widget_slot_id,
        ),
        |ui| {
                let panel = crate::ui::materials::material_slot_panel(
                    ui,
                    (actor_widget_id.as_str(), slot.id.0.as_str()),
                    &view,
                    |ui| {
                        let Some(reference) = effective.as_ref() else {
                            ui.label(
                                egui::RichText::new(
                                    "Default Grid is immutable. Assign a project Material to edit parameters.",
                                )
                                .small()
                                .color(TEXT_DIM),
                            );
                            return;
                        };
                        if broken_project_reference {
                            ui.colored_label(
                                crate::ui::theme::ERROR,
                                "Material reference is broken; rendering Default Grid",
                            );
                        } else if let Some(path) = reference.0.source_path.as_deref() {
                            if is_project_material || is_project_instance {
                                crate::ui::materials::inline_material_document_editor(
                                    world, ui, path,
                                );
                            } else {
                                imported_material_parameters_ui(world, ui, reference);
                            }
                        } else {
                            imported_material_parameters_ui(world, ui, reference);
                        }
                    },
                    |ui| {
                        ui.small(format!("Stable slot: {}", slot.id.0));
                        if let Some(reference) = effective.as_ref() {
                            ui.small(format!("Asset: {}", reference.0.asset_id));
                            ui.small(format!("Subasset: {}", reference.0.sub_asset_id));
                            if let Some(path) = reference.0.source_path.as_deref() {
                                ui.small(path);
                            }
                        } else {
                            ui.small("Immutable engine-owned fallback");
                        }
                    },
                );
                for action in panel.actions {
                    debug_assert_eq!(action.slot_id, slot.id.0);
                    match action.action {
                        crate::ui::materials::MaterialSlotAction::Assign {
                            reference,
                            from_drop,
                        } => {
                            slot.material = Some(MaterialRef::new(reference));
                            response.changed = true;
                            response.accepted_drop |= from_drop;
                        }
                        crate::ui::materials::MaterialSlotAction::BrowseLibrary => {
                            crate::ui::request_editor_tab(
                                world,
                                crate::ui::EditorTab::AssetBrowser,
                            );
                        }
                        crate::ui::materials::MaterialSlotAction::Locate => {
                            locate_asset_ref(
                                world,
                                effective.as_ref().map(|reference| &reference.0),
                                candidates,
                            );
                        }
                        crate::ui::materials::MaterialSlotAction::Clear => {
                            slot.material = None;
                            response.changed = true;
                        }
                        crate::ui::materials::MaterialSlotAction::CreateInstance => {
                            if let Some(reference) = effective.as_ref() {
                                create_instance_and_assign_slot(world, entity, slot, reference);
                            }
                        }
                        crate::ui::materials::MaterialSlotAction::ExtractEditable => {
                            if let Some(reference) = effective.as_ref() {
                                extract_slot_material(world, entity, slot, reference);
                            }
                        }
                        crate::ui::materials::MaterialSlotAction::SetShader(kind) => {
                            if let Some(path) = effective_path {
                                crate::ui::materials::set_material_shader_kind(world, path, kind);
                            }
                        }
                    }
                }
        },
    );
    let property_block = world
        .get::<MaterialPropertyBlocks>(entity)
        .and_then(|blocks| blocks.slots.iter().find(|block| block.slot_id == slot.id))
        .cloned();
    let project_promotion_base = (is_project_material || is_project_instance)
        .then(|| effective.clone())
        .flatten();
    if property_block_promotion_ui(
        ui,
        property_block.as_ref(),
        project_promotion_base.is_some(),
    ) {
        if let (Some(base), Some(block)) = (project_promotion_base, property_block) {
            open_property_block_promotion_review(
                world,
                entity,
                slot.id.clone(),
                slot.name.clone(),
                base,
                block,
            );
        }
    }
    response
}

fn stable_actor_widget_id(world: &World, entity: Entity) -> String {
    world
        .get::<ActorId>(entity)
        .map(|actor_id| format!("actor:{}", actor_id.0))
        .unwrap_or_else(|| format!("runtime:{entity:?}"))
}

fn create_instance_and_assign_slot(
    world: &mut World,
    entity: Entity,
    slot: &shared::MaterialSlot,
    reference: &MaterialRef,
) {
    let empty_block = MaterialPropertyBlock {
        slot_id: slot.id.clone(),
        parameters: Vec::new(),
        textures: Vec::new(),
    };
    let result = plan_material_instance_assignment(
        world,
        entity,
        reference,
        &empty_block,
        &slot.name,
        false,
    )
    .and_then(|transaction| commit_property_block_promotion(world, &transaction));
    world.resource_mut::<crate::scene_io::SceneIo>().status = match result {
        Ok(path) => format!("Created and assigned Material Instance {}", path.display()),
        Err(error) => error,
    };
}

fn extract_slot_material(
    world: &mut World,
    entity: Entity,
    slot: &shared::MaterialSlot,
    reference: &MaterialRef,
) {
    let source = reference.0.source_path.clone().or_else(|| {
        world
            .get_resource::<AssetRegistry>()
            .and_then(|registry| {
                registry
                    .records
                    .iter()
                    .find(|record| record.id.as_string() == reference.0.asset_id)
            })
            .map(|record| record.path.clone())
    });
    if let Some(source) = source {
        crate::ui::asset_browser::begin_actor_material_extraction(
            world,
            &source,
            entity,
            slot.id.clone(),
            reference.0.sub_asset_id.clone(),
        );
    } else {
        world.resource_mut::<crate::scene_io::SceneIo>().status =
            "Material source is unresolved and cannot be extracted".into();
    }
}

pub(super) fn thumbnail_for_mesh(
    mesh: &EditorAssetRef,
    candidates: &[AssetRefCandidate],
) -> Option<egui::TextureId> {
    candidates
        .iter()
        .find(|candidate| {
            candidate.reference.asset_id == mesh.asset_id
                && candidate.reference.sub_asset_id == mesh.sub_asset_id
        })
        .and_then(|candidate| candidate.texture_id)
}

#[expect(
    clippy::too_many_arguments,
    reason = "asset selector rows keep immediate-mode UI inputs explicit"
)]
pub(super) fn asset_selector_row(
    ui: &mut egui::Ui,
    label: &str,
    icon: Icon,
    asset: Option<&EditorAssetRef>,
    inherited_asset: Option<&EditorAssetRef>,
    clearable: bool,
    candidates: &[AssetRefCandidate],
    drop_candidate: Option<&AssetRefCandidate>,
    invalid_drop_reason: Option<&'static str>,
    actions: AssetSelectorActions,
) -> AssetSelectorResponse {
    let mut response = AssetSelectorResponse::default();
    if ui.available_width() < COMPACT_INSPECTOR_WIDTH {
        ui.vertical(|ui| {
            ui.label(label);
            let control_width = ui.available_width().max(1.0);
            asset_selector_control(
                ui,
                icon,
                asset,
                inherited_asset,
                clearable,
                control_width,
                candidates,
                drop_candidate,
                invalid_drop_reason,
                actions,
                &mut response,
            );
        });
    } else {
        ui.horizontal(|ui| {
            let row_width = ui.available_width().max(1.0);
            let label_width = ASSET_SELECTOR_LABEL_WIDTH.min(row_width);
            let control_width = (row_width - label_width - ui.spacing().item_spacing.x).max(1.0);
            ui.add_sized([label_width, 20.0], egui::Label::new(label));
            asset_selector_control(
                ui,
                icon,
                asset,
                inherited_asset,
                clearable,
                control_width,
                candidates,
                drop_candidate,
                invalid_drop_reason,
                actions,
                &mut response,
            );
        });
    }
    response
}

#[expect(
    clippy::too_many_arguments,
    reason = "asset selector controls keep immediate-mode UI inputs explicit"
)]
pub(super) fn asset_selector_control(
    ui: &mut egui::Ui,
    icon: Icon,
    asset: Option<&EditorAssetRef>,
    inherited_asset: Option<&EditorAssetRef>,
    clearable: bool,
    control_width: f32,
    candidates: &[AssetRefCandidate],
    drop_candidate: Option<&AssetRefCandidate>,
    invalid_drop_reason: Option<&'static str>,
    actions: AssetSelectorActions,
    response: &mut AssetSelectorResponse,
) {
    let display_asset = asset.or(inherited_asset);
    let inherited = asset.is_none() && inherited_asset.is_some();
    let selector_width = control_width.min(ui.available_width()).max(1.0);
    exact_region(
        ui,
        egui::vec2(selector_width, ASSET_SELECTOR_HEIGHT),
        egui::Layout::top_down(egui::Align::Min),
        |ui| {
            let contained_clip = ui.clip_rect().intersect(ui.max_rect());
            ui.set_clip_rect(contained_clip);
            let rect = ui.max_rect();
            let valid_drag = drop_candidate.is_some();
            let drop_hovered = valid_drag && ui.rect_contains_pointer(rect);
            let invalid_hovered = invalid_drop_reason.is_some() && ui.rect_contains_pointer(rect);
            let stroke = if drop_hovered {
                egui::Stroke::new(2.0_f32, egui::Color32::from_rgb(125, 198, 255))
            } else if invalid_hovered {
                egui::Stroke::new(2.0_f32, crate::ui::theme::ERROR)
            } else if valid_drag {
                egui::Stroke::new(1.0_f32, egui::Color32::from_rgb(58, 88, 122))
            } else {
                egui::Stroke::new(1.0_f32, BORDER)
            };
            let fill = if drop_hovered {
                egui::Color32::from_rgb(29, 57, 86)
            } else if invalid_hovered {
                egui::Color32::from_rgb(69, 31, 35)
            } else {
                WIDGET_BG.linear_multiply(0.75)
            };
            let inner_width = (selector_width - 18.0).max(1.0);
            egui::Frame::new()
                .fill(fill)
                .stroke(stroke)
                .corner_radius(egui::CornerRadius::same(4))
                .inner_margin(egui::Margin::symmetric(8, 6))
                .show(ui, |ui| {
                    ui.set_width(inner_width);
                    ui.horizontal(|ui| {
                        ui.add_sized(
                            [18.0, 20.0],
                            egui::Label::new(phosphor_icon(icon, 14.0).color(TEXT_DIM)),
                        );
                        let action_width = match actions {
                            AssetSelectorActions::Full if clearable => 78.0,
                            AssetSelectorActions::Full => 52.0,
                        };
                        let text_width = (ui.available_width() - action_width)
                            .max(1.0)
                            .min(ui.available_width().max(1.0));
                        ui.vertical(|ui| {
                            ui.set_max_width(text_width);
                            let name = display_asset
                                .map(|asset| asset.label.as_str())
                                .filter(|label| !label.trim().is_empty())
                                .unwrap_or("(none)");
                            ui.add(egui::Label::new(name).truncate());
                            let id = invalid_hovered
                                .then(|| invalid_drop_reason.unwrap_or_default().to_string())
                                .or_else(|| {
                                    display_asset.map(|asset| {
                                        if inherited {
                                            format!("Inherited | {}", asset.sub_asset_id)
                                        } else {
                                            asset.sub_asset_id.clone()
                                        }
                                    })
                                })
                                .filter(|id| !id.trim().is_empty())
                                .unwrap_or_else(|| "No imported asset selected".to_string());
                            ui.add(
                                egui::Label::new(egui::RichText::new(id).color(
                                    if invalid_hovered {
                                        crate::ui::theme::ERROR
                                    } else {
                                        TEXT_DIM
                                    },
                                ))
                                .truncate(),
                            );
                        });
                        ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
                            if actions == AssetSelectorActions::Full && clearable {
                                let clear = ui.add_enabled(
                                    asset.is_some(),
                                    egui::Button::new(phosphor_icon(icons::X, 16.0))
                                        .frame(false)
                                        .min_size(egui::vec2(22.0, 22.0)),
                                );
                                if clear.on_hover_text("Clear actor override").clicked() {
                                    response.clear = true;
                                }
                            }
                            if actions == AssetSelectorActions::Full {
                                let locate = ui.add_enabled(
                                    display_asset.is_some_and(EditorAssetRef::is_resolved),
                                    egui::Button::new(phosphor_icon(icons::CROSSHAIR, 16.0))
                                        .frame(false)
                                        .min_size(egui::vec2(22.0, 22.0)),
                                );
                                if locate.on_hover_text("Locate in content browser").clicked() {
                                    response.locate = true;
                                }
                            }
                            if candidates.is_empty() {
                                ui.add_enabled(
                                    false,
                                    egui::Button::new(phosphor_icon(icons::FOLDER_OPEN, 16.0))
                                        .frame(false)
                                        .min_size(egui::vec2(22.0, 22.0)),
                                )
                                .on_hover_text("No imported assets available");
                            } else {
                                let menu =
                                    ui.menu_button(phosphor_icon(icons::FOLDER_OPEN, 16.0), |ui| {
                                        ui.set_min_width(220.0);
                                        for candidate in candidates {
                                            let selected = display_asset
                                                .is_some_and(|asset| asset == &candidate.reference);
                                            let clicked = ui
                                                .selectable_label(
                                                    selected,
                                                    candidate.label.as_str(),
                                                )
                                                .on_hover_text(candidate.detail.as_str())
                                                .clicked();
                                            if clicked {
                                                response.selected =
                                                    Some(candidate.reference.clone());
                                                ui.close();
                                            }
                                        }
                                    });
                                menu.response.on_hover_text("Browse assets");
                            }
                        });
                    });
                });
            if drop_hovered && ui.input(|input| input.pointer.any_released()) {
                if let Some(candidate) = drop_candidate {
                    response.selected = Some(candidate.reference.clone());
                    response.accepted_drop = true;
                }
            }
        },
    );
}

#[cfg(test)]
mod widget_identity_tests {
    use super::*;

    #[test]
    fn endurance_guard_actor_widget_identity_survives_runtime_entity_replacement() {
        let mut world = World::new();
        let first = world.spawn(ActorId::new("persistent-actor")).id();
        let first_key = stable_actor_widget_id(&world, first);
        world.despawn(first);
        let replacement = world.spawn(ActorId::new("persistent-actor")).id();

        assert_eq!(first_key, stable_actor_widget_id(&world, replacement));
        assert_eq!(first_key, "actor:persistent-actor");
    }
}
