use super::*;

pub(super) fn brush_editor_ui(world: &mut World, ui: &mut egui::Ui, entity: Entity) {
    let material_candidates = brush_face_material_ref_candidates(world);
    request_texture_asset_thumbnails(world);
    let texture_candidates = texture_asset_ref_candidates(world);
    let Some(mut brush) = world.get::<BrushDesc>(entity).cloned() else {
        return;
    };
    let original = brush.clone();
    let mut changed = false;
    let mut reset_cube = false;
    let card = component_card_context(
        world,
        entity,
        ComponentCardOptions::removable(COMPONENT_BRUSH_DESC, "Brush", icons::CUBE),
    );
    let card_response = component_card(ui, &card, |ui| {
        property_row(ui, "Kind", |ui| {
            egui::ComboBox::from_id_salt("brush_kind")
                .selected_text(match brush.kind {
                    BrushKind::Additive => "Additive",
                    BrushKind::SubtractiveMarker => "Subtractive Marker",
                })
                .show_ui(ui, |ui| {
                    changed |= ui
                        .selectable_value(&mut brush.kind, BrushKind::Additive, "Additive")
                        .changed();
                    changed |= ui
                        .selectable_value(
                            &mut brush.kind,
                            BrushKind::SubtractiveMarker,
                            "Subtractive Marker",
                        )
                        .changed();
                });
        });
        property_row(ui, "Faces", |ui| {
            ui.label(format!("{}", brush.faces.len()));
        });
        property_row(ui, "Shadows", |ui| {
            ui.horizontal_wrapped(|ui| {
                changed |= ui.checkbox(&mut brush.cast_shadows, "Cast").changed();
                changed |= ui.checkbox(&mut brush.receive_shadows, "Receive").changed();
            });
        });
        brush_validation_ui(ui, &brush);
        changed |= selected_brush_face_controls(
            world,
            ui,
            entity,
            &mut brush,
            &material_candidates,
            &texture_candidates,
        );
        if ui.button("Reset Cube Brush").clicked() {
            reset_cube = true;
        }
    });
    apply_component_card_response(world, entity, card_response);

    if reset_cube {
        brush = BrushDesc::default();
        changed = true;
    }
    if changed && brush != original {
        set_brush_with_history(world, entity, brush);
    }
}

