#include "Game.h"
#include "AnimatedModel.h"
#include "BoneAnimatedModel.h"
#include "planet/PlanetData.h"
#include "utils/Utils.h"
#include "render/OpenGlExtensions.h"
#include <iostream>
#include "render/TextureManager.h"
#include "TextModel.h"
#include <random>
#include <cmath>
#include <algorithm>
#include <functional>
#ifdef __ANDROID__
#include <android/log.h>
#endif



#ifdef EMSCRIPTEN
#include <emscripten.h>
#endif

#include "GameConstants.h"

namespace ZL
{
#ifdef EMSCRIPTEN
	const char* CONST_ZIP_FILE = "resources.zip";
#else
	const char* CONST_ZIP_FILE = "";
#endif

	float x = 0;
	float y = 0;
	float z = 0;

#ifdef EMSCRIPTEN
	Game* Game::s_instance = nullptr;

	void Game::onResourcesZipLoaded(const char* /*filename*/) {
		std::cout << "Resources.zip loaded successfully" << std::endl;
		if (s_instance) {
			s_instance->mainThreadHandler.EnqueueMainThreadTask([&]() {
				s_instance->setupPart2();
			});
		}
	}

	void Game::onResourcesZipError(const char* /*filename*/) {
		std::cout << "Failed to download resources.zip" << std::endl;
	}
#endif

	Game::Game()
		: newTickCount(0)
		, lastTickCount(0)
		, menuManager(renderer)
	{
	}

	Game::~Game() {
#ifndef EMSCRIPTEN
		// In Emscripten, SDL must stay alive across context loss/restore cycles
		// so the window remains valid when the game object is re-created.
		SDL_Quit();
#endif
	}

