#ifndef EDITSCENE_TERRAINEDITOR_HPP
#define EDITSCENE_TERRAINEDITOR_HPP
#pragma once

#include "ComponentEditor.hpp"
#include "../components/Terrain.hpp"
#include "../systems/TerrainSystem.hpp"

#include <OgreResourceGroupManager.h>
#include <OgreTextureManager.h>
#include <OgreTexture.h>

#include <imgui.h>
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>

/**
 * Editor panel for TerrainComponent.
 *
 * Milestone 1: minimal — enable/disable toggle and display of
 * key parameters.  Heightmap editing, road tools, splat painting
 * come in later milestones.
 *
 * Milestone 2: "Show Terrain Colliders" checkbox (runtime-only,
 * not serialized).
 *
 * Milestone 3: sculpting, splat painting with layer/texture
 * selection, heightmap save/load, blend-map save/load.
 */
class TerrainEditor : public ComponentEditor<TerrainComponent> {
public:
	bool renderComponent(flecs::entity entity,
			     TerrainComponent &tc) override
	{
		bool changed = false;

		ImGui::PushID("Terrain");

		/* Enable */
		if (ImGui::Checkbox("Enabled", &tc.enabled))
			changed = true;

		/* Show Terrain Colliders (runtime-only, not saved). */
		TerrainSystem *ts = TerrainSystem::getInstance();
		if (ts) {
			bool show = ts->getShowTerrainColliders();
			if (ImGui::Checkbox("Show Terrain Colliders", &show))
				ts->setShowTerrainColliders(show);
		}

		ImGui::Separator();

		/* --- Sculpting (M3) --- */
		if (ts) {
			ImGui::PushID("Sculpt");
			ImGui::Text("Sculpting");
			bool sculpt = ts->getSculptMode();
			if (ImGui::Checkbox("Sculpt Mode", &sculpt))
				ts->setSculptMode(sculpt);
			if (sculpt)
				ImGui::TextColored(ImVec4(0, 1, 0, 1),
						   "SCULPT MODE ACTIVE");
			ImGui::SameLine();
			ImGui::TextDisabled("(ESC to exit)");

			const char *tools[] = { "Raise", "Lower", "Smooth",
						"Flatten" };
			int tool = (int)ts->getSculptTool();
			if (ImGui::Combo("Tool", &tool, tools,
					 IM_ARRAYSIZE(tools)))
				ts->setSculptTool(
					(TerrainSystem::SculptTool)tool);

			float radius = ts->getSculptRadius();
			if (ImGui::SliderFloat("Radius", &radius, 5.0f, 500.0f))
				ts->setSculptRadius(radius);

			float strength = ts->getSculptStrength();
			if (ImGui::SliderFloat("Strength", &strength, 0.01f,
					       1.0f))
				ts->setSculptStrength(strength);
			ImGui::PopID();
		}

		ImGui::Separator();

		/* --- Splat painting (M3) --- */
		if (ts) {
			ImGui::PushID("Paint");
			ImGui::Text("Splat Painting");
			bool paint = ts->getPaintMode();
			if (ImGui::Checkbox("Paint Mode", &paint))
				ts->setPaintMode(paint);
			if (paint)
				ImGui::TextColored(ImVec4(0.2f, 0.5f, 1, 1),
						   "PAINT MODE ACTIVE");

			renderPaintLayerSelector(tc, ts);

			float pradius = ts->getPaintRadius();
			if (ImGui::SliderFloat("Radius", &pradius, 5.0f, 500.0f))
				ts->setPaintRadius(pradius);

			float pstrength = ts->getPaintStrength();
			if (ImGui::SliderFloat("Strength", &pstrength, -1.0f,
					       1.0f, "%.2f"))
				ts->setPaintStrength(pstrength);
			if (pstrength < 0.0f)
				ImGui::TextColored(ImVec4(1, 0.3f, 0.3f, 1),
						   "(erasing)");
			ImGui::PopID();
		}

		ImGui::Separator();

		/* --- Camera & file operations --- */
		if (ts) {
			if (ImGui::Button("Snap Camera Above Terrain"))
				ts->snapCameraAboveTerrain();
			ImGui::SameLine();
			const std::string hmPath = ts->getHeightmapPath(tc);
			if (ImGui::Button("Save Heightmap"))
				ts->saveHeightmap(hmPath);
			ImGui::SameLine();
			if (ImGui::Button("Load Heightmap"))
				ts->loadHeightmap(hmPath);

			if (ImGui::Button("Save Blend Maps"))
				ts->saveSceneBlendMaps(tc);
			ImGui::SameLine();
			if (ImGui::Button("Load Blend Maps"))
				ts->loadSceneBlendMaps(tc);
		}

		ImGui::Separator();

		/* Read-only display of sizing params */
		ImGui::Text("Terrain ID: %llu",
			    (unsigned long long)tc.terrainId);
		ImGui::Text("Page size:  %d x %d vertices", tc.terrainSize,
			    tc.terrainSize);
		ImGui::Text("World size: %.0f units", tc.worldSize);
		ImGui::Text("Heightmap:  %d x %d", tc.heightmapSize,
			    tc.heightmapSize);
		ImGui::Text("Batch:      %d - %d", tc.minBatchSize,
			    tc.maxBatchSize);

		ImGui::Separator();

		/* Quality params (hot — change takes effect next define). */
		if (ImGui::SliderFloat("Max Pixel Error", &tc.maxPixelError,
				       0.5f, 10.0f, "%.1f"))
			changed = true;
		if (ImGui::SliderFloat("Composite Map Dist",
				       &tc.compositeMapDistance, 50.0f,
				       2000.0f, "%.0f"))
			changed = true;

		ImGui::Separator();

		/* Layers — editable with add/remove.  Max 5 (SM2Profile limit). */
		ImGui::Text("Layers: %zu / 5", tc.layers.size());
		ImGui::SameLine();
		if (ImGui::SmallButton("Add Layer") &&
		    tc.layers.size() < 5) {
			/* If the component has no explicit layers yet, the terrain is
			 * using the implicit default base.  Insert that base layer first
			 * so the new layer becomes a splat-mapped layer on top instead of
			 * replacing the ground. */
			if (tc.layers.empty()) {
				TerrainComponent::Layer baseLayer;
				baseLayer.name = "Base";
				baseLayer.diffuseTexture = "Ground23_col.jpg";
				baseLayer.normalTexture = "Ground23_normheight.dds";
				baseLayer.worldSize = 100.0f;
				tc.layers.push_back(baseLayer);
			}

			TerrainComponent::Layer newLayer;
			newLayer.name = "Layer " +
				std::to_string(tc.layers.size());
			/* Use a visually distinct texture so the user can see
			 * the result of splat painting immediately. */
			newLayer.diffuseTexture = "Ground37_diffspec.dds";
			newLayer.normalTexture = "Ground37_normheight.dds";
			newLayer.worldSize = 100.0f;
			tc.layers.push_back(newLayer);
			changed = true;
			if (ts)
				ts->deactivate();
			Ogre::LogManager::getSingleton().logMessage(
				"TerrainEditor: added layer " + newLayer.name +
				" diffuse=" + newLayer.diffuseTexture +
				" normal=" + newLayer.normalTexture);
		}
		ImGui::SameLine();
		if (ImGui::SmallButton("Remove Layer") &&
		    tc.layers.size() > 1) {
			tc.layers.pop_back();
			changed = true;
			if (ts)
				ts->deactivate();
		}
		for (size_t i = 0; i < tc.layers.size(); ++i) {
			auto &l = tc.layers[i];
			ImGui::PushID((int)i);
			if (ImGui::TreeNode("Layer", "[%zu] %s", i,
					    l.name.empty() ?
						    l.diffuseTexture.c_str() :
						    l.name.c_str())) {
				char buf[256];
				strncpy(buf, l.name.c_str(), sizeof(buf) - 1);
				buf[sizeof(buf) - 1] = 0;
				if (ImGui::InputText("Name", buf, sizeof(buf))) {
					l.name = buf;
					changed = true;
				}

				if (texturePicker("Diffuse", l.diffuseTexture)) {
					changed = true;
					if (ts)
						ts->deactivate();
				}
				renderTexturePreview(l.diffuseTexture);

				if (texturePicker("Normal", l.normalTexture)) {
					changed = true;
					if (ts)
						ts->deactivate();
				}
				renderTexturePreview(l.normalTexture);

				float worldSize = l.worldSize;
				if (ImGui::SliderFloat("World Size", &worldSize,
						       1.0f, 1000.0f, "%.1f")) {
					l.worldSize = worldSize;
					changed = true;
					if (ts)
						ts->deactivate();
				}

				ImGui::TreePop();
			}
			ImGui::PopID();
		}
		ImGui::Separator();

		if (ImGui::Button("Reset to Defaults")) {
			tc = TerrainComponent();
			changed = true;
		}

		ImGui::PopID();
		return changed;
	}

