#![windows_subsystem = "windows"]

extern crate alloc;

mod app_context;
mod asset_loading;
mod auxiliary;
mod dev_tools;
mod drag_drop;
mod engine_editor;
mod messages;
mod mosaic;
mod panels;
mod popups;
mod project_io;
mod render_graph;
#[cfg(not(target_arch = "wasm32"))]
mod settings;
#[cfg(not(target_arch = "wasm32"))]
mod themes;
#[cfg(not(target_arch = "wasm32"))]
mod webview;
mod widgets;
mod windows;

use app_context::AppContext;
use engine_editor::{
    ComponentInspectorUi, TreeCache, ensure_bounding_volumes, gizmo, input, picking,
};
use messages::EditorMessage;
use mosaic::Mosaic;
use nightshade::prelude::*;
#[cfg(not(target_arch = "wasm32"))]
use themes::{nightshade_dark_preset, nightshade_light_preset};
use widgets::EditorWidget;

type EditorMosaic = crate::mosaic::Mosaic<EditorWidget, AppContext, EditorMessage>;

#[cfg(not(target_arch = "wasm32"))]
enum PendingMcpUndo {
    None,
    Transform {
        entity: Entity,
        old_transform: LocalTransform,
    },
    Hierarchy {
        entity: Entity,
        hierarchy: Box<crate::engine_editor::undo::HierarchyNode>,
    },
    BulkHierarchies {
        entities: Vec<Entity>,
        hierarchies: Vec<crate::engine_editor::undo::HierarchyNode>,
    },
}

#[cfg(not(target_arch = "wasm32"))]
impl Default for PendingMcpUndo {
    fn default() -> Self {
        Self::None
    }
}

const DEFAULT_IBL_HDR: &[u8] = include_bytes!("../assets/moonrise.hdr");

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

