#include "SceneSerializer.hpp"
#include "ItemRegistry.hpp"
#include "ContainerStateRegistry.hpp"
#include "ItemStateRegistry.hpp"
#include "../components/Transform.hpp"
#include "../components/Renderable.hpp"
#include "../components/EntityName.hpp"
#include "../components/EditorMarker.hpp"
#include "../components/RigidBody.hpp"
#include "../components/PhysicsCollider.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/Character.hpp"
#include "../components/CharacterSpawner.hpp"
#include "../components/CharacterSlots.hpp"
#include "../components/CharacterIdentity.hpp"
#include "CharacterRegistry.hpp"
#include "../components/AnimationTree.hpp"
#include "../components/AnimationTreeTemplate.hpp"
#include "../components/StartupMenu.hpp"
#include "../components/PlayerController.hpp"
#include "../components/CellGrid.hpp"
#include "../components/GeneratedPhysicsTag.hpp"
#include "../components/BuoyancyInfo.hpp"
#include "../components/WaterPhysics.hpp"
#include "../components/WaterPlane.hpp"
#include "../components/Sun.hpp"
#include "../components/Skybox.hpp"
#include "../components/EventHandler.hpp"
#include "../components/ActionDatabase.hpp"
#include "../components/ActionDebug.hpp"
#include "../components/SmartObject.hpp"
#include "../components/Actuator.hpp"
#include "../components/GoapPlanner.hpp"
#include "../components/Item.hpp"
#include "../components/Inventory.hpp"
#include "../components/PathFollowing.hpp"
#include "../components/BehaviorTree.hpp"
#include "../components/GoapBlackboard.hpp"
#include "../components/GoapRunner.hpp"
#include "../components/NavMesh.hpp"
#include "../components/PrefabInstance.hpp"
#include "EditorUISystem.hpp"
#include <random>
#include <fstream>
#include <iostream>
#include <filesystem>

/* Forward declarations for static helpers used before their definition */
static nlohmann::json serializeGoapBlackboard(const GoapBlackboard &bb);
static void deserializeGoapBlackboard(GoapBlackboard &bb,
				      const nlohmann::json &json);

SceneSerializer::SceneSerializer(flecs::world &world,
				 Ogre::SceneManager *sceneMgr)
	: m_world(world)
	, m_sceneMgr(sceneMgr)
{
}

bool SceneSerializer::saveToFile(const std::string &filepath)
{
	try {
		nlohmann::json scene;
		scene["version"] = "1.0";
		scene["entities"] = nlohmann::json::array();

		// Collect all entities with EditorMarkerComponent
		m_world.query_builder<>()
			.with<EditorMarkerComponent>()
			.build()
			.each([&](flecs::entity entity) {
				// Only save root entities (children will be saved recursively)
				flecs::entity parent = entity.parent();
				if (!parent.is_valid() || parent == 0) {
					scene["entities"].push_back(
						serializeEntity(entity));
				}
			});

		// Save ActionDatabase singleton at scene level
		scene["actionDatabase"] = serializeActionDatabase();

		// Write to file
		std::ofstream file(filepath);
		if (!file.is_open()) {
			m_lastError =
				"Failed to open file for writing: " + filepath;
			return false;
		}

		file << scene.dump(4); // Pretty print with 4-space indent
		file.close();

		return true;
	} catch (const std::exception &e) {
		m_lastError = std::string("Save error: ") + e.what();
		return false;
	}
}

bool SceneSerializer::loadFromFile(const std::string &filepath,
				   EditorUISystem *uiSystem)
{
	try {
		// Read from file
		std::ifstream file(filepath);
		if (!file.is_open()) {
			m_lastError =
				"Failed to open file for reading: " + filepath;
			return false;
		}

		nlohmann::json scene;
		file >> scene;
		file.close();

		// Clear existing entities (optional - could be made configurable)
		// For now, we'll just add to the existing scene

		// Validate version
		if (scene.contains("version")) {
			std::string version = scene["version"];
			if (version != "1.0") {
				m_lastError =
					"Unsupported scene version: " + version;
				return false;
			}
		}

		// Load ActionDatabase singleton at scene level
		if (scene.contains("actionDatabase")) {
			deserializeActionDatabase(scene["actionDatabase"]);
		}

		// Clear entity map for new load
		m_entityMap.clear();

		// First pass: create all entities and populate the entity map
		if (scene.contains("entities") &&
		    scene["entities"].is_array()) {
			Ogre::LogManager::getSingleton().logMessage(
				"SceneSerializer: Loading " +
				Ogre::StringConverter::toString(
					scene["entities"].size()) +
				" entities (first pass)");
			for (const auto &entityJson : scene["entities"]) {
				deserializeEntityFirstPass(
					entityJson, flecs::entity::null(),
					uiSystem);
			}
		}

		// Second pass: resolve procedural texture references and material
		// entity references.
		Ogre::LogManager::getSingleton().logMessage(
			"SceneSerializer: Resolving procedural texture references (second pass)");
		resolveProceduralTextureReferences();

		Ogre::LogManager::getSingleton().logMessage(
			"SceneSerializer: Resolving material references (second pass)");
		resolveMaterialReferences();

		m_entityMap.clear();
		return true;
	} catch (const std::exception &e) {
		m_lastError = std::string("Load error: ") + e.what();
		return false;
	}
}

nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity)
{
	nlohmann::json json;

	// Store entity ID for parent/child relationships
	json["id"] = entity.id();

	// Serialize components
	if (entity.has<EntityNameComponent>()) {
		json["name"] = serializeEntityName(entity);
	}

	if (entity.has<TransformComponent>()) {
		json["transform"] = serializeTransform(entity);
	}

	if (entity.has<RenderableComponent>()) {
		json["renderable"] = serializeRenderable(entity);
	}

	if (entity.has<RigidBodyComponent>()) {
		json["rigidBody"] = serializeRigidBody(entity);
	}

	if (entity.has<PhysicsColliderComponent>()) {
		json["collider"] = serializeCollider(entity);
	}

	if (entity.has<LightComponent>()) {
		json["light"] = serializeLight(entity);
	}

	if (entity.has<CameraComponent>()) {
		json["camera"] = serializeCamera(entity);
	}

	if (entity.has<LodSettingsComponent>()) {
		json["lodSettings"] = serializeLodSettings(entity);
	}

	if (entity.has<LodComponent>()) {
		json["lod"] = serializeLod(entity);
	}

	if (entity.has<StaticGeometryComponent>()) {
		json["staticGeometry"] = serializeStaticGeometry(entity);
	}

	if (entity.has<StaticGeometryMemberComponent>()) {
		json["staticGeometryMember"] =
			serializeStaticGeometryMember(entity);
	}

	if (entity.has<ProceduralTextureComponent>()) {
		json["proceduralTexture"] = serializeProceduralTexture(entity);
	}

	if (entity.has<ProceduralMaterialComponent>()) {
		json["proceduralMaterial"] =
			serializeProceduralMaterial(entity);
	}

	if (entity.has<PrimitiveComponent>()) {
		json["primitive"] = serializePrimitive(entity);
	}

	if (entity.has<TriangleBufferComponent>()) {
		json["triangleBuffer"] = serializeTriangleBuffer(entity);
	}

	if (entity.has<CharacterComponent>()) {
		json["character"] = serializeCharacter(entity);
	}

	if (entity.has<CharacterIdentityComponent>()) {
		json["characterIdentity"] = serializeCharacterIdentity(entity);
	}

	if (entity.has<CharacterSpawnerComponent>()) {
		json["characterSpawner"] = serializeCharacterSpawner(entity);
	}

	if (entity.has<AnimationTreeComponent>()) {
		json["animationTree"] = serializeAnimationTree(entity);
	}

	if (entity.has<AnimationTreeTemplate>()) {
		json["animationTreeTemplate"] =
			serializeAnimationTreeTemplate(entity);
	}

	if (entity.has<StartupMenuComponent>()) {
		json["startupMenu"] = serializeStartupMenu(entity);
	}

	if (entity.has<PlayerControllerComponent>()) {
		json["playerController"] = serializePlayerController(entity);
	}

	// CellGrid/Town components
	if (entity.has<CellGridComponent>()) {
		json["cellGrid"] = serializeCellGrid(entity);
	}
	if (entity.has<TownComponent>()) {
		json["town"] = serializeTown(entity);
	}
	if (entity.has<DistrictComponent>()) {
		json["district"] = serializeDistrict(entity);
	}
	if (entity.has<LotComponent>()) {
		json["lot"] = serializeLot(entity);
	}
	if (entity.has<RoomComponent>()) {
		json["room"] = serializeRoom(entity);
	}
	if (entity.has<RoofComponent>()) {
		json["roof"] = serializeRoof(entity);
	}
	if (entity.has<FurnitureTemplateComponent>()) {
		json["furnitureTemplate"] = serializeFurnitureTemplate(entity);
	}
	if (entity.has<ClearAreaComponent>()) {
		json["clearArea"] = serializeClearArea(entity);
	}

	if (entity.has<NavMeshComponent>()) {
		json["navMesh"] = serializeNavMesh(entity);
	}

	if (entity.has<NavMeshGeometrySource>()) {
		json["navMeshGeometrySource"] =
			serializeNavMeshGeometrySource(entity);
	}

	// Buoyancy components
	if (entity.has<BuoyancyInfo>()) {
		json["buoyancyInfo"] = serializeBuoyancyInfo(entity);
	}

	if (entity.has<WaterPhysics>()) {
		json["waterPhysics"] = serializeWaterPhysics(entity);
	}

	if (entity.has<WaterPlane>()) {
		json["waterPlane"] = serializeWaterPlane(entity);
	}

	// ActionDatabase is now a singleton, serialized at scene level
	if (entity.has<ActionDebug>()) {
		json["actionDebug"] = serializeActionDebug(entity);
	}
	if (entity.has<PathFollowingComponent>()) {
		json["pathFollowing"] = serializePathFollowing(entity);
	}
	if (entity.has<SmartObjectComponent>()) {
		json["smartObject"] = serializeSmartObject(entity);
	}
	if (entity.has<ActuatorComponent>()) {
		json["actuator"] = serializeActuator(entity);
	}
	if (entity.has<EventHandlerComponent>()) {
		json["eventHandler"] = serializeEventHandler(entity);
	}
	if (entity.has<GoapPlannerComponent>()) {
		json["goapPlanner"] = serializeGoapPlanner(entity);
	}
	if (entity.has<GoapRunnerComponent>()) {
		json["goapRunner"] = serializeGoapRunner(entity);
	}
	if (entity.has<BehaviorTreeComponent>()) {
		json["behaviorTree"] = serializeBehaviorTree(entity);
	}

	if (entity.has<GoapBlackboard>()) {
		json["goapBlackboard"] =
			serializeGoapBlackboard(entity.get<GoapBlackboard>());
	}

	if (entity.has<ItemComponent>()) {
		json["item"] = serializeItem(entity);
	}

	if (entity.has<InventoryComponent>()) {
		json["inventory"] = serializeInventory(entity);
	}

	if (entity.has<PrefabInstanceComponent>()) {
		json["prefabInstance"] = serializePrefabInstance(entity);
	}

	if (entity.has<SunComponent>()) {
		json["sun"] = serializeSun(entity);
	}

	if (entity.has<SkyboxComponent>()) {
		json["skybox"] = serializeSkybox(entity);
	}

	// Serialize children
	json["children"] = nlohmann::json::array();
	entity.children([&](flecs::entity child) {
		if (child.has<EditorMarkerComponent>() &&
		    !child.has<GeneratedPhysicsTag>()) {
			json["children"].push_back(serializeEntity(child));
		}
	});

	return json;
}

void SceneSerializer::deserializeEntity(const nlohmann::json &json,
					flecs::entity parent,
					EditorUISystem *uiSystem)
{
	// Create new entity
	flecs::entity entity = m_world.entity();
	entity.add<EditorMarkerComponent>();

	// Store in map for potential future reference
	if (json.contains("id")) {
		uint64_t id = json["id"];
		m_entityMap[id] = entity;
	}

	// Set parent relationship
	if (parent.is_valid() && parent != 0) {
		entity.child_of(parent);
	}

	// Deserialize components
	if (json.contains("name")) {
		deserializeEntityName(entity, json["name"]);
	} else {
		entity.set<EntityNameComponent>(EntityNameComponent("Entity"));
	}

	if (json.contains("transform")) {
		deserializeTransform(entity, json["transform"], parent);
	}

	if (json.contains("renderable")) {
		deserializeRenderable(entity, json["renderable"]);
	}

	if (json.contains("rigidBody")) {
		deserializeRigidBody(entity, json["rigidBody"]);
	}

	if (json.contains("collider")) {
		deserializeCollider(entity, json["collider"]);
	}

	if (json.contains("light")) {
		deserializeLight(entity, json["light"]);
	}

	if (json.contains("camera")) {
		deserializeCamera(entity, json["camera"]);
	}

	if (json.contains("lodSettings")) {
		Ogre::LogManager::getSingleton().logMessage(
			"SceneSerializer: Found lodSettings for entity " +
			Ogre::StringConverter::toString(entity.id()));
		deserializeLodSettings(entity, json["lodSettings"]);
	}

	if (json.contains("lod")) {
		Ogre::LogManager::getSingleton().logMessage(
			"SceneSerializer: Found lod for entity " +
			Ogre::StringConverter::toString(entity.id()));
		deserializeLod(entity, json["lod"]);
	}

	if (json.contains("staticGeometry")) {
		deserializeStaticGeometry(entity, json["staticGeometry"]);
	}

	if (json.contains("staticGeometryMember")) {
		deserializeStaticGeometryMember(entity,
						json["staticGeometryMember"]);
	}

	if (json.contains("proceduralTexture")) {
		deserializeProceduralTexture(entity, json["proceduralTexture"]);
	}

	if (json.contains("proceduralMaterial")) {
		deserializeProceduralMaterial(entity,
					      json["proceduralMaterial"]);
	}

	if (json.contains("primitive")) {
		deserializePrimitive(entity, json["primitive"]);
	}

	if (json.contains("character")) {
		deserializeCharacter(entity, json["character"]);
	}

	/* Deserialize identity before slots so legacy slot data migrates into
	 * the correct registry record. */
	if (json.contains("characterIdentity")) {
		deserializeCharacterIdentity(entity, json["characterIdentity"]);
	}

	if (json.contains("characterSlots")) {
		deserializeCharacterSlots(entity, json["characterSlots"]);
	}

	if (json.contains("characterSpawner")) {
		deserializeCharacterSpawner(entity, json["characterSpawner"]);
	}

	if (json.contains("animationTree")) {
		deserializeAnimationTree(entity, json["animationTree"]);
	}

	if (json.contains("animationTreeTemplate")) {
		deserializeAnimationTreeTemplate(entity,
						 json["animationTreeTemplate"]);
	}

	if (json.contains("startupMenu")) {
		deserializeStartupMenu(entity, json["startupMenu"]);
	}

	if (json.contains("playerController")) {
		deserializePlayerController(entity, json["playerController"]);
	}

	if (json.contains("triangleBuffer")) {
		deserializeTriangleBuffer(entity, json["triangleBuffer"]);
	}

	// CellGrid/Town components
	if (json.contains("cellGrid")) {
		deserializeCellGrid(entity, json["cellGrid"]);
	}
	if (json.contains("town")) {
		deserializeTown(entity, json["town"]);
	}
	if (json.contains("district")) {
		deserializeDistrict(entity, json["district"]);
	}
	if (json.contains("lot")) {
		deserializeLot(entity, json["lot"]);
	}
	if (json.contains("room")) {
		deserializeRoom(entity, json["room"]);
	}
	if (json.contains("roof")) {
		deserializeRoof(entity, json["roof"]);
	}
	if (json.contains("furnitureTemplate")) {
		deserializeFurnitureTemplate(entity, json["furnitureTemplate"]);
	}
	if (json.contains("clearArea")) {
		deserializeClearArea(entity, json["clearArea"]);
	}

	if (json.contains("navMesh")) {
		deserializeNavMesh(entity, json["navMesh"]);
	}

	if (json.contains("navMeshGeometrySource")) {
		deserializeNavMeshGeometrySource(entity,
						 json["navMeshGeometrySource"]);
	}

	// Buoyancy components
	if (json.contains("buoyancyInfo")) {
		deserializeBuoyancyInfo(entity, json["buoyancyInfo"]);
	}

	if (json.contains("waterPhysics")) {
		deserializeWaterPhysics(entity, json["waterPhysics"]);
	}

	if (json.contains("waterPlane")) {
		deserializeWaterPlane(entity, json["waterPlane"]);
	}

	// ActionDatabase is now a singleton, deserialized at scene level
	if (json.contains("actionDebug")) {
		deserializeActionDebug(entity, json["actionDebug"]);
	}
	if (json.contains("pathFollowing")) {
		deserializePathFollowing(entity, json["pathFollowing"]);
	}
	if (json.contains("smartObject")) {
		deserializeSmartObject(entity, json["smartObject"]);
	}
	if (json.contains("actuator")) {
		deserializeActuator(entity, json["actuator"]);
	}
	if (json.contains("eventHandler")) {
		deserializeEventHandler(entity, json["eventHandler"]);
	}
	if (json.contains("goapPlanner")) {
		deserializeGoapPlanner(entity, json["goapPlanner"]);
	}
	if (json.contains("goapRunner")) {
		deserializeGoapRunner(entity, json["goapRunner"]);
	}
	if (json.contains("behaviorTree")) {
		deserializeBehaviorTree(entity, json["behaviorTree"]);
	}

	if (json.contains("goapBlackboard")) {
		GoapBlackboard bb;
		deserializeGoapBlackboard(bb, json["goapBlackboard"]);
		entity.set<GoapBlackboard>(bb);
	}

	if (json.contains("item")) {
		deserializeItem(entity, json["item"]);
	}

	if (json.contains("inventory")) {
		deserializeInventory(entity, json["inventory"]);
	}

	if (json.contains("sun")) {
		deserializeSun(entity, json["sun"]);
	}

	if (json.contains("skybox")) {
		deserializeSkybox(entity, json["skybox"]);
	}

	// Add to UI system if provided
	if (uiSystem) {
		uiSystem->addEntity(entity);
	}

	// Deserialize children
	if (json.contains("children") && json["children"].is_array()) {
		for (const auto &childJson : json["children"]) {
			deserializeEntity(childJson, entity, uiSystem);
		}
	}
}

