#include "ItemRegistry.hpp"
#include <OgreLogManager.h>
#include <fstream>
#include <imgui.h>
#include <algorithm>

/* ===================================================================== */
/*  Singleton                                                            */
/* ===================================================================== */

ItemRegistry *ItemRegistry::ms_singleton = nullptr;

ItemRegistry &ItemRegistry::getSingleton()
{
	OgreAssert(ms_singleton, "ItemRegistry not created");
	return *ms_singleton;
}

ItemRegistry *ItemRegistry::getSingletonPtr()
{
	return ms_singleton;
}

/* ===================================================================== */
/*  Construction / init                                                  */
/* ===================================================================== */

ItemRegistry::ItemRegistry()
{
	ms_singleton = this;
	m_autoSavePath = "items.json";
}

void ItemRegistry::initialize()
{
	if (!std::filesystem::exists(m_autoSavePath))
		return;
	if (!loadFromFile(m_autoSavePath)) {
		Ogre::LogManager::getSingleton().logMessage(
			"ItemRegistry: auto-load failed: " + m_lastError);
	}
}

void ItemRegistry::autoSave()
{
	if (!m_autoSavePath.empty())
		saveToFile(m_autoSavePath);
}

/* ===================================================================== */
/*  Definitions                                                          */
/* ===================================================================== */

bool ItemRegistry::registerItem(const ItemRegistry::ItemDefinition &def)
{
	if (def.itemId.empty()) {
		m_lastError = "Cannot register item with empty itemId";
		return false;
	}
	m_definitions[def.itemId] = def;
	autoSave();
	return true;
}

bool ItemRegistry::removeItem(const std::string &itemId)
{
	auto it = m_definitions.find(itemId);
	if (it == m_definitions.end()) {
		m_lastError = "Item not found: " + itemId;
		return false;
	}
	m_definitions.erase(it);
	autoSave();
	return true;
}

ItemRegistry::ItemDefinition *ItemRegistry::findDefinition(const std::string &itemId)
{
	auto it = m_definitions.find(itemId);
	if (it != m_definitions.end())
		return &it->second;
	return nullptr;
}

const ItemRegistry::ItemDefinition *ItemRegistry::findDefinition(const std::string &itemId) const
{
	auto it = m_definitions.find(itemId);
	if (it != m_definitions.end())
		return &it->second;
	return nullptr;
}

bool ItemRegistry::isUnique(const std::string &itemId) const
{
	auto *def = findDefinition(itemId);
	return def && def->unique;
}

std::string ItemRegistry::getItemName(const std::string &itemId) const
{
	auto *def = findDefinition(itemId);
	return def ? def->itemName : "";
}

std::string ItemRegistry::getItemType(const std::string &itemId) const
{
	auto *def = findDefinition(itemId);
	return def ? def->itemType : "";
}

int ItemRegistry::getMaxStackSize(const std::string &itemId) const
{
	auto *def = findDefinition(itemId);
	return def ? def->maxStackSize : 99;
}

float ItemRegistry::getWeight(const std::string &itemId) const
{
	auto *def = findDefinition(itemId);
	return def ? def->weight : 0.1f;
}

int ItemRegistry::getValue(const std::string &itemId) const
{
	auto *def = findDefinition(itemId);
	return def ? def->value : 1;
}

std::string ItemRegistry::getUseActionName(const std::string &itemId) const
{
	auto *def = findDefinition(itemId);
	return def ? def->useActionName : "";
}

/* ===================================================================== */
/*  Columns                                                              */
/* ===================================================================== */

void ItemRegistry::addColumn(const std::string &name, ColumnDef::Type type)
{
	m_columns.push_back({type, name});
}

void ItemRegistry::removeColumn(const std::string &name)
{
	m_columns.erase(
		std::remove_if(m_columns.begin(), m_columns.end(),
			       [&name](const ColumnDef &c) {
				       return c.name == name;
			       }),
		m_columns.end());
}

/* ===================================================================== */
/*  Persistence                                                          */
/* ===================================================================== */

nlohmann::json ItemRegistry::serialize() const
{
	nlohmann::json j;
	j["version"] = "1.0";

	for (const auto &c : m_columns) {
		nlohmann::json col;
		col["name"] = c.name;
		col["type"] = (c.type == ColumnDef::Int)     ? "int" :
			      (c.type == ColumnDef::Float)   ? "float" :
							       "string";
		j["columns"].push_back(col);
	}

	for (const auto &pair : m_definitions) {
		const ItemRegistry::ItemDefinition &d = pair.second;
		nlohmann::json def;
		def["itemId"] = d.itemId;
		def["itemName"] = d.itemName;
		def["itemType"] = d.itemType;
		def["maxStackSize"] = d.maxStackSize;
		def["weight"] = d.weight;
		def["value"] = d.value;
		def["useActionName"] = d.useActionName;
		def["unique"] = d.unique;
		for (const auto &kv : d.intColumns)
			def["intColumns"][kv.first] = kv.second;
		for (const auto &kv : d.floatColumns)
			def["floatColumns"][kv.first] = kv.second;
		for (const auto &kv : d.stringColumns)
			def["stringColumns"][kv.first] = kv.second;
		j["definitions"].push_back(def);
	}

	return j;
}

