#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 NETWORK
#include "network/WebSocketClient.h"
#else
#include "network/LocalClient.h"
#endif

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

	static bool g_exitBgAnimating = false;

	float x = 0;

	Eigen::Quaternionf generateRandomQuaternion(std::mt19937& gen)
	{

		std::normal_distribution<> distrib(0.0, 1.0);

		Eigen::Quaternionf randomQuat = {
			(float)distrib(gen),
			(float)distrib(gen),
			(float)distrib(gen),
			(float)distrib(gen)
		};

		return randomQuat.normalized();
	}

	std::vector<BoxCoords> generateRandomBoxCoords(int N)
	{
		const float MIN_DISTANCE = 3.0f;
		const float MIN_DISTANCE_SQUARED = MIN_DISTANCE * MIN_DISTANCE;
		const float MIN_COORD = -100.0f;
		const float MAX_COORD = 100.0f;
		const int MAX_ATTEMPTS = 1000;
		std::vector<BoxCoords> boxCoordsArr;

		std::random_device rd;
		std::mt19937 gen(rd());

		std::uniform_real_distribution<> distrib(MIN_COORD, MAX_COORD);

		int generatedCount = 0;

		while (generatedCount < N)
		{
			bool accepted = false;
			int attempts = 0;

			while (!accepted && attempts < MAX_ATTEMPTS)
			{
				Vector3f newPos(
					(float)distrib(gen),
					(float)distrib(gen),
					(float)distrib(gen)
				);

				accepted = true;
				for (const auto& existingBox : boxCoordsArr)
				{

					Vector3f diff = newPos - existingBox.pos;

					float distanceSquared = diff.squaredNorm();

					if (distanceSquared < MIN_DISTANCE_SQUARED)
					{
						accepted = false;
						break;
					}
				}

				if (accepted)
				{
					Eigen::Quaternionf randomQuat = generateRandomQuaternion(gen);

					Matrix3f randomMatrix = randomQuat.toRotationMatrix();

					boxCoordsArr.emplace_back(BoxCoords{ newPos, randomMatrix });
					generatedCount++;
				}
				attempts++;
			}

			if (!accepted) {
				break;
			}
		}

		return boxCoordsArr;
	}

	static Eigen::Matrix4f makeViewMatrix_FromYourCamera()
	{
		Eigen::Matrix4f Tz = Eigen::Matrix4f::Identity();
		Tz(2, 3) = -1.0f * ZL::Environment::zoom;

		Eigen::Matrix4f R = Eigen::Matrix4f::Identity();
		R.block<3, 3>(0, 0) = ZL::Environment::inverseShipMatrix;

		Eigen::Matrix4f Tship = Eigen::Matrix4f::Identity();
		Tship(0, 3) = -ZL::Environment::shipState.position.x();
		Tship(1, 3) = -ZL::Environment::shipState.position.y();
		Tship(2, 3) = -ZL::Environment::shipState.position.z();

		return Tz * R * Tship;
	}

	static Eigen::Matrix4f makePerspective(float fovyRadians, float aspect, float zNear, float zFar)
	{
		// Стандартная перспектива
		float f = 1.0f / std::tan(fovyRadians * 0.5f);

		Eigen::Matrix4f P = Eigen::Matrix4f::Zero();
		P(0, 0) = f / aspect;
		P(1, 1) = f;
		P(2, 2) = (zFar + zNear) / (zNear - zFar);
		P(2, 3) = (2.0f * zFar * zNear) / (zNear - zFar);
		P(3, 2) = -1.0f;
		return P;
	}

	bool Game::worldToScreen(const Vector3f& world, float& outX, float& outY, float& outDepth) const
	{
		// Матрицы должны совпасть с drawBoxes/drawShip по смыслу
		float aspect = static_cast<float>(Environment::width) / static_cast<float>(Environment::height);

		Eigen::Matrix4f V = makeViewMatrix_FromYourCamera();
		Eigen::Matrix4f P = makePerspective(1.0f / 1.5f, aspect, Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);

		Eigen::Vector4f w(world.x(), world.y(), world.z(), 1.0f);
		Eigen::Vector4f clip = P * V * w;

		if (clip.w() <= 0.0001f) return false; // позади камеры

		Eigen::Vector3f ndc = clip.head<3>() / clip.w(); // [-1..1]
		outDepth = ndc.z();

		// В пределах экрана?
		// (можно оставить, можно клампить)
		float sx = (ndc.x() * 0.5f + 0.5f) * Environment::width;
		float sy = (ndc.y() * 0.5f + 0.5f) * Environment::height;

		outX = sx;
		outY = sy;

		// Можно отсеять те, что вне:
		if (sx < -200 || sx > Environment::width + 200) return false;
		if (sy < -200 || sy > Environment::height + 200) return false;

		return true;
	}

	void Game::drawBoxesLabels()
	{
		if (!textRenderer) return;

		// Текст рисуем как 2D поверх всего 3D, но ДО drawUI или после — как хочешь.
		// Чтобы подписи были поверх — делай после drawBoxes и до drawUI (как мы и вставили).

		glDisable(GL_DEPTH_TEST);
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

		for (size_t i = 0; i < boxCoordsArr.size(); ++i)
		{
			if (i >= boxAlive.size() || !boxAlive[i]) continue;
			if (i >= boxLabels.size()) continue;

			// ВАЖНО: твои боксы рисуются с Translate({0,0,45000}) + pos
			Vector3f boxWorld = boxCoordsArr[i].pos + Vector3f{ 0.0f, 0.0f, 45000.0f };

			// Чуть выше бокса по Y (или по Z — как нравится)
			Vector3f labelWorld = boxWorld + Vector3f{ 0.0f, 2.2f, 0.0f };

			float sx, sy, depth;
			if (!worldToScreen(labelWorld, sx, sy, depth)) continue;

			// В твоей UI-системе Y обычно перевёрнут (ты делаешь uiY = height - my).
			// Наш worldToScreen отдаёт Y в системе "низ=0, верх=height" (NDC->screen).
			// Чтобы совпало с твоей UI-логикой, перевернём:
			float uiX = sx;
			float uiY = sy; // если окажется вверх ногами — замени на (Environment::height - sy)

			// Можно делать масштаб по дальности: чем дальше — тем меньше.
			// depth в NDC: ближе к -1 (near) и к 1 (far). Стабильнее считать по расстоянию:
			float dist = (Environment::shipState.position - boxWorld).norm();
			float scale = std::clamp(120.0f / (dist + 1.0f), 0.6f, 1.2f);

			textRenderer->drawText(boxLabels[i], uiX, uiY, scale, /*centered*/true);
		}

		glDisable(GL_BLEND);
		glEnable(GL_DEPTH_TEST);
	}

	Game::Game()
		: window(nullptr)
		, glContext(nullptr)
		, newTickCount(0)
		, lastTickCount(0)
		, planetObject(renderer, taskManager, mainThreadHandler)
	{
		projectiles.reserve(maxProjectiles);
		for (int i = 0; i < maxProjectiles; ++i) {
			projectiles.emplace_back(std::make_unique<Projectile>());
		}
	}

	Game::~Game() {
		if (glContext) {
			SDL_GL_DeleteContext(glContext);
		}
		if (window) {
			SDL_DestroyWindow(window);
		}
		SDL_Quit();
	}

	void Game::setup() {
		glContext = SDL_GL_CreateContext(ZL::Environment::window);

		ZL::BindOpenGlFunctions();
		ZL::CheckGlError();


		//#ifndef SIMPLIFIED
		renderer.shaderManager.AddShaderFromFiles("defaultColor", "resources/shaders/defaultColor.vertex", "resources/shaders/defaultColor_web.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("default", "resources/shaders/default.vertex", "resources/shaders/default_web.fragment", CONST_ZIP_FILE);
		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);

		/*#else
				renderer.shaderManager.AddShaderFromFiles("default", "resources/shaders/default.vertex", "resources/shaders/default_web.fragment", CONST_ZIP_FILE);
				renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/default_env.vertex", "resources/shaders/default_env_web.fragment", CONST_ZIP_FILE);
				renderer.shaderManager.AddShaderFromFiles("defaultAtmosphere", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE);
				renderer.shaderManager.AddShaderFromFiles("planetBake", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE);
				renderer.shaderManager.AddShaderFromFiles("planetStone", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE);
				renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE);
		#endif*/

		bool cfgLoaded = sparkEmitter.loadFromJsonFile("resources/config/spark_config.json", renderer, CONST_ZIP_FILE);
		bool projCfgLoaded = projectileEmitter.loadFromJsonFile("resources/config/spark_projectile_config.json", renderer, CONST_ZIP_FILE);
		bool explosionCfgLoaded = explosionEmitter.loadFromJsonFile("resources/config/explosion_config.json", renderer, CONST_ZIP_FILE);
		explosionEmitter.setEmissionPoints(std::vector<Vector3f>());
		projectileEmitter.setEmissionPoints(std::vector<Vector3f>());

		uiManager.loadFromFile("resources/config/main_menu.json", renderer, CONST_ZIP_FILE);
		std::function<void()> loadGameplayUI;
		loadGameplayUI = [this]() {
			uiManager.loadFromFile("resources/config/ui.json", renderer, CONST_ZIP_FILE);

			auto velocityTv = uiManager.findTextView("velocityText");
			if (velocityTv) {
				velocityTv->rect.x = 10.0f;
				velocityTv->rect.y = static_cast<float>(Environment::height) - velocityTv->rect.h - 10.0f;
			}
			else {
				std::cerr << "Failed to find velocityText in UI" << std::endl;
			}

			uiManager.startAnimationOnNode("backgroundNode", "bgScroll");
			static bool isExitButtonAnimating = false;
			uiManager.setAnimationCallback("settingsButton", "buttonsExit", [this]() {
				std::cerr << "Settings button animation finished -> переход в настройки" << std::endl;
				if (uiManager.pushMenuFromFile("resources/config/settings.json", this->renderer, CONST_ZIP_FILE)) {
					uiManager.setButtonCallback("Opt1", [this](const std::string& n) {
						std::cerr << "Opt1 pressed: " << n << std::endl;
						});
					uiManager.setButtonCallback("Opt2", [this](const std::string& n) {
						std::cerr << "Opt2 pressed: " << n << std::endl;
						});
					uiManager.setButtonCallback("backButton", [this](const std::string& n) {
						uiManager.stopAllAnimations();
						uiManager.popMenu();
						});
				}
				else {
					std::cerr << "Failed to open settings menu after animations" << std::endl;
				}
				});

			uiManager.setAnimationCallback("exitButton", "bgScroll", []() {
				std::cerr << "Exit button bgScroll animation finished" << std::endl;
				g_exitBgAnimating = false;
				});

			uiManager.setButtonCallback("playButton", [this](const std::string& name) {
				std::cerr << "Play button pressed: " << name << std::endl;
				});

			uiManager.setButtonCallback("settingsButton", [this](const std::string& name) {
				std::cerr << "Settings button pressed: " << name << std::endl;
				uiManager.startAnimationOnNode("playButton", "buttonsExit");
				uiManager.startAnimationOnNode("settingsButton", "buttonsExit");
				uiManager.startAnimationOnNode("exitButton", "buttonsExit");
				});

			uiManager.setButtonCallback("exitButton", [this](const std::string& name) {
				std::cerr << "Exit button pressed: " << name << std::endl;

				if (!g_exitBgAnimating) {
					std::cerr << "start repeat anim bgScroll on exitButton" << std::endl;
					g_exitBgAnimating = true;
					uiManager.startAnimationOnNode("exitButton", "bgScroll");
				}
				else {
					std::cerr << "stop repeat anim bgScroll on exitButton" << std::endl;
					g_exitBgAnimating = false;
					uiManager.stopAnimationOnNode("exitButton", "bgScroll");

					auto exitButton = uiManager.findButton("exitButton");
					if (exitButton) {
						exitButton->animOffsetX = 0.0f;
						exitButton->animOffsetY = 0.0f;
						exitButton->animScaleX = 1.0f;
						exitButton->animScaleY = 1.0f;
						exitButton->buildMesh();
					}
				}
				});

			uiManager.setButtonCallback("shootButton", [this](const std::string& name) {
				uint64_t now = SDL_GetTicks64();
				if (now - lastProjectileFireTime >= static_cast<uint64_t>(projectileCooldownMs)) {
					lastProjectileFireTime = now;
					const float projectileSpeed = 60.0f;

					this->fireProjectiles();

					Eigen::Vector3f localForward = { 0, 0, -1 };
					Eigen::Vector3f worldForward = (Environment::shipState.rotation * localForward).normalized();

					Eigen::Vector3f centerPos = Environment::shipState.position +
						Environment::shipState.rotation * Vector3f{ 0, 0.9f, 5.0f };

					Eigen::Quaternionf q(Environment::shipState.rotation);
					float speedToSend = projectileSpeed + Environment::shipState.velocity;
					int shotCount = 2;

					std::string fireMsg = "FIRE:" +
						std::to_string(now) + ":" +
						std::to_string(centerPos.x()) + ":" +
						std::to_string(centerPos.y()) + ":" +
						std::to_string(centerPos.z()) + ":" +
						std::to_string(q.w()) + ":" +
						std::to_string(q.x()) + ":" +
						std::to_string(q.y()) + ":" +
						std::to_string(q.z()) + ":" +
						std::to_string(speedToSend) + ":" +
						std::to_string(shotCount);

					networkClient->Send(fireMsg);
				}
				});

			uiManager.setSliderCallback("velocitySlider", [this](const std::string& name, float value) {
				int newVel = roundf(value * 10);
				if (newVel != Environment::shipState.selectedVelocity) {
					newShipVelocity = newVel;
				}
				});
			};

		uiManager.setButtonCallback("singleButton", [loadGameplayUI](const std::string& name) {
			std::cerr << "Single button pressed: " << name << " -> load gameplay UI\n";
			loadGameplayUI();
			});
		uiManager.setButtonCallback("multiplayerButton", [loadGameplayUI](const std::string& name) {
			std::cerr << "Multiplayer button pressed: " << name << " -> load gameplay UI\n";
			loadGameplayUI();
			});
		uiManager.setButtonCallback("exitButton", [](const std::string& name) {
			std::cerr << "Exit from main menu pressed: " << name << " -> exiting\n";
			Environment::exitGameLoop = true;
			});

		cubemapTexture = std::make_shared<Texture>(
			std::array<TextureDataStruct, 6>{
			CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
				CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
				CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
				CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
				CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE),
				CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE)
		});


		cubemap.data = ZL::CreateCubemap(500);
		cubemap.RefreshVBO();



		//Load texture

		//spaceshipTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/DefaultMaterial_BaseColor_shine.png", CONST_ZIP_FILE));
		//spaceshipBase = LoadFromTextFile02("resources/spaceship006.txt", CONST_ZIP_FILE);
		//spaceshipBase.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI / 2.0, Eigen::Vector3f::UnitY())).toRotationMatrix());// QuatFromRotateAroundY(M_PI / 2.0).toRotationMatrix());

		//spaceshipTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/cap_D.png", CONST_ZIP_FILE));
		//spaceshipBase = LoadFromTextFile02("./resources/spaceship006x.txt", CONST_ZIP_FILE);
		//spaceshipBase.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI / 2.0, Eigen::Vector3f::UnitY())).toRotationMatrix());// QuatFromRotateAroundY(M_PI / 2.0).toRotationMatrix());

		spaceshipTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/MainCharacter_Base_color_sRGB.png", CONST_ZIP_FILE));
		spaceshipBase = LoadFromTextFile02("resources/spaceshipnew001.txt", CONST_ZIP_FILE);
		spaceshipBase.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())).toRotationMatrix());// QuatFromRotateAroundY(M_PI / 2.0).toRotationMatrix());

		spaceshipBase.Move(Vector3f{ 1.2, 0, -5 });

		spaceship.AssignFrom(spaceshipBase);
		spaceship.RefreshVBO();


		//Boxes
		boxTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/box/box.png", CONST_ZIP_FILE));
		boxBase = LoadFromTextFile02("resources/box/box.txt", CONST_ZIP_FILE);

		boxCoordsArr = generateRandomBoxCoords(50);
		boxRenderArr.resize(boxCoordsArr.size());
		for (int i = 0; i < boxCoordsArr.size(); i++)
		{
			boxRenderArr[i].AssignFrom(boxBase);
			//boxRenderArr[i].data = CreateBaseConvexPolyhedron(1999);
			boxRenderArr[i].RefreshVBO();
		}

		boxAlive.resize(boxCoordsArr.size(), true);

		textRenderer = std::make_unique<ZL::TextRenderer>();
		textRenderer->init(renderer, "resources/fonts/DroidSans.ttf", 32);

		boxLabels.clear();
		boxLabels.reserve(boxCoordsArr.size());
		for (size_t i = 0; i < boxCoordsArr.size(); ++i) {
			boxLabels.push_back("Box " + std::to_string(i + 1));
		}

		if (!cfgLoaded)
		{
			throw std::runtime_error("Failed to load spark emitter config file!");
		}

		renderer.InitOpenGL();
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

		planetObject.init();

