//! Unified viewport overlay chrome (toolbars, badges, HUD).

use bevy::prelude::*;
use bevy_egui::{egui, EguiTextureHandle, EguiUserTextures};
use bevy_inspector_egui::bevy_inspector::hierarchy::SelectedEntities;
use egui_phosphor_icons::icons;
use transform_gizmo_bevy::prelude::GizmoOptions;

use crate::assets::operators::{
    assign_animation_clip_operator, assign_audio_clip_operator, place_asset_operator,
    place_subasset_operator,
};
use crate::assets::thumbnails::kind_icon;
use crate::assets::{AssetSelection, AssetSubAssetKind, EditorAssets};
use crate::camera::EditorCamera;
use crate::gizmos::{EditorGizmoMode, EditorGizmoSpace};
use crate::render_target::ViewportRenderTarget;
use crate::selection::{ViewportClick, ViewportPickStack};
use crate::state::PlayPossession;
use crate::viewport::actor_icons::ActorIconSettings;
use crate::viewport::brush_edit::{BrushEditMode, BrushElementSelection};
use crate::viewport::brush_tool::{BrushToolPhase, BrushToolState};
use crate::viewport::{
    material_drop::is_surface_asset_selection, snap_translation, viewport_ground_position,
    EditorViewportMode, MaterialDropState, ViewportDisplayMode, ViewportSettings,
};

use super::helpers::{
    focus_editor_camera_on_selection, viewport_recall_bookmark, viewport_save_bookmark,
};
use super::theme::{
    overlay_chip_frame, viewport_toolbar_frame, ACCENT, ACCENT_HOVER, AXIS_X, AXIS_Y, AXIS_Z,
    BORDER, BORDER_STRONG, ELEVATED_BG, ERROR, SELECTION, SUCCESS, TEXT, TEXT_DIM, TEXT_MUTED,
    WARNING,
};
use super::widgets::{
    icon_button_small, panel_toolbar_row, phosphor_icon, tool_button, tool_button_accent,
};

#[derive(Resource, Default)]
pub struct ViewportUiState {
    pub options_open: bool,
    pub shortcuts_open: bool,
}

const VIEWPORT_TOOLTIP: &str = "RMB + WASD/QE: fly | MMB: pan | Scroll: dolly\nW/E/R: gizmo | X: world/local | F: focus | G: game view | Ctrl+G: grid\nTab: cycle overlapping picks | Play/Pause/Stop: main toolbar (F5 / F6)";

#[expect(
    clippy::too_many_arguments,
    reason = "viewport tab rendering keeps immediate-mode UI inputs explicit"
)]
pub fn viewport_tab_ui(
    world: &mut World,
    ui: &mut egui::Ui,
    viewport_rect: &mut egui::Rect,
    pointer_in_viewport: &mut bool,
    viewport_pointer_pos: &mut Option<egui::Pos2>,
    selected_entities: &SelectedEntities,
    playing: bool,
    _cursor_grabbed: bool,
) {
    let rect = ui.available_rect_before_wrap();
    *viewport_rect = rect;

    if rect.width() <= 1.0 || rect.height() <= 1.0 {
        *pointer_in_viewport = false;
        *viewport_pointer_pos = None;
        return;
    }

    let clean_game_view = world.resource::<ViewportDisplayMode>().clean_game_view;
    let response = ui.allocate_rect(rect, egui::Sense::HOVER);
    // A drag source owns egui's interaction response, so `Response::hover_pos()`
    // is intentionally empty while an asset is dragged over the viewport. The
    // global pointer position remains available and is the correct input for
    // drag previews and exact surface targeting.
    *viewport_pointer_pos = ui
        .input(|input| {
            input
                .pointer
                .interact_pos()
                .or_else(|| input.pointer.hover_pos())
        })
        .filter(|pos| rect.contains(*pos));
    *pointer_in_viewport = viewport_pointer_pos.is_some();

    let primary_clicked = ui.input(|input| {
        input.pointer.button_clicked(egui::PointerButton::Primary) && !input.modifiers.alt
    });
    if primary_clicked && !clean_game_view {
        if let Some(pos) = response
            .interact_pointer_pos()
            .or_else(|| response.hover_pos())
            .filter(|pos| rect.contains(*pos))
        {
            world.resource_mut::<ViewportClick>().0 = Some(pos);
        }
    }

    if let Some(target) = world
        .get_resource::<ViewportRenderTarget>()
        .and_then(|target| target.0.clone())
    {
        let mut textures = world.resource_mut::<EguiUserTextures>();
        let texture_id = textures
            .image_id(target.image.id())
            .unwrap_or_else(|| textures.add_image(EguiTextureHandle::Strong(target.image.clone())));

        let painter = ui.painter();
        painter.rect_filled(rect, 0.0, egui::Color32::BLACK);
        painter.image(
            texture_id,
            rect,
            egui::Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0)),
            egui::Color32::WHITE,
        );
        painter.rect_stroke(
            rect,
            0.0,
            egui::Stroke::new(
                1.0,
                if response.hovered() {
                    BORDER_STRONG
                } else {
                    BORDER
                },
            ),
            egui::StrokeKind::Inside,
        );
    } else {
        ui.allocate_rect(rect, egui::Sense::hover());
        ui.scope_builder(egui::UiBuilder::new().max_rect(rect), |ui| {
            ui.centered_and_justified(|ui| {
                ui.label(egui::RichText::new("Viewport").weak());
            });
        });
    }

    if clean_game_view {
        if playing {
            game_view_overlay(world, ui.ctx(), rect, playing);
        }
        return;
    }

    scene_view_overlay_toolbar(world, ui.ctx(), rect);
    scene_view_selection_hud(world, ui.ctx(), rect, selected_entities);
    scene_view_brush_draw_hints(world, ui.ctx(), rect);
    scene_view_render_badge(world, ui.ctx(), rect);
    scene_view_brush_mode_badge(world, ui.ctx(), rect);
    scene_view_volume_hud(world, ui.ctx(), rect);
    scene_view_orientation_widget(world, ui.ctx(), rect);
    viewport_asset_drop_ui(world, ui, rect, selected_entities);
}

