#include "CharacterSlotSystem.hpp"
#include "CharacterRegistry.hpp"
#include "OgreEntityHack.hpp"
#include "../components/Transform.hpp"
#include "../components/AnimationTree.hpp"
#include "../components/CharacterIdentity.hpp"
#include "../components/CharacterSlots.hpp"
#include <OgreAnimation.h>
#include <OgreAnimationState.h>
#include <OgreAnimationTrack.h>
#include <OgreDataStream.h>
#include <OgreEntity.h>
#include <OgreKeyFrame.h>
#include <OgreLogManager.h>
#include <OgreMesh.h>
#include <OgreResourceGroupManager.h>
#include <OgreSceneNode.h>
#include <algorithm>

CharacterSlotSystem *CharacterSlotSystem::ms_singleton = nullptr;
bool CharacterSlotSystem::s_catalogLoaded = false;
nlohmann::json CharacterSlotSystem::s_bodyParts = nlohmann::json::object();
std::set<Ogre::String> CharacterSlotSystem::s_meshNames;

CharacterSlotSystem *CharacterSlotSystem::getSingletonPtr()
{
	return ms_singleton;
}

CharacterSlotSystem::CharacterSlotSystem(flecs::world &world,
					 Ogre::SceneManager *sceneMgr)
	: m_world(world)
	, m_sceneMgr(sceneMgr)
{
	ms_singleton = this;
	m_world.observer<CharacterIdentityComponent>("CharacterSlotsBuild")
		.event(flecs::OnAdd)
		.each([this](flecs::entity e, CharacterIdentityComponent &) {
			markEntityDirty(e.id());
		});
	m_world.observer<CharacterIdentityComponent>("CharacterSlotsCleanup")
		.event(flecs::OnRemove)
		.each([this](flecs::entity e, CharacterIdentityComponent &) {
			destroyCharacterParts(e);
		});
}

CharacterSlotSystem::~CharacterSlotSystem()
{
	std::vector<flecs::entity_t> toRemove;
	for (auto &pair : m_entities)
		toRemove.push_back(pair.first);
	for (auto id : toRemove)
		destroyCharacterParts(m_world.entity(id));
	if (ms_singleton == this)
		ms_singleton = nullptr;
}

void CharacterSlotSystem::initialize()
{
	if (m_initialized)
		return;
	loadCatalog();
	m_initialized = true;
}

void CharacterSlotSystem::loadCatalog()
{
	if (s_catalogLoaded)
		return;

	s_bodyParts = nlohmann::json::object();
	s_meshNames.clear();

	Ogre::ResourceGroupManager &rgm =
		Ogre::ResourceGroupManager::getSingleton();
	Ogre::StringVector groups = rgm.getResourceGroups();
	std::vector<Ogre::String> partNames;

	for (const auto &group : groups) {
		Ogre::StringVectorPtr names =
			rgm.findResourceNames(group, "body_part_*.json");
		if (names)
			partNames.insert(partNames.end(), names->begin(),
					 names->end());
	}

	for (const auto &name : partNames) {
		Ogre::String group = rgm.findGroupContainingResource(name);
		Ogre::DataStreamPtr stream = rgm.openResource(name, group);
		Ogre::String jsonStr = stream->getAsString();
		try {
			nlohmann::json jdata = nlohmann::json::parse(jsonStr);
			if (!jdata.contains("age") || !jdata.contains("sex") ||
			    !jdata.contains("slot") || !jdata.contains("mesh"))
				continue;

			Ogre::String age = jdata["age"].get<Ogre::String>();
			Ogre::String sex = jdata["sex"].get<Ogre::String>();
			Ogre::String slot = jdata["slot"].get<Ogre::String>();
			Ogre::String mesh = jdata["mesh"].get<Ogre::String>();

			if (!s_bodyParts.contains(age))
				s_bodyParts[age] = nlohmann::json::object();
			if (!s_bodyParts[age].contains(sex))
				s_bodyParts[age][sex] =
					nlohmann::json::object();
			if (!s_bodyParts[age][sex].contains(slot))
				s_bodyParts[age][sex][slot] =
					nlohmann::json::array();

			s_bodyParts[age][sex][slot].push_back(jdata);
			s_meshNames.insert(mesh);

			/* Preload mesh into Characters group */
			try {
				Ogre::MeshManager::getSingleton().load(
					mesh, "Characters");
			} catch (...) {
			}
		} catch (...) {
			continue;
		}
	}

	s_catalogLoaded = true;
}