	void Game::setup() {
		Environment::width = Environment::CONST_DEFAULT_WIDTH;
		Environment::height = Environment::CONST_DEFAULT_HEIGHT;
		Environment::computeProjectionDimensions();

		ZL::BindOpenGlFunctions();
		ZL::CheckGlError();
		renderer.InitOpenGL();
		
#ifdef EMSCRIPTEN
		// These shaders and loading.png are preloaded separately (not from zip),
		// so they are available immediately without waiting for resources.zip.
		renderer.shaderManager.AddShaderFromFiles("defaultColor", "resources/shaders/defaultColor.vertex", "resources/shaders/defaultColor_web.fragment", "");
		renderer.shaderManager.AddShaderFromFiles("default", "resources/shaders/default.vertex", "resources/shaders/default_web.fragment", "");
#else
		renderer.shaderManager.AddShaderFromFiles("defaultColor", "resources/shaders/defaultColor.vertex", "resources/shaders/defaultColor_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("default", "resources/shaders/default.vertex", "resources/shaders/default_desktop.fragment", CONST_ZIP_FILE);
#endif
		loadingTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/loading.png", ""));

		float minDimension;
		float width = Environment::projectionWidth;
		float height = Environment::projectionHeight;

		if (width >= height)
		{
			minDimension = height;
		}
		else
		{
			minDimension = width;
		}

		loadingMesh.data = CreateRect2D({ 0.0f, 0.0f }, { minDimension*0.5f, minDimension*0.5f }, 3);
		loadingMesh.RefreshVBO();
		
#ifdef EMSCRIPTEN
		// Asynchronously download resources.zip; setupPart2() is called on completion.
		// The loading screen stays visible until the download finishes.
		s_instance = this;
		std::cout << "Load resurces step 1" << std::endl;
		emscripten_async_wget("resources.zip", "resources.zip", onResourcesZipLoaded, onResourcesZipError);
#else
		mainThreadHandler.EnqueueMainThreadTask([this]() {
			std::cout << "Load resurces step 2" << std::endl;
			this->setupPart2();
			std::cout << "Load resurces step 3" << std::endl;
		});
#endif

	}


	void Game::setupPart2()
	{

#ifdef EMSCRIPTEN
		renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/env_sky.vertex", "resources/shaders/env_sky_web.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("defaultAtmosphere", "resources/shaders/defaultAtmosphere.vertex", "resources/shaders/defaultAtmosphere_web.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("planetBake", "resources/shaders/planet_bake.vertex", "resources/shaders/planet_bake_web.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("planetStone", "resources/shaders/planet_stone.vertex", "resources/shaders/planet_stone_web.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/planet_land.vertex", "resources/shaders/planet_land_web.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("spark", "resources/shaders/spark.vertex", "resources/shaders/spark_web.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("skinning", "resources/shaders/skinning.vertex", "resources/shaders/default_web.fragment", CONST_ZIP_FILE);

#else
		renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/env_sky.vertex", "resources/shaders/env_sky_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("defaultAtmosphere", "resources/shaders/defaultAtmosphere.vertex", "resources/shaders/defaultAtmosphere_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("planetBake", "resources/shaders/planet_bake.vertex", "resources/shaders/planet_bake_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("planetStone", "resources/shaders/planet_stone.vertex", "resources/shaders/planet_stone_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/planet_land.vertex", "resources/shaders/planet_land_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("spark", "resources/shaders/spark.vertex", "resources/shaders/spark_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("skinning", "resources/shaders/skinning.vertex", "resources/shaders/default_desktop.fragment", CONST_ZIP_FILE);
#endif

		std::cout << "Load resurces step 4" << std::endl;

		roomTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/w/room005.png", CONST_ZIP_FILE));
		roomMesh.data = LoadFromTextFile02("resources/w/room001.txt", CONST_ZIP_FILE);
		roomMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI*0.5, Eigen::Vector3f::UnitY())).toRotationMatrix());
		roomMesh.RefreshVBO();
		std::cout << "Load resurces step 5" << std::endl;
		
		// Load static game objects
		gameObjects = GameObjectLoader::loadAndCreateGameObjects("resources/config2/gameobjects.json", renderer, CONST_ZIP_FILE);
		
		// Load interactive objects
		interactiveObjects = GameObjectLoader::loadAndCreateInteractiveObjects("resources/config2/gameobjects.json", renderer, CONST_ZIP_FILE);

		auto violaTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/w/gg/IMG_20260413_182354_992.png", CONST_ZIP_FILE));

		// Player (Viola)
		player = std::make_unique<Character>();
		/*
		player->loadAnimation(AnimationState::STAND, "resources/w/gg/gg_stand_idle001.txt", CONST_ZIP_FILE);
		player->loadAnimation(AnimationState::WALK, "resources/w/gg/gg_walking001.txt", CONST_ZIP_FILE);
		player->loadAnimation(AnimationState::STAND_TO_ACTION, "resources/w/gg/gg_stand_to_action002.txt", CONST_ZIP_FILE);
		player->loadAnimation(AnimationState::ACTION_ATTACK, "resources/w/gg/gg_action_attack001.txt", CONST_ZIP_FILE);
		player->loadAnimation(AnimationState::ACTION_IDLE, "resources/w/gg/gg_action_idle001.txt", CONST_ZIP_FILE);
		player->loadAnimation(AnimationState::ACTION_TO_STAND, "resources/w/gg/gg_action_to_stand001.txt", CONST_ZIP_FILE);
		*/
		player->loadBinaryAnimation(AnimationState::STAND, "resources/w/gg/gg_stand_idle001.anim", CONST_ZIP_FILE);
		player->loadBinaryAnimation(AnimationState::WALK, "resources/w/gg/gg_walking001.anim", CONST_ZIP_FILE);
		player->loadBinaryAnimation(AnimationState::STAND_TO_ACTION, "resources/w/gg/gg_stand_to_action002.anim", CONST_ZIP_FILE);
		player->loadBinaryAnimation(AnimationState::ACTION_ATTACK, "resources/w/gg/gg_action_attack001.anim", CONST_ZIP_FILE);
		player->loadBinaryAnimation(AnimationState::ACTION_IDLE, "resources/w/gg/gg_action_idle001.anim", CONST_ZIP_FILE);
		player->loadBinaryAnimation(AnimationState::ACTION_TO_STAND, "resources/w/gg/gg_action_to_stand001.anim", CONST_ZIP_FILE);

		player->setTexture(violaTexture);
		player->walkSpeed = 3.0f;
		player->rotationSpeed = 8.0f;

		player->modelScale = 1.f;
		player->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY()));
			//Eigen::Quaternionf::Identity();
			/*Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI * 0.5f, Eigen::Vector3f::UnitX())) * */
			/*Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitZ()));*/

		player->canAttack = true;
		player->isPlayer = true;
		std::cout << "Load resurces step 9" << std::endl;

		// Load NPCs from JSON
		npcs = GameObjectLoader::loadAndCreateNpcs("resources/config2/npcs.json", CONST_ZIP_FILE);

		/*
		
		auto defaultTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/w/default_skin001.png", CONST_ZIP_FILE));

		auto npc01 = std::make_unique<Character>();
		npc01->loadAnimation(AnimationState::STAND, "resources/w/default_idle002.txt", CONST_ZIP_FILE);
		npc01->loadAnimation(AnimationState::WALK, "resources/w/default_walk001.txt", CONST_ZIP_FILE);
		//npc01->loadAnimation(AnimationState::STAND, "resources/idleviola_uv010.txt", CONST_ZIP_FILE);
		//npc01->loadAnimation(AnimationState::WALK, "resources/walkviola_uv010.txt", CONST_ZIP_FILE);
		npc01->setTexture(defaultTexture);
		npc01->walkSpeed = 1.5f;
		npc01->rotationSpeed = 8.0f;
		npc01->modelScale = 0.01f;
		//npc01->modelScale = 0.1f;
		npc01->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY()));

		std::cout << "Load resurces step 10" << std::endl;
		npc01->position = Eigen::Vector3f(0.f, 0.f, -10.f);
		npc01->setTarget(npc01->position);
		npcs.push_back(std::move(npc01));

		*/

		auto ghostTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/w/ghost_skin001.png", CONST_ZIP_FILE));

		std::cout << "Load resurces step 11" << std::endl;
		auto npc02 = std::make_unique<Character>();
		/*
		npc02->loadAnimation(AnimationState::STAND, "resources/w/default_float001.txt", CONST_ZIP_FILE);
		npc02->loadAnimation(AnimationState::WALK, "resources/w/default_float001.txt", CONST_ZIP_FILE);
		npc02->loadAnimation(AnimationState::ACTION_IDLE, "resources/w/float_attack003_cut.txt", CONST_ZIP_FILE);
		npc02->loadAnimation(AnimationState::ACTION_ATTACK, "resources/w/float_attack003.txt", CONST_ZIP_FILE);
		npc02->loadAnimation(AnimationState::STAND_TO_ACTION, "resources/w/default_float001_cut.txt", CONST_ZIP_FILE);
		npc02->loadAnimation(AnimationState::ACTION_TO_STAND, "resources/w/default_float001_cut.txt", CONST_ZIP_FILE);
		*/
		npc02->loadBinaryAnimation(AnimationState::STAND, "resources/w/default_float001.anim", CONST_ZIP_FILE);
		npc02->loadBinaryAnimation(AnimationState::WALK, "resources/w/default_float001.anim", CONST_ZIP_FILE);
		npc02->loadBinaryAnimation(AnimationState::ACTION_IDLE, "resources/w/float_attack003_cut.anim", CONST_ZIP_FILE);
		npc02->loadBinaryAnimation(AnimationState::ACTION_ATTACK, "resources/w/float_attack003.anim", CONST_ZIP_FILE);
		npc02->loadBinaryAnimation(AnimationState::STAND_TO_ACTION, "resources/w/default_float001_cut.anim", CONST_ZIP_FILE);
		npc02->loadBinaryAnimation(AnimationState::ACTION_TO_STAND, "resources/w/default_float001_cut.anim", CONST_ZIP_FILE);
		//npc02->loadAnimation(AnimationState::STAND, "resources/w/float_attack003.txt", CONST_ZIP_FILE);
		//npc02->loadAnimation(AnimationState::STAND, "resources/idleviola_uv010.txt", CONST_ZIP_FILE);
		npc02->setTexture(ghostTexture);
		npc02->walkSpeed = 1.5f;
		npc02->rotationSpeed = 8.0f;
		npc02->modelScale = 0.01f;
		//npc02->modelScale = 0.1f;
		npc02->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY()));

		npc02->position = Eigen::Vector3f(0.f, 0.f, -20.f);
		npc02->setTarget(npc02->position);
		npc02->canAttack = true;
		npc02->attackTarget = player.get();

		npcs.push_back(std::move(npc02));

		std::cout << "Load resurces step 12" << std::endl;

		// Shadow mapping shaders