fn scene_view_render_badge(world: &World, ctx: &egui::Context, scene_rect: egui::Rect) {
    let Some(mode) = world.get_resource::<EditorViewportMode>() else {
        return;
    };
    let mode = *mode;
    let (label, color) = match mode {
        EditorViewportMode::Lit => ("Lit", SUCCESS),
        EditorViewportMode::Unlit => ("Unlit", WARNING),
        EditorViewportMode::Collider => ("Collider", WARNING),
    };

    let profile = world.get_resource::<settings::ActiveCameraRenderProfile>();
    let (gi_label, gi_color) = match profile.map(|profile| profile.gi_path) {
        Some(settings::GiPath::SolariDeferred) => ("Solari", ACCENT),
        _ => ("Forward", SELECTION),
    };

    egui::Area::new(egui::Id::new("scene_view_render_badge"))
        .fixed_pos(scene_rect.right_bottom() + egui::vec2(-10.0, -10.0))
        .pivot(egui::Align2::RIGHT_BOTTOM)
        .interactable(false)
        .show(ctx, |ui| {
            overlay_chip_frame().show(ui, |ui| {
                ui.horizontal(|ui| {
                    ui.label(egui::RichText::new(label).color(color).strong().small());
                    ui.label(egui::RichText::new("/").color(TEXT_MUTED).small());
                    ui.label(
                        egui::RichText::new(gi_label)
                            .color(gi_color)
                            .strong()
                            .small(),
                    );
                });
            });
        });

    if mode == EditorViewportMode::Collider {
        egui::Area::new(egui::Id::new("scene_view_collider_warning"))
            .fixed_pos(scene_rect.left_bottom() + egui::vec2(8.0, -28.0))
            .interactable(false)
            .show(ctx, |ui| {
                overlay_chip_frame().show(ui, |ui| {
                    ui.label(
                        egui::RichText::new("Collider view / meshes hidden")
                            .color(WARNING)
                            .small(),
                    );
                });
            });
    }
}

fn scene_view_brush_mode_badge(world: &World, ctx: &egui::Context, scene_rect: egui::Rect) {
    let Some(mode) = world.get_resource::<BrushEditMode>().copied() else {
        return;
    };
    if !mode.is_element_mode() {
        return;
    }
    let selected_count = world
        .get_resource::<BrushElementSelection>()
        .map(|selection| selection.elements.len())
        .unwrap_or(0);
    egui::Area::new(egui::Id::new("scene_view_brush_mode_badge"))
        .fixed_pos(scene_rect.left_top() + egui::vec2(8.0, 52.0))
        .interactable(false)
        .show(ctx, |ui| {
            overlay_chip_frame().show(ui, |ui| {
                ui.horizontal(|ui| {
                    ui.label(
                        egui::RichText::new(format!("Brush {}", mode.label()))
                            .color(ACCENT)
                            .strong(),
                    );
                    ui.label(
                        egui::RichText::new(format!("{selected_count} selected"))
                            .color(SELECTION)
                            .small(),
                    );
                    ui.label(egui::RichText::new("Element gizmo").color(SUCCESS).small());
                });
                ui.add_space(4.0);
                ui.horizontal_wrapped(|ui| {
                    match mode {
                        BrushEditMode::Vertex => key_hint(ui, "LMB", "Select vertex"),
                        BrushEditMode::Edge => key_hint(ui, "LMB", "Select edge"),
                        BrushEditMode::Face => key_hint(ui, "LMB", "Select face"),
                        BrushEditMode::Clip => key_hint(ui, "LMB", "Select face"),
                        BrushEditMode::Object => {}
                    }
                    if !matches!(mode, BrushEditMode::Clip) {
                        key_hint(ui, "W/E/R", "Move/rotate/scale");
                    }
                    if matches!(mode, BrushEditMode::Clip) {
                        key_hint(ui, "Enter", "Clip");
                    }
                    key_hint(ui, "Shift+LMB", "Multi");
                    key_hint(ui, "Esc", "Object");
                });
            });
        });
}

