#include "CharacterSlotsEditor.hpp"
#include "../systems/CharacterSlotSystem.hpp"
#include <imgui.h>
#include <algorithm>

CharacterSlotsEditor::CharacterSlotsEditor(Ogre::SceneManager *sceneMgr)
	: m_sceneMgr(sceneMgr)
{
}

bool CharacterSlotsEditor::renderComponent(flecs::entity entity,
						   CharacterSlotsComponent &cs)
{
	bool modified = false;

	if (ImGui::CollapsingHeader("Character Slots",
				    ImGuiTreeNodeFlags_DefaultOpen)) {
		ImGui::Indent();

		CharacterSlotSystem::loadCatalog();

		/* Age selector */
		std::vector<Ogre::String> ages = CharacterSlotSystem::getAges();
		Ogre::String currentAge = cs.age;
		if (ImGui::BeginCombo("Age", currentAge.c_str())) {
			for (const auto &age : ages) {
				bool isSelected = (currentAge == age);
				if (ImGui::Selectable(age.c_str(), isSelected)) {
					cs.age = age;
					modified = true;
					cs.dirty = true;
				}
				if (isSelected)
					ImGui::SetItemDefaultFocus();
			}
			ImGui::EndCombo();
		}

		/* Sex selector */
		std::vector<Ogre::String> sexes =
			CharacterSlotSystem::getSexes(cs.age);
		Ogre::String currentSex = cs.sex;
		if (ImGui::BeginCombo("Sex", currentSex.c_str())) {
			for (const auto &sex : sexes) {
				bool isSelected = (currentSex == sex);
				if (ImGui::Selectable(sex.c_str(), isSelected)) {
					cs.sex = sex;
					modified = true;
					cs.dirty = true;
				}
				if (isSelected)
					ImGui::SetItemDefaultFocus();
			}
			ImGui::EndCombo();
		}

		ImGui::Separator();

		/* Global outfit level */
		const char *outfitLabels[] = { "Nude", "Lingerie", "Clothed" };
		int outfit = cs.outfitLevel;
		if (outfit < 0)
			outfit = 0;
		if (outfit > 2)
			outfit = 2;
		if (ImGui::Combo("Outfit", &outfit, outfitLabels, 3)) {
			cs.outfitLevel = outfit;
			modified = true;
			cs.dirty = true;
		}
		ImGui::SameLine();
		ImGui::TextDisabled("(global layer switch)");

		ImGui::Separator();

		/* Front-facing axis */
		{
			Ogre::Vector3 axis = cs.frontAxis;
			float axisVals[3] = { axis.x, axis.y, axis.z };
			ImGui::Text("Front Axis:");
			ImGui::SameLine();
			ImGui::TextDisabled(
				"(direction character faces, used by path following)");
			if (ImGui::DragFloat3("##frontAxis", axisVals, 0.1f,
					      -1.0f, 1.0f)) {
				cs.frontAxis = Ogre::Vector3(
					axisVals[0], axisVals[1], axisVals[2]);
				if (cs.frontAxis.squaredLength() > 0.0001f)
					cs.frontAxis.normalise();
				modified = true;
			}
			ImGui::SameLine();
			if (ImGui::SmallButton("-Z")) {
				cs.frontAxis = Ogre::Vector3::NEGATIVE_UNIT_Z;
				modified = true;
			}
			ImGui::SameLine();
			if (ImGui::SmallButton("+Z")) {
				cs.frontAxis = Ogre::Vector3::UNIT_Z;
				modified = true;
			}
		}

		ImGui::Separator();

		/* Shape Keys */
		if (ImGui::CollapsingHeader("Shape Keys")) {
			auto shapeKeys = CharacterSlotSystem::getShapeKeyNames(
				cs.age, cs.sex);
			if (shapeKeys.empty()) {
				ImGui::TextDisabled("No shape keys available.");
			} else {
				CharacterShapeKeysComponent *skc = nullptr;
				if (entity.has<CharacterShapeKeysComponent>())
					skc = &entity.get_mut<CharacterShapeKeysComponent>();
				else {
					entity.set<CharacterShapeKeysComponent>({});
					skc = &entity.get_mut<CharacterShapeKeysComponent>();
				}

				for (const auto &name : shapeKeys) {
					float val = skc->weights[name];
					if (ImGui::SliderFloat(name.c_str(), &val, 0.0f,
							       1.0f)) {
						skc->weights[name] = val;
						skc->dirty = true;
						cs.dirty = true;
						modified = true;
					}
				}
			}
		}

		ImGui::Separator();

		/* Slot selections */
		std::vector<Ogre::String> availableSlots =
			CharacterSlotSystem::getSlots(cs.age, cs.sex);
		for (const auto &pair : cs.slotSelections) {
			if (std::find(availableSlots.begin(), availableSlots.end(),
				      pair.first) == availableSlots.end())
				availableSlots.push_back(pair.first);
		}
		std::sort(availableSlots.begin(), availableSlots.end());

		for (const auto &slot : availableSlots) {
			/* Ensure selection exists */
			if (cs.slotSelections.find(slot) == cs.slotSelections.end()) {
				SlotSelection sel;
				/* Migrate old explicit mesh if present */
				auto oldIt = cs.slots.find(slot);
				if (oldIt != cs.slots.end())
					sel.explicitMesh = oldIt->second;
				cs.slotSelections[slot] = sel;
			}

			SlotSelection &sel = cs.slotSelections[slot];

			if (ImGui::TreeNode(slot.c_str())) {
				/* Resolved mesh preview */
				Ogre::String resolved =
					CharacterSlotSystem::resolveMesh(cs.age, cs.sex,
									 slot, sel,
									 cs.outfitLevel);
				ImGui::TextDisabled("Resolved: %s",
						    resolved.empty() ? "(none)" :
								       resolved.c_str());

				/* Explicit mesh override */
				bool useExplicit = !sel.explicitMesh.empty();
				if (ImGui::Checkbox("Lock explicit mesh", &useExplicit)) {
					if (!useExplicit) {
						sel.explicitMesh.clear();
					} else {
						/* Lock to currently resolved mesh */
						sel.explicitMesh = CharacterSlotSystem::resolveMesh(
							cs.age, cs.sex, slot, sel, cs.outfitLevel);
					}
					modified = true;
					cs.dirty = true;
				}

				if (useExplicit) {
					std::vector<Ogre::String> meshes =
						CharacterSlotSystem::getMeshes(
							cs.age, cs.sex, slot);
					Ogre::String preview =
						sel.explicitMesh.empty() ?
							"(none)" :
							sel.explicitMesh;
					if (ImGui::BeginCombo("Mesh", preview.c_str())) {
						if (ImGui::Selectable("(none)",
								      sel.explicitMesh.empty())) {
							sel.explicitMesh.clear();
							modified = true;
							cs.dirty = true;
						}
						for (const auto &m : meshes) {
							bool isSelected = (sel.explicitMesh == m);
							if (ImGui::Selectable(m.c_str(), isSelected)) {
								sel.explicitMesh = m;
								modified = true;
								cs.dirty = true;
							}
						}
						ImGui::EndCombo();
					}
				} else {
					/* Layer 1 combo */
					std::vector<Ogre::String> layer1Meshes =
						CharacterSlotSystem::getMeshesForLayer(
							cs.age, cs.sex, slot, 1);
					Ogre::String l1Preview = "none";
					if (!sel.layer1Mesh.empty())
						l1Preview = CharacterSlotSystem::getMeshLabel(
							cs.age, cs.sex, slot, sel.layer1Mesh);
					if (ImGui::BeginCombo("Lingerie (Layer 1)",
							      l1Preview.c_str())) {
						if (ImGui::Selectable("none",
								      sel.layer1Mesh.empty() ||
									      sel.layer1Mesh == "none")) {
							sel.layer1Mesh = "none";
							modified = true;
							cs.dirty = true;
						}
						for (const auto &m : layer1Meshes) {
							Ogre::String label =
								CharacterSlotSystem::getMeshLabel(
									cs.age, cs.sex, slot, m);
							bool isSelected = (sel.layer1Mesh == m);
							if (ImGui::Selectable(label.c_str(),
									      isSelected)) {
								sel.layer1Mesh = m;
								modified = true;
								cs.dirty = true;
							}
						}
						ImGui::EndCombo();
					}

					/* Layer 2 combo */
					std::vector<Ogre::String> layer2Meshes =
						CharacterSlotSystem::getMeshesForLayer(
							cs.age, cs.sex, slot, 2);
					Ogre::String l2Preview = "none";
					if (!sel.layer2Mesh.empty())
						l2Preview = CharacterSlotSystem::getMeshLabel(
							cs.age, cs.sex, slot, sel.layer2Mesh);
					if (ImGui::BeginCombo("Clothing (Layer 2)",
							      l2Preview.c_str())) {
						if (ImGui::Selectable("none",
								      sel.layer2Mesh.empty() ||
									      sel.layer2Mesh == "none")) {
							sel.layer2Mesh = "none";
							modified = true;
							cs.dirty = true;
						}
						for (const auto &m : layer2Meshes) {
							Ogre::String label =
								CharacterSlotSystem::getMeshLabel(
									cs.age, cs.sex, slot, m);
							bool isSelected = (sel.layer2Mesh == m);
							if (ImGui::Selectable(label.c_str(),
									      isSelected)) {
								sel.layer2Mesh = m;
								modified = true;
								cs.dirty = true;
							}
						}
						ImGui::EndCombo();
					}
				}

				ImGui::TreePop();
			}
		}

		/* Rebuild button */
		if (ImGui::Button("Rebuild")) {
			cs.dirty = true;
			modified = true;
		}

		ImGui::Unindent();
	}

	return modified;
}
