#![windows_subsystem = "windows"]

extern crate alloc;

mod events;
mod views;
mod widgets;

use events::*;
use nightshade::prelude::*;
use views::*;
use widgets::Pane;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    launch(Editor::default())?;
    Ok(())
}

#[derive(Default)]
struct Editor {
    context: EditorContext,
    inspector_ui: ComponentInspectorUi,
}

impl State for Editor {
    fn title(&self) -> &str {
        "Nightshade Editor"
    }

    fn initialize(&mut self, world: &mut World) {
        world.resources.user_interface.enabled = true;
        world.resources.graphics.show_grid = true;
        world.resources.graphics.atmosphere = Atmosphere::Sky;

        self.context.show_left_panel = true;
        self.context.show_right_panel = true;

        let camera_position = Vec3::new(0.0, 4.0, 10.0);
        let main_camera = spawn_camera(world, camera_position, "Main Camera".to_string());
        world.resources.active_camera = Some(main_camera);

        spawn_sun(world);

        let mut tiles = egui_tiles::Tiles::default();
        let viewport = tiles.insert_pane(Pane::camera(0));

        self.context.tile_tree = Some(egui_tiles::Tree::new("tile_tree", viewport, tiles));
    }

    fn ui(&mut self, world: &mut World, ui_context: &egui::Context) {
        self.top_panel_ui(world, ui_context);

        if self.context.show_left_panel {
            self.left_panel_ui(world, ui_context);
        }

        if self.context.show_right_panel {
            self.right_panel_ui(world, ui_context);
        }

        self.context.tile_behavior.viewport_rects.clear();
        self.context.tile_behavior.viewport_textures =
            world.resources.user_interface.viewport_textures.clone();

        let mut required_cameras: Vec<Entity> = Vec::new();
        if let Some(tree) = &self.context.tile_tree {
            for (_tile_id, tile) in tree.tiles.iter() {
                if let egui_tiles::Tile::Pane(pane) = tile
                    && let Some(camera_entity) = pane.widget.required_camera(world)
                    && !required_cameras.contains(&camera_entity)
                {
                    required_cameras.push(camera_entity);
                }
            }
        }
        world.resources.user_interface.required_cameras = required_cameras;

        self.context.tile_behavior.world = Some(world as *mut World);

        let mut tree = self.context.tile_tree.take();

        egui::CentralPanel::default()
            .frame(egui::Frame::NONE)
            .show(ui_context, |ui| {
                if let Some(tree) = &mut tree {
                    tree.ui(&mut self.context.tile_behavior, ui);

                    if let Some((tile_id, widget_kind)) =
                        self.context.tile_behavior.add_widget_to.take()
                    {
                        let new_pane = Pane::new(widget_kind);
                        let new_tile_id = tree.tiles.insert_pane(new_pane);

                        if let Some(egui_tiles::Tile::Container(egui_tiles::Container::Tabs(
                            tabs,
                        ))) = tree.tiles.get_mut(tile_id)
                        {
                            tabs.add_child(new_tile_id);
                        }
                    }
                }
            });

        self.context.tile_behavior.world = None;
        self.context.tile_tree = tree;

        if self.context.hovered_file_path.is_some() {
            self.drop_indicator_ui(ui_context);
        }
    }

    fn run_systems(&mut self, world: &mut World) {
        fly_camera_system(world);
        nightshade::ecs::text::systems::sync_text_meshes_system(world);
        let delta_time = world.resources.window.timing.delta_time;
        update_animation_players(world, delta_time);
        apply_animations(world);
        self.update_gizmo(world);
    }

    fn update_render_graph(&mut self, graph: &mut RenderGraph<World>, _world: &World) {
        let _ = graph.set_pass_enabled("compute_grayscale", self.context.grayscale_enabled);
    }

    fn handle_event(&mut self, world: &mut World, message: &nightshade::prelude::Message) {
        match message {
            nightshade::prelude::Message::Input { event } => {
                log::debug!("Input event: {:?}", event);
            }
            nightshade::prelude::Message::App { type_name, payload } => {
                if let Some(event) = payload.downcast_ref::<EditorEvent>() {
                    self.handle_editor_event(world, event);
                } else {
                    log::warn!("Received unknown app event type: {}", type_name);
                }
            }
        }
    }

    fn on_keyboard_input(&mut self, world: &mut World, key_code: KeyCode, key_state: KeyState) {
        if !matches!((key_code, key_state), (KeyCode::KeyQ, KeyState::Pressed)) {
            return;
        }
        world.resources.window.should_exit = true;
    }

    fn configure_render_graph(
        &mut self,
        graph: &mut RenderGraph<World>,
        device: &wgpu::Device,
        surface_format: wgpu::TextureFormat,
        resources: RenderResources,
    ) {
        let compute_grayscale_pass = passes::ComputeGrayscalePass::new(device);
        let _ = graph.add_pass(
            Box::new(compute_grayscale_pass),
            &[
                ("input", resources.scene_color),
                ("output", resources.compute_output),
            ],
        );

        let blit_pass = passes::BlitPass::new(device, surface_format);
        let _ = graph.add_pass(
            Box::new(blit_pass),
            &[
                ("input", resources.compute_output),
                ("output", resources.swapchain),
            ],
        );
    }

    fn on_dropped_file(&mut self, world: &mut World, path: &std::path::Path) {
        log::info!("Dropped file: {:?}", path);
        if let Some(extension) = path.extension() {
            let ext = extension.to_string_lossy().to_lowercase();
            if ext == "hdr" {
                load_hdr_skybox_from_path(world, path.to_path_buf());
            } else if ext == "gltf" || ext == "glb" {
                self.load_gltf(world, path);
            } else {
                log::warn!("Unsupported file type: {}", ext);
            }
        }
        self.context.hovered_file_path = None;
        self.context.drag_file_type = None;
    }

    fn on_hovered_file(&mut self, _world: &mut World, path: &std::path::Path) {
        log::info!("Hovered file: {:?}", path);
        self.context.hovered_file_path = Some(path.to_path_buf());
        if let Some(extension) = path.extension() {
            let ext = extension.to_string_lossy().to_lowercase();
            if ext == "hdr" {
                self.context.drag_file_type = Some("hdr".to_string());
            } else if ext == "gltf" || ext == "glb" {
                self.context.drag_file_type = Some("gltf".to_string());
            } else {
                self.context.drag_file_type = None;
            }
        } else {
            self.context.drag_file_type = None;
        }
    }

    fn on_hovered_file_cancelled(&mut self, _world: &mut World) {
        log::info!("Hovered file cancelled");
        self.context.hovered_file_path = None;
        self.context.drag_file_type = None;
    }