#ifdef NETWORK
		networkClient = std::make_unique<WebSocketClient>(taskManager.getIOContext());
		networkClient->Connect("127.0.0.1", 8080);
#else	
		networkClient = std::make_unique<LocalClient>();
		networkClient->Connect("", 0);
#endif
	}

	void Game::drawCubemap(float skyPercent)
	{
		static const std::string defaultShaderName = "default";
		static const std::string envShaderName = "env_sky";
		static const std::string vPositionName = "vPosition";
		static const std::string vTexCoordName = "vTexCoord";
		static const std::string textureUniformName = "Texture";
		static const std::string skyPercentUniformName = "skyPercent";

		renderer.shaderManager.PushShader(envShaderName);
		renderer.RenderUniform1i(textureUniformName, 0);
		renderer.RenderUniform1f(skyPercentUniformName, skyPercent);
		renderer.EnableVertexAttribArray(vPositionName);
		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.RotateMatrix(Environment::inverseShipMatrix);


		Vector3f worldLightDir = Vector3f(1.0f, -1.0f, -1.0f).normalized();
		Matrix3f viewMatrix = Environment::inverseShipMatrix;
		Vector3f viewLightDir = (viewMatrix * worldLightDir).normalized();


		// Передаем вектор НА источник света
		Vector3f lightToSource = -viewLightDir;
		renderer.RenderUniform3fv("uLightDirView", lightToSource.data());

		// 2. Базовый цвет атмосферы (голубой)
		Vector3f skyColor = { 0.0f, 0.5f, 1.0f };
		renderer.RenderUniform3fv("uSkyColor", skyColor.data());

		// 1. Вектор направления от центра планеты к игроку (в мировых координатах)
		// Предполагаем, что планета в (0,0,0). Если нет, то (shipPosition - planetCenter)
		Vector3f playerDirWorld = Environment::shipState.position.normalized();

		// 2. Направление света в мировом пространстве
		//Vector3f worldLightDir = Vector3f(1.0f, -1.0f, -1.0f).normalized();

		// 3. Считаем глобальную освещенность для игрока (насколько он на свету)
		// Это одно число для всего кадра
		float playerLightFactor = playerDirWorld.dot(-worldLightDir);
		// Ограничиваем и делаем переход мягче
		playerLightFactor = std::clamp((playerLightFactor + 0.2f) / 1.2f, 0.0f, 1.0f);

		renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor);
		renderer.RenderUniform1f("skyPercent", skyPercent);

		CheckGlError();

		glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture->getTexID());
		renderer.DrawVertexRenderStruct(cubemap);

		CheckGlError();


		renderer.PopMatrix();
		renderer.PopProjectionMatrix();
		renderer.DisableVertexAttribArray(vPositionName);

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

	void Game::drawShip()
	{
		static const std::string defaultShaderName = "default";
		static const std::string envShaderName = "env";
		static const std::string vPositionName = "vPosition";
		static const std::string vTexCoordName = "vTexCoord";
		static const std::string textureUniformName = "Texture";

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

		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 }); //Ship camera offset

		if (shipAlive) {
			glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID());
			renderer.DrawVertexRenderStruct(spaceship);
		}
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

		for (const auto& p : projectiles) {
			if (p && p->isActive()) {
				p->draw(renderer);
			}
		}

		if (shipAlive) {
			renderer.PushMatrix();
			renderer.TranslateMatrix({ 0, 0, 16 });
			sparkEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
			renderer.PopMatrix();
			projectileEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
		}

		if (showExplosion) {
			explosionEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
		}

		//glBindTexture(GL_TEXTURE_2D, basePlatformTexture->getTexID());
		//renderer.DrawVertexRenderStruct(basePlatform);

		glDisable(GL_BLEND);
		renderer.PopMatrix();
		renderer.PopProjectionMatrix();
		renderer.DisableVertexAttribArray(vPositionName);
		renderer.DisableVertexAttribArray(vTexCoordName);

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

	void Game::drawBoxes()
	{
		static const std::string defaultShaderName = "default";
		static const std::string envShaderName = "env";
		static const std::string vPositionName = "vPosition";
		static const std::string vTexCoordName = "vTexCoord";
		static const std::string textureUniformName = "Texture";

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

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

		for (int i = 0; i < boxCoordsArr.size(); i++)
		{
			if (!boxAlive[i]) continue;
			renderer.PushMatrix();

			renderer.LoadIdentity();
			renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
			renderer.RotateMatrix(Environment::inverseShipMatrix);
			renderer.TranslateMatrix(-Environment::shipState.position);
			renderer.TranslateMatrix({ 0.f, 0.f, 45000.f });
			renderer.TranslateMatrix(boxCoordsArr[i].pos);
			renderer.RotateMatrix(boxCoordsArr[i].m);

			glBindTexture(GL_TEXTURE_2D, boxTexture->getTexID());
			//glBindTexture(GL_TEXTURE_2D, rockTexture->getTexID());
			renderer.DrawVertexRenderStruct(boxRenderArr[i]);

			renderer.PopMatrix();
		}
		renderer.PopProjectionMatrix();
		renderer.DisableVertexAttribArray(vPositionName);
		renderer.DisableVertexAttribArray(vTexCoordName);

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

	void Game::drawUI()
	{
		static const std::string defaultShaderName = "default";
		static const std::string envShaderName = "env";
		static const std::string vPositionName = "vPosition";
		static const std::string vTexCoordName = "vTexCoord";
		static const std::string textureUniformName = "Texture";

		glClear(GL_DEPTH_BUFFER_BIT);

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

		renderer.DisableVertexAttribArray(vPositionName);
		renderer.DisableVertexAttribArray(vTexCoordName);
		glEnable(GL_BLEND);
		uiManager.draw(renderer);
		glDisable(GL_BLEND);
		renderer.shaderManager.PopShader();
		CheckGlError();
	}

	void Game::drawScene() {
		static const std::string defaultShaderName = "default";
		static const std::string envShaderName = "env";
		static const std::string vPositionName = "vPosition";
		static const std::string vTexCoordName = "vTexCoord";
		static const std::string textureUniformName = "Texture";

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

		glViewport(0, 0, Environment::width, Environment::height);

		CheckGlError();

		float skyPercent = 0.0;
		float distance = planetObject.distanceToPlanetSurface(Environment::shipState.position);
		if (distance > 1500.f)
		{
			skyPercent = 0.0f;
		}
		else if (distance < 800.f)
		{
			skyPercent = 1.0f;
		}
		else
		{
			skyPercent = (1500.f - distance) / (1500.f - 800.f);
		}


		drawCubemap(skyPercent);
		planetObject.draw(renderer);
		if (planetObject.distanceToPlanetSurface(Environment::shipState.position) > 100.f)
		{
			glClear(GL_DEPTH_BUFFER_BIT);
		}
		drawShip();
		drawRemoteShips();
		drawRemoteShipsLabels();
		drawBoxes();
		drawBoxesLabels();

		drawUI();
		CheckGlError();
	}

	void Game::drawRemoteShips() {
		// Используем те же константы имен для шейдеров, что и в drawShip
		static const std::string defaultShaderName = "default";
		static const std::string vPositionName = "vPosition";
		static const std::string vTexCoordName = "vTexCoord";
		static const std::string textureUniformName = "Texture";

		// Активируем шейдер и текстуру (предполагаем, что меш у всех одинаковый)
		renderer.shaderManager.PushShader(defaultShaderName);
		renderer.RenderUniform1i(textureUniformName, 0);

		renderer.EnableVertexAttribArray(vPositionName);
		renderer.EnableVertexAttribArray(vTexCoordName);

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

		// Биндим текстуру корабля один раз для всех удаленных игроков (оптимизация батчинга)
		glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID());

		/*auto now = std::chrono::system_clock::now();

		//Apply server delay:
		now -= std::chrono::milliseconds(CLIENT_DELAY);

		latestRemotePlayers = networkClient->getRemotePlayers();
		*/
		// Если сервер прислал коробки, применяем их однократно вместо локальной генерации
		if (!serverBoxesApplied && networkClient) {
			auto sboxes = networkClient->getServerBoxes();
			if (!sboxes.empty()) {
				boxCoordsArr.clear();
				for (auto& b : sboxes) {
					BoxCoords bc;
					bc.pos = b.first;
					bc.m = b.second;
					boxCoordsArr.push_back(bc);
				}
				boxRenderArr.resize(boxCoordsArr.size());
				for (int i = 0; i < (int)boxCoordsArr.size(); ++i) {
					boxRenderArr[i].AssignFrom(boxBase);
					boxRenderArr[i].RefreshVBO();
				}
				boxAlive.assign(boxCoordsArr.size(), true);
				serverBoxesApplied = true;
			}
		}

		// Итерируемся по актуальным данным из extrapolateRemotePlayers
		for (auto const& [id, remotePlayer] : remotePlayerStates) {

			const ClientState& playerState = remotePlayer;
			if (deadRemotePlayers.count(id)) continue;

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

			renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
			renderer.TranslateMatrix({ 0, -6.f, 0 }); //Ship camera offset
			renderer.RotateMatrix(Environment::inverseShipMatrix);
			renderer.TranslateMatrix(-Environment::shipState.position);


			Eigen::Vector3f relativePos = playerState.position;// -Environment::shipPosition;
			renderer.TranslateMatrix(relativePos);

			// 3. Поворот врага
			renderer.RotateMatrix(playerState.rotation);

			renderer.DrawVertexRenderStruct(spaceship);
			renderer.PopMatrix();
		}

		renderer.PopProjectionMatrix();
		renderer.DisableVertexAttribArray(vPositionName);
		renderer.DisableVertexAttribArray(vTexCoordName);
		renderer.shaderManager.PopShader();

		CheckGlError();
	}

	void Game::drawRemoteShipsLabels()
	{
		if (!textRenderer) return;

#ifdef NETWORK
		// 2D поверх 3D
		glDisable(GL_DEPTH_TEST);
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

		// Берем удаленных игроков
		//latestRemotePlayers = networkClient->getRemotePlayers();

		auto now = std::chrono::system_clock::now();
		now -= std::chrono::milliseconds(CLIENT_DELAY);

		for (auto const& [id, remotePlayer] : remotePlayerStates)
		{

//<<<<<<< HEAD
			const ClientState& st = remotePlayer;
			// Позиция корабля в мире
			Vector3f shipWorld = st.position;

			float distSq = (Environment::shipState.position - shipWorld).squaredNorm();
			/*if (distSq > MAX_DIST_SQ) // дальность прорисовки никнейма
				continue;*/
			float dist = sqrt(distSq);
			float alpha = 1.0f; // постоянная видимость
			/*float alpha = std::clamp(1.f - (dist - FADE_START) / FADE_RANGE, 0.f, 1.f); // дальность прорисовки никнейма
			if (alpha < 0.01f)
				continue; */
			Vector3f labelWorld = shipWorld + Vector3f{ 0.f, -4.f, 0.f }; // регулировка высоты
			float sx, sy, depth;
			if (!worldToScreen(labelWorld, sx, sy, depth))
				continue;

			float uiX = sx, uiY = sy;
			float scale = std::clamp(BASE_SCALE / (dist * PERSPECTIVE_K + 1.f), MIN_SCALE, MAX_SCALE);

			// Дефолтный лейбл
			std::string label = "Player (" + std::to_string(st.id) + ") " + std::to_string((int)dist) + "m";

			// TODO: nickname sync

			textRenderer->drawText(label, uiX + 1.f, uiY + 1.f, scale, true, { 0.f, 0.f, 0.f, alpha }); // color param
			textRenderer->drawText(label, uiX, uiY, scale, true, { 1.f, 1.f, 1.f, alpha });
		}

		glDisable(GL_BLEND);
		glEnable(GL_DEPTH_TEST);
#endif
	}

	void Game::processTickCount() {

		if (lastTickCount == 0) {
			//lastTickCount = SDL_GetTicks64();
			lastTickCount = std::chrono::duration_cast<std::chrono::milliseconds>(
				std::chrono::system_clock::now().time_since_epoch()
			).count();

			lastTickCount = (lastTickCount / 50) * 50;

			return;
		}

		//newTickCount = SDL_GetTicks64();
		newTickCount = std::chrono::duration_cast<std::chrono::milliseconds>(
			std::chrono::system_clock::now().time_since_epoch()
		).count();

		newTickCount = (newTickCount / 50) * 50;

		if (newTickCount - lastTickCount > CONST_TIMER_INTERVAL) {

			size_t delta = newTickCount - lastTickCount;
			if (delta > CONST_MAX_TIME_INTERVAL)
			{
				//throw std::runtime_error("Synchronization is lost");
			}

			auto now_ms = newTickCount;

			sparkEmitter.update(static_cast<float>(delta));
			planetObject.update(static_cast<float>(delta));



			//Handle input:

			if (newShipVelocity != Environment::shipState.selectedVelocity)
			{
				Environment::shipState.selectedVelocity = newShipVelocity;

				std::string msg = "UPD:" + std::to_string(now_ms) + ":" + Environment::shipState.formPingMessageContent();
				networkClient->Send(msg);
			}

			float discreteMag;
			int discreteAngle;

			if (Environment::tapDownHold) {
				float diffx = Environment::tapDownCurrentPos(0) - Environment::tapDownStartPos(0);
				float diffy = Environment::tapDownCurrentPos(1) - Environment::tapDownStartPos(1);

				float rawMag = sqrtf(diffx * diffx + diffy * diffy);
				float maxRadius = 200.0f; // Максимальный вынос джойстика

				if (rawMag > 10.0f) { // Мертвая зона
					// 1. Дискретизируем отклонение (0.0 - 1.0 с шагом 0.1)
					float normalizedMag = min(rawMag / maxRadius, 1.0f);
					discreteMag = std::round(normalizedMag * 10.0f) / 10.0f;

					// 2. Дискретизируем угол (0-359 градусов)
					// atan2 возвращает радианы, переводим в градусы
					float radians = atan2f(diffy, diffx);
					discreteAngle = static_cast<int>(radians * 180.0f / M_PI);
					if (discreteAngle < 0) discreteAngle += 360;

				}
				else
				{
					discreteAngle = -1;
					discreteMag = 0.0f;
				}
			}
			else
			{
				discreteAngle = -1;
				discreteMag = 0.0f;
			}


			if (discreteAngle != Environment::shipState.discreteAngle || discreteMag != Environment::shipState.discreteMag) {
				Environment::shipState.discreteAngle = discreteAngle;
				Environment::shipState.discreteMag = discreteMag;

				std::string msg = "UPD:" + std::to_string(now_ms) + ":" + Environment::shipState.formPingMessageContent();
				networkClient->Send(msg);
				std::cout << "Sending: " << msg << std::endl;
			}

			long long leftoverDelta = delta;
			while (leftoverDelta > 0)
			{
				long long miniDelta = 50;
				Environment::shipState.simulate_physics(miniDelta);
				leftoverDelta -= miniDelta;
			}
			Environment::inverseShipMatrix = Environment::shipState.rotation.inverse();

			static float pingTimer = 0.0f;
			pingTimer += delta;
			if (pingTimer >= 1000.0f) {
				std::string pingMsg = "UPD:" + std::to_string(now_ms) + ":" + Environment::shipState.formPingMessageContent();

				networkClient->Send(pingMsg);
				std::cout << "Sending: " << pingMsg << std::endl;
				pingTimer = 0.0f;
			}


			auto latestRemotePlayers = networkClient->getRemotePlayers();

			std::chrono::system_clock::time_point nowRounded{ std::chrono::milliseconds(newTickCount) };


			for (auto const& [id, remotePlayer] : latestRemotePlayers) {

				if (!remotePlayer.canFetchClientStateAtTime(nowRounded))
				{
					continue;
				}

				ClientState playerState = remotePlayer.fetchClientStateAtTime(nowRounded);

				remotePlayerStates[id] = playerState;

			}

			for (auto& p : projectiles) {
				if (p && p->isActive()) {
					p->update(static_cast<float>(delta), renderer);
				}
			}

			std::vector<Vector3f> projCameraPoints;
			for (const auto& p : projectiles) {
				if (p && p->isActive()) {
					Vector3f worldPos = p->getPosition();
					Vector3f rel = worldPos - Environment::shipState.position;
					Vector3f camPos = Environment::inverseShipMatrix * rel;
					projCameraPoints.push_back(camPos);
				}
			}
			if (!projCameraPoints.empty()) {
				projectileEmitter.setEmissionPoints(projCameraPoints);
				projectileEmitter.emit();
			}
			else {
				projectileEmitter.setEmissionPoints(std::vector<Vector3f>());
			}

			std::vector<Vector3f> shipCameraPoints;
			for (const auto& lp : shipLocalEmissionPoints) {
				Vector3f adjusted = lp + Vector3f{ 0.0f, -Environment::zoom * 0.03f, 0.0f };
				shipCameraPoints.push_back(adjusted);
			}
			if (!shipCameraPoints.empty()) {
				sparkEmitter.setEmissionPoints(shipCameraPoints);
			}

			sparkEmitter.update(static_cast<float>(delta));
			projectileEmitter.update(static_cast<float>(delta));

			explosionEmitter.update(static_cast<float>(delta));
			if (showExplosion) {
				uint64_t now = SDL_GetTicks64();
				if (lastExplosionTime != 0 && now - lastExplosionTime >= explosionDurationMs) {
					showExplosion = false;
					explosionEmitter.setEmissionPoints(std::vector<Vector3f>());
					explosionEmitter.setUseWorldSpace(false);
				}
			}
			if (shipAlive) {
				float distToSurface = planetObject.distanceToPlanetSurface(Environment::shipState.position);
				if (distToSurface <= 0.0f) {

					Vector3f dir = (Environment::shipState.position - PlanetData::PLANET_CENTER_OFFSET).normalized();
					Vector3f collisionPoint = PlanetData::PLANET_CENTER_OFFSET + dir * PlanetData::PLANET_RADIUS;
					Environment::shipState.position = PlanetData::PLANET_CENTER_OFFSET + dir * (PlanetData::PLANET_RADIUS + shipCollisionRadius + 0.1f);

					shipAlive = false;
					gameOver = true;
					Environment::shipState.velocity = 0.0f;
					showExplosion = true;

					explosionEmitter.setUseWorldSpace(true);
					explosionEmitter.setEmissionPoints(std::vector<Vector3f>{ collisionPoint });
					explosionEmitter.emit();
					lastExplosionTime = SDL_GetTicks64();

					std::cerr << "GAME OVER: collision with planet (moved back and exploded)\n";

					if (!uiGameOverShown) {
						if (uiManager.pushMenuFromFile("resources/config/game_over.json", this->renderer, CONST_ZIP_FILE)) {
							uiManager.setButtonCallback("restartButton", [this](const std::string& name) {

								std::string respawnMsg = "RESPAWN:" + std::to_string(networkClient->GetClientId());
								networkClient->Send(respawnMsg);

								this->shipAlive = true;
								this->gameOver = false;
								this->uiGameOverShown = false;
								this->showExplosion = false;
								this->explosionEmitter.setEmissionPoints(std::vector<Vector3f>());
								this->deadRemotePlayers.clear();

								Environment::shipState.position = Vector3f{ 0, 0, 45000.f };
								Environment::shipState.velocity = 0.0f;
								Environment::shipState.rotation = Eigen::Matrix3f::Identity();
								Environment::inverseShipMatrix = Eigen::Matrix3f::Identity();
								Environment::zoom = DEFAULT_ZOOM;
								Environment::tapDownHold = false;

								uiManager.popMenu();
								std::cerr << "Game restarted\n";
								});

							uiManager.setButtonCallback("gameOverExitButton", [this](const std::string& name) {
								Environment::exitGameLoop = true;
								});

							uiGameOverShown = true;
						}
						else {
							std::cerr << "Failed to load game_over.json\n";
						}
					}
				}
				else {
					bool stoneCollided = false;
					int collidedTriIdx = -1;
					Vector3f collidedStonePos = Vector3f{ 0.0f, 0.0f, 0.0f };
					float collidedStoneRadius = 0.0f;

					for (int triIdx : planetObject.triangleIndicesToDraw) {
						if (triIdx < 0 || triIdx >= static_cast<int>(planetObject.planetStones.allInstances.size()))
							continue;

						if (planetObject.planetStones.statuses.size() <= static_cast<size_t>(triIdx))
							continue;

						if (planetObject.planetStones.statuses[triIdx] != ChunkStatus::Live)
							continue;

						const auto& instances = planetObject.planetStones.allInstances[triIdx];
						for (const auto& inst : instances) {

							Vector3f stoneWorld = inst.position;
							Vector3f diff = Environment::shipState.position - stoneWorld;

							float maxScale = (std::max)({ inst.scale(0), inst.scale(1), inst.scale(2) });
							float stoneRadius = StoneParams::BASE_SCALE * maxScale * 0.9f;
							float thresh = shipCollisionRadius + stoneRadius;

							if (diff.squaredNorm() <= thresh * thresh) {
								stoneCollided = true;
								collidedTriIdx = triIdx;
								collidedStonePos = stoneWorld;
								collidedStoneRadius = stoneRadius;
								break;
							}
						}

						if (stoneCollided) break;
					}

					if (stoneCollided) {
						Vector3f away = (Environment::shipState.position - collidedStonePos);
						if (away.squaredNorm() <= 1e-6f) {
							away = Vector3f{ 0.0f, 1.0f, 0.0f };
						}
						away.normalize();

						Environment::shipState.position = collidedStonePos + away * (collidedStoneRadius + shipCollisionRadius + 0.1f);

						shipAlive = false;
						gameOver = true;
						Environment::shipState.velocity = 0.0f;
						showExplosion = true;

						explosionEmitter.setUseWorldSpace(true);
						explosionEmitter.setEmissionPoints(std::vector<Vector3f>{ collidedStonePos });
						explosionEmitter.emit();
						lastExplosionTime = SDL_GetTicks64();

						std::cerr << "GAME OVER: collision with stone on triangle " << collidedTriIdx << std::endl;

						if (collidedTriIdx >= 0 && collidedTriIdx < static_cast<int>(planetObject.stonesToRender.size())) {
							planetObject.stonesToRender[collidedTriIdx].data.PositionData.clear();
							planetObject.stonesToRender[collidedTriIdx].vao.reset();
							planetObject.stonesToRender[collidedTriIdx].positionVBO.reset();
							planetObject.stonesToRender[collidedTriIdx].normalVBO.reset();
							planetObject.stonesToRender[collidedTriIdx].tangentVBO.reset();
							planetObject.stonesToRender[collidedTriIdx].binormalVBO.reset();
							planetObject.stonesToRender[collidedTriIdx].colorVBO.reset();
							planetObject.stonesToRender[collidedTriIdx].texCoordVBO.reset();
						}
						if (collidedTriIdx >= 0 && collidedTriIdx < static_cast<int>(planetObject.planetStones.statuses.size())) {
							planetObject.planetStones.statuses[collidedTriIdx] = ChunkStatus::Empty;
						}

						if (!uiGameOverShown) {
							if (uiManager.pushMenuFromFile("resources/config/game_over.json", this->renderer, CONST_ZIP_FILE)) {
								uiManager.setButtonCallback("restartButton", [this](const std::string& name) {
									this->shipAlive = true;
									this->gameOver = false;
									this->uiGameOverShown = false;
									this->showExplosion = false;
									this->explosionEmitter.setEmissionPoints(std::vector<Vector3f>());
									Environment::shipState.position = Vector3f{ 0, 0, 45000.f };
									Environment::shipState.velocity = 0.0f;
									Environment::shipState.rotation = Eigen::Matrix3f::Identity();
									Environment::inverseShipMatrix = Eigen::Matrix3f::Identity();
									Environment::zoom = DEFAULT_ZOOM;
									Environment::tapDownHold = false;
									uiManager.popMenu();
									std::cerr << "Game restarted\n";
									});

								uiManager.setButtonCallback("gameOverExitButton", [this](const std::string& name) {
									Environment::exitGameLoop = true;
									});

								uiGameOverShown = true;
							}
							else {
								std::cerr << "Failed to load game_over.json\n";
							}
						}
					}
				}
			}

			/*for (int i = 0; i < boxCoordsArr.size(); ++i) {
				if (!boxAlive[i]) continue;
				Vector3f boxWorld = boxCoordsArr[i].pos + Vector3f{ 0.0f, 0.0f, 45000.0f };
				Vector3f diff = Environment::shipState.position - boxWorld;
				float thresh = shipCollisionRadius + boxCollisionRadius;
				if (diff.squaredNorm() <= thresh * thresh) {
					boxAlive[i] = false;

					boxRenderArr[i].data.PositionData.clear();
					boxRenderArr[i].vao.reset();
					boxRenderArr[i].positionVBO.reset();
					boxRenderArr[i].texCoordVBO.reset();
					showExplosion = true;

					Vector3f rel = boxWorld - Environment::shipState.position;
					Vector3f camPos = Environment::inverseShipMatrix * rel;
					explosionEmitter.setUseWorldSpace(true);
					explosionEmitter.setEmissionPoints(std::vector<Vector3f>{ boxWorld });
					explosionEmitter.emit();
					lastExplosionTime = SDL_GetTicks64();

					std::cerr << "Box destroyed at index " << i << std::endl;
				}
			}

			const float projectileHitRadius = 1.5f;
			for (auto& p : projectiles) {
				if (!p || !p->isActive()) continue;
				Vector3f ppos = p->getPosition();
				Vector3f projInBoxSpace = Environment::inverseShipMatrix * (ppos - Environment::shipState.position);
				for (int i = 0; i < boxCoordsArr.size(); ++i) {
					if (!boxAlive[i]) continue;
					Vector3f boxWorld = boxCoordsArr[i].pos + Vector3f{ 0.0f, 6.0f, 45000.0f };
					Vector3f dd = ppos - boxWorld;
					float thresh = boxCollisionRadius + projectileHitRadius;
					if (dd.squaredNorm() <= thresh * thresh) {
						boxAlive[i] = false;
						boxRenderArr[i].data.PositionData.clear();
						boxRenderArr[i].vao.reset();
						boxRenderArr[i].positionVBO.reset();
						boxRenderArr[i].texCoordVBO.reset();

						showExplosion = true;
						explosionEmitter.setUseWorldSpace(true);
						explosionEmitter.setEmissionPoints(std::vector<Vector3f>{ boxWorld });
						explosionEmitter.emit();
						lastExplosionTime = SDL_GetTicks64();

						p->deactivate();
						std::cerr << "Box destroyed by projectile at index " << i << std::endl;
						break;
					}
				}
			}*/

			// update velocity text
			if (shipAlive && !gameOver) {
				auto velocityTv = uiManager.findTextView("velocityText");
				if (velocityTv) {
					std::string velocityStr = "Velocity: " + std::to_string(static_cast<int>(Environment::shipState.velocity));
					uiManager.setText("velocityText", velocityStr);
				}
			}

			uiManager.update(static_cast<float>(delta));
			lastTickCount = newTickCount;
		}
	}

	void Game::fireProjectiles() {
		std::vector<Vector3f> localOffsets = {
			Vector3f{ -1.5f, 0.9f, 5.0f },
			Vector3f{ 1.5f, 0.9f, 5.0f }
		};

		const float projectileSpeed = 60.0f;
		const float lifeMs = 5000.0f;
		const float size = 0.5f;

		Vector3f localForward = { 0,0,-1 };
		Vector3f worldForward = (Environment::shipState.rotation * localForward).normalized();

		for (const auto& lo : localOffsets) {
			Vector3f worldPos = Environment::shipState.position + Environment::shipState.rotation * lo;
			Vector3f worldVel = worldForward * (projectileSpeed + Environment::shipState.velocity);

			for (auto& p : projectiles) {
				if (!p->isActive()) {
					p->init(worldPos, worldVel, lifeMs, size, projectileTexture, renderer);
					break;
				}
			}
		}
	}

	void Game::render() {
		SDL_GL_MakeCurrent(ZL::Environment::window, glContext);
		ZL::CheckGlError();

		glClearColor(0.0f, 1.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;
			}
#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) {
				// Координаты Finger в SDL нормализованы от 0.0 до 1.0
				int mx = static_cast<int>(event.tfinger.x * Environment::width);
				int my = static_cast<int>(event.tfinger.y * Environment::height);
				handleDown(mx, my);
			}
			else if (event.type == SDL_FINGERUP) {
				int mx = static_cast<int>(event.tfinger.x * Environment::width);
				int my = static_cast<int>(event.tfinger.y * Environment::height);
				handleUp(mx, my);
			}
			else if (event.type == SDL_FINGERMOTION) {
				int mx = static_cast<int>(event.tfinger.x * Environment::width);
				int my = static_cast<int>(event.tfinger.y * Environment::height);
				handleMotion(mx, my);
			}
