#include "GameObjectLoader.h"
#include <fstream>
#include <iostream>
#include <cmath>
#include "render/Renderer.h"
#include "render/TextureManager.h"
#include "utils/Utils.h"
#include <Eigen/Geometry>
#include "../Character.h"

namespace ZL {

	void set_Texture(Character& npc, const TextureDataStruct& texture);
	void set_Texture(Character& npc, const std::string& meshName, const TextureDataStruct& texture);

	using json = nlohmann::json;

	// -----------------------------------------------------------------------
	// Private helpers
	// -----------------------------------------------------------------------

	json GameObjectLoader::loadJson(const std::string& jsonPath, const std::string& zipPath)
	{
		std::string content;
		try {
			if (zipPath.empty()) {
				content = readTextFile(jsonPath);
			} else {
				auto buf = readFileFromZIP(jsonPath, zipPath);
				if (buf.empty())
					throw std::runtime_error("empty result from zip");
				content.assign(buf.begin(), buf.end());
			}
		} catch (const std::exception& e) {
			throw std::runtime_error("Failed to read " + jsonPath + ": " + e.what());
		}

		try {
			return json::parse(content);
		} catch (const std::exception& e) {
			throw std::runtime_error("JSON parse error in " + jsonPath + ": " + e.what());
		}
	}

	static GameObjectData parseGameObjectData(const json& item)
	{
		GameObjectData data;
		data.name                = item.value("name", "Unknown");
		data.texturePath         = item.value("texturePath", "");
		data.textureDarkandsPath = item.value("textureDarkandsPath", "");
		data.meshPath            = item.value("meshPath", "");
		data.rotationX   = item.value("rotationX", 0.0f);
		data.rotationY   = item.value("rotationY", 0.0f);
		data.rotationZ   = item.value("rotationZ", 0.0f);
		data.positionX   = item.value("positionX", 0.0f);
		data.positionY   = item.value("positionY", 0.0f);
		data.positionZ   = item.value("positionZ", 0.0f);
		data.scale       = item.value("scale", 1.0f);
		return data;
	}

	std::vector<GameObjectData> GameObjectLoader::loadFromJson(const std::string& jsonPath, const std::string& zipPath)
	{
		std::vector<GameObjectData> objects;
		json j = loadJson(jsonPath, zipPath);

		if (!j.contains("objects") || !j["objects"].is_array()) {
			std::cerr << "Warning: 'objects' array not found in " << jsonPath << std::endl;
			return objects;
		}

		for (const auto& item : j["objects"]) {
			GameObjectData data = parseGameObjectData(item);
			if (!data.meshPath.empty())
				objects.push_back(std::move(data));
		}

		std::cout << "Loaded " << objects.size() << " static objects from " << jsonPath << std::endl;
		return objects;
	}

	std::vector<InteractiveObjectData> GameObjectLoader::loadInteractiveFromJson(const std::string& jsonPath, const std::string& zipPath)
	{
		std::vector<InteractiveObjectData> objects;
		json j = loadJson(jsonPath, zipPath);

		if (!j.contains("objects") || !j["objects"].is_array()) {
			std::cerr << "Warning: 'objects' array not found in " << jsonPath << std::endl;
			return objects;
		}

		for (const auto& item : j["objects"]) {
			InteractiveObjectData data;
			data.base                = parseGameObjectData(item);
			data.interactionRadius   = item.value("interactionRadius", 2.0f);
			data.activateFunctionName = item.value("activateFunction", "");
			data.pivotX              = item.value("pivotX", 0.0f);
			data.pivotY              = item.value("pivotY", 0.0f);
			data.pivotZ              = item.value("pivotZ", 0.0f);
			if (!data.base.meshPath.empty())
				objects.push_back(std::move(data));
		}

		std::cout << "Loaded " << objects.size() << " interactive objects from " << jsonPath << std::endl;
		return objects;
	}

