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

#include <imgui.h>

#include <keel/media.hpp>

#include "commands/addcolorcommand.hpp"
#include "commands/addpagecommand.hpp"
#include "commands/applycolorallpagescommand.hpp"
#include "commands/duplicatepagecommand.hpp"
#include "commands/movecolorcommand.hpp"
#include "commands/movepagecommand.hpp"
#include "commands/removecolorcommand.hpp"
#include "commands/removepagecommand.hpp"
#include "commands/renamepagecommand.hpp"
#include "commands/updatecolorcommand.hpp"
#include "commands/updatecolorinfocommand.hpp"

#include "paletteeditor-imgui.hpp"


namespace nostalgia::gfx {

namespace ig = studio::ig;

struct ColorDragDrop {
	static constexpr auto TypeName = "nostalgia.gfx.ColorDragDrop";
	static constexpr auto TypeVersion = 1;
	uint32_t i{};
};

OX_MODEL_BEGIN(ColorDragDrop)
	OX_MODEL_FIELD(i)
OX_MODEL_END()

struct PageDragDrop {
	static constexpr auto TypeName = "nostalgia.gfx.PageDragDrop";
	static constexpr auto TypeVersion = 1;
	uint32_t page{};
};

OX_MODEL_BEGIN(PageDragDrop)
	OX_MODEL_FIELD(page)
OX_MODEL_END()

void PaletteEditorImGui::PageRenameDialog::draw() noexcept {
	if (!m_show) {
		return;
	}
	if (ig::BeginPopup("Rename Page", m_show)) {
		if (ImGui::IsWindowAppearing()) {
			ImGui::SetKeyboardFocusHere();
		}
		ig::InputText("Name", m_name);
		auto const nameInputFocused = ImGui::IsItemFocused();
		auto const resp = ig::PopupControlsOkCancel(m_show);
		if ((nameInputFocused && ImGui::IsKeyPressed(ImGuiKey_Enter))
			|| resp == ig::PopupResponse::OK) {
			inputSubmitted.emit(m_name);
			close();
		} else if (resp == ig::PopupResponse::Cancel) {
			close();
		}
		ImGui::EndPopup();
	}
}


PaletteEditorImGui::PaletteEditorImGui(studio::Context &sctx, ox::StringParam path):
		Editor(sctx, std::move(path)),
		m_sctx(sctx),
		m_pal(m_sctx.project->loadObj<Palette>(itemPath()).unwrapThrow()) {
	undoStack()->changeTriggered.connect(this, &PaletteEditorImGui::handleCommand);
	m_pageRenameDlg.inputSubmitted.connect(this, &PaletteEditorImGui::renamePage);
}

void PaletteEditorImGui::draw(studio::Context&) noexcept {
	auto const scale = ig::dpiScale();
	auto const paneSize = ImGui::GetContentRegionAvail();
	{
		ImGui::BeginChild("Pages", {scale * 280, paneSize.y}, true);
		drawPagesEditor();
		ImGui::EndChild();
	}
	ImGui::SameLine();
	{
		ImGui::BeginChild("Colors", {scale * -1, paneSize.y}, true);
		drawColorsEditor();
		ImGui::EndChild();
	}
	m_pageRenameDlg.draw();
}

ox::Error PaletteEditorImGui::saveItem() noexcept {
	return m_sctx.project->writeObj(itemPath(), m_pal, ox::ClawFormat::Organic);
}

void PaletteEditorImGui::navigateTo(ox::StringViewCR arg) noexcept {
	auto const args = ox::split<2>(arg, ';');
	if (args.size() < 2) {
		return;
	}
	auto const &color = args[0];
	auto const &page = args[1];
	{
		auto const [c, err] = strToInt(color);
		if (!err && static_cast<size_t>(c) < colorCnt(m_pal)) {
			m_selectedColorRow = static_cast<size_t>(c);
		}
	}
	{
		auto const [pg, err] = strToInt(page);
		if (!err && static_cast<size_t>(pg) < m_pal.pages.size()) {
			m_page = static_cast<size_t>(pg);
		}
	}
}

void PaletteEditorImGui::drawColumnLeftAlign(ox::CStringViewCR txt) noexcept {
	ImGui::TableNextColumn();
	ImGui::Text("%s", txt.c_str());
}

void PaletteEditorImGui::drawColumn(ox::CStringViewCR txt) noexcept {
	ImGui::TableNextColumn();
	ImGui::SetCursorPosX(
			ImGui::GetCursorPosX() + ImGui::GetColumnWidth() - ImGui::CalcTextSize(txt.data()).x);
	ImGui::Text("%s", txt.c_str());
}

void PaletteEditorImGui::numShortcuts(size_t &val, size_t const sizeRange) noexcept {
	auto const lastElem = sizeRange - 1;
	if (ImGui::IsKeyPressed(ImGuiKey_0)) {
		val = ox::min<size_t>(9, lastElem);
	} else for (auto i = 8u; i < 9; --i) {
		auto const key = static_cast<ImGuiKey>(ImGuiKey_1 + i);
		if (ImGui::IsKeyPressed(key)) {
			val = ox::min<size_t>(i, lastElem);
			break;
		}
	}
}

void PaletteEditorImGui::colorInput(
	ox::CStringViewCR label,
	int &v,
	bool &inputFocused,
	FocusCmd const colorChannel) noexcept {
	if (colorChannel == m_focusCmd) [[unlikely]] {
		ImGui::SetKeyboardFocusHere();
		m_focusCmd = FocusCmd::None;
	}
	ImGui::InputInt(label.c_str(), &v, 1, 5);
	inputFocused = inputFocused || ImGui::IsItemFocused();
	v = ox::max(v, 0);
}

void PaletteEditorImGui::drawColorsEditor() noexcept {
	constexpr auto tableFlags = ImGuiTableFlags_RowBg;
	auto const colorsSz = ImGui::GetContentRegionAvail();
	auto colorEditor = m_selectedColorRow < colorCnt(m_pal, m_page);
	auto const colorEditorWidth = 220;
	static constexpr auto toolbarHeight = 40;
	{
		auto constexpr sz = ImVec2{70, 24};
		if (ig::PushButton("Add", sz)) {
			auto const colorSz = colorCnt(m_pal, m_page);
			constexpr Color16 c = 0;
			std::ignore = pushCommand<AddColorCommand>(m_pal, c, colorSz);
		}
		ImGui::SameLine();
		ImGui::BeginDisabled(m_selectedColorRow >= colorCnt(m_pal, m_page));
		{
			if (ig::PushButton("Remove", sz)) {
				std::ignore = pushCommand<RemoveColorCommand>(m_pal, m_selectedColorRow);
				m_selectedColorRow = ox::min(colorCnt(m_pal, m_page) - 1, m_selectedColorRow);
				colorEditor = m_selectedColorRow < colorCnt(m_pal, m_page);
			}
		}
		ImGui::EndDisabled();
	}
	auto const tblWidth = (colorsSz.x - static_cast<float>(colorEditorWidth) - 8.f)
			* static_cast<float>(colorEditor);
	auto const scale = ig::dpiScale();
	ImGui::BeginTable(
			"Colors",
			6,
			tableFlags,
			{tblWidth * scale, colorsSz.y - (toolbarHeight + 5) * scale});
	{
		ImGui::TableSetupColumn("Idx", ImGuiTableColumnFlags_WidthFixed, 25 * scale);
		ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 100 * scale);
		ImGui::TableSetupColumn("Red", ImGuiTableColumnFlags_WidthFixed, 40 * scale);
		ImGui::TableSetupColumn("Green", ImGuiTableColumnFlags_WidthFixed, 40 * scale);
		ImGui::TableSetupColumn("Blue", ImGuiTableColumnFlags_WidthFixed, 40 * scale);
		ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_NoHide);
		ImGui::TableHeadersRow();
		if (m_page < m_pal.pages.size()) {
			for (auto i = 0u; auto const &c: m_pal.pages[m_page].colors) {
				ig::IDStackItem const idStackItem(static_cast<int>(i));
				ImGui::TableNextRow();
				drawColumn(i + 1);
				drawColumnLeftAlign(m_pal.colorNames[i]);
				drawColumn(red16(c));
				drawColumn(green16(c));
				drawColumn(blue16(c));
				ImGui::TableNextColumn();
				auto const ic = ImGui::GetColorU32({redf(c), greenf(c), bluef(c), 1});
				ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ic);
				if (ImGui::Selectable(
						"##ColorRow", i == m_selectedColorRow, ImGuiSelectableFlags_SpanAllColumns)) {
					m_selectedColorRow = i;
				}
				std::ignore = ig::dragDropSource([this, i] {
					ImGui::Text("%s", m_pal.colorNames[i].c_str());
					return ig::setDragDropPayload(ColorDragDrop{i});
				}, ImGuiDragDropFlags_SourceAllowNullID);
				if (ig::DragDropTarget const d; d) {
					auto const [src, err] = ig::getDragDropPayload<ColorDragDrop>();
					if (!err) {
						std::ignore = pushCommand<MoveColorCommand>(m_pal, src.i, i);
					}
				}
				++i;
			}
		}
	}
	ImGui::EndTable();
	if (colorEditor) {
		ImGui::SameLine();
		ImGui::BeginChild("ColorEditor", {colorEditorWidth, -1}, true);
		drawColorEditor();
		ImGui::EndChild();
	}
}

