pub mod dock;
pub mod docks;
pub mod input;
pub mod page;
pub mod scene;
pub mod settings;
pub mod ui;

pub(crate) use crate::editor::dock::*;

use crate::about::AboutWindow;
use crate::build::build;
use crate::debug;
use crate::editor::page::EditorTabVisibility;
use crate::editor::settings::editor::{EDITOR_SETTINGS, EditorSettingsWindow};
use crate::editor::settings::project::ProjectSettingsWindow;
use crate::editor::ui::UiEditor;
use crate::plugin::PluginRegistry;
use crate::stats::NerdStats;
use crossbeam_channel::{Receiver, Sender, unbounded};
use docks::console::EucalyptusConsole;
use dropbear_engine::animation::MorphTargetInfo;
use dropbear_engine::billboarding::BillboardPipeline;
use dropbear_engine::buffer::DynamicBuffer;
use dropbear_engine::entity::EntityTransform;
use dropbear_engine::graphics::InstanceRaw;
use dropbear_engine::mipmap::MipMapper;
use dropbear_engine::multisampling::AntiAliasingMode;
use dropbear_engine::pipelines::DropbearShaderPipeline;
use dropbear_engine::pipelines::GlobalsUniform;
use dropbear_engine::pipelines::light_cube::LightCubePipeline;
use dropbear_engine::pipelines::shader::MainRenderPipeline;
use dropbear_engine::sky::{DEFAULT_SKY_TEXTURE, HdrLoader, SkyPipeline};
use dropbear_engine::{
    DropbearWindowBuilder, WindowData, camera::Camera, entity::Transform, future::FutureHandle,
    graphics::SharedGraphicsContext, scene::SceneCommand,
};
use egui::{self, Ui};
use egui_dock::{DockArea, DockState, NodeIndex, Style};
use eucalyptus_core::component::{ComponentRegistry, SerializedComponent};
use eucalyptus_core::hierarchy::{Children, Parent, SceneHierarchy};
use eucalyptus_core::physics::PhysicsState;
use eucalyptus_core::scene::{SceneConfig, SceneEntity};
use eucalyptus_core::states::Label;
use eucalyptus_core::{APP_INFO, register_components};
use eucalyptus_core::{
    camera::{CameraComponent, CameraType, DebugCamera},
    fatal, info,
    input::InputState,
    scripting::BuildStatus,
    states,
    states::{PROJECT, SCENES, WorldLoadingStatus},
    success,
    utils::ViewportMode,
    warn,
};
use glam::Mat4;
use hecs::{Entity, World};
use kino_ui::KinoState;
use kino_ui::rendering::KinoWGPURenderer;
use kino_ui::windowing::KinoWinitWindowing;
use log::{debug, error};
use parking_lot::{Mutex, RwLock};
use rfd::FileDialog;
use std::cmp::PartialEq;
use std::collections::{HashSet, VecDeque};
use std::rc::Rc;
use std::{collections::HashMap, path::PathBuf, sync::Arc, time::Instant};
use tokio::sync::oneshot;
use transform_gizmo_egui::{EnumSet, Gizmo, GizmoMode, GizmoOrientation};
use wgpu::{Color, Extent3d};
use winit::dpi::PhysicalSize;
use winit::window::{CursorGrabMode, WindowAttributes};
use winit::{keyboard::KeyCode, window::Window};
use dropbear_engine::pipelines::animation::AnimationDefaults;

pub struct Editor {
    pub dt: f32,
    pub scene_command: SceneCommand,
    pub world: Box<World>,
    pub physics_state: PhysicsState,
    pub game_editor_dock_state: DockState<EditorTabId>,
    pub ui_editor_dock_state: DockState<EditorTabId>,
    pub texture_id: Option<egui::TextureId>,
    pub size: Extent3d,
    pub instance_buffer_cache: HashMap<u64, DynamicBuffer<InstanceRaw>>,
    pub color: Color,

    pub ui_editor: UiEditor,

    pub current_page: EditorTabVisibility,

    // rendering
    pub light_cube_pipeline: Option<LightCubePipeline>,
    pub main_render_pipeline: Option<MainRenderPipeline>,
    pub shader_globals: Option<GlobalsUniform>,
    pub mipmapper: Option<MipMapper>,
    pub sky_pipeline: Option<SkyPipeline>,
    pub billboard_pipeline: Option<BillboardPipeline>,
    pub kino: Option<KinoState>,
    pub animation_pipeline: Option<AnimationDefaults>,
    pub(crate) animated_instance_buffers: HashMap<Entity, DynamicBuffer<InstanceRaw>>,
    pub(crate) animated_bind_group_cache: HashMap<Entity, (u64, wgpu::BindGroup)>,
    pub(crate) static_bind_group_cache: HashMap<u64, wgpu::BindGroup>,
    pub(crate) last_morph_info_per_mesh: HashMap<u32, MorphTargetInfo>, // key = morph_deltas_offset

    pub active_camera: Arc<Mutex<Option<Entity>>>,

    pub selected_entities: Vec<Entity>,

    pub is_viewport_focused: bool,
    // is_cursor_locked: bool,
    pub window: Option<Arc<Window>>,

    pub show_new_project: bool,
    pub project_name: String,
    pub(crate) project_path: Arc<Mutex<Option<PathBuf>>>,
    pub pending_scene_switch: bool,

    pub gizmo: Gizmo,
    pub previously_selected_entity: Option<hecs::Entity>,
    pub selected_entity: Option<hecs::Entity>,
    pub viewport_mode: ViewportMode,
    pub viewport_drag: Option<crate::editor::dock::DragState>,

    pub(crate) signal: VecDeque<Signal>,
    pub(crate) undo_stack: Vec<UndoableAction>,
    // todo: add redo (later)
    // redo_stack: Vec<UndoableAction>,
    pub(crate) editor_state: EditorState,
    pub gizmo_mode: EnumSet<GizmoMode>,
    pub gizmo_orientation: GizmoOrientation,
    pub console: EucalyptusConsole,
    pub tab_registry: EditorTabRegistry,

    // might as well save some memory if its not required...
    // #[allow(unused)] // unused to allow for JVM to startup
    // pub(crate) script_manager: ScriptManager,
    /// State of the input
    pub(crate) input_state: Box<InputState>,

    // channels
    /// A threadsafe Unbounded Receiver, typically used for checking the status of the world loading
    pub progress_tx: Option<crossbeam_channel::Receiver<WorldLoadingStatus>>,
    /// Used to check if the world has been loaded in
    is_world_loaded: IsWorldLoadedYet,
    /// Used to fetch the current status of the loading, so it can be used for different
    /// egui loading windows or splash screens and such.
    pub current_state: WorldLoadingStatus,

    // handles for futures
    pub world_load_handle: Option<FutureHandle>,
    pub(crate) light_spawn_queue: Vec<FutureHandle>,
    pub(crate) pending_components: Vec<(hecs::Entity, FutureHandle)>,
    pub(crate) pending_model_swaps: Vec<(hecs::Entity, FutureHandle)>,
    pub world_receiver: Option<oneshot::Receiver<hecs::World>>,

    // building
    pub progress_rx: Option<Receiver<BuildStatus>>,
    pub handle_created: Option<FutureHandle>,
    pub build_logs: Vec<String>,
    pub build_progress: f32,
    pub show_build_window: bool,
    pub last_build_error: Option<String>,
    pub show_build_error_window: bool,

    // plugins
    pub plugin_registry: PluginRegistry,

    pub game_dock_state_shared: Option<Arc<Mutex<DockState<EditorTabId>>>>,
    pub ui_dock_state_shared: Option<Arc<Mutex<DockState<EditorTabId>>>>,

    // scene creation
    open_new_scene_window: bool,
    new_scene_name: String,
    new_scene_target_dir: Option<PathBuf>,
    current_scene_name: Option<String>,
    pending_scene_load: Option<PendingSceneLoad>,
    pending_scene_creation: Option<(String, PathBuf)>,

