/*
 * Copyright 2023 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
 */

#include <algorithm>

#include <studio/imguiutil.hpp>

#include <nostalgia/gfx/studio.hpp>

#include "commands/addobject.hpp"
#include "commands/rmobject.hpp"
#include "commands/editobject.hpp"
#include "commands/addpalette.hpp"
#include "commands/rmpalette.hpp"
#include "commands/changetilesheet.hpp"

#include "worldobjectseteditor-imgui.hpp"

namespace jasper::world {

namespace ig = studio::ig;

constexpr auto btnSize = ImVec2{22, ig::BtnSz.y};

WorldObjectSetEditorImGui::WorldObjectSetEditorImGui(
		studio::Context &sctx,
		ox::StringParam path):
	Editor(sctx, std::move(path)),
	m_sctx{sctx},
	m_itemPath{itemPath()},
	m_doc{*readObj<WorldObjectSet>(keelCtx(m_sctx), itemPath()).unwrapThrow()},
	m_tileSheet{readObj<ngfx::TileSheet>(keelCtx(m_sctx), m_doc.tilesheet)
		.or_value(keel::AssetRef<ngfx::TileSheet>{})},
	m_tsPicker{"Tile Sheet Chooser", keelCtx(m_sctx), ngfx::FileExts_TileSheet},
	m_addPalPopup{"Palette Chooser", keelCtx(m_sctx), ngfx::FileExt_npal} {
	auto &kctx = keelCtx(m_sctx);
	auto const [tsPath, err] = getPath(kctx, m_doc.tilesheet);
	if (!err) {
		m_tilesheetPath = tsPath;
	}
	std::ignore = loadObj();
	m_tileSheet.updated.connect(this, &WorldObjectSetEditorImGui::loadObj);
	undoStack()->changeTriggered.connect(this, &WorldObjectSetEditorImGui::handleCmd);
	buildPaletteDisplayNameList();
}

void WorldObjectSetEditorImGui::draw(studio::Context&) noexcept {
	const auto paneSize = ImGui::GetContentRegionAvail();
	constexpr auto resourcesWidth = 300.f;
	ImGui::BeginChild("ObjEditor", {paneSize.x - resourcesWidth, 0});
	if (m_selectedObj < m_doc.objects.size()) {
		drawObjEditor();
	}
	ImGui::EndChild();
	ImGui::SameLine();
	ImGui::BeginChild("Resources", {0, 0});
	{
		{
			ig::IDStackItem const idStackItem{"TileSheetSelector"};
			drawTileSheetSelector();
		}
		ImGui::Separator();
		{
			ig::IDStackItem const idStackItem{"ObjSelector"};
			drawObjSelector();
		}
		ImGui::Separator();
		{
			ig::IDStackItem const idStackItem{"PaletteList"};
			drawPaletteList();
		}
	}
	ImGui::EndChild();
	drawAddPalettePopup();
	drawTileSheetPicker();
}

void WorldObjectSetEditorImGui::onActivated() noexcept {
}

void WorldObjectSetEditorImGui::navigateTo(ox::StringViewCR arg) noexcept {
	auto const idx = ox::find_if(
		m_doc.objects.begin(),
		m_doc.objects.end(),
		[arg](WorldObject const &wo) {
			return wo.name == arg;
		});
	if (idx != m_doc.objects.end()) {
		m_selectedObj = idx.offset();
		std::ignore = loadObj();
	}
}

WorldObject const &WorldObjectSetEditorImGui::activeObj() const noexcept {
	return m_doc.objects[m_selectedObj];
}

WorldObject &WorldObjectSetEditorImGui::activeObj() noexcept {
	return m_doc.objects[m_selectedObj];
}

void WorldObjectSetEditorImGui::buildPaletteDisplayNameList() noexcept {
	m_paletteDisplayNames.clear();
	for (auto i = 1u; auto const &pal : m_doc.palettes) {
		auto const path = keel::getPath(keelCtx(m_sctx.tctx), pal).or_value("Invalid path");
		m_paletteDisplayNames.emplace_back(ox::sfmt("{}: {}", i, path));
		++i;
	}
}

void WorldObjectSetEditorImGui::drawTileSheetSelector() noexcept {
	auto constexpr tsFlags = ImGuiInputTextFlags_ReadOnly;
	ig::InputTextWithHint("##Tile Sheet", "Path to Tile Sheet", m_tilesheetPath, tsFlags);
	if (ig::DragDropTarget const d; d) {
		auto const [fr, err] = ig::getDragDropPayload<studio::FileRef>("FileRef");
		if (!err && ngfx::isTileSheet(fr.path)) {
			oxLogError(setTileSheet(fr.path));
		}
	}
	ImGui::SameLine();
	if (ImGui::Button("Browse")) {
		m_tsPicker.open();
	}
}

void WorldObjectSetEditorImGui::drawObjSelector() noexcept {
	ig::IDStackItem const idStackItem1("ObjSelector");
	if (ig::PushButton("+", btnSize)) {
		auto cmd = ox::make_unique<AddObject>(m_doc);
		m_selectedObj = cmd->insertIdx();
		std::ignore = undoStack()->push(std::move(cmd));
		std::ignore = loadObj();
	}
	ImGui::SameLine();
	if (ig::PushButton("-", btnSize)) {
		std::ignore = pushCommand<RmObject>(m_doc, m_selectedObj);
	}
	if (ig::ListBox("Objects", [this](size_t i) -> ox::CStringView {
			return m_doc.objects[i].name;
		}, m_doc.objects.size(), m_selectedObj)) {
		std::ignore = loadObj();
	}
}

ox::Error WorldObjectSetEditorImGui::loadObj() noexcept {
	if (m_selectedObj >= m_doc.objects.size()) {
		return ox::Error{1};
	}
	auto const &obj = activeObj();
	auto &nameBuff = m_objEditor.nameBuff;
	nameBuff = obj.name;
	m_objEditor.palette = obj.palBank;
	m_objEditor.frames = obj.frames;
	m_objEditor.interval = obj.intervalMs;
	m_visibleSubsheet = getSubsheet(*m_tileSheet, obj.subsheetId);
	auto const subsheetId = !m_visibleSubsheet || m_visibleSubsheet->subsheets.empty() ?
		obj.subsheetId : m_visibleSubsheet->subsheets[0].id;
	m_visibleSubsheet = getSubsheet(*m_tileSheet, subsheetId);
	int w = 0;
	int h = 0;
	if (m_visibleSubsheet) {
		w = m_visibleSubsheet->columns;
		h = m_visibleSubsheet->rows;
	}
	auto const tileIdx = getTileIdx(
		*m_tileSheet,
		subsheetId).or_value(0);
	auto const &palAddr = m_doc.palettes.size() > obj.palBank ?
		m_doc.palettes[obj.palBank] : ox::FileAddress{};
	return m_colView.setup(
			m_doc.tilesheet,
			palAddr,
			w,
			h,
			static_cast<uint_t>(tileIdx),
			obj.collisionMap);
}

[[nodiscard]]
static ox::Vec2 clickPos(
		ImVec2 const &imgSz,
		ImVec2 const &offset,
		ox::Vec2 clickPos) noexcept {
	clickPos.x -= offset.x;
	clickPos.y -= offset.y;
	clickPos.x /= imgSz.x;
	clickPos.y /= imgSz.y;
	return clickPos;
}

void WorldObjectSetEditorImGui::drawObjEditor() noexcept {
	ig::IDStackItem const idStackItem{"ObjEditor"};
	ig::IndentStackItem const indent1{10};
	ImGui::NewLine();
	auto &nameBuff = m_objEditor.nameBuff;
	if (ig::InputText("Name", nameBuff)) {
		std::ignore = pushCommand<EditObjectName>(
				m_doc,
				m_selectedObj,
				ox::String{nameBuff});
	}
	// SubSheet Selector
	{
		ig::IDStackItem const subsheetSelectorStackItem("SubsheetSelector");
		if (ig::ComboBox("Palette", m_paletteDisplayNames, m_objEditor.palette)) {
			std::ignore = pushCommand<EditObjectPalette>(
					m_doc, m_selectedObj, static_cast<uint8_t>(m_objEditor.palette));
		}
		if (ImGui::InputInt("Frames", &m_objEditor.frames, 1)) {
			m_objEditor.frames = ox::max(m_objEditor.frames, 1);
			std::ignore = pushCommand<EditObjectFrames>(
					m_doc, m_selectedObj, static_cast<uint8_t>(m_objEditor.frames));
		}
		ImGui::BeginDisabled(m_objEditor.frames < 2);
		{
			if (ImGui::InputInt("Interval (ms)", &m_objEditor.interval, 50, 1000)) {
				m_objEditor.interval = ox::clamp(m_objEditor.interval, 50, 65535);
				std::ignore = pushCommand<EditObjectInterval>(
						m_doc, m_selectedObj, static_cast<uint16_t>(m_objEditor.interval));
			}
		}
		ImGui::EndDisabled();
		ImGui::Separator();
		ImGui::Text("Subsheet:");
		ImGui::BeginChild("SubsheetSelector", {300, 300});
		{
			ImGui::SetNextItemOpen(true);
			if (m_tileSheet) {
				drawSubSheetNode(m_tileSheet->subsheet);
			}
		}
		ImGui::EndChild();
	}
	// collision map
	if (m_visibleSubsheet) {
		ImGui::NewLine();
		ImGui::Text("Collision Map:");
		ig::IndentStackItem const indent2{30};
		m_colView.draw();
		auto const &fb = m_colView.framebuffer();
		uintptr_t const buffId = fb.color.id;
		auto const scale = static_cast<float>(m_colView.scale());
		auto const width = static_cast<float>(m_visibleSubsheet->columns * ngfx::TileWidth);
		auto const height = static_cast<float>(m_visibleSubsheet->rows * ngfx::TileHeight);
		auto const horzPct = width / 240.f;
		auto const vertPct = height / 160.f;
		auto const imageSz = ImVec2{width * scale, height * scale};
		auto const imagePos = ImGui::GetCursorScreenPos();
		ImGui::Image(
				std::bit_cast<ImTextureID>(buffId),
				imageSz,
				{0, 1.0},
				{horzPct, 1 - vertPct});
		auto const &io = ImGui::GetIO();
		if (ImGui::IsItemClicked()) {
			auto const mousePos = clickPos(imageSz, imagePos, ox::Vec2{io.MousePos});
			auto &obj = activeObj();
			auto intermediate = obj.collisionMap;
			if (m_colView.click(mousePos, intermediate)) {
				std::ignore = pushCommand<EditObjectCollisionMap>(m_doc, m_selectedObj, intermediate);
			}
		}
	}
}

void WorldObjectSetEditorImGui::drawSubSheetNode(ngfx::TileSheet::SubSheet const &ss) noexcept {
	auto constexpr dirFlags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;
	auto const &obj = activeObj();
	auto const selected = ss.id == obj.subsheetId ? ImGuiTreeNodeFlags_Selected : 0;
	ig::IDStackItem const idStackItem{ss.name};
	if (!ss.subsheets.empty()) {
		if (ImGui::TreeNodeEx(ss.name.c_str(), dirFlags | selected)) {
			if (ImGui::IsItemClicked()) {
				std::ignore = pushCommand<EditObjectSubSheet>(
					m_doc, m_selectedObj, ss.id, static_cast<uint8_t>(ss.subsheets.size()));
			}
			if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
				ngfx::navigateToTileSheet(m_sctx, m_tilesheetPath, ss.id);
			}
			for (auto const &child : ss.subsheets) {
				drawSubSheetNode(child);
			}
			ImGui::TreePop();
		} else {
			if (ImGui::IsItemClicked()) {
				std::ignore = pushCommand<EditObjectSubSheet>(
					m_doc, m_selectedObj, ss.id, static_cast<uint8_t>(ss.subsheets.size()));
			}
			if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
				ngfx::navigateToTileSheet(m_sctx, m_tilesheetPath, ss.id);
			}
		}
	} else if (ImGui::TreeNodeEx(ss.name.c_str(), ImGuiTreeNodeFlags_Leaf | selected)) {
		if (ImGui::IsItemClicked()) {
			std::ignore = pushCommand<EditObjectSubSheet>(m_doc, m_selectedObj, ss.id, static_cast<uint8_t>(1));
		}
		if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
			ngfx::navigateToTileSheet(m_sctx, m_tilesheetPath, ss.id);
		}
		ImGui::TreePop();
	}
}