#ifdef EMSCRIPTEN
		renderer.shaderManager.AddShaderFromFiles("shadow_depth", "resources/shaders/shadow_depth.vertex", "resources/shaders/shadow_depth_web.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("shadow_depth_skinning", "resources/shaders/shadow_depth_skinning.vertex", "resources/shaders/shadow_depth_web.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("default_shadow", "resources/shaders/default_shadow.vertex", "resources/shaders/default_shadow_web.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("skinning_shadow", "resources/shaders/skinning_shadow.vertex", "resources/shaders/default_shadow_web.fragment", CONST_ZIP_FILE);
#else
		renderer.shaderManager.AddShaderFromFiles("shadow_depth", "resources/shaders/shadow_depth.vertex", "resources/shaders/shadow_depth_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("shadow_depth_skinning", "resources/shaders/shadow_depth_skinning.vertex", "resources/shaders/shadow_depth_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("default_shadow", "resources/shaders/default_shadow.vertex", "resources/shaders/default_shadow_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("skinning_shadow", "resources/shaders/skinning_shadow.vertex", "resources/shaders/default_shadow_desktop.fragment", CONST_ZIP_FILE);
#endif

		// Create shadow map (2048x2048, ortho size 40, near 0.1, far 100)
		shadowMap = std::make_unique<ShadowMap>(2048, 40.0f, 0.1f, 100.0f);
		shadowMap->setLightDirection(Eigen::Vector3f(-0.5f, -1.0f, -0.3f));
		std::cout << "Shadow map initialized" << std::endl;

		setupNavigation();
		
		loadingCompleted = true;

		dialogueSystem.init(renderer, CONST_ZIP_FILE);
		dialogueSystem.loadDatabase("resources/dialogue/sample_dialogues.json");
		/*dialogueSystem.addTriggerZone({
			"ghost_room_trigger",
			"test_line_dialogue",
			Eigen::Vector3f(0.0f, 0.0f, -8.5f),
			2.0f,
			true,
			false
		});*/

		scriptEngine.init(this);

		std::cout << "Load resurces step 13" << std::endl;

		// Load UI with inventory button
		try {
			menuManager.uiManager.loadFromFile("resources/config2/ui_inventory.json", renderer, CONST_ZIP_FILE);
			std::cout << "UI loaded successfully" << std::endl;

			menuManager.uiManager.setNodeVisible("inventory_items_panel", false);
			menuManager.uiManager.setNodeVisible("close_inventory_button", false);

			menuManager.uiManager.setTextButtonCallback("inventory_button", [this](const std::string& name) {
				std::cout << "[UI] Inventory button clicked" << std::endl;
				//scriptEngine.callItemPickupCallback("toggle_inventory");
				scriptEngine.showInventory(this);
				//scriptEngine
				//scriptEngine
			});

			menuManager.uiManager.setTextButtonCallback("close_inventory_button", [this](const std::string& name) {
				std::cout << "[UI] Close button clicked" << std::endl;
				menuManager.uiManager.setNodeVisible("inventory_items_panel", false);
				menuManager.uiManager.setNodeVisible("close_inventory_button", false);
				inventoryOpen = false;
			});
		}
		catch (const std::exception& e) {
			std::cerr << "Failed to load UI: " << e.what() << std::endl;
		}
	}

	void Game::setupNavigation()
	{
		std::vector<PathFinder::ObstacleMesh> obstacles;
		obstacles.reserve(gameObjects.size() + interactiveObjects.size());

		for (const auto& item : gameObjects) {
			const LoadedGameObject& gameObj = item.second;
			obstacles.push_back({ &gameObj.mesh.data, Eigen::Vector3f::Zero() });
		}

		for (const InteractiveObject& intObj : interactiveObjects) {
			if (!intObj.isActive) {
				continue;
			}
			obstacles.push_back({ &intObj.mesh.data, intObj.position });
		}

		navigation.build(obstacles, "resources/config2/navigation.json", CONST_ZIP_FILE);

#ifdef SHOW_PATH
		buildDebugNavMeshes();
#endif

		auto planner = [this](const Eigen::Vector3f& start, const Eigen::Vector3f& end) {
			return navigation.findPath(start, end);
		};

		if (player) {
			player->setPathPlanner(planner);
		}

		for (auto& npc : npcs) {
			if (npc) {
				npc->setPathPlanner(planner);
			}
		}
	}

#ifdef SHOW_PATH
	void Game::buildDebugNavMeshes()
	{
		debugNavMeshes.clear();
		const auto& areas = navigation.getAreas();
		float y = navigation.getFloorY() + 0.02f;
		Eigen::Vector3f red(1.0f, 0.0f, 0.0f);

		for (const auto& area : areas) {
			if (area.polygon.size() < 3) continue;
			VertexRenderStruct mesh;
			mesh.data = CreatePolygonFloor(area.polygon, y, red);
			mesh.RefreshVBO();
			debugNavMeshes.push_back(std::move(mesh));
		}
	}

	void Game::drawDebugNavigation()
	{
		renderer.shaderManager.PushShader("defaultColor");
		renderer.SetMatrix();
		for (const auto& mesh : debugNavMeshes) {
			renderer.DrawVertexRenderStruct(mesh);
		}
		renderer.shaderManager.PopShader();
		renderer.SetMatrix();
	}
#endif

	InteractiveObject* Game::raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir) {
		if (interactiveObjects.empty()) {
			std::cout << "[RAYCAST] No interactive objects to check" << std::endl;
			return nullptr;
		}

		std::cout << "[RAYCAST] Starting raycast with " << interactiveObjects.size() << " objects" << std::endl;
		std::cout << "[RAYCAST] Ray origin: (" << rayOrigin.x() << ", " << rayOrigin.y() << ", " << rayOrigin.z() << ")" << std::endl;
		std::cout << "[RAYCAST] Ray dir: (" << rayDir.x() << ", " << rayDir.y() << ", " << rayDir.z() << ")" << std::endl;

		float closestDistance = FLT_MAX;
		InteractiveObject* closestObject = nullptr;

		for (auto& intObj : interactiveObjects) {
			std::cout << "[RAYCAST] Checking object: " << intObj.name << " (active: " << intObj.isActive << ")" << std::endl;
			
			if (!intObj.isActive) {
				std::cout << "[RAYCAST]   -> Object inactive, skipping" << std::endl;
				continue;
			}

			std::cout << "[RAYCAST]   Position: (" << intObj.position.x() << ", " << intObj.position.y() << ", " 
				<< intObj.position.z() << "), Radius: " << intObj.interactionRadius << std::endl;

			Eigen::Vector3f toObject = intObj.position - rayOrigin;
			std::cout << "[RAYCAST]   Vector to object: (" << toObject.x() << ", " << toObject.y() << ", " << toObject.z() << ")" << std::endl;

			float distanceAlongRay = toObject.dot(rayDir);
			std::cout << "[RAYCAST]   Distance along ray: " << distanceAlongRay << std::endl;

			if (distanceAlongRay < 0.1f) {
				std::cout << "[RAYCAST]   -> Object behind camera, skipping" << std::endl;
				continue;
			}

			Eigen::Vector3f closestPointOnRay = rayOrigin + rayDir * distanceAlongRay;
			float distToObject = (closestPointOnRay - intObj.position).norm();
			
			std::cout << "[RAYCAST]   Distance to object: " << distToObject 
				<< " (interaction radius: " << intObj.interactionRadius << ")" << std::endl;

			if (distToObject <= intObj.interactionRadius && distanceAlongRay < closestDistance) {
				std::cout << "[RAYCAST]   *** HIT DETECTED! ***" << std::endl;
				closestDistance = distanceAlongRay;
				closestObject = &intObj;
			}
		}

		if (closestObject) {
			std::cout << "[RAYCAST] *** RAYCAST SUCCESS: Found object " << closestObject->name << " ***" << std::endl;
		} else {
			std::cout << "[RAYCAST] No objects hit" << std::endl;
		}

		return closestObject;
	}

	Character* Game::raycastNpcs(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir, float maxDistance) {
		Character* closestNpc = nullptr;
		float closestDist = maxDistance;

		for (auto& npc : npcs) {
			Eigen::Vector3f toNpc = npc->position - rayOrigin;
			float distAlongRay = toNpc.dot(rayDir);
			if (distAlongRay < 0.1f) continue;

			Eigen::Vector3f closestPoint = rayOrigin + rayDir * distAlongRay;
			float distToNpc = (closestPoint - npc->position).norm();

			float radius = npc->modelScale * 50.0f;

			if (distToNpc <= radius && distAlongRay < closestDist) {
				closestDist = distAlongRay;
				closestNpc = npc.get();
			}
		}
		return closestNpc;
	}
	
	void Game::drawUI()
	{
		glClear(GL_DEPTH_BUFFER_BIT);

		glDisable(GL_DEPTH_TEST);
		glDepthMask(GL_FALSE);

		renderer.shaderManager.PushShader(defaultShaderName);
		renderer.RenderUniform1i(textureUniformName, 0);
		glEnable(GL_BLEND);

		menuManager.uiManager.draw(renderer);
		dialogueSystem.draw(renderer);

		glDisable(GL_BLEND);
		renderer.shaderManager.PopShader();

		glDepthMask(GL_TRUE);
		glEnable(GL_DEPTH_TEST);

		CheckGlError();
	}

	void Game::drawGame()
	{
		glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
		glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);


		renderer.shaderManager.PushShader(defaultShaderName);
		renderer.RenderUniform1i(textureUniformName, 0);

		renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
			static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
			Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
		renderer.PushMatrix();

		renderer.LoadIdentity();
		renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
		//renderer.TranslateMatrix({ 0, -6.f, 0 });

		renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraInclination, Eigen::Vector3f::UnitX())).toRotationMatrix());
		renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraAzimuth, Eigen::Vector3f::UnitY())).toRotationMatrix());
		const Eigen::Vector3f& camTarget = player ? player->position : Eigen::Vector3f::Zero();
		renderer.TranslateMatrix({ -camTarget.x(), -camTarget.y(), -camTarget.z() });

		glBindTexture(GL_TEXTURE_2D, roomTexture->getTexID());
		renderer.DrawVertexRenderStruct(roomMesh);

		for (auto& [name, gameObj] : gameObjects) {
			glBindTexture(GL_TEXTURE_2D, gameObj.texture->getTexID());
			renderer.DrawVertexRenderStruct(gameObj.mesh);
		}

		/*
		glBindTexture(GL_TEXTURE_2D, fireboxTexture->getTexID());
		renderer.DrawVertexRenderStruct(fireboxMesh);
		glBindTexture(GL_TEXTURE_2D, inaiTexture->getTexID());
		renderer.DrawVertexRenderStruct(inaiMesh);
		glBindTexture(GL_TEXTURE_2D, benchTexture->getTexID());
		renderer.DrawVertexRenderStruct(benchMesh);
		*/
		for (auto& intObj : interactiveObjects) {
			if (intObj.isActive) {
				intObj.draw(renderer);
			}
		}

		if (player) player->draw(renderer);
		for (auto& npc : npcs) npc->draw(renderer);

