#ifndef EDITSCENE_CHARACTERREGISTRY_HPP
#define EDITSCENE_CHARACTERREGISTRY_HPP
#pragma once

#include <Ogre.h>
#include <flecs.h>
#include <nlohmann/json.hpp>
#include <cstdint>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>

#include "MarkovNameGenerator.hpp"
#include "../components/CharacterSlots.hpp"

class EditorUISystem;
class CharacterSlotSystem;

/**
 * Global character and social-group registry.
 *
 * Characters are referenced by a persistent uint64_t ID.  Each character
 * stores a path to its appearance prefab (CharacterSlotsComponent data).
 *
 * The registry auto-saves to a fixed file after every mutation and loads
 * on initialization.  Character prefabs are auto-named from the ID and
 * name, created from templates, and deleted along with the character.
 */
class CharacterRegistry {
public:
	static CharacterRegistry &getSingleton();
	static CharacterRegistry *getSingletonPtr();

private:
	static CharacterRegistry *ms_singleton;

public:
	/* ------------------------------------------------------------------ */
	/*  Column schema                                                       */
	/* ------------------------------------------------------------------ */
	struct ColumnDef {
		enum Type { Int, Float, String };
		Type type;
		std::string name;
	};

	/* ------------------------------------------------------------------ */
	/*  Records                                                             */
	/* ------------------------------------------------------------------ */
	struct CharacterRecord {
		uint64_t id = 0;
		std::string firstName;
		std::string lastName;
		std::string prefabPath; /* relative path to prefab JSON */

		/* RPG data (authoritative source for class, level, stats, etc.) */
		std::string className;
		int level = 1;
		int64_t currentXP = 0;
		int availablePoints = 0;
		std::unordered_map<std::string, int> stats;
		std::unordered_map<std::string, int> skills;
		std::unordered_map<std::string, int> needs;
		std::unordered_map<std::string, int> currentPools;
		bool levelUpPending = false;

		/* Age category string (e.g. "adult", "child", "elder") */
		std::string age = "adult";

		/* Age in years (runtime progression) */
		int ageYears = 0;

		/* Inline appearance (authoritative source for character visuals) */
		std::string inlineSex = "male";
		int inlineOutfitLevel = 2;
		std::unordered_map<std::string, SlotSelection>
			inlineSlotSelections;
		std::unordered_map<std::string, float> inlineShapeKeyWeights;
		Ogre::Vector3 frontAxis = Ogre::Vector3::NEGATIVE_UNIT_Z;

		/* Runtime-only characters are NOT saved to character_registry.json */
		bool persistent = true;

		/* Spawn position/rotation (persisted in save games) */
		Ogre::Vector3 position = Ogre::Vector3::ZERO;
		Ogre::Quaternion rotation = Ogre::Quaternion::IDENTITY;

		/* Pregnancy state (progresses whether spawned or not) */
		uint64_t pregnantByFatherId = 0;
		float pregnancyProgress = 0.0f;
		float pregnancyMaxProgress = 0.0f;
		float basePregnancyDuration = 0.0f; /* 0 = use global */

		/* Tags */
		std::vector<std::string> tags;

		/* Extensible custom columns */
		std::unordered_map<std::string, int64_t> intColumns;
		std::unordered_map<std::string, double> floatColumns;
		std::unordered_map<std::string, std::string> stringColumns;

		/* Version counter incremented when the record changes in ways that
		 * require a respawn of distance-spawned characters. Runtime-only. */
		uint64_t version = 0;
	};

	struct GroupRecord {
		uint64_t id = 0;
		std::string name;
		std::vector<uint64_t> memberIds;

		/* Extensible custom columns */
		std::unordered_map<std::string, int64_t> intColumns;
		std::unordered_map<std::string, double> floatColumns;
		std::unordered_map<std::string, std::string> stringColumns;
	};

	struct Relationship {
		uint64_t sourceId = 0;
		uint64_t targetId = 0;
		bool sourceIsGroup = false;
		bool targetIsGroup = false;

		/* Numeric stats: friendship, love, hate, loyalty, fear … */
		std::unordered_map<std::string, float> stats;

		/* Tags: wife, sibling, parent, enemy, ally, mentor … */
		std::unordered_set<std::string> tags;
	};

	/* ------------------------------------------------------------------ */
	/*  Life-cycle                                                          */
	/* ------------------------------------------------------------------ */
	CharacterRegistry();
	~CharacterRegistry() = default;

	/* non-copyable */
	CharacterRegistry(const CharacterRegistry &) = delete;
	CharacterRegistry &operator=(const CharacterRegistry &) = delete;

	void setWorld(flecs::world *world)
	{
		m_world = world;
	}
	void setSceneManager(Ogre::SceneManager *sceneMgr)
	{
		m_sceneMgr = sceneMgr;
	}
	void setEditorUISystem(EditorUISystem *ui)
	{
		m_uiSystem = ui;
	}
	void setCharacterSlotSystem(CharacterSlotSystem *slotSystem)
	{
		m_slotSystem = slotSystem;
	}

