#include <iostream>
#include <filesystem>
#include "EditorApp.hpp"
#include "GameMode.hpp"
#include "systems/EditorUISystem.hpp"
#include "systems/PhysicsSystem.hpp"
#include "systems/BuoyancySystem.hpp"
#include "systems/EditorSunSystem.hpp"
#include "systems/EditorSkyboxSystem.hpp"
#include "systems/EditorWaterPlaneSystem.hpp"
#include "systems/LightSystem.hpp"
#include "systems/CameraSystem.hpp"
#include "systems/LodSystem.hpp"
#include "systems/StaticGeometrySystem.hpp"
#include "systems/ProceduralTextureSystem.hpp"
#include "systems/ProceduralMaterialSystem.hpp"
#include "systems/ProceduralMeshSystem.hpp"
#include "systems/CharacterSlotSystem.hpp"
#include "systems/CharacterSpawnerSystem.hpp"
#include "components/CharacterSpawner.hpp"
#include "systems/AnimationTreeSystem.hpp"
#include "systems/HairPhysicsSystem.hpp"
#include "systems/BehaviorTreeSystem.hpp"
#include "systems/NavMeshSystem.hpp"
#include "systems/CharacterSystem.hpp"
#include "systems/SmartObjectSystem.hpp"
#include "systems/GoapRunnerSystem.hpp"
#include "systems/PathFollowingSystem.hpp"
#include "systems/GoapPlannerSystem.hpp"
#include "systems/CellGridSystem.hpp"

#include "systems/NormalDebugSystem.hpp"
#include "systems/RoomLayoutSystem.hpp"
#include "systems/StartupMenuSystem.hpp"
#include "systems/PauseMenuSystem.hpp"
#include "systems/DialogueSystem.hpp"
#include "systems/ItemRegistry.hpp"
#include "systems/ContainerStateRegistry.hpp"
#include "systems/ItemStateRegistry.hpp"
#include "systems/CharacterClassSystem.hpp"
#include "systems/PregnancySystem.hpp"
#include "components/CharacterClassDatabase.hpp"
#include "lua/LuaCharacterApi.hpp"
#include "lua/LuaSaveLoadApi.hpp"
#include "systems/PlayerControllerSystem.hpp"
#include "systems/SceneSerializer.hpp"
#include "systems/SaveLoadSystem.hpp"
#include "systems/SaveLoadDialog.hpp"
#include "camera/EditorCamera.hpp"
#include "components/EntityName.hpp"
#include "components/Transform.hpp"
#include "components/Renderable.hpp"
#include "components/EditorMarker.hpp"
#include "components/RuntimeMarker.hpp"
#include "components/PhysicsCollider.hpp"
#include "components/RigidBody.hpp"
#include "components/GeneratedPhysicsTag.hpp"
#include "components/BuoyancyInfo.hpp"
#include "components/InWater.hpp"
#include "components/WaterPhysics.hpp"
#include "components/WaterPlane.hpp"
#include "components/Sun.hpp"
#include "components/Skybox.hpp"
#include "components/Light.hpp"
#include "components/Camera.hpp"
#include "components/Lod.hpp"
#include "components/LodSettings.hpp"
#include "components/StaticGeometry.hpp"
#include "components/StaticGeometryMember.hpp"
#include "components/ProceduralTexture.hpp"
#include "components/ProceduralMaterial.hpp"
#include "components/Primitive.hpp"
#include "components/TriangleBuffer.hpp"
#include "components/CharacterSlots.hpp"
#include "components/CharacterIdentity.hpp"
#include "systems/CharacterRegistry.hpp"
#include "components/AnimationTree.hpp"
#include "components/AnimationTreeTemplate.hpp"
#include "components/Character.hpp"
#include "components/StartupMenu.hpp"
#include "components/PlayerController.hpp"
#include "components/CellGrid.hpp"
#include "components/CellGridModule.hpp"
#include "components/ActionDatabase.hpp"
#include "components/ActionDebug.hpp"
#include "components/BehaviorTree.hpp"
#include "components/GoapBlackboard.hpp"
#include "components/PrefabInstance.hpp"
#include "systems/PrefabSystem.hpp"
#include "components/NavMesh.hpp"
#include "components/SmartObject.hpp"
#include "components/Actuator.hpp"
#include "components/GoapPlanner.hpp"
#include "components/GoapRunner.hpp"
#include "components/PathFollowing.hpp"
#include "systems/ActuatorSystem.hpp"
#include "systems/EventHandlerSystem.hpp"
#include "systems/EventBus.hpp"
#include "systems/ItemSystem.hpp"
#include "components/EventHandler.hpp"
#include "components/Item.hpp"
#include "components/Inventory.hpp"

#ifdef EDITSCENE_VERBOSE_DEBUG
#define EDITSCENE_LOG_INPUT(msg) \
	Ogre::LogManager::getSingleton().logMessage(msg)
#else
#define EDITSCENE_LOG_INPUT(msg)
#endif

#include <OgreRTShaderSystem.h>
#include <OgreArchiveManager.h>
#include "package/OgrePackageArchive.h"
#include <imgui.h>
#include <SDL2/SDL.h>
#include "lua/LuaEntityApi.hpp"
#include "lua/LuaComponentApi.hpp"
#include "lua/LuaEventApi.hpp"
#include "lua/LuaActionApi.hpp"
#include "lua/LuaBehaviorTreeApi.hpp"
#include "lua/LuaGameModeApi.hpp"
#include "lua/LuaCharacterClassApi.hpp"
#include "lua/LuaDialogueApi.hpp"
#include "lua/LuaItemApi.hpp"

//=============================================================================
// ImGuiRenderListener Implementation
//=============================================================================

ImGuiRenderListener::ImGuiRenderListener(Ogre::ImGuiOverlay *imguiOverlay,
					 EditorUISystem *uiSystem,
					 Ogre::RenderWindow *renderWindow,
					 EditorApp *editorApp)
	: m_imguiOverlay(imguiOverlay)
	, m_uiSystem(uiSystem)
	, m_renderWindow(renderWindow)
	, m_editorApp(editorApp)
{
	m_lastTime = m_timer.getMilliseconds();
}

void ImGuiRenderListener::preViewportUpdate(
	const Ogre::RenderTargetViewportEvent &evt)
{
	(void)evt;

	// Calculate delta time
	unsigned long currentTime = m_timer.getMilliseconds();
	m_deltaTime = (currentTime - m_lastTime) / 1000.0f;
	m_lastTime = currentTime;

	// Start ImGui frame before viewport renders
	Ogre::ImGuiOverlay::NewFrame();

	// Render editor UI with delta time
	if (m_uiSystem) {
		m_uiSystem->update(m_deltaTime);
	}

	// Render actuator markers in game mode (after NewFrame so draw
	// commands survive)
	if (m_editorApp) {
		ActuatorSystem *actuatorSys = m_editorApp->getActuatorSystem();
		if (actuatorSys)
			actuatorSys->render();
	}

	// Render startup menu in game mode (inside ImGui frame scope)
	if (m_editorApp &&
	    m_editorApp->getGameMode() == EditorApp::GameMode::Game &&
	    m_editorApp->getGamePlayState() == EditorApp::GamePlayState::Menu) {
		StartupMenuSystem *sms = m_editorApp->getStartupMenuSystem();
		if (sms)
			sms->update(m_deltaTime);
	}

	// Render pause menu in game mode (inside ImGui frame scope)
	if (m_editorApp &&
	    m_editorApp->getGameMode() == EditorApp::GameMode::Game &&
	    m_editorApp->getGamePlayState() ==
		    EditorApp::GamePlayState::Paused &&
	    !(m_editorApp->getCharacterClassSystem() &&
	      m_editorApp->getCharacterClassSystem()->isSheetOpen())) {
		PauseMenuSystem::getInstance().update(m_deltaTime);
	}

	// Render dialogue box (game mode or editor preview)
	DialogueSystem::getInstance().update(m_deltaTime);

	// Character class system (needs, level-ups, dialogs)
	if (m_editorApp && m_editorApp->getCharacterClassSystem()) {
		m_editorApp->getCharacterClassSystem()->update(m_deltaTime);
		m_editorApp->getCharacterClassSystem()->renderDialogs();
	}

	// Pregnancy system (advances pregnancies, triggers birth)
	if (m_editorApp && m_editorApp->getPregnancySystem()) {
		m_editorApp->getPregnancySystem()->update(m_deltaTime);
	}
}