    fn on_dropped_file_data(&mut self, world: &mut World, name: &str, data: &[u8]) {
        log::info!("Dropped file data: {} ({} bytes)", name, data.len());
        let lower_name = name.to_lowercase();
        if lower_name.ends_with(".gltf") || lower_name.ends_with(".glb") {
            self.load_gltf_from_bytes(world, name, data);
        } else {
            log::warn!("Unsupported file type: {}", name);
        }
    }
}

impl Editor {
    fn left_panel_ui(&mut self, world: &mut World, ui_context: &egui::Context) {
        egui::SidePanel::left("left_panel")
            .default_width(200.0)
            .show(ui_context, |ui| {
                egui::ScrollArea::vertical()
                    .id_salt("left_panel_scroll")
                    .show(ui, |ui| {
                        ui.collapsing("Scene Tree", |ui| {
                            WorldTreeUi::ui_with_pointers(
                                world as *mut World,
                                &mut self.context.selected_entity as *mut Option<Entity>,
                                ui,
                            );
                        });
                        ui.collapsing("Prefabs", |ui| {
                            if self.context.tile_behavior.prefabs.is_empty() {
                                ui.label("No prefabs loaded");
                                ui.label("Drop .gltf/.glb files or use Import menu");
                            } else {
                                let mut prefab_to_remove = None;
                                for (index, prefab_metadata) in
                                    self.context.tile_behavior.prefabs.iter().enumerate()
                                {
                                    ui.horizontal(|ui| {
                                        let label = if let Some(source) =
                                            &prefab_metadata.source_path
                                        {
                                            format!("{} ({})", prefab_metadata.prefab.name, source)
                                        } else {
                                            prefab_metadata.prefab.name.clone()
                                        };

                                        if ui.button(&label).clicked() {
                                            spawn_prefab_with_animations(
                                                world,
                                                &prefab_metadata.prefab,
                                                &prefab_metadata.animations,
                                                nalgebra_glm::vec3(0.0, 0.0, 0.0),
                                            );
                                        }
                                        if ui.small_button("x").clicked() {
                                            prefab_to_remove = Some(index);
                                        }
                                    });
                                }
                                if let Some(index) = prefab_to_remove {
                                    self.context.tile_behavior.prefabs.remove(index);
                                }
                            }
                        });
                    });
            });
    }

    fn right_panel_ui(&mut self, world: &mut World, ui_context: &egui::Context) {
        egui::SidePanel::right("right_panel")
            .default_width(250.0)
            .show(ui_context, |ui| {
                ui.heading("Inspector");
                ui.separator();
                egui::ScrollArea::vertical()
                    .id_salt("inspector_scroll")
                    .show(ui, |ui| {
                        ComponentInspectorUi::ui(
                            &mut self.inspector_ui,
                            &mut self.context,
                            world,
                            ui,
                        );
                    });
            });
    }

    fn handle_editor_event(&mut self, _world: &mut World, event: &EditorEvent) {
        match event {
            EditorEvent::EntitySelected(entity) => {
                log::info!("Entity selected: {:?}", entity);
                self.context.selected_entity = Some(*entity);
            }
            EditorEvent::EntityDeselected => {
                log::info!("Entity deselected");
                self.context.selected_entity = None;
            }
            EditorEvent::PanelToggled { panel } => {
                log::info!("Panel toggled: {:?}", panel);
            }
        }
    }