	/**
	 * Load registry from auto-save file.  Call after setWorld/setSceneManager.
	 */
	void initialize();

	/* ------------------------------------------------------------------ */
	/*  Characters                                                          */
	/* ------------------------------------------------------------------ */
	uint64_t createCharacter(const std::string &firstName,
				 const std::string &lastName,
				 const std::string &templatePath = "",
				 bool persistent = true);
	void deleteCharacter(uint64_t id);
	CharacterRecord *findCharacter(uint64_t id);
	const CharacterRecord *findCharacter(uint64_t id) const;
	const std::unordered_map<uint64_t, CharacterRecord> &
	getCharacters() const
	{
		return m_characters;
	}

	/* ------------------------------------------------------------------ */
	/*  Groups / Factions                                                   */
	/* ------------------------------------------------------------------ */
	uint64_t createGroup(const std::string &name);
	void deleteGroup(uint64_t id);
	void addToGroup(uint64_t groupId, uint64_t characterId);
	void removeFromGroup(uint64_t groupId, uint64_t characterId);
	GroupRecord *findGroup(uint64_t id);
	const GroupRecord *findGroup(uint64_t id) const;
	const std::unordered_map<uint64_t, GroupRecord> &getGroups() const
	{
		return m_groups;
	}

	/* ------------------------------------------------------------------ */
	/*  Relationships                                                       */
	/* ------------------------------------------------------------------ */
	Relationship *findRelationship(uint64_t sourceId, bool sourceIsGroup,
				       uint64_t targetId, bool targetIsGroup);
	const Relationship *findRelationship(uint64_t sourceId,
					     bool sourceIsGroup,
					     uint64_t targetId,
					     bool targetIsGroup) const;

	void setRelationshipStat(uint64_t sourceId, bool sourceIsGroup,
				 uint64_t targetId, bool targetIsGroup,
				 const std::string &stat, float value);
	float getRelationshipStat(uint64_t sourceId, bool sourceIsGroup,
				  uint64_t targetId, bool targetIsGroup,
				  const std::string &stat) const;

	void addRelationshipTag(uint64_t sourceId, bool sourceIsGroup,
				uint64_t targetId, bool targetIsGroup,
				const std::string &tag);
	void removeRelationshipTag(uint64_t sourceId, bool sourceIsGroup,
				   uint64_t targetId, bool targetIsGroup,
				   const std::string &tag);
	bool hasRelationshipTag(uint64_t sourceId, bool sourceIsGroup,
				uint64_t targetId, bool targetIsGroup,
				const std::string &tag) const;

	std::vector<const Relationship *> getRelationships(uint64_t id) const;
	void purgeRelationships(uint64_t id);

	/* ------------------------------------------------------------------ */
	/*  Custom columns                                                      */
	/* ------------------------------------------------------------------ */
	void addCharacterColumn(const std::string &name, ColumnDef::Type type);
	void removeCharacterColumn(const std::string &name);
	const std::vector<ColumnDef> &getCharacterColumns() const
	{
		return m_characterColumns;
	}

	void addGroupColumn(const std::string &name, ColumnDef::Type type);
	void removeGroupColumn(const std::string &name);
	const std::vector<ColumnDef> &getGroupColumns() const
	{
		return m_groupColumns;
	}

	/* ------------------------------------------------------------------ */
	/*  Prefab helpers (static)                                             */
	/* ------------------------------------------------------------------ */
	static bool isValidCharacterPrefab(const std::string &filepath);
	static bool readPrefabSlots(const std::string &filepath,
				    std::string &age, std::string &sex,
				    int &outfitLevel);
	static bool writePrefabSlots(const std::string &filepath,
				     const std::string &age,
				     const std::string &sex, int outfitLevel);
	static bool copyPrefab(const std::string &src, const std::string &dst);
	static bool deletePrefabFile(const std::string &filepath);

	/* ------------------------------------------------------------------ */
	/*  Templates                                                           */
	/* ------------------------------------------------------------------ */
	void scanTemplates();
	const std::vector<std::string> &getTemplates() const
	{
		return m_templates;
	}

	/* ------------------------------------------------------------------ */
	/*  Name Generation                                                     */
	/* ------------------------------------------------------------------ */
	void learnNamesFromRegistry();
	std::string generateFirstName(const std::string &sex = "male") const;
	std::string generateLastName() const;

	const MarkovNameGenerator &getMaleFirstNameGen() const
	{
		return m_maleFirstNameGen;
	}
	const MarkovNameGenerator &getFemaleFirstNameGen() const
	{
		return m_femaleFirstNameGen;
	}

	std::vector<std::string> &getMaleFirstNameSeeds()
	{
		return m_maleFirstNameSeeds;
	}
	std::vector<std::string> &getFemaleFirstNameSeeds()
	{
		return m_femaleFirstNameSeeds;
	}
	std::vector<std::string> &getLastNameSeeds()
	{
		return m_lastNameSeeds;
	}

	/*  Family / Birth                                                      */
	/* ------------------------------------------------------------------ */
	std::vector<uint64_t> getParents(uint64_t childId) const;
	std::vector<uint64_t> getChildren(uint64_t parentId) const;
	uint64_t createChild(uint64_t parentA, uint64_t parentB);