pub(super) fn terrain_editor_ui(world: &mut World, ui: &mut egui::Ui, entity: Entity) {
    let material_candidates = brush_face_material_ref_candidates(world);
    let dragging_selection = world
        .get_resource::<EditorAssets>()
        .and_then(|assets| assets.dragging_selection().cloned());
    let material_drop_candidate = dragging_selection.as_ref().and_then(|selection| {
        asset_ref_candidate_from_selection(world, selection, AssetRefCandidateKind::Material)
    });
    let Some(mut terrain) = world.get::<TerrainDesc>(entity).cloned() else {
        return;
    };
    let original = terrain.clone();
    let mut changed = false;
    let mut requested_resolution = terrain.resolution;
    let mut remove_layer = None;
    let mut swap_layers = None;
    let mut locate_layer = None;
    let mut accepted_drop = false;
    let card = component_card_context(
        world,
        entity,
        ComponentCardOptions::removable(COMPONENT_TERRAIN_DESC, "Terrain", icons::MOUNTAINS),
    );
    let card_response = component_card(ui, &card, |ui| {
        property_row(ui, "Grid", |ui| {
            ui.horizontal(|ui| {
                ui.add(
                    egui::DragValue::new(&mut requested_resolution)
                        .range(2..=1025)
                        .suffix(" samples"),
                );
                if ui
                    .add_enabled(
                        requested_resolution != terrain.resolution,
                        egui::Button::new("Resize Flat"),
                    )
                    .on_hover_text("Replaces the current height grid with a flat grid")
                    .clicked()
                {
                    let replacement = TerrainDesc::flat(requested_resolution);
                    terrain.resolution = replacement.resolution;
                    terrain.heights = replacement.heights;
                    terrain.material_weights.clear();
                    terrain.chunk_quads = terrain.chunk_quads.min(terrain.resolution - 1).max(1);
                    changed = true;
                }
            });
        });
        property_row(ui, "Sample Spacing", |ui| {
            changed |= ui
                .add(
                    egui::DragValue::new(&mut terrain.sample_spacing)
                        .range(0.01..=1000.0)
                        .speed(0.1)
                        .suffix(" m"),
                )
                .changed();
        });
        property_row(ui, "Height Scale", |ui| {
            changed |= ui
                .add(
                    egui::DragValue::new(&mut terrain.height_scale)
                        .range(0.01..=10000.0)
                        .speed(0.1)
                        .suffix(" m"),
                )
                .changed();
        });
        property_row(ui, "Chunk Size", |ui| {
            changed |= ui
                .add(
                    egui::DragValue::new(&mut terrain.chunk_quads)
                        .range(1..=terrain.resolution.saturating_sub(1))
                        .suffix(" quads"),
                )
                .changed();
        });
        property_row(ui, "Collision", |ui| {
            changed |= ui
                .checkbox(&mut terrain.generate_colliders, "Generate")
                .changed();
        });
        property_row(ui, "Shadows", |ui| {
            ui.horizontal_wrapped(|ui| {
                changed |= ui.checkbox(&mut terrain.cast_shadows, "Cast").changed();
                changed |= ui
                    .checkbox(&mut terrain.receive_shadows, "Receive")
                    .changed();
            });
        });
        ui.add_space(6.0);
        ui.separator();
        ui.horizontal(|ui| {
            ui.label(egui::RichText::new("Material Layers").strong());
            ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
                if ui
                    .add_enabled(
                        terrain.material_layers.len() < shared::TERRAIN_MATERIAL_LAYER_LIMIT,
                        egui::Button::new(phosphor_icon(icons::PLUS, 16.0)),
                    )
                    .on_hover_text("Add a terrain blend channel")
                    .clicked()
                {
                    let material = if terrain.material_layers.is_empty() {
                        terrain.base_material.take()
                    } else {
                        None
                    };
                    terrain.material_layers.push(shared::TerrainMaterialLayer {
                        material,
                        ..Default::default()
                    });
                    changed = true;
                }
            });
        });
        if terrain.material_layers.is_empty() {
            ui.label(
                egui::RichText::new(
                    terrain
                        .base_material
                        .as_ref()
                        .map(|material| format!("Legacy base: {}", material.label))
                        .unwrap_or_else(|| "No layers assigned; visible terrain fallback".into()),
                )
                .color(TEXT_DIM)
                .small(),
            );
        }
        for index in 0..terrain.material_layers.len() {
            ui.add_space(4.0);
            ui.horizontal(|ui| {
                ui.label(
                    egui::RichText::new(format!("Layer {}", index + 1))
                        .strong()
                        .color(if index == 0 {
                            SELECTION_BG_MUTED
                        } else {
                            TEXT_DIM
                        }),
                );
                ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
                    if icon_button_small(ui, icons::TRASH, "Remove layer").clicked() {
                        remove_layer = Some(index);
                    }
                    if ui
                        .add_enabled(
                            index + 1 < terrain.material_layers.len(),
                            egui::Button::new(phosphor_icon(icons::ARROW_DOWN, 15.0)).frame(false),
                        )
                        .on_hover_text("Move layer down")
                        .clicked()
                    {
                        swap_layers = Some((index, index + 1));
                    }
                    if ui
                        .add_enabled(
                            index > 0,
                            egui::Button::new(phosphor_icon(icons::ARROW_UP, 15.0)).frame(false),
                        )
                        .on_hover_text("Move layer up")
                        .clicked()
                    {
                        swap_layers = Some((index, index - 1));
                    }
                });
            });
            let response = asset_selector_row(
                ui,
                "Material",
                icons::PALETTE,
                terrain.material_layers[index].material.as_ref(),
                None,
                true,
                &material_candidates,
                material_drop_candidate.as_ref(),
                None,
                AssetSelectorActions::Full,
            );
            if let Some(selected) = response.selected {
                terrain.material_layers[index].material = Some(selected);
                changed = true;
            }
            if response.clear {
                terrain.material_layers[index].material = None;
                changed = true;
            }
            if response.locate {
                locate_layer = Some(index);
            }
            accepted_drop |= response.accepted_drop;
            property_row(ui, "UV Tiling", |ui| {
                changed |= ui
                    .add(
                        egui::DragValue::new(&mut terrain.material_layers[index].uv_scale)
                            .range(0.01..=1024.0)
                            .speed(0.1)
                            .suffix("×"),
                    )
                    .changed();
            });
            if !terrain.material_weights.is_empty() {
                let covered = terrain
                    .material_weights
                    .iter()
                    .filter(|weights| weights[index] > 0)
                    .count();
                ui.label(
                    egui::RichText::new(format!(
                        "{} / {} samples carry this layer",
                        covered,
                        terrain.material_weights.len()
                    ))
                    .color(TEXT_DIM)
                    .small(),
                );
            }
        }
        match terrain.validate() {
            Ok(()) => {
                ui.label(
                    egui::RichText::new(format!(
                        "{} heights • {}×{} chunks",
                        terrain.heights.len(),
                        (terrain.resolution - 1).div_ceil(terrain.chunk_quads),
                        (terrain.resolution - 1).div_ceil(terrain.chunk_quads)
                    ))
                    .color(crate::ui::theme::SUCCESS),
                );
            }
            Err(error) => {
                ui.label(egui::RichText::new(error).color(crate::ui::theme::ERROR));
            }
        }
    });
    apply_component_card_response(world, entity, card_response);

    if let Some(index) = remove_layer {
        remove_terrain_material_layer(&mut terrain, index);
        changed = true;
    }
    if let Some((a, b)) = swap_layers {
        terrain.material_layers.swap(a, b);
        for weights in &mut terrain.material_weights {
            weights.swap(a, b);
        }
        changed = true;
    }
    if let Some(index) = locate_layer {
        locate_asset_ref(
            world,
            terrain
                .material_layers
                .get(index)
                .and_then(|layer| layer.material.as_ref()),
            &material_candidates,
        );
    }
    if accepted_drop {
        clear_asset_drag(world);
    }

    if changed && terrain != original {
        let _ = reflected_component_transaction(
            world,
            entity,
            "Edit Terrain",
            shared::AUTHORING_COMPONENT_TERRAIN,
            COMPONENT_TERRAIN_DESC,
            move |world, entity| {
                world.entity_mut(entity).insert(terrain);
                Ok(())
            },
        );
    }
}