void ImGuiRenderListener::postViewportUpdate(
	const Ogre::RenderTargetViewportEvent &evt)
{
	(void)evt;

	// Capture stats after rendering (they're updated at end of frame)
	if (m_renderWindow && m_uiSystem) {
		Ogre::RenderTarget::FrameStats stats =
			m_renderWindow->getStatistics();
		m_uiSystem->setLastBatchCount(
			static_cast<int>(stats.batchCount));
	}

	// End ImGui frame after viewport renders
	ImGui::EndFrame();
}

//=============================================================================
// EditorApp Implementation
//=============================================================================

EditorApp::EditorApp()
	: OgreBites::ApplicationContext("EditSceneEditor")
	, m_sceneMgr(nullptr)
	, m_overlaySystem(nullptr)
	, m_imguiOverlay(nullptr)
	, m_currentModifiers(0)
	, m_gameMode(GameMode::Editor)
	, m_gamePlayState(GamePlayState::Menu)
{
}

EditorApp::~EditorApp()
{
	// Shutdown UI system first (cleans up gizmo while SceneManager is still valid)
	if (m_uiSystem) {
		m_uiSystem->shutdown();
	}

	// Delete all editor entities before OGRE cleanup
	// This ensures all components with Ogre resources are cleaned up while SceneManager exists
	// Collect entities first, then delete after iteration (can't modify during iteration)
	std::vector<flecs::entity> entitiesToDelete;

	// DialogueSystem is a singleton, no manual teardown needed

	m_startupMenuSystem.reset();
	m_playerControllerSystem.reset();
	m_itemSystem.reset();
	m_eventHandlerSystem.reset();
	m_actuatorSystem.reset();
	m_goapPlannerSystem.reset();
	m_pathFollowingSystem.reset();
	m_goapRunnerSystem.reset();
	m_smartObjectSystem.reset();
	m_roomLayoutSystem.reset();
	m_normalDebugSystem.reset();
	m_cellGridSystem.reset();
	m_characterSystem.reset();
	m_navMeshSystem.reset();
	m_behaviorTreeSystem.reset();
	m_animationTreeSystem.reset();
	m_characterSlotSystem.reset();
	m_characterSpawnerSystem.reset();
	m_proceduralMeshSystem.reset();
	m_proceduralMaterialSystem.reset();
	m_proceduralTextureSystem.reset();
	m_staticGeometrySystem.reset();
	m_lodSystem.reset();
	m_cameraSystem.reset();
	m_lightSystem.reset();
	m_physicsSystem.reset();
	m_imguiListener.reset();
	m_uiSystem.reset();
	m_camera.reset();

	// Now OGRE can shut down safely
	// Singletons are managed by OGRE, don't delete them
}