fn scene_view_brush_draw_hints(world: &World, ctx: &egui::Context, scene_rect: egui::Rect) {
    let Some(tool) = world.get_resource::<BrushToolState>() else {
        return;
    };
    if !tool.active {
        return;
    }
    let hint = match tool.phase {
        BrushToolPhase::Outline if tool.vertices.len() < 3 => "Place points".to_string(),
        BrushToolPhase::Outline => "Set height".to_string(),
        BrushToolPhase::Height => format!("{:.1}m", tool.height),
    };
    egui::Area::new(egui::Id::new("scene_view_brush_draw_hints"))
        .fixed_pos(scene_rect.left_top() + egui::vec2(8.0, 52.0))
        .interactable(false)
        .show(ctx, |ui| {
            overlay_chip_frame().show(ui, |ui| {
                ui.horizontal(|ui| {
                    ui.label(egui::RichText::new("Draw Brush").color(SELECTION).strong());
                    ui.label(
                        egui::RichText::new(format!("{} pts", tool.vertices.len()))
                            .color(TEXT_DIM)
                            .small(),
                    );
                    ui.label(egui::RichText::new(hint).color(SUCCESS).small());
                });
                ui.add_space(4.0);
                ui.horizontal_wrapped(|ui| match tool.phase {
                    BrushToolPhase::Outline => {
                        key_hint(ui, "LMB", "Point");
                        key_hint(ui, "Enter", "Height");
                        key_hint(ui, "Backspace", "Remove");
                        key_hint(ui, "Esc", "Cancel");
                        key_hint(ui, "RMB", "Cancel");
                    }
                    BrushToolPhase::Height => {
                        key_hint(ui, "Mouse Up/Down", "Height");
                        key_hint(ui, "Enter", "Create");
                        key_hint(ui, "LMB", "Create");
                        key_hint(ui, "Backspace", "Outline");
                        key_hint(ui, "Esc", "Cancel");
                        key_hint(ui, "RMB", "Cancel");
                    }
                });
            });
        });
}

fn key_hint(ui: &mut egui::Ui, key: &str, label: &str) {
    ui.horizontal(|ui| {
        ui.label(
            egui::RichText::new(key)
                .monospace()
                .color(TEXT)
                .background_color(ELEVATED_BG),
        );
        ui.label(egui::RichText::new(label).color(TEXT_DIM).small());
    });
}

fn scene_view_selection_hud(
    world: &World,
    ctx: &egui::Context,
    scene_rect: egui::Rect,
    selected_entities: &SelectedEntities,
) {
    let Some(&primary) = selected_entities.as_slice().first() else {
        return;
    };
    let name = world
        .get::<Name>(primary)
        .map(|name| name.as_str().to_string())
        .unwrap_or_else(|| "Selected entity".to_string());
    let kind = world
        .get::<shared::ActorKind>(primary)
        .map(|kind| format!("{kind:?}"))
        .unwrap_or_else(|| "Entity".to_string());
    let count = selected_entities.len();
    let selection_detail = if count > 1 {
        format!("{kind}  /  {count} selected")
    } else {
        kind
    };

    let pick_detail = world.get_resource::<ViewportPickStack>().and_then(|stack| {
        (stack.entities.len() > 1 && stack.entities.contains(&primary)).then(|| {
            format!(
                "Pick {}/{}",
                stack.index.min(stack.entities.len() - 1) + 1,
                stack.entities.len()
            )
        })
    });
    let top_offset = if scene_rect.width() < 1100.0 {
        50.0
    } else {
        10.0
    };

    egui::Area::new(egui::Id::new("scene_view_selection_hud"))
        .fixed_pos(scene_rect.right_top() + egui::vec2(-10.0, top_offset))
        .pivot(egui::Align2::RIGHT_TOP)
        .interactable(false)
        .show(ctx, |ui| {
            overlay_chip_frame().show(ui, |ui| {
                ui.horizontal(|ui| {
                    ui.label(phosphor_icon(icons::CROSSHAIR, 17.0).color(SELECTION));
                    ui.vertical(|ui| {
                        ui.set_max_width(260.0);
                        ui.add(
                            egui::Label::new(egui::RichText::new(&name).color(TEXT).strong())
                                .truncate(),
                        );
                        ui.label(
                            egui::RichText::new(&selection_detail)
                                .color(TEXT_DIM)
                                .small(),
                        );
                    });
                    if let Some(pick_detail) = pick_detail {
                        ui.separator();
                        ui.label(
                            egui::RichText::new(pick_detail)
                                .color(ACCENT)
                                .monospace()
                                .small(),
                        );
                    }
                });
            });
        });
}

fn scene_view_orientation_widget(world: &mut World, ctx: &egui::Context, scene_rect: egui::Rect) {
    let rotation = {
        let mut cameras = world.query_filtered::<(&Camera, &GlobalTransform), With<EditorCamera>>();
        let mut rotation = None;
        for (camera, transform) in cameras.iter(world) {
            rotation.get_or_insert(transform.rotation());
            if camera.is_active {
                rotation = Some(transform.rotation());
                break;
            }
        }
        rotation
    };
    let Some(rotation) = rotation else {
        return;
    };

    egui::Area::new(egui::Id::new("scene_view_orientation_widget"))
        .fixed_pos(scene_rect.right_bottom() + egui::vec2(-10.0, -48.0))
        .pivot(egui::Align2::RIGHT_BOTTOM)
        .interactable(false)
        .show(ctx, |ui| {
            overlay_chip_frame().show(ui, |ui| {
                let (rect, _) =
                    ui.allocate_exact_size(egui::vec2(72.0, 72.0), egui::Sense::hover());
                let painter = ui.painter_at(rect);
                let center = rect.center();
                painter.circle_filled(center, 4.0, egui::Color32::from_rgb(210, 214, 219));

                let view_rotation = rotation.inverse();
                let mut axes = [
                    ("X", view_rotation * Vec3::X, AXIS_X),
                    ("Y", view_rotation * Vec3::Y, AXIS_Y),
                    ("Z", view_rotation * Vec3::Z, AXIS_Z),
                ];
                axes.sort_by(|left, right| {
                    left.1
                        .z
                        .partial_cmp(&right.1.z)
                        .unwrap_or(std::cmp::Ordering::Equal)
                });
                for (label, axis, color) in axes {
                    let endpoint = center + egui::vec2(axis.x, -axis.y) * 27.0;
                    let facing = axis.z <= 0.0;
                    let color = if facing {
                        color
                    } else {
                        color.gamma_multiply(0.48)
                    };
                    painter.line_segment(
                        [center, endpoint],
                        egui::Stroke::new(if facing { 2.2 } else { 1.2 }, color),
                    );
                    painter.circle_filled(endpoint, if facing { 9.0 } else { 7.0 }, color);
                    painter.text(
                        endpoint,
                        egui::Align2::CENTER_CENTER,
                        label,
                        egui::FontId::monospace(10.0),
                        egui::Color32::from_rgb(18, 20, 22),
                    );
                }
            });
        });
}

