#ifndef EDITSCENE_GOAP_BLACKBOARD_HPP
#define EDITSCENE_GOAP_BLACKBOARD_HPP
#pragma once

#include <Ogre.h>
#include <array>
#include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>

/**
 * Lightweight GOAP blackboard for action preconditions, effects,
 * and per-character runtime state.
 *
 * Uses a 64-bit bitfield for fast boolean flag checks (used by GOAP planner).
 * Supports int, float, and Vector3 values (int is used for preconditions/effects;
 * float/vec3 are for behavior-tree-driven character state).
 */
struct GoapBlackboard {
	// Boolean flags: 64 bits available. These are used by the GOAP planner.
	uint64_t bits = 0;
	uint64_t mask = 0; // which bits are actually set

	// Bitmask for comparison: only bits set here are compared.
	// Defaults to all 1s (compare all bits).
	uint64_t bitmask = ~0ULL;

	// Named integer values (health, hunger, etc.) — used by preconditions/effects
	std::unordered_map<std::string, int> values;

	// Named float values — runtime character state
	std::unordered_map<std::string, float> floatValues;

	// Named Vector3 values — runtime character state
	std::unordered_map<std::string, Ogre::Vector3> vec3Values;

	// Named string values — event params, tags, etc.
	std::unordered_map<std::string, Ogre::String> stringValues;

	GoapBlackboard() = default;

	/* --- Bit accessors --- */
	void setBit(int index, bool value)
	{
		if (index < 0 || index >= 64)
			return;
		uint64_t bit = 1ULL << index;
		mask |= bit;
		if (value)
			bits |= bit;
		else
			bits &= ~bit;
	}

	bool getBit(int index) const
	{
		if (index < 0 || index >= 64)
			return false;
		return (bits >> index) & 1ULL;
	}

	bool hasBit(int index) const
	{
		if (index < 0 || index >= 64)
			return false;
		return (mask >> index) & 1ULL;
	}

	void clearBit(int index)
	{
		if (index < 0 || index >= 64)
			return;
		uint64_t bit = 1ULL << index;
		mask &= ~bit;
		bits &= ~bit;
	}

	/* --- Integer value accessors (backward compat) --- */
	void setValue(const std::string &key, int value)
	{
		values[key] = value;
	}

	int getValue(const std::string &key, int defaultValue = 0) const
	{
		auto it = values.find(key);
		if (it != values.end())
			return it->second;
		return defaultValue;
	}

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

	void removeValue(const std::string &key)
	{
		values.erase(key);
	}

	/* --- Float value accessors --- */
	void setFloatValue(const std::string &key, float value)
	{
		floatValues[key] = value;
	}

	float getFloatValue(const std::string &key,
			    float defaultValue = 0.0f) const
	{
		auto it = floatValues.find(key);
		if (it != floatValues.end())
			return it->second;
		return defaultValue;
	}

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

	void removeFloatValue(const std::string &key)
	{
		floatValues.erase(key);
	}

	/* --- Vector3 value accessors --- */
	void setVec3Value(const std::string &key, const Ogre::Vector3 &value)
	{
		vec3Values[key] = value;
	}

	Ogre::Vector3 getVec3Value(
	    const std::string &key,
	    const Ogre::Vector3 &defaultValue = Ogre::Vector3::ZERO) const
	{
		auto it = vec3Values.find(key);
		if (it != vec3Values.end())
			return it->second;
		return defaultValue;
	}

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

	void removeVec3Value(const std::string &key)
	{
		vec3Values.erase(key);
	}

	/* --- String value accessors --- */
	void setStringValue(const std::string &key, const Ogre::String &value)
	{
		stringValues[key] = value;
	}

	Ogre::String getStringValue(const std::string &key,
				    const Ogre::String &defaultValue = "") const
	{
		auto it = stringValues.find(key);
		if (it != stringValues.end())
			return it->second;
		return defaultValue;
	}

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

	void removeStringValue(const std::string &key)
	{
		stringValues.erase(key);
	}

	/* --- Merge another blackboard into this one --- */
	void merge(const GoapBlackboard &other);

	/* --- Generic scalar lookup (tries int then float) --- */
	bool getScalarValue(const std::string &key, float &out) const;

	/* --- GOAP methods --- */
	bool satisfies(const GoapBlackboard &other) const;
	void apply(const GoapBlackboard &other);
	int distanceTo(const GoapBlackboard &target,
		       bool ignoreValues = false) const;

	/* --- Utility --- */
	bool isValid() const
	{
		return mask != 0 || !values.empty() || !floatValues.empty() ||
		       !vec3Values.empty();
	}

	void clear()
	{
		bits = 0;
		mask = 0;
		values.clear();
		floatValues.clear();
		vec3Values.clear();
		stringValues.clear();
	}

	Ogre::String dump() const;

	// Bit naming: global registry for human-readable bit names
	static std::array<std::string, 64> &getBitNameRegistry();
	static void setBitName(int index, const std::string &name);
	static const char *getBitName(int index);
	static int findBitByName(const std::string &name);

	// List all set bit indices
	std::vector<int> getSetBits() const;

	// Equality
	bool operator==(const GoapBlackboard &other) const
	{
		return bits == other.bits && mask == other.mask &&
		       bitmask == other.bitmask &&
		       values == other.values &&
		       floatValues == other.floatValues &&
		       vec3Values == other.vec3Values &&
		       stringValues == other.stringValues;
	}

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

#endif // EDITSCENE_GOAP_BLACKBOARD_HPP
