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

#include <imgui.h>

#include <turbine/gfx.hpp>

#include <studio/imguiutil.hpp>

namespace studio::ig {

static constexpr void scaleSz(ImVec2 &sz) noexcept {
	auto const scale = dpiScale();
	sz.x *= scale;
	sz.y *= scale;
}

static thread_local float g_dpiScale = 1.f;

void setDpiScale(float const scale) noexcept {
	g_dpiScale = scale;
}

[[nodiscard]]
float dpiScale() noexcept {
	return g_dpiScale;
}

ChildStackItem::ChildStackItem(ox::CStringViewCR id, ImVec2 sz) noexcept {
	scaleSz(sz);
	ImGui::BeginChild(id.c_str(), sz);
}

ChildStackItem::~ChildStackItem() noexcept {
	ImGui::EndChild();
}

IDStackItem::IDStackItem(int const id) noexcept {
	ImGui::PushID(id);
}

IDStackItem::IDStackItem(ox::CString const id) noexcept {
	ImGui::PushID(id);
}

IDStackItem::IDStackItem(ox::CStringViewCR id) noexcept: IDStackItem(id.c_str()) {}

IDStackItem::~IDStackItem() noexcept {
	ImGui::PopID();
}


IndentStackItem::IndentStackItem(float id) noexcept: m_indent(id) {
	ImGui::Indent(m_indent);
}

IndentStackItem::~IndentStackItem() noexcept {
	ImGui::Indent(-m_indent);
}


void centerNextWindow() noexcept {
    auto const &io = ImGui::GetIO();
    auto const sz = io.DisplaySize;
    ImGui::SetNextWindowPos(
    	{sz.x * 0.5f, sz.y * 0.5f},
    	ImGuiCond_Always,
    	{0.5f, 0.5f});
}

bool PushButton(ox::CStringViewCR lbl, ImVec2 const &btnSz) noexcept {
	auto const scale = dpiScale();
	return ImGui::Button(lbl.c_str(), {btnSz.x * scale, btnSz.y * scale});
}

PopupResponse PopupControlsOkCancel(
	float const popupWidth,
	bool &popupOpen,
	ox::CStringViewCR ok,
	ox::CStringViewCR cancel) {
	auto out = PopupResponse::None;
	auto const scale = dpiScale();
	auto const btnSz = ImVec2{50 * scale, BtnSz.y * scale};
	ImGui::Separator();
	ImGui::SetCursorPosX(popupWidth - 118 * scale);
	if (ImGui::Button(ok.c_str(), btnSz)) {
		popupOpen = false;
		out = PopupResponse::OK;
	}
	ImGui::SameLine();
	if (ImGui::IsKeyDown(ImGuiKey_Escape) || ImGui::Button(cancel.c_str(), btnSz)) {
		popupOpen = false;
		out = PopupResponse::Cancel;
	}
	return out;
}

PopupResponse PopupControlsOkCancel(
	bool &popupOpen,
	ox::CStringViewCR ok,
	ox::CStringViewCR cancel) {
	auto const scale = dpiScale();
	return PopupControlsOkCancel(ImGui::GetContentRegionAvail().x + 17 * scale, popupOpen, ok, cancel);
}

PopupResponse PopupControlsOk(
	bool &popupOpen,
	ox::CStringViewCR ok) {
	auto const scale = dpiScale();
	auto out = PopupResponse::None;
	auto const btnSz = ImVec2{50 * scale, BtnSz.y * scale};
	ImGui::Separator();
	ImGui::SetCursorPosX(ImGui::GetContentRegionAvail().x - 42 * scale);
	if (ImGui::Button(ok.c_str(), btnSz)) {
		popupOpen = false;
		out = PopupResponse::OK;
	}
	ImGui::SameLine();
	if (ImGui::IsKeyDown(ImGuiKey_Escape)) {
		popupOpen = false;
		out = PopupResponse::Cancel;
	}
	return out;
}

bool BeginPopup(ox::CStringViewCR popupName, bool &show, ImVec2 sz) {
	scaleSz(sz);
	constexpr auto modalFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
	centerNextWindow();
	ImGui::OpenPopup(popupName.c_str());
	ImGui::SetNextWindowSize(sz);
	return ImGui::BeginPopupModal(popupName.c_str(), &show, modalFlags);
}

bool ComboBox(
		ox::CStringViewCR lbl,
		ox::SpanView<ox::CStringView> const list,
		size_t &selectedIdx) noexcept {
	bool out{};
	auto const first = selectedIdx < list.size() ? list[selectedIdx].c_str() : "";
	if (ImGui::BeginCombo(lbl.c_str(), first, 0)) {
		for (auto i = 0u; i < list.size(); ++i) {
			auto const selected = selectedIdx == i;
			if (ImGui::Selectable(list[i].c_str(), selected) && selectedIdx != i) {
				selectedIdx = i;
				out = true;
			}
		}
		ImGui::EndCombo();
	}
	return out;
}

bool ComboBox(
		ox::CStringViewCR lbl,
		ox::Span<ox::String const> const list,
		size_t &selectedIdx) noexcept {
	bool out{};
	auto const first = selectedIdx < list.size() ? list[selectedIdx].c_str() : "";
	if (ImGui::BeginCombo(lbl.c_str(), first, 0)) {
		for (auto i = 0u; i < list.size(); ++i) {
			auto const selected = selectedIdx == i;
			if (ImGui::Selectable(list[i].c_str(), selected) && selectedIdx != i) {
				selectedIdx = i;
				out = true;
			}
		}
		ImGui::EndCombo();
	}
	return out;
}

bool ComboBox(
		ox::CStringViewCR lbl,
		std::function<ox::CStringView(size_t)> const &f,
		size_t const strCnt,
		size_t &selectedIdx) noexcept {
	bool out{};
	auto const first = selectedIdx < strCnt ? f(selectedIdx).c_str() : "";
	if (ImGui::BeginCombo(lbl.c_str(), first, 0)) {
		for (auto i = 0u; i < strCnt; ++i) {
			auto const selected = (selectedIdx == i);
			if (ImGui::Selectable(f(i).c_str(), selected) && selectedIdx != i) {
				selectedIdx = i;
				out = true;
			}
		}
		ImGui::EndCombo();
	}
	return out;
}

bool FileComboBox(
		ox::CStringViewCR lbl,
		Context &sctx,
		ox::StringViewCR fileExt,
		size_t &selectedIdx) noexcept {
	auto const &list = sctx.project->fileList(fileExt);
	return ComboBox(lbl, list, selectedIdx);
}

bool ListBox(
		ox::CStringViewCR name,
		std::function<ox::CStringView(size_t)> const &f,
		size_t const strCnt,
		size_t &selIdx,
		ImVec2 sz) noexcept {
	auto const scale = dpiScale();
	sz = {sz.x * scale, sz.y * scale};
	auto out = false;
	if (ImGui::BeginListBox(name.c_str(), sz)) {
		for (size_t i = 0; i < strCnt; ++i) {
			auto const str = f(i);
			IDStackItem const idStackItem2(static_cast<int>(i));
			if (ImGui::Selectable(str.c_str(), selIdx == i)) {
				if (i != selIdx) {
					selIdx = i;
					out = true;
				}
			}
		}
		ImGui::EndListBox();
	}
	return out;
}

bool ListBox(ox::CStringViewCR name, ox::SpanView<ox::String> const &list, size_t &selIdx) noexcept {
	return ListBox(name, [list](size_t const i) -> ox::CStringView {
		return list[i];
	}, list.size(), selIdx);
}

bool ListBox(ox::CStringViewCR name, ox::SpanView<ox::CStringView> const &list, size_t &selIdx) noexcept {
	return ListBox(name, [list](size_t const i) -> ox::CStringView {
		return list[i];
	}, list.size(), selIdx);
}


FilePicker::FilePicker(
		Context &sctx,
		ox::StringParam title,
		ox::StringParam fileExt,
		ImVec2 const &size) noexcept:
	m_sctx(sctx),
	m_title(std::move(title)),
	m_fileExt(std::move(fileExt)),
	m_size(size) {
}

void FilePicker::draw() noexcept {
	if (!m_show) {
		return;
	}
	auto constexpr popupSz = ImVec2{450.f, 0};
	IDStackItem const idStackItem(m_title);
	if (BeginPopup(m_title, m_show, popupSz)) {
		auto const &list = m_sctx.project->fileList(m_fileExt);
		size_t selIdx{};
		ComboBox(m_title, list, selIdx);
		if (PopupControlsOkCancel(popupSz.x, m_show) == ig::PopupResponse::OK) {
			filePicked.emit(list[selIdx]);
		}
		ImGui::EndPopup();
	}
}

void FilePicker::show() noexcept {
	m_show = true;
}


Popup::Popup(ox::StringParam title) noexcept: m_title{std::move(title)} {
}

void Popup::open() noexcept {
	m_stage = Stage::Opening;
}

void Popup::close() noexcept {
	m_stage = Stage::Closed;
	m_open = false;
}

bool Popup::isOpen() const noexcept {
	return m_open;
}


QuestionPopup::QuestionPopup(ox::StringParam title, ox::StringParam question) noexcept:
	Popup{std::move(title)},
	m_question{std::move(question)} {
}

void QuestionPopup::setQuestion(ox::StringParam msg) noexcept {
	m_question = std::move(msg);
}

void QuestionPopup::draw(Context &ctx) noexcept {
	switch (m_stage) {
		case Stage::Closed:
			break;
		case Stage::Opening:
			ImGui::OpenPopup(m_title.c_str());
			m_stage = Stage::Open;
			m_open = true;
			// require extended refresh in case new contents require resize
			// this can take around a second
			turbine::requireRefreshFor(ctx.tctx, 1000);
		[[fallthrough]];
		case Stage::Open:
			centerNextWindow();
			ImGui::SetNextWindowSize({});
			constexpr auto modalFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
			if (ImGui::BeginPopupModal(m_title.c_str(), &m_open, modalFlags)) {
				ImGui::Text("%s", m_question.c_str());
				auto const r = PopupControlsOkCancel(m_open, "Yes", "No");
				switch (r) {
					case PopupResponse::None:
						break;
					case PopupResponse::OK:
						response.emit(r);
						close();
						break;
					case PopupResponse::Cancel:
						response.emit(r);
						close();
						break;
				}
				ImGui::EndPopup();
			}
			break;
	}
}


MessagePopup::MessagePopup(ox::StringParam title, ox::StringParam msg) noexcept:
	Popup{std::move(title)},
	m_msg{std::move(msg)} {
}

void MessagePopup::show(ox::StringParam msg) noexcept {
	m_msg = std::move(msg);
	open();
}

void MessagePopup::draw(Context&) noexcept {
	switch (m_stage) {
		case Stage::Closed:
			break;
		case Stage::Opening:
			ImGui::OpenPopup(m_title.c_str());
			m_stage = Stage::Open;
			m_open = true;
		[[fallthrough]];
		case Stage::Open:
			centerNextWindow();
			ImGui::SetNextWindowSize({});
			constexpr auto modalFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
			if (ImGui::BeginPopupModal(m_title.c_str(), &m_open, modalFlags)) {
				ImGui::Text("%s", m_msg.c_str());
				auto const r = PopupControlsOk(m_open, "OK");
				switch (r) {
					case PopupResponse::None:
						break;
					case PopupResponse::OK:
						response.emit(r);
						close();
						break;
					case PopupResponse::Cancel:
						break;
				}
				ImGui::EndPopup();
			}
			break;
	}
}


bool s_mainWinHasFocus{};
bool mainWinHasFocus() noexcept {
	return s_mainWinHasFocus;
}

}