    fn top_panel_ui(&mut self, world: &mut World, ui_context: &egui::Context) {
        egui::TopBottomPanel::top("top").show(ui_context, |ui| {
            ui.horizontal(|ui| {
                egui::MenuBar::new().ui(ui, |ui| {
                    ui.menu_button("View", |ui| {
                        let left_panel_before = self.context.show_left_panel;
                        let right_panel_before = self.context.show_right_panel;

                        ui.checkbox(&mut self.context.show_left_panel, "Left Panel");
                        ui.checkbox(&mut self.context.show_right_panel, "Right Panel");

                        ui.separator();
                        ui.checkbox(&mut world.resources.graphics.show_grid, "Grid");

                        ui.separator();
                        ui.label("Render Layers:");
                        ui.checkbox(
                            &mut world.resources.graphics.render_layer_world_enabled,
                            "World Layer",
                        );
                        ui.checkbox(
                            &mut world.resources.graphics.render_layer_overlay_enabled,
                            "Overlay Layer",
                        );

                        ui.separator();
                        ui.checkbox(&mut self.context.grayscale_enabled, "Grayscale");

                        if self.context.show_left_panel != left_panel_before {
                            publish_app_event(
                                world,
                                EditorEvent::PanelToggled { panel: Panel::Left },
                            );
                        }
                        if self.context.show_right_panel != right_panel_before {
                            publish_app_event(
                                world,
                                EditorEvent::PanelToggled {
                                    panel: Panel::Right,
                                },
                            );
                        }
                    });
                    ui.separator();
                    ui.label("Atmosphere:");
                    egui::ComboBox::from_id_salt("atmosphere_combo")
                        .selected_text(format!("{:?}", world.resources.graphics.atmosphere))
                        .show_ui(ui, |ui| {
                            ui.selectable_value(
                                &mut world.resources.graphics.atmosphere,
                                Atmosphere::None,
                                "None",
                            );
                            ui.selectable_value(
                                &mut world.resources.graphics.atmosphere,
                                Atmosphere::Sky,
                                "Sky",
                            );
                            ui.selectable_value(
                                &mut world.resources.graphics.atmosphere,
                                Atmosphere::CloudySky,
                                "Cloudy Sky",
                            );
                            ui.selectable_value(
                                &mut world.resources.graphics.atmosphere,
                                Atmosphere::Space,
                                "Space",
                            );
                            ui.selectable_value(
                                &mut world.resources.graphics.atmosphere,
                                Atmosphere::Nebula,
                                "Nebula",
                            );
                            ui.selectable_value(
                                &mut world.resources.graphics.atmosphere,
                                Atmosphere::Sunset,
                                "Sunset",
                            );
                            ui.selectable_value(
                                &mut world.resources.graphics.atmosphere,
                                Atmosphere::Hdr,
                                "HDR",
                            );
                        });
                    ui.separator();
                    ui.menu_button("Tools", |ui| {
                        if ui.button("Clear Scene").clicked() {
                            self.clear_scene(world);
                            ui.close();
                        }

                        ui.separator();

                        let mut cameras: Vec<Entity> = world.query_entities(CAMERA).collect();
                        cameras.sort_by_key(|entity| entity.id);
                        let camera_count = cameras.len();
                        if ui
                            .add_enabled(
                                camera_count > 0,
                                egui::Button::new(format!("Show All Cameras ({})", camera_count)),
                            )
                            .clicked()
                        {
                            let mut tiles = egui_tiles::Tiles::default();
                            let viewport_ids: Vec<_> = cameras
                                .iter()
                                .enumerate()
                                .map(|(index, _)| tiles.insert_pane(Pane::camera(index)))
                                .collect();

                            let root = if viewport_ids.len() == 1 {
                                viewport_ids[0]
                            } else {
                                tiles.insert_grid_tile(viewport_ids)
                            };

                            self.context.tile_tree =
                                Some(egui_tiles::Tree::new("tile_tree", root, tiles));
                            ui.close();
                        }

                        ui.separator();

                        ui.menu_button("Create", |ui| {
                            ui.set_min_width(150.0);

                            ui.label("Lighting");
                            if ui.button("Sun").clicked() {
                                spawn_sun(world);
                                ui.close();
                            }
                            ui.separator();

                            ui.label("Primitives");
                            if ui.button("Cube").clicked() {
                                spawn_cube_at(world, Vec3::new(0.0, 0.0, 0.0));
                                ui.close();
                            }
                            if ui.button("Sphere").clicked() {
                                spawn_sphere_at(world, Vec3::new(0.0, 0.0, 0.0));
                                ui.close();
                            }
                            if ui.button("Cylinder").clicked() {
                                spawn_cylinder_at(world, Vec3::new(0.0, 0.0, 0.0));
                                ui.close();
                            }
                            if ui.button("Torus").clicked() {
                                spawn_torus_at(world, Vec3::new(0.0, 0.0, 0.0));
                                ui.close();
                            }
                            if ui.button("Cone").clicked() {
                                spawn_cone_at(world, Vec3::new(0.0, 0.0, 0.0));
                                ui.close();
                            }
                            if ui.button("Plane").clicked() {
                                spawn_plane_at(world, Vec3::new(0.0, 0.0, 0.0));
                                ui.close();
                            }
                        });

                        ui.separator();
                        ui.label("Tests");
                        ui.separator();

                        ui.label("Line Scaling Test:");
                        if ui.button("Spawn 20,000 Lines").clicked() {
                            spawn_lines(world);
                        }
                        ui.separator();
                        ui.label("Mesh Scaling Test:");
                        if ui.button("Spawn 10,000 Meshes").clicked() {
                            spawn_meshes(world);
                        }
                        ui.separator();
                        ui.label("Text Test:");
                        if ui.button("Spawn 3D Text").clicked() {
                            spawn_3d_text(world);
                        }
                        if ui.button("Spawn 5K Text Lattice (Stress Test)").clicked() {
                            spawn_text_lattice(world);
                        }
                    });
                    ui.separator();
                });

                if self.context.selected_entity.is_some() {
                    ui.separator();
                    ui.label("Gizmo Mode:");
                    let old_mode = self.context.gizmo_mode;
                    egui::ComboBox::from_label("")
                        .selected_text(format!("{:?}", self.context.gizmo_mode))
                        .show_ui(ui, |ui| {
                            ui.selectable_value(
                                &mut self.context.gizmo_mode,
                                nightshade::ecs::gizmos::GizmoMode::LocalTranslation,
                                "Local Translation",
                            );
                            ui.selectable_value(
                                &mut self.context.gizmo_mode,
                                nightshade::ecs::gizmos::GizmoMode::GlobalTranslation,
                                "Global Translation",
                            );
                            ui.selectable_value(
                                &mut self.context.gizmo_mode,
                                nightshade::ecs::gizmos::GizmoMode::Rotation,
                                "Rotation",
                            );
                            ui.selectable_value(
                                &mut self.context.gizmo_mode,
                                nightshade::ecs::gizmos::GizmoMode::Scale,
                                "Scale",
                            );
                        });

                    if old_mode != self.context.gizmo_mode
                        && let Some(old_gizmo) = &self.context.gizmo
                    {
                        nightshade::ecs::gizmos::destroy_gizmo(world, old_gizmo);
                        self.context.gizmo = None;
                        self.context.hover_axis = None;
                        self.context.drag_mode = nightshade::ecs::gizmos::GizmoDragMode::None;
                    }
                }

                ui.allocate_ui_with_layout(
                    egui::Vec2::new(ui.available_width(), ui.available_height()),
                    egui::Layout::right_to_left(egui::Align::Center),
                    |ui| {
                        let lines = query_number_of_lines(world);
                        ui.label(format!("Lines: {}/{}", lines, MAX_LINES));
                        ui.separator();
                        ui.label(format!("Entities: {}", world.get_all_entities().len()));
                        ui.separator();
                        ui.label(format!(
                            "FPS: {}",
                            world.resources.window.timing.frames_per_second
                        ));
                    },
                );
            });
        });
    }

    fn load_gltf(&mut self, world: &mut World, path: &std::path::Path) {
        log::info!("Loading GLTF from: {:?}", path);

        let source_path = path
            .file_name()
            .and_then(|name| name.to_str())
            .unwrap_or("unknown")
            .to_string();

        match nightshade::ecs::prefab::import_gltf_from_path(path) {
            Ok(result) => {
                log::info!("Successfully loaded GLTF file");
                log::info!("Loaded {} meshes", result.meshes.len());
                log::info!("Loaded {} materials", result.materials.len());
                log::info!("Loaded {} textures", result.textures.len());
                log::info!("Loaded {} prefabs", result.prefabs.len());
                log::info!("Loaded {} animations", result.animations.len());

                for (name, (rgba_data, width, height)) in result.textures {
                    log::info!("Loading texture '{}': {}x{}", name, width, height);
                    world.queue_command(WorldCommand::LoadTexture {
                        name,
                        rgba_data,
                        width,
                        height,
                    });
                }

                for (name, mesh) in result.meshes {
                    log::info!(
                        "Mesh '{}': {} vertices, {} indices",
                        name,
                        mesh.vertices.len(),
                        mesh.indices.len()
                    );
                    world.resources.mesh_cache.insert(name, mesh);
                }

                let mut last_spawned_entity = None;
                for prefab in &result.prefabs {
                    log::info!("Spawning prefab '{}'", prefab.name);
                    let entity = spawn_prefab_with_animations(
                        world,
                        prefab,
                        &result.animations,
                        nalgebra_glm::vec3(0.0, 0.0, 0.0),
                    );
                    log::info!("Spawned prefab with root entity {:?}", entity);
                    last_spawned_entity = Some(entity);
                }

                if let Some(entity) = last_spawned_entity {
                    self.context.selected_entity = Some(entity);
                }

                for prefab in result.prefabs {
                    self.context.tile_behavior.prefabs.push(PrefabMetadata {
                        prefab,
                        animations: result.animations.clone(),
                        source_path: Some(source_path.clone()),
                    });
                }
            }
            Err(e) => {
                log::error!("Failed to load GLTF file: {}", e);
            }
        }
    }