#ifdef SHOW_PATH
		drawDebugNavigation();
#endif

		renderer.PopMatrix();

		renderer.PopProjectionMatrix();

		renderer.shaderManager.PopShader();

	}
	
	void Game::drawShadowDepthPass()
	{
		if (!shadowMap) return;

		const Eigen::Vector3f& sceneCenter = player ? player->position : Eigen::Vector3f::Zero();
		shadowMap->updateLightSpaceMatrix(sceneCenter);

		shadowMap->bind();

		glEnable(GL_DEPTH_TEST);
		glDepthFunc(GL_LESS);

		// Use front-face culling during depth pass to reduce shadow acne on lit faces
		//glCullFace(GL_FRONT);
		glEnable(GL_CULL_FACE);

		renderer.shaderManager.PushShader("shadow_depth");

		// Set up light's orthographic projection
		const Eigen::Matrix4f& lightProj = shadowMap->getLightProjectionMatrix();
		const Eigen::Matrix4f& lightView = shadowMap->getLightViewMatrix();

		// Push the light's projection matrix via the 6-param ortho overload won't
		// match our pre-computed matrix. Instead, use the raw stack approach:
		// push a dummy projection then overwrite via PushSpecialMatrix-style.
		// Simpler: push ortho then push the light view as modelview.
		renderer.PushProjectionMatrix(
			-40.0f, 40.0f,
			-40.0f, 40.0f,
			0.1f, 100.0f);

		const Eigen::Vector3f& lightDir = shadowMap->getLightDirection();
		Eigen::Vector3f lightPos = sceneCenter - lightDir * 50.0f;
		Eigen::Vector3f up(0.0f, 1.0f, 0.0f);
		if (std::abs(lightDir.dot(up)) > 0.99f) {
			up = Eigen::Vector3f(0.0f, 0.0f, 1.0f);
		}

		// Build the light view matrix and push it
		renderer.PushSpecialMatrix(lightView);

		// Draw static geometry
		renderer.DrawVertexRenderStruct(roomMesh);

		for (auto& [name, gameObj] : gameObjects) {
			renderer.DrawVertexRenderStruct(gameObj.mesh);
		}

		for (auto& intObj : interactiveObjects) {
			if (intObj.isActive && intObj.texture) {
				renderer.PushMatrix();
				renderer.TranslateMatrix(intObj.position);
				renderer.DrawVertexRenderStruct(intObj.mesh);
				renderer.PopMatrix();
			}
		}

		// Draw characters (they handle their own skinning shader switch internally)
		if (player) player->drawShadowDepth(renderer);
		for (auto& npc : npcs) npc->drawShadowDepth(renderer);

		renderer.PopMatrix();  // light view
		renderer.PopProjectionMatrix();
		renderer.shaderManager.PopShader();

		//glCullFace(GL_BACK);
		glDisable(GL_CULL_FACE);

		shadowMap->unbind();
	}

	void Game::drawGameWithShadows()
	{
		glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
		glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

#ifdef DEBUG_LIGHT
		// Debug mode: render from the light's point of view using the plain
		// textured shader so we can see what the shadow map "sees".
		renderer.shaderManager.PushShader(defaultShaderName);
		renderer.RenderUniform1i(textureUniformName, 0);

		renderer.PushProjectionMatrix(
			-40.0f, 40.0f,
			-40.0f, 40.0f,
			0.1f, 1000.0f);

		renderer.PushSpecialMatrix(shadowMap->getLightViewMatrix());

#else
		static const std::string shadowShaderName = "default_shadow";

		renderer.shaderManager.PushShader(shadowShaderName);
		renderer.RenderUniform1i(textureUniformName, 0);
		renderer.RenderUniform1i("uShadowMap", 1);

		// Bind shadow map texture to unit 1
		glActiveTexture(GL_TEXTURE1);
		glBindTexture(GL_TEXTURE_2D, shadowMap->getDepthTexture());
		glActiveTexture(GL_TEXTURE0);

		renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
			static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
			Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
		renderer.PushMatrix();

		renderer.LoadIdentity();
		renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });

		renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraInclination, Eigen::Vector3f::UnitX())).toRotationMatrix());
		renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraAzimuth, Eigen::Vector3f::UnitY())).toRotationMatrix());
		const Eigen::Vector3f& camTarget = player ? player->position : Eigen::Vector3f::Zero();
		renderer.TranslateMatrix({ -camTarget.x(), -camTarget.y(), -camTarget.z() });

		// Capture the camera view matrix and compute uLightFromCamera
		cameraViewMatrix = renderer.GetCurrentModelViewMatrix();
		Eigen::Matrix4f cameraViewInverse = cameraViewMatrix.inverse();
		Eigen::Matrix4f lightFromCamera = shadowMap->getLightSpaceMatrix() * cameraViewInverse;
		renderer.RenderUniformMatrix4fv("uLightFromCamera", false, lightFromCamera.data());

		// Light direction in camera space for diffuse lighting
		Eigen::Vector3f lightDirCamera = cameraViewMatrix.block<3,3>(0,0) * shadowMap->getLightDirection();
		renderer.RenderUniform3fv("uLightDir", lightDirCamera.data());