bool CharacterSlotSystem::isCatalogLoaded()
{
	return s_catalogLoaded;
}

std::vector<Ogre::String> CharacterSlotSystem::getAges()
{
	std::vector<Ogre::String> ages;
	if (!s_catalogLoaded)
		return ages;
	for (auto &el : s_bodyParts.items())
		ages.push_back(el.key());
	return ages;
}

std::vector<Ogre::String> CharacterSlotSystem::getSexes(const Ogre::String &age)
{
	std::vector<Ogre::String> sexes;
	if (!s_catalogLoaded || !s_bodyParts.contains(age))
		return sexes;
	for (auto &el : s_bodyParts[age].items())
		sexes.push_back(el.key());
	return sexes;
}

std::vector<Ogre::String> CharacterSlotSystem::getSlots(const Ogre::String &age,
							const Ogre::String &sex)
{
	std::vector<Ogre::String> slots;
	if (!s_catalogLoaded || !s_bodyParts.contains(age) ||
	    !s_bodyParts[age].contains(sex))
		return slots;
	for (auto &el : s_bodyParts[age][sex].items())
		slots.push_back(el.key());
	return slots;
}

std::vector<Ogre::String>
CharacterSlotSystem::getMeshesForLayer(const Ogre::String &age,
				       const Ogre::String &sex,
				       const Ogre::String &slot, int layer)
{
	std::vector<Ogre::String> meshes;
	if (!s_catalogLoaded || !s_bodyParts.contains(age) ||
	    !s_bodyParts[age].contains(sex) ||
	    !s_bodyParts[age][sex].contains(slot))
		return meshes;

	for (const auto &entry : s_bodyParts[age][sex][slot]) {
		int entryLayer = entry.value("layer", 0);
		if (entryLayer == layer)
			meshes.push_back(entry.value("mesh", ""));
	}
	return meshes;
}

Ogre::String CharacterSlotSystem::getMeshLabel(const Ogre::String &age,
					       const Ogre::String &sex,
					       const Ogre::String &slot,
					       const Ogre::String &mesh)
{
	if (!s_catalogLoaded || !s_bodyParts.contains(age) ||
	    !s_bodyParts[age].contains(sex) ||
	    !s_bodyParts[age][sex].contains(slot))
		return mesh;

	for (const auto &entry : s_bodyParts[age][sex][slot]) {
		if (entry.value("mesh", "") == mesh) {
			const auto &garments = entry.value(
				"garments", nlohmann::json::array());
			if (!garments.empty()) {
				Ogre::String label;
				for (size_t i = 0; i < garments.size(); ++i) {
					if (i > 0)
						label += " + ";
					label += garments[i].get<std::string>();
				}
				return label;
			}
			/* For non-garment slots (hair, face, etc.), derive
			 * a human-readable label from the mesh filename */
			Ogre::String label = mesh;
			/* Strip directory prefix */
			auto slashPos = label.rfind('/');
			if (slashPos != Ogre::String::npos)
				label = label.substr(slashPos + 1);
			/* Strip .mesh extension */
			auto dotPos = label.rfind(".mesh");
			if (dotPos != Ogre::String::npos)
				label = label.substr(0, dotPos);
			/* Strip sex prefix (male_/female_) */
			Ogre::String sexPrefix = sex + "_";
			if (label.find(sexPrefix) == 0)
				label = label.substr(sexPrefix.length());
			/* Replace underscores with spaces */
			for (auto &c : label) {
				if (c == '_')
					c = ' ';
			}
			return label;
		}
	}
	return mesh;
}