    // about
    nerd_stats: Rc<RwLock<NerdStats>>,

    // component registry
    pub component_registry: Arc<ComponentRegistry>,

    // play mode process tracking
    pub(crate) play_mode_process: Option<std::process::Child>,
    pub(crate) play_mode_pid: Option<u32>,
    pub(crate) play_mode_exit_rx: Option<std::sync::mpsc::Receiver<()>>,

    pub(crate) asset_clipboard: Option<AssetClipboard>,
    pub(crate) pending_aa_reload: Option<AntiAliasingMode>,

    pub(crate) last_active_camera_for_per_frame: Option<hecs::Entity>,
}

impl Editor {
    pub fn new() -> anyhow::Result<Self> {
        let mut tab_registry = EditorTabRegistry::new();
        crate::register_docks(&mut tab_registry);

        let viewport_tab = tab_registry
            .id_for_title("Viewport")
            .expect("Viewport tab must be registered");
        let entity_list_tab = tab_registry
            .id_for_title("Model/Entity List")
            .expect("Entity list tab must be registered");
        let resource_tab = tab_registry
            .id_for_title("Resource Inspector")
            .expect("Resource inspector tab must be registered");
        let asset_tab = tab_registry
            .id_for_title("Asset Viewer")
            .expect("Asset viewer tab must be registered");

        let tabs = vec![viewport_tab];
        let mut dock_state = DockState::new(tabs);

        let surface = dock_state.main_surface_mut();
        let [_old, right] = surface.split_right(NodeIndex::root(), 0.25, vec![resource_tab]);
        let [_old, _] = surface.split_left(NodeIndex::root(), 0.20, vec![entity_list_tab]);
        let [_old, _] = surface.split_below(right, 0.5, vec![asset_tab]);

        eucalyptus_core::utils::start_deadlock_detector();

        let mut plugin_registry = PluginRegistry::new();
        if let Err(e) = plugin_registry.load_plugins() {
            warn!("Failed to load plugins: {e}");
        }

        let mut component_registry = ComponentRegistry::new();
        register_components(&mut component_registry);

        for plugin in plugin_registry.plugins.values_mut() {
            let plugin_id = plugin.id().to_string();
            log::debug!("Located plugin: {}", plugin_id);
            // let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
            //     plugin.register_component(&mut component_registry);
            // }));

            // if result.is_ok() {
            //     log::info!("Registered components for plugin '{plugin_id}'");
            // } else {
            //     warn!("Plugin '{plugin_id}' panicked during component registration");
            // }
        }

        let component_registry = Arc::new(component_registry);

        Ok(Self {
            scene_command: SceneCommand::None,
            texture_id: None,
            size: Extent3d::default(),
            main_render_pipeline: None,
            shader_globals: None,
            color: Color::default(),
            is_viewport_focused: false,
            // is_cursor_locked: false,
            window: None,
            world: Box::new(World::new()),
            physics_state: PhysicsState::new(),
            game_editor_dock_state: dock_state,
            show_new_project: false,
            project_name: String::new(),
            project_path: Arc::new(Mutex::new(None)),
            pending_scene_switch: false,
            gizmo: Gizmo::default(),
            previously_selected_entity: None,
            selected_entity: None,
            viewport_mode: ViewportMode::None,
            viewport_drag: None,
            signal: VecDeque::new(),
            undo_stack: Vec::new(),
            // script_manager: ScriptManager::new()?,
            editor_state: EditorState::Editing,
            gizmo_mode: EnumSet::empty(),
            gizmo_orientation: GizmoOrientation::Global,
            console: EucalyptusConsole::new(None),
            tab_registry,
            input_state: Box::new(InputState::new()),
            light_cube_pipeline: None,
            active_camera: Arc::new(Mutex::new(None)),
            selected_entities: Vec::new(),
            progress_tx: None,
            is_world_loaded: IsWorldLoadedYet::new(),
            current_state: WorldLoadingStatus::Idle,
            world_load_handle: None,
            light_spawn_queue: vec![],
            pending_components: vec![],
            pending_model_swaps: vec![],
            world_receiver: None,
            progress_rx: None,
            handle_created: None,
            build_logs: Vec::new(),
            build_progress: 0.0,
            show_build_window: false,
            last_build_error: None,
            show_build_error_window: false,
            plugin_registry,
            game_dock_state_shared: None,
            ui_dock_state_shared: None,
            open_new_scene_window: false,
            new_scene_name: String::new(),
            new_scene_target_dir: None,
            current_scene_name: None,
            pending_scene_load: None,
            pending_scene_creation: None,
            nerd_stats: Rc::new(RwLock::new(NerdStats::default())),
            component_registry,
            play_mode_process: None,
            play_mode_pid: None,
            play_mode_exit_rx: None,
            asset_clipboard: None,
            pending_aa_reload: None,
            instance_buffer_cache: HashMap::new(),
            mipmapper: None,
            sky_pipeline: None,
            billboard_pipeline: None,
            kino: None,
            animation_pipeline: None,
            animated_instance_buffers: Default::default(),
            animated_bind_group_cache: Default::default(),
            static_bind_group_cache: Default::default(),
            dt: 60.0,
            ui_editor_dock_state: DockState::new(vec![]),
            current_page: EditorTabVisibility::GameEditor,
            ui_editor: UiEditor::new(),
            last_morph_info_per_mesh: Default::default(),
            last_active_camera_for_per_frame: None,
        })
    }

    pub(crate) fn unique_label_for_world(world: &World, base: &str) -> Label {
        Self::unique_label_for_world_with_extra(world, base, &HashSet::new())
    }

    pub(crate) fn unique_label_for_world_with_extra(
        world: &World,
        base: &str,
        extra_taken: &HashSet<String>,
    ) -> Label {
        fn parse_suffix(name: &str) -> Option<(&str, u32)> {
            let mut parts = name.rsplitn(2, '.');
            let suffix = parts.next()?;
            let root = parts.next()?;
            if suffix.len() == 3 && suffix.chars().all(|c| c.is_ascii_digit()) {
                if let Ok(value) = suffix.parse::<u32>() {
                    return Some((root, value));
                }
            }
            None
        }

        let mut existing = HashSet::new();
        for (_, label) in world.query::<(Entity, &Label)>().iter() {
            existing.insert(label.as_str().to_string());
        }
        existing.extend(extra_taken.iter().cloned());
        {
            let pending = crate::spawn::PENDING_SPAWNS.lock();
            for spawn in pending.iter() {
                existing.insert(spawn.scene_entity.label.as_str().to_string());
            }
        }

        if !existing.contains(base) {
            return Label::new(base);
        }

        let (root, base_suffix) = match parse_suffix(base) {
            Some((root, suffix)) => (root.to_string(), suffix),
            None => (base.to_string(), 0),
        };

        let mut max_suffix = base_suffix;
        for name in &existing {
            if name == &root {
                continue;
            }
            if let Some((candidate_root, suffix)) = parse_suffix(name) {
                if candidate_root == root {
                    max_suffix = max_suffix.max(suffix);
                }
            }
        }

        let mut next = max_suffix.max(1);
        loop {
            let candidate = format!("{root}.{next:03}");
            if !existing.contains(&candidate) {
                return Label::new(candidate.as_str());
            }
            next += 1;
        }
    }

