#pragma once

#include "render/Renderer.h"
#include "render/TextureManager.h"
#include <vector>
#include <chrono>
#include <string>

namespace ZL {

	struct SparkParticle {
		Vector3f position;
		Vector3f velocity;
		float scale;
		float lifeTime;
		float maxLifeTime;
		bool active;
		int emitterIndex;

		SparkParticle() : position({ 0,0,0 }), velocity({ 0,0,0 }), scale(1.0f),
			lifeTime(0), maxLifeTime(1000.0f), active(false), emitterIndex(0) {
		}
	};

	class SparkEmitter {
	private:
		std::vector<SparkParticle> particles;
		std::vector<Vector3f> emissionPoints;
		std::vector<Vector3f> prevEmissionPoints;
		std::chrono::steady_clock::time_point lastEmissionTime;
		float emissionRate;
		bool isActive;

		std::vector<Vector3f> drawPositions;
		std::vector<Vector2f> drawTexCoords;
		bool drawDataDirty;
		VertexRenderStruct sparkQuad;
		std::shared_ptr<Texture> texture;

		int maxParticles;
		float particleSize;
		float biasX;

		// Ranges (used when config supplies intervals)
		struct FloatRange { float min=0; float max=0; };
		FloatRange speedRange;    // XY speed
		FloatRange zSpeedRange;   // Z speed
		FloatRange scaleRange;
		FloatRange lifeTimeRange;

		std::string shaderProgramName;

		// Sparks spawn within this radius of each emission point. 0 means
		// "spawn exactly at the point" (legacy behavior).
		float emissionRadius;

		bool configured;
		// Rebuilds the CPU-side quad/UV arrays. If cameraViewMatrix is identity,
		// particles are rendered as axis-aligned quads in world XY and sorted by
		// world Z (legacy behavior). Otherwise quads are billboarded toward the
		// camera using its world-space right/up axes, and particles are sorted
		// back-to-front in eye space for correct alpha blending.
		void prepareDrawData(const Eigen::Matrix4f& cameraViewMatrix);
		bool useWorldSpace;

		// When non-zero, velocities spray in a cone around this direction
		// instead of the default planar XY + Z spread used by JSON emitters.
		Vector3f emissionDirection = Vector3f{ 0.f, 0.f, 0.f };

	public:
		SparkEmitter();
		SparkEmitter(const SparkEmitter& copyFrom);
		SparkEmitter(const std::vector<Vector3f>& positions, float rate = 100.0f);
		SparkEmitter(const std::vector<Vector3f>& positions,
			std::shared_ptr<Texture> tex,
			float rate = 100.0f);

		void setEmissionPoints(const std::vector<Vector3f>& positions);
		void resetEmissionPoints(const std::vector<Vector3f>& positions);
		void setTexture(std::shared_ptr<Texture> tex);
		void setEmissionRate(float rate) { emissionRate = rate; }
		void setShaderProgramName(const std::string& name) { shaderProgramName = name; }
		void setMaxParticles(int max) { maxParticles = max; particles.resize(maxParticles); }
		void setParticleSize(float size) { particleSize = size; }
		void setBiasX(float bias) { biasX = bias; }

		void setUseWorldSpace(bool use) { useWorldSpace = use; }
		bool loadFromJsonFile(const std::string& path, Renderer& renderer, const std::string& zipFile = "");

		// Programmatic configuration (alternative to loadFromJsonFile).
		// Ranges follow the same semantics as the JSON fields.
		void setSpeedRange(float minVal, float maxVal) { speedRange.min = minVal; speedRange.max = maxVal; }
		void setZSpeedRange(float minVal, float maxVal) { zSpeedRange.min = minVal; zSpeedRange.max = maxVal; }
		void setScaleRange(float minVal, float maxVal) { scaleRange.min = minVal; scaleRange.max = maxVal; }
		void setLifeTimeRange(float minVal, float maxVal) { lifeTimeRange.min = minVal; lifeTimeRange.max = maxVal; }
		void setEmissionDirection(const Vector3f& dir) { emissionDirection = dir; }
		// 0 = spawn exactly at the emission point; >0 = spawn at a uniform random
		// point inside a 3D ball of this radius around the emission point.
		void setEmissionRadius(float r) { emissionRadius = r; }
		void markConfigured() { configured = true; }
		bool isConfigured() const { return configured; }

		void update(float deltaTimeMs);
		void emit(float ageMs = 0.0f, float lerpT = 0.0f);

		// Вызывать ДО draw() в начале кадра: готовит данные и загружает в VBO.
		// Pass the current camera view matrix (world → eye) to billboard sparks
		// toward the camera and sort them back-to-front. Identity (default)
		// keeps the legacy non-billboarded behavior.
		void prepareForDraw(const Eigen::Matrix4f& cameraViewMatrix = Eigen::Matrix4f::Identity());

		void draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight);
		void draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight, bool withRotation);

		const std::vector<SparkParticle>& getParticles() const;
		size_t getActiveParticleCount() const;
		std::shared_ptr<Texture> getTexture() const { return texture; }

		void setIsActive(bool newActive)
		{
			isActive = newActive;
		}

	private:
		void initParticle(SparkParticle& particle, int emitterIndex);
		Vector3f getRandomVelocity(int emitterIndex);
	};

} // namespace ZL