std::vector<Ogre::String>
CharacterSlotSystem::getMeshes(const Ogre::String &age, const Ogre::String &sex,
			       const Ogre::String &slot)
{
	std::vector<Ogre::String> meshes;
	if (!s_catalogLoaded || !s_bodyParts.contains(age) ||
	    !s_bodyParts[age].contains(sex) ||
	    !s_bodyParts[age][sex].contains(slot))
		return meshes;
	for (const auto &entry : s_bodyParts[age][sex][slot])
		meshes.push_back(entry["mesh"].get<Ogre::String>());
	return meshes;
}

std::vector<Ogre::String>
CharacterSlotSystem::getShapeKeyNames(const Ogre::String &age,
				      const Ogre::String &sex)
{
	std::set<Ogre::String> keySet;
	if (!s_catalogLoaded || !s_bodyParts.contains(age) ||
	    !s_bodyParts[age].contains(sex))
		return {};

	for (auto &slotEl : s_bodyParts[age][sex].items()) {
		for (const auto &entry : slotEl.value()) {
			const auto &keys = entry.value("shape_keys",
						       nlohmann::json::array());
			for (const auto &k : keys)
				keySet.insert(k.get<Ogre::String>());
		}
	}

	return std::vector<Ogre::String>(keySet.begin(), keySet.end());
}

static std::vector<Ogre::String> garmentsForMesh(const Ogre::String &age,
						 const Ogre::String &sex,
						 const Ogre::String &slot,
						 const Ogre::String &mesh)
{
	if (!CharacterSlotSystem::isCatalogLoaded())
		return {};
	const nlohmann::json &cat = CharacterSlotSystem::getCatalog();
	if (!cat.contains(age) || !cat[age].contains(sex) ||
	    !cat[age][sex].contains(slot))
		return {};
	for (const auto &entry : cat[age][sex][slot]) {
		if (entry.value("mesh", "") == mesh) {
			std::vector<Ogre::String> result;
			for (const auto &g :
			     entry.value("garments", nlohmann::json::array()))
				result.push_back(g.get<std::string>());
			return result;
		}
	}
	return {};
}