void SceneSerializer::deserializeEntityFirstPass(const nlohmann::json &json,
						 flecs::entity parent,
						 EditorUISystem *uiSystem)
{
	// Create new entity
	flecs::entity entity = m_world.entity();
	entity.add<EditorMarkerComponent>();

	// Store in map for potential future reference
	if (json.contains("id")) {
		uint64_t id = json["id"];
		m_entityMap[id] = entity;
	}

	// Set parent relationship
	if (parent.is_valid() && parent != 0) {
		entity.child_of(parent);
	}

	deserializeEntityComponents(entity, json, parent, uiSystem, true, true,
				    true);
}

void SceneSerializer::deserializeEntityComponents(
	flecs::entity entity, const nlohmann::json &json, flecs::entity parent,
	EditorUISystem *uiSystem, bool processTransform, bool processName,
	bool addEditorMarker)
{
	if (processName) {
		if (json.contains("name")) {
			deserializeEntityName(entity, json["name"]);
		} else {
			entity.set<EntityNameComponent>(
				EntityNameComponent("Entity"));
		}
	}

	if (processTransform && json.contains("transform")) {
		deserializeTransform(entity, json["transform"], parent);
	}

	if (json.contains("renderable")) {
		deserializeRenderable(entity, json["renderable"]);
	}

	if (json.contains("rigidBody")) {
		deserializeRigidBody(entity, json["rigidBody"]);
	}

	if (json.contains("collider")) {
		deserializeCollider(entity, json["collider"]);
	}

	if (json.contains("light")) {
		deserializeLight(entity, json["light"]);
	}

	if (json.contains("camera")) {
		deserializeCamera(entity, json["camera"]);
	}

	if (json.contains("lodSettings")) {
		deserializeLodSettings(entity, json["lodSettings"]);
	}

	if (json.contains("lod")) {
		deserializeLod(entity, json["lod"]);
	}

	if (json.contains("staticGeometry")) {
		deserializeStaticGeometry(entity, json["staticGeometry"]);
	}

	if (json.contains("staticGeometryMember")) {
		deserializeStaticGeometryMember(entity,
						json["staticGeometryMember"]);
	}

	if (json.contains("proceduralTexture")) {
		deserializeProceduralTexture(entity, json["proceduralTexture"]);
	}

	if (json.contains("proceduralMaterial")) {
		deserializeProceduralMaterial(entity,
					      json["proceduralMaterial"]);
	}

	if (json.contains("primitive")) {
		deserializePrimitive(entity, json["primitive"]);
	}

	if (json.contains("character")) {
		deserializeCharacter(entity, json["character"]);
	}

	/* Deserialize identity before slots so legacy slot data migrates into
	 * the correct registry record. */
	if (json.contains("characterIdentity")) {
		deserializeCharacterIdentity(entity, json["characterIdentity"]);
	}

	if (json.contains("characterSlots")) {
		deserializeCharacterSlots(entity, json["characterSlots"]);
	}

	if (json.contains("characterSpawner")) {
		deserializeCharacterSpawner(entity, json["characterSpawner"]);
	}

	if (json.contains("animationTree")) {
		deserializeAnimationTree(entity, json["animationTree"]);
	}

	if (json.contains("animationTreeTemplate")) {
		deserializeAnimationTreeTemplate(entity,
						 json["animationTreeTemplate"]);
	}

	if (json.contains("startupMenu")) {
		deserializeStartupMenu(entity, json["startupMenu"]);
	}

	if (json.contains("playerController")) {
		deserializePlayerController(entity, json["playerController"]);
	}

	if (json.contains("triangleBuffer")) {
		deserializeTriangleBuffer(entity, json["triangleBuffer"]);
	}

	if (json.contains("prefabInstance")) {
		deserializePrefabInstance(entity, json["prefabInstance"]);
	}

	// CellGrid/Town components - deserialize WITHOUT resolving
	// material references (deferred to second pass)
	if (json.contains("cellGrid")) {
		deserializeCellGrid(entity, json["cellGrid"]);
	}
	if (json.contains("town")) {
		m_pendingTownResolutions.push_back({ entity, json["town"] });
		TownComponent town;
		town.townName = json["town"].value("townName", "New Town");
		town.materialName = json["town"].value("materialName", "");
		town.proceduralMaterialEntityId =
			json["town"].value("proceduralMaterialEntityId", "");
		town.textureRectName =
			json["town"].value("textureRectName", "");
		if (json["town"].contains("colorRects") &&
		    json["town"]["colorRects"].is_object()) {
			for (auto &[name, rectJson] :
			     json["town"]["colorRects"].items()) {
				TownComponent::ColorRect rect;
				rect.left = rectJson.value("left", 0.0f);
				rect.top = rectJson.value("top", 0.0f);
				rect.right = rectJson.value("right", 1.0f);
				rect.bottom = rectJson.value("bottom", 1.0f);
				if (rectJson.contains("color")) {
					auto &c = rectJson["color"];
					rect.color.r = c.value("r", 1.0f);
					rect.color.g = c.value("g", 1.0f);
					rect.color.b = c.value("b", 1.0f);
					rect.color.a = c.value("a", 1.0f);
				}
				town.colorRects[name] = rect;
			}
		}
		town.dirty = true;
		town.materialDirty = true;
		entity.set<TownComponent>(town);
	}
	if (json.contains("district")) {
		m_pendingDistrictResolutions.push_back(
			{ entity, json["district"] });
		DistrictComponent district;
		district.radius = json["district"].value("radius", 50.0f);
		district.elevation = json["district"].value("elevation", 0.0f);
		district.height = json["district"].value("height", 0.2f);
		district.isPlaza = json["district"].value("isPlaza", false);
		district.proceduralMaterialEntityId = json["district"].value(
			"proceduralMaterialEntityId", "");
		district.textureRectName =
			json["district"].value("textureRectName", "");
		district.friction = json["district"].value("friction", 0.5f);
		if (json["district"].contains("lotTemplateNames") &&
		    json["district"]["lotTemplateNames"].is_array()) {
			for (const auto &name :
			     json["district"]["lotTemplateNames"]) {
				if (name.is_string()) {
					district.lotTemplateNames.push_back(
						name);
				}
			}
		}
		district.dirty = true;
		entity.set<DistrictComponent>(district);
	}
	if (json.contains("lot")) {
		m_pendingLotResolutions.push_back({ entity, json["lot"] });
		LotComponent lot;
		lot.width = json["lot"].value("width", 10);
		lot.depth = json["lot"].value("depth", 10);
		lot.elevation = json["lot"].value("elevation", 0.0f);
		lot.angle = json["lot"].value("angle", 0.0f);
		lot.offsetX = json["lot"].value("offsetX", 0.0f);
		lot.offsetZ = json["lot"].value("offsetZ", 0.0f);
		lot.templateName = json["lot"].value("templateName", "");
		lot.proceduralMaterialEntityId =
			json["lot"].value("proceduralMaterialEntityId", "");
		lot.textureRectName = json["lot"].value("textureRectName", "");
		lot.friction = json["lot"].value("friction", 0.5f);
		lot.dirty = true;
		entity.set<LotComponent>(lot);
	}
	if (json.contains("room")) {
		deserializeRoom(entity, json["room"]);
	}
	if (json.contains("roof")) {
		deserializeRoof(entity, json["roof"]);
	}
	if (json.contains("furnitureTemplate")) {
		deserializeFurnitureTemplate(entity, json["furnitureTemplate"]);
	}
	if (json.contains("clearArea")) {
		deserializeClearArea(entity, json["clearArea"]);
	}

	if (json.contains("navMesh")) {
		deserializeNavMesh(entity, json["navMesh"]);
	}

	if (json.contains("navMeshGeometrySource")) {
		deserializeNavMeshGeometrySource(entity,
						 json["navMeshGeometrySource"]);
	}

	if (json.contains("buoyancyInfo")) {
		deserializeBuoyancyInfo(entity, json["buoyancyInfo"]);
	}

	if (json.contains("waterPhysics")) {
		deserializeWaterPhysics(entity, json["waterPhysics"]);
	}

	if (json.contains("waterPlane")) {
		deserializeWaterPlane(entity, json["waterPlane"]);
	}

	// ActionDatabase is now a singleton, deserialized at scene level
	if (json.contains("actionDebug")) {
		deserializeActionDebug(entity, json["actionDebug"]);
	}
	if (json.contains("pathFollowing")) {
		deserializePathFollowing(entity, json["pathFollowing"]);
	}
	if (json.contains("smartObject")) {
		deserializeSmartObject(entity, json["smartObject"]);
	}
	if (json.contains("actuator")) {
		deserializeActuator(entity, json["actuator"]);
		if (json.contains("eventHandler")) {
			deserializeEventHandler(entity, json["eventHandler"]);
		}
	}
	if (json.contains("goapPlanner")) {
		deserializeGoapPlanner(entity, json["goapPlanner"]);
	}
	if (json.contains("goapRunner")) {
		deserializeGoapRunner(entity, json["goapRunner"]);
	}
	if (json.contains("behaviorTree")) {
		deserializeBehaviorTree(entity, json["behaviorTree"]);
	}

	if (json.contains("item")) {
		deserializeItem(entity, json["item"]);
	}

	if (json.contains("inventory")) {
		deserializeInventory(entity, json["inventory"]);
	}

	if (json.contains("sun")) {
		deserializeSun(entity, json["sun"]);
	}

	if (json.contains("skybox")) {
		deserializeSkybox(entity, json["skybox"]);
	}

	if (uiSystem) {
		uiSystem->addEntity(entity);
	}

	if (json.contains("children") && json["children"].is_array()) {
		for (const auto &childJson : json["children"]) {
			flecs::entity child = m_world.entity();
			if (addEditorMarker)
				child.add<EditorMarkerComponent>();
			if (childJson.contains("id")) {
				uint64_t cid = childJson.value("id", 0ULL);
				if (cid)
					m_entityMap[cid] = child;
			}
			child.child_of(entity);
			deserializeEntityComponents(child, childJson, entity,
						    uiSystem, true, true,
						    addEditorMarker);
		}
	}
}

// ------------------------------------------------------------------
// Prefab support
// ------------------------------------------------------------------

nlohmann::json SceneSerializer::serializePrefabInstance(flecs::entity entity)
{
	nlohmann::json json;
	auto &prefab = entity.get<PrefabInstanceComponent>();
	json["prefabPath"] = prefab.prefabPath;
	return json;
}

void SceneSerializer::deserializePrefabInstance(flecs::entity entity,
						const nlohmann::json &json)
{
	PrefabInstanceComponent prefab;
	prefab.prefabPath = json.value("prefabPath", "");
	prefab.instantiated = false;
	entity.set<PrefabInstanceComponent>(prefab);
}

bool SceneSerializer::savePrefab(flecs::entity rootEntity,
				 const std::string &filepath)
{
	try {
		std::filesystem::path p(filepath);
		std::filesystem::create_directories(p.parent_path());

		nlohmann::json prefab = serializeEntity(rootEntity);
		std::ofstream file(filepath);
		if (!file.is_open()) {
			m_lastError = "Failed to open prefab for writing: " +
				      filepath;
			return false;
		}
		file << prefab.dump(4);
		file.close();
		return true;
	} catch (const std::exception &e) {
		m_lastError = std::string("Prefab save error: ") + e.what();
		return false;
	}
}

flecs::entity SceneSerializer::loadPrefabForEdit(const std::string &filepath,
						 EditorUISystem *uiSystem)
{
	try {
		std::ifstream file(filepath);
		if (!file.is_open()) {
			m_lastError = "Failed to open prefab: " + filepath;
			return flecs::entity::null();
		}

		nlohmann::json prefabJson;
		file >> prefabJson;
		file.close();

		flecs::entity entity = m_world.entity();
		entity.add<EditorMarkerComponent>();
		if (prefabJson.contains("id")) {
			m_entityMap[prefabJson["id"]] = entity;
		}

		if (!prefabJson.contains("name")) {
			entity.set<EntityNameComponent>(
				EntityNameComponent("Prefab"));
		}

		deserializeEntityComponents(entity, prefabJson,
					    flecs::entity::null(), uiSystem,
					    true, true, true);

		return entity;
	} catch (const std::exception &e) {
		m_lastError = std::string("Prefab load error: ") + e.what();
		return flecs::entity::null();
	}
}

bool SceneSerializer::instantiatePrefab(flecs::entity instanceEntity,
					const std::string &filepath,
					EditorUISystem *uiSystem)
{
	try {
		std::ifstream file(filepath);
		if (!file.is_open()) {
			m_lastError = "Failed to open prefab: " + filepath;
			return false;
		}

		nlohmann::json prefabJson;
		file >> prefabJson;
		file.close();

		// Save entity map state — prefabs use their own local IDs
		auto savedMap = m_entityMap;
		m_entityMap.clear();

		// Store prefab root ID mapping if present
		if (prefabJson.contains("id")) {
			m_entityMap[prefabJson["id"]] = instanceEntity;
		}

		// Apply prefab root components to instance entity.
		// Skip transform — the instance entity's TransformComponent
		// is the world-space override.
		// Skip name — the instance entity keeps its own name.
		flecs::entity parent = instanceEntity.parent();
		deserializeEntityComponents(instanceEntity, prefabJson, parent,
					    uiSystem, false, false, false);

		// Restore main scene entity map
		m_entityMap = savedMap;

		return true;
	} catch (const std::exception &e) {
		m_lastError =
			std::string("Prefab instantiate error: ") + e.what();
		return false;
	}
}

void SceneSerializer::resolveProceduralTextureReferences()
{
	for (auto &pending : m_pendingProceduralTextureResolutions) {
		flecs::entity entity = pending.first;
		const std::string &textureId = pending.second;
		if (!entity.is_alive() || textureId.empty() ||
		    !entity.has<ProceduralMaterialComponent>())
			continue;

		auto &material = entity.get_mut<ProceduralMaterialComponent>();
		flecs::entity resolved = flecs::entity::null();
		m_world.query<ProceduralTextureComponent>().each(
			[&](flecs::entity textureEntity,
			    ProceduralTextureComponent &texture) {
				if (texture.textureId == textureId) {
					resolved = textureEntity;
				}
			});

		if (resolved.is_alive()) {
			material.diffuseTextureEntity = resolved;
			Ogre::LogManager::getSingleton().logMessage(
				"SceneSerializer: Resolved ProceduralMaterial " +
				std::to_string(entity.id()) +
				" diffuse texture " + textureId);
		}
	}

	m_pendingProceduralTextureResolutions.clear();
}

void SceneSerializer::resolveMaterialReferences()
{
	// Resolve Town material references
	for (auto &pending : m_pendingTownResolutions) {
		flecs::entity entity = pending.entity;
		if (!entity.is_alive())
			continue;
		auto &town = entity.get_mut<TownComponent>();

		if (!town.proceduralMaterialEntityId.empty()) {
			try {
				uint64_t matId = std::stoull(
					town.proceduralMaterialEntityId);
				auto matIt = m_entityMap.find(matId);
				if (matIt != m_entityMap.end()) {
					flecs::entity matEntity = matIt->second;
					if (matEntity.is_alive() &&
					    matEntity.has<
						    ProceduralMaterialComponent>()) {
						town.proceduralMaterialEntity =
							matEntity;
						town.proceduralMaterialEntityId =
							std::to_string(matEntity.id());
						Ogre::LogManager::getSingleton().logMessage(
							"SceneSerializer: Resolved Town material entity " +
							Ogre::StringConverter::
								toString(
									matId));
					}
				}
			} catch (...) {
			}
		}
	}

	// Resolve District material references
	for (auto &pending : m_pendingDistrictResolutions) {
		flecs::entity entity = pending.entity;
		if (!entity.is_alive())
			continue;
		auto &district = entity.get_mut<DistrictComponent>();

		if (!district.proceduralMaterialEntityId.empty()) {
			try {
				uint64_t matId = std::stoull(
					district.proceduralMaterialEntityId);
				auto matIt = m_entityMap.find(matId);
				if (matIt != m_entityMap.end()) {
					flecs::entity matEntity = matIt->second;
					if (matEntity.is_alive() &&
					    matEntity.has<
						    ProceduralMaterialComponent>()) {
						district.proceduralMaterialEntity =
							matEntity;
						district.proceduralMaterialEntityId =
							std::to_string(matEntity.id());
						Ogre::LogManager::getSingleton().logMessage(
							"SceneSerializer: Resolved District material entity " +
							Ogre::StringConverter::
								toString(
									matId));
					}
				}
			} catch (...) {
			}
		}
	}

	// Resolve Lot material references
	for (auto &pending : m_pendingLotResolutions) {
		flecs::entity entity = pending.entity;
		if (!entity.is_alive())
			continue;
		auto &lot = entity.get_mut<LotComponent>();

		if (!lot.proceduralMaterialEntityId.empty()) {
			try {
				uint64_t matId = std::stoull(
					lot.proceduralMaterialEntityId);
				auto matIt = m_entityMap.find(matId);
				if (matIt != m_entityMap.end()) {
					flecs::entity matEntity = matIt->second;
					if (matEntity.is_alive() &&
					    matEntity.has<
						    ProceduralMaterialComponent>()) {
						lot.proceduralMaterialEntity =
							matEntity;
						lot.proceduralMaterialEntityId =
							std::to_string(matEntity.id());
						Ogre::LogManager::getSingleton().logMessage(
							"SceneSerializer: Resolved Lot material entity " +
							Ogre::StringConverter::
								toString(
									matId));
					}
				}
			} catch (...) {
			}
		}
	}

	// Clear pending resolutions
	m_pendingTownResolutions.clear();
	m_pendingDistrictResolutions.clear();
	m_pendingLotResolutions.clear();
}