    /// Collects `entity` and all of its descendants into a flat list of [`SceneEntity`]s
    /// (root-first, DFS), plus a map of `child_label -> parent_label` for the subtree.
    pub(crate) fn collect_entity_subtree(
        world: &World,
        entity: hecs::Entity,
        registry: &ComponentRegistry,
    ) -> (Vec<SceneEntity>, HashMap<Label, Label>) {
        let mut entities: Vec<SceneEntity> = Vec::new();
        let mut parent_map: HashMap<Label, Label> = HashMap::new();

        fn visit(
            world: &World,
            entity: hecs::Entity,
            registry: &ComponentRegistry,
            parent_label: Option<&Label>,
            entities: &mut Vec<SceneEntity>,
            parent_map: &mut HashMap<Label, Label>,
        ) {
            let Some(scene_entity) = SceneEntity::from_world(world, entity, registry) else {
                return;
            };

            if let Some(pl) = parent_label {
                parent_map.insert(scene_entity.label.clone(), pl.clone());
            }

            let own_label = scene_entity.label.clone();
            entities.push(scene_entity);

            let children: Vec<hecs::Entity> = world
                .get::<&Children>(entity)
                .map(|c| c.children().to_vec())
                .unwrap_or_default();

            for child in children {
                visit(
                    world,
                    child,
                    registry,
                    Some(&own_label),
                    entities,
                    parent_map,
                );
            }
        }

        visit(
            world,
            entity,
            registry,
            None,
            &mut entities,
            &mut parent_map,
        );
        (entities, parent_map)
    }

    fn double_key_pressed(&mut self, key: KeyCode) -> bool {
        let now = Instant::now();

        if let Some(last_time) = self.input_state.last_key_press_times.get(&key) {
            let time_diff = now.duration_since(*last_time);

            if time_diff <= self.input_state.double_press_threshold {
                self.input_state.last_key_press_times.remove(&key);
                return true;
            }
        }

        self.input_state.last_key_press_times.insert(key, now);
        false
    }

    /// Save the current world state to the active scene
    pub fn save_current_scene(&mut self) -> anyhow::Result<()> {
        log::debug!("Saving current scene");
        let mut scenes = SCENES.write();

        if scenes.is_empty() {
            return Err(anyhow::anyhow!("No scenes loaded to save"));
        }

        let target_scene_name = self
            .current_scene_name
            .clone()
            .or_else(|| scenes.first().map(|scene| scene.scene_name.clone()))
            .ok_or_else(|| anyhow::anyhow!("Unable to determine active scene"))?;

        let scene = scenes
            .iter_mut()
            .find(|scene| scene.scene_name == target_scene_name)
            .ok_or_else(|| anyhow::anyhow!("Active scene '{}' is not loaded", target_scene_name))?;

        scene.entities.clear();
        scene.hierarchy_map = SceneHierarchy::new();
        log::debug!(
            "Reset internal hierarchy map for scene {}",
            scene.scene_name
        );

        let mut entity_ids = self.world.query::<Entity>().iter().collect::<Vec<_>>();
        entity_ids.sort_by_key(|e| e.to_bits().get());

        for id in entity_ids {
            let Ok(label) = self.world.get::<&Label>(id) else {
                log::warn!("Skipping entity {:?} without Label during save", id);
                continue;
            };
            let entity_label = (*label).clone();

            let components = self
                .component_registry
                .extract_all_components(&self.world, id);

            if let Ok(children_comp) = self.world.get::<&Children>(id) {
                for &child_entity in children_comp.children() {
                    if let Ok(child_label) = self.world.get::<&Label>(child_entity) {
                        scene
                            .hierarchy_map
                            .set_parent(Label::new(child_label.as_str()), entity_label.clone());
                    } else {
                        log::warn!(
                            "Unable to resolve child entity {:?} for parent '{}' when saving scene",
                            child_entity,
                            entity_label
                        );
                    }
                }
            }

            let scene_entity = SceneEntity {
                label: entity_label,
                components,
                entity_id: Some(id),
            };

            log::debug!("Saved entity: {}", scene_entity.label);
            scene.entities.push(scene_entity);
        }

        log::info!(
            "Saved {} entities to scene '{}'",
            scene.entities.len(),
            scene.scene_name
        );

        let project_path = {
            let project = PROJECT.read();
            project.project_path.clone()
        };

        log::debug!("Writing scene to {}", project_path.display());
        scene.write_to(&project_path)?;
        log::debug!("Saved active scene '{}' to disk", scene.scene_name);

        Ok(())
    }

    pub fn save_project_config(&mut self) -> anyhow::Result<()> {
        log::debug!("starting save of project config");
        self.save_current_scene()?;

        {
            log::debug!("Writing to editor settings");
            let mut config = EDITOR_SETTINGS.write();
            config.game_editor_dock_state = Some(self.game_editor_dock_state.clone());
            config.ui_editor_dock_state = Some(self.ui_editor_dock_state.clone());
            config.save()?;
        }

        {
            log::debug!("Writing to project");
            let mut config = PROJECT.write();
            config.write_project_only()?;
        }

        Ok(())
    }