#[derive(Default)]
struct Editor {
    context: AppContext,
    mosaic: EditorMosaic,
    inspector_ui: ComponentInspectorUi,
    tree_cache: TreeCache,
    #[cfg(not(target_arch = "wasm32"))]
    toasts: crate::mosaic::Toasts,
    #[cfg(not(target_arch = "wasm32"))]
    settings: crate::mosaic::Settings<settings::EditorSettingsData>,
    project: Option<project_io::EditorProjectFile>,
    project_state: project_io::ProjectState,
    theme_state: crate::mosaic::ThemeState,
    #[cfg(not(target_arch = "wasm32"))]
    webview_state: webview::WebviewState,
    scene_browser_dirty: bool,
    #[cfg(not(target_arch = "wasm32"))]
    pending_mcp_undo: PendingMcpUndo,
}

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.atmosphere = Atmosphere::Sky;
        world.resources.graphics.selection_outline_enabled = true;

        load_hdr_skybox(world, DEFAULT_IBL_HDR.to_vec());

        self.context.ui.visibility.left_panel = true;
        self.context.ui.visibility.right_panel = true;

        let focus = Vec3::zeros();
        let radius = 10.0;
        let yaw = 0.5;
        let pitch = 0.4;
        let main_camera = nightshade::ecs::camera::spawn_pan_orbit_camera(
            world,
            focus,
            radius,
            yaw,
            pitch,
            "Main Camera".to_string(),
        );
        world.resources.active_camera = Some(main_camera);

        spawn_sun(world);

        self.mosaic = Mosaic::with_tree(crate::project_io::create_default_tile_tree());

        #[cfg(target_arch = "wasm32")]
        {
            world.resources.graphics.show_grid = true;
        }

        #[cfg(not(target_arch = "wasm32"))]
        {
            self.settings = crate::mosaic::Settings::load("nightshade-editor");
            world.resources.graphics.show_grid = self.settings.data.show_grid;

            self.theme_state
                .presets
                .insert(0, nightshade_light_preset());
            self.theme_state.presets.insert(0, nightshade_dark_preset());
            self.theme_state.current_config = self.theme_state.presets[0].clone();
            self.theme_state.selected_preset_index = Some(0);

            if let Some(theme_name) = &self.settings.data.theme_name
                && let Some((index, preset)) = self
                    .theme_state
                    .presets
                    .iter()
                    .enumerate()
                    .find(|(_, preset)| &preset.name == theme_name)
            {
                self.theme_state.current_config = preset.clone();
                self.theme_state.selected_preset_index = Some(index);
            }
            if let Err(error) = self.settings.save() {
                tracing::error!("Failed to save initial settings: {error}");
            }
            if let Some(startup_project) = self.settings.data.startup_project.clone() {
                let path = std::path::PathBuf::from(&startup_project);
                if path.exists() {
                    self.load_project_from_path(world, &path);
                } else {
                    tracing::warn!("Startup project does not exist: {}", startup_project);
                }
            }
        }
    }

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

        crate::mosaic::apply_theme(ui_context, &self.theme_state);
        self.theme_state.preview_theme_index = None;

        #[cfg(not(target_arch = "wasm32"))]
        self.update_window_title(world);

        self.top_panel_ui(world, ui_context);

        if self.context.ui.visibility.left_panel {
            self.left_panel_ui(world, ui_context);
        }

        if self.context.ui.visibility.right_panel {
            self.right_panel_ui(world, ui_context);
        }

        self.render_mosaic_and_scene_browser(world, ui_context);
        self.process_mosaic_messages(world);
        #[cfg(not(target_arch = "wasm32"))]
        self.update_webviews(world);
        self.handle_popups(world, ui_context);
        self.render_quit_confirmation(world, ui_context);
        self.render_auxiliary_windows(world, ui_context);
        self.render_overlays(world, ui_context);
    }

    fn run_systems(&mut self, world: &mut World) {
        nightshade::ecs::text::systems::sync_text_meshes_system(world);
        nightshade::ecs::navmesh::debug::navmesh_debug_draw_system(world);
        nightshade::ecs::physics::physics_debug_draw_system(world);
        update_animation_players(world);
        apply_animations(world);
        nightshade::ecs::transform::systems::run_systems(world);

        nightshade::ecs::script::run_scripts_system(world);

        world.resources.user_interface.hud_wants_pointer =
            self.context.editor.gizmo_interaction.hover_axis.is_some()
                || self.context.editor.gizmo_interaction.is_dragging();

        match self.context.editor.camera_mode {
            engine_editor::context::CameraMode::Orbit => {
                nightshade::ecs::camera::pan_orbit_camera_system(world);
            }
            engine_editor::context::CameraMode::Fly => {
                nightshade::ecs::camera::fly_camera_system(world);
            }
        }
        ensure_bounding_volumes(world);
        if gizmo::update_modal_transform(&mut self.context.editor, world) {
            self.project_state.mark_modified();
        }
        picking::update_marquee_selection(&mut self.context.editor, world);
        picking::update_picking(&mut self.context.editor, world);
        if let Some(position) = picking::check_context_menu_trigger(&self.context.editor, world) {
            self.context.ui.popup.entity_context_menu = Some(position);
        }
        if gizmo::update_gizmo(&mut self.context.editor, world) {
            self.project_state.mark_modified();
        }
        world.resources.graphics.bounding_volume_selected_entity =
            self.context.editor.selection.primary();
        world.resources.graphics.show_selected_bounding_volume = false;

        if self.context.ui.tree_dirty {
            self.tree_cache.mark_dirty();
            self.context.ui.tree_dirty = false;
        }
    }

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

    fn on_keyboard_input(&mut self, world: &mut World, key_code: KeyCode, key_state: KeyState) {
        let result =
            input::on_keyboard_input_handler(&mut self.context.editor, world, key_code, key_state);
        self.context.ui.tree_dirty |= result.tree_dirty;
        if result.project_modified {
            self.project_state.mark_modified();
        }
        match result.signal {
            Some(input::InputSignal::QuitRequested) => {
                self.context.ui.popup.quit_confirmation = true;
            }
            Some(input::InputSignal::AddPrimitiveRequested(position)) => {
                self.context.ui.popup.add_primitive = Some(position);
            }
            None => {}
        }
    }

    fn configure_render_graph(
        &mut self,
        graph: &mut RenderGraph<World>,
        device: &wgpu::Device,
        surface_format: wgpu::TextureFormat,
        resources: RenderResources,
    ) {
        render_graph::configure_editor_render_graph(graph, device, surface_format, resources);
    }

    fn on_dropped_file(&mut self, world: &mut World, path: &std::path::Path) {
        self.handle_dropped_file(world, path);
    }

    fn on_hovered_file(&mut self, _world: &mut World, path: &std::path::Path) {
        self.handle_hovered_file(path);
    }

    fn on_hovered_file_cancelled(&mut self, _world: &mut World) {
        self.handle_hovered_file_cancelled();
    }

    fn on_dropped_file_data(&mut self, world: &mut World, name: &str, data: &[u8]) {
        self.handle_dropped_file_data(world, name, data);
    }

    #[cfg(not(target_arch = "wasm32"))]
    fn handle_mcp_command(
        &mut self,
        world: &mut World,
        command: &nightshade::mcp::McpCommand,
    ) -> Option<nightshade::mcp::McpResponse> {
        use crate::engine_editor::undo::capture_hierarchy;
        use nightshade::mcp::McpCommand;

        let capture_transform = |name: &str| -> PendingMcpUndo {
            if let Some(&entity) = world.resources.entity_names.get(name) {
                if let Some(&old_transform) = world.core.get_local_transform(entity) {
                    PendingMcpUndo::Transform {
                        entity,
                        old_transform,
                    }
                } else {
                    PendingMcpUndo::None
                }
            } else {
                PendingMcpUndo::None
            }
        };

        self.pending_mcp_undo = match command {
            McpCommand::SetPosition(request) => capture_transform(&request.name),
            McpCommand::SetRotation(request) => capture_transform(&request.name),
            McpCommand::SetScale(request) => capture_transform(&request.name),
            McpCommand::DespawnEntity(request) => {
                if let Some(&entity) = world.resources.entity_names.get(&request.name) {
                    let hierarchy = capture_hierarchy(world, entity);
                    PendingMcpUndo::Hierarchy {
                        entity,
                        hierarchy: Box::new(hierarchy),
                    }
                } else {
                    PendingMcpUndo::None
                }
            }
            McpCommand::ClearScene(_) => {
                let entities: Vec<Entity> =
                    world.resources.entity_names.values().copied().collect();
                let hierarchies: Vec<_> = entities
                    .iter()
                    .map(|&entity| capture_hierarchy(world, entity))
                    .collect();
                PendingMcpUndo::BulkHierarchies {
                    entities,
                    hierarchies,
                }
            }
            _ => PendingMcpUndo::None,
        };

        Option::None
    }

    #[cfg(not(target_arch = "wasm32"))]
    fn after_mcp_command(
        &mut self,
        world: &mut World,
        command: &nightshade::mcp::McpCommand,
        response: &nightshade::mcp::McpResponse,
    ) {
        use crate::engine_editor::undo::{UndoableOperation, capture_hierarchy};
        use nightshade::mcp::{McpCommand, McpResponse};

        let is_success = matches!(response, McpResponse::Success(_));

        let spawn_name = match command {
            McpCommand::SpawnEntity(request) => Some(request.name.as_str()),
            McpCommand::SpawnPrefab(request) => Some(request.entity_name.as_str()),
            McpCommand::SpawnLight(request) => Some(request.name.as_str()),
            McpCommand::SpawnWater(request) => Some(request.name.as_str()),
            McpCommand::Spawn3dText(request) => Some(request.name.as_str()),
            McpCommand::SpawnDecal(request) => Some(request.name.as_str()),
            McpCommand::SpawnParticles(request) => Some(request.name.as_str()),
            _ => None,
        };

        let transform_name = match command {
            McpCommand::SetPosition(request) => Some(request.name.as_str()),
            McpCommand::SetRotation(request) => Some(request.name.as_str()),
            McpCommand::SetScale(request) => Some(request.name.as_str()),
            _ => None,
        };

        if let Some(name) = spawn_name {
            if is_success && let Some(&entity) = world.resources.entity_names.get(name) {
                let hierarchy = capture_hierarchy(world, entity);
                self.context.editor.undo_history.push(
                    UndoableOperation::EntityCreated {
                        hierarchy: Box::new(hierarchy),
                        current_entity: entity,
                    },
                    format!("MCP: Spawn {}", name),
                );
            }
            self.context.ui.tree_dirty = true;
        } else if let Some(name) = transform_name {
            if is_success {
                let pending = std::mem::replace(&mut self.pending_mcp_undo, PendingMcpUndo::None);
                if let PendingMcpUndo::Transform {
                    entity,
                    old_transform,
                } = pending
                    && let Some(&new_transform) = world.core.get_local_transform(entity)
                    && old_transform != new_transform
                {
                    self.context.editor.undo_history.push(
                        UndoableOperation::TransformChanged {
                            entity,
                            old_transform,
                            new_transform,
                        },
                        format!("MCP: Transform {}", name),
                    );
                }
            }
        } else {
            match command {
                McpCommand::DespawnEntity(_) => {
                    if is_success {
                        let pending =
                            std::mem::replace(&mut self.pending_mcp_undo, PendingMcpUndo::None);
                        if let PendingMcpUndo::Hierarchy { entity, hierarchy } = pending {
                            self.context.editor.undo_history.push(
                                UndoableOperation::EntityDeleted {
                                    hierarchy,
                                    deleted_entity: entity,
                                },
                                "MCP: Despawn entity",
                            );
                        }
                    }
                    self.context.ui.tree_dirty = true;
                }
                McpCommand::SetParent(_) => {
                    self.context.ui.tree_dirty = true;
                }
                McpCommand::ClearScene(_) => {
                    if is_success {
                        let pending =
                            std::mem::replace(&mut self.pending_mcp_undo, PendingMcpUndo::None);
                        if let PendingMcpUndo::BulkHierarchies {
                            entities,
                            hierarchies,
                        } = pending
                        {
                            self.context.editor.undo_history.push(
                                UndoableOperation::BulkEntitiesDeleted {
                                    hierarchies,
                                    deleted_entities: entities,
                                },
                                "MCP: Clear scene",
                            );
                        }
                    }
                    self.context.ui.tree_dirty = true;
                }
                McpCommand::Batch(_) | McpCommand::RunCommands(_) => {
                    self.context.ui.tree_dirty = true;
                }
                _ => {}
            }
        }
    }
}