nlohmann::json SceneSerializer::serializeTransform(flecs::entity entity)
{
	auto &transform = entity.get<TransformComponent>();
	nlohmann::json json;

	json["position"] = { { "x", transform.position.x },
			     { "y", transform.position.y },
			     { "z", transform.position.z } };

	json["rotation"] = { { "w", transform.rotation.w },
			     { "x", transform.rotation.x },
			     { "y", transform.rotation.y },
			     { "z", transform.rotation.z } };

	json["scale"] = { { "x", transform.scale.x },
			  { "y", transform.scale.y },
			  { "z", transform.scale.z } };

	return json;
}

nlohmann::json SceneSerializer::serializeRenderable(flecs::entity entity)
{
	auto &renderable = entity.get<RenderableComponent>();
	nlohmann::json json;
	json["meshName"] = renderable.meshName;
	json["visible"] = renderable.visible;
	return json;
}

nlohmann::json SceneSerializer::serializeEntityName(flecs::entity entity)
{
	auto &name = entity.get<EntityNameComponent>();
	nlohmann::json json;
	json["name"] = name.name;
	return json;
}

nlohmann::json SceneSerializer::serializeRigidBody(flecs::entity entity)
{
	auto &rb = entity.get<RigidBodyComponent>();
	nlohmann::json json;

	// Serialize body type as string
	switch (rb.bodyType) {
	case RigidBodyComponent::BodyType::Static:
		json["bodyType"] = "static";
		break;
	case RigidBodyComponent::BodyType::Dynamic:
		json["bodyType"] = "dynamic";
		break;
	case RigidBodyComponent::BodyType::Kinematic:
		json["bodyType"] = "kinematic";
		break;
	}

	json["mass"] = rb.mass;
	json["friction"] = rb.friction;
	json["restitution"] = rb.restitution;
	json["isSensor"] = rb.isSensor;
	json["enabled"] = rb.enabled;

	return json;
}

nlohmann::json SceneSerializer::serializeCollider(flecs::entity entity)
{
	auto &collider = entity.get<PhysicsColliderComponent>();
	nlohmann::json json;

	// Serialize shape type as string
	switch (collider.shapeType) {
	case PhysicsColliderComponent::ShapeType::Box:
		json["shapeType"] = "box";
		break;
	case PhysicsColliderComponent::ShapeType::Sphere:
		json["shapeType"] = "sphere";
		break;
	case PhysicsColliderComponent::ShapeType::Capsule:
		json["shapeType"] = "capsule";
		break;
	case PhysicsColliderComponent::ShapeType::Cylinder:
		json["shapeType"] = "cylinder";
		break;
	case PhysicsColliderComponent::ShapeType::Mesh:
		json["shapeType"] = "mesh";
		break;
	case PhysicsColliderComponent::ShapeType::ConvexHull:
		json["shapeType"] = "convexHull";
		break;
	}

	// Serialize shape parameters
	json["parameters"] = { { "x", collider.parameters.x },
			       { "y", collider.parameters.y },
			       { "z", collider.parameters.z } };

	json["radius"] = collider.radius;
	json["halfHeight"] = collider.halfHeight;
	json["meshName"] = collider.meshName;

	// Serialize offset
	json["offset"] = { { "x", collider.offset.x },
			   { "y", collider.offset.y },
			   { "z", collider.offset.z } };

	// Serialize rotation offset
	json["rotationOffset"] = { { "w", collider.rotationOffset.w },
				   { "x", collider.rotationOffset.x },
				   { "y", collider.rotationOffset.y },
				   { "z", collider.rotationOffset.z } };

	return json;
}

nlohmann::json SceneSerializer::serializeLight(flecs::entity entity)
{
	auto &light = entity.get<LightComponent>();
	nlohmann::json json;

	// Serialize light type
	switch (light.lightType) {
	case LightComponent::LightType::Point:
		json["lightType"] = "point";
		break;
	case LightComponent::LightType::Directional:
		json["lightType"] = "directional";
		break;
	case LightComponent::LightType::Spotlight:
		json["lightType"] = "spotlight";
		break;
	}

	// Colors
	json["diffuseColor"] = { { "r", light.diffuseColor.r },
				 { "g", light.diffuseColor.g },
				 { "b", light.diffuseColor.b },
				 { "a", light.diffuseColor.a } };

	json["specularColor"] = { { "r", light.specularColor.r },
				  { "g", light.specularColor.g },
				  { "b", light.specularColor.b },
				  { "a", light.specularColor.a } };

	json["intensity"] = light.intensity;

	// Attenuation
	json["range"] = light.range;
	json["constantAttenuation"] = light.constantAttenuation;
	json["linearAttenuation"] = light.linearAttenuation;
	json["quadraticAttenuation"] = light.quadraticAttenuation;

	// Spotlight settings
	json["spotlightInnerAngle"] = light.spotlightInnerAngle;
	json["spotlightOuterAngle"] = light.spotlightOuterAngle;
	json["spotlightFalloff"] = light.spotlightFalloff;

	// Direction
	json["direction"] = { { "x", light.direction.x },
			      { "y", light.direction.y },
			      { "z", light.direction.z } };

	json["castShadows"] = light.castShadows;

	return json;
}

nlohmann::json SceneSerializer::serializeCamera(flecs::entity entity)
{
	auto &camera = entity.get<CameraComponent>();
	nlohmann::json json;

	json["fovY"] = camera.fovY;
	json["nearClip"] = camera.nearClip;
	json["farClip"] = camera.farClip;
	json["aspectRatio"] = camera.aspectRatio;
	json["orthographic"] = camera.orthographic;
	json["orthoWidth"] = camera.orthoWidth;
	json["orthoHeight"] = camera.orthoHeight;

	return json;
}

void SceneSerializer::deserializeTransform(flecs::entity entity,
					   const nlohmann::json &json,
					   flecs::entity parentEntity)
{
	TransformComponent transform;

	// Read position
	if (json.contains("position")) {
		auto &pos = json["position"];
		transform.position = Ogre::Vector3(pos.value("x", 0.0f),
						   pos.value("y", 0.0f),
						   pos.value("z", 0.0f));
	}

	// Read rotation
	if (json.contains("rotation")) {
		auto &rot = json["rotation"];
		transform.rotation = Ogre::Quaternion(rot.value("w", 1.0f),
						      rot.value("x", 0.0f),
						      rot.value("y", 0.0f),
						      rot.value("z", 0.0f));
	}

	// Read scale
	if (json.contains("scale")) {
		auto &scl = json["scale"];
		transform.scale = Ogre::Vector3(scl.value("x", 1.0f),
						scl.value("y", 1.0f),
						scl.value("z", 1.0f));
	}

	// Create scene node
	if (parentEntity.is_valid() && parentEntity != 0 &&
	    parentEntity.has<TransformComponent>()) {
		// Child of parent entity's node
		auto &parentTransform = parentEntity.get<TransformComponent>();
		if (parentTransform.node) {
			transform.node =
				parentTransform.node->createChildSceneNode();
		} else {
			transform.node = m_sceneMgr->getRootSceneNode()
						 ->createChildSceneNode();
		}
	} else {
		// Root level
		transform.node =
			m_sceneMgr->getRootSceneNode()->createChildSceneNode();
	}

	transform.applyToNode();
	entity.set<TransformComponent>(transform);
}

void SceneSerializer::deserializeRenderable(flecs::entity entity,
					    const nlohmann::json &json)
{
	RenderableComponent renderable;
	renderable.meshName = json.value("meshName", "");
	renderable.visible = json.value("visible", true);
	renderable.entity = nullptr;

	// Load the mesh immediately if mesh name is provided
	if (!renderable.meshName.empty() && entity.has<TransformComponent>()) {
		auto &transform = entity.get_mut<TransformComponent>();
		if (transform.node) {
			try {
				renderable.entity = m_sceneMgr->createEntity(
					renderable.meshName);
				transform.node->attachObject(renderable.entity);
				renderable.entity->setVisible(
					renderable.visible);
			} catch (const Ogre::Exception &e) {
				// Mesh not found - entity will be created but without mesh
				// User can load mesh manually later
				Ogre::LogManager::getSingleton().logMessage(
					"Warning: Failed to load mesh '" +
					renderable.meshName +
					"' for entity: " + e.getDescription());
			}
		}
	}

	entity.set<RenderableComponent>(renderable);
}

void SceneSerializer::deserializeEntityName(flecs::entity entity,
					    const nlohmann::json &json)
{
	std::string name = json.value("name", "Entity");
	entity.set<EntityNameComponent>(EntityNameComponent(name));
}

void SceneSerializer::deserializeRigidBody(flecs::entity entity,
					   const nlohmann::json &json)
{
	RigidBodyComponent rb;

	// Deserialize body type
	std::string bodyType = json.value("bodyType", "static");
	if (bodyType == "static") {
		rb.bodyType = RigidBodyComponent::BodyType::Static;
	} else if (bodyType == "dynamic") {
		rb.bodyType = RigidBodyComponent::BodyType::Dynamic;
	} else if (bodyType == "kinematic") {
		rb.bodyType = RigidBodyComponent::BodyType::Kinematic;
	}

	rb.mass = json.value("mass", 1.0f);
	rb.friction = json.value("friction", 0.5f);
	rb.restitution = json.value("restitution", 0.0f);
	rb.isSensor = json.value("isSensor", false);
	rb.enabled = json.value("enabled", true);

	// Mark as dirty so physics system will create the body
	rb.bodyDirty = true;
	rb.bodyCreated = false;

	entity.set<RigidBodyComponent>(rb);
}

void SceneSerializer::deserializeCollider(flecs::entity entity,
					  const nlohmann::json &json)
{
	PhysicsColliderComponent collider;

	// Deserialize shape type
	std::string shapeType = json.value("shapeType", "box");
	if (shapeType == "box") {
		collider.shapeType = PhysicsColliderComponent::ShapeType::Box;
	} else if (shapeType == "sphere") {
		collider.shapeType =
			PhysicsColliderComponent::ShapeType::Sphere;
	} else if (shapeType == "capsule") {
		collider.shapeType =
			PhysicsColliderComponent::ShapeType::Capsule;
	} else if (shapeType == "cylinder") {
		collider.shapeType =
			PhysicsColliderComponent::ShapeType::Cylinder;
	} else if (shapeType == "mesh") {
		collider.shapeType = PhysicsColliderComponent::ShapeType::Mesh;
	} else if (shapeType == "convexHull") {
		collider.shapeType =
			PhysicsColliderComponent::ShapeType::ConvexHull;
	}

	// Deserialize shape parameters
	if (json.contains("parameters")) {
		auto &params = json["parameters"];
		collider.parameters = Ogre::Vector3(params.value("x", 0.5f),
						    params.value("y", 0.5f),
						    params.value("z", 0.5f));
	}

	collider.radius = json.value("radius", 0.5f);
	collider.halfHeight = json.value("halfHeight", 1.0f);
	collider.meshName = json.value("meshName", "");

	// Deserialize offset
	if (json.contains("offset")) {
		auto &off = json["offset"];
		collider.offset = Ogre::Vector3(off.value("x", 0.0f),
						off.value("y", 0.0f),
						off.value("z", 0.0f));
	}

	// Deserialize rotation offset
	if (json.contains("rotationOffset")) {
		auto &rot = json["rotationOffset"];
		collider.rotationOffset = Ogre::Quaternion(
			rot.value("w", 1.0f), rot.value("x", 0.0f),
			rot.value("y", 0.0f), rot.value("z", 0.0f));
	}

	// Mark as dirty so physics system will create the shape
	collider.shapeDirty = true;

	entity.set<PhysicsColliderComponent>(collider);
}

void SceneSerializer::deserializeLight(flecs::entity entity,
				       const nlohmann::json &json)
{
	LightComponent light;

	// Deserialize light type
	std::string lightType = json.value("lightType", "point");
	if (lightType == "point") {
		light.lightType = LightComponent::LightType::Point;
	} else if (lightType == "directional") {
		light.lightType = LightComponent::LightType::Directional;
	} else if (lightType == "spotlight") {
		light.lightType = LightComponent::LightType::Spotlight;
	}

	// Deserialize colors
	if (json.contains("diffuseColor")) {
		auto &col = json["diffuseColor"];
		light.diffuseColor = Ogre::ColourValue(col.value("r", 0.8f),
						       col.value("g", 0.8f),
						       col.value("b", 0.8f),
						       col.value("a", 1.0f));
	}

	if (json.contains("specularColor")) {
		auto &col = json["specularColor"];
		light.specularColor = Ogre::ColourValue(col.value("r", 0.5f),
							col.value("g", 0.5f),
							col.value("b", 0.5f),
							col.value("a", 1.0f));
	}

	light.intensity = json.value("intensity", 1.0f);

	// Attenuation
	light.range = json.value("range", 100.0f);
	light.constantAttenuation = json.value("constantAttenuation", 1.0f);
	light.linearAttenuation = json.value("linearAttenuation", 0.0f);
	light.quadraticAttenuation = json.value("quadraticAttenuation", 0.0f);

	// Spotlight settings
	light.spotlightInnerAngle = json.value("spotlightInnerAngle", 30.0f);
	light.spotlightOuterAngle = json.value("spotlightOuterAngle", 45.0f);
	light.spotlightFalloff = json.value("spotlightFalloff", 1.0f);

	// Direction
	if (json.contains("direction")) {
		auto &dir = json["direction"];
		light.direction = Ogre::Vector3(dir.value("x", 0.0f),
						dir.value("y", -1.0f),
						dir.value("z", 0.0f));
	}

	light.castShadows = json.value("castShadows", true);

	// Mark as dirty so light system will create the light
	light.needsRebuild = true;

	entity.set<LightComponent>(light);
}

void SceneSerializer::deserializeCamera(flecs::entity entity,
					const nlohmann::json &json)
{
	CameraComponent camera;

	camera.fovY = json.value("fovY", 45.0f);
	camera.nearClip = json.value("nearClip", 0.1f);
	camera.farClip = json.value("farClip", 1000.0f);
	camera.aspectRatio = json.value("aspectRatio", 16.0f / 9.0f);
	camera.orthographic = json.value("orthographic", false);
	camera.orthoWidth = json.value("orthoWidth", 10.0f);
	camera.orthoHeight = json.value("orthoHeight", 10.0f);

	// Mark as dirty so camera system will create the camera
	camera.needsRebuild = true;

	entity.set<CameraComponent>(camera);
}

nlohmann::json SceneSerializer::serializeLodSettings(flecs::entity entity)
{
	auto &settings = entity.get<LodSettingsComponent>();
	nlohmann::json json;

	// Serialize settings ID
	json["settingsId"] = settings.settingsId;

	// Serialize strategy
	switch (settings.strategy) {
	case LodSettingsComponent::Strategy::Distance:
		json["strategy"] = "distance";
		break;
	case LodSettingsComponent::Strategy::PixelCount:
		json["strategy"] = "pixelCount";
		break;
	case LodSettingsComponent::Strategy::EdgePixelCount:
		json["strategy"] = "edgePixelCount";
		break;
	}

	// Serialize LOD levels
	json["lodLevels"] = nlohmann::json::array();
	for (const auto &level : settings.lodLevels) {
		nlohmann::json levelJson;
		levelJson["distance"] = level.distance;

		switch (level.reductionMethod) {
		case LodLevelConfig::ReductionMethod::Proportional:
			levelJson["reductionMethod"] = "proportional";
			break;
		case LodLevelConfig::ReductionMethod::Constant:
			levelJson["reductionMethod"] = "constant";
			break;
		case LodLevelConfig::ReductionMethod::CollapseCost:
			levelJson["reductionMethod"] = "collapseCost";
			break;
		}

		levelJson["reductionValue"] = level.reductionValue;
		levelJson["useManualMesh"] = level.useManualMesh;
		levelJson["manualMeshName"] = level.manualMeshName;

		json["lodLevels"].push_back(levelJson);
	}

	// Serialize advanced options
	json["useCompression"] = settings.useCompression;
	json["useVertexNormals"] = settings.useVertexNormals;
	json["preventPunchingHoles"] = settings.preventPunchingHoles;
	json["preventBreakingLines"] = settings.preventBreakingLines;
	json["useBackgroundQueue"] = settings.useBackgroundQueue;
	json["outsideWeight"] = settings.outsideWeight;
	json["outsideWalkAngle"] = settings.outsideWalkAngle;

	return json;
}

nlohmann::json SceneSerializer::serializeLod(flecs::entity entity)
{
	auto &lod = entity.get<LodComponent>();
	nlohmann::json json;

	// Serialize settings ID (persistent across scene loads)
	json["settingsId"] = lod.settingsId;

	json["distanceMultiplier"] = lod.distanceMultiplier;

	return json;
}

void SceneSerializer::deserializeLodSettings(flecs::entity entity,
					     const nlohmann::json &json)
{
	LodSettingsComponent settings;

	// Deserialize settings ID
	settings.settingsId = json.value("settingsId", "");
	Ogre::LogManager::getSingleton().logMessage(
		"Deserialize LodSettings: loaded settingsId=" +
		settings.settingsId);

	// Auto-generate ID if empty
	if (settings.settingsId.empty()) {
		settings.settingsId =
			"lod_settings_" + std::to_string(entity.id());
		Ogre::LogManager::getSingleton().logMessage(
			"Deserialize LodSettings: auto-generated settingsId=" +
			settings.settingsId);
	}

	// Deserialize strategy
	std::string strategy = json.value("strategy", "distance");
	if (strategy == "distance") {
		settings.strategy = LodSettingsComponent::Strategy::Distance;
	} else if (strategy == "pixelCount") {
		settings.strategy = LodSettingsComponent::Strategy::PixelCount;
	} else if (strategy == "edgePixelCount") {
		settings.strategy =
			LodSettingsComponent::Strategy::EdgePixelCount;
	}

	// Deserialize LOD levels
	settings.lodLevels.clear();
	if (json.contains("lodLevels") && json["lodLevels"].is_array()) {
		for (const auto &levelJson : json["lodLevels"]) {
			LodLevelConfig level;
			level.distance = levelJson.value("distance", 100.0f);

			std::string method = levelJson.value("reductionMethod",
							     "proportional");
			if (method == "proportional") {
				level.reductionMethod = LodLevelConfig::
					ReductionMethod::Proportional;
			} else if (method == "constant") {
				level.reductionMethod =
					LodLevelConfig::ReductionMethod::Constant;
			} else if (method == "collapseCost") {
				level.reductionMethod = LodLevelConfig::
					ReductionMethod::CollapseCost;
			}

			level.reductionValue =
				levelJson.value("reductionValue", 0.5f);
			level.useManualMesh =
				levelJson.value("useManualMesh", false);
			level.manualMeshName =
				levelJson.value("manualMeshName", "");

			settings.lodLevels.push_back(level);
		}
	}

	// Deserialize advanced options
	settings.useCompression = json.value("useCompression", true);
	settings.useVertexNormals = json.value("useVertexNormals", true);
	settings.preventPunchingHoles =
		json.value("preventPunchingHoles", false);
	settings.preventBreakingLines =
		json.value("preventBreakingLines", false);
	settings.useBackgroundQueue = json.value("useBackgroundQueue", false);
	settings.outsideWeight = json.value("outsideWeight", 0.0f);
	settings.outsideWalkAngle = json.value("outsideWalkAngle", 0.0f);

	// Mark as dirty so LOD system will apply settings
	settings.markDirty();

	entity.set<LodSettingsComponent>(settings);
}