pub(super) fn remove_terrain_material_layer(terrain: &mut TerrainDesc, index: usize) {
    if index >= terrain.material_layers.len() {
        return;
    }
    terrain.material_layers.remove(index);
    if terrain.material_layers.is_empty() {
        terrain.material_weights.clear();
        return;
    }
    for weights in &mut terrain.material_weights {
        for channel in index..3 {
            weights[channel] = weights[channel + 1];
        }
        weights[3] = 0;
        normalize_terrain_weights(weights, terrain.material_layers.len());
    }
}

pub(super) fn normalize_terrain_weights(weights: &mut [u8; 4], layer_count: usize) {
    for weight in weights.iter_mut().skip(layer_count) {
        *weight = 0;
    }
    let sum: u16 = weights
        .iter()
        .take(layer_count)
        .copied()
        .map(u16::from)
        .sum();
    if sum == 0 {
        *weights = [255, 0, 0, 0];
        return;
    }
    let source = *weights;
    let mut assigned = 0_u16;
    for index in 0..layer_count {
        weights[index] = ((u16::from(source[index]) * 255) / sum) as u8;
        assigned += u16::from(weights[index]);
    }
    let largest = (0..layer_count)
        .max_by_key(|index| source[*index])
        .unwrap_or(0);
    weights[largest] = weights[largest].saturating_add((255 - assigned) as u8);
}

pub(super) fn brush_validation_ui(ui: &mut egui::Ui, brush: &BrushDesc) {
    let report = validate_brush(brush);
    if report.diagnostics.is_empty() {
        return;
    }

    ui.add_space(6.0);
    egui::Frame::new()
        .fill(egui::Color32::from_rgb(31, 32, 36))
        .stroke(egui::Stroke::new(1.0_f32, BORDER))
        .inner_margin(egui::Margin::symmetric(8, 6))
        .show(ui, |ui| {
            let has_errors = !report.is_valid();
            let title_color = if has_errors {
                egui::Color32::from_rgb(255, 137, 129)
            } else {
                egui::Color32::from_rgb(255, 190, 110)
            };
            ui.horizontal(|ui| {
                let icon = if has_errors {
                    icons::WARNING
                } else {
                    icons::INFO
                };
                ui.label(phosphor_icon(icon, 14.0).color(title_color));
                ui.label(
                    egui::RichText::new(if has_errors {
                        "Brush geometry needs repair"
                    } else {
                        "Brush diagnostics"
                    })
                    .color(title_color),
                );
            });
            for diagnostic in report.diagnostics.iter().take(4) {
                let color = match diagnostic.severity {
                    BrushDiagnosticSeverity::Error => egui::Color32::from_rgb(255, 137, 129),
                    BrushDiagnosticSeverity::Warning => egui::Color32::from_rgb(255, 190, 110),
                };
                let face = diagnostic
                    .face
                    .as_ref()
                    .filter(|face| !face.is_empty())
                    .map(|face| format!("{}: ", face.0))
                    .unwrap_or_default();
                ui.label(egui::RichText::new(format!("{face}{}", diagnostic.message)).color(color));
            }
            let hidden_count = report.diagnostics.len().saturating_sub(4);
            if hidden_count > 0 {
                ui.label(egui::RichText::new(format!("+{hidden_count} more")).color(TEXT_MUTED));
            }
        });
}

