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

#pragma once

#include <functional>

#include <imgui.h>

#include <ox/std/bit.hpp>
#include <ox/std/cstringview.hpp>
#include <ox/std/stringparam.hpp>
#include <ox/claw/claw.hpp>

#include <studio/widget.hpp>

namespace turbine {
class Context;
}

namespace studio {
struct Context;
}

namespace studio::ig {

inline constexpr auto BtnSz = ImVec2{52, 22};

void setDpiScale(float scale) noexcept;

[[nodiscard]]
float dpiScale() noexcept;

constexpr ImTextureID toImTextureID(ox::Unsigned_c auto id) noexcept
	requires(sizeof(id) <= sizeof(ox::Uint<sizeof(ImTextureID)*8>)) {
	return std::bit_cast<ImTextureID>(ox::Uint<sizeof(ImTextureID)*8>{id});
}

template<typename T>
ox::Result<T> getDragDropPayload(ox::CStringViewCR name) noexcept {
	auto const payload = ImGui::AcceptDragDropPayload(name.c_str());
	if (!payload) {
		return ox::Error(1, "No drag/drop payload");
	}
	return ox::readClaw<T>({
			static_cast<char const*>(payload->Data),
			static_cast<size_t>(payload->DataSize)});
}

template<typename T>
ox::Result<T> getDragDropPayload() noexcept {
	auto const payload = ImGui::AcceptDragDropPayload(ox::ModelTypeName_v<T>.c_str());
	if (!payload) {
		return ox::Error(1, "No drag/drop payload");
	}
	return ox::readClaw<T>({
			static_cast<char const*>(payload->Data),
			static_cast<size_t>(payload->DataSize)});
}

ox::Error setDragDropPayload(ox::CStringViewCR name, auto const &obj) noexcept {
	OX_REQUIRE(buff, ox::writeClaw(obj, ox::ClawFormat::Metal));
	ImGui::SetDragDropPayload(name.c_str(), buff.data(), buff.size());
	return {};
}

template<typename T>
ox::Error setDragDropPayload(T const &obj) noexcept {
	OX_REQUIRE(buff, ox::writeClaw(obj, ox::ClawFormat::Metal));
	ImGui::SetDragDropPayload(ox::ModelTypeName_v<T>.c_str(), buff.data(), buff.size());
	return {};
}


class DragDropSource {
	private:
		bool const m_active{};
	public:
		explicit DragDropSource(ImGuiDragDropFlags const flags = 0) noexcept:
			m_active{ImGui::BeginDragDropSource(flags)} {
		}
		~DragDropSource() noexcept {
			if (m_active) {
				ImGui::EndDragDropSource();
			}
		}
		constexpr operator bool() const noexcept {
			return m_active;
		}
};

auto dragDropSource(auto const &cb, ImGuiDragDropFlags const flags = 0) noexcept {
	if constexpr(ox::is_same_v<decltype(cb()), ox::Error>) {
		if (ig::DragDropSource const tgt{flags}; tgt) [[unlikely]] {
			return cb();
		}
		return ox::Error{};
	} else {
		if (ig::DragDropSource const tgt{flags}; tgt) [[unlikely]] {
			cb();
		}
	}
}


class DragDropTarget {
	private:
		bool const m_active{};
	public:
		DragDropTarget() noexcept:
			m_active(ImGui::BeginDragDropTarget()) {
		}
		~DragDropTarget() noexcept {
			if (m_active) {
				ImGui::EndDragDropTarget();
			}
		}
		constexpr operator bool() const noexcept {
			return m_active;
		}
};

auto dragDropTarget(auto const &cb) noexcept {
	if constexpr(ox::is_same_v<decltype(cb()), ox::Error>) {
		if (ig::DragDropTarget const tgt; tgt) [[unlikely]] {
			return cb();
		}
		return ox::Error{};
	} else {
		if (ig::DragDropTarget const tgt; tgt) [[unlikely]] {
			cb();
		}
	}
}


class ChildStackItem {
	public:
		explicit ChildStackItem(ox::CStringViewCR id, ImVec2 sz = {}) noexcept;
		~ChildStackItem() noexcept;
};

class IDStackItem {
	public:
		explicit IDStackItem(int id) noexcept;
		explicit IDStackItem(ox::CString const id) noexcept;
		explicit IDStackItem(ox::CStringViewCR id) noexcept;
		~IDStackItem() noexcept;
};

class IndentStackItem {
	private:
		float m_indent{};
	public:
		explicit IndentStackItem(float id) noexcept;
		~IndentStackItem() noexcept;
};

void centerNextWindow() noexcept;

inline void Dummy(ImVec2 const &sz) noexcept {
	auto const scale = dpiScale();
	ImGui::Dummy({sz.x * scale, sz.y * scale});
}

bool PushButton(ox::CStringViewCR lbl, ImVec2 const &btnSz = BtnSz) noexcept;

template<typename Str>
struct TextInput {
	bool changed{};
	Str text;
	explicit constexpr operator ox::String() const noexcept { return ox::String{text}; }
	constexpr operator ox::CStringView() const noexcept { return text; }
	constexpr operator ox::StringView() const noexcept { return text; }
	constexpr operator bool() const noexcept { return changed; }
};

template<size_t MaxChars = 50>
TextInput<ox::IString<MaxChars>> InputText(
		ox::CStringViewCR label,
		ox::StringViewCR currentText,
		ImGuiInputTextFlags const flags = 0,
		ImGuiInputTextCallback const callback = nullptr,
		void *const userData = nullptr) noexcept {
	TextInput<ox::IString<MaxChars>> out = {.text = currentText};
	out.changed = ImGui::InputText(
			label.c_str(), out.text.data(), MaxChars + 1, flags, callback, userData);
	if (out.changed) {
		OX_ALLOW_UNSAFE_BUFFERS_BEGIN
		std::ignore = out.text.unsafeResize(ox::strlen(out.text.c_str()));
		OX_ALLOW_UNSAFE_BUFFERS_END
	}
	return out;
}

template<size_t MaxChars = 50>
TextInput<ox::IString<MaxChars>> InputTextWithHint(
		ox::CStringViewCR label,
		ox::CStringViewCR hint,
		ox::StringViewCR currentText,
		ImGuiInputTextFlags const flags = 0,
		ImGuiInputTextCallback const callback = nullptr,
		void *userData = nullptr) noexcept {
	TextInput<ox::IString<MaxChars>> out = {.text = currentText};
	out.changed = ImGui::InputTextWithHint(
			label.c_str(), hint.c_str(), out.text.data(), MaxChars + 1, flags, callback, userData);
	if (out.changed) {
		OX_ALLOW_UNSAFE_BUFFERS_BEGIN
		std::ignore = out.text.unsafeResize(ox::strlen(out.text.c_str()));
		OX_ALLOW_UNSAFE_BUFFERS_END
	}
	return out;
}

template<size_t StrCap>
bool InputText(
	ox::CStringViewCR label,
	ox::IString<StrCap> &text,
	ImGuiInputTextFlags const flags = 0,
	ImGuiInputTextCallback const callback = nullptr,
	void *userData = nullptr) noexcept {
	auto const out = ImGui::InputText(
		label.c_str(), text.data(), StrCap + 1, flags, callback, userData);
	if (out) {
		OX_ALLOW_UNSAFE_BUFFERS_BEGIN
		std::ignore = text.unsafeResize(ox::strlen(text.c_str()));
		OX_ALLOW_UNSAFE_BUFFERS_END
	}
	return out;
}

enum class PopupResponse {
	None,
	OK,
	Cancel,
};

PopupResponse PopupControlsOkCancel(
	float popupWidth,
	bool &popupOpen,
	ox::CStringViewCR ok = "OK",
	ox::CStringViewCR cancel = "Cancel");

PopupResponse PopupControlsOkCancel(
	bool &popupOpen,
	ox::CStringViewCR ok = "OK",
	ox::CStringViewCR cancel = "Cancel");

PopupResponse PopupControlsOk(
	bool &popupOpen,
	ox::CStringViewCR ok);

[[nodiscard]]
bool BeginPopup(ox::CStringViewCR popupName, bool &show, ImVec2 sz = {285, 0});

/**
 *
 * @param lbl
 * @param list
 * @param selectedIdx
 * @return true if new value selected, false otherwise
 */
bool ComboBox(
		ox::CStringView const &lbl,
		ox::SpanView<ox::CStringView> list,
		size_t &selectedIdx) noexcept;

/**
 *
 * @param lbl
 * @param list
 * @param selectedIdx
 * @return true if new value selected, false otherwise
 */
bool ComboBox(ox::CStringView const &lbl, ox::Span<ox::String const> list, size_t &selectedIdx) noexcept;

/**
 *
 * @param lbl
 * @param f callback function
 * @param selectedIdx
 * @return true if new value selected, false otherwise
 */
bool ComboBox(
		ox::CStringViewCR lbl,
		std::function<ox::CStringView(size_t)> const &f,
		size_t strCnt,
		size_t &selectedIdx) noexcept;

bool FileComboBox(
		ox::CStringViewCR lbl,
		Context &sctx,
		ox::StringViewCR fileExt,
		size_t &selectedIdx) noexcept;

bool ListBox(
		ox::CStringViewCR name,
		std::function<ox::CStringView(size_t)> const &f,
		size_t strCnt,
		size_t &selIdx,
		ImVec2 sz = {0, 0}) noexcept;

/**
 *
 * @param name
 * @param list
 * @param selIdx
 * @return true if new value selected, false otherwise
 */
bool ListBox(ox::CStringViewCR name, ox::SpanView<ox::String> const &list, size_t &selIdx) noexcept;

class FilePicker {
	private:
		bool m_show{};
		Context &m_sctx;
		ox::String const m_title;
		ox::String const m_fileExt;
		ImVec2 const m_size;
	public:
		ox::Signal<ox::Error(ox::StringView)> filePicked;