void ItemRegistry::deserialize(const nlohmann::json &j)
{
	m_definitions.clear();
	m_columns.clear();

	if (j.contains("columns")) {
		for (const auto &col : j["columns"]) {
			ColumnDef::Type t = ColumnDef::String;
			std::string ts = col.value("type", "string");
			if (ts == "int")
				t = ColumnDef::Int;
			else if (ts == "float")
				t = ColumnDef::Float;
			m_columns.push_back({t, col.value("name", "")});
		}
	}

	if (j.contains("definitions")) {
		for (const auto &defJson : j["definitions"]) {
			ItemRegistry::ItemDefinition d;
			d.itemId = defJson.value("itemId", "");
			d.itemName = defJson.value("itemName", "");
			d.itemType = defJson.value("itemType", "misc");
			d.maxStackSize = defJson.value("maxStackSize", 99);
			d.weight = defJson.value("weight", 0.1f);
			d.value = defJson.value("value", 1);
			d.useActionName = defJson.value("useActionName", "");
			d.unique = defJson.value("unique", false);
			if (defJson.contains("intColumns")) {
				for (auto &[key, val] : defJson["intColumns"].items())
					d.intColumns[key] = val.get<int64_t>();
			}
			if (defJson.contains("floatColumns")) {
				for (auto &[key, val] : defJson["floatColumns"].items())
					d.floatColumns[key] = val.get<double>();
			}
			if (defJson.contains("stringColumns")) {
				for (auto &[key, val] : defJson["stringColumns"].items())
					d.stringColumns[key] = val.get<std::string>();
			}
			if (!d.itemId.empty())
				m_definitions[d.itemId] = d;
		}
	}
}

bool ItemRegistry::saveToFile(const std::string &filepath)
{
	try {
		std::ofstream file(filepath);
		if (!file.is_open()) {
			m_lastError = "Cannot open " + filepath;
			return false;
		}
		file << serialize().dump(4);
		return true;
	} catch (const std::exception &e) {
		m_lastError = std::string("Save error: ") + e.what();
		return false;
	}
}

bool ItemRegistry::loadFromFile(const std::string &filepath)
{
	try {
		std::ifstream file(filepath);
		if (!file.is_open()) {
			m_lastError = "Cannot open " + filepath;
			return false;
		}
		nlohmann::json j;
		file >> j;
		deserialize(j);
		return true;
	} catch (const std::exception &e) {
		m_lastError = std::string("Load error: ") + e.what();
		return false;
	}
}

/* ===================================================================== */
/*  ImGui editor                                                         */
/* ===================================================================== */

void ItemRegistry::drawEditor(bool *p_open)
{
	if (p_open && !*p_open)
		return;

	ImGui::SetNextWindowSize(ImVec2(600, 500), ImGuiCond_FirstUseEver);
	if (!ImGui::Begin("Item Registry", p_open))
		return ImGui::End();

	// Left pane: list
	ImGui::BeginChild("ItemList", ImVec2(250, 0), true);

	if (ImGui::Button("Add Item")) {
		std::string newId = m_newItemIdBuf;
		if (!newId.empty() && m_definitions.find(newId) == m_definitions.end()) {
			ItemRegistry::ItemDefinition d;
			d.itemId = newId;
			d.itemName = m_newItemNameBuf[0] ? m_newItemNameBuf : newId;
			registerItem(d);
			m_newItemIdBuf[0] = '\0';
			m_newItemNameBuf[0] = '\0';
		}
	}
	ImGui::InputText("New ID", m_newItemIdBuf, sizeof(m_newItemIdBuf));
	ImGui::InputText("New Name", m_newItemNameBuf, sizeof(m_newItemNameBuf));
	ImGui::Separator();

	int idx = 0;
	for (const auto &pair : m_definitions) {
		const ItemRegistry::ItemDefinition &d = pair.second;
		bool selected = (idx == m_selectedItemIndex);
		if (ImGui::Selectable(
			    (d.itemId + " - " + d.itemName).c_str(), selected)) {
			m_selectedItemIndex = idx;
		}
		idx++;
	}
	ImGui::EndChild();

	ImGui::SameLine();

	// Right pane: edit
	ImGui::BeginChild("ItemEdit", ImVec2(0, 0), true);

	ItemRegistry::ItemDefinition *selectedDef = nullptr;
	idx = 0;
	for (auto &pair : m_definitions) {
		if (idx == m_selectedItemIndex) {
			selectedDef = &pair.second;
			break;
		}
		idx++;
	}

	if (selectedDef) {
		ImGui::Text("Item ID: %s", selectedDef->itemId.c_str());
		static char nameBuf[256];
		static char typeBuf[256];
		static char actionBuf[256];
		snprintf(nameBuf, sizeof(nameBuf), "%s",
			 selectedDef->itemName.c_str());
		snprintf(typeBuf, sizeof(typeBuf), "%s",
			 selectedDef->itemType.c_str());
		snprintf(actionBuf, sizeof(actionBuf), "%s",
			 selectedDef->useActionName.c_str());
		if (ImGui::InputText("Name", nameBuf, sizeof(nameBuf)))
			selectedDef->itemName = nameBuf;
		if (ImGui::InputText("Type", typeBuf, sizeof(typeBuf)))
			selectedDef->itemType = typeBuf;
		if (ImGui::InputInt("Max Stack", &selectedDef->maxStackSize))
			selectedDef->maxStackSize = std::max(1, selectedDef->maxStackSize);
		ImGui::InputFloat("Weight", &selectedDef->weight, 0.1f);
		ImGui::InputInt("Value", &selectedDef->value);
		if (ImGui::InputText("Use Action", actionBuf, sizeof(actionBuf)))
			selectedDef->useActionName = actionBuf;
		if (ImGui::Checkbox("Unique", &selectedDef->unique))
			autoSave();

		if (ImGui::Button("Save Changes"))
			autoSave();
		ImGui::SameLine();
		if (ImGui::Button("Delete Item")) {
			removeItem(selectedDef->itemId);
			m_selectedItemIndex = -1;
		}
	} else {
		ImGui::Text("Select an item to edit");
	}

	ImGui::EndChild();

	ImGui::End();
}