	LoadedGameObject GameObjectLoader::buildLoadedObject(const GameObjectData& data, Renderer& renderer, const std::string& zipPath)
	{
		LoadedGameObject obj;
		obj.name = data.name;

		obj.texture = renderer.textureManager.LoadFromPng(data.texturePath, zipPath);
		if (!data.textureDarkandsPath.empty())
			obj.textureDarklands = renderer.textureManager.LoadFromPng(data.textureDarkandsPath, zipPath);

		if (data.meshPath.size() > 4 && data.meshPath.compare(data.meshPath.size() - 4, 4, ".bin") == 0)
			obj.mesh.data = LoadModelFromBinFile(data.meshPath, zipPath);
		else
			obj.mesh.data = LoadFromTextFile02(data.meshPath, zipPath);

		constexpr float kDeg2Rad = static_cast<float>(M_PI) / 180.0f;
		Eigen::Quaternionf rot = Eigen::Quaternionf::Identity();
		if (data.rotationX != 0.0f)
			rot = Eigen::Quaternionf(Eigen::AngleAxisf(data.rotationX * kDeg2Rad, Eigen::Vector3f::UnitX())) * rot;
		if (data.rotationY != 0.0f)
			rot = Eigen::Quaternionf(Eigen::AngleAxisf(data.rotationY * kDeg2Rad, Eigen::Vector3f::UnitY())) * rot;
		if (data.rotationZ != 0.0f)
			rot = Eigen::Quaternionf(Eigen::AngleAxisf(data.rotationZ * kDeg2Rad, Eigen::Vector3f::UnitZ())) * rot;
		obj.mesh.data.RotateByMatrix(rot.toRotationMatrix());

		if (data.scale != 1.0f)
			obj.mesh.data.Scale(data.scale);

		return obj;
	}

	// -----------------------------------------------------------------------
	// Public API
	// -----------------------------------------------------------------------

	std::unordered_map<std::string, LoadedGameObject> GameObjectLoader::loadAndCreateGameObjects(
		const std::string& jsonPath,
		Renderer& renderer,
		const std::string& zipPath)
	{
		std::unordered_map<std::string, LoadedGameObject> gameObjects;

		for (const auto& data : loadFromJson(jsonPath, zipPath)) {
			std::cout << "Loading game object: " << data.name << std::endl;
			try {
				LoadedGameObject obj = buildLoadedObject(data, renderer, zipPath);

				if (data.positionX != 0.0f || data.positionY != 0.0f || data.positionZ != 0.0f)
					obj.mesh.data.Move({ data.positionX, data.positionY, data.positionZ });

				obj.mesh.RefreshVBO();
				gameObjects[data.name] = std::move(obj);
				std::cout << "Successfully loaded: " << data.name << std::endl;
			} catch (const std::exception& e) {
				std::cerr << "GameObjectLoader: Failed to load '" << data.name << "': " << e.what() << std::endl;
			}
		}

		std::cout << "Total game objects loaded: " << gameObjects.size() << std::endl;
		return gameObjects;
	}

	std::vector<InteractiveObject> GameObjectLoader::loadAndCreateInteractiveObjects(
		const std::string& jsonPath,
		Renderer& renderer,
		const std::string& zipPath)
	{
		std::vector<InteractiveObject> interactiveObjects;

		if (jsonPath.empty())
			return interactiveObjects;

		for (const auto& data : loadInteractiveFromJson(jsonPath, zipPath)) {
			std::cout << "Loading interactive object: " << data.base.name << std::endl;
			try {
				InteractiveObject intObj;
				intObj.loadedObject       = buildLoadedObject(data.base, renderer, zipPath);
				intObj.interactionRadius  = data.interactionRadius;
				intObj.activateFunctionName = data.activateFunctionName;
				intObj.pivot = Eigen::Vector3f(data.pivotX, data.pivotY, data.pivotZ);

				intObj.loadedObject.mesh.RefreshVBO();

				// Center mesh at origin; store corrected world position separately.
				if (!intObj.loadedObject.mesh.data.PositionData.empty()) {
					Eigen::Vector3f meshMin = intObj.loadedObject.mesh.data.PositionData[0];
					Eigen::Vector3f meshMax = intObj.loadedObject.mesh.data.PositionData[0];
					for (const auto& v : intObj.loadedObject.mesh.data.PositionData) {
						meshMin = meshMin.cwiseMin(v);
						meshMax = meshMax.cwiseMax(v);
					}
					Eigen::Vector3f meshCenter = (meshMin + meshMax) * 0.5f;

					intObj.loadedObject.mesh.data.Move(-meshCenter);
					intObj.loadedObject.mesh.RefreshVBO();

					intObj.position = Eigen::Vector3f(data.base.positionX, data.base.positionY, data.base.positionZ) + meshCenter;
				} else {
					intObj.position = Eigen::Vector3f(data.base.positionX, data.base.positionY, data.base.positionZ);
				}

				interactiveObjects.push_back(std::move(intObj));

				if (!data.activateFunctionName.empty())
					std::cout << "Successfully loaded interactive: " << data.base.name
					          << " (function: " << data.activateFunctionName << ")" << std::endl;
				else
					std::cout << "Successfully loaded interactive: " << data.base.name << std::endl;

			} catch (const std::exception& e) {
				std::cerr << "GameObjectLoader: Failed to load interactive '" << data.base.name << "': " << e.what() << std::endl;
			}
		}

		std::cout << "Total interactive objects loaded: " << interactiveObjects.size() << std::endl;
		return interactiveObjects;
	}