		FilePicker(
				studio::Context &sctx,
				ox::StringParam title,
				ox::StringParam fileExt,
				ImVec2 const &size = {}) noexcept;

		void draw() noexcept;

		void show() noexcept;

};

class Popup: public Widget {
	protected:
		enum class Stage {
			Closed,
			Opening,
			Open,
		};
		Stage m_stage = Stage::Closed;
		bool m_open{};
		ox::String m_title;

	public:
		ox::Signal<ox::Error(PopupResponse)> response;

		explicit Popup(ox::StringParam title) noexcept;

		void open() noexcept;

		void close() noexcept;

		[[nodiscard]]
		bool isOpen() const noexcept;

};

class QuestionPopup: public Popup {
	private:
		ox::String m_question;

	public:
		ox::Signal<ox::Error(PopupResponse)> response;

		QuestionPopup(ox::StringParam title, ox::StringParam question) noexcept;

		void setQuestion(ox::StringParam msg) noexcept;

		void draw(Context &ctx) noexcept override;

};

class MessagePopup: public Popup {
 	private:
		ox::String m_msg;

	public:
		ox::Signal<ox::Error(PopupResponse)> response;

		MessagePopup(ox::StringParam title, ox::StringParam msg) noexcept;

		void show(ox::StringParam msg) noexcept;

		void draw(Context &ctx) noexcept override;

};

[[nodiscard]]
bool mainWinHasFocus() noexcept;

}