#endif

		glBindTexture(GL_TEXTURE_2D, roomTexture->getTexID());
		renderer.DrawVertexRenderStruct(roomMesh);

		for (auto& [name, gameObj] : gameObjects) {
			glBindTexture(GL_TEXTURE_2D, gameObj.texture->getTexID());
			renderer.DrawVertexRenderStruct(gameObj.mesh);
		}

		for (auto& intObj : interactiveObjects) {
			if (intObj.isActive) {
				intObj.draw(renderer);
			}
		}

#ifdef DEBUG_LIGHT
		// In debug-light mode characters use the plain shaders (draw normally
		// but from the light's viewpoint — projection/view already on stack).
		if (player) player->draw(renderer);
		for (auto& npc : npcs) npc->draw(renderer);
#else
		// Characters use their own shadow-aware shaders
		if (player) player->drawWithShadow(renderer, lightFromCamera, shadowMap->getDepthTexture(), lightDirCamera);
		for (auto& npc : npcs) npc->drawWithShadow(renderer, lightFromCamera, shadowMap->getDepthTexture(), lightDirCamera);
#endif

		renderer.PopMatrix();
		renderer.PopProjectionMatrix();
		renderer.shaderManager.PopShader();
	}

	void Game::drawScene() {
		glViewport(0, 0, Environment::width, Environment::height);
		if (!loadingCompleted) {
			drawLoading();
		}
		else
		{
			if (shadowMap) {
				drawShadowDepthPass();
				drawGameWithShadows();
			} else {
				drawGame();
			}
			drawUI();
		}
		CheckGlError();
	}

	void Game::drawLoading()
	{
		glClear(GL_DEPTH_BUFFER_BIT);

		renderer.shaderManager.PushShader(defaultShaderName);
		renderer.RenderUniform1i(textureUniformName, 0);

		float width = Environment::projectionWidth;
		float height = Environment::projectionHeight;

		renderer.PushProjectionMatrix(
			-width * 0.5f, width*0.5f,
			-height * 0.5f, height * 0.5f,
			-10, 10);

		renderer.PushMatrix();
		renderer.LoadIdentity();

		glBindTexture(GL_TEXTURE_2D, loadingTexture->getTexID());
		renderer.DrawVertexRenderStruct(loadingMesh);

		renderer.PopMatrix();
		renderer.PopProjectionMatrix();
		renderer.shaderManager.PopShader();
		CheckGlError();
	}

	
	int64_t Game::getSyncTimeMs() {
		int64_t localNow = std::chrono::duration_cast<std::chrono::milliseconds>(
			std::chrono::system_clock::now().time_since_epoch()).count();
		return localNow;
	}
	

	void Game::processTickCount() {

		if (lastTickCount == 0) {
			lastTickCount = getSyncTimeMs();

			lastTickCount = (lastTickCount / 50) * 50;

			return;
		}

		newTickCount = getSyncTimeMs();

		newTickCount = (newTickCount / 50) * 50;

		if (newTickCount - lastTickCount > CONST_TIMER_INTERVAL) {

			int64_t delta = newTickCount - lastTickCount;

			lastTickCount = newTickCount;

			//if (player) player->update(delta);
			if (player) {
				player->update(delta);
				dialogueSystem.update(static_cast<int>(delta), player->position);
			}
			for (auto& npc : npcs) npc->update(delta);

			

			// Check if player reached target interactive object
			if (targetInteractiveObject && player) {
				float distToObject = (player->position - targetInteractiveObject->position).norm();

				// If player is close enough to pick up the item
				if (distToObject <= targetInteractiveObject->interactionRadius + 1.0f) {
					std::cout << "[PICKUP] Player reached object! Distance: " << distToObject << std::endl;
					std::cout << "[PICKUP] Calling Lua callback for: " << targetInteractiveObject->id << std::endl;

					// Call custom activate function if specified, otherwise use fallback
					try {
						if (!targetInteractiveObject->activateFunctionName.empty()) {
							std::cout << "[PICKUP] Using custom function: " << targetInteractiveObject->activateFunctionName << std::endl;
							scriptEngine.callActivateFunction(targetInteractiveObject->activateFunctionName);
						}
						else {
							std::cout << "[PICKUP] Using fallback callback" << std::endl;
							scriptEngine.callItemPickupCallback(targetInteractiveObject->id);
						}
					}
					catch (const std::exception& e) {
						std::cerr << "[PICKUP] Error calling function: " << e.what() << std::endl;
					}

					targetInteractiveObject = nullptr;
				}

				//for (auto& npc : npcs) npc->update(delta);
				//if (player) {
				//	dialogueSystem.update(static_cast<int>(delta), player->position);
				//}
			}

			// Some AI stuff
			



		}
	}

	void Game::render() {
		ZL::CheckGlError();

		glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		drawScene();
		processTickCount();

		SDL_GL_SwapWindow(ZL::Environment::window);
	}

	void Game::update() {
		SDL_Event event;
		while (SDL_PollEvent(&event)) {
			if (event.type == SDL_QUIT) {
				Environment::exitGameLoop = true;
			}

			
			if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_RESIZED) {
				// Обновляем размеры и сбрасываем кеш текстов, т.к. меши хранятся в пикселях
				Environment::width = event.window.data1;
				Environment::height = event.window.data2;
				Environment::computeProjectionDimensions();
				//menuManager.uiManager.updateAllLayouts();
				std::cout << "Window resized: " << Environment::width << "x" << Environment::height << std::endl;

				//space.clearTextRendererCache();
			}

#ifdef __ANDROID__
			if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_AC_BACK) {
				Environment::exitGameLoop = true;
			}