	const char *getName() const override
	{
		return "Terrain";
	}

private:
	static std::vector<std::string> &getTextureList()
	{
		static std::vector<std::string> s_textures;
		return s_textures;
	}

	static bool &getTexturesScanned()
	{
		static bool s_scanned = false;
		return s_scanned;
	}

	static std::string &getTextureSearchBuffer()
	{
		static std::string s_search;
		return s_search;
	}

	static std::string &getTexturePickerTarget()
	{
		static std::string s_target;
		return s_target;
	}

	static bool &getTexturePickerOpen()
	{
		static bool s_open = false;
		return s_open;
	}

	static bool isTextureExtension(const std::string &ext)
	{
		static const char *imageExts[] = { "jpg", "jpeg", "png",
						   "dds", "tga", "bmp", "tif",
						   "tiff", "gif", "ktx" };
		std::string lower = ext;
		std::transform(lower.begin(), lower.end(), lower.begin(),
			       ::tolower);
		for (const char *e : imageExts) {
			if (lower == e)
				return true;
		}
		return false;
	}

	static void scanTextureFiles()
	{
		if (getTexturesScanned())
			return;

		std::vector<std::string> &textures = getTextureList();
		textures.clear();

		Ogre::ResourceGroupManager &rgm =
			Ogre::ResourceGroupManager::getSingleton();
		Ogre::StringVector groups = rgm.getResourceGroups();

		for (const auto &group : groups) {
			Ogre::FileInfoListPtr fileList =
				rgm.findResourceFileInfo(group, "*");
			if (!fileList)
				continue;

			for (const auto &fileInfo : *fileList) {
				const std::string &filename = fileInfo.filename;
				size_t dotPos = filename.find_last_of('.');
				if (dotPos == std::string::npos)
					continue;

				std::string ext = filename.substr(dotPos + 1);
				if (isTextureExtension(ext))
					textures.push_back(filename);
			}
		}

		std::sort(textures.begin(), textures.end());
		textures.erase(std::unique(textures.begin(), textures.end()),
			       textures.end());

		getTexturesScanned() = true;
	}