void SceneSerializer::deserializeLod(flecs::entity entity,
				     const nlohmann::json &json)
{
	LodComponent lod;

	// Deserialize settings ID
	lod.settingsId = json.value("settingsId", "");
	Ogre::LogManager::getSingleton().logMessage(
		"Deserialize LOD: settingsId=" + lod.settingsId);

	// Try to find the settings entity by ID
	if (!lod.settingsId.empty()) {
		m_world.query<LodSettingsComponent>().each([&](flecs::entity e,
							       LodSettingsComponent
								       &settings) {
			Ogre::LogManager::getSingleton().logMessage(
				"Deserialize LOD: checking settings entity " +
				Ogre::StringConverter::toString(e.id()) +
				" with settingsId=" + settings.settingsId);
			if (settings.settingsId == lod.settingsId) {
				Ogre::LogManager::getSingleton().logMessage(
					"Deserialize LOD: MATCH FOUND!");
				lod.settingsEntity = e;
			}
		});
	}

	lod.distanceMultiplier = json.value("distanceMultiplier", 1.0f);
	lod.lodApplied = false;
	lod.dirty = true;

	entity.set<LodComponent>(lod);

	Ogre::LogManager::getSingleton().logMessage(
		"Deserialize LOD: Final settingsEntity=" +
		Ogre::StringConverter::toString(lod.settingsEntity.id()) +
		", hasValid=" + (lod.hasValidSettings() ? "true" : "false"));
}

nlohmann::json SceneSerializer::serializeStaticGeometry(flecs::entity entity)
{
	auto &region = entity.get<StaticGeometryComponent>();
	nlohmann::json json;

	json["regionId"] = region.regionId;
	json["regionName"] = region.regionName;
	json["renderingDistance"] = region.renderingDistance;
	json["castShadows"] = region.castShadows;
	json["regionDimensions"] = region.regionDimensions;
	json["visibilityFlags"] = region.visibilityFlags;

	return json;
}

nlohmann::json
SceneSerializer::serializeStaticGeometryMember(flecs::entity entity)
{
	auto &member = entity.get<StaticGeometryMemberComponent>();
	nlohmann::json json;

	json["regionId"] = member.regionId;
	json["meshName"] = member.meshName;
	json["materialName"] = member.materialName;
	json["lodSettingsId"] = member.lodSettingsId;
	json["castShadows"] = member.castShadows;
	json["visible"] = member.visible;

	return json;
}

void SceneSerializer::deserializeStaticGeometry(flecs::entity entity,
						const nlohmann::json &json)
{
	StaticGeometryComponent region;

	region.regionId = json.value("regionId", "");
	if (region.regionId.empty()) {
		// Generate new ID if missing
		static std::random_device rd;
		static std::mt19937 gen(rd());
		static std::uniform_int_distribution<> dis(1000, 9999);
		region.regionId = "region_" + std::to_string(dis(gen));
	}

	region.regionName = json.value("regionName", "");
	region.renderingDistance = json.value("renderingDistance", 0.0f);
	region.castShadows = json.value("castShadows", true);
	region.regionDimensions = json.value("regionDimensions", 1000.0f);
	region.visibilityFlags = json.value("visibilityFlags", 0xFFFFFFFF);
	region.dirty = true;

	entity.set<StaticGeometryComponent>(region);
}

void SceneSerializer::deserializeStaticGeometryMember(
	flecs::entity entity, const nlohmann::json &json)
{
	StaticGeometryMemberComponent member;

	member.regionId = json.value("regionId", "");
	member.meshName = json.value("meshName", "");
	member.materialName = json.value("materialName", "");
	member.lodSettingsId = json.value("lodSettingsId", "");
	member.castShadows = json.value("castShadows", true);
	member.visible = json.value("visible", true);
	member.dirty = true;

	entity.set<StaticGeometryMemberComponent>(member);
}

nlohmann::json SceneSerializer::serializeProceduralTexture(flecs::entity entity)
{
	auto &texture = entity.get<ProceduralTextureComponent>();
	nlohmann::json json;

	json["textureId"] = texture.textureId;
	json["textureName"] = texture.textureName;
	json["textureSize"] = texture.textureSize;
	json["uvMargin"] = texture.uvMargin;

	// Serialize colors array
	nlohmann::json colorsJson = nlohmann::json::array();
	for (int i = 0; i < ProceduralTextureComponent::RECT_COUNT; ++i) {
		nlohmann::json color;
		color["r"] = texture.colors[i * 4 + 0];
		color["g"] = texture.colors[i * 4 + 1];
		color["b"] = texture.colors[i * 4 + 2];
		color["a"] = texture.colors[i * 4 + 3];
		colorsJson.push_back(color);
	}
	json["colors"] = colorsJson;

	// Serialize named rectangles
	nlohmann::json namedRectsJson = nlohmann::json::array();
	for (const auto &pair : texture.namedRects) {
		const auto &rect = pair.second;
		nlohmann::json rectJson;
		rectJson["name"] = rect.name;
		rectJson["u1"] = rect.u1;
		rectJson["v1"] = rect.v1;
		rectJson["u2"] = rect.u2;
		rectJson["v2"] = rect.v2;
		namedRectsJson.push_back(rectJson);
	}
	json["namedRects"] = namedRectsJson;

	return json;
}

void SceneSerializer::deserializeProceduralTexture(flecs::entity entity,
						   const nlohmann::json &json)
{
	ProceduralTextureComponent texture;

	texture.textureId = json.value("textureId", "");
	texture.textureName = json.value("textureName", "");
	texture.textureSize = json.value("textureSize", 512);
	texture.uvMargin = json.value("uvMargin", 0.01f);

	// Auto-generate texture ID if empty
	if (texture.textureId.empty()) {
		texture.textureId = generatePersistentId();
	}

	// Deserialize colors array
	if (json.contains("colors") && json["colors"].is_array()) {
		int i = 0;
		for (const auto &colorJson : json["colors"]) {
			if (i >= ProceduralTextureComponent::RECT_COUNT)
				break;
			texture.colors[i * 4 + 0] = colorJson.value("r", 1.0f);
			texture.colors[i * 4 + 1] = colorJson.value("g", 1.0f);
			texture.colors[i * 4 + 2] = colorJson.value("b", 1.0f);
			texture.colors[i * 4 + 3] = colorJson.value("a", 1.0f);
			++i;
		}
	}

	// Deserialize named rectangles
	if (json.contains("namedRects") && json["namedRects"].is_array()) {
		for (const auto &rectJson : json["namedRects"]) {
			std::string name = rectJson.value("name", "");
			if (!name.empty()) {
				TextureRectInfo rect;
				rect.name = name;
				rect.u1 = rectJson.value("u1", 0.0f);
				rect.v1 = rectJson.value("v1", 0.0f);
				rect.u2 = rectJson.value("u2", 1.0f);
				rect.v2 = rectJson.value("v2", 1.0f);
				texture.namedRects[name] = rect;
			}
		}
	}

	texture.dirty = true;

	entity.set<ProceduralTextureComponent>(texture);
}

nlohmann::json
SceneSerializer::serializeProceduralMaterial(flecs::entity entity)
{
	auto &material = entity.get_mut<ProceduralMaterialComponent>();
	nlohmann::json json;

	json["materialId"] = material.materialId;
	json["materialName"] = material.materialName;

	// Ensure diffuseTextureId is populated from texture entity if empty
	std::string textureIdToSave = material.diffuseTextureId;
	if (textureIdToSave.empty() &&
	    material.diffuseTextureEntity.is_alive() &&
	    material.diffuseTextureEntity.has<ProceduralTextureComponent>()) {
		const auto &texture =
			material.diffuseTextureEntity
				.get<ProceduralTextureComponent>();
		textureIdToSave = texture.textureId;
		// Update the component for future saves
		material.diffuseTextureId = textureIdToSave;
	}

	json["diffuseTextureId"] = textureIdToSave;

	json["ambient"] = { { "r", material.ambient[0] },
			    { "g", material.ambient[1] },
			    { "b", material.ambient[2] } };
	json["diffuse"] = { { "r", material.diffuse[0] },
			    { "g", material.diffuse[1] },
			    { "b", material.diffuse[2] } };
	json["specular"] = { { "r", material.specular[0] },
			     { "g", material.specular[1] },
			     { "b", material.specular[2] } };
	json["shininess"] = material.shininess;
	json["roughness"] = material.roughness;

	return json;
}

void SceneSerializer::deserializeProceduralMaterial(flecs::entity entity,
						    const nlohmann::json &json)
{
	ProceduralMaterialComponent material;

	material.materialId = json.value("materialId", "");
	material.materialName = json.value("materialName", "");
	material.diffuseTextureId = json.value("diffuseTextureId", "");

	// Auto-generate material ID if empty
	if (material.materialId.empty()) {
		material.materialId = generatePersistentId();
	}

	// Texture reference is resolved in a second pass after all entities
	// are loaded, so the referenced ProceduralTexture entity exists even if
	// it appears later in the JSON file.
	if (!material.diffuseTextureId.empty()) {
		m_pendingProceduralTextureResolutions.push_back(
			{ entity, material.diffuseTextureId });
	}

	if (json.contains("ambient")) {
		material.ambient[0] = json["ambient"].value("r", 0.2f);
		material.ambient[1] = json["ambient"].value("g", 0.2f);
		material.ambient[2] = json["ambient"].value("b", 0.2f);
	}

	if (json.contains("diffuse")) {
		material.diffuse[0] = json["diffuse"].value("r", 1.0f);
		material.diffuse[1] = json["diffuse"].value("g", 1.0f);
		material.diffuse[2] = json["diffuse"].value("b", 1.0f);
	}

	if (json.contains("specular")) {
		material.specular[0] = json["specular"].value("r", 0.0f);
		material.specular[1] = json["specular"].value("g", 0.0f);
		material.specular[2] = json["specular"].value("b", 0.0f);
	}

	material.shininess = json.value("shininess", 32.0f);
	material.roughness = json.value("roughness", 0.5f);

	material.dirty = true;

	entity.set<ProceduralMaterialComponent>(material);
}

nlohmann::json SceneSerializer::serializePrimitive(flecs::entity entity)
{
	auto &prim = entity.get<PrimitiveComponent>();
	nlohmann::json json;

	json["type"] = static_cast<int>(prim.type);

	// Box parameters
	json["boxSizeX"] = prim.boxSizeX;
	json["boxSizeY"] = prim.boxSizeY;
	json["boxSizeZ"] = prim.boxSizeZ;

	// Plane parameters
	json["planeSizeX"] = prim.planeSizeX;
	json["planeSizeY"] = prim.planeSizeY;
	json["planeSegmentsX"] = prim.planeSegmentsX;
	json["planeSegmentsY"] = prim.planeSegmentsY;

	return json;
}

void SceneSerializer::deserializePrimitive(flecs::entity entity,
					   const nlohmann::json &json)
{
	PrimitiveComponent prim;

	prim.type =
		static_cast<PrimitiveComponent::Type>(json.value("type", 0));

	prim.boxSizeX = json.value("boxSizeX", 1.0f);
	prim.boxSizeY = json.value("boxSizeY", 1.0f);
	prim.boxSizeZ = json.value("boxSizeZ", 1.0f);

	prim.planeSizeX = json.value("planeSizeX", 1.0f);
	prim.planeSizeY = json.value("planeSizeY", 1.0f);
	prim.planeSegmentsX = json.value("planeSegmentsX", 1);
	prim.planeSegmentsY = json.value("planeSegmentsY", 1);

	prim.dirty = true;

	entity.set<PrimitiveComponent>(prim);
}

nlohmann::json SceneSerializer::serializeTriangleBuffer(flecs::entity entity)
{
	auto &tb = entity.get<TriangleBufferComponent>();
	nlohmann::json json;

	// Only serialize configuration, not the buffer contents
	json["meshName"] = tb.meshName;
	json["textureEntity"] = tb.textureEntity.id();
	json["textureRectName"] = tb.textureRectName;
	json["materialEntity"] = tb.materialEntity.id();
	json["useStaticGeometry"] = tb.useStaticGeometry;
	json["staticGeometryEntity"] = tb.staticGeometryEntity.id();

	return json;
}

void SceneSerializer::deserializeTriangleBuffer(flecs::entity entity,
						const nlohmann::json &json)
{
	TriangleBufferComponent tb;

	tb.meshName = json.value("meshName", "");
	tb.textureRectName = json.value("textureRectName", "");
	tb.useStaticGeometry = json.value("useStaticGeometry", false);

	// Resolve entity references using the entity map
	if (json.contains("textureEntity")) {
		uint64_t id = json["textureEntity"];
		if (id != 0) {
			// Look up in entity map to find the corresponding new entity
			auto it = m_entityMap.find(id);
			if (it != m_entityMap.end()) {
				tb.textureEntity = it->second;
			} else {
				// Entity not found in map (might not exist or not loaded yet)
				// Create a reference that will be invalid but can be resolved later
				tb.textureEntity = m_world.entity(id);
			}
		}
	}

	if (json.contains("materialEntity")) {
		uint64_t id = json["materialEntity"];
		if (id != 0) {
			// Look up in entity map to find the corresponding new entity
			auto it = m_entityMap.find(id);
			if (it != m_entityMap.end()) {
				tb.materialEntity = it->second;
			} else {
				// Entity not found in map (might not exist or not loaded yet)
				// Create a reference that will be invalid but can be resolved later
				tb.materialEntity = m_world.entity(id);
			}
		}
	}

	if (json.contains("staticGeometryEntity")) {
		uint64_t id = json["staticGeometryEntity"];
		if (id != 0) {
			// Look up in entity map to find the corresponding new entity
			auto it = m_entityMap.find(id);
			if (it != m_entityMap.end()) {
				tb.staticGeometryEntity = it->second;
			} else {
				// Entity not found in map (might not exist or not loaded yet)
				// Create a reference that will be invalid but can be resolved later
				tb.staticGeometryEntity = m_world.entity(id);
			}
		}
	}

	tb.dirty = true;

	entity.set<TriangleBufferComponent>(tb);
}

nlohmann::json SceneSerializer::serializeCharacter(flecs::entity entity)
{
	auto &cc = entity.get<CharacterComponent>();
	nlohmann::json json;

	json["enabled"] = cc.enabled;
	json["radius"] = cc.radius;
	json["height"] = cc.height;
	json["offset"] = { cc.offset.x, cc.offset.y, cc.offset.z };
	json["linearVelocity"] = { cc.linearVelocity.x, cc.linearVelocity.y,
				   cc.linearVelocity.z };
	json["headRadius"] = cc.headRadius;
	json["headOffsetY"] = cc.headOffsetY;
	json["headBoneName"] = cc.headBoneName;
	json["chestHalfExtents"] = {
		cc.chestHalfExtents.x, cc.chestHalfExtents.y,
		cc.chestHalfExtents.z
	};
	json["chestOffsetY"] = cc.chestOffsetY;
	json["chestBoneName"] = cc.chestBoneName;

	return json;
}

void SceneSerializer::deserializeCharacter(flecs::entity entity,
					   const nlohmann::json &json)
{
	CharacterComponent cc;
	cc.enabled = json.value("enabled", true);
	cc.radius = json.value("radius", 0.3f);
	cc.height = json.value("height", 1.8f);
	if (json.contains("offset") && json["offset"].is_array() &&
	    json["offset"].size() >= 3) {
		cc.offset = Ogre::Vector3(json["offset"][0].get<float>(),
					  json["offset"][1].get<float>(),
					  json["offset"][2].get<float>());
	}
	if (json.contains("linearVelocity") &&
	    json["linearVelocity"].is_array() &&
	    json["linearVelocity"].size() >= 3) {
		cc.linearVelocity =
			Ogre::Vector3(json["linearVelocity"][0].get<float>(),
				      json["linearVelocity"][1].get<float>(),
				      json["linearVelocity"][2].get<float>());
	}
	cc.headRadius = json.value("headRadius", 0.13f);
	cc.headOffsetY = json.value("headOffsetY", 0.0f);
	cc.headBoneName = json.value("headBoneName", Ogre::String("mixamorig:Head"));
	if (json.contains("chestHalfExtents") &&
	    json["chestHalfExtents"].is_array() &&
	    json["chestHalfExtents"].size() >= 3) {
		cc.chestHalfExtents = Ogre::Vector3(
			json["chestHalfExtents"][0].get<float>(),
			json["chestHalfExtents"][1].get<float>(),
			json["chestHalfExtents"][2].get<float>());
	} else if (json.contains("chestRadius")) {
		float r = json.value("chestRadius", 0.25f);
		cc.chestHalfExtents = Ogre::Vector3(r, r, r);
	}
	cc.chestOffsetY = json.value("chestOffsetY", 0.0f);
	cc.chestBoneName = json.value("chestBoneName", Ogre::String("mixamorig:Spine2"));
	cc.dirty = true;
	entity.set<CharacterComponent>(cc);
}

nlohmann::json SceneSerializer::serializeCharacterIdentity(flecs::entity entity)
{
	auto &ci = entity.get<CharacterIdentityComponent>();
	nlohmann::json json;
	json["registryId"] = ci.registryId;
	return json;
}