fn scene_view_volume_hud(world: &World, ctx: &egui::Context, scene_rect: egui::Rect) {
    let Some(contributions) = world.get_resource::<settings::ActiveVolumeContribution>() else {
        return;
    };
    let Some(primary) = contributions.entries.first() else {
        return;
    };
    egui::Area::new(egui::Id::new("scene_view_volume_hud"))
        .fixed_pos(scene_rect.left_bottom() + egui::vec2(8.0, -56.0))
        .interactable(false)
        .show(ctx, |ui| {
            overlay_chip_frame().show(ui, |ui| {
                ui.horizontal(|ui| {
                    ui.label(phosphor_icon(icons::CUBE, 14.0).color(SELECTION));
                    ui.label(
                        egui::RichText::new(&primary.label)
                            .color(TEXT)
                            .strong()
                            .small(),
                    );
                    ui.label(
                        egui::RichText::new(format!(
                            "{:.0}% / {} overrides",
                            primary.weight * 100.0,
                            primary.override_count
                        ))
                        .color(TEXT_DIM)
                        .small(),
                    );
                });
            });
        });
}

fn scene_view_overlay_toolbar(world: &mut World, ctx: &egui::Context, scene_rect: egui::Rect) {
    let mut options_open = world.resource::<ViewportUiState>().options_open;
    let mut shortcuts_open = world.resource::<ViewportUiState>().shortcuts_open;

    egui::Area::new(egui::Id::new("scene_view_toolbar"))
        .fixed_pos(scene_rect.left_top() + egui::vec2(6.0, 6.0))
        .interactable(true)
        .show(ctx, |ui| {
            viewport_toolbar_frame().show(ui, |ui| {
                panel_toolbar_row(ui, |ui| {
                    let gizmo_mode = *world.resource::<crate::gizmos::EditorGizmoMode>();
                    if tool_button_accent(
                        ui,
                        icons::ARROWS_OUT_CARDINAL,
                        gizmo_mode == EditorGizmoMode::Translate,
                        "Translate (W)",
                    )
                    .clicked()
                    {
                        super::helpers::set_gizmo_mode(world, EditorGizmoMode::Translate);
                    }
                    if tool_button_accent(
                        ui,
                        icons::ARROWS_CLOCKWISE,
                        gizmo_mode == EditorGizmoMode::Rotate,
                        "Rotate (E)",
                    )
                    .clicked()
                    {
                        super::helpers::set_gizmo_mode(world, EditorGizmoMode::Rotate);
                    }
                    if tool_button_accent(
                        ui,
                        icons::ARROWS_IN_LINE_HORIZONTAL,
                        gizmo_mode == EditorGizmoMode::Scale,
                        "Scale (R)",
                    )
                    .clicked()
                    {
                        super::helpers::set_gizmo_mode(world, EditorGizmoMode::Scale);
                    }

                    ui.separator();

                    let space = *world.resource::<EditorGizmoSpace>();
                    if tool_button(
                        ui,
                        icons::GLOBE,
                        space == EditorGizmoSpace::World,
                        "World space (X)",
                    )
                    .clicked()
                    {
                        super::helpers::set_gizmo_space(world, EditorGizmoSpace::World);
                    }
                    if tool_button(
                        ui,
                        icons::CUBE,
                        space == EditorGizmoSpace::Local,
                        "Local space (X)",
                    )
                    .clicked()
                    {
                        super::helpers::set_gizmo_space(world, EditorGizmoSpace::Local);
                    }

                    ui.separator();

                    let viewport_mode = *world.resource::<EditorViewportMode>();
                    if tool_button(
                        ui,
                        icons::SUN,
                        viewport_mode == EditorViewportMode::Lit,
                        "Lit shading (WYSIWYG)",
                    )
                    .clicked()
                    {
                        *world.resource_mut::<EditorViewportMode>() = EditorViewportMode::Lit;
                    }
                    if tool_button(
                        ui,
                        icons::PAINT_BRUSH,
                        viewport_mode == EditorViewportMode::Unlit,
                        "Unlit base color (debug albedo)",
                    )
                    .clicked()
                    {
                        *world.resource_mut::<EditorViewportMode>() = EditorViewportMode::Unlit;
                    }
                    if tool_button(
                        ui,
                        icons::BOUNDING_BOX,
                        viewport_mode == EditorViewportMode::Collider,
                        "Collider view (hide meshes)",
                    )
                    .clicked()
                    {
                        *world.resource_mut::<EditorViewportMode>() = EditorViewportMode::Collider;
                    }

                    ui.separator();

                    if let Some(mut settings) = world.get_resource_mut::<ViewportSettings>() {
                        let grid = settings.show_grid;
                        if tool_button(ui, icons::GRID_FOUR, grid, "Toggle grid (Ctrl+G)").clicked()
                        {
                            settings.show_grid = !settings.show_grid;
                        }
                        let snap = settings.snap_translate;
                        if tool_button(ui, icons::MAGNET, snap, "Snap translation").clicked() {
                            settings.snap_translate = !settings.snap_translate;
                        }
                        if settings.snap_translate {
                            ui.label(
                                egui::RichText::new(format!("{:.2}m", settings.snap_increment))
                                    .color(SELECTION)
                                    .monospace()
                                    .small(),
                            );
                        }
                    }

                    if let Some(mut visualizers) =
                        world.get_resource_mut::<crate::visualizers::EditorVisualizationSettings>()
                    {
                        let enabled = visualizers.enabled;
                        if tool_button(ui, icons::EYE, enabled, "Toggle scene visualizers")
                            .clicked()
                        {
                            visualizers.enabled = !visualizers.enabled;
                        }
                    }

                    let clean_game_view = world.resource::<ViewportDisplayMode>().clean_game_view;
                    if tool_button(
                        ui,
                        icons::GAME_CONTROLLER,
                        clean_game_view,
                        "Game view overlay (G)",
                    )
                    .clicked()
                    {
                        world.resource_mut::<ViewportDisplayMode>().clean_game_view =
                            !clean_game_view;
                    }

                    if icon_button_small(ui, icons::CROSSHAIR, "Focus selection (F)").clicked() {
                        focus_editor_camera_on_selection(world);
                    }

                    if icon_button_small(ui, icons::GEAR, "Viewport options").clicked() {
                        options_open = !options_open;
                    }
                    if icon_button_small(ui, icons::QUESTION, VIEWPORT_TOOLTIP).clicked() {
                        shortcuts_open = !shortcuts_open;
                    }
                });
            });
        });

    if options_open {
        scene_options_popover(world, ctx, scene_rect, &mut options_open);
    }
    if shortcuts_open {
        viewport_shortcuts_window(ctx, &mut shortcuts_open);
    }

    world.resource_mut::<ViewportUiState>().options_open = options_open;
    world.resource_mut::<ViewportUiState>().shortcuts_open = shortcuts_open;
}