    fn load_gltf_from_bytes(&mut self, world: &mut World, name: &str, data: &[u8]) {
        log::info!("Loading GLTF from bytes: {} ({} bytes)", name, data.len());

        let source_path = name.to_string();

        match nightshade::ecs::prefab::import_gltf_from_bytes(data) {
            Ok(result) => {
                log::info!("Successfully loaded GLTF from bytes");
                log::info!("Loaded {} meshes", result.meshes.len());
                log::info!("Loaded {} materials", result.materials.len());
                log::info!("Loaded {} textures", result.textures.len());
                log::info!("Loaded {} prefabs", result.prefabs.len());
                log::info!("Loaded {} animations", result.animations.len());

                for (texture_name, (rgba_data, width, height)) in result.textures {
                    log::info!("Loading texture '{}': {}x{}", texture_name, width, height);
                    world.queue_command(WorldCommand::LoadTexture {
                        name: texture_name,
                        rgba_data,
                        width,
                        height,
                    });
                }

                for (mesh_name, mesh) in result.meshes {
                    log::info!(
                        "Mesh '{}': {} vertices, {} indices",
                        mesh_name,
                        mesh.vertices.len(),
                        mesh.indices.len()
                    );
                    world.resources.mesh_cache.insert(mesh_name, mesh);
                }

                let mut last_spawned_entity = None;
                for prefab in &result.prefabs {
                    log::info!("Spawning prefab '{}'", prefab.name);
                    let entity = spawn_prefab_with_animations(
                        world,
                        prefab,
                        &result.animations,
                        nalgebra_glm::vec3(0.0, 0.0, 0.0),
                    );
                    log::info!("Spawned prefab with root entity {:?}", entity);
                    last_spawned_entity = Some(entity);
                }

                if let Some(entity) = last_spawned_entity {
                    self.context.selected_entity = Some(entity);
                }

                for prefab in result.prefabs {
                    self.context.tile_behavior.prefabs.push(PrefabMetadata {
                        prefab,
                        animations: result.animations.clone(),
                        source_path: Some(source_path.clone()),
                    });
                }
            }
            Err(e) => {
                log::error!("Failed to load GLTF from bytes: {}", e);
            }
        }
    }