void SceneSerializer::deserializeCharacterIdentity(flecs::entity entity,
						   const nlohmann::json &json)
{
	CharacterIdentityComponent ci;
	ci.registryId = json.value("registryId", 0);
	entity.set<CharacterIdentityComponent>(ci);
}

nlohmann::json SceneSerializer::serializeCharacterSpawner(flecs::entity entity)
{
	auto &spawner = entity.get<CharacterSpawnerComponent>();
	nlohmann::json json;
	json["registryId"] = spawner.registryId;
	json["spawnDistance"] = std::sqrt(spawner.spawnDistanceSq);
	json["despawnDistance"] = std::sqrt(spawner.despawnDistanceSq);
	return json;
}

void SceneSerializer::deserializeCharacterSpawner(flecs::entity entity,
							  const nlohmann::json &json)
{
	CharacterSpawnerComponent spawner;
	spawner.registryId = json.value("registryId", 0);
	float spawnDist = json.value("spawnDistance", 100.0f);
	float despawnDist = json.value("despawnDistance", 200.0f);
	spawner.spawnDistanceSq = spawnDist * spawnDist;
	spawner.despawnDistanceSq = despawnDist * despawnDist;
	entity.set<CharacterSpawnerComponent>(spawner);
}

/* Forward declarations for AnimationTreeNode serialization */
static nlohmann::json serializeAnimationTreeNode(const AnimationTreeNode &node);
static void deserializeAnimationTreeNode(AnimationTreeNode &node,
						 const nlohmann::json &json);

nlohmann::json SceneSerializer::serializeCharacterSlots(flecs::entity entity)
{
	/* CharacterSlotsComponent is deprecated and no longer saved. */
	(void)entity;
	return nlohmann::json::object();
}

void SceneSerializer::deserializeCharacterSlots(flecs::entity entity,
						const nlohmann::json &json)
{
	/* Migrate legacy CharacterSlots data into the character registry.
	 * The empty component is added as a placeholder reminder. */
	uint64_t registryId = 0;
	if (entity.has<CharacterIdentityComponent>())
		registryId = entity.get<CharacterIdentityComponent>().registryId;

	if (registryId == 0) {
		registryId = CharacterRegistry::getSingleton().createCharacter(
			"Migrated", "Character", "", true);
		entity.set<CharacterIdentityComponent>(
			CharacterIdentityComponent{ registryId });
	}

	CharacterRegistry::CharacterRecord *rec =
		CharacterRegistry::getSingleton().findCharacter(registryId);
	if (rec) {
		rec->inlineSex = json.value("sex", rec->inlineSex);
		if (json.contains("slots") && json["slots"].is_object()) {
			for (auto &[slot, mesh] : json["slots"].items()) {
				SlotSelection sel;
				sel.explicitMesh = mesh.get<std::string>();
				rec->inlineSlotSelections[slot] = sel;
			}
		}
		if (json.contains("slotSelections") &&
		    json["slotSelections"].is_object()) {
			for (auto &[slot, selJson] :
			     json["slotSelections"].items()) {
				SlotSelection sel;
				if (selJson.contains("layer0Mesh"))
					sel.layer0Mesh =
						selJson.value("layer0Mesh", "");
				if (selJson.contains("layer1Mesh"))
					sel.layer1Mesh =
						selJson.value("layer1Mesh", "");
				if (selJson.contains("layer2Mesh"))
					sel.layer2Mesh =
						selJson.value("layer2Mesh", "");
				if (selJson.contains("layer") &&
				    sel.layer1Mesh.empty() &&
				    sel.layer2Mesh.empty()) {
					int oldLayer = selJson.value("layer", 2);
					if (oldLayer == 1)
						sel.layer1Mesh = "auto";
					else if (oldLayer >= 2)
						sel.layer2Mesh = "auto";
				}
				sel.explicitMesh =
					selJson.value("explicitMesh", "");
				rec->inlineSlotSelections[slot] = sel;
			}
		}
		if (json.contains("frontAxis") &&
		    json["frontAxis"].is_array() &&
		    json["frontAxis"].size() >= 3) {
			Ogre::Vector3 fa(
				json["frontAxis"][0].get<float>(),
				json["frontAxis"][1].get<float>(),
				json["frontAxis"][2].get<float>());
			if (fa.squaredLength() > 0.0001f)
				fa.normalise();
			rec->frontAxis = fa;
		}
		CharacterRegistry::getSingleton().autoSave();
	}

	entity.add<CharacterSlotsComponent>();
	CharacterRegistry::getSingleton().markCharacterDirty(registryId);
}

static nlohmann::json serializeAnimationTreeNode(const AnimationTreeNode &node)
{
	nlohmann::json json;
	json["type"] = node.type;
	if (!node.name.empty())
		json["name"] = node.name;
	if (!node.animationName.empty())
		json["animationName"] = node.animationName;
	if (node.speed != 1.0f)
		json["speed"] = node.speed;
	if (node.fadeSpeed != 7.5f)
		json["fadeSpeed"] = node.fadeSpeed;
	if (!node.endTransitions.empty()) {
		json["endTransitions"] = nlohmann::json::object();
		for (const auto &pair : node.endTransitions)
			json["endTransitions"][pair.first] = pair.second;
	}
	if (!node.children.empty()) {
		json["children"] = nlohmann::json::array();
		for (const auto &child : node.children)
			json["children"].push_back(
				serializeAnimationTreeNode(child));
	}
	return json;
}

static void deserializeAnimationTreeNode(AnimationTreeNode &node,
					 const nlohmann::json &json)
{
	node.type = json.value("type", "animation");
	node.name = json.value("name", "");
	node.animationName = json.value("animationName", "");
	node.speed = json.value("speed", 1.0f);
	node.fadeSpeed = json.value("fadeSpeed", 7.5f);
	node.endTransitions.clear();
	if (json.contains("endTransitions") &&
	    json["endTransitions"].is_object()) {
		for (auto &[key, val] : json["endTransitions"].items())
			node.endTransitions[key] = val.get<std::string>();
	}
	node.children.clear();
	if (json.contains("children") && json["children"].is_array()) {
		for (const auto &childJson : json["children"]) {
			AnimationTreeNode child;
			deserializeAnimationTreeNode(child, childJson);
			node.children.push_back(child);
		}
	}
}

nlohmann::json SceneSerializer::serializeAnimationTree(flecs::entity entity)
{
	auto &at = entity.get<AnimationTreeComponent>();
	nlohmann::json json;
	json["enabled"] = at.enabled;
	json["useRootMotion"] = at.useRootMotion;
	if (!at.templateName.empty())
		json["templateName"] = at.templateName;
	json["root"] = serializeAnimationTreeNode(at.root);
	return json;
}

void SceneSerializer::deserializeAnimationTree(flecs::entity entity,
					       const nlohmann::json &json)
{
	AnimationTreeComponent at;
	at.enabled = json.value("enabled", true);
	at.useRootMotion = json.value("useRootMotion", false);
	at.templateName = json.value("templateName", "");
	if (json.contains("root"))
		deserializeAnimationTreeNode(at.root, json["root"]);
	at.dirty = true;
	entity.set<AnimationTreeComponent>(at);
}

nlohmann::json
SceneSerializer::serializeAnimationTreeTemplate(flecs::entity entity)
{
	auto &templ = entity.get<AnimationTreeTemplate>();
	nlohmann::json json;
	json["name"] = templ.name;
	json["version"] = templ.version;
	return json;
}

void SceneSerializer::deserializeAnimationTreeTemplate(
	flecs::entity entity, const nlohmann::json &json)
{
	AnimationTreeTemplate templ;
	templ.name = json.value("name", "");
	templ.version = json.value("version", 1);
	entity.set<AnimationTreeTemplate>(templ);
}

// ============================================================================
// CellGrid/Town Component Serialization
// ============================================================================

nlohmann::json SceneSerializer::serializeCellGrid(flecs::entity entity)
{
	auto &grid = entity.get<CellGridComponent>();
	nlohmann::json json;

	json["width"] = grid.width;
	json["height"] = grid.height;
	json["depth"] = grid.depth;
	json["cellSize"] = grid.cellSize;
	json["cellHeight"] = grid.cellHeight;
	json["generationScript"] = grid.generationScript;

	// Serialize texture rectangles
	json["floorRectName"] = grid.floorRectName;
	json["ceilingRectName"] = grid.ceilingRectName;
	json["extWallRectName"] = grid.extWallRectName;
	json["intWallRectName"] = grid.intWallRectName;
	json["extDoorFrameRectName"] = grid.extDoorFrameRectName;
	json["intDoorFrameRectName"] = grid.intDoorFrameRectName;
	json["extWindowFrameRectName"] = grid.extWindowFrameRectName;
	json["intWindowFrameRectName"] = grid.intWindowFrameRectName;
	json["roofTopRectName"] = grid.roofTopRectName;
	json["roofSideRectName"] = grid.roofSideRectName;
	json["friction"] = grid.friction;

	// Serialize cells
	nlohmann::json cellsJson = nlohmann::json::array();
	for (const auto &cell : grid.cells) {
		cellsJson.push_back({ { "x", cell.x },
				      { "y", cell.y },
				      { "z", cell.z },
				      { "flags", cell.flags } });
	}
	json["cells"] = cellsJson;

	// Serialize furniture cells
	nlohmann::json furnitureJson = nlohmann::json::array();
	for (const auto &furn : grid.furnitureCells) {
		nlohmann::json furnJson;
		furnJson["x"] = furn.x;
		furnJson["y"] = furn.y;
		furnJson["z"] = furn.z;
		furnJson["furnitureType"] = furn.furnitureType;
		furnJson["rotation"] = furn.rotation;
		furnJson["tags"] = furn.tags;
		furnitureJson.push_back(furnJson);
	}
	json["furnitureCells"] = furnitureJson;

	return json;
}

nlohmann::json SceneSerializer::serializeTown(flecs::entity entity)
{
	auto &town = entity.get<TownComponent>();
	nlohmann::json json;

	json["townName"] = town.townName;
	json["materialName"] = town.materialName;
	json["proceduralMaterialEntityId"] = town.proceduralMaterialEntityId;
	json["textureRectName"] = town.textureRectName;

	// Serialize color rects
	nlohmann::json colorRectsJson = nlohmann::json::object();
	for (const auto &pair : town.colorRects) {
		colorRectsJson[pair.first] = {
			{ "left", pair.second.left },
			{ "top", pair.second.top },
			{ "right", pair.second.right },
			{ "bottom", pair.second.bottom },
			{ "color",
			  { { "r", pair.second.color.r },
			    { "g", pair.second.color.g },
			    { "b", pair.second.color.b },
			    { "a", pair.second.color.a } } }
		};
	}
	json["colorRects"] = colorRectsJson;

	return json;
}

nlohmann::json SceneSerializer::serializeDistrict(flecs::entity entity)
{
	auto &district = entity.get<DistrictComponent>();
	nlohmann::json json;

	json["radius"] = district.radius;
	json["elevation"] = district.elevation;
	json["height"] = district.height;
	json["isPlaza"] = district.isPlaza;
	json["lotTemplateNames"] = district.lotTemplateNames;
	json["proceduralMaterialEntityId"] =
		district.proceduralMaterialEntityId;
	json["textureRectName"] = district.textureRectName;
	json["friction"] = district.friction;

	return json;
}

nlohmann::json SceneSerializer::serializeLot(flecs::entity entity)
{
	auto &lot = entity.get<LotComponent>();
	nlohmann::json json;

	json["width"] = lot.width;
	json["depth"] = lot.depth;
	json["elevation"] = lot.elevation;
	json["angle"] = lot.angle;
	json["offsetX"] = lot.offsetX;
	json["offsetZ"] = lot.offsetZ;
	json["templateName"] = lot.templateName;
	json["proceduralMaterialEntityId"] = lot.proceduralMaterialEntityId;
	json["textureRectName"] = lot.textureRectName;
	json["friction"] = lot.friction;

	return json;
}

nlohmann::json SceneSerializer::serializeRoom(flecs::entity entity)
{
	auto &room = entity.get_mut<RoomComponent>();

	// Ensure room has a persistent ID
	room.ensurePersistentId(entity.id());

	nlohmann::json json;

	json["persistentId"] = room.persistentId;
	json["minX"] = room.minX;
	json["minY"] = room.minY;
	json["minZ"] = room.minZ;
	json["maxX"] = room.maxX;
	json["maxY"] = room.maxY;
	json["maxZ"] = room.maxZ;
	json["roomType"] = room.roomType;
	json["tags"] = room.tags;
	json["createFloor"] = room.createFloor;
	json["createCeiling"] = room.createCeiling;
	json["createInteriorWalls"] = room.createInteriorWalls;
	json["createWindows"] = room.createWindows;
	json["fillRoomWithFurniture"] = room.fillRoomWithFurniture;
	json["furnitureSeed"] = room.furnitureSeed;
	json["furnitureYOffset"] = room.furnitureYOffset;

	// Serialize exits (bool array for Z-, Z+, X-, X+)
	nlohmann::json exits = nlohmann::json::array();
	for (int i = 0; i < 4; i++) {
		exits.push_back(room.exits[i]);
	}
	json["exits"] = exits;

	// Serialize connected room persistent IDs (not entity IDs, as they change on save/load)
	nlohmann::json connections = nlohmann::json::array();
	for (const std::string &id : room.connectedRoomIds) {
		connections.push_back(id);
	}
	json["connectedRoomIds"] = connections;

	return json;
}

nlohmann::json SceneSerializer::serializeRoof(flecs::entity entity)
{
	auto &roof = entity.get<RoofComponent>();
	nlohmann::json json;

	json["type"] = static_cast<int>(roof.type);
	json["posX"] = roof.posX;
	json["posY"] = roof.posY;
	json["posZ"] = roof.posZ;
	json["sizeX"] = roof.sizeX;
	json["sizeZ"] = roof.sizeZ;
	json["offsetX"] = roof.offsetX;
	json["offsetY"] = roof.offsetY;
	json["offsetZ"] = roof.offsetZ;
	json["baseHeight"] = roof.baseHeight;
	json["maxHeight"] = roof.maxHeight;

	return json;
}

nlohmann::json SceneSerializer::serializeFurnitureTemplate(flecs::entity entity)
{
	auto &furn = entity.get<FurnitureTemplateComponent>();
	nlohmann::json json;

	json["templateName"] = furn.templateName;
	json["tags"] = furn.tags;
	json["meshName"] = furn.meshName;
	json["materialName"] = furn.materialName;
	json["requiresWall"] = furn.requiresWall;
	json["requiresFloor"] = furn.requiresFloor;
	json["blocksPath"] = furn.blocksPath;
	json["offsetX"] = furn.offsetX;
	json["offsetY"] = furn.offsetY;
	json["offsetZ"] = furn.offsetZ;
	json["weight"] = furn.weight;

	return json;
}

void SceneSerializer::deserializeCellGrid(flecs::entity entity,
					  const nlohmann::json &json)
{
	CellGridComponent grid;

	grid.width = json.value("width", 10);
	grid.height = json.value("height", 1);
	grid.depth = json.value("depth", 10);
	grid.cellSize = json.value("cellSize", 4.0f);
	grid.cellHeight = json.value("cellHeight", 4.0f);
	grid.generationScript = json.value("generationScript", "");

	// Deserialize texture rectangles
	grid.floorRectName = json.value("floorRectName", "");
	grid.ceilingRectName = json.value("ceilingRectName", "");
	grid.extWallRectName = json.value("extWallRectName", "");
	grid.intWallRectName = json.value("intWallRectName", "");
	grid.extDoorFrameRectName = json.value("extDoorFrameRectName", "");
	grid.intDoorFrameRectName = json.value("intDoorFrameRectName", "");
	grid.extWindowFrameRectName = json.value("extWindowFrameRectName", "");
	grid.intWindowFrameRectName = json.value("intWindowFrameRectName", "");
	grid.roofTopRectName = json.value("roofTopRectName", "");
	grid.roofSideRectName = json.value("roofSideRectName", "");
	grid.friction = json.value("friction", 0.5f);

	// Deserialize cells
	if (json.contains("cells") && json["cells"].is_array()) {
		for (const auto &cellJson : json["cells"]) {
			Cell cell;
			cell.x = cellJson.value("x", 0);
			cell.y = cellJson.value("y", 0);
			cell.z = cellJson.value("z", 0);
			cell.flags = cellJson.value("flags", 0ULL);
			grid.cells.push_back(cell);
		}
	}

	// Deserialize furniture cells
	if (json.contains("furnitureCells") &&
	    json["furnitureCells"].is_array()) {
		for (const auto &furnJson : json["furnitureCells"]) {
			FurnitureCell furn;
			furn.x = furnJson.value("x", 0);
			furn.y = furnJson.value("y", 0);
			furn.z = furnJson.value("z", 0);
			furn.furnitureType =
				furnJson.value("furnitureType", "");
			furn.rotation = furnJson.value("rotation", 0);
			if (furnJson.contains("tags") &&
			    furnJson["tags"].is_array()) {
				for (const auto &tag : furnJson["tags"]) {
					if (tag.is_string()) {
						furn.tags.push_back(tag);
					}
				}
			}
			grid.furnitureCells.push_back(furn);
		}
	}

	grid.dirty = true;
	entity.set<CellGridComponent>(grid);
}