pub(super) fn selected_brush_face_controls(
    world: &mut World,
    ui: &mut egui::Ui,
    entity: Entity,
    brush: &mut BrushDesc,
    material_candidates: &[AssetRefCandidate],
    texture_candidates: &[AssetRefCandidate],
) -> bool {
    let Some(selection) = world.get_resource::<BrushElementSelection>() else {
        return false;
    };
    if selection.brush != Some(entity) {
        return false;
    }
    let selected_faces: Vec<_> = selection
        .elements
        .iter()
        .filter_map(|element| match element {
            BrushElementKey::Face { face } => Some(face.clone()),
            _ => None,
        })
        .collect();
    if selected_faces.is_empty() {
        return false;
    }

    let dragging_selection = world
        .get_resource::<EditorAssets>()
        .and_then(|assets| assets.dragging_selection().cloned());
    let material_drop_candidate = dragging_selection.as_ref().and_then(|selection| {
        asset_ref_candidate_from_selection(world, selection, AssetRefCandidateKind::Material)
    });
    let texture_drop_candidate = dragging_selection.as_ref().and_then(|selection| {
        asset_ref_candidate_from_selection(world, selection, AssetRefCandidateKind::Texture)
    });
    let mut changed = false;
    ui.add_space(6.0);
    ui.separator();
    ui.label(
        egui::RichText::new(format!("{} selected face(s)", selected_faces.len())).color(TEXT_DIM),
    );

    for face_id in selected_faces {
        let Some(face) = brush.faces.iter_mut().find(|face| face.id == face_id) else {
            continue;
        };
        let label = if face.id.0.trim().is_empty() {
            "Face".to_string()
        } else {
            face.id.0.clone()
        };
        egui::CollapsingHeader::new(label)
            .default_open(true)
            .show(ui, |ui| {
                property_row(ui, "UV offset", |ui| {
                    changed |= ui
                        .add(egui::DragValue::new(&mut face.uv_offset.x).speed(0.05))
                        .changed();
                    changed |= ui
                        .add(egui::DragValue::new(&mut face.uv_offset.y).speed(0.05))
                        .changed();
                });
                property_row(ui, "UV scale", |ui| {
                    changed |= ui
                        .add(egui::DragValue::new(&mut face.uv_scale.x).speed(0.05))
                        .changed();
                    changed |= ui
                        .add(egui::DragValue::new(&mut face.uv_scale.y).speed(0.05))
                        .changed();
                });
                property_row(ui, "UV rotation", |ui| {
                    changed |= ui
                        .add(egui::DragValue::new(&mut face.uv_rotation).speed(1.0))
                        .changed();
                });
                let material_response = asset_selector_row(
                    ui,
                    "Material",
                    icons::PALETTE,
                    face.material.as_ref(),
                    None,
                    true,
                    material_candidates,
                    material_drop_candidate.as_ref(),
                    None,
                    AssetSelectorActions::Full,
                );
                if let Some(selected) = material_response.selected {
                    face.material = Some(selected);
                    changed = true;
                }
                if material_response.clear {
                    face.material = None;
                    changed = true;
                }
                if material_response.locate {
                    locate_asset_ref(world, face.material.as_ref(), material_candidates);
                }
                if material_response.accepted_drop {
                    clear_asset_drag(world);
                }

                let texture_response = asset_selector_row(
                    ui,
                    "Texture",
                    icons::IMAGE,
                    face.texture.as_ref(),
                    None,
                    true,
                    texture_candidates,
                    texture_drop_candidate.as_ref(),
                    None,
                    AssetSelectorActions::Full,
                );
                if let Some(selected) = texture_response.selected {
                    face.texture = Some(selected);
                    changed = true;
                }
                if texture_response.clear {
                    face.texture = None;
                    changed = true;
                }
                if texture_response.locate {
                    locate_asset_ref(world, face.texture.as_ref(), texture_candidates);
                }
                if texture_response.accepted_drop {
                    clear_asset_drag(world);
                }
            });
    }

    changed
}