Ogre::String CharacterSlotSystem::resolveMesh(const Ogre::String &age,
					      const Ogre::String &sex,
					      const Ogre::String &slot,
					      const SlotSelection &sel,
					      int outfitLevel)
{
	if (!sel.explicitMesh.empty())
		return sel.explicitMesh;

	if (!s_catalogLoaded || !s_bodyParts.contains(age) ||
	    !s_bodyParts[age].contains(sex) ||
	    !s_bodyParts[age][sex].contains(slot))
		return "";

	const auto &slotEntries = s_bodyParts[age][sex][slot];

	/* If layer 0 is explicitly selected, use it */
	if (sel.layer0Mesh != "none" && !sel.layer0Mesh.empty()) {
		for (const auto &entry : slotEntries) {
			if (entry.value("mesh", "") == sel.layer0Mesh)
				return sel.layer0Mesh;
		}
	}

	/* outfitLevel: 0=nude, 1=lingerie, 2=clothed */
	if (outfitLevel >= 2 && sel.layer2Mesh != "none" &&
	    !sel.layer2Mesh.empty()) {
		/* If layer 1 is also selected, try to find a combined mesh
		 * whose garments array contains both selections */
		if (sel.layer1Mesh != "none" && !sel.layer1Mesh.empty()) {
			auto l1g =
				garmentsForMesh(age, sex, slot, sel.layer1Mesh);
			auto l2g =
				garmentsForMesh(age, sex, slot, sel.layer2Mesh);
			std::set<Ogre::String> required;
			for (const auto &g : l1g)
				required.insert(g);
			for (const auto &g : l2g)
				required.insert(g);
			if (!required.empty()) {
				Ogre::String combinedMesh;
				for (const auto &entry : slotEntries) {
					auto entryGarments = entry.value(
						"garments",
						nlohmann::json::array());
					std::set<Ogre::String> eg;
					for (const auto &g : entryGarments)
						eg.insert(g.get<std::string>());
					bool containsAll = true;
					for (const auto &g : required) {
						if (eg.find(g) == eg.end()) {
							containsAll = false;
							break;
						}
					}
					if (containsAll) {
						Ogre::String m =
							entry["mesh"]
								.get<Ogre::String>();
						/* Prefer exact layer2 match if it already
						 * satisfies the requirement */
						if (m == sel.layer2Mesh)
							return m;
						if (combinedMesh.empty())
							combinedMesh = m;
					}
				}
				if (!combinedMesh.empty())
					return combinedMesh;
			}
		}
		for (const auto &entry : slotEntries) {
			if (entry.value("mesh", "") == sel.layer2Mesh)
				return sel.layer2Mesh;
		}
	}
	if (outfitLevel >= 1 && sel.layer1Mesh != "none" &&
	    !sel.layer1Mesh.empty()) {
		for (const auto &entry : slotEntries) {
			if (entry.value("mesh", "") == sel.layer1Mesh)
				return sel.layer1Mesh;
		}
	}

	/* Fallback to layer 0 (nude base) — prefer shortest name.
	 * Base mesh names are shortest; combined meshes add suffixes.
	 * We also penalise Blender duplicate names (.001, .002). */
	Ogre::String bestLayer0;
	size_t bestLen = 0;
	for (const auto &entry : slotEntries) {
		if (entry.value("layer", 0) == 0) {
			Ogre::String mesh = entry["mesh"].get<Ogre::String>();
			size_t effectiveLen = mesh.length();
			size_t dotMesh = mesh.rfind(".mesh");
			if (dotMesh != Ogre::String::npos && dotMesh >= 4) {
				bool isDup = true;
				for (size_t i = dotMesh - 3; i < dotMesh; ++i) {
					if (!isdigit(static_cast<unsigned char>(
						    mesh[i]))) {
						isDup = false;
						break;
					}
				}
				if (isDup && mesh[dotMesh - 4] == '.')
					effectiveLen += 1000;
			}
			if (bestLayer0.empty() || effectiveLen < bestLen) {
				bestLayer0 = mesh;
				bestLen = effectiveLen;
			}
		}
	}
	if (!bestLayer0.empty())
		return bestLayer0;

	/* Last resort: first available entry */
	if (!slotEntries.empty())
		return slotEntries[0]["mesh"].get<Ogre::String>();

	return "";
}

void CharacterSlotSystem::update()
{
	if (!m_initialized)
		return;

	std::vector<flecs::entity_t> dirtyList(m_dirtyEntities.begin(),
					       m_dirtyEntities.end());
	m_dirtyEntities.clear();
	for (flecs::entity_t id : dirtyList) {
		flecs::entity e = m_world.entity(id);
		if (e.is_alive() && e.has<CharacterIdentityComponent>())
			buildCharacter(e);
	}
}

static const nlohmann::json *findCatalogEntry(const Ogre::String &age,
					      const Ogre::String &sex,
					      const Ogre::String &slot,
					      const Ogre::String &mesh)
{
	if (!CharacterSlotSystem::isCatalogLoaded())
		return nullptr;
	const nlohmann::json &cat = CharacterSlotSystem::getCatalog();
	if (!cat.contains(age) || !cat[age].contains(sex) ||
	    !cat[age][sex].contains(slot))
		return nullptr;
	for (const auto &entry : cat[age][sex][slot]) {
		if (entry.value("mesh", "") == mesh)
			return &entry;
	}
	return nullptr;
}

static void ensureMeshPoseAnimation(const Ogre::String &meshName)
{
	Ogre::MeshPtr mesh;
	try {
		mesh = Ogre::MeshManager::getSingleton().load(meshName,
							      "Characters");
	} catch (...) {
		return;
	}
	if (!mesh || mesh->getPoseCount() == 0)
		return;
	try {
		mesh->getAnimation("ShapeKeys");
	} catch (...) {
		Ogre::Animation *anim =
			mesh->createAnimation("ShapeKeys", 1.0f);
		Ogre::VertexAnimationTrack *track =
			anim->createVertexTrack(0, Ogre::VAT_POSE);
		Ogre::VertexPoseKeyFrame *kf =
			track->createVertexPoseKeyFrame(0.0f);
		for (size_t i = 0; i < mesh->getPoseCount(); ++i)
			kf->addPoseReference(static_cast<ushort>(i), 0.0f);
	}
}