void SceneSerializer::deserializeTown(flecs::entity entity,
				      const nlohmann::json &json)
{
	TownComponent town;

	town.townName = json.value("townName", "New Town");
	town.materialName = json.value("materialName", "");
	town.proceduralMaterialEntityId =
		json.value("proceduralMaterialEntityId", "");
	town.textureRectName = json.value("textureRectName", "");

	// Resolve material entity reference
	if (!town.proceduralMaterialEntityId.empty()) {
		try {
			uint64_t matId =
				std::stoull(town.proceduralMaterialEntityId);
			// Look up in entity map to find the corresponding new entity
			auto it = m_entityMap.find(matId);
			if (it != m_entityMap.end()) {
				flecs::entity matEntity = it->second;
				if (matEntity.is_alive() &&
				    matEntity.has<
					    ProceduralMaterialComponent>()) {
					town.proceduralMaterialEntity =
						matEntity;
					town.proceduralMaterialEntityId =
						std::to_string(matEntity.id());
				}
			} else {
				// Entity not found in map (might not exist or not loaded yet)
				// Create a reference that will be invalid but can be resolved later
				flecs::entity matEntity(m_world, matId);
				if (matEntity.is_alive() &&
				    matEntity.has<
					    ProceduralMaterialComponent>()) {
					town.proceduralMaterialEntity =
						matEntity;
					town.proceduralMaterialEntityId =
						std::to_string(matEntity.id());
				}
			}
		} catch (...) {
			// Invalid ID format, ignore
		}
	}

	// Deserialize color rects
	if (json.contains("colorRects") && json["colorRects"].is_object()) {
		for (auto &[name, rectJson] : json["colorRects"].items()) {
			TownComponent::ColorRect rect;
			rect.left = rectJson.value("left", 0.0f);
			rect.top = rectJson.value("top", 0.0f);
			rect.right = rectJson.value("right", 1.0f);
			rect.bottom = rectJson.value("bottom", 1.0f);
			if (rectJson.contains("color")) {
				auto &c = rectJson["color"];
				rect.color.r = c.value("r", 1.0f);
				rect.color.g = c.value("g", 1.0f);
				rect.color.b = c.value("b", 1.0f);
				rect.color.a = c.value("a", 1.0f);
			}
			town.colorRects[name] = rect;
		}
	}

	town.dirty = true;
	town.materialDirty = true;
	entity.set<TownComponent>(town);
}

void SceneSerializer::deserializeDistrict(flecs::entity entity,
					  const nlohmann::json &json)
{
	DistrictComponent district;

	district.radius = json.value("radius", 50.0f);
	district.elevation = json.value("elevation", 0.0f);
	district.height = json.value("height", 0.2f);
	district.isPlaza = json.value("isPlaza", false);
	district.proceduralMaterialEntityId =
		json.value("proceduralMaterialEntityId", "");
	district.textureRectName = json.value("textureRectName", "");
	district.friction = json.value("friction", 0.5f);

	// Resolve material entity reference
	if (!district.proceduralMaterialEntityId.empty()) {
		try {
			uint64_t matId = std::stoull(
				district.proceduralMaterialEntityId);
			// Look up in entity map to find the corresponding new entity
			auto it = m_entityMap.find(matId);
			if (it != m_entityMap.end()) {
				flecs::entity matEntity = it->second;
				if (matEntity.is_alive() &&
				    matEntity.has<
					    ProceduralMaterialComponent>()) {
					district.proceduralMaterialEntity =
						matEntity;
					district.proceduralMaterialEntityId =
						std::to_string(matEntity.id());
				}
			} else {
				// Entity not found in map (might not exist or not loaded yet)
				// Create a reference that will be invalid but can be resolved later
				flecs::entity matEntity(m_world, matId);
				if (matEntity.is_alive() &&
				    matEntity.has<
					    ProceduralMaterialComponent>()) {
					district.proceduralMaterialEntity =
						matEntity;
					district.proceduralMaterialEntityId =
						std::to_string(matEntity.id());
				}
			}
		} catch (...) {
			// Invalid ID format, ignore
		}
	}

	if (json.contains("lotTemplateNames") &&
	    json["lotTemplateNames"].is_array()) {
		for (const auto &name : json["lotTemplateNames"]) {
			if (name.is_string()) {
				district.lotTemplateNames.push_back(name);
			}
		}
	}

	district.dirty = true;
	entity.set<DistrictComponent>(district);
}

void SceneSerializer::deserializeLot(flecs::entity entity,
				     const nlohmann::json &json)
{
	LotComponent lot;

	lot.width = json.value("width", 10);
	lot.depth = json.value("depth", 10);
	lot.elevation = json.value("elevation", 0.0f);
	lot.angle = json.value("angle", 0.0f);
	lot.offsetX = json.value("offsetX", 0.0f);
	lot.offsetZ = json.value("offsetZ", 0.0f);
	lot.templateName = json.value("templateName", "");
	lot.proceduralMaterialEntityId =
		json.value("proceduralMaterialEntityId", "");
	lot.textureRectName = json.value("textureRectName", "");
	lot.friction = json.value("friction", 0.5f);

	// Resolve material entity reference
	if (!lot.proceduralMaterialEntityId.empty()) {
		try {
			uint64_t matId =
				std::stoull(lot.proceduralMaterialEntityId);
			// Look up in entity map to find the corresponding new entity
			auto it = m_entityMap.find(matId);
			if (it != m_entityMap.end()) {
				flecs::entity matEntity = it->second;
				if (matEntity.is_alive() &&
				    matEntity.has<
					    ProceduralMaterialComponent>()) {
					lot.proceduralMaterialEntity =
						matEntity;
					lot.proceduralMaterialEntityId =
						std::to_string(matEntity.id());
				}
			} else {
				// Entity not found in map (might not exist or not loaded yet)
				// Create a reference that will be invalid but can be resolved later
				flecs::entity matEntity(m_world, matId);
				if (matEntity.is_alive() &&
				    matEntity.has<
					    ProceduralMaterialComponent>()) {
					lot.proceduralMaterialEntity =
						matEntity;
					lot.proceduralMaterialEntityId =
						std::to_string(matEntity.id());
				}
			}
		} catch (...) {
			// Invalid ID format, ignore
		}
	}

	lot.dirty = true;
	entity.set<LotComponent>(lot);
}

void SceneSerializer::deserializeRoom(flecs::entity entity,
				      const nlohmann::json &json)
{
	RoomComponent room;

	// Load persistent ID (auto-generate if not present for backward compatibility)
	room.persistentId = json.value("persistentId", "");
	if (room.persistentId.empty()) {
		room.ensurePersistentId(entity.id());
	}

	room.minX = json.value("minX", 0);
	room.minY = json.value("minY", 0);
	room.minZ = json.value("minZ", 0);
	room.maxX = json.value("maxX", 0);
	room.maxY = json.value("maxY", 0);
	room.maxZ = json.value("maxZ", 0);
	room.roomType = json.value("roomType", "");
	room.createFloor = json.value("createFloor", true);
	room.createCeiling = json.value("createCeiling", true);
	room.createInteriorWalls = json.value("createInteriorWalls", true);
	room.createWindows = json.value("createWindows", false);
	room.fillRoomWithFurniture = json.value("fillRoomWithFurniture", false);
	room.furnitureSeed = json.value("furnitureSeed", 42u);
	room.furnitureYOffset = json.value("furnitureYOffset", 0.05f);

	// Load exits (bool array for Z-, Z+, X-, X+)
	if (json.contains("exits") && json["exits"].is_array()) {
		int i = 0;
		for (const auto &exitVal : json["exits"]) {
			if (i >= 4)
				break;
			if (exitVal.is_boolean()) {
				room.exits[i] = exitVal.get<bool>();
			}
			i++;
		}
	}

	if (json.contains("tags") && json["tags"].is_array()) {
		for (const auto &tag : json["tags"]) {
			if (tag.is_string()) {
				room.tags.push_back(tag);
			}
		}
	}

	// Load connected rooms using persistent IDs
	if (json.contains("connectedRoomIds") &&
	    json["connectedRoomIds"].is_array()) {
		// New format: persistent IDs
		for (const auto &id : json["connectedRoomIds"]) {
			if (id.is_string()) {
				room.connectedRoomIds.push_back(
					id.get<std::string>());
			}
		}
	}
	// Backward compatibility: old format used entity IDs which are invalid after reload
	// We skip loading these as they would be garbage

	room.markDirty();
	entity.set<RoomComponent>(room);
}

void SceneSerializer::deserializeRoof(flecs::entity entity,
				      const nlohmann::json &json)
{
	RoofComponent roof;

	roof.type = static_cast<RoofComponent::Type>(json.value("type", 0));
	roof.posX = json.value("posX", 0);
	roof.posY = json.value("posY", 0);
	roof.posZ = json.value("posZ", 0);
	roof.sizeX = json.value("sizeX", 1);
	roof.sizeZ = json.value("sizeZ", 1);
	roof.offsetX = json.value("offsetX", 0.0f);
	roof.offsetY = json.value("offsetY", 0.0f);
	roof.offsetZ = json.value("offsetZ", 0.0f);
	roof.baseHeight = json.value("baseHeight", 0.5f);
	roof.maxHeight = json.value("maxHeight", 0.5f);

	entity.set<RoofComponent>(roof);
}

void SceneSerializer::deserializeFurnitureTemplate(flecs::entity entity,
						   const nlohmann::json &json)
{
	FurnitureTemplateComponent furn;

	furn.templateName = json.value("templateName", "");
	furn.meshName = json.value("meshName", "");
	furn.materialName = json.value("materialName", "");
	furn.requiresWall = json.value("requiresWall", false);
	furn.requiresFloor = json.value("requiresFloor", true);
	furn.blocksPath = json.value("blocksPath", true);
	furn.offsetX = json.value("offsetX", 0.0f);
	furn.offsetY = json.value("offsetY", 0.0f);
	furn.offsetZ = json.value("offsetZ", 0.0f);
	furn.weight = json.value("weight", 1.0f);

	if (json.contains("tags") && json["tags"].is_array()) {
		for (const auto &tag : json["tags"]) {
			if (tag.is_string()) {
				furn.tags.push_back(tag);
			}
		}
	}

	entity.set<FurnitureTemplateComponent>(furn);
}

nlohmann::json SceneSerializer::serializeClearArea(flecs::entity entity)
{
	auto &clearArea = entity.get<ClearAreaComponent>();
	nlohmann::json json;

	json["minX"] = clearArea.minX;
	json["minY"] = clearArea.minY;
	json["minZ"] = clearArea.minZ;
	json["maxX"] = clearArea.maxX;
	json["maxY"] = clearArea.maxY;
	json["maxZ"] = clearArea.maxZ;
	json["clearCells"] = clearArea.clearCells;
	json["clearFurniture"] = clearArea.clearFurniture;
	json["clearRoofs"] = clearArea.clearRoofs;
	json["clearRooms"] = clearArea.clearRooms;

	return json;
}

void SceneSerializer::deserializeClearArea(flecs::entity entity,
					   const nlohmann::json &json)
{
	ClearAreaComponent clearArea;

	clearArea.minX = json.value("minX", 0);
	clearArea.minY = json.value("minY", 0);
	clearArea.minZ = json.value("minZ", 0);
	clearArea.maxX = json.value("maxX", 1);
	clearArea.maxY = json.value("maxY", 1);
	clearArea.maxZ = json.value("maxZ", 1);
	clearArea.clearCells = json.value("clearCells", true);
	clearArea.clearFurniture = json.value("clearFurniture", true);
	clearArea.clearRoofs = json.value("clearRoofs", false);
	clearArea.clearRooms = json.value("clearRooms", false);

	clearArea.markDirty();
	entity.set<ClearAreaComponent>(clearArea);
}

nlohmann::json SceneSerializer::serializeStartupMenu(flecs::entity entity)
{
	auto &sm = entity.get<StartupMenuComponent>();
	nlohmann::json json;
	json["fontName"] = sm.fontName;
	json["fontSize"] = sm.fontSize;
	json["newGameScene"] = sm.newGameScene;
	json["showLoadGame"] = sm.showLoadGame;
	json["showOptions"] = sm.showOptions;
	json["showQuit"] = sm.showQuit;
	return json;
}

nlohmann::json SceneSerializer::serializePlayerController(flecs::entity entity)
{
	auto &pc = entity.get<PlayerControllerComponent>();
	nlohmann::json json;
	json["cameraMode"] = pc.cameraMode;
	json["targetCharacterName"] = pc.targetCharacterName;
	json["fpsBoneName"] = pc.fpsBoneName;
	json["tpsDistance"] = pc.tpsDistance;
	json["tpsHeight"] = pc.tpsHeight;
	json["mouseSensitivity"] = pc.mouseSensitivity;
	json["locomotionStateMachine"] = pc.locomotionStateMachine;
	json["idleState"] = pc.idleState;
	json["walkState"] = pc.walkState;
	json["runState"] = pc.runState;
	json["swimIdleState"] = pc.swimIdleState;
	json["swimState"] = pc.swimState;
	json["swimFastState"] = pc.swimFastState;
	json["actuatorDistance"] = pc.actuatorDistance;
	json["actuatorCooldown"] = pc.actuatorCooldown;
	json["actuatorColor"] = { pc.actuatorColor.x, pc.actuatorColor.y,
				  pc.actuatorColor.z };
	json["distantCircleRadius"] = pc.distantCircleRadius;
	json["nearCircleRadius"] = pc.nearCircleRadius;
	json["actuatorLabelFontSize"] = pc.actuatorLabelFontSize;
	return json;
}

void SceneSerializer::deserializeStartupMenu(flecs::entity entity,
					     const nlohmann::json &json)
{
	StartupMenuComponent sm;
	sm.fontName = json.value("fontName", sm.fontName);
	sm.fontSize = json.value("fontSize", sm.fontSize);
	sm.newGameScene = json.value("newGameScene", sm.newGameScene);
	sm.showLoadGame = json.value("showLoadGame", sm.showLoadGame);
	sm.showOptions = json.value("showOptions", sm.showOptions);
	sm.showQuit = json.value("showQuit", sm.showQuit);
	entity.set<StartupMenuComponent>(sm);
}

void SceneSerializer::deserializePlayerController(flecs::entity entity,
						  const nlohmann::json &json)
{
	PlayerControllerComponent pc;
	pc.cameraMode = json.value("cameraMode", pc.cameraMode);
	pc.targetCharacterName =
		json.value("targetCharacterName", pc.targetCharacterName);
	pc.fpsBoneName = json.value("fpsBoneName", pc.fpsBoneName);
	pc.tpsDistance = json.value("tpsDistance", pc.tpsDistance);
	pc.tpsHeight = json.value("tpsHeight", pc.tpsHeight);
	pc.mouseSensitivity =
		json.value("mouseSensitivity", pc.mouseSensitivity);
	pc.locomotionStateMachine =
		json.value("locomotionStateMachine", pc.locomotionStateMachine);
	pc.idleState = json.value("idleState", pc.idleState);
	pc.walkState = json.value("walkState", pc.walkState);
	pc.runState = json.value("runState", pc.runState);
	pc.swimIdleState = json.value("swimIdleState", pc.swimIdleState);
	pc.swimState = json.value("swimState", pc.swimState);
	pc.swimFastState = json.value("swimFastState", pc.swimFastState);
	pc.actuatorDistance =
		json.value("actuatorDistance", pc.actuatorDistance);
	pc.actuatorCooldown =
		json.value("actuatorCooldown", pc.actuatorCooldown);
	if (json.contains("actuatorColor") &&
	    json["actuatorColor"].is_array() &&
	    json["actuatorColor"].size() >= 3) {
		pc.actuatorColor = Ogre::Vector3(json["actuatorColor"][0],
						 json["actuatorColor"][1],
						 json["actuatorColor"][2]);
	}
	pc.distantCircleRadius =
		json.value("distantCircleRadius", pc.distantCircleRadius);
	pc.nearCircleRadius =
		json.value("nearCircleRadius", pc.nearCircleRadius);
	pc.actuatorLabelFontSize =
		json.value("actuatorLabelFontSize", pc.actuatorLabelFontSize);
	// inputLocked is runtime-only, always reset to false on load
	pc.inputLocked = false;
	entity.set<PlayerControllerComponent>(pc);
}

// ============================================================================
// Buoyancy Component Serialization
// ============================================================================

nlohmann::json SceneSerializer::serializeBuoyancyInfo(flecs::entity entity)
{
	auto &buoyancy = entity.get<BuoyancyInfo>();
	nlohmann::json json;

	json["enabled"] = buoyancy.enabled;
	json["buoyancy"] = buoyancy.buoyancy;
	json["linearDrag"] = buoyancy.linearDrag;
	json["angularDrag"] = buoyancy.angularDrag;
	json["waterSurfaceY"] = buoyancy.waterSurfaceY;
	json["useCustomWaterLevel"] = buoyancy.useCustomWaterLevel;
	json["submergedThreshold"] = buoyancy.submergedThreshold;

	return json;
}

nlohmann::json SceneSerializer::serializeWaterPhysics(flecs::entity entity)
{
	auto &water = entity.get<WaterPhysics>();
	nlohmann::json json;

	json["enabled"] = water.enabled;
	json["waterSurfaceY"] = water.waterSurfaceY;
	json["waterDensity"] = water.waterDensity;
	json["gravity"] = water.gravity;
	json["defaultBuoyancy"] = water.defaultBuoyancy;
	json["defaultLinearDrag"] = water.defaultLinearDrag;
	json["defaultAngularDrag"] = water.defaultAngularDrag;
	json["defaultSubmergedThreshold"] = water.defaultSubmergedThreshold;

	return json;
}

void SceneSerializer::deserializeBuoyancyInfo(flecs::entity entity,
					      const nlohmann::json &json)
{
	BuoyancyInfo buoyancy;

	buoyancy.enabled = json.value("enabled", true);
	buoyancy.buoyancy = json.value("buoyancy", 1.0f);
	buoyancy.linearDrag = json.value("linearDrag", 0.1f);
	buoyancy.angularDrag = json.value("angularDrag", 0.05f);
	buoyancy.waterSurfaceY = json.value("waterSurfaceY", -0.1f);
	buoyancy.useCustomWaterLevel = json.value("useCustomWaterLevel", false);
	buoyancy.submergedThreshold = json.value("submergedThreshold", 0.3f);

	entity.set<BuoyancyInfo>(buoyancy);
}

void SceneSerializer::deserializeWaterPhysics(flecs::entity entity,
					      const nlohmann::json &json)
{
	WaterPhysics water;

	water.enabled = json.value("enabled", true);
	water.waterSurfaceY = json.value("waterSurfaceY", -0.1f);
	water.waterDensity = json.value("waterDensity", 1000.0f);
	water.gravity = json.value("gravity", 9.81f);
	water.defaultBuoyancy = json.value("defaultBuoyancy", 1.5f);
	water.defaultLinearDrag = json.value("defaultLinearDrag", 0.5f);
	water.defaultAngularDrag = json.value("defaultAngularDrag", 0.01f);
	water.defaultSubmergedThreshold =
		json.value("defaultSubmergedThreshold", 0.3f);

	entity.set<WaterPhysics>(water);
}

