#ifndef EDITSCENE_EVENT_PARAMS_HPP
#define EDITSCENE_EVENT_PARAMS_HPP
#pragma once

#include <Ogre.h>
#include <cstdint>
#include <cstring>
#include <string>
#include <vector>
#include <cassert>

/**
 * @file EventParams.hpp
 * @brief Tagged union type for event parameters.
 *
 * A C++11-compatible, RTTI-free tagged union that supports:
 *   - Entity ID (uint64_t)
 *   - Integer (int64_t)
 *   - Float (float)
 *   - Double (double)
 *   - String (std::string)
 *   - Array of entity IDs (std::vector<uint64_t>)
 *   - Array of integers (std::vector<int64_t>)
 *   - Array of floats (std::vector<float>)
 *   - Array of doubles (std::vector<double>)
 *   - Array of strings (std::vector<std::string>)
 *
 * Named parameters are stored as a map of string -> EventValue,
 * where EventValue is a tagged union of the above types.
 */

// Forward declaration for friend function
struct lua_State;

namespace editScene
{

// ---------------------------------------------------------------------------
// EventValue: A single tagged-union value
// ---------------------------------------------------------------------------

struct EventValue {
	enum Type {
		NIL = 0,
		ENTITY_ID,
		INT,
		FLOAT,
		DOUBLE,
		STRING,
		ENTITY_ID_ARRAY,
		INT_ARRAY,
		FLOAT_ARRAY,
		DOUBLE_ARRAY,
		STRING_ARRAY
	};

	Type type;

	union {
		uint64_t asEntityId;
		int64_t asInt;
		float asFloat;
		double asDouble;
	};

	// Heap-allocated data (strings and arrays)
	// We use raw pointers to avoid std::unique_ptr (C++11 compatible)
	std::string *strPtr;
	void *arrayPtr; // points to std::vector<T>*
	size_t arraySize;

	EventValue()
		: type(NIL)
		, asEntityId(0)
		, strPtr(nullptr)
		, arrayPtr(nullptr)
		, arraySize(0)
	{
	}

	explicit EventValue(uint64_t entityId)
		: type(ENTITY_ID)
		, asEntityId(entityId)
		, strPtr(nullptr)
		, arrayPtr(nullptr)
		, arraySize(0)
	{
	}

	explicit EventValue(int64_t val)
		: type(INT)
		, asInt(val)
		, strPtr(nullptr)
		, arrayPtr(nullptr)
		, arraySize(0)
	{
	}

	explicit EventValue(int val)
		: type(INT)
		, asInt(static_cast<int64_t>(val))
		, strPtr(nullptr)
		, arrayPtr(nullptr)
		, arraySize(0)
	{
	}

	explicit EventValue(float val)
		: type(FLOAT)
		, asFloat(val)
		, strPtr(nullptr)
		, arrayPtr(nullptr)
		, arraySize(0)
	{
	}

	explicit EventValue(double val)
		: type(DOUBLE)
		, asDouble(val)
		, strPtr(nullptr)
		, arrayPtr(nullptr)
		, arraySize(0)
	{
	}

	explicit EventValue(const std::string &val)
		: type(STRING)
		, asEntityId(0)
		, strPtr(new std::string(val))
		, arrayPtr(nullptr)
		, arraySize(0)
	{
	}

	explicit EventValue(const char *val)
		: type(STRING)
		, asEntityId(0)
		, strPtr(new std::string(val ? val : ""))
		, arrayPtr(nullptr)
		, arraySize(0)
	{
	}

	// Array constructors
	explicit EventValue(const std::vector<uint64_t> &arr)
		: type(ENTITY_ID_ARRAY)
		, asEntityId(0)
		, strPtr(nullptr)
		, arrayPtr(new std::vector<uint64_t>(arr))
		, arraySize(arr.size())
	{
	}

	explicit EventValue(const std::vector<int64_t> &arr)
		: type(INT_ARRAY)
		, asEntityId(0)
		, strPtr(nullptr)
		, arrayPtr(new std::vector<int64_t>(arr))
		, arraySize(arr.size())
	{
	}

	explicit EventValue(const std::vector<int> &arr)
		: type(INT_ARRAY)
		, asEntityId(0)
		, strPtr(nullptr)
		, arrayPtr(new std::vector<int64_t>(arr.begin(), arr.end()))
		, arraySize(arr.size())
	{
	}

	explicit EventValue(const std::vector<float> &arr)
		: type(FLOAT_ARRAY)
		, asEntityId(0)
		, strPtr(nullptr)
		, arrayPtr(new std::vector<float>(arr))
		, arraySize(arr.size())
	{
	}