    /// The window when loading a project or a scene or anything that uses [`WorldLoadingStatus`]
    fn show_project_loading_window(&mut self, ctx: &egui::Context) {
        if let Some(ref mut rx) = self.progress_tx {
            match rx.try_recv() {
                Ok(status) => match status {
                    WorldLoadingStatus::LoadingEntity { index, name, total } => {
                        log::debug!("Loading entity: {} ({}/{})", name, index + 1, total);
                        self.current_state =
                            WorldLoadingStatus::LoadingEntity { index, name, total };
                    }
                    WorldLoadingStatus::Completed => {
                        log::debug!(
                            "Received WorldLoadingStatus::Completed - project loading finished"
                        );
                        self.is_world_loaded.mark_project_loaded();
                        self.current_state = WorldLoadingStatus::Completed;
                        self.progress_tx = None;
                        log::debug!("Returning back");
                        return;
                    }
                    WorldLoadingStatus::Idle => {
                        log::debug!("Project loading is idle");
                    }
                },
                Err(_) => {
                    // log::debug!("Unable to receive the progress: {}", e);
                }
            }
        } else {
            log::debug!("No progress receiver available");
        }

        egui::Window::new("Loading Project")
            .collapsible(false)
            .resizable(false)
            .anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0])
            .fixed_size([300.0, 100.0])
            .show(ctx, |ui| {
                ui.vertical_centered(|ui| {
                    ui.add_space(10.0);
                    ui.horizontal(|ui| {
                        ui.spinner();
                        ui.label("Loading...");
                    });
                    // ui.add_space(5.0);
                    // ui.add(egui::ProgressBar::new(progress).text(format!("{:.0}%", progress * 100.0)));
                    match &self.current_state {
                        WorldLoadingStatus::Idle => {
                            ui.label("Initialising...");
                        }
                        WorldLoadingStatus::LoadingEntity { name, .. } => {
                            ui.label(format!("Loading entity: {}", name));
                        }
                        WorldLoadingStatus::Completed => {
                            ui.label("Done!");
                        }
                    }
                });
            });
    }

    /// Loads the project config.
    ///
    /// It uses an unbounded sender to send messages back to the receiver so it can
    /// be used within threads.
    pub async fn load_project_config(
        graphics: Arc<SharedGraphicsContext>,
        sender: Option<Sender<WorldLoadingStatus>>,
        world: &mut World,
        world_sender: Option<oneshot::Sender<hecs::World>>,
        active_camera: Arc<Mutex<Option<hecs::Entity>>>,
        project_path: Arc<Mutex<Option<PathBuf>>>,
        game_dock_state: Arc<Mutex<DockState<EditorTabId>>>,
        ui_dock_state: Arc<Mutex<DockState<EditorTabId>>>,
        component_registry: Arc<ComponentRegistry>,
    ) -> anyhow::Result<()> {
        {
            let config = PROJECT.read();
            let mut path = project_path.lock();
            *path = Some(config.project_path.clone());

            let layout = EDITOR_SETTINGS.read();

            if let Some(layout) = &layout.game_editor_dock_state {
                let mut dock = game_dock_state.lock();
                let layout = layout.clone();
                *dock = layout.clone();
            }

            if let Some(layout) = &layout.ui_editor_dock_state {
                let mut dock = ui_dock_state.lock();
                let layout = layout.clone();
                *dock = layout.clone();
            }
        }

        let last_scene = {
            let config = PROJECT.read();
            config.last_opened_scene.clone()
        };

        let first_scene_opt = {
            let scenes = SCENES.read();
            if let Some(scene_name) = last_scene {
                scenes
                    .iter()
                    .find(|scene| scene.scene_name == scene_name)
                    .cloned()
            } else {
                scenes.first().cloned()
            }
        };

        {
            if let Some(mut first_scene) = first_scene_opt {
                let cam = first_scene
                    .load_into_world(
                        world,
                        graphics,
                        &component_registry.clone(),
                        sender.clone(),
                        false,
                    )
                    .await?;
                let mut a_c = active_camera.lock();
                *a_c = Some(cam);

                log::info!(
                    "Successfully loaded scene with {} entities",
                    first_scene.entities.len(),
                );
            } else {
                let existing_debug_camera = {
                    world
                        .query::<(Entity, &Camera, &CameraComponent)>()
                        .iter()
                        .find_map(|(entity, _, component)| {
                            if matches!(component.camera_type, CameraType::Debug) {
                                Some(entity)
                            } else {
                                None
                            }
                        })
                };

                if let Some(camera_entity) = existing_debug_camera {
                    log::info!("Using existing debug camera");
                    let mut a_c = active_camera.lock();
                    *a_c = Some(camera_entity);
                } else {
                    log::info!("No scenes found, creating default debug camera");

                    let debug_camera = Camera::predetermined(graphics, Some("Debug Camera"));
                    let component = DebugCamera::new();

                    {
                        let e = world.spawn((Label::from("Debug Camera"), debug_camera, component));
                        let mut a_c = active_camera.lock();
                        *a_c = Some(e);
                    }
                }
            }
        }

        if let Some(ref s) = sender.clone() {
            let _ = s.send(WorldLoadingStatus::Completed);
        }

        if let Some(ws) = world_sender {
            let _ = ws.send(std::mem::take(world));
        }

        Ok(())
    }

    fn cleanup_scene_resources(
        &mut self,
        graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>,
    ) {
        if let Some(handle) = self.world_load_handle.take() {
            graphics.future_queue.cancel(&handle);
        }

        self.light_spawn_queue.clear();
        self.progress_tx = None;
        self.world_receiver = None;
        self.current_state = WorldLoadingStatus::Idle;

        self.world.clear();
        self.selected_entity = None;
        self.previously_selected_entity = None;
        self.active_camera.lock().take();

        self.main_render_pipeline = None;
        self.shader_globals = None;
        self.texture_id = None;
        self.light_cube_pipeline = None;
    }

    fn start_async_scene_load(
        &mut self,
        mut scene: SceneConfig,
        graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>,
    ) {
        self.cleanup_scene_resources(graphics.clone());

        let (progress_sender, progress_receiver) = unbounded::<WorldLoadingStatus>();
        self.progress_tx = Some(progress_receiver);
        self.current_state = WorldLoadingStatus::Idle;

        let (world_sender, world_receiver) = oneshot::channel();
        self.world_receiver = Some(world_receiver);

        self.is_world_loaded = IsWorldLoadedYet::new();
        self.is_world_loaded.mark_scene_loaded();

        let graphics_shared = graphics.clone();
        let active_camera = self.active_camera.clone();
        let scene_name = scene.scene_name.clone();
        let component_registry_clone = self.component_registry.clone();

        let handle = graphics.future_queue.push(async move {
            let mut temp_world = World::new();

            let load_result = scene
                .load_into_world(
                    &mut temp_world,
                    graphics_shared.clone(),
                    &component_registry_clone,
                    Some(progress_sender.clone()),
                    false,
                )
                .await;

            match load_result {
                Ok(active_entity) => {
                    let mut camera_lock = active_camera.lock();
                    *camera_lock = Some(active_entity);
                }
                Err(err) => {
                    log::error!("Failed to load scene '{}': {}", scene_name, err);
                }
            }

            let _ = progress_sender.send(WorldLoadingStatus::Completed);

            if world_sender.send(temp_world).is_err() {
                log::error!("Failed to deliver loaded world for scene '{}'", scene_name);
            }
        });

        self.world_load_handle = Some(handle);
    }

    fn create_new_scene(&mut self, name: &str, target_dir: PathBuf) -> anyhow::Result<()> {
        let trimmed_name = name.trim();
        if trimmed_name.is_empty() {
            return Err(anyhow::anyhow!("Scene name cannot be empty"));
        }

        if trimmed_name.contains('/') || trimmed_name.contains('\\') || trimmed_name.contains(':') {
            return Err(anyhow::anyhow!(
                "Scene name cannot contain path separator characters"
            ));
        }

        let project_root = {
            let cfg = PROJECT.read();
            cfg.project_path.clone()
        };

        if project_root.as_os_str().is_empty() {
            return Err(anyhow::anyhow!("Project path is not set"));
        }

        if !target_dir.starts_with(&project_root) {
            return Err(anyhow::anyhow!("Target directory is outside the project"));
        }

        let target_path = target_dir.join(format!("{}.eucs", trimmed_name));
        if target_path.exists() {
            return Err(anyhow::anyhow!("Scene '{}' already exists", trimmed_name));
        }

        let scene_config = SceneConfig::new(trimmed_name.to_string(), &target_path);
        scene_config.write_to_path()?;

        self.queue_scene_load_from_path(&target_path)?;
        success!("Created scene '{}'", trimmed_name);
        Ok(())
    }

    fn queue_scene_load_from_path(&mut self, path: &std::path::Path) -> anyhow::Result<()> {
        let should_persist_current = self.current_scene_name.is_some()
            && self.is_world_loaded.is_fully_loaded()
            && self.world.len() > 0
            && {
                let scenes = SCENES.read();
                !scenes.is_empty()
            };

        if should_persist_current {
            self.save_current_scene()?;
        }

        if let Some(current) = self.current_scene_name.as_deref() {
            states::unload_scene(current);
        }

        let scene = SceneConfig::read_from(path)?;

        {
            let mut scenes = SCENES.write();
            scenes.retain(|existing| existing.scene_name != scene.scene_name);
            scenes.insert(0, scene.clone());
        }

        {
            let mut project = PROJECT.write();
            project.last_opened_scene = Some(scene.scene_name.clone());
            project.write_to_all()?;
        }

        log::info!("Scene '{}' staged for loading", scene.scene_name);

        self.current_scene_name = Some(scene.scene_name.clone());
        self.pending_scene_load = Some(PendingSceneLoad { scene });

        Ok(())
    }

    fn open_scene_from_path(&mut self, path: PathBuf) -> anyhow::Result<()> {
        if path
            .extension()
            .and_then(|ext| ext.to_str())
            .map(|ext| ext.eq_ignore_ascii_case("eucs"))
            != Some(true)
        {
            return Err(anyhow::anyhow!("Selected file is not an .eucs scene"));
        }

        let project_root = {
            let cfg = PROJECT.read();
            cfg.project_path.clone()
        };

        if project_root.as_os_str().is_empty() {
            return Err(anyhow::anyhow!("Project path is not set"));
        }

        if !path.starts_with(&project_root) {
            return Err(anyhow::anyhow!(
                "Scene '{}' is outside of the current project",
                path.display()
            ));
        }

        info!("Loading scene from path: {}", path.display());
        self.queue_scene_load_from_path(&path)
    }

    pub fn show_ui(&mut self, ui: &mut Ui, graphics: Arc<SharedGraphicsContext>) {
        puffin::profile_function!();

        {
            puffin::profile_scope!("show_ui.pending_scene_creation");
            if let Some((scene_name, target_dir)) = self.pending_scene_creation.take() {
                let result = self.create_new_scene(scene_name.as_str(), target_dir);
                self.new_scene_name.clear();
                if let Err(e) = result {
                    fatal!("Failed to create scene '{}': {}", scene_name, e);
                }
            }
        }

        egui::Panel::top("menu_bar").show_inside(ui, |ui| {
            puffin::profile_scope!("show_ui.top_bar");
            let top_bar_rect = ui.max_rect();

            ui.horizontal(|ui| {
                egui::MenuBar::new().ui(ui, |ui| {
                    ui.menu_button("File", |ui| {
                    if ui.button("New Scene").clicked() {
                        self.open_new_scene_window = true;
                    }

                    if ui.button("Open Scene").clicked() {
                        let scenes_dir = {
                            let project = PROJECT.read();
                            project.project_path.join("scenes")
                        };

                        let mut dialog = FileDialog::new();
                        if scenes_dir.exists() {
                            dialog = dialog.set_directory(&scenes_dir);
                        }

                        let dialog = dialog.add_filter("Eucalyptus Scenes", &["eucs"]);

                        if let Some(path) = dialog.pick_file() {
                            if let Err(e) = self.open_scene_from_path(path) {
                                fatal!("Failed to open scene: {}", e);
                            }
                        }
                    }

                    if ui.button("Save").clicked() {
                        match self.save_project_config() {
                            Ok(_) => {}
                            Err(e) => {
                                fatal!("Error saving project: {}", e);
                            }
                        }
                        success!("Successfully saved project");
                    }
                    if ui.button("Reveal project").clicked() {
                        let project_path = { PROJECT.read().project_path.clone() };
                        match open::that(project_path) {
                            Ok(()) => info!("Revealed project"),
                            Err(e) => warn!("Unable to open project: {}", e),
                        }
                    }
                    ui.separator();
                    {
                        if ui.button("Editor Settings").clicked() {
                            debug!("Editor settings");
                            let window_data = DropbearWindowBuilder::new()
                                .with_attributes(
                                    WindowAttributes::default()
                                        .with_title("eucalyptus editor - settings"),
                                )
                                .add_scene_with_input(
                                    Rc::new(RwLock::new(EditorSettingsWindow::new())),
                                    "editor_settings",
                                )
                                .set_initial_scene("editor_settings")
                                .build();
                            self.scene_command = SceneCommand::RequestWindow(window_data);
                            debug!("Requested editor settings window");
                        };
                        if ui
                            .button(format!("{} Settings", PROJECT.read().project_name.clone()))
                            .clicked()
                        {
                            debug!("Project Settings");
                            let window_data = DropbearWindowBuilder::new()
                                .with_attributes(WindowAttributes::default().with_title(format!(
                                    "{} - settings",
                                    PROJECT.read().project_name.clone()
                                )))
                                .add_scene_with_input(
                                    Rc::new(RwLock::new(ProjectSettingsWindow::new())),
                                    "project_settings_window",
                                )
                                .set_initial_scene("project_settings_window")
                                .build();
                            self.scene_command = SceneCommand::RequestWindow(window_data);
                            debug!("Requested project settings window");
                        };
                    }
                    ui.separator();
                    if matches!(self.editor_state, EditorState::Playing) {
                        if ui.button("Stop").clicked() {
                            self.signal.push_back(Signal::StopPlaying);
                        }
                    } else if ui.button("Play").clicked() {
                        // run prechecks to ensure a starting camera exists and stuff
                        let mut found_starting = false;
                        for (_, comp) in self.world.query::<(&Camera, &CameraComponent)>().iter() {
                            if comp.starting_camera {
                                found_starting = true;
                            }
                        }

                        if !found_starting {
                            fatal!("Unable to start play mode: No initial camera set for this scene");
                        } else {
                            self.signal.push_back(Signal::Play);
                        }
                    }
                    ui.menu_button("Export", |ui| {
                        // todo: create a window for better build menu
                        if ui.button("Build").clicked() {
                            {
                                let proj = PROJECT.read();
                                match build(
                                    proj.project_path
                                        .join(format!("{}.eucp", proj.project_name.clone()))
                                        .clone(),
                                ) {
                                    Ok(thingy) => {
                                        success!("Project output at {}", thingy.display())
                                    }
                                    Err(e) => {
                                        fatal!(
                                            "Unable to build project [{}]: {}",
                                            proj.project_path.clone().display(),
                                            e
                                        );
                                    }
                                }
                            }
                        }
                        ui.label("Package"); // todo: create a window for label
                    });
                    ui.separator();
                    if ui.button("Quit").clicked() {
                        match self.save_project_config() {
                            Ok(_) => {
                                log::info!("Saved, quitting...");
                                self.scene_command = SceneCommand::Quit(None);
                            }
                            Err(e) => {
                                fatal!("Error saving project: {}", e);
                            }
                        }
                        success!("Successfully saved project");
                    }
                    });
                    ui.menu_button("Edit", |ui| {
                    if ui.button("Copy").clicked() {
                        let entities_to_copy: Vec<Entity> = if !self.selected_entities.is_empty() {
                            self.selected_entities.clone()
                        } else if let Some(e) = self.selected_entity {
                            vec![e]
                        } else {
                            vec![]
                        };
                        if entities_to_copy.is_empty() {
                            warn!("Unable to copy entity: None selected");
                        } else {
                            let sel_bits: HashSet<u64> = entities_to_copy.iter()
                                .map(|e| e.to_bits().get()).collect();
                            let mut all_entities = Vec::new();
                            let mut all_parent_map = HashMap::new();
                            let mut seen_labels: HashSet<String> = HashSet::new();
                            for entity in &entities_to_copy {
                                let mut skip = false;
                                let mut cur = *entity;
                                while let Ok(p) = self.world.get::<&Parent>(cur) {
                                    let pe = p.parent();
                                    if sel_bits.contains(&pe.to_bits().get()) { skip = true; break; }
                                    cur = pe;
                                }
                                if skip { continue; }
                                let (sub_e, sub_pm) = Editor::collect_entity_subtree(
                                    self.world.as_ref(), *entity, &self.component_registry);
                                for se in sub_e {
                                    if seen_labels.insert(se.label.to_string()) {
                                        all_entities.push(se);
                                    }
                                }
                                all_parent_map.extend(sub_pm);
                            }
                            if all_entities.is_empty() {
                                warn!("Unable to copy entity: Unable to obtain label");
                            } else {
                                self.signal.retain(|s| !matches!(s, Signal::Copy(_, _)));
                                self.signal.push_back(Signal::Copy(all_entities, all_parent_map));
                                info!("Copied {} entity(ies)!", entities_to_copy.len());
                            }
                        }
                    }

                    if ui.button("Paste").clicked() {
                        let clipboard = self.signal.iter().find_map(|s| {
                            if let Signal::Copy(entities, parent_map) = s {
                                Some((entities.clone(), parent_map.clone()))
                            } else {
                                None
                            }
                        });

                        match clipboard {
                            Some((entities, parent_map)) => {
                                let paste_parent = self.selected_entity
                                    .and_then(|e| self.world.get::<&Label>(e).ok().map(|l| (*l).clone()));
                                self.signal.push_back(Signal::Paste(entities, parent_map, paste_parent));
                            }
                            None => {
                                warn!("Unable to paste: You haven't selected anything!");
                            }
                        }
                    }

                    if ui.button("Undo").clicked() {
                        self.signal.push_back(Signal::Undo);
                    }
                    ui.label("Redo");
                    });

                    ui.menu_button("Window", |ui_window| {
                    let mut enabled_docks = Vec::new();
                    let mut disabled_docks = Vec::new();

                    for (k, v) in self.tab_registry.descriptors.iter() {
                        if v.visibility.intersects(self.current_page) {
                            enabled_docks.push((*k, v));
                        } else {
                            disabled_docks.push((*k, v));
                        }
                    }

                    for (k, v) in &enabled_docks {
                        if ui_window.button(v.title.as_str()).clicked() {
                            if self.current_page.contains(EditorTabVisibility::GameEditor) {
                                self.game_editor_dock_state.push_to_first_leaf(*k);
                            } else if self.current_page.contains(EditorTabVisibility::UIEditor) {
                                self.ui_editor_dock_state.push_to_first_leaf(*k);
                            } else {
                                fatal!("Unable to open dock: no page is open right now, likely editor bug");
                            }
                        }
                    }

                    if !enabled_docks.is_empty() && !disabled_docks.is_empty() {
                        ui_window.separator();
                    }

                    for (_k, v) in &disabled_docks {
                        let mut available_locations = Vec::new();
                        if v.visibility.contains(EditorTabVisibility::GameEditor) {
                            available_locations.push("Game");
                        }
                        if v.visibility.contains(EditorTabVisibility::UIEditor) {
                            available_locations.push("UI");
                        }

                        let location_list = available_locations.join(", ");
                        let response = ui_window.add_enabled(false, egui::Button::new(v.title.as_str()));
                        response.on_hover_text(format!(
                            "Dock only available on [{}]",
                            location_list
                        ));
                    }
                    });

                    ui.menu_button("Assets", |ui| {
                        if ui.button("Flush unused assets").clicked() {
                            self.signal.push_back(Signal::FlushUnusedAssets);
                        }
                    });
                    ui.menu_button("Help", |ui| {
                    if ui.button("Show AppData folder").clicked() {
                        match app_dirs2::app_root(app_dirs2::AppDataType::UserData, &APP_INFO) {
                            Ok(val) => match open::that(&val) {
                                Ok(()) => info!("Opened logs folder"),
                                Err(e) => fatal!("Unable to open {}: {}", val.display(), e),
                            },
                            Err(e) => {
                                fatal!("Unable to show logs: {}", e);
                            }
                        };
                    }

                    if ui.button("Nerd Stats").clicked() {
                        log::debug!("Requested nerd stats window");

                        self.nerd_stats.write().show_window = true;
                    }

                    if ui.button("About").clicked() {
                        log::debug!("About window requested to be opened");
                        let about = Rc::new(RwLock::new(AboutWindow::new()));
                        let window = DropbearWindowBuilder::new()
                            .with_attributes(
                                WindowAttributes::default()
                                    .with_title("About eucalyptus editor")
                                    .with_inner_size(PhysicalSize::new(500, 300))
                                    .with_resizable(false),
                            )
                            .add_scene_with_input(about, "about")
                            .set_initial_scene("about")
                            .build();
                        self.scene_command = SceneCommand::RequestWindow(window);
                    }
                    });

                    {
                        let cfg = EDITOR_SETTINGS.read();
                        if cfg.is_debug_menu_shown {
                            debug::show_menu_bar(ui, &mut self.signal);
                        }
                    }
                });

            });

            let center_rect = egui::Rect::from_center_size(
                top_bar_rect.center(),
                egui::vec2(160.0, top_bar_rect.height()),
            );

            ui.scope_builder(egui::UiBuilder::new().max_rect(center_rect), |ui| {
                puffin::profile_scope!("show_ui.page_switcher");
                ui.with_layout(
                    egui::Layout::centered_and_justified(egui::Direction::LeftToRight),
                    |ui| {
                        ui.horizontal(|ui| {
                            let game_selected =
                                self.current_page.contains(EditorTabVisibility::GameEditor);
                            if ui.selectable_label(game_selected, "Game").clicked() {
                                self.current_page = EditorTabVisibility::GameEditor;
                            }

                            ui.label("|");

                            let ui_selected =
                                self.current_page.contains(EditorTabVisibility::UIEditor);
                            if ui.selectable_label(ui_selected, "UI").clicked() {
                                self.current_page = EditorTabVisibility::UIEditor;
                            }
                        });
                    },
                );
            });

            let right_rect = egui::Rect::from_min_max(
                egui::pos2(top_bar_rect.right() - 96.0, top_bar_rect.top()),
                egui::pos2(top_bar_rect.right() - 8.0, top_bar_rect.bottom()),
            );

            ui.scope_builder(egui::UiBuilder::new().max_rect(right_rect), |ui| {
                puffin::profile_scope!("show_ui.play_controls");
                ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
                    let can_play = matches!(self.editor_state, EditorState::Playing);
                    ui.group(|ui| {
                        ui.add_enabled_ui(can_play, |ui| {
                            if ui.button("⏹").clicked() {
                                log::debug!("Menu button Stop button pressed");
                                self.signal.push_back(Signal::StopPlaying);
                            }
                        });

                        ui.add_enabled_ui(!can_play, |ui| {
                            if ui.button("▶").clicked() {
                                log::debug!("Menu Button Play button pressed");
                                let mut found_starting = false;
                                for (_, comp) in self
                                    .world
                                    .query::<(&Camera, &CameraComponent)>()
                                    .iter()
                                {
                                    if comp.starting_camera {
                                        found_starting = true;
                                    }
                                }

                                if !found_starting {
                                    fatal!("Unable to start play mode: No initial camera set for this scene");
                                } else {
                                    self.signal.push_back(Signal::Play);
                                }
                            }
                        });
                    });
                });
            });
        });

        egui::Panel::bottom("status bar")
            .resizable(false)
            .show_inside(ui, |ui| {
                puffin::profile_scope!("show_ui.status_bar");
                let active_camera = *self.active_camera.lock();
                let (label, is_debug, active_entity) = if let Some(entity) = active_camera {
                    let camera_label = self
                        .world
                        .get::<&Label>(entity)
                        .map(|label| label.as_str().to_string())
                        .ok()
                        .or_else(|| {
                            self.world
                                .get::<&Camera>(entity)
                                .map(|camera| camera.label.clone())
                                .ok()
                        })
                        .unwrap_or_else(|| "Camera".to_string());
                    let is_debug = self
                        .world
                        .get::<&CameraComponent>(entity)
                        .map(|comp| matches!(comp.camera_type, CameraType::Debug))
                        .unwrap_or(false);
                    (camera_label, is_debug, Some(entity))
                } else {
                    ("Viewport Camera".to_string(), true, None)
                };

                let selected_entity = self.selected_entity;
                let selected_has_camera = selected_entity
                    .and_then(|entity| {
                        self.world
                            .query_one::<(&Camera, &CameraComponent)>(entity)
                            .get()
                            .ok()
                            .map(|_| entity)
                    })
                    .is_some();

                let is_selected_active = selected_entity
                    .and_then(|entity| active_entity.map(|active| (entity, active)))
                    .map(|(selected, active)| selected == active)
                    .unwrap_or(false);

                let text_color = if is_debug {
                    ui.visuals().weak_text_color()
                } else if selected_has_camera && is_selected_active {
                    egui::Color32::from_rgb(80, 200, 120)
                } else if selected_has_camera {
                    egui::Color32::from_rgb(245, 230, 160)
                } else {
                    ui.visuals().weak_text_color()
                };

                ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
                    if self.current_page.contains(EditorTabVisibility::GameEditor) {
                        ui.colored_label(text_color, format!("Viewing through {label}"));
                    } else if self.current_page.contains(EditorTabVisibility::UIEditor) {
                        if let Some(e) = self.ui_editor.active_entity {
                            let label = self.world.get::<&Label>(e);
                            if let Ok(l) = label {
                                ui.colored_label(text_color, format!("Editing {l}"));
                            } else {
                                ui.colored_label(
                                    egui::Color32::from_rgb(255, 0, 0),
                                    format!("error: unable to fetch label for entity {:?}", e),
                                );
                            }
                        } else {
                            ui.label("Not editing anything");
                        }
                    }
                });
            });

        let Some(view) = self.texture_id else {
            egui::CentralPanel::default().show_inside(ui, |ui| {
                puffin::profile_scope!("show_ui.viewport_initialising");
                ui.centered_and_justified(|ui| {
                    ui.label("Viewport is still initialising...");
                });
            });
            return;
        };

        egui::CentralPanel::default().show_inside(ui, |ui| {
            puffin::profile_scope!("show_ui.dock_area");
            DockArea::new(
                if self.current_page.contains(EditorTabVisibility::GameEditor) {
                    &mut self.game_editor_dock_state
                } else if self.current_page.contains(EditorTabVisibility::UIEditor) {
                    &mut self.ui_editor_dock_state
                } else {
                    panic!(
                        "Unable to locate create dock area: current page not set, likely editor bug"
                    );
                },
            )
            .style(Style::from_egui(ui.style().as_ref()))
            .show_inside(
                ui,
                &mut EditorTabViewer {
                    view,
                    graphics: graphics.clone(),
                    gizmo: &mut self.gizmo,
                    tex_size: self.size,
                    world: &mut self.world,
                    selected_entity: &mut self.selected_entity,
                    selected_entities: &mut self.selected_entities,
                    viewport_mode: &mut self.viewport_mode,
                    undo_stack: &mut self.undo_stack,
                    signal: &mut self.signal,
                    active_camera: &mut self.active_camera,
                    gizmo_mode: &mut self.gizmo_mode,
                    gizmo_orientation: &mut self.gizmo_orientation,
                    editor_mode: &mut self.editor_state,
                    plugin_registry: &mut self.plugin_registry,
                    build_logs: &mut self.build_logs,
                    component_registry: &self.component_registry,
                    tab_registry: &self.tab_registry,
                    eucalyptus_console: &mut self.console,
                    current_scene_name: &mut self.current_scene_name,
                    ui_editor: &mut self.ui_editor,
                    viewport_drag: &mut self.viewport_drag,
                },
            );
        });

        {
            let mut project_path = self.project_path.lock();
            crate::utils::show_new_project_window(
                ui.ctx(),
                &mut self.show_new_project,
                &mut self.project_name,
                &mut project_path,
                |name, path| {
                    crate::utils::start_project_creation(name.to_string(), Some(path.clone()));
                    self.pending_scene_switch = true;
                },
            );
        }

        if self.pending_scene_switch {
            self.scene_command = SceneCommand::SwitchScene("editor".to_string());
            self.pending_scene_switch = false;
        }

        self.show_nerd_stats_window(ui.ctx());

        let mut open_flag = self.open_new_scene_window;
        let mut close_requested = false;
        if open_flag {
            egui::Window::new("New Scene").open(&mut open_flag).show(
                ui.ctx(),
                |ui| {
                    ui.vertical(|ui| {
                        ui.label("Name: ");
                        ui.text_edit_singleline(&mut self.new_scene_name);

                        ui.add_space(4.0);

                        let scenes_root = {
                            let project = PROJECT.read();
                            project.project_path.join("resources").join("scenes")
                        };
                        let target_dir =
                            self.new_scene_target_dir.as_deref().unwrap_or(&scenes_root);
                        let display_path = target_dir
                            .strip_prefix(PROJECT.read().project_path.as_path())
                            .unwrap_or(target_dir)
                            .display()
                            .to_string();

                        ui.label("Folder:");
                        ui.horizontal(|ui| {
                            ui.label(&display_path);
                            if ui.button("Browse…").clicked() {
                                let mut dialog = FileDialog::new();
                                if scenes_root.exists() {
                                    dialog = dialog.set_directory(&scenes_root);
                                }
                                if let Some(dir) = dialog.pick_folder() {
                                    self.new_scene_target_dir = Some(dir);
                                }
                            }
                            if self.new_scene_target_dir.is_some() && ui.button("Reset").clicked() {
                                self.new_scene_target_dir = None;
                            }
                        });

                        ui.add_space(4.0);

                        if ui.button("Create").clicked() {
                            let dir = self.new_scene_target_dir.clone().unwrap_or(scenes_root);
                            self.pending_scene_creation = Some((self.new_scene_name.clone(), dir));
                            close_requested = true;
                        }
                    });
                },
            );
        }

        if close_requested {
            open_flag = false;
        }

        self.open_new_scene_window = open_flag;
    }

    fn show_nerd_stats_window(&mut self, ctx: &egui::Context) {
        let mut stats = self.nerd_stats.write();
        let mut open_flag = stats.show_window;

        if open_flag {
            egui::Window::new("Nerd Stats")
                .resizable(true)
                .collapsible(false)
                .default_size([600.0, 500.0])
                .open(&mut open_flag)
                .show(ctx, |ui| {
                    stats.content(ui);
                });
        }

        stats.show_window = open_flag;
    }

    pub fn switch_to_debug_camera(&mut self) {
        let debug_camera = self
            .world
            .query::<(Entity, &Camera, &CameraComponent)>()
            .iter()
            .find_map(|(e, _, comp)| {
                if matches!(comp.camera_type, CameraType::Debug) {
                    Some(e)
                } else {
                    None
                }
            });

        if let Some(camera_entity) = debug_camera {
            let mut active_camera = self.active_camera.lock();
            *active_camera = Some(camera_entity);
            info!("Switched to debug camera");
        } else {
            warn!("No debug camera found in the world");
        }
    }

    pub fn switch_to_player_camera(&mut self) {
        let player_camera = self
            .world
            .query::<(Entity, &Camera, &CameraComponent)>()
            .iter()
            .find_map(
                |(e, _, comp)| {
                    if comp.starting_camera { Some(e) } else { None }
                },
            );

        if let Some(camera_entity) = player_camera {
            let mut active_camera = self.active_camera.lock();
            *active_camera = Some(camera_entity);
            info!("Switched to player camera");
        } else {
            warn!("No player camera found in the world");
        }
    }

    pub fn is_using_debug_camera(&self) -> bool {
        let active_camera = self.active_camera.lock();
        if let Some(active_camera_entity) = *active_camera
            && let Ok(component) = self
                .world
                .query_one::<&CameraComponent>(active_camera_entity)
                .get()
        {
            return matches!(component.camera_type, CameraType::Debug);
        }
        false
    }

    /// Loads all the wgpu resources such as renderer.
    ///
    /// **Note**: To be ran AFTER [`Editor::load_project_config`]
    pub fn load_wgpu_nerdy_stuff<'a>(
        &mut self,
        graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>,
        skybox_texture: Option<&Vec<u8>>,
    ) {
        let mut main_render_pipeline = MainRenderPipeline::new(graphics.clone());
        let light_cube_pipeline = LightCubePipeline::new(graphics.clone());
        self.shader_globals = Some(GlobalsUniform::new(
            graphics.clone(),
            Some("editor shader globals"),
        ));
        self.mipmapper = None;
        self.billboard_pipeline = Some(BillboardPipeline::new(graphics.clone()));
        *graphics.debug_draw.lock() =
            Some(dropbear_engine::debug::DebugDraw::new(graphics.clone()));
        self.kino = Some(KinoState::new(
            KinoWGPURenderer::new(
                &graphics.device,
                &graphics.queue,
                graphics.hdr.read().format(),
                [
                    graphics.viewport_texture.size.width as f32,
                    graphics.viewport_texture.size.height as f32,
                ],
            ),
            KinoWinitWindowing::new(graphics.window.clone(), None),
        ));

        self.animation_pipeline = None;

        self.texture_id = Some((*graphics.texture_id).clone());
        self.window = Some(graphics.window.clone());
        self.is_world_loaded.mark_rendering_loaded();

        let mut pending_sky_pipeline = None;

        let active_camera = self.active_camera.lock().clone();
        if let Some(camera_entity) = active_camera {
            if let Ok(camera) = self.world.query_one::<&Camera>(camera_entity).get() {
                self.animation_pipeline = Some(AnimationDefaults::new(graphics.clone()));

                if let Some(globals) = self.shader_globals.as_ref() {
                    let _ = main_render_pipeline.per_frame_bind_group(
                        graphics.clone(),
                        globals.buffer.buffer(),
                        camera.buffer(),
                        light_cube_pipeline.light_buffer(),
                    );
                }

                let sky_texture_result = HdrLoader::from_equirectangular_bytes(
                    &graphics.device,
                    &graphics.queue,
                    skybox_texture.map_or(DEFAULT_SKY_TEXTURE, |v| v.as_slice()),
                    1080,
                    Some("sky texture"),
                );

                match sky_texture_result {
                    Ok(sky_texture) => {
                        pending_sky_pipeline = Some(SkyPipeline::new(
                            graphics.clone(),
                            sky_texture,
                            camera.buffer(),
                        ));
                    }
                    Err(e) => {
                        error!("Failed to load sky texture: {}", e);
                    }
                }
            } else {
                error!("Unable to create editor bind groups without an active camera component");
            }
        } else {
            error!("Unable to create editor bind groups without an active camera");
        }

        if let Some(sky_pipeline) = pending_sky_pipeline {
            self.sky_pipeline = Some(sky_pipeline);
        }

        // leave to last
        self.main_render_pipeline = Some(main_render_pipeline);
        self.light_cube_pipeline = Some(light_cube_pipeline);
    }

    /// Initialises another eucalyptus-editor play mode app as a separate process and monitors it in a separate thread.
    pub fn load_play_mode(&mut self) -> anyhow::Result<()> {
        use std::process::{Command, Stdio};
        use std::sync::mpsc::channel;
        use std::thread;

        let current_exe = std::env::current_exe()
            .map_err(|e| anyhow::anyhow!("Failed to get current executable path: {}", e))?;

        let project_dir = {
            let cfg = PROJECT.read();
            cfg.project_path.clone()
        };

        let current_scene = self
            .current_scene_name
            .clone()
            .ok_or_else(|| anyhow::anyhow!("No current scene loaded; cannot launch play mode"))?;

        log::info!(
            "Launching play mode: {} play --project {:?}",
            current_exe.display(),
            project_dir
        );

        let mut child = Command::new(&current_exe)
            .arg("play")
            .arg(&project_dir)
            .arg(&current_scene)
            .stdin(Stdio::null())
            .stdout(Stdio::inherit())
            .stderr(Stdio::inherit())
            .spawn()
            .map_err(|e| anyhow::anyhow!("Failed to spawn play mode process: {}", e))?;

        let pid = child.id();
        log::info!("Play mode process started with PID: {}", pid);
        success!("Play mode launched (PID: {})", pid);

        let (tx, rx) = channel();

        thread::spawn(move || {
            log::debug!("Watch thread started for play mode process {}", pid);

            match child.wait() {
                Ok(status) => {
                    log::info!("Play mode process {} exited with status: {}", pid, status);

                    if !status.success() {
                        fatal!("Play mode exited early: {:?}", status.code())
                    }

                    if let Err(e) = tx.send(()) {
                        log::error!("Failed to send play mode exit notification: {}", e);
                    }
                }
                Err(e) => {
                    log::error!("Error waiting for play mode process {}: {}", pid, e);
                    let _ = tx.send(());
                }
            }

            log::debug!("Watch thread for play mode process {} terminated", pid);
        });

        self.play_mode_process = None;
        self.play_mode_pid = Some(pid);
        self.play_mode_exit_rx = Some(rx);
        self.editor_state = EditorState::Playing;

        Ok(())
    }
}