nlohmann::json SceneSerializer::serializeSun(flecs::entity entity)
{
	const SunComponent &sun = entity.get<SunComponent>();
	nlohmann::json json;

	json["enabled"] = sun.enabled;
	json["timeOfDay"] = sun.timeOfDay;
	json["timeSpeed"] = sun.timeSpeed;
	json["sunColor"] = { sun.sunColor.r, sun.sunColor.g, sun.sunColor.b };
	json["moonColor"] = { sun.moonColor.r, sun.moonColor.g,
			      sun.moonColor.b };
	json["ambientDay"] = { sun.ambientDay.r, sun.ambientDay.g,
			       sun.ambientDay.b };
	json["ambientNight"] = { sun.ambientNight.r, sun.ambientNight.g,
				 sun.ambientNight.b };
	json["ambientSunrise"] = { sun.ambientSunrise.r, sun.ambientSunrise.g,
				   sun.ambientSunrise.b };
	json["ambientSunset"] = { sun.ambientSunset.r, sun.ambientSunset.g,
				  sun.ambientSunset.b };
	json["showSunSphere"] = sun.showSunSphere;
	json["showMoonSphere"] = sun.showMoonSphere;
	json["sunSphereSize"] = sun.sunSphereSize;
	json["moonSphereSize"] = sun.moonSphereSize;
	json["orbitTilt"] = sun.orbitTilt;
	json["intensity"] = sun.intensity;
	json["castShadows"] = sun.castShadows;

	return json;
}

nlohmann::json SceneSerializer::serializeSkybox(flecs::entity entity)
{
	const SkyboxComponent &sky = entity.get<SkyboxComponent>();
	nlohmann::json json;

	json["enabled"] = sky.enabled;
	json["size"] = sky.size;
	json["dayTopColor"] = { sky.dayTopColor.r, sky.dayTopColor.g,
				sky.dayTopColor.b };
	json["dayBottomColor"] = { sky.dayBottomColor.r, sky.dayBottomColor.g,
				   sky.dayBottomColor.b };
	json["nightTopColor"] = { sky.nightTopColor.r, sky.nightTopColor.g,
				  sky.nightTopColor.b };
	json["nightBottomColor"] = { sky.nightBottomColor.r,
				     sky.nightBottomColor.g,
				     sky.nightBottomColor.b };
	json["sunriseColor"] = { sky.sunriseColor.r, sky.sunriseColor.g,
				 sky.sunriseColor.b };
	json["sunsetColor"] = { sky.sunsetColor.r, sky.sunsetColor.g,
				sky.sunsetColor.b };
	json["sunSize"] = sky.sunSize;
	json["moonSize"] = sky.moonSize;
	json["starsEnabled"] = sky.starsEnabled;
	json["cloudiness"] = sky.cloudiness;

	return json;
}

void SceneSerializer::deserializeSun(flecs::entity entity,
				     const nlohmann::json &json)
{
	SunComponent sun;

	sun.enabled = json.value("enabled", true);
	sun.timeOfDay = json.value("timeOfDay", 12.0f);
	sun.timeSpeed = json.value("timeSpeed", 0.05f);
	if (json.contains("sunColor") && json["sunColor"].is_array() &&
	    json["sunColor"].size() >= 3) {
		sun.sunColor = Ogre::ColourValue(json["sunColor"][0],
						 json["sunColor"][1],
						 json["sunColor"][2]);
	}
	if (json.contains("moonColor") && json["moonColor"].is_array() &&
	    json["moonColor"].size() >= 3) {
		sun.moonColor = Ogre::ColourValue(json["moonColor"][0],
						  json["moonColor"][1],
						  json["moonColor"][2]);
	}
	if (json.contains("ambientDay") && json["ambientDay"].is_array() &&
	    json["ambientDay"].size() >= 3) {
		sun.ambientDay = Ogre::ColourValue(json["ambientDay"][0],
						   json["ambientDay"][1],
						   json["ambientDay"][2]);
	}
	if (json.contains("ambientNight") && json["ambientNight"].is_array() &&
	    json["ambientNight"].size() >= 3) {
		sun.ambientNight = Ogre::ColourValue(json["ambientNight"][0],
						     json["ambientNight"][1],
						     json["ambientNight"][2]);
	}
	if (json.contains("ambientSunrise") &&
	    json["ambientSunrise"].is_array() &&
	    json["ambientSunrise"].size() >= 3) {
		sun.ambientSunrise = Ogre::ColourValue(
			json["ambientSunrise"][0], json["ambientSunrise"][1],
			json["ambientSunrise"][2]);
	}
	if (json.contains("ambientSunset") &&
	    json["ambientSunset"].is_array() &&
	    json["ambientSunset"].size() >= 3) {
		sun.ambientSunset = Ogre::ColourValue(json["ambientSunset"][0],
						      json["ambientSunset"][1],
						      json["ambientSunset"][2]);
	}
	sun.showSunSphere = json.value("showSunSphere", true);
	sun.showMoonSphere = json.value("showMoonSphere", true);
	sun.sunSphereSize = json.value("sunSphereSize", 5.0f);
	sun.moonSphereSize = json.value("moonSphereSize", 3.0f);
	sun.orbitTilt = json.value("orbitTilt", 15.0f);
	sun.intensity = json.value("intensity", 1.0f);
	sun.castShadows = json.value("castShadows", true);

	entity.set<SunComponent>(sun);
}

void SceneSerializer::deserializeSkybox(flecs::entity entity,
					const nlohmann::json &json)
{
	SkyboxComponent sky;

	sky.enabled = json.value("enabled", true);
	sky.size = json.value("size", 500.0f);
	if (json.contains("dayTopColor") && json["dayTopColor"].is_array() &&
	    json["dayTopColor"].size() >= 3) {
		sky.dayTopColor = Ogre::ColourValue(json["dayTopColor"][0],
						    json["dayTopColor"][1],
						    json["dayTopColor"][2]);
	}
	if (json.contains("dayBottomColor") &&
	    json["dayBottomColor"].is_array() &&
	    json["dayBottomColor"].size() >= 3) {
		sky.dayBottomColor = Ogre::ColourValue(
			json["dayBottomColor"][0], json["dayBottomColor"][1],
			json["dayBottomColor"][2]);
	}
	if (json.contains("nightTopColor") &&
	    json["nightTopColor"].is_array() &&
	    json["nightTopColor"].size() >= 3) {
		sky.nightTopColor = Ogre::ColourValue(json["nightTopColor"][0],
						      json["nightTopColor"][1],
						      json["nightTopColor"][2]);
	}
	if (json.contains("nightBottomColor") &&
	    json["nightBottomColor"].is_array() &&
	    json["nightBottomColor"].size() >= 3) {
		sky.nightBottomColor =
			Ogre::ColourValue(json["nightBottomColor"][0],
					  json["nightBottomColor"][1],
					  json["nightBottomColor"][2]);
	}
	if (json.contains("sunriseColor") && json["sunriseColor"].is_array() &&
	    json["sunriseColor"].size() >= 3) {
		sky.sunriseColor = Ogre::ColourValue(json["sunriseColor"][0],
						     json["sunriseColor"][1],
						     json["sunriseColor"][2]);
	}
	if (json.contains("sunsetColor") && json["sunsetColor"].is_array() &&
	    json["sunsetColor"].size() >= 3) {
		sky.sunsetColor = Ogre::ColourValue(json["sunsetColor"][0],
						    json["sunsetColor"][1],
						    json["sunsetColor"][2]);
	}
	sky.sunSize = json.value("sunSize", 0.05f);
	sky.moonSize = json.value("moonSize", 0.03f);
	sky.starsEnabled = json.value("starsEnabled", true);
	sky.cloudiness = json.value("cloudiness", 0.0f);

	entity.set<SkyboxComponent>(sky);
}

nlohmann::json SceneSerializer::serializeWaterPlane(flecs::entity entity)
{
	const WaterPlane &wp = entity.get<WaterPlane>();
	nlohmann::json json;

	json["enabled"] = wp.enabled;
	json["waterSurfaceY"] = wp.waterSurfaceY;
	json["planeSize"] = wp.planeSize;
	json["autoUpdateFromWaterPhysics"] = wp.autoUpdateFromWaterPhysics;
	json["waterColor"] = { wp.waterColor.r, wp.waterColor.g,
			       wp.waterColor.b, wp.waterColor.a };
	json["reflectivity"] = wp.reflectivity;
	json["waveSpeed"] = wp.waveSpeed;
	json["waveScale"] = wp.waveScale;
	json["tiling"] = wp.tiling;
	json["renderTextureSize"] = wp.renderTextureSize;

	return json;
}

void SceneSerializer::deserializeWaterPlane(flecs::entity entity,
					    const nlohmann::json &json)
{
	WaterPlane wp;

	wp.enabled = json.value("enabled", true);
	wp.waterSurfaceY = json.value("waterSurfaceY", -0.1f);
	wp.planeSize = json.value("planeSize", 500.0f);
	wp.autoUpdateFromWaterPhysics =
		json.value("autoUpdateFromWaterPhysics", true);
	if (json.contains("waterColor") && json["waterColor"].is_array() &&
	    json["waterColor"].size() >= 4) {
		wp.waterColor = Ogre::ColourValue(json["waterColor"][0],
						  json["waterColor"][1],
						  json["waterColor"][2],
						  json["waterColor"][3]);
	}
	wp.reflectivity = json.value("reflectivity", 0.5f);
	wp.waveSpeed = json.value("waveSpeed", 1.0f);
	wp.waveScale = json.value("waveScale", 0.02f);
	wp.tiling = json.value("tiling", 0.012f);
	wp.renderTextureSize = json.value("renderTextureSize", 512);

	entity.set<WaterPlane>(wp);
}

// ============================================================================
// AI/GOAP Component Serialization Helpers
// ============================================================================

static nlohmann::json serializeGoapBlackboard(const GoapBlackboard &bb)
{
	nlohmann::json json;
	json["bits"] = (uint64_t)bb.bits;
	json["mask"] = (uint64_t)bb.mask;
	if (bb.bitmask != ~0ULL)
		json["bitmask"] = (uint64_t)bb.bitmask;
	if (!bb.values.empty()) {
		json["values"] = nlohmann::json::object();
		for (const auto &pair : bb.values)
			json["values"][pair.first] = pair.second;
	}
	if (!bb.floatValues.empty()) {
		json["floatValues"] = nlohmann::json::object();
		for (const auto &pair : bb.floatValues)
			json["floatValues"][pair.first] = pair.second;
	}
	if (!bb.vec3Values.empty()) {
		json["vec3Values"] = nlohmann::json::object();
		for (const auto &pair : bb.vec3Values) {
			nlohmann::json v;
			v.push_back(pair.second.x);
			v.push_back(pair.second.y);
			v.push_back(pair.second.z);
			json["vec3Values"][pair.first] = v;
		}
	}
	if (!bb.stringValues.empty()) {
		json["stringValues"] = nlohmann::json::object();
		for (const auto &pair : bb.stringValues)
			json["stringValues"][pair.first] = pair.second;
	}
	return json;
}

static void deserializeGoapBlackboard(GoapBlackboard &bb,
				      const nlohmann::json &json)
{
	bb.bits = json.value("bits", (uint64_t)0);
	bb.mask = json.value("mask", (uint64_t)0);
	bb.bitmask = json.value("bitmask", ~0ULL);
	bb.values.clear();
	if (json.contains("values") && json["values"].is_object()) {
		for (auto &[key, val] : json["values"].items())
			bb.values[key] = val.get<int>();
	}
	bb.floatValues.clear();
	if (json.contains("floatValues") && json["floatValues"].is_object()) {
		for (auto &[key, val] : json["floatValues"].items())
			bb.floatValues[key] = val.get<float>();
	}
	bb.vec3Values.clear();
	if (json.contains("vec3Values") && json["vec3Values"].is_object()) {
		for (auto &[key, val] : json["vec3Values"].items()) {
			if (val.is_array() && val.size() >= 3)
				bb.vec3Values[key] =
					Ogre::Vector3(val[0].get<float>(),
						      val[1].get<float>(),
						      val[2].get<float>());
		}
	}
	bb.stringValues.clear();
	if (json.contains("stringValues") && json["stringValues"].is_object()) {
		for (auto &[key, val] : json["stringValues"].items())
			bb.stringValues[key] = val.get<std::string>();
	}
}

static nlohmann::json serializeBehaviorTreeNode(const BehaviorTreeNode &node)
{
	nlohmann::json json;
	json["type"] = node.type;
	if (!node.name.empty())
		json["name"] = node.name;
	if (!node.params.empty())
		json["params"] = node.params;
	if (!node.children.empty()) {
		json["children"] = nlohmann::json::array();
		for (const auto &child : node.children)
			json["children"].push_back(
				serializeBehaviorTreeNode(child));
	}
	return json;
}

static void deserializeBehaviorTreeNode(BehaviorTreeNode &node,
					const nlohmann::json &json)
{
	node.type = json.value("type", "task");
	node.name = json.value("name", "");
	node.params = json.value("params", "");
	node.children.clear();
	if (json.contains("children") && json["children"].is_array()) {
		for (const auto &childJson : json["children"]) {
			BehaviorTreeNode child;
			deserializeBehaviorTreeNode(child, childJson);
			node.children.push_back(child);
		}
	}
}

static nlohmann::json serializeGoapAction(const GoapAction &action)
{
	nlohmann::json json;
	json["name"] = action.name;
	json["cost"] = action.cost;
	json["preconditions"] = serializeGoapBlackboard(action.preconditions);
	json["effects"] = serializeGoapBlackboard(action.effects);
	if (action.preconditionMask != ~0ULL)
		json["preconditionMask"] = action.preconditionMask;
	json["behaviorTree"] = serializeBehaviorTreeNode(action.behaviorTree);
	if (!action.behaviorTreeName.empty())
		json["behaviorTreeName"] = action.behaviorTreeName;
	return json;
}

static void deserializeGoapAction(GoapAction &action,
				  const nlohmann::json &json)
{
	action.name = json.value("name", "Unnamed");
	action.cost = json.value("cost", 1);
	if (json.contains("preconditions"))
		deserializeGoapBlackboard(action.preconditions,
					  json["preconditions"]);
	if (json.contains("effects"))
		deserializeGoapBlackboard(action.effects, json["effects"]);
	action.preconditionMask = json.value("preconditionMask", ~0ULL);
	if (json.contains("behaviorTree"))
		deserializeBehaviorTreeNode(action.behaviorTree,
					    json["behaviorTree"]);
	action.behaviorTreeName = json.value("behaviorTreeName", "");
}

static nlohmann::json serializeGoapGoal(const GoapGoal &goal)
{
	nlohmann::json json;
	json["name"] = goal.name;
	json["priority"] = goal.priority;
	json["target"] = serializeGoapBlackboard(goal.target);
	if (!goal.condition.empty())
		json["condition"] = goal.condition;
	return json;
}

static void deserializeGoapGoal(GoapGoal &goal, const nlohmann::json &json)
{
	goal.name = json.value("name", "Unnamed");
	goal.priority = json.value("priority", 1);
	if (json.contains("target"))
		deserializeGoapBlackboard(goal.target, json["target"]);
	goal.condition = json.value("condition", "");
}

nlohmann::json SceneSerializer::serializeActionDatabase()
{
	const ActionDatabase *dbPtr = ActionDatabase::getSingletonPtr();
	if (!dbPtr)
		return nlohmann::json();
	const ActionDatabase &db = *dbPtr;
	nlohmann::json json;

	json["actions"] = nlohmann::json::array();
	for (const auto &action : db.actions)
		json["actions"].push_back(serializeGoapAction(action));

	json["goals"] = nlohmann::json::array();
	for (const auto &goal : db.goals)
		json["goals"].push_back(serializeGoapGoal(goal));

	// Save bit names
	nlohmann::json bitNames = nlohmann::json::array();
	for (int i = 0; i < 64; i++) {
		const char *name = GoapBlackboard::getBitName(i);
		if (name) {
			nlohmann::json entry;
			entry["index"] = i;
			entry["name"] = name;
			bitNames.push_back(entry);
		}
	}
	if (!bitNames.empty())
		json["bitNames"] = bitNames;

	return json;
}

void SceneSerializer::deserializeActionDatabase(const nlohmann::json &json)
{
	ActionDatabase *db = ActionDatabase::getSingletonPtr();
	if (!db)
		return;

	if (json.contains("actions") && json["actions"].is_array()) {
		for (const auto &actionJson : json["actions"]) {
			GoapAction action;
			deserializeGoapAction(action, actionJson);
			db->addOrReplaceAction(action);
		}
	}

	if (json.contains("goals") && json["goals"].is_array()) {
		for (const auto &goalJson : json["goals"]) {
			GoapGoal goal;
			deserializeGoapGoal(goal, goalJson);
			db->addOrReplaceGoal(goal);
		}
	}

	// Load bit names
	if (json.contains("bitNames") && json["bitNames"].is_array()) {
		for (const auto &entry : json["bitNames"]) {
			if (entry.contains("index") && entry.contains("name"))
				GoapBlackboard::setBitName(
					entry["index"].get<int>(),
					entry["name"].get<std::string>());
		}
	}
}

nlohmann::json SceneSerializer::serializeActionDebug(flecs::entity entity)
{
	const ActionDebug &debug = entity.get<ActionDebug>();
	nlohmann::json json;
	json["blackboard"] = serializeGoapBlackboard(debug.blackboard);
	if (!debug.selectedActionName.empty())
		json["selectedActionName"] = debug.selectedActionName;
	if (!debug.selectedGoalName.empty())
		json["selectedGoalName"] = debug.selectedGoalName;
	return json;
}