#endif

#ifdef __ANDROID__
			if (event.type == SDL_FINGERDOWN) {
				int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
				int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
				handleDown(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
			}
			else if (event.type == SDL_FINGERUP) {
				int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
				int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
				handleUp(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
			}
			else if (event.type == SDL_FINGERMOTION) {
				int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
				int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
				handleMotion(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
			}
#else
			// Emscripten on mobile browser: handle real touch events with per-finger IDs.
			// SDL_HINT_TOUCH_MOUSE_EVENTS="0" is set in main.cpp so these don't
			// also fire SDL_MOUSEBUTTONDOWN, preventing double-processing.
#ifdef EMSCRIPTEN
			if (event.type == SDL_FINGERDOWN) {
				int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
				int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
				handleDown(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
			}
			else if (event.type == SDL_FINGERUP) {
				int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
				int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
				handleUp(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
			}
			else if (event.type == SDL_FINGERMOTION) {
				int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
				int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
				handleMotion(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
			}
#endif

			if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) {
				if (event.button.button == SDL_BUTTON_LEFT) {
					int mx = static_cast<int>((float)event.button.x / Environment::width * Environment::projectionWidth);
					int my = static_cast<int>((float)event.button.y / Environment::height * Environment::projectionHeight);
					
					if (event.type == SDL_MOUSEBUTTONDOWN) {
						std::cout << "\n========== MOUSE DOWN EVENT ==========" << std::endl;
						handleDown(ZL::UiManager::MOUSE_FINGER_ID, mx, my);

						player->attackTarget = nullptr;
						if (menuManager.uiManager.isUiInteractionForFinger(ZL::UiManager::MOUSE_FINGER_ID)) {
							std::cout << "[CLICK] UI handled, skipping character movement" << std::endl;
							continue;
						}

						// Calculate ray for picking
						if (dialogueSystem.blocksGameplayInput()) {
							dialogueSystem.handlePointerReleased(static_cast<float>(mx), Environment::projectionHeight - static_cast<float>(my));
							continue;
						}

						// Unproject click to ground plane (y=0) for Viola's walk target
						float ndcX = 2.0f * event.button.x / Environment::width - 1.0f;
						float ndcY = 1.0f - 2.0f * event.button.y / Environment::height;
						float aspect = (float)Environment::width / (float)Environment::height;
						float tanHalfFov = tan(CAMERA_FOV_Y * 0.5f);

						float cosAzim = cos(cameraAzimuth), sinAzim = sin(cameraAzimuth);
						float cosIncl = cos(cameraInclination), sinIncl = sin(cameraInclination);

						Eigen::Vector3f camRight(cosAzim, 0.f, sinAzim);
						Eigen::Vector3f camForward(sinAzim * cosIncl, -sinIncl, -cosAzim * cosIncl);
						Eigen::Vector3f camUp(sinAzim * sinIncl, cosIncl, -cosAzim * sinIncl);
						const Eigen::Vector3f& playerPos = player ? player->position : Eigen::Vector3f::Zero();
						Eigen::Vector3f camPos = playerPos + Eigen::Vector3f(-sinAzim * cosIncl, sinIncl, cosAzim * cosIncl) * Environment::zoom;

						Eigen::Vector3f rayDir = (camForward + camRight * (ndcX * aspect * tanHalfFov) + camUp * (ndcY * tanHalfFov)).normalized();

						std::cout << "[CLICK] Camera position: (" << camPos.x() << ", " << camPos.y() << ", " << camPos.z() << ")" << std::endl;
						std::cout << "[CLICK] Ray direction: (" << rayDir.x() << ", " << rayDir.y() << ", " << rayDir.z() << ")" << std::endl;

						// First check if we clicked on interactive object
						InteractiveObject* clickedObject = raycastInteractiveObjects(camPos, rayDir);
						if (clickedObject && player && clickedObject->isActive) {
							std::cout << "[CLICK] *** SUCCESS: Clicked on interactive object: " << clickedObject->name << " ***" << std::endl;
							std::cout << "[CLICK] Object position: (" << clickedObject->position.x() << ", "
								<< clickedObject->position.y() << ", " << clickedObject->position.z() << ")" << std::endl;
							std::cout << "[CLICK] Player position: (" << player->position.x() << ", "
								<< player->position.y() << ", " << player->position.z() << ")" << std::endl;

							targetInteractiveObject = clickedObject;
							player->setTarget(clickedObject->position);
							std::cout << "[CLICK] Player moving to object..." << std::endl;
						}
						else {
							// Check if we clicked on an NPC
							Character* clickedNpc = raycastNpcs(camPos, rayDir);
							if (clickedNpc && player) {
								float distance = (player->position - clickedNpc->position).norm();
								if (distance <= clickedNpc->interactionRadius) {
									int npcIndex = -1;
									for (size_t i = 0; i < npcs.size(); ++i) {
										if (npcs[i].get() == clickedNpc) {
											npcIndex = static_cast<int>(i);
											break;
										}
									}
									if (npcIndex != -1) {
										std::cout << "[CLICK] *** SUCCESS: Clicked on NPC index: " << npcIndex << " ***" << std::endl;
										scriptEngine.callNpcInteractCallback(npcIndex);

										if (clickedNpc->canAttack)
										{
											player->attackTarget = clickedNpc;
										}
									}
								}
								else {
									std::cout << "[CLICK] Too far from NPC (distance " << distance
										<< " > " << clickedNpc->interactionRadius << ")" << std::endl;
								}
							}
							else if (rayDir.y() < -0.001f && player) {
								// Otherwise, unproject click to ground plane for Viola's walk target
								float t = -camPos.y() / rayDir.y();
								Eigen::Vector3f hit = camPos + rayDir * t;
								std::cout << "[CLICK] Clicked on ground at: (" << hit.x() << ", " << hit.z() << ")" << std::endl;
								
								if (player->currentState == AnimationState::STAND || player->currentState == AnimationState::WALK)
								{
									player->setTarget(Eigen::Vector3f(hit.x(), 0.f, hit.z()));
								}
							}
							else {
								std::cout << "[CLICK] No valid target found" << std::endl;
							}
						}
						std::cout << "========================================\n" << std::endl;
					} else {
						handleUp(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
					}
				}
				else if (event.button.button == SDL_BUTTON_RIGHT) {
					if (event.type == SDL_MOUSEBUTTONDOWN) {
						rightMouseDown = true;
						lastMouseX = event.button.x;
						lastMouseY = event.button.y;
					}
					else {
						rightMouseDown = false;
					}
				}
			}
			else if (event.type == SDL_MOUSEMOTION) {
				int mx = static_cast<int>((float)event.motion.x / Environment::width * Environment::projectionWidth);
				int my = static_cast<int>((float)event.motion.y / Environment::height * Environment::projectionHeight);
				handleMotion(ZL::UiManager::MOUSE_FINGER_ID, mx, my);

				if (rightMouseDown) {
					int dx = event.motion.x - lastMouseX;
					int dy = event.motion.y - lastMouseY;
					lastMouseX = event.motion.x;
					lastMouseY = event.motion.y;

					const float sensitivity = 0.005f;
					cameraAzimuth += dx * sensitivity;
					cameraInclination += dy * sensitivity;

					const float minInclination = M_PI * 30.f / 180.f;
					const float maxInclination = M_PI * 0.5f;
					cameraInclination = max(minInclination, min(maxInclination, cameraInclination));
				}
			}

			if (event.type == SDL_MOUSEWHEEL) {
				static const float zoomstep = 2.0f;
				if (event.wheel.y > 0) {
					Environment::zoom -= zoomstep;
				}
				else if (event.wheel.y < 0) {
					Environment::zoom += zoomstep;
				}
				if (Environment::zoom < zoomstep) {
					Environment::zoom = zoomstep;
				}
			}

			if (event.type == SDL_KEYDOWN && dialogueSystem.handleKeyDown(event.key.keysym.sym)) {
				continue;
			}

			if (event.type == SDL_KEYDOWN && event.key.repeat == 0) {
				switch (event.key.keysym.sym) {
				case SDLK_f:
					dialogueSystem.startDialogue("test_line_dialogue");
					break;

				case SDLK_e:
					dialogueSystem.startDialogue("test_cutscene_dialogue");
					break;

				case SDLK_p:
					if (player->battle_state == 0)
					{
						player->battle_state = 1;
					}
					else
					{
						player->battle_state = 0;
					}
					break;

				case SDLK_l:
					if (player->attack == 0)
					{
						player->attack = 1;
					}
					break;
				case SDLK_RETURN:

				default:
					break;
				}
			}

			// Обработка ввода текста
			if (event.type == SDL_KEYDOWN) {
				if (event.key.keysym.sym == SDLK_BACKSPACE) {
					//menuManager.uiManager.onKeyBackspace();
				}
			}

			if (event.type == SDL_TEXTINPUT) {
				// Пропускаем ctrl+c и другие команды
				if ((event.text.text[0] & 0x80) == 0) { // ASCII символы
					//menuManager.uiManager.onKeyPress((unsigned char)event.text.text[0]);
				}
			}

			if (event.type == SDL_KEYUP) {
				if (event.key.keysym.sym == SDLK_r) {
					std::cout << "Camera position: x=" << x << " y=" << y << " z=" << z << std::endl;
				}
			}

			if (event.type == SDL_KEYUP) {
				/*benchMesh.data.Move({-x, -y, 0});
				if (event.key.keysym.sym == SDLK_a) {
					x = x - 0.1;
				}
				if (event.key.keysym.sym == SDLK_d) {
					x = x + 0.1;
				}
				if (event.key.keysym.sym == SDLK_w) {
					y = y - 0.1;
				}
				if (event.key.keysym.sym == SDLK_s) {
					y = y + 0.1;
				}
				benchMesh.data.Move({ x, y, 0 });
				benchMesh.RefreshVBO();*/
			}
#endif
		}
		render();
		
		mainThreadHandler.processMainThreadTasks();

	}

	void Game::handleDown(int64_t fingerId, int mx, int my)
	{
		int uiX = mx;
		int uiY = Environment::projectionHeight - my;

		
		menuManager.uiManager.onTouchDown(fingerId, uiX, uiY);

	}

	void Game::handleUp(int64_t fingerId, int mx, int my)
	{
		int uiX = mx;
		int uiY = Environment::projectionHeight - my;

		// Check BEFORE onTouchUp erases the finger from the map.
		// If this finger started on a UI element, don't notify space —
		// otherwise space would think the ship-control finger was released.
		bool wasUiInteraction = menuManager.uiManager.isUiInteractionForFinger(fingerId);
		menuManager.uiManager.onTouchUp(fingerId, uiX, uiY);

	}

	void Game::handleMotion(int64_t fingerId, int mx, int my)
	{
		int uiX = mx;
		int uiY = Environment::projectionHeight - my;

		// Check before onTouchMove so the "started on UI" state is preserved
		// regardless of what onTouchMove does internally.
		bool wasUiInteraction = menuManager.uiManager.isUiInteractionForFinger(fingerId);
		menuManager.uiManager.onTouchMove(fingerId, uiX, uiY);

	}

	bool Game::requestDialogueStart(const std::string& dialogueId)
	{
		return dialogueSystem.startDialogue(dialogueId);
	}

	void Game::setDialogueFlag(const std::string& flag, int value)
	{
		dialogueSystem.setFlag(flag, value);
	}

	int Game::getDialogueFlag(const std::string& flag) const
	{
		return dialogueSystem.getFlag(flag);
	}

	bool Game::setNavigationAreaAvailable(const std::string& areaName, bool available)
	{
		return navigation.setAreaAvailable(areaName, available);
	}


}  // namespace ZL