/**
 * Helper: retrieve the character's registry record.
 * Returns nullptr if the entity is not a registered character.
 */
static const CharacterRegistry::CharacterRecord *
getCharacterRecord(flecs::entity e)
{
	if (!e.has<CharacterIdentityComponent>())
		return nullptr;
	auto &id = e.get<CharacterIdentityComponent>();
	return CharacterRegistry::getSingleton().findCharacter(id.registryId);
}

void CharacterSlotSystem::buildCharacter(flecs::entity e)
{
	destroyCharacterParts(e);

	const CharacterRegistry::CharacterRecord *rec = getCharacterRecord(e);
	if (!rec)
		return;

	const Ogre::String &age = rec->age;
	const Ogre::String &sex = rec->inlineSex;
	const auto &slotSelections = rec->inlineSlotSelections;
	const int outfitLevel = rec->inlineOutfitLevel;

	/* Make sure every catalog slot exists in the selection map. */
	auto slots = getSlots(age, sex);
	std::unordered_map<Ogre::String, SlotSelection> selections =
		slotSelections;
	for (const auto &slot : slots) {
		if (selections.find(slot) == selections.end())
			selections[slot] = SlotSelection();
	}

	if (!e.has<TransformComponent>())
		return;

	auto &transform = e.get_mut<TransformComponent>();
	if (!transform.node)
		return;

	/* Determine master slot (face preferred, else first non-empty) */
	Ogre::String masterSlot;
	if (selections.find("face") != selections.end()) {
		Ogre::String mesh = resolveMesh(age, sex, "face",
						selections["face"],
						outfitLevel);
		if (!mesh.empty())
			masterSlot = "face";
	}
	if (masterSlot.empty()) {
		for (const auto &pair : selections) {
			Ogre::String mesh = resolveMesh(age, sex, pair.first,
							pair.second,
							outfitLevel);
			if (!mesh.empty()) {
				masterSlot = pair.first;
				break;
			}
		}
	}

	if (masterSlot.empty())
		return;

	Ogre::String masterMesh = resolveMesh(age, sex, masterSlot,
					      selections[masterSlot],
					      outfitLevel);

	if (masterMesh.empty())
		return;

	/* Pre-create pose animation on mesh so entity knows about it */
	ensureMeshPoseAnimation(masterMesh);

	Ogre::Entity *masterEnt = nullptr;
	try {
		Ogre::MeshPtr meshPtr = Ogre::MeshManager::getSingleton().load(
			masterMesh, "Characters");
		masterEnt = m_sceneMgr->createEntity(meshPtr);
		transform.node->attachObject(masterEnt);
		m_entities[e.id()].parts[masterSlot] = masterEnt;
		m_entities[e.id()].masterEntity = masterEnt;

		/* Setup pose animation for shape keys */
		const nlohmann::json *entry =
			findCatalogEntry(age, sex, masterSlot, masterMesh);
		applyShapeKeys(e, masterEnt, entry);

		/* Re-prepare temp buffers after enabling vertex animation. */
		prepareEntityTempBlendBuffers(masterEnt);

		/* Notify AnimationTreeSystem that entity changed */
		if (e.has<AnimationTreeComponent>())
			e.get_mut<AnimationTreeComponent>().dirty = true;
	} catch (const Ogre::Exception &ex) {
		Ogre::LogManager::getSingleton().logMessage(
			"[CharacterSlotSystem] buildCharacter: FAILED to load master mesh '" +
			masterMesh + "': " + ex.getDescription());
		return;
	}

	for (const auto &pair : selections) {
		const Ogre::String &slot = pair.first;
		const SlotSelection &sel = pair.second;

		if (slot == masterSlot)
			continue;

		Ogre::String mesh =
			resolveMesh(age, sex, slot, sel, outfitLevel);
		if (mesh.empty())
			continue;

		try {
			ensureMeshPoseAnimation(mesh);
			Ogre::MeshPtr partMesh =
				Ogre::MeshManager::getSingleton().load(
					mesh, "Characters");
			Ogre::Entity *partEnt =
				m_sceneMgr->createEntity(partMesh);

			/* Check if this mesh has its own skeleton
			 * (e.g. hair exported with a hair-specific
			 * armature).  If so, attach to the master's
			 * Head bone instead of sharing skeletons. */
			const nlohmann::json *entry =
				findCatalogEntry(age, sex, slot, mesh);
			bool ownSkeleton = false;
			Ogre::String attachBone = "mixamorig:Head";
			if (entry) {
				ownSkeleton = entry->value(
					"own_skeleton", false);
				attachBone = entry->value(
					"attach_to_bone", attachBone);
			}

			if (ownSkeleton) {
				/* Hair with its own skeleton: attach
				 * to the master entity's Head bone
				 * so it follows head movement while
				 * being animated by its own skeleton.
				 */
				masterEnt->attachObjectToBone(
					attachBone, partEnt);
				Ogre::LogManager::getSingleton()
					.logMessage(
						"[CharacterSlotSystem] "
						"Attached hair '" +
						slot +
						"' with own skeleton to "
						"bone '" +
						attachBone + "'");
			} else {
				partEnt->shareSkeletonInstanceWith(
					masterEnt);
				transform.node->attachObject(partEnt);
			}
			m_entities[e.id()].parts[slot] = partEnt;

			applyShapeKeys(e, partEnt, entry);

			prepareEntityTempBlendBuffers(partEnt);

		} catch (const Ogre::Exception &ex) {
			Ogre::LogManager::getSingleton().logMessage(
				"[CharacterSlotSystem] buildCharacter: FAILED to load part '" +
				slot + "' mesh '" + mesh +
				"': " + ex.getDescription());
		}
	}
}