nlohmann::json SceneSerializer::serializePathFollowing(flecs::entity entity)
{
	const PathFollowingComponent &pf = entity.get<PathFollowingComponent>();
	nlohmann::json json;
	json["pathFollowingStates"] = nlohmann::json::array();
	for (const auto &pfState : pf.pathFollowingStates) {
		nlohmann::json entry;
		entry["name"] = pfState.name;
		entry["stateMachineStates"] = nlohmann::json::array();
		for (const auto &pair : pfState.stateMachineStates) {
			nlohmann::json pairEntry;
			pairEntry["stateMachine"] = pair.first;
			pairEntry["stateName"] = pair.second;
			entry["stateMachineStates"].push_back(pairEntry);
		}
		json["pathFollowingStates"].push_back(entry);
	}
	json["walkSpeed"] = pf.walkSpeed;
	json["runSpeed"] = pf.runSpeed;
	json["useRootMotion"] = pf.useRootMotion;
	json["currentLocomotionState"] = pf.currentLocomotionState;
	json["hasTarget"] = pf.hasTarget;
	if (pf.hasTarget) {
		nlohmann::json tp;
		tp["x"] = pf.targetPosition.x;
		tp["y"] = pf.targetPosition.y;
		tp["z"] = pf.targetPosition.z;
		json["targetPosition"] = tp;
	}
	if (!pf.path.empty()) {
		json["path"] = nlohmann::json::array();
		for (const auto &p : pf.path) {
			nlohmann::json pt;
			pt["x"] = p.x;
			pt["y"] = p.y;
			pt["z"] = p.z;
			json["path"].push_back(pt);
		}
	}
	json["pathIndex"] = pf.pathIndex;
	json["pathRecalcTimer"] = pf.pathRecalcTimer;
	return json;
}

void SceneSerializer::deserializeActionDebug(flecs::entity entity,
					     const nlohmann::json &json)
{
	ActionDebug debug;
	if (json.contains("blackboard"))
		deserializeGoapBlackboard(debug.blackboard, json["blackboard"]);
	debug.selectedActionName = json.value("selectedActionName", "");
	debug.selectedGoalName = json.value("selectedGoalName", "");
	entity.set<ActionDebug>(debug);
}

void SceneSerializer::deserializePathFollowing(flecs::entity entity,
					       const nlohmann::json &json)
{
	PathFollowingComponent pf;
	if (json.contains("pathFollowingStates") &&
	    json["pathFollowingStates"].is_array()) {
		pf.pathFollowingStates.clear();
		for (const auto &entry : json["pathFollowingStates"]) {
			PathFollowingState pfState;
			pfState.name = entry.value("name", "");
			if (entry.contains("stateMachineStates") &&
			    entry["stateMachineStates"].is_array()) {
				for (const auto &pairEntry :
				     entry["stateMachineStates"]) {
					Ogre::String sm = pairEntry.value(
						"stateMachine", "main");
					Ogre::String state = pairEntry.value(
						"stateName", "Idle");
					pfState.stateMachineStates.push_back(
						{ sm, state });
				}
			}
			pf.pathFollowingStates.push_back(pfState);
		}
	} else if (json.contains("animStates") &&
		   json["animStates"].is_array()) {
		// Backward compatibility: old animStates format
		pf.pathFollowingStates.clear();
		for (const auto &entry : json["animStates"]) {
			PathFollowingState pfState;
			pfState.name = entry.value("name", "");
			Ogre::String mainSM =
				entry.value("mainStateMachine", "main");
			Ogre::String subSM =
				entry.value("subStateMachine", "locomotion");
			Ogre::String stateName =
				entry.value("stateName", "Idle");
			pfState.stateMachineStates.push_back({ mainSM, subSM });
			pfState.stateMachineStates.push_back(
				{ subSM, stateName });
			pf.pathFollowingStates.push_back(pfState);
		}
	} else {
		// Default states
		pf.pathFollowingStates.clear();
		pf.pathFollowingStates.push_back(PathFollowingState("idle"));
		pf.pathFollowingStates.push_back(PathFollowingState("walk"));
		pf.pathFollowingStates.push_back(PathFollowingState("run"));
	}

	pf.walkSpeed = json.value("walkSpeed", 2.5f);
	pf.runSpeed = json.value("runSpeed", 5.0f);
	pf.useRootMotion = json.value("useRootMotion", true);
	pf.currentLocomotionState =
		json.value("currentLocomotionState", "idle");
	pf.hasTarget = json.value("hasTarget", false);
	if (json.contains("targetPosition") &&
	    json["targetPosition"].is_object()) {
		auto &tp = json["targetPosition"];
		pf.targetPosition = Ogre::Vector3(tp.value("x", 0.0f),
						  tp.value("y", 0.0f),
						  tp.value("z", 0.0f));
	}
	if (json.contains("path") && json["path"].is_array()) {
		pf.path.clear();
		for (const auto &pt : json["path"]) {
			pf.path.push_back(Ogre::Vector3(pt.value("x", 0.0f),
							pt.value("y", 0.0f),
							pt.value("z", 0.0f)));
		}
	}
	pf.pathIndex = json.value("pathIndex", 0);
	pf.pathRecalcTimer = json.value("pathRecalcTimer", 0.0f);
	entity.set<PathFollowingComponent>(pf);
}

nlohmann::json SceneSerializer::serializeSmartObject(flecs::entity entity)
{
	const SmartObjectComponent &so = entity.get<SmartObjectComponent>();
	nlohmann::json json;
	json["radius"] = so.radius;
	json["height"] = so.height;
	json["actionNames"] = so.actionNames;
	return json;
}

void SceneSerializer::deserializeSmartObject(flecs::entity entity,
					     const nlohmann::json &json)
{
	SmartObjectComponent so;
	so.radius = json.value("radius", 1.0f);
	so.height = json.value("height", 1.8f);
	if (json.contains("actionNames") && json["actionNames"].is_array()) {
		so.actionNames.clear();
		for (const auto &name : json["actionNames"]) {
			if (name.is_string())
				so.actionNames.push_back(name);
		}
	}
	entity.set<SmartObjectComponent>(so);
}

nlohmann::json SceneSerializer::serializeGoapPlanner(flecs::entity entity)
{
	const GoapPlannerComponent &planner =
		entity.get<GoapPlannerComponent>();
	nlohmann::json json;
	json["actionNames"] = planner.actionNames;
	if (!planner.goalNames.empty())
		json["goalNames"] = planner.goalNames;
	json["smartObjectDistance"] = planner.smartObjectDistance;
	json["includeSmartObjects"] = planner.includeSmartObjects;
	if (!planner.actionDatabaseRef.empty())
		json["actionDatabaseRef"] = planner.actionDatabaseRef;
	return json;
}

void SceneSerializer::deserializeGoapPlanner(flecs::entity entity,
					     const nlohmann::json &json)
{
	GoapPlannerComponent planner;
	if (json.contains("actionNames") && json["actionNames"].is_array()) {
		planner.actionNames.clear();
		for (const auto &name : json["actionNames"]) {
			if (name.is_string())
				planner.actionNames.push_back(name);
		}
	}
	if (json.contains("goalNames") && json["goalNames"].is_array()) {
		planner.goalNames.clear();
		for (const auto &name : json["goalNames"]) {
			if (name.is_string())
				planner.goalNames.push_back(name);
		}
	}
	planner.smartObjectDistance = json.value("smartObjectDistance", 50.0f);
	planner.includeSmartObjects = json.value("includeSmartObjects", true);
	planner.actionDatabaseRef = json.value("actionDatabaseRef", "");
	planner.planDirty = true;
	entity.set<GoapPlannerComponent>(planner);
}

nlohmann::json SceneSerializer::serializeGoapRunner(flecs::entity entity)
{
	const GoapRunnerComponent &runner = entity.get<GoapRunnerComponent>();
	nlohmann::json json;
	json["state"] = static_cast<int>(runner.state);
	json["currentActionIndex"] = runner.currentActionIndex;
	if (!runner.currentActionName.empty())
		json["currentActionName"] = runner.currentActionName;
	json["actionTimer"] = runner.actionTimer;
	if (runner.targetSmartObjectId != 0)
		json["targetSmartObjectId"] = runner.targetSmartObjectId;
	if (!runner.planActions.empty()) {
		json["planActions"] = runner.planActions;
	}
	json["autoReplan"] = runner.autoReplan;
	return json;
}

void SceneSerializer::deserializeGoapRunner(flecs::entity entity,
					    const nlohmann::json &json)
{
	GoapRunnerComponent runner;
	runner.state =
		static_cast<GoapRunnerComponent::State>(json.value("state", 0));
	runner.currentActionIndex = json.value("currentActionIndex", 0);
	runner.currentActionName = json.value("currentActionName", "");
	runner.actionTimer = json.value("actionTimer", 0.0f);
	runner.targetSmartObjectId = json.value("targetSmartObjectId", 0ULL);
	if (json.contains("planActions") && json["planActions"].is_array()) {
		for (const auto &name : json["planActions"]) {
			if (name.is_string())
				runner.planActions.push_back(name);
		}
	}
	runner.autoReplan = json.value("autoReplan", true);
	entity.set<GoapRunnerComponent>(runner);
}

nlohmann::json SceneSerializer::serializeBehaviorTree(flecs::entity entity)
{
	const BehaviorTreeComponent &bt = entity.get<BehaviorTreeComponent>();
	nlohmann::json json;
	json["enabled"] = bt.enabled;
	if (!bt.treeName.empty())
		json["treeName"] = bt.treeName;
	json["root"] = serializeBehaviorTreeNode(bt.root);
	return json;
}

void SceneSerializer::deserializeBehaviorTree(flecs::entity entity,
					      const nlohmann::json &json)
{
	BehaviorTreeComponent bt;
	bt.enabled = json.value("enabled", true);
	bt.treeName = json.value("treeName", "");
	if (json.contains("root"))
		deserializeBehaviorTreeNode(bt.root, json["root"]);
	bt.dirty = true;
	entity.set<BehaviorTreeComponent>(bt);
}

nlohmann::json SceneSerializer::serializeNavMesh(flecs::entity entity)
{
	const NavMeshComponent &nm = entity.get<NavMeshComponent>();
	nlohmann::json json;
	json["cellSize"] = nm.cellSize;
	json["cellHeight"] = nm.cellHeight;
	json["agentHeight"] = nm.agentHeight;
	json["agentRadius"] = nm.agentRadius;
	json["agentMaxClimb"] = nm.agentMaxClimb;
	json["agentMaxSlope"] = nm.agentMaxSlope;
	json["edgeMaxLen"] = nm.edgeMaxLen;
	json["edgeMaxError"] = nm.edgeMaxError;
	json["regionMinSize"] = nm.regionMinSize;
	json["regionMergeSize"] = nm.regionMergeSize;
	json["tileSize"] = nm.tileSize;
	json["enabled"] = nm.enabled;
	json["debugDraw"] = nm.debugDraw;
	return json;
}

void SceneSerializer::deserializeNavMesh(flecs::entity entity,
					 const nlohmann::json &json)
{
	NavMeshComponent nm;
	nm.cellSize = json.value("cellSize", 0.3f);
	nm.cellHeight = json.value("cellHeight", 0.2f);
	nm.agentHeight = json.value("agentHeight", 2.5f);
	nm.agentRadius = json.value("agentRadius", 0.5f);
	nm.agentMaxClimb = json.value("agentMaxClimb", 1.0f);
	nm.agentMaxSlope = json.value("agentMaxSlope", 20.0f);
	nm.edgeMaxLen = json.value("edgeMaxLen", 12.0f);
	nm.edgeMaxError = json.value("edgeMaxError", 1.3f);
	nm.regionMinSize = json.value("regionMinSize", 50.0f);
	nm.regionMergeSize = json.value("regionMergeSize", 20.0f);
	nm.tileSize = json.value("tileSize", 48);
	nm.enabled = json.value("enabled", true);
	nm.debugDraw = json.value("debugDraw", false);
	nm.dirty = true;
	entity.set<NavMeshComponent>(nm);
}

nlohmann::json
SceneSerializer::serializeNavMeshGeometrySource(flecs::entity entity)
{
	const NavMeshGeometrySource &src = entity.get<NavMeshGeometrySource>();
	nlohmann::json json;
	json["include"] = src.include;
	return json;
}

void SceneSerializer::deserializeNavMeshGeometrySource(
	flecs::entity entity, const nlohmann::json &json)
{
	NavMeshGeometrySource src;
	src.include = json.value("include", true);
	entity.set<NavMeshGeometrySource>(src);
}

nlohmann::json SceneSerializer::serializeActuator(flecs::entity entity)
{
	const ActuatorComponent &actuator = entity.get<ActuatorComponent>();
	nlohmann::json json;
	json["radius"] = actuator.radius;
	json["height"] = actuator.height;
	json["actionNames"] = actuator.actionNames;
	return json;
}

void SceneSerializer::deserializeActuator(flecs::entity entity,
					  const nlohmann::json &json)
{
	ActuatorComponent actuator;
	actuator.radius = json.value("radius", 1.5f);
	actuator.height = json.value("height", 1.8f);
	if (json.contains("actionNames") && json["actionNames"].is_array()) {
		actuator.actionNames.clear();
		for (const auto &name : json["actionNames"]) {
			if (name.is_string())
				actuator.actionNames.push_back(name);
		}
	}
	entity.set<ActuatorComponent>(actuator);
}

nlohmann::json SceneSerializer::serializeEventHandler(flecs::entity entity)
{
	const EventHandlerComponent &handler =
		entity.get<EventHandlerComponent>();
	nlohmann::json json;
	json["eventName"] = handler.eventName;
	json["actionName"] = handler.actionName;
	json["enabled"] = handler.enabled;
	return json;
}

void SceneSerializer::deserializeEventHandler(flecs::entity entity,
					      const nlohmann::json &json)
{
	EventHandlerComponent handler;
	handler.eventName = json.value("eventName", handler.eventName);
	handler.actionName = json.value("actionName", handler.actionName);
	handler.enabled = json.value("enabled", handler.enabled);
	entity.set<EventHandlerComponent>(handler);
}

nlohmann::json SceneSerializer::serializeItem(flecs::entity entity)
{
	const ItemComponent &item = entity.get<ItemComponent>();
	nlohmann::json json;
	json["itemId"] = item.itemId;
	json["stackSize"] = item.stackSize;
	if (!item.action.empty())
		json["action"] = item.action;
	if (!item.instanceId.empty())
		json["instanceId"] = item.instanceId;
	if (item.disabled)
		json["disabled"] = true;
	return json;
}

nlohmann::json SceneSerializer::serializeInventory(flecs::entity entity)
{
	const InventoryComponent &inv = entity.get<InventoryComponent>();
	nlohmann::json json;
	json["maxSlots"] = inv.maxSlots;
	json["maxWeight"] = inv.maxWeight;
	json["isContainer"] = inv.isContainer;
	json["isOpen"] = inv.isOpen;
	json["containerId"] = inv.containerId;

	nlohmann::json slotsJson = nlohmann::json::array();
	for (const auto &slot : inv.slots) {
		nlohmann::json slotJson;
		slotJson["itemEntity"] = (uint64_t)slot.itemEntity;
		slotJson["itemId"] = slot.itemId;
		slotJson["stackSize"] = slot.stackSize;
		slotsJson.push_back(slotJson);
	}
	json["slots"] = slotsJson;

	return json;
}

static void ensureItemInRegistry(const std::string &itemId,
				 const nlohmann::json &json)
{
	if (itemId.empty())
		return;
	ItemRegistry *reg = ItemRegistry::getSingletonPtr();
	if (!reg || reg->hasItem(itemId))
		return;
	ItemRegistry::ItemDefinition def;
	def.itemId = itemId;
	def.itemName = json.value("itemName", itemId);
	def.itemType = json.value("itemType", "misc");
	def.maxStackSize = json.value("maxStackSize", 99);
	def.weight = json.value("weight", 0.1f);
	def.value = json.value("value", 1);
	def.useActionName = json.value("useActionName", "");
	def.unique = json.value("unique", false);
	reg->registerItem(def);
}

void SceneSerializer::deserializeItem(flecs::entity entity,
				      const nlohmann::json &json)
{
	ItemComponent item;
	item.itemId = json.value("itemId", "");
	item.stackSize = json.value("stackSize", 1);
	item.instanceId = json.value("instanceId", "");
	item.disabled = json.value("disabled", false);
	// If the item has an instanceId, check global state registry
	// in case it was picked up in a previous session.
	if (!item.instanceId.empty() &&
	    ItemStateRegistry::getInstance().isDisabled(item.instanceId)) {
		item.disabled = true;
	}

	// Backward compatibility: old scenes had inline item data
	if (item.itemId.empty() && json.contains("itemName")) {
		item.itemId = json.value("itemName", "");
	}
	ensureItemInRegistry(item.itemId, json);
	entity.set<ItemComponent>(item);
}

void SceneSerializer::deserializeInventory(flecs::entity entity,
					   const nlohmann::json &json)
{
	InventoryComponent inv;
	inv.maxSlots = json.value("maxSlots", 20);
	inv.maxWeight = json.value("maxWeight", 50.0f);
	inv.isContainer = json.value("isContainer", false);
	inv.isOpen = json.value("isOpen", false);
	inv.containerId = json.value("containerId", "");

	inv.slots.clear();
	if (json.contains("slots") && json["slots"].is_array()) {
		for (const auto &slotJson : json["slots"]) {
			InventorySlot slot;
			slot.itemEntity = (flecs::entity_t)slotJson.value(
				"itemEntity", (uint64_t)0);
			slot.itemId = slotJson.value("itemId", "");
			slot.stackSize = slotJson.value("stackSize", 0);

			// Backward compatibility: old slots had inline data
			if (slot.itemId.empty() &&
			    slotJson.contains("itemName")) {
				slot.itemId = slotJson.value("itemName", "");
			}
			ensureItemInRegistry(slot.itemId, slotJson);
			inv.slots.push_back(slot);
		}
	}

	inv.recalculateWeight();
	entity.set<InventoryComponent>(inv);

	// For containers with a containerId, check global state override
	if (inv.isContainer && !inv.containerId.empty()) {
		auto &cstate = ContainerStateRegistry::getInstance();
		if (cstate.hasState(inv.containerId)) {
			auto stateSlots = cstate.getState(inv.containerId);
			inv.slots.clear();
			for (const auto &s : stateSlots) {
				InventorySlot slot;
				slot.itemId = s.itemId;
				slot.stackSize = s.stackSize;
				inv.slots.push_back(slot);
			}
			inv.recalculateWeight();
			entity.set<InventoryComponent>(inv);
		}
	}
}
