#pragma once

#include "render/Renderer.h"
#include "Environment.h"
#include "render/TextureManager.h"
#include "render/TextRenderer.h"
#include "items/GameObjectLoader.h"
#include "items/InteractiveObject.h"
#include "navigation/PathFinder.h"
#include "render/ShadowMap.h"

#include "ScriptEngine.h"
#include "dialogue/DialogueSystem.h"
#include "SparkEmitter.h"
#include "TeleportZone.h"
#include <functional>
#include <cstdint>
#include <unordered_map>
namespace ZL
{

	struct TriggerZone
	{
		std::string id;
		Eigen::Vector3f position = Eigen::Vector3f::Zero();
		float radius = 1.5f;
		float hysteresis = 0.3f;  // exit fires at radius + hysteresis to prevent flickering
		bool enabled = true;
		bool playerInside = false; // runtime tracking state, not serialized
	};

	struct LocationSetup
	{
		std::string gameObjectsJsonPath;
		std::string interactiveObjectsJsonPath;
		std::string npcsJsonPath;
		std::string dialoguesJsonPath;
		std::vector<std::string> navigationJsonPaths;
		std::string scriptPath;
		std::string teleportsJsonPath;
		std::string triggerZonesJsonPath;
		Eigen::Vector3f playerPosition = Eigen::Vector3f::Zero();
	};

	class Location
	{
	public:
		Location(Renderer& iRenderer, Inventory& iInventory);

		std::unordered_map<std::string, LoadedGameObject> gameObjects;

		std::vector<InteractiveObject> interactiveObjects;


		std::unique_ptr<Character> player;
		std::vector<std::unique_ptr<Character>> npcs;

		float cameraAzimuth = 0.0f;
		float cameraInclination = M_PI * 30.f / 180.f;

		std::vector<PathFinder> navigationMaps;
		PathFinder* navigation = nullptr;
		int activeNavigationIndex = 0;

		std::unique_ptr<ShadowMap> shadowMap;
		Eigen::Matrix4f cameraViewMatrix = Eigen::Matrix4f::Identity();
		InteractiveObject* targetInteractiveObject = nullptr;

		// "Walk to NPC, then fire on_npc_interact" — mirrors targetInteractiveObject
		// so a click from outside interactionRadius still leads to a Lua callback
		// once the player gets close enough.
		Character* targetInteractNpc = nullptr;
		int targetInteractNpcIndex = -1;

		std::unique_ptr<TextRenderer> npcNameText;

		ScriptEngine scriptEngine;
		Dialogue::DialogueSystem dialogueSystem;

		std::vector<TeleportZone> teleportZones;
		TeleportZone* targetTeleportZone = nullptr;
		std::function<void(const std::string&, const Eigen::Vector3f&, float)> onTeleport;

		std::vector<TriggerZone> triggerZones;

		// Set by Game and kept in sync across location transitions.
		// Read by draw functions and raycast — do not write from Location code.
		bool isDarklands = false;

		// Set by Game after setup(). Lua and onDeathAnimComplete call this to
		// request the transition without coupling Location to Game directly.
		std::function<bool()> requestDarklandsTransition;

		// Navigation editor — toggle with 'N', save with 'B', right-click to finalize polygon
		bool navigationEditorMode = false;

		std::vector<Eigen::Vector3f> navigationEditorPoints;
		VertexRenderStruct            navigationEditorPointsMesh;
		std::vector<VertexRenderStruct> navigationEditorNavMeshes;
		std::vector<std::string> navigationMapPaths;
		int         navigationEditorObstacleCounter = 0;

		void navigationEditorBuildNavMeshes();
		void navigationEditorDrawNavigation();
		void navigationEditorRebuildPointsMesh();
		void navigationEditorDrawPoints();
		void navigationEditorHandleLeftClick(const Eigen::Vector3f& hit, bool ctrlHeld);
		void navigationEditorHandleRightClick();
		void navigationEditorSave();
		void navigationEditorReload();

		void gameObjectEditorPlaceTree();
		void gameObjectEditorSave();

		std::vector<GameObjectData> editorPlacedObjects;
		int editorPlacedObjectCounter = 0;

		// Set by Game when the user's primary pointer (left mouse / single touch)
		// has crossed the tap-vs-drag threshold and is now rotating the camera.
		bool cameraDragging = false;
		int lastMouseX = 0;
		int lastMouseY = 0;


		void setup(const LocationSetup& params);
		void setupNavigation(const std::vector<std::string>& paths);
		bool switchNavigation(int index);
		InteractiveObject* raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir);
		Character* raycastNpcs(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir, float maxDistance = 100.0f);
	
		void drawGame();
		void drawShadowDepthPass();
		void drawGameWithShadows();
		void drawGameDarklands();

		bool setNavigationAreaAvailable(const std::string& areaName, bool available);

		void update(int64_t deltaMs);

		void handleDown(int64_t fingerId, int eventX, int eventY, int mx, int my);
		void handleUp(int64_t fingerId, int mx, int my);
		void handleMotion(int64_t fingerId, int eventX, int eventY, int mx, int my);

		bool requestDialogueStart(const std::string& dialogueId);
		bool requestCutsceneStart(const std::string& cutsceneId);
		void setOnCutsceneFinished(std::function<void(const std::string&)> cb);
		void setDialogueFlag(const std::string& flag, int value);
		int getDialogueFlag(const std::string& flag) const;

	protected:
		Renderer& renderer;
		Inventory& inventory;		

	private:
		void resolveCharacterCollisions();
		void updateDynamicReplans(int64_t deltaMs);
		void loadTeleportZones(const std::string& jsonPath, const char* zipFile);
		void loadTriggerZones(const std::string& jsonPath, const char* zipFile);
		void updateTriggerZones(const Eigen::Vector3f& playerPos);
		void nudgeCharacterAside(Character* standing, const Eigen::Vector3f& awayFrom);
		int  findNpcIndex(const Character* npc) const;
		void fireBumpCallbacks(Character* mover, Character* standing);

		std::unordered_map<Character*, Eigen::Vector3f> lastCharacterPositions;
		std::unordered_map<Character*, int64_t> replanCooldownRemainingMs;

		static constexpr float NPC_BUMP_CALLBACK_COOLDOWN = 5.0f;
		std::unordered_map<int, float> npcBumpedByPlayerCooldown;
		std::unordered_map<int, float> npcBumpsPlayerCooldown;
	};

} // namespace ZL