	std::vector<NpcData> GameObjectLoader::loadNpcsFromJson(const std::string& jsonPath, const std::string& zipPath)
	{
		std::vector<NpcData> npcs;
		json j = loadJson(jsonPath, zipPath);

		if (!j.contains("npcs") || !j["npcs"].is_array()) {
			std::cerr << "Warning: 'npcs' array not found in " << jsonPath << std::endl;
			return npcs;
		}

		try {
			for (const auto& item : j["npcs"]) {
				NpcData data;

				data.id   = item.value("id", "npc_unknown");
				data.name = item.value("name", "Unknown NPC");
				data.texturePath = item.value("texturePath", "");
				if (item.contains("meshTextures") && item["meshTextures"].is_object()) {
					for (auto it = item["meshTextures"].begin(); it != item["meshTextures"].end(); ++it) {
						if (it.value().is_string())
							data.meshTextures[it.key()] = it.value().get<std::string>();
					}
				}
				data.interactionRadius       = item.value("interactionRadius", 0.0f);
				data.animationIdlePath       = item.value("animationIdlePath", "");
				data.animationWalkPath       = item.value("animationWalkPath", "");
				data.animationActionIdlePath = item.value("animationActionIdlePath", "");
				data.animationActionAttackPath  = item.value("animationActionAttackPath", "");
				data.animationStandToActionPath = item.value("animationStandToActionPath", "");
				data.animationActionToStandPath = item.value("animationActionToStandPath", "");
				data.animationActionToDeathPath = item.value("animationActionToDeathPath", "");
				data.animationDeathIdlePath     = item.value("animationDeathIdlePath", "");
				data.positionX      = item.value("positionX", 0.0f);
				data.positionY      = item.value("positionY", 0.0f);
				data.positionZ      = item.value("positionZ", 0.0f);
				data.walkSpeed      = item.value("walkSpeed", 1.5f);
				data.rotationSpeed  = item.value("rotationSpeed", 8.0f);
				data.modelScale     = item.value("modelScale", 0.01f);
				data.modelCorrectionRotX = item.value("modelCorrectionRotX", 0.0f) * static_cast<float>(M_PI) / 180.0f;
				data.modelCorrectionRotY = item.value("modelCorrectionRotY", 0.0f) * static_cast<float>(M_PI) / 180.0f;
				data.modelCorrectionRotZ = item.value("modelCorrectionRotZ", 0.0f) * static_cast<float>(M_PI) / 180.0f;
				data.facingAngle    = item.value("facingAngle", 0.0f) * static_cast<float>(M_PI) / 180.0f;
				data.hp             = item.value("hp", 100.0f);
				data.canAttack      = item.value("canAttack", false);
				data.enabled        = item.value("enabled", true);

				npcs.push_back(std::move(data));
			}
			std::cout << "Successfully loaded " << npcs.size() << " NPCs from " << jsonPath << std::endl;
		} catch (const std::exception& e) {
			std::cerr << "Error loading NPCs from JSON: " << e.what() << std::endl;
		}

		return npcs;
	}