    fn drop_indicator_ui(&self, ui_context: &egui::Context) {
        egui::Area::new(egui::Id::new("drop_indicator"))
            .anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0])
            .show(ui_context, |ui| {
                ui.with_layout(egui::Layout::top_down(egui::Align::Center), |ui| {
                    let frame = egui::Frame::default()
                        .fill(egui::Color32::from_rgba_premultiplied(30, 30, 30, 200))
                        .corner_radius(10.0)
                        .stroke(egui::Stroke::new(
                            2.0,
                            egui::Color32::from_rgb(100, 150, 250),
                        ))
                        .inner_margin(30.0);

                    frame.show(ui, |ui| {
                        if let Some(file_type) = &self.context.drag_file_type {
                            if file_type == "hdr" {
                                ui.heading("Drop HDR file to use as skybox");
                            } else if file_type == "gltf" {
                                ui.heading("Drop GLTF/GLB to import to scene");
                            } else {
                                ui.heading("Drop file to load");
                            }
                        } else {
                            ui.heading("Drop file to load");
                        }
                    });
                });
            });
    }

    fn set_gizmo_visible(
        &self,
        world: &mut World,
        gizmo: &nightshade::ecs::gizmos::GizmoState,
        visible: bool,
    ) {
        if let Some(vis) = world.get_visibility_mut(gizmo.root) {
            vis.visible = visible;
        }

        let descendants: Vec<Entity> =
            nightshade::ecs::transform::queries::query_descendants(world, gizmo.root)
                .into_iter()
                .collect();

        for descendant in descendants {
            if let Some(vis) = world.get_visibility_mut(descendant) {
                vis.visible = visible;
            }
        }
    }

    fn clear_scene(&mut self, world: &mut World) {
        log::info!("Clearing scene");

        let camera_entity = world.resources.active_camera;
        let all_entities: Vec<Entity> = world.get_all_entities();

        for entity in all_entities {
            if Some(entity) == camera_entity {
                continue;
            }

            world.queue_command(WorldCommand::DespawnRecursive { entity });
        }

        self.context.selected_entity = None;

        log::info!("Scene cleared");
    }

    fn update_gizmo(&mut self, world: &mut World) {
        use nightshade::ecs::gizmos;
        use nightshade::ecs::picking::queries::{PickingOptions, pick_entities};

        if self.context.gizmo.is_none() {
            let dummy_entity = world.spawn_entities(
                LOCAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY | GLOBAL_TRANSFORM,
                1,
            )[0];

            let gizmo = match self.context.gizmo_mode {
                nightshade::ecs::gizmos::GizmoMode::LocalTranslation
                | nightshade::ecs::gizmos::GizmoMode::GlobalTranslation => {
                    gizmos::create_translation_gizmo(world, dummy_entity, self.context.gizmo_mode)
                }
                nightshade::ecs::gizmos::GizmoMode::Rotation => {
                    gizmos::create_rotation_gizmo(world, dummy_entity)
                }
                nightshade::ecs::gizmos::GizmoMode::Scale => {
                    gizmos::create_scale_gizmo(world, dummy_entity)
                }
            };

            world.queue_command(WorldCommand::DespawnRecursive {
                entity: dummy_entity,
            });

            world.add_components(gizmo.root, PARENT | IGNORE_PARENT_SCALE);
            self.set_gizmo_visible(world, &gizmo, false);
            gizmos::set_gizmo_drag_visibility(
                world,
                &gizmo,
                nightshade::ecs::gizmos::GizmoDragMode::None,
            );
            if let Some(parent) = world.get_parent_mut(gizmo.root) {
                parent.0 = None;
            }

            self.context.gizmo = Some(gizmo);
        }

        if let Some(gizmo) = &self.context.gizmo {
            if let Some(selected_entity) = self.context.selected_entity {
                if world.resources.active_camera == Some(selected_entity) {
                    if let Some(parent_mut) = world.get_parent_mut(gizmo.root) {
                        parent_mut.0 = None;
                    }
                    self.set_gizmo_visible(world, gizmo, false);
                    self.context.hover_axis = None;
                    self.context.drag_mode = nightshade::ecs::gizmos::GizmoDragMode::None;

                    return;
                }

                if let Some(parent) = world.get_parent(gizmo.root)
                    && parent.0 != Some(selected_entity)
                {
                    if let Some(parent_mut) = world.get_parent_mut(gizmo.root) {
                        parent_mut.0 = Some(selected_entity);
                    }
                    mark_local_transform_dirty(world, gizmo.root);
                }

                self.set_gizmo_visible(world, gizmo, true);
                gizmos::set_gizmo_drag_visibility(world, gizmo, self.context.drag_mode);
            } else {
                if let Some(parent_mut) = world.get_parent_mut(gizmo.root) {
                    parent_mut.0 = None;
                }
                self.set_gizmo_visible(world, gizmo, false);
                self.context.hover_axis = None;
                self.context.drag_mode = nightshade::ecs::gizmos::GizmoDragMode::None;

                return;
            }
        }

        if let Some(gizmo) = &self.context.gizmo
            && let Some(selected_entity) = self.context.selected_entity
        {
            if let Some(camera_entity) = world.resources.active_camera
                && let (Some(camera_transform), Some(target_transform)) = (
                    world.get_global_transform(camera_entity),
                    world.get_global_transform(selected_entity),
                )
            {
                let camera_pos = nalgebra_glm::vec3(
                    camera_transform.0[(0, 3)],
                    camera_transform.0[(1, 3)],
                    camera_transform.0[(2, 3)],
                );
                let target_pos = nalgebra_glm::vec3(
                    target_transform.0[(0, 3)],
                    target_transform.0[(1, 3)],
                    target_transform.0[(2, 3)],
                );
                let distance = nalgebra_glm::distance(&camera_pos, &target_pos);
                self.context.camera_distance = distance;

                let scale_factor = 0.08;
                let min_scale = 0.3;
                let max_scale = 50.0;
                let scale = (distance * scale_factor).max(min_scale).min(max_scale);

                if let Some(transform) = world.get_local_transform(gizmo.root) {
                    let mut new_transform = *transform;
                    new_transform.translation = Vec3::zeros();
                    new_transform.scale = nalgebra_glm::vec3(scale, scale, scale);
                    new_transform.rotation = Quat::identity();
                    world.assign_local_transform(gizmo.root, new_transform);
                }
            }

            if world.resources.user_interface.consumed_event {
                if !matches!(
                    self.context.drag_mode,
                    nightshade::ecs::gizmos::GizmoDragMode::None
                ) {
                    self.context.drag_mode = nightshade::ecs::gizmos::GizmoDragMode::None;
                    self.context.hover_axis = None;
                    gizmos::set_gizmo_drag_visibility(
                        world,
                        gizmo,
                        nightshade::ecs::gizmos::GizmoDragMode::None,
                    );
                }
                return;
            }

            let mouse = &world.resources.input.mouse;
            let mouse_pos = mouse.position;
            let mouse_pressed = mouse.state.contains(MouseState::LEFT_CLICKED);

            let is_dragging = !matches!(
                self.context.drag_mode,
                nightshade::ecs::gizmos::GizmoDragMode::None
            );
            let mouse_in_selected_viewport = self.is_mouse_in_selected_viewport(world);

            if !mouse_in_selected_viewport && !is_dragging {
                if self.context.hover_axis.is_some() {
                    self.context.hover_axis = None;
                    gizmos::highlight_gizmo_axis(world, gizmo, None);
                }
                return;
            }

            if !is_dragging {
                let all_picked = pick_entities(world, mouse_pos, PickingOptions::default());
                let hover_axis = all_picked
                    .iter()
                    .find_map(|result| gizmos::check_gizmo_axis_hit(world, gizmo, result.entity));

                if hover_axis != self.context.hover_axis {
                    self.context.hover_axis = hover_axis;
                    gizmos::highlight_gizmo_axis(world, gizmo, hover_axis);
                }

                if mouse_pressed && let Some(axis) = hover_axis {
                    let new_drag_mode = determine_drag_mode(axis);
                    self.context.drag_mode = new_drag_mode;
                    gizmos::set_gizmo_drag_visibility(world, gizmo, new_drag_mode);
                }
            } else if mouse_pressed {
                if let Some(camera_entity) = world.resources.active_camera {
                    let mouse_delta = world.resources.input.mouse.raw_mouse_delta;
                    let base_sensitivity = 0.01;
                    let distance_scale = (self.context.camera_distance * 0.1).sqrt().max(0.5);
                    let sensitivity = base_sensitivity * distance_scale;

                    if let Some(camera_transform) = world.get_global_transform(camera_entity) {
                        let camera_right = nalgebra_glm::vec3(
                            camera_transform.0[(0, 0)],
                            camera_transform.0[(1, 0)],
                            camera_transform.0[(2, 0)],
                        )
                        .normalize();
                        let camera_up = nalgebra_glm::vec3(
                            camera_transform.0[(0, 1)],
                            camera_transform.0[(1, 1)],
                            camera_transform.0[(2, 1)],
                        )
                        .normalize();

                        let drag_params = DragParams {
                            mode: self.context.gizmo_mode,
                            camera_right,
                            camera_up,
                            mouse_delta,
                            sensitivity,
                        };

                        match &self.context.drag_mode {
                            nightshade::ecs::gizmos::GizmoDragMode::Axis(axis) => {
                                apply_axis_drag(world, selected_entity, *axis, drag_params);
                            }
                            nightshade::ecs::gizmos::GizmoDragMode::Plane(plane_normal) => {
                                apply_plane_drag(
                                    world,
                                    selected_entity,
                                    *plane_normal,
                                    drag_params,
                                );
                            }
                            nightshade::ecs::gizmos::GizmoDragMode::Free => {
                                apply_free_drag(
                                    world,
                                    selected_entity,
                                    self.context.gizmo_mode,
                                    camera_right,
                                    camera_up,
                                    mouse_delta,
                                    sensitivity,
                                );
                            }
                            nightshade::ecs::gizmos::GizmoDragMode::None => {}
                        }
                    }
                }
            } else {
                self.context.drag_mode = nightshade::ecs::gizmos::GizmoDragMode::None;
                gizmos::set_gizmo_drag_visibility(
                    world,
                    gizmo,
                    nightshade::ecs::gizmos::GizmoDragMode::None,
                );

                let new_hover_axis = if mouse_in_selected_viewport {
                    let all_picked = pick_entities(world, mouse_pos, PickingOptions::default());
                    all_picked.iter().find_map(|result| {
                        gizmos::check_gizmo_axis_hit(world, gizmo, result.entity)
                    })
                } else {
                    None
                };

                if new_hover_axis != self.context.hover_axis {
                    self.context.hover_axis = new_hover_axis;
                    gizmos::highlight_gizmo_axis(world, gizmo, new_hover_axis);
                }
            }
        }
    }

    fn is_mouse_in_selected_viewport(&self, world: &World) -> bool {
        if let Some(viewport_rect) = &world.resources.window.active_viewport_rect {
            let mouse_pos = world.resources.input.mouse.position;
            return viewport_rect.contains(mouse_pos);
        }
        true
    }
}