	/* Pregnancy helpers */
	bool conceive(uint64_t motherId, uint64_t fatherId);
	void abortPregnancy(uint64_t motherId);
	bool isPregnant(uint64_t motherId) const;

	float getBasePregnancyDuration() const
	{
		return m_basePregnancyDuration;
	}
	void setBasePregnancyDuration(float v)
	{
		m_basePregnancyDuration = v;
	}
	float getPregnancyTimeScale() const
	{
		return m_pregnancyTimeScale;
	}
	void setPregnancyTimeScale(float v)
	{
		m_pregnancyTimeScale = v;
	}

	std::vector<std::string> &getBirthRandomizableShapeKeys()
	{
		return m_birthRandomizableShapeKeys;
	}
	std::vector<std::string> &getBirthExcludedShapeKeys()
	{
		return m_birthExcludedShapeKeys;
	}

	/*  Spawn / Save                                                        */
	/* ------------------------------------------------------------------ */
	flecs::entity findSpawnedEntity(uint64_t id) const;
	/**
	 * Mark the spawned entity for a character as dirty so its visual
	 * appearance (CharacterSlotsComponent) is rebuilt on the next
	 * update.  Call this after changing outfitLevel, age, or any other
	 * registry field that affects the character's look.
	 */
	void markCharacterDirty(uint64_t id);

	/**
	 * Return the current version of a character record. The version is
	 * bumped by markCharacterDirty() and can be used by spawners to detect
	 * registry changes that require a respawn.
	 */
	uint64_t getCharacterVersion(uint64_t id) const;

	bool despawnCharacter(uint64_t id);
	flecs::entity spawnCharacter(uint64_t id);
	flecs::entity spawnInlineCharacter(const CharacterRecord &c,
					   const Ogre::Vector3 &pos);
	bool savePrefabForCharacter(uint64_t id);

	/**
	 * Initialize RPG data from class definition.
	 * Resets stats/skills/needs to class base, simulates level-ups,
	 * and sets pools to full.
	 */
	void initializeFromClass(uint64_t id);

	/* ------------------------------------------------------------------ */
	/*  Persistence                                                         */
	/* ------------------------------------------------------------------ */
	nlohmann::json serialize() const;
	void deserialize(const nlohmann::json &j);

	bool saveToFile(const std::string &filepath);
	bool loadFromFile(const std::string &filepath);
	const std::string &getLastError() const
	{
		return m_lastError;
	}

	/* ------------------------------------------------------------------ */
	/*  ImGui editor                                                        */
	/* ------------------------------------------------------------------ */
	void drawEditor(bool *p_open = nullptr);

	/* Auto-save to fixed path (called after mutations) */
	void autoSave();

private:
	uint64_t m_nextId = 1;
	uint64_t m_nextRuntimeId = 1ull << 32;

	std::unordered_map<uint64_t, CharacterRecord> m_characters;
	std::unordered_map<uint64_t, GroupRecord> m_groups;
	std::vector<Relationship> m_relationships;

	std::vector<ColumnDef> m_characterColumns;
	std::vector<ColumnDef> m_groupColumns;

	/* Fast relationship indexes (value = index into m_relationships) */
	std::unordered_multimap<uint64_t, size_t> m_relBySource;
	std::unordered_multimap<uint64_t, size_t> m_relByTarget;

	mutable std::string m_lastError;
	std::string m_autoSavePath;
	std::vector<std::string> m_templates;

	MarkovNameGenerator m_maleFirstNameGen;
	MarkovNameGenerator m_femaleFirstNameGen;
	MarkovNameGenerator m_lastNameGen;

	std::vector<std::string> m_maleFirstNameSeeds;
	std::vector<std::string> m_femaleFirstNameSeeds;
	std::vector<std::string> m_lastNameSeeds;

	std::vector<std::string> m_birthRandomizableShapeKeys;
	std::vector<std::string> m_birthExcludedShapeKeys;

	/* Global pregnancy config */
	float m_basePregnancyDuration = 300.0f;
	float m_pregnancyTimeScale = 1.0f;

	void rebuildNameGenerators();

	flecs::world *m_world = nullptr;
	Ogre::SceneManager *m_sceneMgr = nullptr;
	EditorUISystem *m_uiSystem = nullptr;
	CharacterSlotSystem *m_slotSystem = nullptr;

	void rebuildIndexes();
	void addToIndex(size_t relIndex);
	void removeFromIndex(uint64_t sourceId, uint64_t targetId,
			     size_t relIndex);

	std::string generatePrefabPath(uint64_t id,
				       const std::string &firstName,
				       const std::string &lastName) const;

	/* UI state */
	int m_editorTab = 0;
	uint64_t m_selectedCharacterId = 0;
	uint64_t m_selectedGroupId = 0;
	uint64_t m_relSourceId = 0;
	int m_relSourceIsGroup = 0;
};

#endif // EDITSCENE_CHARACTERREGISTRY_HPP