void EditorApp::setup()
{
	try {
		// Register the Package archive factory before any resource
		// locations are resolved (locateResources is called inside
		// ApplicationContext::setup()).
		static Ogre::PackageArchiveFactory s_packageFactory;
		Ogre::ArchiveManager::getSingleton().addArchiveFactory(
			&s_packageFactory);

		// Base setup
		OgreBites::ApplicationContext::setup();

		// Get root and create scene manager
		Ogre::Root *root = getRoot();
		m_sceneMgr = root->createSceneManager();
		m_sceneMgr->setAmbientLight(
			Ogre::ColourValue(0.3f, 0.3f, 0.3f));

		// Add scene manager to RTShader generator
		Ogre::RTShader::ShaderGenerator *shadergen =
			Ogre::RTShader::ShaderGenerator::getSingletonPtr();
		if (shadergen) {
			shadergen->addSceneManager(m_sceneMgr);
		}

		// Setup overlay system - get from ApplicationContext
		m_overlaySystem = getOverlaySystem();
		OgreAssert(m_overlaySystem, "OverlaySystem not available");
		m_sceneMgr->addRenderQueueListener(m_overlaySystem);

		// Setup ImGui overlay
		m_imguiOverlay = initialiseImGui();
		if (m_imguiOverlay) {
			m_imguiOverlay->setZOrder(300);
			ImGui::StyleColorsDark();
		}

		// Setup camera
		m_camera = std::make_unique<EditorCamera>(m_sceneMgr,
							  getRenderWindow());

		// Setup ECS and scene
		setupECS();
		setupLights();
		createGrid();
		createAxes();
		createDefaultEntities();

		// Setup UI system
		m_uiSystem = std::make_unique<EditorUISystem>(
			m_world, m_sceneMgr, getRenderWindow());
		if (m_uiSystem)
			m_uiSystem->setEditorUIEnabled(m_gameMode ==
						       GameMode::Editor);
		m_uiSystem->setEditorCamera(m_camera.get());

		// Setup physics system
		m_physicsSystem = std::make_unique<EditorPhysicsSystem>(
			m_world, m_sceneMgr);
		m_physicsSystem->initialize();
		m_uiSystem->setPhysicsSystem(m_physicsSystem.get());

		// Setup buoyancy system (requires physics system)
		// Get the physics wrapper from the physics system
		m_buoyancySystem = std::make_unique<BuoyancySystem>(
			m_world, m_physicsSystem->getPhysicsWrapper());
		m_buoyancySystem->initialize();

		m_sunSystem =
			std::make_unique<EditorSunSystem>(m_world, m_sceneMgr);
		m_skyboxSystem = std::make_unique<EditorSkyboxSystem>(
			m_world, m_sceneMgr);
		m_waterPlaneSystem = std::make_unique<EditorWaterPlaneSystem>(
			m_world, m_sceneMgr);

		// Apply debug setting if it was set before system creation
		if (m_debugBuoyancy) {
			m_buoyancySystem->setDebugEnabled(true);
		}

		// Set buoyancy system in UI system for configuration
		if (m_uiSystem) {
			m_uiSystem->setBuoyancySystem(m_buoyancySystem.get());
		}

		// Setup light system
		m_lightSystem = std::make_unique<EditorLightSystem>(m_world,
								    m_sceneMgr);

		// Setup camera system
		m_cameraSystem = std::make_unique<EditorCameraSystem>(
			m_world, m_sceneMgr, getRenderWindow());

		// Setup LOD system
		m_lodSystem =
			std::make_unique<EditorLodSystem>(m_world, m_sceneMgr);
		m_lodSystem->initialize();

		// Setup StaticGeometry system
		m_staticGeometrySystem = std::make_unique<StaticGeometrySystem>(
			m_world, m_sceneMgr);
		m_staticGeometrySystem->initialize();

		// Setup ProceduralTexture system
		m_proceduralTextureSystem =
			std::make_unique<ProceduralTextureSystem>(m_world,
								  m_sceneMgr);
		m_proceduralTextureSystem->initialize();

		// Setup ProceduralMaterial system
		m_proceduralMaterialSystem =
			std::make_unique<ProceduralMaterialSystem>(m_world,
								   m_sceneMgr);
		m_proceduralMaterialSystem->initialize();

		// Setup ProceduralMesh system
		m_proceduralMeshSystem = std::make_unique<ProceduralMeshSystem>(
			m_world, m_sceneMgr);
		m_proceduralMeshSystem->initialize();

		// Setup CharacterSlot system
		m_characterSlotSystem = std::make_unique<CharacterSlotSystem>(
			m_world, m_sceneMgr);
		m_characterSlotSystem->initialize();
		if (m_uiSystem)
			m_uiSystem->getCharacterRegistry().setCharacterSlotSystem(
				m_characterSlotSystem.get());

		// Setup CharacterSpawner system
		m_characterSpawnerSystem =
			std::make_unique<CharacterSpawnerSystem>(
				m_world, m_sceneMgr, m_cameraSystem.get(),
				m_uiSystem.get());
		m_characterSpawnerSystem->initialize();

		// Setup AnimationTree system
		m_animationTreeSystem = std::make_unique<AnimationTreeSystem>(
			m_world, m_sceneMgr);
		m_animationTreeSystem->initialize();

		// Setup HairPhysics system
		m_hairPhysicsSystem = std::make_unique<HairPhysicsSystem>(
			m_world, m_sceneMgr,
			m_physicsSystem->getPhysicsWrapper(),
			m_characterSlotSystem.get());
		m_hairPhysicsSystem->initialize();

		// Setup Character physics system (needed by BehaviorTreeSystem)
		m_characterSystem =
			std::make_unique<CharacterSystem>(m_world, m_sceneMgr);
		m_characterSystem->initialize();

		m_behaviorTreeSystem = std::make_unique<BehaviorTreeSystem>(
			m_world, m_sceneMgr, m_animationTreeSystem.get(),
			m_characterSystem.get());

		// Setup NavMesh system
		m_navMeshSystem =
			std::make_unique<NavMeshSystem>(m_world, m_sceneMgr);

		// Setup SmartObject system (requires NavMesh, BehaviorTree, and AnimationTree)
		m_smartObjectSystem = std::make_unique<SmartObjectSystem>(
			m_world, m_sceneMgr, m_navMeshSystem.get(),
			m_behaviorTreeSystem.get());
		// Wire up AnimationTreeSystem for animation state machine control
		m_smartObjectSystem->setAnimationTreeSystem(
			m_animationTreeSystem.get());
		// Wire up EditorApp for game mode detection
		m_smartObjectSystem->setEditorApp(this);

		// Setup Actuator system
		m_actuatorSystem = std::make_unique<ActuatorSystem>(
			m_world, m_sceneMgr, this, m_behaviorTreeSystem.get());

		// Setup Item system
		m_itemSystem = std::make_unique<ItemSystem>(
			m_world, m_sceneMgr, this, m_behaviorTreeSystem.get());

		// Wire ItemSystem into ActuatorSystem for pickup handling
		m_actuatorSystem->setItemSystem(m_itemSystem.get());

		// Setup Event Handler system
		m_eventHandlerSystem = std::make_unique<EventHandlerSystem>(
			m_world, m_behaviorTreeSystem.get());

		// Wire ItemSystem into SmartObjectSystem for BT access
		m_smartObjectSystem->setItemSystem(m_itemSystem.get());

		// Setup GOAP Runner system
		m_goapRunnerSystem = std::make_unique<GoapRunnerSystem>(
			m_world, m_sceneMgr, m_smartObjectSystem.get(),
			m_behaviorTreeSystem.get(), m_navMeshSystem.get());
		m_goapRunnerSystem->setEditorApp(this);
		m_goapRunnerSystem->setAnimationTreeSystem(
			m_animationTreeSystem.get());

		// Setup GOAP Planner system
		m_goapPlannerSystem =
			std::make_unique<GoapPlannerSystem>(m_world);
		m_goapPlannerSystem->setEditorApp(this);

		// Setup Path Following system
		m_pathFollowingSystem = std::make_unique<PathFollowingSystem>(
			m_world, m_sceneMgr, m_navMeshSystem.get());
		m_pathFollowingSystem->setAnimationTreeSystem(
			m_animationTreeSystem.get());

		// Setup CellGrid system
		m_cellGridSystem =
			std::make_unique<CellGridSystem>(m_world, m_sceneMgr);
		m_cellGridSystem->initialize();

		// Wire CellGridSystem into NavMeshSystem so it can collect
		// batched frame/furniture geometry from StaticGeometry.
		m_navMeshSystem->setCellGridSystem(m_cellGridSystem.get());

		// Setup NormalDebug system (disabled by default)
		m_normalDebugSystem = std::make_unique<NormalDebugSystem>(
			m_world, m_sceneMgr, m_cellGridSystem.get());

		// Wire NormalDebugSystem into UI for toggle
		if (m_uiSystem) {
			m_uiSystem->setNormalDebugSystem(
				m_normalDebugSystem.get());
		}

		// Setup RoomLayout system
		m_roomLayoutSystem =
			std::make_unique<RoomLayoutSystem>(m_world, m_sceneMgr);
		m_roomLayoutSystem->initialize();

		// Setup game systems
		m_startupMenuSystem = std::make_unique<StartupMenuSystem>(
			m_world, m_sceneMgr, this);
		DialogueSystem::getInstance().init(this);
		DialogueSystem::getInstance().loadSettings("dialogue.json");
		PauseMenuSystem::getInstance().init(this);
		static ItemRegistry s_itemRegistry;
		ItemRegistry::getSingleton().initialize();
		ItemStateRegistry::getInstance().loadFromFile(
			"item_state.json");
		ContainerStateRegistry::getInstance().loadFromFile(
			"container_state.json");

		m_characterClassSystem =
			std::make_unique<CharacterClassSystem>(m_world, this);
		m_pregnancySystem = std::make_unique<PregnancySystem>(m_world);
		CharacterClassDatabase::loadFromJson("character_class.json");

		m_playerControllerSystem =
			std::make_unique<PlayerControllerSystem>(
				m_world, m_sceneMgr, this);

		if (m_gameMode == GameMode::Game) {
			// Load startup menu scene configured in editor.
			// This must happen before show() so the
			// StartupMenuComponent entity exists for font preparation.
			SceneSerializer serializer(m_world, m_sceneMgr);
			Ogre::LogManager::getSingleton().logMessage(
				"Game mode: Loading startup_menu.json...");
			if (serializer.loadFromFile("startup_menu.json",
						    m_uiSystem.get())) {
				PrefabSystem prefabSys(m_world, m_sceneMgr);
				prefabSys.resolveInstances();
				Ogre::LogManager::getSingleton().logMessage(
					"Game mode: startup_menu.json loaded");
			} else {
				Ogre::LogManager::getSingleton().logMessage(
					"Game mode: Failed to load startup_menu.json: " +
					serializer.getLastError());
			}

			// Pre-load fonts before showing overlay so the font is
			// added to the atlas before OGRE builds it in createFontTexture().
			// The StartupMenuComponent entity is now available from the
			// startup_menu.json scene loaded above.
			if (m_startupMenuSystem)
				m_startupMenuSystem->prepareFont();
			DialogueSystem::getInstance().prepareFont();
			PauseMenuSystem::getInstance().prepareFont();
			if (m_characterClassSystem)
				m_characterClassSystem->getConfig().prepareFont(
					this);
		}

		// Now show the overlay — font atlas will be built with our font
		if (m_imguiOverlay)
			m_imguiOverlay->show();

		// Add default entities to UI cache
		for (auto &e : m_defaultEntities) {
			m_uiSystem->addEntity(e);
		}

		// Create and register ImGui render listener
		m_imguiListener = std::make_unique<ImGuiRenderListener>(
			m_imguiOverlay, m_uiSystem.get(), getRenderWindow(),
			this);
		getRenderWindow()->addListener(m_imguiListener.get());

		// Register input listeners
		addInputListener(this);
		addInputListener(getImGuiInputListener());

		// Initialize Lua scripting
		{
			lua_State *L = m_lua.getState();

			// Store the Flecs world pointer in the Lua registry
			// so Lua API functions can access it.
			flecs::world *worldPtr = &m_world;
			lua_pushlightuserdata(L, worldPtr);
			lua_setfield(L, LUA_REGISTRYINDEX,
				     "EditSceneFlecsWorld");

			// Register all Lua API modules.
			// Order matters: Entity API creates the "ecs" table,
			// Component and Event APIs add to it.
			editScene::registerLuaEntityApi(L);
			editScene::registerLuaComponentApi(L);
			editScene::registerLuaEventApi(L);
			editScene::registerLuaActionApi(L);
			editScene::registerLuaBehaviorTreeApi(L);
			editScene::registerLuaGameModeApi(L);
			editScene::registerLuaCharacterClassApi(L);
			editScene::registerLuaCharacterApi(L);
			editScene::registerLuaDialogueApi(L);
			editScene::registerLuaItemApi(L);

			// Run late setup: load data.lua and initial scripts.
			m_lua.lateSetup();

			// Auto-load Action Database from "actions.json" in the General
			// resource group.  If the file is missing or contains errors
			// the error is logged and the database stays empty (scene
			// components will still be processed below).
			ActionDatabase::loadFromJson("actions.json");

			// Re-process scene components so that any
			// ActionDatabaseComponent entities in the scene override or
			// append actions from the file.
			ActionDatabase::reloadFromSceneComponents(m_world);
		}

		if (m_gameMode == GameMode::Game) {
			// Queue "game_start" event after Lua scripts are loaded
			// so Lua subscribers registered via ecs.subscribe_event()
			// will receive the event.
			EventBus::getInstance().send("game_start");
		}

		// Game mode can be set externally before setup() is called
		m_setupComplete = true;

	} catch (const std::exception &e) {
		Ogre::LogManager::getSingleton().logMessage(
			"Setup failed: " + Ogre::String(e.what()));
		throw;
	}
}