	explicit EventValue(const std::vector<double> &arr)
		: type(DOUBLE_ARRAY)
		, asEntityId(0)
		, strPtr(nullptr)
		, arrayPtr(new std::vector<double>(arr))
		, arraySize(arr.size())
	{
	}

	explicit EventValue(const std::vector<std::string> &arr)
		: type(STRING_ARRAY)
		, asEntityId(0)
		, strPtr(nullptr)
		, arrayPtr(new std::vector<std::string>(arr))
		, arraySize(arr.size())
	{
	}

	// Copy constructor
	EventValue(const EventValue &other)
		: type(other.type)
		, asEntityId(other.asEntityId)
		, strPtr(nullptr)
		, arrayPtr(nullptr)
		, arraySize(other.arraySize)
	{
		copyHeapData(other);
	}

	// Copy assignment
	EventValue &operator=(const EventValue &other)
	{
		if (this != &other) {
			destroyHeapData();
			type = other.type;
			asEntityId = other.asEntityId;
			arraySize = other.arraySize;
			strPtr = nullptr;
			arrayPtr = nullptr;
			copyHeapData(other);
		}
		return *this;
	}

	// Move constructor
	EventValue(EventValue &&other) noexcept : type(other.type),
						  asEntityId(other.asEntityId),
						  strPtr(other.strPtr),
						  arrayPtr(other.arrayPtr),
						  arraySize(other.arraySize)
	{
		other.type = NIL;
		other.strPtr = nullptr;
		other.arrayPtr = nullptr;
		other.arraySize = 0;
	}

	// Move assignment
	EventValue &operator=(EventValue &&other) noexcept
	{
		if (this != &other) {
			destroyHeapData();
			type = other.type;
			asEntityId = other.asEntityId;
			strPtr = other.strPtr;
			arrayPtr = other.arrayPtr;
			arraySize = other.arraySize;
			other.type = NIL;
			other.strPtr = nullptr;
			other.arrayPtr = nullptr;
			other.arraySize = 0;
		}
		return *this;
	}

	~EventValue()
	{
		destroyHeapData();
	}

	// --- Accessors ---

	Type getType() const
	{
		return type;
	}

	uint64_t getEntityId() const
	{
		assert(type == ENTITY_ID);
		return asEntityId;
	}

	int64_t getInt() const
	{
		assert(type == INT);
		return asInt;
	}

	float getFloat() const
	{
		assert(type == FLOAT);
		return asFloat;
	}

	double getDouble() const
	{
		assert(type == DOUBLE);
		return asDouble;
	}

	const std::string &getString() const
	{
		assert(type == STRING && strPtr != nullptr);
		return *strPtr;
	}

	const std::vector<uint64_t> &getEntityIdArray() const
	{
		assert(type == ENTITY_ID_ARRAY && arrayPtr != nullptr);
		return *static_cast<const std::vector<uint64_t> *>(arrayPtr);
	}

	const std::vector<int64_t> &getIntArray() const
	{
		assert(type == INT_ARRAY && arrayPtr != nullptr);
		return *static_cast<const std::vector<int64_t> *>(arrayPtr);
	}

	const std::vector<float> &getFloatArray() const
	{
		assert(type == FLOAT_ARRAY && arrayPtr != nullptr);
		return *static_cast<const std::vector<float> *>(arrayPtr);
	}

	const std::vector<double> &getDoubleArray() const
	{
		assert(type == DOUBLE_ARRAY && arrayPtr != nullptr);
		return *static_cast<const std::vector<double> *>(arrayPtr);
	}

	const std::vector<std::string> &getStringArray() const
	{
		assert(type == STRING_ARRAY && arrayPtr != nullptr);
		return *static_cast<const std::vector<std::string> *>(arrayPtr);
	}

	// --- Convenience: get numeric value as double ---
	double asNumeric() const
	{
		switch (type) {
		case INT:
			return static_cast<double>(asInt);
		case FLOAT:
			return static_cast<double>(asFloat);
		case DOUBLE:
			return asDouble;
		case ENTITY_ID:
			return static_cast<double>(asEntityId);
		default:
			return 0.0;
		}
	}

	// --- Equality ---
	bool operator==(const EventValue &other) const
	{
		if (type != other.type)
			return false;
		switch (type) {
		case NIL:
			return true;
		case ENTITY_ID:
			return asEntityId == other.asEntityId;
		case INT:
			return asInt == other.asInt;
		case FLOAT:
			return asFloat == other.asFloat;
		case DOUBLE:
			return asDouble == other.asDouble;
		case STRING:
			return strPtr && other.strPtr &&
			       *strPtr == *other.strPtr;
		case ENTITY_ID_ARRAY:
			return arrayPtr && other.arrayPtr &&
			       getEntityIdArray() == other.getEntityIdArray();
		case INT_ARRAY:
			return arrayPtr && other.arrayPtr &&
			       getIntArray() == other.getIntArray();
		case FLOAT_ARRAY:
			return arrayPtr && other.arrayPtr &&
			       getFloatArray() == other.getFloatArray();
		case DOUBLE_ARRAY:
			return arrayPtr && other.arrayPtr &&
			       getDoubleArray() == other.getDoubleArray();
		case STRING_ARRAY:
			return arrayPtr && other.arrayPtr &&
			       getStringArray() == other.getStringArray();
		}
		return false;
	}