void WorldObjectSetEditorImGui::drawPaletteList() noexcept {
	ig::IDStackItem const idStackItem{"PaletteList"};
	if (ig::PushButton("+", btnSize)) {
		showAddPalette();
	}
	ImGui::SameLine();
	if (ig::PushButton("-", btnSize)) {
		rmSelectedPalette();
	}
	constexpr auto flags = ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody;
	if (ImGui::BeginTable("Subsheets", 1, flags)) {
		ImGui::TableSetupColumn("Palette", ImGuiTableColumnFlags_NoHide);
		ImGui::TableHeadersRow();
		drawPaletteListItems();
		ImGui::EndTable();
	}
	if (ig::DragDropTarget const d; d) {
		auto const [fr, err] = ig::getDragDropPayload<studio::FileRef>("FileRef");
		if (!err && endsWith(fr.path, ngfx::FileExt_npal)) {
			addPalette(fr.path);
		}
	}
}

void WorldObjectSetEditorImGui::drawPaletteListItems() noexcept {
	for (auto i = 0u; auto const &pal : m_doc.palettes) {
		ig::IDStackItem const idStackItem{static_cast<int>(i)};
		ImGui::TableNextRow();
		ImGui::TableNextColumn();
		auto const path
			= keel::getPath(keelCtx(m_sctx.tctx), pal).or_value("Invalid path");
		ImGui::Text("%s", path.c_str());
		ImGui::SameLine();
		if (ImGui::Selectable("##PaletteSelection", i == m_selectedPal, ImGuiSelectableFlags_SpanAllColumns)) {
			m_selectedPal = i;
		}
		if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
			auto const [p, err] = m_doc.palettes[i].getPath();
			if (!err) {
				ngfx::navigateToPalette(m_sctx, p);
			}
		}
		++i;
	}
}