fn query_number_of_lines(world: &World) -> usize {
    world
        .query_entities(LINES)
        .map(|entity| {
            world
                .get_lines(entity)
                .map(|lines| lines.lines.len())
                .unwrap_or(0)
        })
        .sum::<usize>()
}

fn spawn_lines(world: &mut World) {
    const LINE_COUNT: usize = 2_000;

    let entity = world.spawn_entities(
        LINES | NAME | LOCAL_TRANSFORM | GLOBAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY | VISIBILITY,
        1,
    )[0];

    if let Some(name) = world.get_name_mut(entity) {
        *name = Name(format!("{} Lines", LINE_COUNT));
    }

    if let Some(transform) = world.get_local_transform_mut(entity) {
        *transform = LocalTransform::default();
    }

    if let Some(lines) = world.get_lines_mut(entity) {
        let mut test_lines = Vec::new();

        (0..LINE_COUNT).for_each(|index| {
            let angle = (index as f32 / LINE_COUNT as f32) * std::f32::consts::PI * 20.0;
            let radius = 5.0 + (index as f32 / LINE_COUNT as f32) * 50.0;
            let height = (index as f32 / 100.0).sin() * 20.0;

            let start = Vec3::new(radius * angle.cos(), height, radius * angle.sin());
            let end = Vec3::new(
                radius * (angle + 0.05).cos(),
                height + 2.0,
                radius * (angle + 0.05).sin(),
            );

            let hue = (index as f32 / LINE_COUNT as f32) * 360.0;
            let color = hue_to_rgb(hue);

            test_lines.push(Line {
                start,
                end,
                color: Vec4::new(color.x, color.y, color.z, 1.0),
            });
        });

        lines.lines = test_lines;
    }
}

fn hue_to_rgb(hue: f32) -> Vec3 {
    let h = hue / 60.0;
    let c = 1.0;
    let x = c * (1.0 - ((h % 2.0) - 1.0).abs());

    let (r, g, b) = match h as i32 {
        0 => (c, x, 0.0),
        1 => (x, c, 0.0),
        2 => (0.0, c, x),
        3 => (0.0, x, c),
        4 => (x, 0.0, c),
        _ => (c, 0.0, x),
    };

    Vec3::new(r, g, b)
}

fn spawn_meshes(world: &mut World) {
    const MESH_COUNT: usize = 10_000;
    const GRID_SIZE: usize = 22;
    const SPACING: f32 = 2.0;

    let parent_entity = world.spawn_entities(
        NAME | LOCAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY | GLOBAL_TRANSFORM,
        1,
    )[0];

    world.set_name(parent_entity, Name("10,000 Meshes Group".to_string()));
    world.set_local_transform(
        parent_entity,
        LocalTransform {
            translation: Vec3::new(0.0, 0.0, 0.0),
            rotation: Quat::identity(),
            scale: Vec3::new(1.0, 1.0, 1.0),
        },
    );
    world.set_local_transform_dirty(parent_entity, LocalTransformDirty);
    world.set_global_transform(parent_entity, GlobalTransform::default());

    let mut mesh_count = 0;
    let mesh_types = ["Cube", "Sphere", "Cylinder", "Cone", "Torus", "Plane"];
    let offset = -(GRID_SIZE as f32 * SPACING) / 2.0;
    let y_offset = 1.0;

    'outer: for x in 0..GRID_SIZE {
        for y in 0..GRID_SIZE {
            for z in 0..GRID_SIZE {
                if mesh_count >= MESH_COUNT {
                    break 'outer;
                }

                let entity = world.spawn_entities(
                    NAME | LOCAL_TRANSFORM
                        | LOCAL_TRANSFORM_DIRTY
                        | GLOBAL_TRANSFORM
                        | RENDER_MESH
                        | MATERIAL
                        | VISIBILITY
                        | CASTS_SHADOW
                        | PARENT,
                    1,
                )[0];

                world.update_parent(entity, Some(Parent(Some(parent_entity))));

                world.set_name(entity, Name(format!("Mesh {}", mesh_count + 1)));

                world.set_local_transform(
                    entity,
                    LocalTransform {
                        translation: Vec3::new(
                            offset + x as f32 * SPACING,
                            offset + y as f32 * SPACING + y_offset,
                            offset + z as f32 * SPACING,
                        ),
                        rotation: Quat::identity(),
                        scale: Vec3::new(0.5, 0.5, 0.5),
                    },
                );
                world.set_local_transform_dirty(entity, LocalTransformDirty);
                world.set_global_transform(entity, GlobalTransform::default());

                let mesh_type = mesh_types[mesh_count % mesh_types.len()];
                world.set_render_mesh(
                    entity,
                    RenderMesh {
                        name: mesh_type.to_string(),
                    },
                );

                let color = match mesh_count % 6 {
                    0 => [1.0, 0.5, 0.5, 1.0],
                    1 => [0.5, 1.0, 0.5, 1.0],
                    2 => [0.5, 0.5, 1.0, 1.0],
                    3 => [1.0, 1.0, 0.5, 1.0],
                    4 => [1.0, 0.5, 1.0, 1.0],
                    _ => [0.5, 1.0, 1.0, 1.0],
                };
                let mut material = Material {
                    base_color: color,
                    emissive_factor: [0.0, 0.0, 0.0],
                    alpha_mode: AlphaMode::Opaque,
                    alpha_cutoff: 0.5,
                    base_texture: None,
                    emissive_texture: None,
                    roughness: 0.8,
                    metallic: 0.0,
                    unlit: false,
                    cache_id: None,
                };
                let material_id = world.resources.material_cache.insert_or_get_id(&material);
                world.resources.material_cache.add_reference(&material_id);
                material.cache_id = Some(material_id);
                world.set_material(entity, material);

                world.set_visibility(entity, Visibility { visible: true });
                world.set_casts_shadow(entity, CastsShadow);

                mesh_count += 1;
            }
        }
    }

    log::info!("Spawned {} meshes in a lattice formation", mesh_count);
}