	std::vector<std::unique_ptr<Character>> GameObjectLoader::loadAndCreate_Npcs(
		const std::string& jsonPath,
		const std::string& zipPath)
	{
		std::vector<std::unique_ptr<Character>> npcs;

		for (const auto& npcData : loadNpcsFromJson(jsonPath, zipPath)) {
			std::cout << "Loading NPC: " << npcData.name << std::endl;

			auto npc = std::make_unique<Character>();

			try {
				if (npcData.animationIdlePath.substr(npcData.animationIdlePath.size() - 5) == ".anim")
					npc->loadBinaryAnimation(AnimationState::STAND, npcData.animationIdlePath, zipPath);
				else
					npc->loadAnimation(AnimationState::STAND, npcData.animationIdlePath, zipPath);
				std::cout << "  Loaded IDLE animation: " << npcData.animationIdlePath << std::endl;
			} catch (const std::exception& e) {
				std::cerr << "GameObjectLoader: Failed to load IDLE animation for '" << npcData.name << "': " << e.what() << std::endl;
				continue;
			}

			try {
				if (npcData.animationWalkPath.substr(npcData.animationWalkPath.size() - 5) == ".anim")
					npc->loadBinaryAnimation(AnimationState::WALK, npcData.animationWalkPath, zipPath);
				else
					npc->loadAnimation(AnimationState::WALK, npcData.animationWalkPath, zipPath);
				std::cout << "  Loaded WALK animation: " << npcData.animationWalkPath << std::endl;
			} catch (const std::exception& e) {
				std::cerr << "GameObjectLoader: Failed to load WALK animation for '" << npcData.name << "': " << e.what() << std::endl;
				continue;
			}

			auto loadOptionalAnim = [&](AnimationState state, const std::string& path, const char* label) {
				if (path.empty()) return;
				try {
					if (path.size() >= 5 && path.substr(path.size() - 5) == ".anim")
						npc->loadBinaryAnimation(state, path, zipPath);
					else
						npc->loadAnimation(state, path, zipPath);
					std::cout << "  Loaded " << label << " animation: " << path << std::endl;
				} catch (const std::exception& e) {
					std::cerr << "GameObjectLoader: Failed to load " << label
					          << " animation for '" << npcData.name << "': " << e.what() << std::endl;
				}
			};
			loadOptionalAnim(AnimationState::ACTION_IDLE,     npcData.animationActionIdlePath,     "ACTION_IDLE");
			loadOptionalAnim(AnimationState::ACTION_ATTACK,   npcData.animationActionAttackPath,   "ACTION_ATTACK");
			loadOptionalAnim(AnimationState::STAND_TO_ACTION, npcData.animationStandToActionPath,  "STAND_TO_ACTION");
			loadOptionalAnim(AnimationState::ACTION_TO_STAND, npcData.animationActionToStandPath,  "ACTION_TO_STAND");
			loadOptionalAnim(AnimationState::ACTION_TO_DEATH, npcData.animationActionToDeathPath,  "ACTION_TO_DEATH");
			loadOptionalAnim(AnimationState::DEATH_IDLE,      npcData.animationDeathIdlePath,      "DEATH_IDLE");

			try {
				if (!npcData.meshTextures.empty()) {
					for (const auto& entry : npcData.meshTextures) {
						set_Texture(*npc, entry.first, CreateTextureDataFromPng(entry.second, zipPath.c_str()));
						std::cout << "    -> mesh '" << entry.first << "'" << std::endl;
					}
				} else {
					set_Texture(*npc, CreateTextureDataFromPng(npcData.texturePath, zipPath.c_str()));
					std::cout << "  Loaded texture: " << npcData.texturePath << std::endl;
				}
			} catch (const std::exception& e) {
				std::cerr << "GameObjectLoader: Failed to load texture for '" << npcData.name << "': " << e.what() << std::endl;
				continue;
			}

			npc->walkSpeed        = npcData.walkSpeed;
			npc->rotationSpeed    = npcData.rotationSpeed;
			npc->modelScale       = npcData.modelScale;
			npc->position         = Eigen::Vector3f(npcData.positionX, npcData.positionY, npcData.positionZ);
			npc->facingAngle      = npcData.facingAngle;
			npc->hp               = npcData.hp;
			npc->initialHp        = npcData.hp;
			npc->canAttack        = npcData.canAttack;
			npc->enabled          = npcData.enabled;
			npc->interactionRadius = npcData.interactionRadius;
			npc->npcId            = npcData.id;
			npc->npcName          = npcData.name;

			Eigen::Quaternionf corrRot = Eigen::Quaternionf::Identity();
			if (npcData.modelCorrectionRotX != 0.0f)
				corrRot = Eigen::Quaternionf(Eigen::AngleAxisf(npcData.modelCorrectionRotX, Eigen::Vector3f::UnitX())) * corrRot;
			if (npcData.modelCorrectionRotY != 0.0f)
				corrRot = Eigen::Quaternionf(Eigen::AngleAxisf(npcData.modelCorrectionRotY, Eigen::Vector3f::UnitY())) * corrRot;
			if (npcData.modelCorrectionRotZ != 0.0f)
				corrRot = Eigen::Quaternionf(Eigen::AngleAxisf(npcData.modelCorrectionRotZ, Eigen::Vector3f::UnitZ())) * corrRot;
			npc->modelCorrectionRotation = corrRot;

			npc->setTarget(npc->position);
			npcs.push_back(std::move(npc));

			std::cout << "Successfully loaded NPC: " << npcData.name << " at ("
			          << npcData.positionX << ", " << npcData.positionY << ", " << npcData.positionZ << ")" << std::endl;
		}

		std::cout << "Total NPCs loaded: " << npcs.size() << std::endl;
		return npcs;
	}

} // namespace ZL