void EditorApp::setGameMode(GameMode mode)
{
	if (m_setupComplete) {
		Ogre::LogManager::getSingleton().logMessage(
			"setGameMode ignored: cannot change mode after setup");
		return;
	}
	m_gameMode = mode;
	editScene::setEditSceneGameMode(mode == GameMode::Game ?
						editScene::GameMode::Game :
						editScene::GameMode::Editor);
	if (m_gameMode == GameMode::Game) {
		m_gamePlayState = GamePlayState::Menu;
		editScene::setEditSceneGamePlayState(
			editScene::GamePlayState::Menu);
		if (m_uiSystem)
			m_uiSystem->setEditorUIEnabled(false);
	} else {
		m_gamePlayState = GamePlayState::Menu;
		editScene::setEditSceneGamePlayState(
			editScene::GamePlayState::Menu);
		if (m_uiSystem)
			m_uiSystem->setEditorUIEnabled(true);
	}
}

void EditorApp::setDebugBuoyancy(bool enabled)
{
	m_debugBuoyancy = enabled;

	// Apply debug setting to buoyancy system if it exists
	if (m_buoyancySystem) {
		m_buoyancySystem->setDebugEnabled(enabled);
	}

	// Log the debug mode change only if OGRE is initialized
	// (setDebugBuoyancy may be called before OGRE setup)
	try {
		if (Ogre::LogManager::getSingletonPtr()) {
			Ogre::LogManager::getSingleton().logMessage(
				"Buoyancy debug mode: " +
				Ogre::StringConverter::toString(enabled));
		}
	} catch (...) {
		// Ignore if OGRE not initialized yet
	}
}

void EditorApp::setGamePlayState(GamePlayState state)
{
	m_gamePlayState = state;
	editScene::setEditSceneGamePlayState(
		state == GamePlayState::Playing ?
			editScene::GamePlayState::Playing :
		state == GamePlayState::Paused ?
			editScene::GamePlayState::Paused :
			editScene::GamePlayState::Menu);

	/* Avoid stuck movement keys when leaving the Playing state (e.g. opening
	 * the inventory/pause menu while Shift+W is held). */
	if (state != GamePlayState::Playing)
		m_gameInput.clearMovementInput();

	// Grab/ungrab mouse based on gameplay state
	if (m_gameMode == GameMode::Game) {
		if (state == GamePlayState::Playing) {
			setWindowGrab(true);
		} else if (state == GamePlayState::Menu) {
			setWindowGrab(false);
		} else if (state == GamePlayState::Paused) {
			setWindowGrab(false);
		}
	}
}

void EditorApp::clearScene()
{
	// Destroy all entities with EditorMarkerComponent
	std::vector<flecs::entity> entitiesToDelete;
	m_world.query<EditorMarkerComponent>().each(
		[&](flecs::entity e, EditorMarkerComponent) {
			entitiesToDelete.push_back(e);
		});
	for (auto &e : entitiesToDelete) {
		if (e.is_alive() && m_uiSystem) {
			m_uiSystem->deleteEntity(e);
		} else if (e.is_alive()) {
			e.destruct();
		}
	}
	if (m_uiSystem) {
		m_uiSystem->clearEntityCache();
	}
}

void EditorApp::startNewGame(const Ogre::String &scenePath)
{
	clearScene();
	SceneSerializer serializer(m_world, m_sceneMgr);
	if (serializer.loadFromFile(scenePath, m_uiSystem.get())) {
		PrefabSystem prefabSys(m_world, m_sceneMgr);
		prefabSys.resolveInstances();
		setGamePlayState(GamePlayState::Playing);
		m_currentBaseScene = scenePath;
		m_playTime = 0.0f;
		Ogre::LogManager::getSingleton().logMessage(
			"Game started: loaded scene " + scenePath);

		// Set up player character: resolve the actual target character
		// (spawned instance if the controller targets a spawner) so the
		// character sheet and inventory are on the same entity that
		// ActuatorSystem uses.
		flecs::entity playerCharacter = getPlayerCharacterEntity();
		if (playerCharacter.is_valid() && playerCharacter.is_alive()) {
			if (!playerCharacter.has<CharacterIdentityComponent>()) {
				// Create a character record for the player
				uint64_t charId =
					CharacterRegistry::getSingleton()
						.createCharacter("Player",
								 "One", "",
								 false);
				// Link the entity to the registry record
				playerCharacter.set<CharacterIdentityComponent>(
					CharacterIdentityComponent{ charId });

				// Set default class and initial stats from
				// the database
				auto *rec = CharacterRegistry::getSingleton()
						    .findCharacter(charId);
				if (rec) {
					auto &db = CharacterClassDatabase::
						getSingleton();
					// Use "pclass" for the player if it
					// exists, otherwise fall back to the
					// first available class
					const auto &classNames =
						db.getClassNames();
					rec->className = "pclass";
					bool found = false;
					for (const auto &cn : classNames) {
						if (cn == "pclass") {
							found = true;
							break;
						}
					}
					if (!found && !classNames.empty()) {
						rec->className = classNames[0];
					}
					const auto *cls =
						db.findClass(rec->className);
					if (cls) {
						// Initialize stats from
						// baseStats
						for (const auto &pair :
						     cls->baseStats) {
							rec->stats[pair.first] =
								pair.second;
						}
						// Initialize needs
						auto needNames =
							db.getNeedNames();
						for (const auto &n :
						     needNames) {
							const auto *def =
								db.findNeed(n);
							if (def)
								rec->needs[n] =
									0;
						}
					}
				}
				Ogre::LogManager::getSingleton().logMessage(
					"EditorApp: created character record for player entity " +
					Ogre::StringConverter::toString(
						(int)playerCharacter.id()));
			}
			// Ensure player has an inventory for the character sheet
			if (!playerCharacter.has<InventoryComponent>()) {
				playerCharacter.set<InventoryComponent>(
					InventoryComponent());
			}
		} else {
			Ogre::LogManager::getSingleton().logMessage(
				"EditorApp: no player entity found in scene");
		}

		// Send "scene_ready" event after scene is loaded and
		// entities/components are populated and ready to run.
		EventBus::getInstance().send("scene_ready");
	} else {
		Ogre::LogManager::getSingleton().logMessage(
			"Failed to load scene: " + serializer.getLastError());
	}
}

void EditorApp::saveGame(const std::string &slotPath,
			 const std::string &slotName)
{
	EventBus::getInstance().send("save_game_requested");

	nlohmann::json saveData;
	saveData["version"] = "2.0";
	saveData["saveGame"] = { { "baseScene", m_currentBaseScene },
				 { "timestamp",
				   SaveLoadSystem::getCurrentTimestamp() },
				 { "slotName", slotName },
				 { "playTime", m_playTime } };

	/* Sync spawned character positions into the registry.
	 * Read from the live SceneNode because TransformComponent
	 * cache is not updated by physics/animation systems. */
	auto &registry = CharacterRegistry::getSingleton();
	m_world.query<CharacterIdentityComponent>().each(
		[&](flecs::entity e, CharacterIdentityComponent &ci) {
			auto *rec = registry.findCharacter(ci.registryId);
			if (!rec || !e.has<TransformComponent>())
				return;
			auto &t = e.get<TransformComponent>();
			if (t.node) {
				rec->position = t.node->_getDerivedPosition();
				rec->rotation =
					t.node->_getDerivedOrientation();
			} else {
				rec->position = t.position;
				rec->rotation = t.rotation;
			}
		});

	/* Identify the player character registry ID for load-time setup */
	uint64_t playerCharId = 0;
	flecs::entity playerChar = getPlayerCharacterEntity();
	if (playerChar.is_alive() &&
	    playerChar.has<CharacterIdentityComponent>()) {
		playerCharId =
			playerChar.get<CharacterIdentityComponent>().registryId;
	}
	saveData["saveGame"]["playerCharacterId"] = playerCharId;

	saveData["characterRegistry"] = registry.serialize();
	saveData["containerState"] =
		ContainerStateRegistry::getInstance().serialize();
	saveData["itemState"] = ItemStateRegistry::getInstance().serialize();

	/* Runtime entities — skip characters and player controller */
	saveData["runtimeEntities"] = nlohmann::json::array();
	SceneSerializer serializer(m_world, m_sceneMgr);
	m_world.query<RuntimeMarkerComponent>().each(
		[&](flecs::entity e, const RuntimeMarkerComponent &) {
			if (e.has<CharacterIdentityComponent>())
				return;
			if (e.has<PlayerControllerComponent>())
				return;
			saveData["runtimeEntities"].push_back(
				serializer.serializeEntity(e));
		});

	/* Character runtime component overrides (restored after registry spawn) */
	saveData["characterRuntimeData"] = nlohmann::json::object();
	static const std::vector<std::string> s_runtimeKeys = {
		"character",	 "goapBlackboard",
		"goapPlanner",	 "goapRunner",
		"pathFollowing", "behaviorTree",
		"inventory",	 "actionDebug",
		"animationTree", "animationTreeTemplate"
	};
	m_world.query<CharacterIdentityComponent>().each(
		[&](flecs::entity e, CharacterIdentityComponent &ci) {
			nlohmann::json entityJson =
				serializer.serializeEntity(e);
			nlohmann::json filtered;
			for (const auto &key : s_runtimeKeys) {
				if (entityJson.contains(key))
					filtered[key] = entityJson[key];
			}
			saveData["characterRuntimeData"]
				[std::to_string(ci.registryId)] = filtered;
		});

	/* Lua callback data */
	saveData["luaData"] = editScene::collectLuaSaveData(m_lua.getState());

	if (SaveLoadSystem::writeSaveFile(slotPath, saveData)) {
		Ogre::LogManager::getSingleton().logMessage("Game saved to: " +
							    slotPath);
		EventBus::getInstance().send("game_saved");
	} else {
		Ogre::LogManager::getSingleton().logMessage(
			"Failed to save game to: " + slotPath);
	}
}

