#include "AnimationTreeRegistry.hpp"
#include <OgreLogManager.h>
#include <OgreMeshManager.h>
#include <OgreResourceGroupManager.h>
#include <OgreSkeleton.h>
#include <OgreAnimation.h>
#include <fstream>
#include <filesystem>

AnimationTreeRegistry *AnimationTreeRegistry::ms_singleton = nullptr;

AnimationTreeRegistry &AnimationTreeRegistry::getSingleton()
{
	static AnimationTreeRegistry instance;
	return instance;
}

AnimationTreeRegistry *AnimationTreeRegistry::getSingletonPtr()
{
	return &getSingleton();
}

AnimationTreeRegistry::AnimationTreeRegistry()
	: m_autoSavePath(defaultPath())
{
	ms_singleton = this;
}

void AnimationTreeRegistry::initialize()
{
	if (!std::filesystem::exists(m_autoSavePath))
		return;
	if (!loadFromFile(m_autoSavePath)) {
		Ogre::LogManager::getSingleton().logMessage(
			"AnimationTreeRegistry: auto-load failed: " +
			m_lastError);
	}
}

bool AnimationTreeRegistry::registerTree(const AnimationTreeDefinition &def)
{
	bool wasNew = m_trees.find(def.name) == m_trees.end();
	m_trees[def.name] = def;
	if (wasNew)
		Ogre::LogManager::getSingleton().logMessage(
			"AnimationTreeRegistry: registered tree '" + def.name +
			"'");
	autoSave();
	return wasNew;
}

bool AnimationTreeRegistry::removeTree(const Ogre::String &name)
{
	if (m_trees.erase(name) == 0)
		return false;
	autoSave();
	return true;
}

AnimationTreeRegistry::AnimationTreeDefinition *
AnimationTreeRegistry::findTree(const Ogre::String &name)
{
	auto it = m_trees.find(name);
	if (it != m_trees.end())
		return &it->second;
	return nullptr;
}

const AnimationTreeRegistry::AnimationTreeDefinition *
AnimationTreeRegistry::findTree(const Ogre::String &name) const
{
	auto it = m_trees.find(name);
	if (it != m_trees.end())
		return &it->second;
	return nullptr;
}

std::vector<Ogre::String> AnimationTreeRegistry::getTreeNames() const
{
	std::vector<Ogre::String> names;
	names.reserve(m_trees.size());
	for (const auto &pair : m_trees)
		names.push_back(pair.first);
	return names;
}

Ogre::String AnimationTreeRegistry::generateUniqueName(const Ogre::String &base)
	const
{
	if (m_trees.find(base) == m_trees.end())
		return base;
	for (int i = 1; i < 10000; ++i) {
		Ogre::String candidate = base + "_" + Ogre::StringConverter::toString(i);
		if (m_trees.find(candidate) == m_trees.end())
			return candidate;
	}
	return base + "_" + Ogre::StringConverter::toString(
				   (unsigned long long)this);
}

void AnimationTreeRegistry::markModified(const Ogre::String &name)
{
	auto it = m_trees.find(name);
	if (it != m_trees.end()) {
		++it->second.version;
		autoSave();
	}
}

std::vector<AnimationTreeRegistry::SkeletonSource>
AnimationTreeRegistry::getAvailableSkeletonSources() const
{
	std::vector<SkeletonSource> result;
	Ogre::ResourceGroupManager &rgm =
		Ogre::ResourceGroupManager::getSingleton();
	try {
		Ogre::StringVectorPtr names =
			rgm.findResourceNames("Characters", "*.mesh");
		if (!names)
			return result;

		std::unordered_map<Ogre::String, Ogre::String> seen;
		for (const auto &meshName : *names) {
			Ogre::String skelName =
				getSkeletonNameForMesh(meshName);
			if (skelName.empty())
				continue;
			if (seen.find(skelName) != seen.end())
				continue;
			seen[skelName] = meshName;
			result.push_back({ skelName, meshName });
		}
	} catch (...) {
	}
	std::sort(result.begin(), result.end(),
		  [](const SkeletonSource &a, const SkeletonSource &b) {
			  return a.skeletonName < b.skeletonName;
		  });
	return result;
}