fn spawn_3d_text(world: &mut World) {
    let text_index = world.resources.text_cache.add_text("3D Text Object");
    let entity = world.spawn_entities(
        NAME | LOCAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY | GLOBAL_TRANSFORM | TEXT | VISIBILITY,
        1,
    )[0];

    world.set_name(entity, Name("3D Text".to_string()));

    world.set_local_transform(
        entity,
        LocalTransform {
            translation: Vec3::new(
                rand::random::<f32>() * 10.0 - 5.0,
                3.0,
                rand::random::<f32>() * 10.0 - 5.0,
            ),
            rotation: Quat::identity(),
            scale: Vec3::new(1.0, 1.0, 1.0),
        },
    );
    world.set_local_transform_dirty(entity, LocalTransformDirty);
    world.set_global_transform(entity, GlobalTransform::default());

    if let Some(text) = world.get_text_mut(entity) {
        text.text_index = text_index;
        text.properties = TextProperties {
            font_size: 32.0,
            color: Vec4::new(
                rand::random::<f32>(),
                rand::random::<f32>(),
                rand::random::<f32>(),
                1.0,
            ),
            alignment: TextAlignment::Center,
            vertical_alignment: VerticalAlignment::Middle,
            ..Default::default()
        };
        text.dirty = true;
    }
}

fn spawn_text_lattice(world: &mut World) {
    const TEXT_COUNT: usize = 5_000;
    const GRID_X: usize = 25;
    const GRID_Y: usize = 20;
    const GRID_Z: usize = 10;
    const SPACING: f32 = 3.0;

    let parent_entity = world.spawn_entities(
        NAME | LOCAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY | GLOBAL_TRANSFORM,
        1,
    )[0];

    world.set_name(
        parent_entity,
        Name("Text Lattice (Stress Test)".to_string()),
    );
    world.set_local_transform(
        parent_entity,
        LocalTransform {
            translation: Vec3::new(0.0, 5.0, 0.0),
            rotation: Quat::identity(),
            scale: Vec3::new(1.0, 1.0, 1.0),
        },
    );
    world.set_local_transform_dirty(parent_entity, LocalTransformDirty);
    world.set_global_transform(parent_entity, GlobalTransform::default());

    let text_variations = [
        "Text", "Hello", "World", "Test", "Lorem", "Ipsum", "3D", "Engine", "Render", "Buffer",
        "Dynamic", "Scale", "Vertex", "Index", "GPU", "SDF",
    ];

    let offset_x = -(GRID_X as f32 * SPACING) / 2.0;
    let offset_y = -(GRID_Y as f32 * SPACING) / 2.0;
    let offset_z = -(GRID_Z as f32 * SPACING) / 2.0;

    let mut text_count = 0;

    'outer: for x in 0..GRID_X {
        for y in 0..GRID_Y {
            for z in 0..GRID_Z {
                if text_count >= TEXT_COUNT {
                    break 'outer;
                }

                let text_content = format!(
                    "{} #{}",
                    text_variations[text_count % text_variations.len()],
                    text_count + 1
                );
                let text_index = world.resources.text_cache.add_text(&text_content);

                let entity = world.spawn_entities(
                    NAME | LOCAL_TRANSFORM
                        | LOCAL_TRANSFORM_DIRTY
                        | GLOBAL_TRANSFORM
                        | TEXT
                        | VISIBILITY
                        | PARENT,
                    1,
                )[0];

                world.set_parent(entity, Parent(Some(parent_entity)));
                world.set_name(entity, Name(format!("Text {}", text_count + 1)));

                world.set_local_transform(
                    entity,
                    LocalTransform {
                        translation: Vec3::new(
                            offset_x + x as f32 * SPACING,
                            offset_y + y as f32 * SPACING,
                            offset_z + z as f32 * SPACING,
                        ),
                        rotation: Quat::identity(),
                        scale: Vec3::new(1.0, 1.0, 1.0),
                    },
                );
                world.set_local_transform_dirty(entity, LocalTransformDirty);
                world.set_global_transform(entity, GlobalTransform::default());

                if let Some(text) = world.get_text_mut(entity) {
                    text.text_index = text_index;
                    text.properties = TextProperties {
                        font_size: 16.0,
                        color: Vec4::new(
                            rand::random::<f32>(),
                            rand::random::<f32>(),
                            rand::random::<f32>(),
                            1.0,
                        ),
                        alignment: TextAlignment::Center,
                        vertical_alignment: VerticalAlignment::Middle,
                        ..Default::default()
                    };
                    text.dirty = true;
                }

                text_count += 1;
            }
        }
    }

    log::info!(
        "Spawned {} text entities in a lattice formation",
        text_count
    );
}

fn determine_drag_mode(axis: Vec3) -> nightshade::ecs::gizmos::GizmoDragMode {
    use nightshade::ecs::gizmos::GizmoDragMode;

    if axis.x > 1.05 || axis.y > 1.05 || axis.z > 1.05 {
        let plane_normal = if axis.x > 1.05 && axis.y > 1.05 {
            nalgebra_glm::vec3(0.0, 0.0, 1.0)
        } else if axis.y > 1.05 && axis.z > 1.05 {
            nalgebra_glm::vec3(1.0, 0.0, 0.0)
        } else if axis.x > 1.05 && axis.z > 1.05 {
            nalgebra_glm::vec3(0.0, 1.0, 0.0)
        } else {
            return GizmoDragMode::Free;
        };
        GizmoDragMode::Plane(plane_normal)
    } else {
        let single_axis = if axis.x > 0.99 {
            nalgebra_glm::vec3(1.0, 0.0, 0.0)
        } else if axis.y > 0.99 {
            nalgebra_glm::vec3(0.0, 1.0, 0.0)
        } else if axis.z > 0.99 {
            nalgebra_glm::vec3(0.0, 0.0, 1.0)
        } else {
            return GizmoDragMode::Free;
        };
        GizmoDragMode::Axis(single_axis)
    }
}

struct DragParams {
    mode: nightshade::ecs::gizmos::GizmoMode,
    camera_right: Vec3,
    camera_up: Vec3,
    mouse_delta: Vec2,
    sensitivity: f32,
}