void EditorApp::loadGame(const std::string &slotPath)
{
	EventBus::getInstance().send("load_game_requested");

	nlohmann::json saveData;
	if (!SaveLoadSystem::readSaveFile(slotPath, saveData)) {
		Ogre::LogManager::getSingleton().logMessage(
			"Failed to read save file: " + slotPath);
		return;
	}

	std::string baseScene = saveData["saveGame"].value("baseScene", "");
	if (baseScene.empty()) {
		Ogre::LogManager::getSingleton().logMessage(
			"Save file missing base scene reference");
		return;
	}

	clearScene();

	SceneSerializer serializer(m_world, m_sceneMgr);
	if (!serializer.loadFromFile(baseScene, m_uiSystem.get())) {
		Ogre::LogManager::getSingleton().logMessage(
			"Failed to load base scene: " +
			serializer.getLastError());
		return;
	}
	PrefabSystem prefabSys(m_world, m_sceneMgr);
	prefabSys.resolveInstances();

	/* Restore registries */
	auto &registry = CharacterRegistry::getSingleton();
	if (saveData.contains("characterRegistry"))
		registry.deserialize(saveData["characterRegistry"]);
	if (saveData.contains("containerState"))
		ContainerStateRegistry::getInstance().deserialize(
			saveData["containerState"]);
	if (saveData.contains("itemState"))
		ItemStateRegistry::getInstance().deserialize(
			saveData["itemState"]);

	/* Destroy ALL spawned characters so the registry spawn is the
	 * only source of characters. */
	std::vector<flecs::entity> charsToDestroy;
	m_world.query<CharacterIdentityComponent>().each(
		[&](flecs::entity e, CharacterIdentityComponent &) {
			charsToDestroy.push_back(e);
		});
	for (auto e : charsToDestroy) {
		if (!e.is_alive())
			continue;
		if (m_uiSystem)
			m_uiSystem->deleteEntity(e);
		else
			e.destruct();
	}

	/* Spawn persistent characters from registry.
	 * Only spawn characters that were actually present in the
	 * world at save time (listed in characterRuntimeData).
	 * This avoids respawning registry records that were not
	 * spawned (e.g. characters from other scenes) at origin. */
	const auto &runtimeData = saveData.value("characterRuntimeData",
						 nlohmann::json::object());
	bool filterByRuntime = saveData.contains("characterRuntimeData") &&
			       runtimeData.is_object();
	for (const auto &pair : registry.getCharacters()) {
		const auto &rec = pair.second;
		if (!rec.persistent)
			continue;
		if (filterByRuntime &&
		    !runtimeData.contains(std::to_string(rec.id)))
			continue;
		registry.spawnCharacter(rec.id);
	}

	/* Restore character runtime component overrides */
	if (saveData.contains("characterRuntimeData") &&
	    saveData["characterRuntimeData"].is_object()) {
		for (auto &el : saveData["characterRuntimeData"].items()) {
			uint64_t rid = std::stoull(el.key());
			flecs::entity spawned = registry.findSpawnedEntity(rid);
			if (!spawned.is_alive())
				continue;
			serializer.deserializeEntityComponents(
				spawned, el.value(), flecs::entity::null(),
				nullptr, false, false, false);
		}
	}

	/* Restore other runtime entities (dropped items, etc.) */
	if (saveData.contains("runtimeEntities") &&
	    saveData["runtimeEntities"].is_array()) {
		for (const auto &entityJson : saveData["runtimeEntities"]) {
			flecs::entity targetEntity = flecs::entity::null();

			/* Try match by item instanceId */
			if (entityJson.contains("item") &&
			    entityJson["item"].is_object() &&
			    entityJson["item"].contains("instanceId")) {
				std::string iid =
					entityJson["item"]["instanceId"];
				m_world.query<ItemComponent>().each(
					[&](flecs::entity e,
					    ItemComponent &item) {
						if (item.instanceId == iid)
							targetEntity = e;
					});
			}

			/* Try match by name */
			if (!targetEntity.is_alive() &&
			    entityJson.contains("name") &&
			    entityJson["name"].is_object() &&
			    entityJson["name"].contains("name")) {
				std::string ename = entityJson["name"]["name"];
				m_world.query<EntityNameComponent>().each(
					[&](flecs::entity e,
					    EntityNameComponent &nameComp) {
						if (nameComp.name == ename)
							targetEntity = e;
					});
			}

			if (targetEntity.is_alive()) {
				/* Apply overrides to existing entity.
				 * Handle transform specially to preserve SceneNode. */
				if (entityJson.contains("transform") &&
				    entityJson["transform"].is_object()) {
					auto &t = entityJson["transform"];
					if (targetEntity
						    .has<TransformComponent>()) {
						auto &trans = targetEntity.get_mut<
							TransformComponent>();
						if (t.contains("position")) {
							auto &p = t["position"];
							trans.position = Ogre::Vector3(
								p.value("x",
									0.0f),
								p.value("y",
									0.0f),
								p.value("z",
									0.0f));
						}
						if (t.contains("rotation")) {
							auto &r = t["rotation"];
							trans.rotation = Ogre::Quaternion(
								r.value("w",
									1.0f),
								r.value("x",
									0.0f),
								r.value("y",
									0.0f),
								r.value("z",
									0.0f));
						}
						if (t.contains("scale")) {
							auto &s = t["scale"];
							trans.scale = Ogre::Vector3(
								s.value("x",
									1.0f),
								s.value("y",
									1.0f),
								s.value("z",
									1.0f));
						}
						trans.applyToNode();
					}
				}
				serializer.deserializeEntityComponents(
					targetEntity, entityJson,
					flecs::entity::null(), m_uiSystem.get(),
					false);
			} else {
				/* Create new runtime entity */
				flecs::entity newEntity = m_world.entity();
				newEntity.add<RuntimeMarkerComponent>();
				serializer.deserializeEntityComponents(
					newEntity, entityJson,
					flecs::entity::null(), m_uiSystem.get(),
					true);
			}
		}
	}

	/* Setup player controller to point to spawned player character */
	uint64_t playerCharId =
		saveData["saveGame"].value("playerCharacterId", 0ULL);
	flecs::entity playerController;
	m_world.query<EntityNameComponent>().each(
		[&](flecs::entity e, EntityNameComponent &name) {
			if (name.name == "player")
				playerController = e;
		});
	if (playerController.is_alive() && playerCharId != 0) {
		flecs::entity playerChar;
		m_world.query<CharacterIdentityComponent>().each(
			[&](flecs::entity e, CharacterIdentityComponent &ci) {
				if (ci.registryId == playerCharId)
					playerChar = e;
			});
		if (playerChar.is_alive() &&
		    playerController.has<PlayerControllerComponent>()) {
			auto &pc =
				playerController
					.get_mut<PlayerControllerComponent>();
			/* If the saved character is currently owned by a spawner,
			 * keep the controller targeted at the spawner so the
			 * character respawns correctly.  Otherwise target the
			 * character directly. */
			flecs::entity targetEntity = playerChar;
			if (m_characterSpawnerSystem) {
				flecs::entity spawner =
					m_characterSpawnerSystem
						->getSpawnerForCharacter(playerChar);
				if (spawner.is_alive())
					targetEntity = spawner;
			}
			if (targetEntity.has<EntityNameComponent>()) {
				pc.targetCharacterName =
					targetEntity.get<EntityNameComponent>()
						.name;
			}
			if (!playerChar.has<InventoryComponent>()) {
				playerChar.set<InventoryComponent>(
					InventoryComponent());
			}
		}
	}

	/* Lua callback data */
	if (saveData.contains("luaData")) {
		editScene::applyLuaLoadData(m_lua.getState(),
					    saveData["luaData"]);
	}

	m_currentBaseScene = baseScene;
	m_playTime = saveData["saveGame"].value("playTime", 0.0f);
	setGamePlayState(GamePlayState::Playing);

	Ogre::LogManager::getSingleton().logMessage("Game loaded from: " +
						    slotPath);
	EventBus::getInstance().send("game_loaded");
}