#else

			if (event.type == SDL_MOUSEBUTTONDOWN) {
				int mx = event.button.x;
				int my = event.button.y;
				handleDown(mx, my);
			}
			if (event.type == SDL_MOUSEBUTTONUP) {

				int mx = event.button.x;
				int my = event.button.y;
				handleUp(mx, my);
			}
			if (event.type == SDL_MOUSEMOTION) {

				int mx = event.motion.x;
				int my = event.motion.y;
				handleMotion(mx, my);

			}

			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_KEYUP)
			{
				if (event.key.keysym.sym == SDLK_i)
				{
					x = x + 1;
				}
				if (event.key.keysym.sym == SDLK_k)
				{
					x = x - 1;
				}
				if (event.key.keysym.sym == SDLK_a)
				{
					Environment::shipState.position = { 9466.15820, 1046.00159, 18531.2090 };
				}
			}
#endif
		}
		render();
		mainThreadHandler.processMainThreadTasks();
		networkClient->Poll();

		if (networkClient) {
			auto pending = networkClient->getPendingProjectiles();
			if (!pending.empty()) {
				const float projectileSpeed = 60.0f;
				const float lifeMs = 5000.0f;
				const float size = 0.5f;
				for (const auto& pi : pending) {
					const std::vector<Vector3f> localOffsets = {
						Vector3f{ -1.5f, 0.9f, 5.0f },
						Vector3f{  1.5f, 0.9f, 5.0f }
						//Vector3f{},
						//Vector3f{}
					};

					Vector3f localForward = { 0, 0, -1 };
					Vector3f worldForward = pi.rotation * localForward;

					float len = worldForward.norm();
					if (len <= 1e-6f) {
						continue;
					}
					worldForward /= len;

					Vector3f baseVel = worldForward * pi.velocity;

					for (const auto& off : localOffsets) {
						Vector3f shotPos = pi.position + (pi.rotation * off);

						for (auto& p : projectiles) {
							if (!p->isActive()) {
								p->init(shotPos, baseVel, lifeMs, size, projectileTexture, renderer);
								break;
							}
						}
					}
				}
				/*
				auto remotePlayersSnapshot = networkClient->getRemotePlayers();
				for (const auto& pi : pending) {
					Eigen::Vector3f dir = pi.direction;
					float len = dir.norm();
					if (len <= 1e-6f) continue;
					dir /= len;

					Eigen::Matrix3f shooterRot = Eigen::Matrix3f::Identity();
					float shooterVel = 0.0f;
					auto it = remotePlayersSnapshot.find(pi.shooterId);
					if (it != remotePlayersSnapshot.end()) {
						std::chrono::system_clock::time_point pktTime{ std::chrono::milliseconds(pi.clientTime) };
						if (it->second.canFetchClientStateAtTime(pktTime)) {
							ClientState shooterState = it->second.fetchClientStateAtTime(pktTime);
							shooterRot = shooterState.rotation;
							shooterVel = shooterState.velocity;
						}
					}

					float speedWithOwner = projectileSpeed + shooterVel;
					Eigen::Vector3f baseVel = dir * speedWithOwner;

					int shotCount = 2;

					std::vector<Eigen::Vector3f> localOffsets = {
						{-1.5f, 0.9f, 5.0f},
						{ 1.5f, 0.9f, 5.0f}
					};

					for (int i = 0; i < shotCount; ++i) {
						Eigen::Vector3f rotatedOffset = shooterRot * localOffsets[i];
						Eigen::Vector3f shotPos = pi.position + rotatedOffset;

						for (auto& p : projectiles) {
							if (!p->isActive()) {
								p->init(shotPos, baseVel, lifeMs, size, projectileTexture, renderer);
								break;
							}
						}
					}
				}*/
			}
			// Обработка событий смерти, присланных сервером
			auto deaths = networkClient->getPendingDeaths();
			if (!deaths.empty()) {
				int localId = networkClient->GetClientId();
				std::cout << "Client: Received " << deaths.size() << " death events" << std::endl;

				for (const auto& d : deaths) {
					std::cout << "Client: Processing death - target=" << d.targetId
						<< ", killer=" << d.killerId << ", pos=("
						<< d.position.x() << ", " << d.position.y() << ", " << d.position.z() << ")" << std::endl;

					showExplosion = true;
					explosionEmitter.setUseWorldSpace(true);
					explosionEmitter.setEmissionPoints(std::vector<Vector3f>{ d.position });
					explosionEmitter.emit();
					lastExplosionTime = SDL_GetTicks64();
					std::cout << "Client: Explosion emitted at (" << d.position.x() << ", "
						<< d.position.y() << ", " << d.position.z() << ")" << std::endl;

					if (d.targetId == localId) {
						std::cout << "Client: Local ship destroyed!" << std::endl;
						shipAlive = false;
						gameOver = true;
						Environment::shipState.velocity = 0.0f;

						if (!uiGameOverShown) {
							if (uiManager.pushMenuFromFile("resources/config/game_over.json",
								this->renderer, CONST_ZIP_FILE)) {
								uiManager.setButtonCallback("restartButton", [this](const std::string& name) {
									std::cout << "Client: Restart button clicked, sending RESPAWN" << std::endl;

									std::string respawnMsg = "RESPAWN:" + std::to_string(networkClient->GetClientId());
									networkClient->Send(respawnMsg);

									this->shipAlive = true;
									this->gameOver = false;
									this->uiGameOverShown = false;
									this->showExplosion = false;
									this->explosionEmitter.setEmissionPoints(std::vector<Vector3f>());
									this->deadRemotePlayers.clear();

									Environment::shipState.position = Vector3f{ 0, 0, 45000.f };
									Environment::shipState.velocity = 0.0f;
									Environment::shipState.rotation = Eigen::Matrix3f::Identity();
									Environment::inverseShipMatrix = Eigen::Matrix3f::Identity();
									Environment::zoom = DEFAULT_ZOOM;
									Environment::tapDownHold = false;

									uiManager.popMenu();
									std::cerr << "Game restarted\n";
									});

								uiManager.setButtonCallback("gameOverExitButton", [this](const std::string& name) {
									Environment::exitGameLoop = true;
									});

								uiGameOverShown = true;
							}
						}
					}
					else {
						deadRemotePlayers.insert(d.targetId);
						std::cout << "Marked remote player " << d.targetId << " as dead" << std::endl;
					}
				}
			}

			auto respawns = networkClient->getPendingRespawns();
			if (!respawns.empty()) {
				for (const auto& respawnId : respawns) {
					deadRemotePlayers.erase(respawnId);

					auto it = remotePlayerStates.find(respawnId);
					if (it != remotePlayerStates.end()) {
						it->second.position = Vector3f{ 0.f, 0.f, 45000.f };
						it->second.velocity = 0.0f;
						it->second.rotation = Eigen::Matrix3f::Identity();
					}

					std::cout << "Client: Remote player " << respawnId << " respawned, removed from dead list" << std::endl;
				}
			}

			auto boxDestructions = networkClient->getPendingBoxDestructions();
			if (!boxDestructions.empty()) {
				std::cout << "Game: Received " << boxDestructions.size() << " box destruction events" << std::endl;

				for (const auto& destruction : boxDestructions) {
					int idx = destruction.boxIndex;

					if (idx >= 0 && idx < (int)boxCoordsArr.size()) {
						if (boxAlive[idx]) {
							boxAlive[idx] = false;

							boxRenderArr[idx].data.PositionData.clear();
							boxRenderArr[idx].vao.reset();
							boxRenderArr[idx].positionVBO.reset();
							boxRenderArr[idx].texCoordVBO.reset();

							showExplosion = true;
							explosionEmitter.setUseWorldSpace(true);
							explosionEmitter.setEmissionPoints(std::vector<Vector3f>{ destruction.position });
							explosionEmitter.emit();
							lastExplosionTime = SDL_GetTicks64();

							std::cout << "Game: Box " << idx << " destroyed by player "
								<< destruction.destroyedBy << std::endl;
						}
					}
				}
			}
		}
	}

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

		uiManager.onMouseDown(uiX, uiY);

		bool uiHandled = false;

		for (const auto& button : uiManager.findButton("") ? std::vector<std::shared_ptr<UiButton>>{} : std::vector<std::shared_ptr<UiButton>>{}) {
			(void)button;
		}

		auto pressedSlider = [&]() -> std::shared_ptr<UiSlider> {
			for (const auto& slider : uiManager.findSlider("") ? std::vector<std::shared_ptr<UiSlider>>{} : std::vector<std::shared_ptr<UiSlider>>{}) {
				(void)slider;
			}
			return nullptr;
			}();

		if (!uiManager.isUiInteraction()) {
			Environment::tapDownHold = true;

			Environment::tapDownStartPos(0) = mx;
			Environment::tapDownStartPos(1) = my;

			Environment::tapDownCurrentPos(0) = mx;
			Environment::tapDownCurrentPos(1) = my;
		}
	}

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

		uiManager.onMouseUp(uiX, uiY);

		if (!uiManager.isUiInteraction()) {
			Environment::tapDownHold = false;
		}
	}

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

		uiManager.onMouseMove(uiX, uiY);

		if (Environment::tapDownHold && !uiManager.isUiInteraction()) {
			Environment::tapDownCurrentPos(0) = mx;
			Environment::tapDownCurrentPos(1) = my;
		}
	}

	/*
	std::string Game::formPingMessageContent()
	{
		Eigen::Quaternionf q(Environment::shipMatrix);

		std::string pingMsg = std::to_string(Environment::shipPosition.x()) + ":"
			+ std::to_string(Environment::shipPosition.y()) + ":"
			+ std::to_string(Environment::shipPosition.z()) + ":"
			+ std::to_string(q.w()) + ":"
			+ std::to_string(q.x()) + ":"
			+ std::to_string(q.y()) + ":"
			+ std::to_string(q.z()) + ":"
			+ std::to_string(Environment::currentAngularVelocity.x()) + ":"
			+ std::to_string(Environment::currentAngularVelocity.y()) + ":"
			+ std::to_string(Environment::currentAngularVelocity.z()) + ":"
			+ std::to_string(Environment::shipVelocity) + ":"
			+ std::to_string(Environment::shipSelectedVelocity) + ":"
			+ std::to_string(Environment::lastSentMagnitude) + ":" // Используем те же static переменные из блока ROT
			+ std::to_string(Environment::lastSentAngle);

		return pingMsg;
	}*/


}  // namespace ZL