Ogre::String AnimationTreeRegistry::getSkeletonNameForMesh(
	const Ogre::String &meshName) const
{
	try {
		Ogre::MeshPtr mesh = Ogre::MeshManager::getSingleton().load(
			meshName, "Characters");
		if (!mesh || !mesh->hasSkeleton())
			return Ogre::String();
		return mesh->getSkeletonName();
	} catch (const std::exception &e) {
		Ogre::LogManager::getSingleton().logMessage(
			"AnimationTreeRegistry: failed to read skeleton for '" +
			meshName + "': " + e.what());
	}
	return Ogre::String();
}

std::vector<Ogre::String>
AnimationTreeRegistry::getAnimationsForSource(const Ogre::String &source) const
{
	std::vector<Ogre::String> result;
	if (source.empty())
		return result;

	try {
		Ogre::MeshPtr mesh = Ogre::MeshManager::getSingleton().load(
			source, "Characters");
		if (!mesh || !mesh->getSkeleton())
			return result;

		Ogre::SkeletonPtr skel = mesh->getSkeleton();
		unsigned short numAnims = skel->getNumAnimations();
		for (unsigned short i = 0; i < numAnims; ++i) {
			Ogre::Animation *anim = skel->getAnimation(i);
			if (anim)
				result.push_back(anim->getName());
		}
	} catch (const std::exception &e) {
		Ogre::LogManager::getSingleton().logMessage(
			"AnimationTreeRegistry: failed to enumerate animations for '" +
			source + "': " + e.what());
	}

	std::sort(result.begin(), result.end());
	return result;
}

nlohmann::json AnimationTreeRegistry::serialize() const
{
	nlohmann::json j;
	j["version"] = "1.0";
	j["trees"] = nlohmann::json::array();
	for (const auto &pair : m_trees) {
		const AnimationTreeDefinition &def = pair.second;
		nlohmann::json treeJson;
		treeJson["name"] = def.name;
		treeJson["skeletonSource"] = def.skeletonSource;
		treeJson["version"] = def.version;
		treeJson["root"] = animationTreeNodeToJson(def.root);
		j["trees"].push_back(treeJson);
	}
	return j;
}

void AnimationTreeRegistry::deserialize(const nlohmann::json &j)
{
	m_trees.clear();
	if (!j.contains("trees") || !j["trees"].is_array())
		return;
	for (const auto &treeJson : j["trees"]) {
		if (!treeJson.contains("name"))
			continue;
		AnimationTreeDefinition def;
		def.name = treeJson.value("name", "");
		def.skeletonSource = treeJson.value("skeletonSource", "");
		def.version = treeJson.value("version", 1);
		if (treeJson.contains("root"))
			animationTreeNodeFromJson(def.root, treeJson["root"]);
		if (!def.name.empty())
			m_trees[def.name] = def;
	}
}

bool AnimationTreeRegistry::saveToFile(const std::string &filepath)
{
	try {
		std::ofstream file(filepath);
		if (!file.is_open()) {
			m_lastError = "Failed to open file for writing: " + filepath;
			return false;
		}
		file << serialize().dump(4);
		file.close();
		return true;
	} catch (const std::exception &e) {
		m_lastError = std::string("Save error: ") + e.what();
		return false;
	}
}

bool AnimationTreeRegistry::loadFromFile(const std::string &filepath)
{
	try {
		if (!std::filesystem::exists(filepath)) {
			m_lastError = "File not found: " + filepath;
			return false;
		}
		std::ifstream file(filepath);
		if (!file.is_open()) {
			m_lastError = "Failed to open file for reading: " + filepath;
			return false;
		}
		nlohmann::json j;
		file >> j;
		file.close();
		deserialize(j);
		return true;
	} catch (const std::exception &e) {
		m_lastError = std::string("Load error: ") + e.what();
		return false;
	}
}

void AnimationTreeRegistry::autoSave()
{
	if (!m_autoSavePath.empty())
		saveToFile(m_autoSavePath);
}