/// Describes an action that is undoable
pub enum UndoableAction {
    /// A change in transform. The entity + the old transform. Undoing will revert the transform
    Transform(hecs::Entity, Transform),
    /// A change in EntityTransform. The entity + the old transform. Undoing will revert the transform
    EntityTransform(hecs::Entity, EntityTransform),
    /// A change of label of the entity. Undoing will revert its label
    Label(hecs::Entity, String),
    RemoveStartingCamera(Entity),
}

impl UndoableAction {
    pub fn undo(&self, world: &mut World) -> anyhow::Result<()> {
        match self {
            UndoableAction::Transform(entity, transform) => {
                if let Ok(e_t) = world.query_one::<&mut Transform>(*entity).get() {
                    *e_t = *transform;
                    log::debug!("Reverted transform");
                    Ok(())
                } else {
                    Err(anyhow::anyhow!("Could not find an entity to query"))
                }
            }
            UndoableAction::EntityTransform(entity, transform) => {
                if let Ok(e_t) = world.query_one::<&mut EntityTransform>(*entity).get() {
                    *e_t = *transform;
                    log::debug!("Reverted entity transform");
                    Ok(())
                } else {
                    Err(anyhow::anyhow!("Could not find an entity to query"))
                }
            }
            UndoableAction::Label(entity, original_label) => {
                if let Ok(label) = world.query_one_mut::<&mut Label>(*entity) {
                    label.set(original_label.clone());
                    Ok(())
                } else {
                    anyhow::bail!("No entity found (with or without the Label property)");
                }
            }
            UndoableAction::RemoveStartingCamera(old) => {
                for comp in &mut world.query::<&mut CameraComponent>() {
                    comp.starting_camera = false;
                }
                if let Ok((cam, comp)) =
                    world.query_one_mut::<(&Camera, &mut CameraComponent)>(*old)
                {
                    comp.starting_camera = true;
                    log::debug!("Reverted starting camera back to true for '{}'", cam.label);
                }
                Ok(())
            }
        }
    }
}