fn scene_options_popover(
    world: &mut World,
    ctx: &egui::Context,
    scene_rect: egui::Rect,
    open: &mut bool,
) {
    egui::Area::new(egui::Id::new("scene_view_options"))
        .fixed_pos(scene_rect.left_top() + egui::vec2(6.0, 44.0))
        .show(ctx, |ui| {
            viewport_toolbar_frame().show(ui, |ui| {
                ui.set_min_width(220.0);
                ui.heading("Viewport Options");
                if let Some(mut display) = world.get_resource_mut::<ViewportDisplayMode>() {
                    ui.checkbox(&mut display.clean_game_view, "Game view overlay");
                    ui.small("G toggles game view; Ctrl+G toggles grid");
                }
                if let Some(mut settings) = world.get_resource_mut::<ViewportSettings>() {
                    ui.separator();
                    ui.checkbox(&mut settings.show_grid, "Show grid");
                    ui.checkbox(&mut settings.snap_translate, "Snap translation");
                    ui.add(
                        egui::Slider::new(&mut settings.snap_increment, 0.25..=4.0)
                            .text("Snap step")
                            .suffix(" m"),
                    );
                }
                if let Some(mut gizmo_options) = world.get_resource_mut::<GizmoOptions>() {
                    ui.separator();
                    ui.add(
                        egui::Slider::new(&mut gizmo_options.visuals.gizmo_size, 40.0..=160.0)
                            .text("Gizmo size"),
                    );
                }
                if let Some(mut mode) = world.get_resource_mut::<EditorViewportMode>() {
                    ui.separator();
                    ui.label("Shading");
                    ui.horizontal(|ui| {
                        ui.selectable_value(&mut *mode, EditorViewportMode::Lit, "Lit");
                        ui.selectable_value(&mut *mode, EditorViewportMode::Unlit, "Unlit");
                        ui.selectable_value(&mut *mode, EditorViewportMode::Collider, "Colliders");
                    });
                }
                let mut actor_icons_active = false;
                if let Some(mut visualizers) =
                    world.get_resource_mut::<crate::visualizers::EditorVisualizationSettings>()
                {
                    ui.separator();
                    ui.checkbox(&mut visualizers.enabled, "Show visualizers");
                    ui.add_enabled_ui(visualizers.enabled, |ui| {
                        ui.checkbox(&mut visualizers.show_actor_icons, "Actor root icons");
                        ui.add_enabled_ui(visualizers.show_actor_icons, |ui| {
                            ui.horizontal_wrapped(|ui| {
                                ui.checkbox(&mut visualizers.show_actor_icon_actor, "Actors");
                                ui.checkbox(&mut visualizers.show_actor_icon_mesh, "Meshes");
                                ui.checkbox(&mut visualizers.show_actor_icon_light, "Lights");
                                ui.checkbox(&mut visualizers.show_actor_icon_audio, "Audio");
                                ui.checkbox(&mut visualizers.show_actor_icon_physics, "Physics");
                                ui.checkbox(&mut visualizers.show_actor_icon_gameplay, "Gameplay");
                                ui.checkbox(&mut visualizers.show_actor_icon_volume, "Volumes");
                                ui.checkbox(
                                    &mut visualizers.show_actor_icon_prefab_model,
                                    "Prefabs/models",
                                );
                                ui.checkbox(&mut visualizers.show_actor_icon_debug, "Warnings");
                            });
                        });
                        actor_icons_active = visualizers.enabled && visualizers.show_actor_icons;
                        ui.separator();
                        ui.checkbox(&mut visualizers.show_colliders, "Colliders");
                        ui.checkbox(&mut visualizers.show_lights, "Lights");
                        ui.checkbox(&mut visualizers.show_audio, "Audio ranges");
                        ui.checkbox(&mut visualizers.show_spawns, "Player spawns");
                        ui.checkbox(&mut visualizers.show_gameplay, "Gameplay markers");
                        ui.checkbox(&mut visualizers.show_volumes, "Volumes");
                        ui.checkbox(
                            &mut visualizers.show_prefab_model_anchors,
                            "Prefab/model anchors",
                        );
                        ui.checkbox(&mut visualizers.show_runtime, "Runtime player/cameras");
                        ui.checkbox(&mut visualizers.pickable_proxies, "Selectable proxies");
                    });
                }
                if actor_icons_active {
                    if let Some(mut icon_settings) = world.get_resource_mut::<ActorIconSettings>() {
                        ui.add(
                            egui::Slider::new(
                                &mut icon_settings.size_pixels,
                                ActorIconSettings::MIN_SIZE_PIXELS
                                    ..=ActorIconSettings::MAX_SIZE_PIXELS,
                            )
                            .text("Icon size"),
                        );
                    }
                }
                ui.horizontal(|ui| {
                    if ui.button("Save bookmark").clicked() {
                        viewport_save_bookmark(world);
                    }
                    if ui.button("Recall bookmark").clicked() {
                        viewport_recall_bookmark(world);
                    }
                });
                ui.small("Ctrl+Shift+1 save | Ctrl+Shift+2 recall");
                if ui.button("Close").clicked() {
                    *open = false;
                }
            });
        });
}