void EditorApp::setupECS()
{
	// Register components
	m_world.component<EntityNameComponent>();
	m_world.component<TransformComponent>();
	m_world.component<RenderableComponent>();
	m_world.component<EditorMarkerComponent>();

	// Register physics components
	m_world.component<PhysicsColliderComponent>();
	m_world.component<GeneratedPhysicsTag>();
	m_world.component<RigidBodyComponent>();
	m_world.component<BuoyancyInfo>();
	m_world.component<WaterPhysics>();
	m_world.component<WaterPlane>();

	// Register light and camera components
	m_world.component<LightComponent>();
	m_world.component<CameraComponent>();

	// Register LOD components
	m_world.component<LodComponent>();
	m_world.component<LodSettingsComponent>();

	// Register StaticGeometry components
	m_world.component<StaticGeometryComponent>();
	m_world.component<StaticGeometryMemberComponent>();

	// Register ProceduralTexture component
	m_world.component<ProceduralTextureComponent>();

	// Register ProceduralMaterial component
	m_world.component<ProceduralMaterialComponent>();

	// Register Primitive and TriangleBuffer components
	m_world.component<PrimitiveComponent>();
	m_world.component<TriangleBufferComponent>();

	// Register CharacterSlots component
	m_world.component<CharacterSlotsComponent>();

	// Register CharacterIdentity component
	m_world.component<CharacterIdentityComponent>();

	// Register AnimationTree component
	m_world.component<AnimationTreeComponent>();

	// Register AnimationTreeTemplate component
	m_world.component<AnimationTreeTemplate>();

	// Register Character component
	m_world.component<CharacterComponent>();

	// Register game components
	m_world.component<StartupMenuComponent>();
	m_world.component<PlayerControllerComponent>();
	m_world.component<InWater>();

	// Register environment components
	m_world.component<SunComponent>();
	m_world.component<SkyboxComponent>();

	// Register AI/GOAP components
	// ActionDatabase is now a singleton, registered in ActionDatabaseModule
	m_world.component<ActionDebug>();
	m_world.component<BehaviorTreeComponent>();
	m_world.component<GoapBlackboard>();

	// Register Smart Object component
	m_world.component<SmartObjectComponent>();

	// Register Actuator component
	m_world.component<ActuatorComponent>();

	// Register Event Handler component
	m_world.component<EventHandlerComponent>();

	// Register GOAP Planner component
	m_world.component<GoapPlannerComponent>();

	// Register GOAP Runner component
	m_world.component<GoapRunnerComponent>();

	// Register Path Following component
	m_world.component<PathFollowingComponent>();

	// Register Navigation components
	m_world.component<NavMeshComponent>();
	m_world.component<NavMeshGeometrySource>();

	// Register CellGrid/Town components
	CellGridModule::registerComponents(m_world);

	// Register PrefabInstance component
	m_world.component<PrefabInstanceComponent>();

	// Register Item and Inventory components
	m_world.component<ItemComponent>();
	m_world.component<InventoryComponent>();
}

void EditorApp::createDefaultEntities()
{
	// Start with empty scene - user creates entities as needed
}

void EditorApp::setupLights()
{
	// Directional light
	Ogre::Light *dirLight = m_sceneMgr->createLight("DirectionalLight");
	dirLight->setType(Ogre::Light::LT_DIRECTIONAL);
	dirLight->setDiffuseColour(Ogre::ColourValue(1.0f, 1.0f, 1.0f));
	dirLight->setSpecularColour(Ogre::ColourValue(0.5f, 0.5f, 0.5f));

	Ogre::SceneNode *dirNode =
		m_sceneMgr->getRootSceneNode()->createChildSceneNode();
	dirNode->attachObject(dirLight);
	dirNode->setDirection(Ogre::Vector3(1, -1, 0), Ogre::Node::TS_WORLD);

	// Fill light
	Ogre::Light *fillLight = m_sceneMgr->createLight("FillLight");
	fillLight->setType(Ogre::Light::LT_DIRECTIONAL);
	fillLight->setDiffuseColour(Ogre::ColourValue(0.4f, 0.4f, 0.4f));

	Ogre::SceneNode *fillNode =
		m_sceneMgr->getRootSceneNode()->createChildSceneNode();
	fillNode->attachObject(fillLight);
	fillNode->setDirection(Ogre::Vector3(-0.5f, -1, 0.5f),
			       Ogre::Node::TS_WORLD);
}

void EditorApp::createGrid()
{
	try {
		Ogre::ManualObject *grid =
			m_sceneMgr->createManualObject("Grid");
		// Use Ogre/AxisGizmo which properly supports vertex colors
		grid->begin("Ogre/AxisGizmo",
			    Ogre::RenderOperation::OT_LINE_LIST);

		// Draw grid lines
		float size = 10.0f;
		int divisions = 20;
		float step = size * 2.0f / divisions;

		for (int i = 0; i <= divisions; ++i) {
			float pos = -size + i * step;

			// Lines along X
			grid->position(pos, 0, -size);
			grid->position(pos, 0, size);

			// Lines along Z
			grid->position(-size, 0, pos);
			grid->position(size, 0, pos);
		}

		grid->end();

		m_gridNode =
			m_sceneMgr->getRootSceneNode()->createChildSceneNode(
				"GridNode");
		m_gridNode->attachObject(grid);

		// Set initial visibility based on game mode
		m_gridNode->setVisible(m_gameMode == GameMode::Editor);
	} catch (const std::exception &e) {
		Ogre::LogManager::getSingleton().logMessage(
			"Grid creation failed: " + Ogre::String(e.what()));
	}
}

void EditorApp::createAxes()
{
	try {
		// X axis (red)
		Ogre::ManualObject *axisX =
			m_sceneMgr->createManualObject("AxisX");
		axisX->begin("Ogre/AxisGizmo",
			     Ogre::RenderOperation::OT_LINE_LIST);
		axisX->colour(1.0f, 0.0f, 0.0f);
		axisX->position(0, 0, 0);
		axisX->position(2, 0, 0);
		axisX->end();

		// Y axis (green)
		Ogre::ManualObject *axisY =
			m_sceneMgr->createManualObject("AxisY");
		axisY->begin("Ogre/AxisGizmo",
			     Ogre::RenderOperation::OT_LINE_LIST);
		axisY->colour(0.0f, 1.0f, 0.0f);
		axisY->position(0, 0, 0);
		axisY->position(0, 2, 0);
		axisY->end();

		// Z axis (blue)
		Ogre::ManualObject *axisZ =
			m_sceneMgr->createManualObject("AxisZ");
		axisZ->begin("Ogre/AxisGizmo",
			     Ogre::RenderOperation::OT_LINE_LIST);
		axisZ->colour(0.0f, 0.0f, 1.0f);
		axisZ->position(0, 0, 0);
		axisZ->position(0, 0, 2);
		axisZ->end();

		// Create axis node
		m_axisNode =
			m_sceneMgr->getRootSceneNode()->createChildSceneNode(
				"AxisNode");
		m_axisNode->attachObject(axisX);
		m_axisNode->attachObject(axisY);
		m_axisNode->attachObject(axisZ);

		// Set initial visibility based on game mode
		m_axisNode->setVisible(m_gameMode == GameMode::Editor);
	} catch (const std::exception &e) {
		Ogre::LogManager::getSingleton().logMessage(
			"Axis creation failed: " + Ogre::String(e.what()));
	}
}

bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt)
{
	bool paused = (m_gamePlayState == GamePlayState::Paused);

	if (m_gameMode == GameMode::Editor) {
		// Update camera
		if (m_camera) {
			m_camera->update(evt.timeSinceLastFrame);
		}
	} else if (m_gameMode == GameMode::Game) {
		if (m_gamePlayState == GamePlayState::Playing) {
			m_playTime += evt.timeSinceLastFrame;
			if (m_playerControllerSystem) {
				m_playerControllerSystem->update(
					evt.timeSinceLastFrame);
			}
		}
	}

	/* --- Gameplay systems (paused when in Paused state) --- */
	if (!paused) {
		/* --- Visual mesh setup (must run before animation) --- */
		if (m_characterSpawnerSystem) {
			m_characterSpawnerSystem->update();
		}
		if (m_characterSlotSystem) {
			m_characterSlotSystem->update();
		}
		/* --- Animation / procedural generation --- */
		if (m_animationTreeSystem) {
			m_animationTreeSystem->update(evt.timeSinceLastFrame);
			if (m_behaviorTreeSystem)
				m_behaviorTreeSystem->update(
					evt.timeSinceLastFrame);
		}
		if (m_pathFollowingSystem) {
			m_pathFollowingSystem->update(evt.timeSinceLastFrame);
		}
		if (m_proceduralMeshSystem) {
			m_proceduralMeshSystem->update();
		}

		/* --- Static world generation (meshes + physics) --- */
		if (m_roomLayoutSystem) {
			m_roomLayoutSystem->update();
		}
		if (m_cellGridSystem) {
			m_cellGridSystem->update();
		}

		/* --- Normal debug visualization (after geometry is built) --- */
		if (m_normalDebugSystem) {
			m_normalDebugSystem->update();
		}

		/* --- NavMesh builds after static geometry is ready --- */
		if (m_navMeshSystem) {
			m_navMeshSystem->update(evt.timeSinceLastFrame);
		}

		/* --- Smart Object system (AI navigation to smart objects) --- */
		if (m_smartObjectSystem) {
			m_smartObjectSystem->update(evt.timeSinceLastFrame);
		}

		/* --- GOAP Planner system (plan generation) --- */
		if (m_goapPlannerSystem) {
			m_goapPlannerSystem->update(evt.timeSinceLastFrame);
		}

		/* --- GOAP Runner system (plan execution) --- */
		if (m_goapRunnerSystem) {
			m_goapRunnerSystem->update(evt.timeSinceLastFrame);
		}

		/* --- Actuator system (player interaction prompts) --- */
		if (m_actuatorSystem) {
			m_actuatorSystem->update(evt.timeSinceLastFrame);
		}

		/* --- Event Handler system (event-driven BTs) --- */
		if (m_eventHandlerSystem) {
			m_eventHandlerSystem->update(evt.timeSinceLastFrame);
		}

		/* --- Dynamic physics (characters after static world) --- */
		if (m_characterSystem) {
			m_characterSystem->update(evt.timeSinceLastFrame);
		}

		/* --- Buoyancy system (before physics so impulse is integrated) --- */
		if (m_buoyancySystem) {
			// Update camera position for water detection area
			if (m_camera) {
				Ogre::Vector3 cameraPos =
					m_camera->getPosition();
				m_buoyancySystem->setCameraPosition(cameraPos);
			}
			m_buoyancySystem->update(evt.timeSinceLastFrame);
		}

		/* --- Hair physics root sync (before physics step) --- */
		if (m_hairPhysicsSystem) {
			m_hairPhysicsSystem->prePhysicsUpdate();
		}

		/* --- Main physics step --- */
		if (m_physicsSystem) {
			m_physicsSystem->update(evt.timeSinceLastFrame);
		}

		/* --- Hair physics pose read-back (after physics step) --- */
		if (m_hairPhysicsSystem) {
			m_hairPhysicsSystem->postPhysicsUpdate();
		}
	}

	/* --- Rendering support systems --- */
	if (m_sunSystem) {
		m_sunSystem->update(evt.timeSinceLastFrame);
	}
	if (m_skyboxSystem) {
		Ogre::Camera *cam = nullptr;
		if (m_sceneMgr->hasCamera("PlayerCamera"))
			cam = m_sceneMgr->getCamera("PlayerCamera");
		if (!cam && m_camera)
			cam = m_camera->getCamera();
		m_skyboxSystem->update(cam);
	}
	if (m_waterPlaneSystem) {
		Ogre::Camera *cam = nullptr;
		if (m_sceneMgr->hasCamera("PlayerCamera"))
			cam = m_sceneMgr->getCamera("PlayerCamera");
		if (!cam && m_camera)
			cam = m_camera->getCamera();
		m_waterPlaneSystem->update(evt.timeSinceLastFrame, cam);
	}
	if (m_lightSystem) {
		m_lightSystem->update();
	}
	if (m_cameraSystem) {
		m_cameraSystem->update();
	}
	if (m_lodSystem) {
		m_lodSystem->update();
	}
	if (m_staticGeometrySystem) {
		m_staticGeometrySystem->update();
	}
	if (m_proceduralTextureSystem) {
		m_proceduralTextureSystem->update();
	}
	if (m_proceduralMaterialSystem) {
		m_proceduralMaterialSystem->update();
	}

	// Handle "I" key to toggle character sheet (iPressed is valid here,
	// set by keyPressed which fires between frames via messagePump)
	if (m_gameInput.iPressed) {
		Ogre::String modeStr =
			(m_gameMode == GameMode::Game) ? "Game" : "Editor";
		Ogre::String stateStr =
			(m_gamePlayState == GamePlayState::Playing) ?
				"Playing" :
			(m_gamePlayState == GamePlayState::Paused) ? "Paused" :
								     "Menu";
		Ogre::String ccsStr = m_characterClassSystem ? "valid" : "null";
		Ogre::LogManager::getSingleton().logMessage(
			"EditorApp: iPressed detected, mode=" + modeStr +
			" state=" + stateStr + " ccs=" + ccsStr);
	}
	if (m_gameMode == GameMode::Game && m_gameInput.iPressed &&
	    m_characterClassSystem) {
		if (m_characterClassSystem->isSheetOpen()) {
			// Close sheet and unpause
			m_characterClassSystem->toggleCharacterSheet(
				flecs::entity::null());
			setGamePlayState(GamePlayState::Playing);
		} else if (m_gamePlayState == GamePlayState::Playing) {
			// Resolve the live player character (spawned instance if
			// the controller targets a spawner) so the sheet shows
			// the same entity ActuatorSystem uses.
			flecs::entity playerCharacter = getPlayerCharacterEntity();
			if (playerCharacter.is_valid() &&
			    playerCharacter.is_alive()) {
				m_characterClassSystem->toggleCharacterSheet(
					playerCharacter);
				setGamePlayState(GamePlayState::Paused);
				Ogre::LogManager::getSingleton().logMessage(
					"EditorApp: opened sheet for player entity " +
					Ogre::StringConverter::toString(
						(int)playerCharacter.id()));
			} else {
				Ogre::LogManager::getSingleton().logMessage(
					"EditorApp: no player entity found");
			}
		}
	}

	// Reset per-frame input state
	m_gameInput.resetPerFrame();

	// Don't call base class - it crashes when iterating input listeners
	return true;
}

bool EditorApp::mouseMoved(const OgreBites::MouseMotionEvent &evt)
{
	if (m_gameMode == GameMode::Game) {
		m_gameInput.mouseMoved = true;
		m_gameInput.mouseDeltaX += evt.xrel;
		m_gameInput.mouseDeltaY += evt.yrel;
		return true;
	}

	// Skip if ImGui wants to capture mouse (for gizmo, we still want to process even if over UI)
	// But we need to update hover state
	if (m_camera && m_uiSystem) {
		Ogre::Ray mouseRay = m_camera->getMouseRay(evt.x, evt.y);
		Ogre::Vector2 delta(evt.xrel, evt.yrel);

		// Try gizmo first - gizmo handles its own visibility checks
		if (m_uiSystem->onMouseMoved(mouseRay, delta)) {
			return true;
		}
	}

	// Only pass to camera if ImGui doesn't want the mouse
	ImGuiIO &io = ImGui::GetIO();
	if (!io.WantCaptureMouse && m_camera) {
		m_camera->handleMouseMove(evt);
	}
	return true;
}

bool EditorApp::mousePressed(const OgreBites::MouseButtonEvent &evt)
{
	if (m_gameMode == GameMode::Game) {
		return true;
	}

	// Get mouse ray for gizmo interaction FIRST (before ImGui check)
	// This allows clicking on 3D gizmo even when mouse is over empty UI areas
	if (m_camera && m_uiSystem) {
		Ogre::Ray mouseRay = m_camera->getMouseRay(evt.x, evt.y);
		std::cout << "click: " << mouseRay.getOrigin() << " "
			  << mouseRay.getDirection() << std::endl;
		// Try gizmo first - if it handles the event, don't pass to camera or UI
		if (m_uiSystem->onMousePressed(mouseRay)) {
			return true;
		}
	}

	// Then check if ImGui wants the mouse
	ImGuiIO &io = ImGui::GetIO();
	if (io.WantCaptureMouse) {
		return false; // Let ImGui handle it
	}

	if (m_camera) {
		m_camera->handleMousePress(evt);
	}
	return true;
}