void CharacterSlotSystem::applyShapeKeys(flecs::entity e, Ogre::Entity *ent,
					 const nlohmann::json *entry)
{
	if (!ent || !entry)
		return;

	Ogre::MeshPtr mesh = ent->getMesh();
	if (!mesh || mesh->getPoseCount() == 0)
		return;

	/* Create a pose animation track if one doesn't exist */
	Ogre::Animation *anim = nullptr;
	try {
		anim = mesh->getAnimation("ShapeKeys");
	} catch (...) {
		anim = mesh->createAnimation("ShapeKeys", 1.0f);
	}

	/* Ensure the animation has at least one pose track.
	 * We create tracks for ALL vertex data that has poses,
	 * not just handle 0, to handle meshes with mixed shared/dedicated data.
	 */
	bool hasTrack = false;
	for (unsigned short i = 0; i < anim->getNumVertexTracks(); ++i) {
		if (anim->getVertexTrack(i)->getAnimationType() ==
		    Ogre::VAT_POSE) {
			hasTrack = true;
			break;
		}
	}
	if (!hasTrack) {
		Ogre::VertexAnimationTrack *track =
			anim->createVertexTrack(0, Ogre::VAT_POSE);
		Ogre::VertexPoseKeyFrame *kf =
			track->createVertexPoseKeyFrame(0.0f);
		for (size_t i = 0; i < mesh->getPoseCount(); ++i)
			kf->addPoseReference(static_cast<ushort>(i), 0.0f);
	}

	/* Ensure the entity has an animation state for ShapeKeys.
	 * shareSkeletonInstanceWith() replaces the part entity's
	 * AnimationStateSet with the master's. If the master doesn't
	 * have ShapeKeys, the part entity won't have it either.
	 * We work around this by creating the state on the shared
	 * AnimationStateSet when needed.
	 */
	Ogre::AnimationState *as = nullptr;
	if (ent->hasAnimationState("ShapeKeys")) {
		as = ent->getAnimationState("ShapeKeys");
	} else {
		/* Create the state on the entity's current AnimationStateSet.
		 * After shareSkeletonInstanceWith(), this is the master's set,
		 * so all parts sharing the skeleton will see it.
		 */
		Ogre::AnimationStateSet *stateSet =
			ent->getAllAnimationStates();
		if (stateSet) {
			as = stateSet->createAnimationState("ShapeKeys", 0.0,
							    1.0, 1.0, false);
		} else {
			Ogre::LogManager::getSingleton().logMessage(
				"[CharacterSlotSystem] applyShapeKeys: entity '" +
				ent->getName() + "' mesh '" + mesh->getName() +
				"' has no AnimationStateSet");
			return;
		}
	}
	as->setEnabled(true);
	as->setLoop(false);

	/* Build name -> pose index map from catalog.
	 *
	 * IMPORTANT: The catalog now skips "Basis" to match OGRE's pose indexing.
	 * OGRE's blender2ogre exporter skips the Basis shape key (index 0),
	 * so pose index 0 in OGRE = first non-Basis shape key in the catalog.
	 * This means catalog index i directly maps to OGRE pose index i.
	 */
	const auto &shapeKeys =
		entry->value("shape_keys", nlohmann::json::array());
	std::unordered_map<Ogre::String, size_t> nameToIndex;
	for (size_t i = 0; i < shapeKeys.size(); ++i)
		nameToIndex[shapeKeys[i].get<Ogre::String>()] = i;

	/* Apply weights from the character registry record */
	const CharacterRegistry::CharacterRecord *rec = getCharacterRecord(e);
	if (rec) {
		for (const auto &pair : rec->inlineShapeKeyWeights) {
			auto it = nameToIndex.find(pair.first);
			if (it == nameToIndex.end())
				continue;
			if (it->second >= mesh->getPoseCount())
				continue;
			/* Update the keyframe's pose reference influence
			 * on ALL pose tracks in the animation, not just track 0.
			 */
			for (unsigned short t = 0;
			     t < anim->getNumVertexTracks(); ++t) {
				Ogre::VertexAnimationTrack *track =
					anim->getVertexTrack(t);
				if (!track ||
				    track->getAnimationType() != Ogre::VAT_POSE)
					continue;
				Ogre::VertexPoseKeyFrame *kf =
					track->getVertexPoseKeyFrame(0);
				if (kf)
					kf->updatePoseReference(
						static_cast<ushort>(it->second),
						pair.second);
			}
		}
	}

	/* Force OGRE to update the entity with new pose weights */
	as->setTimePosition(0.0f);
}