fn viewport_shortcuts_window(ctx: &egui::Context, open: &mut bool) {
    egui::Window::new("Viewport Shortcuts")
        .open(open)
        .default_width(320.0)
        .show(ctx, |ui| {
            ui.label("Viewport navigation");
            ui.small("RMB + WASD/QE - fly camera");
            ui.small("MMB drag - pan");
            ui.small("Scroll - dolly");
            ui.separator();
            ui.label("Tools");
            ui.small("W / E / R - translate / rotate / scale gizmo");
            ui.small("X - toggle world / local space");
            ui.small("G - toggle game view overlay");
            ui.small("Ctrl+G - toggle grid");
            ui.small("F - focus selection");
            ui.small("Tab - cycle overlapping selection at click");
            ui.small("F5 - play / stop (main toolbar)");
            ui.small("F6 - pause / resume (while playing)");
            ui.small("F8 - possess / eject (while playing)");
            ui.small("Esc - deselect / free cursor");
        });
}

fn game_view_overlay(world: &World, ctx: &egui::Context, rect: egui::Rect, playing: bool) {
    egui::Area::new(egui::Id::new("game_view_hud"))
        .fixed_pos(rect.left_top() + egui::vec2(6.0, 6.0))
        .interactable(false)
        .show(ctx, |ui| {
            viewport_toolbar_frame().show(ui, |ui| {
                if !playing {
                    ui.label(egui::RichText::new("Game View").strong());
                    ui.small("Press F5 or Play to run here.");
                    return;
                }

                ui.label(egui::RichText::new("Playing").strong());
                let possession = *world.resource::<PlayPossession>();
                let possess_label = match possession {
                    PlayPossession::Possessed => "Possessed",
                    PlayPossession::Ejected => "Ejected (Scene camera)",
                };
                ui.small(possess_label);
                ui.small("F5 stop | F6 pause | F8 possess/eject | Esc free cursor");
            });
        });
}