bool EditorApp::mouseReleased(const OgreBites::MouseButtonEvent &evt)
{
	if (m_gameMode == GameMode::Game) {
		return true;
	}

	// Handle gizmo mouse release (always process to end dragging)
	if (m_uiSystem) {
		if (m_uiSystem->onMouseReleased()) {
			return true;
		}
	}

	// Only pass to camera if ImGui doesn't want the mouse
	ImGuiIO &io = ImGui::GetIO();
	if (!io.WantCaptureMouse && m_camera) {
		m_camera->handleMouseRelease(evt);
	}
	return true;
}

bool EditorApp::keyPressed(const OgreBites::KeyboardEvent &evt)
{
	m_currentModifiers = evt.keysym.mod;

	if (m_gameMode == GameMode::Game) {
		if (evt.keysym.sym == OgreBites::SDLK_ESCAPE) {
			if (m_gamePlayState == GamePlayState::Playing)
				setGamePlayState(GamePlayState::Paused);
			else if (m_gamePlayState == GamePlayState::Paused)
				setGamePlayState(GamePlayState::Playing);
			return true;
		}

		/* Ignore repeated keydowns for movement keys. Releasing Shift
		 * while W/A/S/D is held can generate a repeated keydown with a
		 * different symbol after the physical release, which would
		 * re-stick the key. Also avoid re-setting a key that is already
		 * marked pressed so duplicate initial keydowns cannot flip it
		 * back on after a release. */
		bool isRepeat = evt.repeat != 0;

		switch (evt.keysym.sym) {
		case 'w':
		case 'W':
			if (!isRepeat && !m_gameInput.w) {
				m_gameInput.w = true;
				EDITSCENE_LOG_INPUT("[INPUT_DBG] keyPressed W");
			}
			break;
		case 's':
		case 'S':
			if (!isRepeat && !m_gameInput.s) {
				m_gameInput.s = true;
				EDITSCENE_LOG_INPUT("[INPUT_DBG] keyPressed S");
			}
			break;
		case 'a':
		case 'A':
			if (!isRepeat && !m_gameInput.a) {
				m_gameInput.a = true;
				EDITSCENE_LOG_INPUT("[INPUT_DBG] keyPressed A");
			}
			break;
		case 'd':
		case 'D':
			if (!isRepeat && !m_gameInput.d) {
				m_gameInput.d = true;
				EDITSCENE_LOG_INPUT("[INPUT_DBG] keyPressed D");
			}
			break;
		case OgreBites::SDLK_LSHIFT:
		case SDLK_RSHIFT:
			if (!isRepeat && !m_gameInput.shift) {
				m_gameInput.shift = true;
				EDITSCENE_LOG_INPUT("[INPUT_DBG] keyPressed SHIFT");
			}
			break;
		case 'e':
		case 'E':
			m_gameInput.e = true;
			m_gameInput.ePressed = true;
			break;
		case 'f':
		case 'F':
			m_gameInput.f = true;
			m_gameInput.fPressed = true;
			break;
		case 'i':
		case 'I':
			m_gameInput.i = true;
			m_gameInput.iPressed = true;
			break;
		}
		return true;
	}

	// Forward to camera for FPS movement
	if (m_camera) {
		m_camera->handleKeyboard(evt);
	}

	// Delete key to delete selected entity
	if (evt.keysym.sym == 127) { // Delete key
		// Handled in UI
	}

	// F key to focus camera on selected entity
	if (evt.keysym.sym == 'f' || evt.keysym.sym == 'F') {
		if (m_camera && m_uiSystem &&
		    m_uiSystem->getSelectedEntity().is_alive()) {
			auto entity = m_uiSystem->getSelectedEntity();
			if (entity.has<TransformComponent>()) {
				auto &transform =
					entity.get<TransformComponent>();
				m_camera->focusOn(transform.position);
			}
		}
	}

	return true;
}

bool EditorApp::keyReleased(const OgreBites::KeyboardEvent &evt)
{
	m_currentModifiers = evt.keysym.mod;

	if (m_gameMode == GameMode::Game) {
		switch (evt.keysym.sym) {
		case 'w':
		case 'W':
			m_gameInput.w = false;
			EDITSCENE_LOG_INPUT("[INPUT_DBG] keyReleased W");
			break;
		case 's':
		case 'S':
			m_gameInput.s = false;
			EDITSCENE_LOG_INPUT("[INPUT_DBG] keyReleased S");
			break;
		case 'a':
		case 'A':
			m_gameInput.a = false;
			EDITSCENE_LOG_INPUT("[INPUT_DBG] keyReleased A");
			break;
		case 'd':
		case 'D':
			m_gameInput.d = false;
			EDITSCENE_LOG_INPUT("[INPUT_DBG] keyReleased D");
			break;
		case OgreBites::SDLK_LSHIFT:
		case SDLK_RSHIFT:
			m_gameInput.shift = false;
			EDITSCENE_LOG_INPUT("[INPUT_DBG] keyReleased SHIFT");
			break;
		case 'e':
		case 'E':
			m_gameInput.e = false;
			break;
		case 'f':
		case 'F':
			m_gameInput.f = false;
			break;
		case 'i':
		case 'I':
			m_gameInput.i = false;
			break;
		}

		return true;
	}

	// Forward to camera for FPS movement
	if (m_camera) {
		m_camera->handleKeyboard(evt);
	}

	return true;
}

flecs::entity EditorApp::getSelectedEntity() const
{
	if (m_uiSystem) {
		return m_uiSystem->getSelectedEntity();
	}
	return flecs::entity::null();
}

flecs::entity EditorApp::getPlayerCharacterEntity()
{
	flecs::entity controller = flecs::entity::null();
	m_world.query<EntityNameComponent>().each(
		[&](flecs::entity e, EntityNameComponent &name) {
			if (name.name == "player")
				controller = e;
		});

	if (!controller.is_alive() ||
	    !controller.has<PlayerControllerComponent>())
		return flecs::entity::null();

	if (m_playerControllerSystem)
		return m_playerControllerSystem->getCurrentTarget(controller);

	auto &pc = controller.get<PlayerControllerComponent>();
	flecs::entity target = flecs::entity::null();
	m_world.query<EntityNameComponent>().each(
		[&](flecs::entity e, EntityNameComponent &en) {
			if (en.name == pc.targetCharacterName)
				target = e;
		});
	return target;
}

PauseMenuSystem *EditorApp::getPauseMenuSystem() const
{
	return &PauseMenuSystem::getInstance();
}

DialogueSystem *EditorApp::getDialogueSystem() const
{
	return &DialogueSystem::getInstance();
}

void EditorApp::locateResources()
{
	Ogre::ResourceGroupManager::getSingleton().createResourceGroup(
		"Characters", true);
	Ogre::ResourceGroupManager::getSingleton().createResourceGroup(
		"LuaScripts", false);
	Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
		"./lua-scripts.package", "Package", "LuaScripts", true, true);

	/* Try multiple relative paths for characters to handle different
	 * working directories (build root vs binary subdirectory) */
	struct CharPathPair {
		const char *male;
		const char *female;
	};
	CharPathPair charPaths[] = {
		{ "./characters/male", "./characters/female" },
		{ "../../../characters/male", "../../../characters/female" },
		{ "../../characters/male", "../../characters/female" },
		{ "../characters/male", "../characters/female" },
	};

	bool added = false;
	for (const auto &pair : charPaths) {
		if (std::filesystem::exists(pair.male)) {
			Ogre::ResourceGroupManager::getSingleton()
				.addResourceLocation(pair.male, "FileSystem",
						     "Characters", false, true);
			Ogre::ResourceGroupManager::getSingleton()
				.addResourceLocation(pair.female, "FileSystem",
						     "Characters", false, true);
			Ogre::LogManager::getSingleton().logMessage(
				"Characters resource location added: " +
				Ogre::String(pair.male));
			added = true;
			break;
		}
	}
	if (!added) {
		Ogre::LogManager::getSingleton().logMessage(
			"WARNING: Could not find characters directory from " +
			std::filesystem::current_path().string());
	}

	OgreBites::ApplicationContext::locateResources();
}