/// This enum will be used to describe the type of command/signal. This is only between
/// the editor and unlike SceneCommand, this will ping a signal everywhere in that scene
#[derive(Default)]
pub enum Signal {
    #[default]
    None,
    Copy(Vec<SceneEntity>, HashMap<Label, Label>),

    Paste(Vec<SceneEntity>, HashMap<Label, Label>, Option<Label>),
    AssetCopy {
        source: PathBuf,
        division: AssetDivision,
    },
    AssetPaste {
        target_dir: PathBuf,
        division: AssetDivision,
    },
    Delete,
    Undo,
    Play,
    StopPlaying,
    FlushUnusedAssets,
    /// Adds a new component instance using the async init pipeline.
    AddComponent(hecs::Entity, Box<dyn SerializedComponent>),
    RequestNewWindow(WindowData),
    ReloadWGPUData {
        skybox_texture: Option<Vec<u8>>,
    },
    UpdateViewportSize((f32, f32)),
}

#[derive(Clone, Debug)]
pub struct AssetClipboard {
    pub source: PathBuf,
    pub division: AssetDivision,
}

#[derive(Debug)]
pub enum EditorState {
    Editing,
    Building,
    Playing,
}

struct PendingSceneLoad {
    scene: SceneConfig,
}

pub enum PendingSpawnType {
    Light,
    Camera,
    ProcGen,
}

pub(crate) struct IsWorldLoadedYet {
    /// Whether the project configuration and world data has been loaded
    pub project_loaded: bool,
    /// Whether the scene rendering and UI setup is complete
    pub scene_loaded: bool,
    /// Checks if the wgpu rendering contexts have been initialised for rendering
    pub rendering_loaded: bool,
}

impl IsWorldLoadedYet {
    pub fn new() -> Self {
        Self {
            project_loaded: false,
            scene_loaded: false,
            rendering_loaded: false,
        }
    }

    pub fn is_fully_loaded(&self) -> bool {
        self.project_loaded && self.scene_loaded
    }

    pub fn mark_project_loaded(&mut self) {
        self.project_loaded = true;
    }

    pub fn mark_scene_loaded(&mut self) {
        self.scene_loaded = true;
    }

    pub fn mark_rendering_loaded(&mut self) {
        self.rendering_loaded = true;
    }
}

impl Default for IsWorldLoadedYet {
    fn default() -> Self {
        Self::new()
    }
}