void PaletteEditorImGui::drawPagesEditor() noexcept {
	auto const scale = ig::dpiScale();
	constexpr auto tableFlags = ImGuiTableFlags_RowBg;
	auto const paneSz = ImGui::GetContentRegionAvail();
	constexpr auto toolbarHeight = 40;
	auto const btnSz = ImVec2{paneSz.x / 4 - 5.5f * scale, 24 * scale};
	if (ImGui::Button("Add", btnSz)) {
		if (m_pal.pages.empty()) {
			std::ignore = pushCommand<AddPageCommand>(m_pal);
		} else {
			std::ignore = pushCommand<DuplicatePageCommand>(m_pal, 0u, m_pal.pages.size());
		}
		m_page = m_pal.pages.size() - 1;
	}
	ImGui::SameLine();
	if (ImGui::Button("Remove", btnSz)) {
		std::ignore = pushCommand<RemovePageCommand>(m_pal, m_page);
		m_page = std::min(m_page, m_pal.pages.size() - 1);
	}
	ImGui::SameLine();
	if (ImGui::Button("Clone", btnSz)) {
		std::ignore = pushCommand<DuplicatePageCommand>(m_pal, m_page, m_pal.pages.size());
	}
	ImGui::SameLine();
	if (ImGui::Button("Rename", btnSz)) {
		m_pageRenameDlg.show(m_pal.pages[m_page].name);
	}
	ImGui::BeginTable(
			"PageSelect",
			2,
			tableFlags,
			{paneSz.x, paneSz.y - (toolbarHeight + 5) * scale});
	{
		ImGui::TableSetupColumn("Page", ImGuiTableColumnFlags_WidthFixed, 60);
		ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 200);
		ImGui::TableHeadersRow();
		for (auto i = 0u; i < m_pal.pages.size(); ++i) {
			ig::IDStackItem const idStackItem(static_cast<int>(i));
			ImGui::TableNextRow();
			drawColumn(i + 1);
			drawColumnLeftAlign(m_pal.pages[i].name);
			ImGui::SameLine();
			if (ImGui::Selectable("##PageRow", i == m_page, ImGuiSelectableFlags_SpanAllColumns)) {
				m_page = i;
			}
			std::ignore = ig::dragDropSource([this, i] {
				ImGui::Text("%s", m_pal.pages[i].name.c_str());
				return ig::setDragDropPayload(PageDragDrop{i});
			}, ImGuiDragDropFlags_SourceAllowNullID);
			if (ig::DragDropTarget const d; d) {
				auto const [src, err] = ig::getDragDropPayload<PageDragDrop>();
				if (!err) {
					std::ignore = pushCommand<MovePageCommand>(m_pal, src.page, i);
				}
			}
		}
	}
	ImGui::EndTable();
}