fn apply_axis_drag(world: &mut World, target: Entity, axis: Vec3, params: DragParams) {
    use nightshade::ecs::gizmos::GizmoMode;

    let DragParams {
        mode,
        camera_right,
        camera_up,
        mouse_delta,
        sensitivity,
    } = params;
    match mode {
        GizmoMode::LocalTranslation | GizmoMode::GlobalTranslation => {
            let screen_movement = camera_right * mouse_delta.x - camera_up * mouse_delta.y;
            let movement_amount = nalgebra_glm::dot(&screen_movement, &axis) * sensitivity;
            let world_movement = axis * movement_amount;

            if let Some(transform) = world.get_local_transform(target) {
                let mut new_transform = *transform;
                new_transform.translation += world_movement;
                world.assign_local_transform(target, new_transform);
            }
        }
        GizmoMode::Rotation => {
            let rotation_speed = sensitivity * 2.0;
            let angle = if axis.x > 0.99 {
                -mouse_delta.y * rotation_speed
            } else if axis.y > 0.99 {
                mouse_delta.x * rotation_speed
            } else {
                -mouse_delta.x * rotation_speed
            };

            if angle.abs() > 0.001 {
                let world_rotation = nalgebra_glm::quat_angle_axis(angle, &axis);
                if let Some(transform) = world.get_local_transform(target) {
                    let mut new_transform = *transform;
                    new_transform.rotation = world_rotation * new_transform.rotation;
                    world.assign_local_transform(target, new_transform);
                }
            }
        }
        GizmoMode::Scale => {
            let scale_delta = (mouse_delta.x - mouse_delta.y) * sensitivity;
            if let Some(transform) = world.get_local_transform(target) {
                let mut new_transform = *transform;
                if axis.x > 0.99 {
                    new_transform.scale.x *= 1.0 + scale_delta;
                } else if axis.y > 0.99 {
                    new_transform.scale.y *= 1.0 + scale_delta;
                } else {
                    new_transform.scale.z *= 1.0 + scale_delta;
                }
                new_transform.scale.x = new_transform.scale.x.max(0.01);
                new_transform.scale.y = new_transform.scale.y.max(0.01);
                new_transform.scale.z = new_transform.scale.z.max(0.01);
                world.assign_local_transform(target, new_transform);
            }
        }
    }
}

fn apply_plane_drag(world: &mut World, target: Entity, plane_normal: Vec3, params: DragParams) {
    use nightshade::ecs::gizmos::GizmoMode;

    let DragParams {
        mode,
        camera_right,
        camera_up,
        mouse_delta,
        sensitivity,
    } = params;
    match mode {
        GizmoMode::LocalTranslation | GizmoMode::GlobalTranslation => {
            let screen_movement = camera_right * mouse_delta.x * sensitivity
                - camera_up * mouse_delta.y * sensitivity;

            let movement_in_plane =
                screen_movement - plane_normal * nalgebra_glm::dot(&screen_movement, &plane_normal);

            if let Some(transform) = world.get_local_transform(target) {
                let mut new_transform = *transform;
                new_transform.translation += movement_in_plane;
                world.assign_local_transform(target, new_transform);
            }
        }
        GizmoMode::Rotation => {
            let rotation_speed = sensitivity * 2.0;
            let angle = if plane_normal.z.abs() > 0.99 {
                mouse_delta.x * rotation_speed
            } else if plane_normal.x.abs() > 0.99 {
                -mouse_delta.y * rotation_speed
            } else {
                mouse_delta.x * rotation_speed
            };

            if angle.abs() > 0.001 {
                let world_rotation = nalgebra_glm::quat_angle_axis(angle, &plane_normal);
                if let Some(transform) = world.get_local_transform(target) {
                    let mut new_transform = *transform;
                    new_transform.rotation = world_rotation * new_transform.rotation;
                    world.assign_local_transform(target, new_transform);
                }
            }
        }
        GizmoMode::Scale => {
            let scale_delta = (mouse_delta.x - mouse_delta.y) * sensitivity;
            if let Some(transform) = world.get_local_transform(target) {
                let mut new_transform = *transform;
                if plane_normal.z.abs() > 0.99 {
                    new_transform.scale.x *= 1.0 + scale_delta;
                    new_transform.scale.y *= 1.0 + scale_delta;
                } else if plane_normal.x.abs() > 0.99 {
                    new_transform.scale.y *= 1.0 + scale_delta;
                    new_transform.scale.z *= 1.0 + scale_delta;
                } else {
                    new_transform.scale.x *= 1.0 + scale_delta;
                    new_transform.scale.z *= 1.0 + scale_delta;
                }
                new_transform.scale.x = new_transform.scale.x.max(0.01);
                new_transform.scale.y = new_transform.scale.y.max(0.01);
                new_transform.scale.z = new_transform.scale.z.max(0.01);
                world.assign_local_transform(target, new_transform);
            }
        }
    }
}

fn apply_free_drag(
    world: &mut World,
    target: Entity,
    mode: nightshade::ecs::gizmos::GizmoMode,
    camera_right: Vec3,
    camera_up: Vec3,
    mouse_delta: Vec2,
    sensitivity: f32,
) {
    use nightshade::ecs::gizmos::GizmoMode;

    match mode {
        GizmoMode::LocalTranslation | GizmoMode::GlobalTranslation => {
            let screen_movement = camera_right * mouse_delta.x * sensitivity
                - camera_up * mouse_delta.y * sensitivity;

            if let Some(transform) = world.get_local_transform(target) {
                let mut new_transform = *transform;
                new_transform.translation += screen_movement;
                world.assign_local_transform(target, new_transform);
            }
        }
        GizmoMode::Rotation => {
            let rotation_speed = sensitivity * 2.0;
            let axis_x = nalgebra_glm::vec3(1.0, 0.0, 0.0);
            let axis_y = nalgebra_glm::vec3(0.0, 1.0, 0.0);

            let rotation_x =
                nalgebra_glm::quat_angle_axis(-mouse_delta.y * rotation_speed, &axis_x);
            let rotation_y = nalgebra_glm::quat_angle_axis(mouse_delta.x * rotation_speed, &axis_y);

            if let Some(transform) = world.get_local_transform(target) {
                let mut new_transform = *transform;
                new_transform.rotation = rotation_y * rotation_x * new_transform.rotation;
                world.assign_local_transform(target, new_transform);
            }
        }
        GizmoMode::Scale => {
            let scale_delta = (mouse_delta.x - mouse_delta.y) * sensitivity;
            let scale_factor = 1.0 + scale_delta;

            if let Some(transform) = world.get_local_transform(target) {
                let mut new_transform = *transform;
                new_transform.scale *= scale_factor;
                new_transform.scale.x = new_transform.scale.x.max(0.01);
                new_transform.scale.y = new_transform.scale.y.max(0.01);
                new_transform.scale.z = new_transform.scale.z.max(0.01);
                world.assign_local_transform(target, new_transform);
            }
        }
    }
}