void WorldObjectSetEditorImGui::drawAddPalettePopup() noexcept {
	if (auto o = m_addPalPopup.draw(m_sctx)) {
		addPalette(*o);
	}
}

void WorldObjectSetEditorImGui::drawTileSheetPicker() noexcept {
	if (auto ts = m_tsPicker.draw(m_sctx)) {
		oxLogError(setTileSheet(*ts));
	}
}

void WorldObjectSetEditorImGui::addPalette(ox::StringViewCR path) noexcept {
	auto const [uuid, err] = keel::pathToUuid(
		keelCtx(m_sctx.tctx), path);
	if (!err) {
		auto const uuidUrl = ox::sfmt("uuid://{}", uuid.toString());
		ox::FileAddress addr{uuidUrl};
		auto const idx = ox::findIdx(m_doc.palettes.begin(), m_doc.palettes.end(), addr);
		if (idx.error) {
			std::ignore = pushCommand<AddPalette>(m_doc, std::move(addr));
		}
	}
}

void WorldObjectSetEditorImGui::showAddPalette() noexcept {
	m_addPalPopup.open();
}

void WorldObjectSetEditorImGui::rmSelectedPalette() noexcept {
	std::ignore = pushCommand<RmPalette>(m_doc, m_selectedPal);
}

ox::Error WorldObjectSetEditorImGui::setTileSheet(ox::StringViewCR path) noexcept {
	auto const [uuid, err] = keel::pathToUuid(
			keelCtx(m_sctx), path);
	auto const uuidUrl = ox::sfmt("uuid://{}", uuid.toString());
	std::ignore = pushCommand<ChangeTileSheet>
			(keelCtx(m_sctx), m_doc, ox::FileAddress{uuidUrl}, m_tilesheetPath);
	return loadObj();
}

ox::Error WorldObjectSetEditorImGui::handleCmd(studio::UndoCommand const*cmd) noexcept {
	if (dynamic_cast<ChangeTileSheet const*>(cmd)) {
		auto &kctx = keelCtx(m_sctx.tctx);
		std::ignore = m_tileSheet.updated.disconnectObject(this);
		oxLogError(readObj<ngfx::TileSheet>(kctx, m_doc.tilesheet).moveTo(m_tileSheet));
		m_tileSheet.updated.connect(this, &WorldObjectSetEditorImGui::loadObj);
	} else if (
		dynamic_cast<AddPalette const*>(cmd) ||
	    dynamic_cast<RmPalette const*>(cmd)) {
		buildPaletteDisplayNameList();
	} else if (dynamic_cast<EditObjectPalette const*>(cmd)) {
		auto const &obj = activeObj();
		m_objEditor.palette = obj.palBank;
	}
	return loadObj();
}

ox::Error WorldObjectSetEditorImGui::saveItem() noexcept {
	return m_sctx.project->writeObj(m_itemPath, m_doc, ox::ClawFormat::Organic);
}

}