void PaletteEditorImGui::drawColorEditor() noexcept {
	auto const c = color(m_pal, m_page, m_selectedColorRow);
	int r = red16(c);
	int g = green16(c);
	int b = blue16(c);
	int const a = alpha16(c);
	auto const newName = ig::InputText<50>(
			"Name", m_pal.colorNames[m_selectedColorRow]);
	auto const nameInputFocused = ImGui::IsItemFocused();
	ImGui::Separator();
	bool colorInFocused{};
	colorInput("Red", r, colorInFocused, FocusCmd::Red);
	colorInput("Green", g, colorInFocused, FocusCmd::Green);
	colorInput("Blue", b, colorInFocused, FocusCmd::Blue);
	// color preview
	{
		ImGui::PushStyleColor(
			ImGuiCol_ChildBg,
			color32(color16(r, g, b)) | 0xff'00'00'00);
		ImGui::BeginChild("ColorPreview", {0, 25});
		ImGui::EndChild();
		ImGui::PopStyleColor();
	}
	if (ig::PushButton("Apply to all pages", {-1, ig::BtnSz.y})) {
		std::ignore = pushCommand<ApplyColorAllPagesCommand>(
				m_pal, m_page, m_selectedColorRow);
	}
	if (ig::mainWinHasFocus() && !ImGui::IsKeyDown(ImGuiKey_ModCtrl)) {
		if (!ImGui::IsKeyDown(ImGuiKey_ModAlt)) {
			if (!nameInputFocused && !colorInFocused) {
				numShortcuts(m_selectedColorRow, largestPage(m_pal));
			}
		} else {
			numShortcuts(m_page, m_pal.pages.size());
		}
		if (!nameInputFocused) {
			if (ImGui::IsKeyDown(ImGuiKey_R)) {
				m_focusCmd = FocusCmd::Red;
			}
			if (ImGui::IsKeyDown(ImGuiKey_G)) {
				m_focusCmd = FocusCmd::Green;
			}
			if (ImGui::IsKeyDown(ImGuiKey_B)) {
				m_focusCmd = FocusCmd::Blue;
			}
		}
	}
	auto const newColor = color16(r, g, b, a);
	if (c != newColor) {
		std::ignore = pushCommand<UpdateColorCommand>(m_pal, m_page, m_selectedColorRow, newColor);
	}
	if (newName) {
		std::ignore = pushCommand<UpdateColorInfoCommand>(
				m_pal, m_selectedColorRow, static_cast<ox::String>(newName.text));
	}
}

ox::Error PaletteEditorImGui::renamePage(ox::StringViewCR name) noexcept {
	return pushCommand<RenamePageCommand>(m_pal, m_page, name);
}

ox::Error PaletteEditorImGui::handleCommand(studio::UndoCommand const*cmd) noexcept {
	if (dynamic_cast<RemovePageCommand const*>(cmd)) {
		m_page = ox::min(m_page, m_pal.pages.size() - 1);
	} else if (auto const dupPageCmd = dynamic_cast<DuplicatePageCommand const*>(cmd)) {
		m_page = ox::clamp<size_t>(dupPageCmd->insertIdx(), 0, m_pal.pages.size() - 1);
	}
	return {};
}

}