fn viewport_asset_drop_ui(
    world: &mut World,
    ui: &mut egui::Ui,
    viewport_rect: egui::Rect,
    selected: &SelectedEntities,
) {
    let pointer_pos = ui.input(|input| input.pointer.hover_pos());
    let drag_selection = world
        .resource::<EditorAssets>()
        .dragging_selection()
        .cloned();
    let surface_drag = drag_selection
        .as_ref()
        .is_some_and(|selection| is_surface_asset_selection(world, selection));
    let mut drag_descriptor = drag_selection
        .as_ref()
        .and_then(|selection| asset_drag_descriptor(world, selection, selected));
    let over_viewport = pointer_pos.is_some_and(|pos| viewport_rect.contains(pos));
    let surface_feedback = surface_drag
        .then(|| world.get_resource::<MaterialDropState>())
        .flatten()
        .and_then(MaterialDropState::feedback)
        .cloned();
    if surface_drag && over_viewport {
        if let Some((_, _, action)) = drag_descriptor.as_mut() {
            *action = surface_feedback
                .as_ref()
                .map(|feedback| feedback.action.clone())
                .unwrap_or_else(|| "Finding an assignable surface".into());
        }
    }
    let drop_valid = (surface_drag && over_viewport)
        .then(|| surface_feedback.as_ref().map(|feedback| feedback.valid))
        .flatten();
    let highlight_color = match drop_valid {
        Some(true) => SUCCESS,
        Some(false) => ERROR,
        None => ACCENT_HOVER,
    };

    let drag_visual_state = if !over_viewport {
        AssetDragVisualState::Outside
    } else {
        match drop_valid {
            Some(true) => AssetDragVisualState::Valid,
            Some(false) => AssetDragVisualState::Invalid,
            None => AssetDragVisualState::Targeted,
        }
    };
    if let (Some(pointer), Some((icon, label, action))) = (pointer_pos, drag_descriptor.as_ref()) {
        draw_asset_drag_preview(
            ui,
            viewport_rect,
            pointer,
            *icon,
            label,
            action,
            drag_visual_state,
        );
    }

    if drag_selection.is_some() && over_viewport {
        ui.painter().rect_stroke(
            viewport_rect.shrink(2.0),
            0.0,
            egui::Stroke::new(2.0, highlight_color),
            egui::StrokeKind::Inside,
        );
    }

    let pointer_released = ui.input(|input| input.pointer.any_released());
    if !pointer_released {
        return;
    }

    if surface_drag && over_viewport {
        // The Last-schedule surface-drop session owns exact hit resolution and commit.
        return;
    }

    if !pointer_pos.is_some_and(|pos| viewport_rect.contains(pos)) {
        world.resource_mut::<EditorAssets>().clear_drag();
        return;
    }

    world.resource_mut::<EditorAssets>().clear_drag();

    let Some(selection) = drag_selection else {
        return;
    };
    let placement = viewport_ground_position(world, pointer_pos.unwrap(), viewport_rect)
        .map(|pos| snap_translation(pos, world.resource::<ViewportSettings>()))
        .unwrap_or(Vec3::ZERO);
    if matches!(
        selection,
        AssetSelection::SubAsset {
            kind: AssetSubAssetKind::AnimationClip,
            ..
        }
    ) {
        if let Some(entity) = selected
            .as_slice()
            .iter()
            .copied()
            .find(|entity| world.get::<shared::SkinnedMeshRenderer>(*entity).is_some())
        {
            assign_animation_clip_operator(world, selection, entity);
        } else {
            place_subasset_operator(world, selection, placement);
        }
        return;
    }
    if matches!(
        selection,
        AssetSelection::SubAsset {
            kind: AssetSubAssetKind::Mesh,
            ..
        }
    ) {
        place_subasset_operator(world, selection, placement);
        return;
    }
    if matches!(
        selection,
        AssetSelection::SubAsset {
            kind: AssetSubAssetKind::Skeleton,
            ..
        }
    ) {
        return;
    }

    let asset = {
        let assets = world.resource::<EditorAssets>();
        assets.asset_for_selection(&selection).cloned()
    };
    let Some(asset) = asset else {
        return;
    };
    if matches!(asset.kind, crate::assets::EditorAssetKind::AudioClip) {
        if let Some(entity) = selected
            .as_slice()
            .iter()
            .copied()
            .find(|entity| world.get::<shared::AudioSourceDesc>(*entity).is_some())
        {
            assign_audio_clip_operator(world, asset, entity);
        } else {
            place_asset_operator(world, asset, placement);
        }
        return;
    }
    place_asset_operator(world, asset, placement);
}