	bool operator!=(const EventValue &other) const
	{
		return !(*this == other);
	}

private:
	void copyHeapData(const EventValue &other)
	{
		if (other.type == STRING && other.strPtr) {
			strPtr = new std::string(*other.strPtr);
		} else if (other.type == ENTITY_ID_ARRAY && other.arrayPtr) {
			arrayPtr = new std::vector<uint64_t>(
				*static_cast<const std::vector<uint64_t> *>(
					other.arrayPtr));
		} else if (other.type == INT_ARRAY && other.arrayPtr) {
			arrayPtr = new std::vector<int64_t>(
				*static_cast<const std::vector<int64_t> *>(
					other.arrayPtr));
		} else if (other.type == FLOAT_ARRAY && other.arrayPtr) {
			arrayPtr = new std::vector<float>(
				*static_cast<const std::vector<float> *>(
					other.arrayPtr));
		} else if (other.type == DOUBLE_ARRAY && other.arrayPtr) {
			arrayPtr = new std::vector<double>(
				*static_cast<const std::vector<double> *>(
					other.arrayPtr));
		} else if (other.type == STRING_ARRAY && other.arrayPtr) {
			arrayPtr = new std::vector<std::string>(
				*static_cast<const std::vector<std::string> *>(
					other.arrayPtr));
		}
	}

	void destroyHeapData()
	{
		if (type == STRING) {
			delete strPtr;
		} else if (type == ENTITY_ID_ARRAY) {
			delete static_cast<std::vector<uint64_t> *>(arrayPtr);
		} else if (type == INT_ARRAY) {
			delete static_cast<std::vector<int64_t> *>(arrayPtr);
		} else if (type == FLOAT_ARRAY) {
			delete static_cast<std::vector<float> *>(arrayPtr);
		} else if (type == DOUBLE_ARRAY) {
			delete static_cast<std::vector<double> *>(arrayPtr);
		} else if (type == STRING_ARRAY) {
			delete static_cast<std::vector<std::string> *>(
				arrayPtr);
		}
		strPtr = nullptr;
		arrayPtr = nullptr;
	}
};

// ---------------------------------------------------------------------------
// EventParams: A map of named EventValue entries
// ---------------------------------------------------------------------------

class EventParams {
public:
	EventParams() = default;

	// --- Set values ---

	void setEntityId(const std::string &key, uint64_t val)
	{
		m_values[key] = EventValue(val);
	}

	void setInt(const std::string &key, int64_t val)
	{
		m_values[key] = EventValue(val);
	}

	void setFloat(const std::string &key, float val)
	{
		m_values[key] = EventValue(val);
	}

	void setDouble(const std::string &key, double val)
	{
		m_values[key] = EventValue(val);
	}

	void setString(const std::string &key, const std::string &val)
	{
		m_values[key] = EventValue(val);
	}

	void setEntityIdArray(const std::string &key,
			      const std::vector<uint64_t> &val)
	{
		m_values[key] = EventValue(val);
	}

	void setIntArray(const std::string &key,
			 const std::vector<int64_t> &val)
	{
		m_values[key] = EventValue(val);
	}

	void setFloatArray(const std::string &key,
			   const std::vector<float> &val)
	{
		m_values[key] = EventValue(val);
	}

	void setDoubleArray(const std::string &key,
			    const std::vector<double> &val)
	{
		m_values[key] = EventValue(val);
	}

	void setStringArray(const std::string &key,
			    const std::vector<std::string> &val)
	{
		m_values[key] = EventValue(val);
	}

	// --- Get values ---

	bool has(const std::string &key) const
	{
		return m_values.find(key) != m_values.end();
	}

	const EventValue *get(const std::string &key) const
	{
		auto it = m_values.find(key);
		if (it != m_values.end())
			return &it->second;
		return nullptr;
	}

	EventValue *get(const std::string &key)
	{
		auto it = m_values.find(key);
		if (it != m_values.end())
			return &it->second;
		return nullptr;
	}

	// --- Typed getters with defaults ---

	uint64_t getEntityId(const std::string &key,
			     uint64_t defaultVal = 0) const
	{
		auto v = get(key);
		if (v && v->getType() == EventValue::ENTITY_ID)
			return v->getEntityId();
		return defaultVal;
	}