	static bool texturePicker(const char *label, std::string &current)
	{
		bool changed = false;

		std::vector<std::string> &textures = getTextureList();
		if (!getTexturesScanned())
			scanTextureFiles();

		/* Simple combo if the list is small, otherwise just show
		 * current value with a Browse button. */
		int currentIdx = -1;
		std::vector<const char *> items;
		items.reserve(textures.size() + 1);
		items.push_back("(none)");
		for (size_t i = 0; i < textures.size(); ++i) {
			if (textures[i] == current)
				currentIdx = (int)i + 1;
			items.push_back(textures[i].c_str());
		}

		int idx = currentIdx;
		if (ImGui::Combo(label, &idx, items.data(),
				 (int)items.size())) {
			if (idx <= 0)
				current.clear();
			else if (idx - 1 < (int)textures.size())
				current = textures[idx - 1];
			changed = true;
		}

		ImGui::SameLine();
		if (ImGui::Button("Browse")) {
			getTexturePickerTarget() = label;
			getTexturePickerOpen() = true;
			getTextureSearchBuffer().clear();
		}

		renderTextureBrowser(label, current);
		return changed;
	}

	static void renderTextureBrowser(const char *label,
					 std::string &current)
	{
		if (getTexturePickerTarget() != label)
			return;

		if (getTexturePickerOpen())
			ImGui::OpenPopup("Texture Browser");

		bool open = getTexturePickerOpen();
		if (!ImGui::BeginPopupModal("Texture Browser", &open,
					    ImGuiWindowFlags_AlwaysAutoResize)) {
			if (!open)
				getTexturePickerOpen() = false;
			return;
		}

		std::vector<std::string> &textures = getTextureList();

		char searchBuf[256];
		strncpy(searchBuf, getTextureSearchBuffer().c_str(),
			sizeof(searchBuf) - 1);
		searchBuf[sizeof(searchBuf) - 1] = 0;
		if (ImGui::InputText("Search", searchBuf, sizeof(searchBuf)))
			getTextureSearchBuffer() = searchBuf;

		ImGui::Separator();

		ImGui::BeginChild("TextureList", ImVec2(400, 300), true);

		std::string search = getTextureSearchBuffer();
		std::transform(search.begin(), search.end(), search.begin(),
			       ::tolower);

		for (const auto &tex : textures) {
			if (!search.empty()) {
				std::string lower = tex;
				std::transform(lower.begin(), lower.end(),
					       lower.begin(), ::tolower);
				if (lower.find(search) == std::string::npos)
					continue;
			}

			bool isCurrent = (current == tex);
			if (isCurrent)
				ImGui::PushStyleColor(
					ImGuiCol_Text,
					ImVec4(0.2f, 0.8f, 0.2f, 1.0f));

			if (ImGui::Selectable(tex.c_str(), isCurrent)) {
				current = tex;
				getTexturePickerOpen() = false;
				ImGui::CloseCurrentPopup();
			}

			if (isCurrent)
				ImGui::PopStyleColor();
		}

		if (textures.empty())
			ImGui::TextDisabled("No textures found");

		ImGui::EndChild();

		ImGui::Separator();

		if (ImGui::Button("Cancel", ImVec2(120, 0))) {
			getTexturePickerOpen() = false;
			ImGui::CloseCurrentPopup();
		}
		ImGui::SameLine();
		if (ImGui::Button("Refresh", ImVec2(120, 0))) {
			getTexturesScanned() = false;
			scanTextureFiles();
		}

		ImGui::EndPopup();
		getTexturePickerOpen() = open;
	}