void CharacterSlotSystem::markEntityDirty(flecs::entity_t id)
{
	m_dirtyEntities.insert(id);
}

void CharacterSlotSystem::destroyCharacterParts(flecs::entity e)
{
	auto it = m_entities.find(e.id());
	if (it == m_entities.end())
		return;

	for (auto &pair : it->second.parts) {
		Ogre::Entity *ent = pair.second;
		if (ent) {
			try {
				ent->detachFromParent();
				m_sceneMgr->destroyEntity(ent);
			} catch (...) {
			}
		}
	}

	it->second.parts.clear();
	it->second.masterEntity = nullptr;
	m_entities.erase(it);
}

Ogre::Entity *CharacterSlotSystem::getMasterEntity(flecs::entity e)
{
	auto it = m_entities.find(e.id());
	if (it == m_entities.end())
		return nullptr;
	return it->second.masterEntity;
}

Ogre::Entity *CharacterSlotSystem::getSlotEntity(flecs::entity e,
						 const Ogre::String &slot)
{
	auto it = m_entities.find(e.id());
	if (it == m_entities.end())
		return nullptr;
	auto pit = it->second.parts.find(slot);
	if (pit == it->second.parts.end())
		return nullptr;
	return pit->second;
}

void CharacterSlotSystem::setSlotVisible(flecs::entity e,
					 const Ogre::String &slot, bool visible)
{
	Ogre::Entity *ent = getSlotEntity(e, slot);
	if (ent) {
		ent->setVisible(visible);
	}
}