	int64_t getInt(const std::string &key, int64_t defaultVal = 0) const
	{
		auto v = get(key);
		if (v && v->getType() == EventValue::INT)
			return v->getInt();
		return defaultVal;
	}

	float getFloat(const std::string &key, float defaultVal = 0.0f) const
	{
		auto v = get(key);
		if (v && v->getType() == EventValue::FLOAT)
			return v->getFloat();
		return defaultVal;
	}

	double getDouble(const std::string &key, double defaultVal = 0.0) const
	{
		auto v = get(key);
		if (v && v->getType() == EventValue::DOUBLE)
			return v->getDouble();
		return defaultVal;
	}

	std::string getString(const std::string &key,
			      const std::string &defaultVal = "") const
	{
		auto v = get(key);
		if (v && v->getType() == EventValue::STRING)
			return v->getString();
		return defaultVal;
	}

	// --- Remove ---

	void remove(const std::string &key)
	{
		m_values.erase(key);
	}

	// --- Clear ---

	void clear()
	{
		m_values.clear();
	}

	// --- Size ---

	size_t size() const
	{
		return m_values.size();
	}

	bool empty() const
	{
		return m_values.empty();
	}

	// --- Iteration ---

	typedef std::unordered_map<std::string, EventValue>::const_iterator
		ConstIterator;
	typedef std::unordered_map<std::string, EventValue>::iterator Iterator;

	ConstIterator begin() const
	{
		return m_values.begin();
	}
	ConstIterator end() const
	{
		return m_values.end();
	}
	Iterator begin()
	{
		return m_values.begin();
	}
	Iterator end()
	{
		return m_values.end();
	}

	// --- Merge ---

	void merge(const EventParams &other)
	{
		for (const auto &pair : other.m_values)
			m_values[pair.first] = pair.second;
	}

	// --- Equality ---

	bool operator==(const EventParams &other) const
	{
		return m_values == other.m_values;
	}

	bool operator!=(const EventParams &other) const
	{
		return !(*this == other);
	}

	// --- Dump for debugging ---

	std::string dump() const
	{
		std::string result = "EventParams:\n";
		for (const auto &pair : m_values) {
			result += "  " + pair.first + " = ";
			switch (pair.second.getType()) {
			case EventValue::NIL:
				result += "nil";
				break;
			case EventValue::ENTITY_ID:
				result += "entity:" +
					  std::to_string(
						  pair.second.getEntityId());
				break;
			case EventValue::INT:
				result += std::to_string(pair.second.getInt());
				break;
			case EventValue::FLOAT:
				result +=
					std::to_string(pair.second.getFloat());
				break;
			case EventValue::DOUBLE:
				result +=
					std::to_string(pair.second.getDouble());
				break;
			case EventValue::STRING:
				result += "'" + pair.second.getString() + "'";
				break;
			case EventValue::ENTITY_ID_ARRAY: {
				result += "[";
				const auto &arr =
					pair.second.getEntityIdArray();
				for (size_t i = 0; i < arr.size(); i++) {
					if (i > 0)
						result += ", ";
					result += "e:" + std::to_string(arr[i]);
				}
				result += "]";
				break;
			}
			case EventValue::INT_ARRAY: {
				result += "[";
				const auto &arr = pair.second.getIntArray();
				for (size_t i = 0; i < arr.size(); i++) {
					if (i > 0)
						result += ", ";
					result += std::to_string(arr[i]);
				}
				result += "]";
				break;
			}
			case EventValue::FLOAT_ARRAY: {
				result += "[";
				const auto &arr = pair.second.getFloatArray();
				for (size_t i = 0; i < arr.size(); i++) {
					if (i > 0)
						result += ", ";
					result += std::to_string(arr[i]);
				}
				result += "]";
				break;
			}
			case EventValue::DOUBLE_ARRAY: {
				result += "[";
				const auto &arr = pair.second.getDoubleArray();
				for (size_t i = 0; i < arr.size(); i++) {
					if (i > 0)
						result += ", ";
					result += std::to_string(arr[i]);
				}
				result += "]";
				break;
			}
			case EventValue::STRING_ARRAY: {
				result += "[";
				const auto &arr = pair.second.getStringArray();
				for (size_t i = 0; i < arr.size(); i++) {
					if (i > 0)
						result += ", ";
					result += "'" + arr[i] + "'";
				}
				result += "]";
				break;
			}
			}
			result += "\n";
		}
		return result;
	}

	// Allow LuaEventApi to access m_values directly for efficiency
	friend EventParams readEventParams(lua_State *L, int idx);

private:
	std::unordered_map<std::string, EventValue> m_values;
};

} // namespace editScene

#endif // EDITSCENE_EVENT_PARAMS_HPP