fn asset_drag_descriptor(
    world: &World,
    selection: &AssetSelection,
    selected: &SelectedEntities,
) -> Option<(egui_phosphor_icons::Icon, String, String)> {
    let assets = world.resource::<EditorAssets>();
    let asset = assets.asset_for_selection(selection)?;
    let icon = match selection {
        AssetSelection::SubAsset {
            kind: AssetSubAssetKind::Mesh,
            ..
        } => icons::CUBE,
        AssetSelection::SubAsset {
            kind: AssetSubAssetKind::Material,
            ..
        } => icons::PAINT_BRUSH,
        AssetSelection::SubAsset {
            kind: AssetSubAssetKind::Texture,
            ..
        } => icons::IMAGE,
        AssetSelection::SubAsset {
            kind: AssetSubAssetKind::Skeleton,
            ..
        } => icons::BONE,
        AssetSelection::SubAsset {
            kind: AssetSubAssetKind::AnimationClip,
            ..
        } => icons::FILM_STRIP,
        _ => kind_icon(&asset.kind),
    };
    let label = match selection {
        AssetSelection::SubAsset { label, .. } => label.clone(),
        _ => asset.label.clone(),
    };
    let action = match selection {
        AssetSelection::SubAsset {
            kind: AssetSubAssetKind::Texture,
            ..
        } => "Drop onto a primitive or brush face",
        AssetSelection::SubAsset {
            kind: AssetSubAssetKind::Material,
            ..
        } => "Drop onto an exact renderer slot",
        AssetSelection::SubAsset {
            kind: AssetSubAssetKind::Skeleton,
            ..
        } => "Inspect skeleton",
        AssetSelection::SubAsset {
            kind: AssetSubAssetKind::AnimationClip,
            ..
        } if selected
            .as_slice()
            .iter()
            .any(|entity| world.get::<shared::SkinnedMeshRenderer>(*entity).is_some()) =>
        {
            "Assign clip to animated actor"
        }
        AssetSelection::SubAsset {
            kind: AssetSubAssetKind::AnimationClip,
            ..
        } => "Create animated actor",
        _ => match asset.kind {
            crate::assets::EditorAssetKind::Texture => "Drop onto a primitive or brush face",
            crate::assets::EditorAssetKind::Material => "Drop onto a renderable surface",
            crate::assets::EditorAssetKind::AudioClip
                if selected
                    .as_slice()
                    .iter()
                    .any(|entity| world.get::<shared::AudioSourceDesc>(*entity).is_some()) =>
            {
                "Assign clip to audio source"
            }
            crate::assets::EditorAssetKind::AudioClip => "Create audio source",
            _ => "Place in scene",
        },
    };
    Some((icon, label, action.to_string()))
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum AssetDragVisualState {
    Outside,
    Targeted,
    Valid,
    Invalid,
}

impl AssetDragVisualState {
    fn over_viewport(self) -> bool {
        self != Self::Outside
    }

    fn color(self) -> egui::Color32 {
        match self {
            Self::Outside => TEXT_DIM,
            Self::Targeted => ACCENT_HOVER,
            Self::Valid => SUCCESS,
            Self::Invalid => ERROR,
        }
    }
}

fn draw_asset_drag_preview(
    ui: &egui::Ui,
    viewport_rect: egui::Rect,
    pointer: egui::Pos2,
    icon: egui_phosphor_icons::Icon,
    label: &str,
    action: &str,
    visual_state: AssetDragVisualState,
) {
    let over_viewport = visual_state.over_viewport();
    let highlight_color = visual_state.color();
    if over_viewport {
        let painter = ui.painter();
        painter.circle_filled(pointer, 3.0, highlight_color);
        painter.circle_stroke(pointer, 11.0, egui::Stroke::new(1.5, highlight_color));
        painter.line_segment(
            [
                pointer - egui::vec2(17.0, 0.0),
                pointer - egui::vec2(7.0, 0.0),
            ],
            egui::Stroke::new(1.5, highlight_color),
        );
        painter.line_segment(
            [
                pointer + egui::vec2(7.0, 0.0),
                pointer + egui::vec2(17.0, 0.0),
            ],
            egui::Stroke::new(1.5, highlight_color),
        );
        painter.line_segment(
            [
                pointer - egui::vec2(0.0, 17.0),
                pointer - egui::vec2(0.0, 7.0),
            ],
            egui::Stroke::new(1.5, highlight_color),
        );
        painter.line_segment(
            [
                pointer + egui::vec2(0.0, 7.0),
                pointer + egui::vec2(0.0, 17.0),
            ],
            egui::Stroke::new(1.5, highlight_color),
        );
    }

    let content = ui.ctx().content_rect();
    let pos = egui::pos2(
        (pointer.x + 20.0).min(content.right() - 258.0),
        (pointer.y + 20.0).min(content.bottom() - 64.0),
    );
    egui::Area::new(egui::Id::new("asset_drag_preview"))
        .fixed_pos(pos)
        .default_size(egui::vec2(246.0, 60.0))
        .order(egui::Order::Tooltip)
        .interactable(false)
        .show(ui.ctx(), |ui| {
            ui.set_min_size(egui::vec2(246.0, 60.0));
            ui.set_max_size(egui::vec2(246.0, 60.0));
            overlay_chip_frame().show(ui, |ui| {
                ui.set_width(228.0);
                ui.horizontal(|ui| {
                    ui.label(phosphor_icon(icon, 20.0).color(if over_viewport {
                        highlight_color
                    } else {
                        TEXT_DIM
                    }));
                    ui.vertical(|ui| {
                        ui.spacing_mut().item_spacing.y = 1.0;
                        ui.label(egui::RichText::new(label).strong().color(TEXT));
                        ui.label(
                            egui::RichText::new(if over_viewport {
                                action
                            } else {
                                "Drag into the viewport"
                            })
                            .size(11.0)
                            .color(if over_viewport {
                                highlight_color
                            } else {
                                TEXT_MUTED
                            }),
                        );
                    });
                });
            });
        });

    if over_viewport {
        let corner = 16.0;
        let inset = viewport_rect.shrink(8.0);
        for (a, b, c) in [
            (
                inset.left_top(),
                egui::vec2(corner, 0.0),
                egui::vec2(0.0, corner),
            ),
            (
                inset.right_top(),
                egui::vec2(-corner, 0.0),
                egui::vec2(0.0, corner),
            ),
            (
                inset.left_bottom(),
                egui::vec2(corner, 0.0),
                egui::vec2(0.0, -corner),
            ),
            (
                inset.right_bottom(),
                egui::vec2(-corner, 0.0),
                egui::vec2(0.0, -corner),
            ),
        ] {
            ui.painter()
                .line_segment([a, a + b], egui::Stroke::new(3.0, highlight_color));
            ui.painter()
                .line_segment([a, a + c], egui::Stroke::new(3.0, highlight_color));
        }
    }
}