	static void renderTexturePreview(const std::string &textureName)
	{
		if (textureName.empty())
			return;

		Ogre::TextureManager &tm =
			Ogre::TextureManager::getSingleton();
		Ogre::ResourceGroupManager &rgm =
			Ogre::ResourceGroupManager::getSingleton();

		/* Find the resource group that contains this texture. */
		Ogre::String groupName = Ogre::ResourceGroupManager::
			DEFAULT_RESOURCE_GROUP_NAME;
		if (!tm.resourceExists(textureName, groupName)) {
			for (const auto &g : rgm.getResourceGroups()) {
				if (tm.resourceExists(textureName, g)) {
					groupName = g;
					break;
				}
			}
		}

		/* Try to load the texture and display a small thumbnail.
		 * We attempt to retrieve the renderer-specific native
		 * handle (OpenGL texture ID) and fall back to a coloured
		 * placeholder if that fails. */
		try {
			Ogre::TexturePtr tex = tm.load(textureName, groupName);
			if (!tex)
				return;

			Ogre::uint glID = 0;
			try {
				glID = tex->getCustomAttribute("GLID");
			} catch (const std::exception &) {
				glID = 0;
			}

			if (glID != 0) {
				ImTextureID texID = (ImTextureID)(uintptr_t)glID;
				ImGui::Image(texID, ImVec2(64, 64),
					     ImVec2(0, 0), ImVec2(1, 1));
			} else {
				ImVec4 colour(0.5f, 0.5f, 0.5f, 1.0f);
				ImGui::ColorButton(
					"Preview", colour,
					ImGuiColorEditFlags_NoTooltip |
						ImGuiColorEditFlags_NoBorder,
					ImVec2(64, 64));
			}
			ImGui::SameLine();
			ImGui::Text("%dx%d", (int)tex->getWidth(),
				    (int)tex->getHeight());
		} catch (const std::exception &) {
		}
	}

	static void renderPaintLayerSelector(TerrainComponent &tc,
					     TerrainSystem *ts)
	{
		if (tc.layers.size() < 2) {
			ImGui::TextColored(
				ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
				"Add a layer to enable splat painting.");
			return;
		}

		if (!tc.layers.empty() &&
		    tc.layers[0].diffuseTexture ==
			    tc.layers[ts->getPaintLayerIndex()].diffuseTexture) {
			ImGui::TextColored(
				ImVec4(1.0f, 0.8f, 0.2f, 1.0f),
				"Warning: paint layer uses the same diffuse texture as the base layer.");
		}

		int layerIdx = ts->getPaintLayerIndex();
		layerIdx = std::max(1, std::min(layerIdx,
						(int)tc.layers.size() - 1));
		if (layerIdx != ts->getPaintLayerIndex())
			ts->setPaintLayerIndex(layerIdx);

		std::vector<const char *> items;
		items.reserve(tc.layers.size());
		static char labels[5][128];
		for (size_t i = 0; i < tc.layers.size() && i < 5; ++i) {
			const auto &l = tc.layers[i];
			snprintf(labels[i], sizeof(labels[i]), "[%zu] %s", i,
				 l.name.empty() ? l.diffuseTexture.c_str() :
						  l.name.c_str());
			items.push_back(labels[i]);
		}

		int idx = layerIdx;
		if (ImGui::Combo("Paint Layer", &idx, items.data(),
				 (int)items.size())) {
			ts->setPaintLayerIndex(idx);
		}
	}
};

#endif // EDITSCENE_TERRAINEDITOR_HPP
