#include <hex/ui/imgui_imhex_extensions.h>

#include <imgui.h>
#include <imgui_internal.h>
#include <implot.h>
#include <implot_internal.h>
#include <cimgui.h>
#include <opengl_support.h>

#undef IMGUI_DEFINE_MATH_OPERATORS

#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>

#include <lunasvg.h>

#include <set>
#include <string>
#include <algorithm>
#include <numbers>

#include <hex/api/imhex_api/system.hpp>

#include <hex/api/task_manager.hpp>
#include <hex/api/theme_manager.hpp>
#include <hex/helpers/logger.hpp>

namespace ImGuiExt {

    using namespace ImGui;

    namespace {

        bool isOpenGLExtensionSupported(const char *name) {
            static std::set<std::string> extensions;

            if (extensions.empty()) {
                GLint extensionCount = 0;
                glGetIntegerv(GL_NUM_EXTENSIONS, &extensionCount);

                for (GLint i = 0; i < extensionCount; i++) {
                    std::string extension = reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, i));
                    extensions.emplace(std::move(extension));
                }
            }

            return extensions.contains(name);
        }

        bool isOpenGLVersionAtLeast(u8 major, u8 minor) {
            static GLint actualMajor = 0, actualMinor = 0;
            if (actualMajor == 0 || actualMinor == 0) {
                glGetIntegerv(GL_MAJOR_VERSION, &actualMajor);
                glGetIntegerv(GL_MINOR_VERSION, &actualMinor);
            }

            return actualMajor > major || (actualMajor == major && actualMinor >= minor);
        }

        constexpr auto getGLFilter(Texture::Filter filter) {
            switch (filter) {
                using enum Texture::Filter;
                case Nearest:
                    return GL_NEAREST;
                case Linear:
                    return GL_LINEAR;
            }

            return GL_NEAREST;
        }

        [[maybe_unused]] GLint getMaxSamples(GLenum target, GLenum format) {
            GLint maxSamples;

            glGetInternalformativ(target, format, GL_SAMPLES, 1, &maxSamples);
            return maxSamples;
        }

        GLuint createTextureFromRGBA8Array(const ImU8 *buffer, int width, int height, Texture::Filter filter) {
            GLuint texture;

            // Generate texture
            glGenTextures(1, &texture);
            glBindTexture(GL_TEXTURE_2D, texture);

            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, getGLFilter(filter));
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, getGLFilter(filter));

            #if defined(GL_UNPACK_ROW_LENGTH)
                glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
            #endif

            // Allocate storage for the texture
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);

            return texture;
        }

        GLuint createMultisampleTextureFromRGBA8Array(const ImU8 *buffer, int width, int height, Texture::Filter filter) {
            // Create a regular texture from the RGBA8 array
            GLuint texture = createTextureFromRGBA8Array(buffer, width, height, filter);

            if (filter == Texture::Filter::Nearest) {
                return texture;
            }

            if (!isOpenGLVersionAtLeast(3,2)) {
                return texture;
            }

            if (!isOpenGLExtensionSupported("GL_ARB_texture_multisample")) {
                return texture;
            }

            #if defined(GL_TEXTURE_2D_MULTISAMPLE)
                static const auto sampleCount = std::min(static_cast<GLint>(8), getMaxSamples(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8));

                // Generate renderbuffer
                GLuint renderbuffer;
                glGenRenderbuffers(1, &renderbuffer);
                glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
                glRenderbufferStorageMultisample(GL_RENDERBUFFER, sampleCount, GL_DEPTH24_STENCIL8, width, height);

                // Generate framebuffer
                GLuint framebuffer;
                glGenFramebuffers(1, &framebuffer);
                glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

                // Unbind framebuffer on exit
                ON_SCOPE_EXIT {
                    glBindFramebuffer(GL_FRAMEBUFFER, 0);
                };

                // Attach texture to color attachment 0
                glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, texture, 0);

                // Attach renderbuffer to depth-stencil attachment
                glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, renderbuffer);

                // Check framebuffer status
                if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
                    hex::log::error("Driver claims to support texture multisampling but it's not working");
                    return texture;
                }

            #endif

            return texture;
        }

    }
    
    Texture Texture::fromImage(const ImU8 *buffer, int size, Filter filter) {
        if (size == 0)
            return {};

        unsigned char *imageData = nullptr;

        Texture result;
        imageData = stbi_load_from_memory(buffer, size, &result.m_width, &result.m_height, nullptr, 4);
        if (imageData == nullptr)
            return {};

        GLuint texture = createMultisampleTextureFromRGBA8Array(imageData, result.m_width, result.m_height, filter);

        STBI_FREE(imageData);

        result.m_textureId = texture;

        return result;
    }

    Texture Texture::fromImage(std::span<const std::byte> buffer, Filter filter) {
        return Texture::fromImage(reinterpret_cast<const ImU8*>(buffer.data()), buffer.size(), filter);
    }


    Texture Texture::fromImage(const std::fs::path &path, Filter filter) {
        return Texture::fromImage(wolv::util::toUTF8String(path).c_str(), filter);
    }

    Texture Texture::fromImage(const char *path, Filter filter) {
        Texture result;
        unsigned char *imageData = stbi_load(path, &result.m_width, &result.m_height, nullptr, 4);
        if (imageData == nullptr)
            return {};

        GLuint texture = createMultisampleTextureFromRGBA8Array(imageData, result.m_width, result.m_height, filter);

        STBI_FREE(imageData);

        result.m_textureId = texture;

        return result;
    }

    Texture Texture::fromGLTexture(unsigned int glTexture, int width, int height) {
        Texture texture;
        texture.m_textureId = glTexture;
        texture.m_width = width;
        texture.m_height = height;

        return texture;
    }

    Texture Texture::fromBitmap(std::span<const std::byte> buffer, int width, int height, Filter filter) {
        return Texture::fromBitmap(reinterpret_cast<const ImU8*>(buffer.data()), buffer.size(), width, height, filter);
    }

    Texture Texture::fromBitmap(const ImU8 *buffer, int size, int width, int height, Filter filter) {
        if (width * height * 4 > size)
            return {};

        GLuint texture = createMultisampleTextureFromRGBA8Array(buffer, width, height, filter);

        Texture result;
        result.m_width = width;
        result.m_height = height;
        result.m_textureId = texture;

        return result;
    }

    Texture Texture::fromSVG(const char *path, int width, int height, Filter filter) {
        const auto scaleFactor = hex::ImHexApi::System::getBackingScaleFactor();

        auto document = lunasvg::Document::loadFromFile(path);
        auto bitmap = document->renderToBitmap(width * scaleFactor, height * scaleFactor);

        auto texture = createMultisampleTextureFromRGBA8Array(bitmap.data(), bitmap.width(), bitmap.height(), filter);

        Texture result;
        result.m_width = bitmap.width() / scaleFactor;
        result.m_height = bitmap.height() / scaleFactor;
        result.m_textureId = texture;

        return result;
    }

    Texture Texture::fromSVG(const std::fs::path &path, int width, int height, Filter filter) {
        return Texture::fromSVG(wolv::util::toUTF8String(path).c_str(), width, height, filter);
    }

    Texture Texture::fromSVG(std::span<const std::byte> buffer, int width, int height, Filter filter) {
        const auto scaleFactor = hex::ImHexApi::System::getBackingScaleFactor();

        auto document = lunasvg::Document::loadFromData(reinterpret_cast<const char*>(buffer.data()), buffer.size());
        auto bitmap = document->renderToBitmap(width * scaleFactor, height * scaleFactor);
        bitmap.convertToRGBA();

        auto texture = createMultisampleTextureFromRGBA8Array(bitmap.data(), bitmap.width(), bitmap.height(), filter);

        Texture result;
        result.m_width = bitmap.width() / scaleFactor;
        result.m_height = bitmap.height() / scaleFactor;
        result.m_textureId = texture;

        return result;
    }

    Texture::Texture(Texture&& other) noexcept {
        m_textureId = other.m_textureId;
        m_width = other.m_width;
        m_height = other.m_height;

        other.m_textureId = 0;
    }

    Texture& Texture::operator=(Texture&& other) noexcept {
        if (this == &other)
            return *this;

        this->reset();

        m_textureId = other.m_textureId;
        m_width = other.m_width;
        m_height = other.m_height;

        other.m_textureId = 0;
        
        return *this;
    }

    Texture::~Texture() {
        this->reset();
    }

    void Texture::reset() {
        if (m_textureId != 0) {
            #if !defined(OS_WEB)
                if (glDeleteTextures != nullptr)
                    glDeleteTextures(1, reinterpret_cast<GLuint*>(&m_textureId));
            #endif
            m_textureId = 0;
        }
    }

    std::vector<u8> Texture::toBytes() const noexcept {
        std::vector<u8> result(m_width * m_height * 4);

        #if defined(OS_WEB)
            GLuint fbo;
            glGenFramebuffers(1, &fbo);
            glBindFramebuffer(GL_FRAMEBUFFER, fbo);
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_textureId, 0);

            glReadPixels(0, 0, m_width, m_height, GL_RGBA, GL_UNSIGNED_BYTE, result.data());

            glBindFramebuffer(GL_FRAMEBUFFER, 0);
            glDeleteFramebuffers(1, &fbo);
        #else
            glBindTexture(GL_TEXTURE_2D, m_textureId);
            glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, result.data());
            glBindTexture(GL_TEXTURE_2D, 0);
        #endif

        return result;
    }

    float GetTextWrapPos() {
        return GImGui->CurrentWindow->DC.TextWrapPos;
    }

    int UpdateStringSizeCallback(ImGuiInputTextCallbackData *data) {
        if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) {
            auto &string = *static_cast<std::string *>(data->UserData);

            string.resize(data->BufTextLen);
            data->Buf = string.data();
        }

        return 0;
    }

    bool IconHyperlink(const char *icon, const char *label, const ImVec2 &size_arg, ImGuiButtonFlags flags) {
        ImGuiWindow *window = GetCurrentWindow();
        if (window->SkipItems)
            return false;

        ImGuiContext &g         = *GImGui;
        const ImGuiID id        = window->GetID(label);
        ImVec2 label_size       = CalcTextSize(icon, nullptr, false);
        label_size.x += CalcTextSize(" ", nullptr, false).x + CalcTextSize(label, nullptr, false).x;

        ImVec2 pos  = window->DC.CursorPos;
        ImVec2 size = CalcItemSize(size_arg, label_size.x, label_size.y);

        const ImRect bb(pos, pos + size);
        ItemAdd(bb, id);

        bool hovered, held;
        bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);

        // Render
        BeginGroup();
        TextDisabled("%s", icon);
        SameLine();

        const ImU32 col = hovered ? GetColorU32(ImGuiCol_ButtonHovered) : GetColorU32(ImGuiCol_ButtonActive);
        PushStyleColor(ImGuiCol_Text, ImU32(col));
        TextUnformatted(label);

        if (hovered)
            GetWindowDrawList()->AddLine(ImVec2(pos.x, pos.y + size.y), pos + size, ImU32(col));

        PopStyleColor();

        EndGroup();

        IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
        return pressed;
    }

    bool Hyperlink(const char *label, const ImVec2 &size_arg, ImGuiButtonFlags flags) {
        ImGuiWindow *window = GetCurrentWindow();

        ImGuiContext &g         = *GImGui;
        const ImGuiID id        = window->GetID(label);
        const ImVec2 label_size = CalcTextSize(label, nullptr, true);

        ImVec2 pos  = window->DC.CursorPos;
        ImVec2 size = CalcItemSize(size_arg, label_size.x, label_size.y);

        const ImRect bb(pos, pos + size);
        ItemAdd(bb, id);

        bool hovered, held;
        bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);

        // Render
        const ImU32 col = hovered ? GetColorU32(ImGuiCol_ButtonHovered) : GetColorU32(ImGuiCol_ButtonActive);
        PushStyleColor(ImGuiCol_Text, ImU32(col));
        TextEx(label, nullptr, ImGuiTextFlags_NoWidthForLargeClippedText);    // Skip formatting

        if (hovered)
            GetWindowDrawList()->AddLine(ImVec2(pos.x, pos.y + size.y), pos + size, ImU32(col));

        PopStyleColor();

        IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
        return pressed;
    }

    bool BulletHyperlink(const char *label, const ImVec2 &size_arg, ImGuiButtonFlags flags) {
        ImGuiWindow *window = GetCurrentWindow();
        if (window->SkipItems)
            return false;

        ImGuiContext &g         = *GImGui;
        const ImGuiStyle &style = g.Style;
        const ImGuiID id        = window->GetID(label);
        const ImVec2 label_size = CalcTextSize(label, nullptr, true);

        ImVec2 pos  = window->DC.CursorPos;
        ImVec2 size = CalcItemSize(size_arg, label_size.x, label_size.y) + ImVec2(g.FontSize + style.FramePadding.x * 2, 0.0F);

        const ImRect bb(pos, pos + size);
        ItemSize(size, 0);
        if (!ItemAdd(bb, id))
            return false;

        bool hovered, held;
        bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);

        // Render
        const ImU32 col = hovered ? GetColorU32(ImGuiCol_ButtonHovered) : GetColorU32(ImGuiCol_ButtonActive);
        PushStyleColor(ImGuiCol_Text, ImU32(col));
        RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x, g.FontSize * 0.5F), col);
        RenderText(bb.Min + ImVec2(g.FontSize * 0.5 + style.FramePadding.x, 0.0F), label, nullptr, false);
        GetWindowDrawList()->AddLine(bb.Min + ImVec2(g.FontSize * 0.5 + style.FramePadding.x, size.y), pos + size - ImVec2(g.FontSize * 0.5 + style.FramePadding.x, 0), ImU32(col));
        PopStyleColor();

        IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
        return pressed;
    }

    bool DescriptionButton(const char *label, const char *description, const char *icon, const ImVec2 &size_arg, ImGuiButtonFlags flags) {
        ImGuiWindow *window = GetCurrentWindow();
        if (window->SkipItems)
            return false;

        ImGuiContext &g         = *GImGui;
        const ImGuiStyle &style = g.Style;
        const ImGuiID id        = window->GetID(label);
        const ImVec2 label_size = CalcTextSize(label, nullptr, true);
        const ImVec2 text_size  = label_size + CalcTextSize(description, nullptr, true);

        ImVec2 pos = window->DC.CursorPos;
        if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrLineTextBaseOffset)    // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag)
            pos.y += window->DC.CurrLineTextBaseOffset - style.FramePadding.y;
        ImVec2 size = CalcItemSize(size_arg, text_size.x + style.FramePadding.x * 4.0F, text_size.y + style.FramePadding.y * 4.0F);

        const ImRect bb(pos, pos + size);
        ItemSize(size, style.FramePadding.y);
        if (!ItemAdd(bb, id))
            return false;

        bool hovered, held;
        bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);

        PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0, 0.5));
        PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1);

        // Render
        const ImU32 col = GetCustomColorU32((held && hovered) ? ImGuiCustomCol_DescButtonActive : hovered ? ImGuiCustomCol_DescButtonHovered
                                                                                                          : ImGuiCustomCol_DescButton);
        float icon_padding = style.FramePadding.x * 2;
        float label_padding = icon_padding + style.FramePadding.x * 5;
        float description_padding = label_padding + style.FramePadding.x * 2;

        RenderNavCursor(bb, id);
        RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
        PushFont(GImGui->Font, GImGui->FontSizeBase * 1.25F);
        const ImVec2 icon_size  = CalcTextSize(icon, nullptr, true);
        RenderTextClipped(bb.Min + ImVec2(icon_padding, (size.y - icon_size.y) / 2), bb.Max - style.FramePadding, icon, nullptr, nullptr);
        PopFont();
        PushStyleColor(ImGuiCol_Text, GetColorU32(ImGuiCol_ButtonActive));
        RenderTextClipped(bb.Min + style.FramePadding * 2 + ImVec2(label_padding, 0), bb.Max - style.FramePadding, label, nullptr, nullptr);
        PopStyleColor();
        PushStyleColor(ImGuiCol_Text, GetColorU32(ImGuiCol_Text));
        auto clipBb = bb;
        clipBb.Max.x -= style.FramePadding.x;
        RenderTextClipped(bb.Min + style.FramePadding * 2 + ImVec2(description_padding, label_size.y), bb.Max - style.FramePadding, description, nullptr, &text_size, style.ButtonTextAlign, &clipBb);
        PopStyleColor();

        PopStyleVar(2);

        // Automatically close popups
        // if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
        //    CloseCurrentPopup();

        IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
        return pressed;
    }

    bool DescriptionButtonProgress(const char *label, const char *description, const char *icon, float fraction, const ImVec2 &size_arg, ImGuiButtonFlags flags) {
        ImGuiWindow *window = GetCurrentWindow();
        if (window->SkipItems)
            return false;

        ImGuiContext &g         = *GImGui;
        const ImGuiStyle &style = g.Style;
        const ImGuiID id        = window->GetID(label);
        const ImVec2 text_size  = CalcTextSize(fmt::format("{}\n{}", label, description).c_str(), nullptr, true);
        const ImVec2 label_size = CalcTextSize(label, nullptr, true);

        ImVec2 pos = window->DC.CursorPos;
        if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrLineTextBaseOffset)    // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag)
            pos.y += window->DC.CurrLineTextBaseOffset - style.FramePadding.y;
        ImVec2 size = CalcItemSize(size_arg, text_size.x + style.FramePadding.x * 4.0F, text_size.y + style.FramePadding.y * 6.0F);

        const ImRect bb(pos, pos + size);
        ItemSize(size, style.FramePadding.y);
        if (!ItemAdd(bb, id))
            return false;

        bool hovered, held;
        bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);

        PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0, 0.5));
        PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1);

        // Render
        const ImU32 col = GetCustomColorU32((held && hovered) ? ImGuiCustomCol_DescButtonActive : hovered ? ImGuiCustomCol_DescButtonHovered
                                                                                                          : ImGuiCustomCol_DescButton);
        float icon_padding = style.FramePadding.x * 2;
        float label_padding = icon_padding + style.FramePadding.x * 5;
        float description_padding = label_padding + style.FramePadding.x * 2;

        RenderNavCursor(bb, id);
        RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
        PushFont(GImGui->Font, GImGui->FontSizeBase * 1.25F);
        const ImVec2 icon_size  = CalcTextSize(icon, nullptr, true);
        RenderTextClipped(bb.Min + ImVec2(icon_padding, (size.y - icon_size.y) / 2), bb.Max - style.FramePadding, icon, nullptr, nullptr);
        PopFont();
        PushStyleColor(ImGuiCol_Text, GetColorU32(ImGuiCol_ButtonActive));
        RenderTextClipped(bb.Min + style.FramePadding * 2 + ImVec2(label_padding, 0), bb.Max - style.FramePadding, label, nullptr, nullptr);
        PopStyleColor();
        PushStyleColor(ImGuiCol_Text, GetColorU32(ImGuiCol_Text));
        auto clipBb = bb;
        clipBb.Max.x -= style.FramePadding.x;
        RenderTextClipped(bb.Min + style.FramePadding * 2 + ImVec2(description_padding, label_size.y), bb.Max - style.FramePadding, description, nullptr, &text_size, style.ButtonTextAlign, &clipBb);
        PopStyleColor();

        RenderFrame(ImVec2(bb.Min.x, bb.Max.y - 5 * hex::ImHexApi::System::getGlobalScale()), bb.Max, GetColorU32(ImGuiCol_ScrollbarBg), false, style.FrameRounding);
        RenderFrame(ImVec2(bb.Min.x, bb.Max.y - 5 * hex::ImHexApi::System::getGlobalScale()), ImVec2(bb.Min.x + fraction * bb.GetSize().x, bb.Max.y), GetColorU32(ImGuiCol_Button), false, style.FrameRounding);
        RenderFrame(bb.Min, bb.Max, 0x00, true, style.FrameRounding);

        PopStyleVar(2);

        // Automatically close popups
        // if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
        //    CloseCurrentPopup();

        IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
        return pressed;
    }

    void HelpHover(const char *text, const char *icon, ImU32 iconColor) {
        PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
        PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0, 0, 0, 0));
        PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0, 0, 0, 0));
        PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
        PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0F);

        PushStyleColor(ImGuiCol_Text, iconColor);
        ImGui::PushID(text);
        Button(icon, ImVec2(0, ImGui::GetTextLineHeightWithSpacing()));
        ImGui::PopID();
        PopStyleColor();

        if (IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
            SetNextWindowSizeConstraints(ImVec2(GetTextLineHeight() * 25, 0), ImVec2(GetTextLineHeight() * 35, FLT_MAX));
            BeginTooltip();
            TextFormattedWrapped("{}", text);
            EndTooltip();
        }

        PopStyleVar(2);
        PopStyleColor(3);
    }

    void UnderlinedText(const char *label, ImColor color, const ImVec2 &size_arg) {
        ImGuiWindow *window = GetCurrentWindow();

        const ImVec2 label_size = CalcTextSize(label, nullptr, true);

        ImVec2 pos  = window->DC.CursorPos;
        ImVec2 size = CalcItemSize(size_arg, label_size.x, label_size.y);

        PushStyleColor(ImGuiCol_Text, ImU32(color));
        TextEx(label, nullptr, ImGuiTextFlags_NoWidthForLargeClippedText);    // Skip formatting
        GetWindowDrawList()->AddLine(ImVec2(pos.x, pos.y + size.y), pos + size, ImU32(color));
        PopStyleColor();
    }

    void UnderwavedText(const char *label, ImColor textColor, ImColor lineColor, const ImVec2 &size_arg) {
        ImGuiWindow *window = GetCurrentWindow();
        std::string labelStr(label);
        for (char letter : labelStr) {
            std::string letterStr(1, letter);
            const ImVec2 label_size = CalcTextSize(letterStr.c_str(), nullptr, true);
            ImVec2 size = CalcItemSize(size_arg, label_size.x, label_size.y);
            ImVec2 pos = window->DC.CursorPos;
            float lineWidth = size.x / 3.0f;
            float halfLineW = lineWidth / 2.0f;
            float lineY = pos.y + size.y;
            ImVec2 initial = ImVec2(pos.x, lineY);
            ImVec2 pos1 = ImVec2(pos.x + lineWidth, lineY - 2.0f);
            ImVec2 pos2 = ImVec2(pos.x + lineWidth + halfLineW, lineY);
            ImVec2 pos3 = ImVec2(pos.x + lineWidth * 2 + halfLineW, lineY - 2.0f);
            ImVec2 pos4 = ImVec2(pos.x + lineWidth * 3, lineY - 1.0f);

            PushStyleColor(ImGuiCol_Text, ImU32(textColor));
            TextEx(letterStr.c_str(), nullptr, ImGuiTextFlags_NoWidthForLargeClippedText);    // Skip formatting
            GetWindowDrawList()->AddLine(initial, pos1, ImU32(lineColor),0.4f);
            GetWindowDrawList()->AddLine(pos1, pos2, ImU32(lineColor),0.3f);
            GetWindowDrawList()->AddLine(pos2, pos3, ImU32(lineColor),0.4f);
            GetWindowDrawList()->AddLine(pos3, pos4, ImU32(lineColor),0.3f);
            PopStyleColor();
            window->DC.CursorPos = ImVec2(pos.x + size.x, pos.y);
        }
    }

    void TextSpinner(const char *label) {
        Text("[%c] %s", "|/-\\"[ImU32(GetTime() * 20) % 4], label);
    }

    void Header(const char *label, bool firstEntry) {
        if (!firstEntry)
            NewLine();
        SeparatorText(label);
    }

    void HeaderColored(const char *label, ImColor color, bool firstEntry) {
        if (!firstEntry)
            NewLine();
        TextFormattedColored(color, "{}", label);
        Separator();
    }

    bool InfoTooltip(const char *text, bool isSeparator) {
        static double lastMoveTime;
        static ImGuiID lastHoveredID;

        double currTime   = GetTime();
        ImGuiID hoveredID = GetHoveredID();

        bool result = false;
        if (IsItemHovered(ImGuiHoveredFlags_DelayNormal) && (currTime - lastMoveTime) >= 0.5 && hoveredID == lastHoveredID) {
            if (!std::string_view(text).empty()) {
                const auto textWidth = CalcTextSize(text).x;

                const auto maxWidth = 300 * hex::ImHexApi::System::getGlobalScale();
                const bool wrapping = textWidth > maxWidth;

                if (wrapping)
                    ImGui::SetNextWindowSizeConstraints(ImVec2(maxWidth, 0), ImVec2(maxWidth, FLT_MAX));
                else
                    ImGui::SetNextWindowSize(ImVec2(textWidth + GetStyle().WindowPadding.x * 2, 0));

                if (BeginTooltip()) {
                    if (isSeparator)
                        SeparatorText(text);
                    else {
                        if (wrapping)
                            TextFormattedWrapped("{}", text);
                        else
                            TextFormatted("{}", text);
                    }

                    EndTooltip();
                }
            }

            result = true;
        }

        if (hoveredID != lastHoveredID) {
            lastMoveTime = currTime;
        }
        lastHoveredID = hoveredID;

        return result;
    }

    ImU32 GetCustomColorU32(ImGuiCustomCol idx, float alpha_mul) {
        auto &customData = *static_cast<ImHexCustomData *>(GImGui->IO.UserData);
        ImVec4 c         = customData.Colors[idx];
        c.w *= GImGui->Style.Alpha * alpha_mul;
        return ColorConvertFloat4ToU32(c);
    }

    ImVec4 GetCustomColorVec4(ImGuiCustomCol idx, float alpha_mul) {
        auto &customData = *static_cast<ImHexCustomData *>(GImGui->IO.UserData);
        ImVec4 c         = customData.Colors[idx];
        c.w *= GImGui->Style.Alpha * alpha_mul;
        return c;
    }

    float GetCustomStyleFloat(ImGuiCustomStyle idx) {
        auto &customData = *static_cast<ImHexCustomData *>(GImGui->IO.UserData);

        switch (idx) {
            case ImGuiCustomStyle_WindowBlur:
                return customData.styles.WindowBlur;
            default:
                return 0.0F;
        }
    }

    ImVec2 GetCustomStyleVec2(ImGuiCustomStyle idx) {
        std::ignore = idx;
        return {};
    }

    void StyleCustomColorsDark() {
        auto &colors = static_cast<ImHexCustomData *>(GImGui->IO.UserData)->Colors;

        colors[ImGuiCustomCol_DescButton]        = ImColor(20, 20, 20);
        colors[ImGuiCustomCol_DescButtonHovered] = ImColor(40, 40, 40);
        colors[ImGuiCustomCol_DescButtonActive]  = ImColor(60, 60, 60);

        colors[ImGuiCustomCol_ToolbarGray]   = ImColor(230, 230, 230);
        colors[ImGuiCustomCol_ToolbarRed]    = ImColor(231, 76, 60);
        colors[ImGuiCustomCol_ToolbarYellow] = ImColor(241, 196, 15);
        colors[ImGuiCustomCol_ToolbarGreen]  = ImColor(56, 139, 66);
        colors[ImGuiCustomCol_ToolbarBlue]   = ImColor(6, 83, 155);
        colors[ImGuiCustomCol_ToolbarPurple] = ImColor(103, 42, 120);
        colors[ImGuiCustomCol_ToolbarBrown]  = ImColor(219, 179, 119);

        colors[ImGuiCustomCol_Highlight] = ImColor(77, 198, 155);

        colors[ImGuiCustomCol_IEEEToolSign] = ImColor(93, 93, 127);
        colors[ImGuiCustomCol_IEEEToolExp]  = ImColor(93, 127,  93);
        colors[ImGuiCustomCol_IEEEToolMantissa]  = ImColor(127, 93,  93);
    }

    void StyleCustomColorsLight() {
        auto &colors = static_cast<ImHexCustomData *>(GImGui->IO.UserData)->Colors;

        colors[ImGuiCustomCol_DescButton]        = ImColor(230, 230, 230);
        colors[ImGuiCustomCol_DescButtonHovered] = ImColor(210, 210, 210);
        colors[ImGuiCustomCol_DescButtonActive]  = ImColor(190, 190, 190);

        colors[ImGuiCustomCol_ToolbarGray]   = ImColor(25, 25, 25);
        colors[ImGuiCustomCol_ToolbarRed]    = ImColor(231, 76, 60);
        colors[ImGuiCustomCol_ToolbarYellow] = ImColor(241, 196, 15);
        colors[ImGuiCustomCol_ToolbarGreen]  = ImColor(56, 139, 66);
        colors[ImGuiCustomCol_ToolbarBlue]   = ImColor(6, 83, 155);
        colors[ImGuiCustomCol_ToolbarPurple] = ImColor(103, 42, 120);
        colors[ImGuiCustomCol_ToolbarBrown]  = ImColor(219, 179, 119);

        colors[ImGuiCustomCol_Highlight] = ImColor(41, 151, 112);

        colors[ImGuiCustomCol_IEEEToolSign] = ImColor(187, 187, 255);
        colors[ImGuiCustomCol_IEEEToolExp]  = ImColor(187, 255,  187);
        colors[ImGuiCustomCol_IEEEToolMantissa]  = ImColor(255, 187,187);
    }

    void StyleCustomColorsClassic() {
        auto &colors = static_cast<ImHexCustomData *>(GImGui->IO.UserData)->Colors;

        colors[ImGuiCustomCol_DescButton]        = ImColor(40, 40, 80);
        colors[ImGuiCustomCol_DescButtonHovered] = ImColor(60, 60, 100);
        colors[ImGuiCustomCol_DescButtonActive]  = ImColor(80, 80, 120);

        colors[ImGuiCustomCol_ToolbarGray]   = ImColor(230, 230, 230);
        colors[ImGuiCustomCol_ToolbarRed]    = ImColor(231, 76, 60);
        colors[ImGuiCustomCol_ToolbarYellow] = ImColor(241, 196, 15);
        colors[ImGuiCustomCol_ToolbarGreen]  = ImColor(56, 139, 66);
        colors[ImGuiCustomCol_ToolbarBlue]   = ImColor(6, 83, 155);
        colors[ImGuiCustomCol_ToolbarPurple] = ImColor(103, 42, 120);
        colors[ImGuiCustomCol_ToolbarBrown]  = ImColor(219, 179, 119);

        colors[ImGuiCustomCol_Highlight] = ImColor(77, 198, 155);
        colors[ImGuiCustomCol_IEEEToolSign] = ImColor(93, 93, 127);
        colors[ImGuiCustomCol_IEEEToolExp]  = ImColor(93, 127,  93);
        colors[ImGuiCustomCol_IEEEToolMantissa]  = ImColor(127, 93,  93);
    }

    void OpenPopupInWindow(const char *window_name, const char *popup_name) {
        if (Begin(window_name)) {
            OpenPopup(popup_name);
        }
        End();
    }

    void DisableWindowResize(ImGuiDir dir) {
        const auto window = GetCurrentWindow();
        const auto borderId = GetWindowResizeBorderID(window, dir);
        if (borderId == GetHoveredID()) {
            GImGui->ActiveIdMouseButton = 0;
            SetHoveredID(0);
        }
        if (borderId == GetActiveID())
            SetActiveID(0, window);
    }


    bool TitleBarButton(const char *label, ImVec2 size_arg) {
        ImGuiWindow *window = GetCurrentWindow();
        if (window->SkipItems)
            return false;

        ImGuiContext &g         = *GImGui;
        const ImGuiStyle &style = g.Style;
        const ImGuiID id        = window->GetID(label);
        const ImVec2 label_size = CalcTextSize(label, nullptr, true);

        ImVec2 pos = window->DC.CursorPos;

        ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0F, label_size.y + style.FramePadding.y * 2.0F);

        const ImRect bb(pos, pos + size);
        ItemSize(size, style.FramePadding.y);
        if (!ItemAdd(bb, id))
            return false;

        bool hovered, held;
        bool pressed = ButtonBehavior(bb, id, &hovered, &held);

        // Render
        const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered
                                                                                          : ImGuiCol_Button);
        RenderNavCursor(bb, id);
        RenderFrame(bb.Min, bb.Max, col, false, style.FrameRounding);
        RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, nullptr, &label_size, style.ButtonTextAlign, &bb);

        // Automatically close popups
        // if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
        //    CloseCurrentPopup();

        IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
        return pressed;
    }

    bool ToolBarButton(const char *symbol, ImVec4 color) {
        ImGuiWindow *window = GetCurrentWindow();
        if (window->SkipItems)
            return false;

        color.w = 1.0F;

        ImGuiContext &g         = *GImGui;
        const ImGuiStyle &style = g.Style;
        const ImGuiID id        = window->GetID(symbol);
        const ImVec2 label_size = CalcTextSize(symbol, nullptr, true);

        ImVec2 pos = window->DC.CursorPos;

        ImVec2 size = CalcItemSize(ImVec2(1, 1) * GetCurrentWindow()->MenuBarHeight, label_size.x + style.FramePadding.x * 2.0F, label_size.y + style.FramePadding.y * 2.0F);

        ImVec2 padding = (size - label_size) / 2;

        const ImRect bb(pos, pos + size);
        ItemSize(size, style.FramePadding.y);
        if (!ItemAdd(bb, id))
            return false;

        bool hovered, held;
        bool pressed = ButtonBehavior(bb, id, &hovered, &held);

        PushStyleColor(ImGuiCol_Text, color);

        // Render
        ImU32 col = 0x00;
        if (held)
            col = GetColorU32(ImGuiCol_ScrollbarGrabActive);
        else if (hovered)
            col = GetColorU32(ImGuiCol_ScrollbarGrabHovered);
        RenderNavCursor(bb, id);
        RenderFrame(bb.Min, bb.Max, col, false, style.FrameRounding);
        RenderTextClipped(bb.Min + padding, bb.Max - padding, symbol, nullptr, &size, style.ButtonTextAlign, &bb);

        PopStyleColor();

        // Automatically close popups
        // if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
        //    CloseCurrentPopup();

        IMGUI_TEST_ENGINE_ITEM_INFO(id, symbol, g.LastItemData.StatusFlags);
        return pressed;
    }

    bool IconButton(const char *symbol, ImVec4 color, ImVec2 size_arg, ImVec2 iconOffset) {
        ImGuiWindow *window = GetCurrentWindow();
        if (window->SkipItems)
            return false;

        color.w = 1.0F;

        ImGuiContext &g         = *GImGui;
        const ImGuiStyle &style = g.Style;
        const ImGuiID id        = window->GetID(symbol);
        const ImVec2 label_size = CalcTextSize(symbol, nullptr, true);

        ImVec2 pos = window->DC.CursorPos;

        ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0F, label_size.y + style.FramePadding.y * 2.0F);

        const ImRect bb(pos, pos + size);
        ItemSize(size, style.FramePadding.y);
        if (!ItemAdd(bb, id))
            return false;

        bool hovered, held;
        bool pressed = ButtonBehavior(bb, id, &hovered, &held);

        PushStyleColor(ImGuiCol_Text, color);

        // Render
        const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered
                                                                                          : ImGuiCol_Button);
        RenderNavCursor(bb, id);
        RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
        RenderTextClipped(bb.Min + style.FramePadding + iconOffset, bb.Max - style.FramePadding, symbol, nullptr, &label_size, style.ButtonTextAlign, &bb);

        PopStyleColor();

        // Automatically close popups
        // if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
        //    CloseCurrentPopup();

        IMGUI_TEST_ENGINE_ITEM_INFO(id, symbol, g.LastItemData.StatusFlags);
        return pressed;
    }

    bool InputPrefix(const char* label, const char *prefix, std::string &buffer, ImGuiInputTextFlags flags) {
        auto window             = GetCurrentWindow();
        const ImGuiStyle &style = GImGui->Style;


        const ImVec2 label_size = CalcTextSize(label, nullptr, true);
        const ImVec2 frame_size = CalcItemSize(ImVec2(0, 0), CalcTextSize(prefix).x, label_size.y + style.FramePadding.y * 2.0F);
        const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(CalcItemWidth(), frame_size.y));

        SetCursorPosX(GetCursorPosX() + frame_size.x);

        RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);

        PushStyleVar(ImGuiStyleVar_Alpha, 0.6F);
        RenderText(ImVec2(frame_bb.Min.x + style.FramePadding.x, frame_bb.Min.y + style.FramePadding.y), prefix);
        PopStyleVar();

        bool value_changed = false;
        PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0);
        PushStyleColor(ImGuiCol_FrameBg, 0x00000000);
        PushStyleColor(ImGuiCol_FrameBgHovered, 0x00000000);
        PushStyleColor(ImGuiCol_FrameBgActive, 0x00000000);
        value_changed = ImGui::InputText(label, buffer, flags);
        PopStyleColor(3);
        PopStyleVar();

        if (value_changed)
            MarkItemEdited(GImGui->LastItemData.ID);

        return value_changed;
    }

    bool InputIntegerPrefix(const char *label, const char *prefix, void *value, ImGuiDataType type, const char *format, ImGuiInputTextFlags flags) {
        auto window             = GetCurrentWindow();
        const ImGuiStyle &style = GImGui->Style;


        const ImVec2 label_size = CalcTextSize(label, nullptr, true);
        const ImVec2 frame_size = CalcItemSize(ImVec2(0, 0), CalcTextSize(prefix).x, label_size.y + style.FramePadding.y * 2.0F);
        const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(CalcItemWidth(), frame_size.y));

        SetCursorPosX(GetCursorPosX() + frame_size.x);

        char buf[64];
        DataTypeFormatString(buf, IM_ARRAYSIZE(buf), type, value, format);

        RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);

        PushStyleVar(ImGuiStyleVar_Alpha, 0.6F);
        RenderText(ImVec2(frame_bb.Min.x + style.FramePadding.x, frame_bb.Min.y + style.FramePadding.y), prefix);
        PopStyleVar();

        bool value_changed = false;
        PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0);
        PushStyleColor(ImGuiCol_FrameBg, 0x00000000);
        PushStyleColor(ImGuiCol_FrameBgHovered, 0x00000000);
        PushStyleColor(ImGuiCol_FrameBgActive, 0x00000000);
        if (InputTextEx(label, nullptr, buf, IM_ARRAYSIZE(buf), ImVec2(CalcItemWidth() - frame_size.x, label_size.y + style.FramePadding.y * 2.0F), flags))
            value_changed = DataTypeApplyFromText(buf, type, value, format);
        PopStyleColor(3);
        PopStyleVar();

        if (value_changed)
            MarkItemEdited(GImGui->LastItemData.ID);

        return value_changed;
    }

    bool InputHexadecimal(const char *label, u32 *value, ImGuiInputTextFlags flags) {
        return InputIntegerPrefix(label, "0x", value, ImGuiDataType_U32, "%X", flags | ImGuiInputTextFlags_CharsHexadecimal);
    }

    bool InputHexadecimal(const char *label, u64 *value, ImGuiInputTextFlags flags) {
        return InputIntegerPrefix(label, "0x", value, ImGuiDataType_U64, "%llX", flags | ImGuiInputTextFlags_CharsHexadecimal);
    }

    bool SliderBytes(const char *label, u64 *value, u64 min, u64 max, u64 stepSize, ImGuiSliderFlags flags) {
        std::string format;

        if (*value < 1024) {
            format = fmt::format("{} Bytes", *value);
        } else if (*value < 1024 * 1024) {
            format = fmt::format("{:.2f} KB", *value / 1024.0);
        } else if (*value < 1024 * 1024 * 1024) {
            format = fmt::format("{:.2f} MB", *value / (1024.0 * 1024.0));
        } else {
            format = fmt::format("{:.2f} GB", *value / (1024.0 * 1024.0 * 1024.0));
        }

        *value /= stepSize;
        min /= stepSize;
        max /= stepSize;

        auto result = ImGui::SliderScalar(label, ImGuiDataType_U64, value, &min, &max, format.c_str(), flags | ImGuiSliderFlags_Logarithmic);

        *value *= stepSize;

        return result;
    }

    void ProgressBar(float fraction, ImVec2 size_value, float yOffset) {
        ImGuiWindow *window = GetCurrentWindow();
        if (window->SkipItems)
            return;

        ImGuiContext &g         = *GImGui;
        const ImGuiStyle &style = g.Style;

        ImVec2 pos  = window->DC.CursorPos + ImVec2(0, yOffset);
        ImVec2 size = CalcItemSize(size_value, ImGui::GetContentRegionAvail().x, g.FontSize + style.FramePadding.y * 2.0F);
        ImRect bb(pos, pos + size);
        ItemSize(size, 0);
        if (!ItemAdd(bb, 0))
            return;

        // Render
        bool no_progress = fraction < 0;
        fraction = ImSaturate(fraction);
        RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
        bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize));

        if (no_progress) {
            auto time = (fmod(ImGui::GetTime() * 2, 1.8) - 0.4);
            RenderRectFilledInRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), ImSaturate(time), ImSaturate(time + 0.2), style.FrameRounding);
        } else {
            RenderRectFilledInRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0F, fraction, style.FrameRounding);
        }
    }

    void TextUnformattedCentered(const char *text) {
        auto availableSpace = ImGui::GetContentRegionAvail();

        std::string drawString;
        auto textEnd = text + strlen(text);
        for (auto wrapPos = text; wrapPos != textEnd;) {
            wrapPos = ImGui::GetFont()->CalcWordWrapPosition(GetFontSize(), wrapPos, textEnd, availableSpace.x * 0.8F);
            if (text == wrapPos)
                break;

            drawString += std::string(text, wrapPos) + "\n";
            text = wrapPos;
        }

        drawString.pop_back();

        auto textSize = ImGui::CalcTextSize(drawString.c_str());

        ImPlot::AddTextCentered(ImGui::GetWindowDrawList(), ImGui::GetCursorScreenPos() + availableSpace / 2 - ImVec2(0, textSize.y / 2), ImGui::GetColorU32(ImGuiCol_Text), drawString.c_str());
    }
    
    bool InputTextIcon(const char *label, const char *icon, std::string &buffer, ImGuiInputTextFlags flags) {
        return InputTextIconHint(label, icon, nullptr, buffer, flags);
    }

    bool InputTextIconHint(const char* label, const char *icon, const char *hint, std::string &buffer, ImGuiInputTextFlags flags) {
        auto window             = GetCurrentWindow();
        const ImGuiID id        = window->GetID(label);
        const ImGuiStyle &style = GImGui->Style;

        const ImVec2 label_size = CalcTextSize(label, nullptr, true);
        const ImVec2 icon_frame_size = CalcTextSize(icon) + style.FramePadding * 2.0F;
        const ImVec2 frame_size = CalcItemSize(ImVec2(0, 0), icon_frame_size.x, label_size.y + style.FramePadding.y * 2.0F);
        const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);

        SetCursorPosX(GetCursorPosX() + frame_size.x);

        float width_adjustment = window->DC.ItemWidth < 0 ? 0 : icon_frame_size.x;

        bool value_changed = InputTextEx(label, hint, buffer.data(), buffer.size() + 1, ImVec2(CalcItemWidth() - width_adjustment, label_size.y + style.FramePadding.y * 2.0F), ImGuiInputTextFlags_CallbackResize | flags, UpdateStringSizeCallback, &buffer);

        if (value_changed)
            MarkItemEdited(GImGui->LastItemData.ID);

        RenderNavCursor(frame_bb, id);
        RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);

        RenderFrame(frame_bb.Min, frame_bb.Min + icon_frame_size, GetColorU32(ImGuiCol_TableBorderStrong), true, style.FrameRounding);
        RenderText(ImVec2(frame_bb.Min.x + style.FramePadding.x, frame_bb.Min.y + style.FramePadding.y), icon);

        return value_changed;
    }

    bool InputScalarCallback(const char* label, ImGuiDataType data_type, void* p_data, const char* format, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) {
        ImGuiWindow* window = GetCurrentWindow();
        if (window->SkipItems)
            return false;

        ImGuiContext& g = *GImGui;

        if (format == nullptr)
            format = DataTypeGetInfo(data_type)->PrintFmt;

        char buf[64];
        DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);

        bool value_changed = false;
        if ((flags & (ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsScientific)) == 0)
            flags |= ImGuiInputTextFlags_CharsDecimal;
        flags |= ImGuiInputTextFlags_AutoSelectAll;

        if (ImGui::InputText(label, buf, IM_ARRAYSIZE(buf), flags, callback, user_data))
            value_changed = DataTypeApplyFromText(buf, data_type, p_data, format);

        if (value_changed)
            MarkItemEdited(g.LastItemData.ID);

        return value_changed;
    }

    void HideTooltip() {
        char window_name[16];
        ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", GImGui->TooltipOverrideCount);
        if (ImGuiWindow* window = FindWindowByName(window_name); window != nullptr) {
            if (window->Active)
                window->Hidden = true;
        }
    }


    bool BitCheckbox(const char* label, bool* v) {
        ImGuiWindow* window = GetCurrentWindow();
        if (window->SkipItems)
            return false;

        ImGuiContext& g = *GImGui;
        const ImGuiStyle& style = g.Style;
        const ImGuiID id = window->GetID(label);
        const ImVec2 label_size = CalcTextSize(label, nullptr, true);

        const ImVec2 size = ImVec2(CalcTextSize("0").x + style.FramePadding.x * 2, GetFrameHeight());
        const ImVec2 pos = window->DC.CursorPos;
        const ImRect total_bb(pos, pos + size);
        ItemSize(total_bb, style.FramePadding.y);
        if (!ItemAdd(total_bb, id))
        {
            IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
            return false;
        }

        bool hovered, held;
        bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
        if (pressed)
        {
            *v = !(*v);
            MarkItemEdited(id);
        }

        const ImRect check_bb(pos, pos + size);
        RenderNavCursor(total_bb, id);
        RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding);

        RenderText(check_bb.Min + style.FramePadding, *v ? "1" : "0");

        ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y);
        if (label_size.x > 0.0F)
            RenderText(label_pos, label);

        IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
        return pressed;
    }

    bool DimmedButton(const char* label, ImVec2 size, ImGuiButtonFlags flags){
        PushStyleColor(ImGuiCol_ButtonHovered, GetCustomColorU32(ImGuiCustomCol_DescButtonHovered));
        PushStyleColor(ImGuiCol_Button, GetCustomColorU32(ImGuiCustomCol_DescButton));
        PushStyleColor(ImGuiCol_Text, GetColorU32(ImGuiCol_ButtonActive));
        PushStyleColor(ImGuiCol_ButtonActive, GetCustomColorU32(ImGuiCustomCol_DescButtonActive));
        PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1);

        bool res = ButtonEx(label, size, flags);

        PopStyleColor(4);
        PopStyleVar(1);

        return res;
    }

    bool DimmedIconButton(const char *symbol, ImVec4 color, ImVec2 size, ImVec2 iconOffset) {
        PushStyleColor(ImGuiCol_ButtonHovered, GetCustomColorU32(ImGuiCustomCol_DescButtonHovered));
        PushStyleColor(ImGuiCol_Button, GetCustomColorU32(ImGuiCustomCol_DescButton));
        PushStyleColor(ImGuiCol_Text, GetColorU32(ImGuiCol_ButtonActive));
        PushStyleColor(ImGuiCol_ButtonActive, GetCustomColorU32(ImGuiCustomCol_DescButtonActive));
        PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.5 * hex::ImHexApi::System::getGlobalScale());

        bool res = IconButton(symbol, color, size, iconOffset);

        PopStyleColor(4);
        PopStyleVar(1);

        return res;
    }

    bool DimmedArrowButton(const char *id, ImGuiDir dir, ImVec2 size) {
        PushStyleColor(ImGuiCol_ButtonHovered, GetCustomColorU32(ImGuiCustomCol_DescButtonHovered));
        PushStyleColor(ImGuiCol_Button, GetCustomColorU32(ImGuiCustomCol_DescButton));
        PushStyleColor(ImGuiCol_Text, GetColorU32(ImGuiCol_ButtonActive));
        PushStyleColor(ImGuiCol_ButtonActive, GetCustomColorU32(ImGuiCustomCol_DescButtonActive));
        PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.5 * hex::ImHexApi::System::getGlobalScale());

        bool res = ArrowButtonEx(id, dir, size);

        PopStyleColor(4);
        PopStyleVar(1);

        return res;
    }

    bool DimmedButtonToggle(const char *icon, bool *v, ImVec2 size, ImVec2 iconOffset) {
        bool pushed = false;
        bool toggled = false;

        if (*v) {
            PushStyleColor(ImGuiCol_Border, GetStyleColorVec4(ImGuiCol_ButtonActive));
            pushed = true;
        }

        if (DimmedIconButton(icon, GetStyleColorVec4(ImGuiCol_Text), size, iconOffset)) {
            *v = !*v;
            toggled = true;
        }

        if (pushed)
            PopStyleColor();

        return toggled;
    }

    bool DimmedIconToggle(const char *icon, bool *v) {
        bool pushed = false;
        bool toggled = false;

        if (*v) {
            PushStyleColor(ImGuiCol_Border, GetStyleColorVec4(ImGuiCol_ButtonActive));
            pushed = true;
        }

        if (DimmedIconButton(icon, GetStyleColorVec4(ImGuiCol_Text))) {
            *v = !*v;
            toggled = true;
        }

        if (pushed)
            PopStyleColor();

        return toggled;
    }

    bool DimmedIconToggle(const char *iconOn, const char *iconOff, bool *v) {
        bool pushed = false;
        bool toggled = false;

        if (*v) {
            PushStyleColor(ImGuiCol_Border, GetStyleColorVec4(ImGuiCol_ButtonActive));
            pushed = true;
        }

        if (DimmedIconButton(*v ? iconOn : iconOff, GetStyleColorVec4(ImGuiCol_Text))) {
            *v = !*v;
            toggled = true;
        }

        if (pushed)
            PopStyleColor();

        return toggled;
    }

    void TextOverlay(const char *text, ImVec2 pos, float maxWidth) {
        const auto textSize = CalcTextSize(text, nullptr, false, maxWidth);
        const auto textPos  = pos - textSize / 2;
        const auto margin   = GetStyle().FramePadding * 2;
        const auto textRect = ImRect(textPos - margin, textPos + textSize + margin);

        auto drawList = GetWindowDrawList();

        drawList->AddDrawCmd();
        drawList->AddRectFilled(textRect.Min, textRect.Max, GetColorU32(ImGuiCol_WindowBg) | 0xFF000000);
        drawList->AddRect(textRect.Min, textRect.Max, GetColorU32(ImGuiCol_Border));
        drawList->AddDrawCmd();
        drawList->AddText(nullptr, 0.0F, textPos, GetColorU32(ImGuiCol_Text), text, nullptr, maxWidth);
    }

    bool BeginBox() {
        PushStyleVar(ImGuiStyleVar_CellPadding, hex::scaled(5, 5));
        if (BeginTable("##box", 1, ImGuiTableFlags_BordersOuter | ImGuiTableFlags_SizingStretchSame)) {
            TableNextRow();
            TableNextColumn();

            return true;
        }

        return false;
    }

    void EndBox() {
        EndTable();
        PopStyleVar();
    }

    bool BeginSubWindow(const char *label, bool *collapsed, ImVec2 size, ImGuiChildFlags flags) {
        const bool hasMenuBar = !std::string_view(label).empty();

        bool result = false;
        ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 5.0F);
        ImGui::PushID("SubWindow");
        if (ImGui::BeginChild(label, size, ImGuiChildFlags_Borders | ImGuiChildFlags_AutoResizeY | flags, hasMenuBar ? ImGuiWindowFlags_MenuBar : ImGuiWindowFlags_None)) {
            result = true;

            if (hasMenuBar && ImGui::BeginMenuBar()) {
                if (collapsed == nullptr)
                    ImGui::TextUnformatted(label);
                else {
                    const auto &style = ImGui::GetStyle();
                    const auto framePadding = style.FramePadding.x;
                    ImGui::PushStyleVarX(ImGuiStyleVar_FramePadding, 0);
                    ImGui::SetCursorPosX(ImGui::GetCursorPosX() - style.WindowPadding.x + framePadding);
                    ImGui::TreeNodeSetOpen(ImGui::GetID("##CollapseHeader"), !*collapsed);
                    *collapsed = !ImGui::TreeNodeEx("##CollapseHeader", ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanLabelWidth);
                    ImGui::SameLine(0, framePadding);
                    ImGui::TextUnformatted(label);
                    if (!*collapsed) ImGui::TreePop();

                    ImGui::PopStyleVar();
                }
                ImGui::EndMenuBar();
            }

            if (collapsed != nullptr && *collapsed) {
                result = false;
            }
        }
        ImGui::PopStyleVar();

        return result;
    }

    void EndSubWindow() {
        ImGui::EndChild();
        ImGui::PopID();
    }

    bool VSliderAngle(const char* label, const ImVec2& size, float* v_rad, float v_degrees_min, float v_degrees_max, const char* format, ImGuiSliderFlags flags) {
        if (format == nullptr)
            format = "%.0f deg";
        float v_deg = (*v_rad) * 360.0F / (2 * std::numbers::pi_v<float>);
        bool value_changed = ImGui::VSliderFloat(label, size, &v_deg, v_degrees_min, v_degrees_max, format, flags);
        *v_rad = v_deg * (2 * std::numbers::pi_v<float>) / 360.0F;
        return value_changed;
    }

    bool InputFilePicker(const char *label, std::fs::path &path, const std::vector<hex::fs::ItemFilter> &validExtensions) {
        bool picked = false;

        ImGui::PushID(label);

        const auto framePadding = ImGui::GetStyle().FramePadding.x;
        const auto buttonSize = ImVec2(ImGui::CalcTextSize("...").x + framePadding * 2, ImGui::GetFrameHeight());
        ImGui::PushItemWidth(ImGui::CalcItemWidth() - buttonSize.x - framePadding);
        std::string string = wolv::util::toUTF8String(path);
        if (ImGui::InputText("##pathInput", string, ImGuiInputTextFlags_AutoSelectAll)) {
            path = std::u8string(string.begin(), string.end());
            picked = true;
        }
        ImGui::PopItemWidth();

        ImGui::SameLine(0, framePadding);

        if (ImGui::Button("...", buttonSize)) {
            hex::fs::openFileBrowser(hex::fs::DialogMode::Open, validExtensions, [&](const std::fs::path &pickedPath) {
                path = pickedPath;
                picked = true;
            });
        }

        ImGui::SameLine();

        ImGui::TextUnformatted(label);

        ImGui::PopID();

        return picked;
    }

    bool ToggleSwitch(const char *label, bool *v) {
        ImGuiWindow* window = GetCurrentWindow();
        if (window->SkipItems)
            return false;

        ImGuiContext& g = *GImGui;
        const ImGuiStyle& style = g.Style;
        const ImGuiID id = window->GetID(label);
        const ImVec2 label_size = CalcTextSize(label, nullptr, true);

        const ImVec2 size = ImVec2(GetFrameHeight() * 2.0F, GetFrameHeight());
        const ImVec2 pos = window->DC.CursorPos;
        const ImRect total_bb(pos, pos + ImVec2(size.x + (label_size.x > 0.0F ? style.ItemInnerSpacing.x + label_size.x : 0.0F), label_size.y + style.FramePadding.y * 2.0F));
        ItemSize(total_bb, style.FramePadding.y);
        if (!ItemAdd(total_bb, id))
        {
            IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
            return false;
        }

        bool hovered, held;
        bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
        if (pressed)
        {
            *v = !(*v);
            MarkItemEdited(id);
        }

        const ImRect knob_bb(pos, pos + size);

        window->DrawList->AddRectFilled(knob_bb.Min, knob_bb.Max, GetColorU32(held ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : *v ? ImGuiCol_ButtonActive : ImGuiCol_Button), size.y / 2);

        if (*v)
            window->DrawList->AddCircleFilled(knob_bb.Max - ImVec2(size.y / 2, size.y / 2), (size.y - style.ItemInnerSpacing.y) / 2, GetColorU32(ImGuiCol_ScrollbarGrabActive), 16);
        else
            window->DrawList->AddCircleFilled(knob_bb.Min + ImVec2(size.y / 2, size.y / 2), (size.y - style.ItemInnerSpacing.y) / 2, GetColorU32(ImGuiCol_ScrollbarGrabActive), 16);

        ImVec2 label_pos = ImVec2(knob_bb.Max.x + style.ItemInnerSpacing.x, knob_bb.Min.y + style.FramePadding.y);
        if (g.LogEnabled)
            LogRenderedText(&label_pos, *v ? "((*)  )" : "(  (*))");
        if (label_size.x > 0.0F)
            RenderText(label_pos, label);

        IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
        return pressed;
    }

    bool ToggleSwitch(const char *label, bool v) {
        return ToggleSwitch(label, &v);
    }

    bool PopupTitleBarButton(const char* label, bool p_enabled)
    {
        ImGuiContext& g = *GImGui;
        ImGuiWindow* window = g.CurrentWindow;
        const ImGuiID id = window->GetID(label);
        const ImRect title_rect = window->TitleBarRect();
        const ImVec2 size(g.FontSize, g.FontSize); // Button size matches font size for aesthetic consistency.
        const ImVec2 pos = window->DC.CursorPos;
        const ImVec2 max_pos = pos + size;
        const ImRect bb(pos.x, title_rect.Min.y, max_pos.x, title_rect.Max.y);

        ImGui::PushClipRect(title_rect.Min, title_rect.Max, false);

        // Check for item addition (similar to how clipping is handled in the original button functions).
        bool is_clipped = !ItemAdd(bb, id);
        bool hovered, held;
        bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None);
        if (is_clipped)
        {
            ImGui::PopClipRect();
            return pressed;
        }

        // const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
        // window->DrawList->AddCircleFilled(bb.GetCenter(), ImMax(2.0f, g.FontSize * 0.5f + 1.0f), bg_col);

        // Draw the label in the center
        ImU32 text_col = GetColorU32(p_enabled || hovered ? ImGuiCol_Text : ImGuiCol_TextDisabled);
        window->DrawList->AddText(bb.GetCenter() - ImVec2(g.FontSize * 0.45F, g.FontSize * 0.5F), text_col, label);

        // Return the button press state
        ImGui::PopClipRect();
        return pressed;
    }

    void PopupTitleBarText(const char* text) {
        ImGuiContext& g = *GImGui;
        ImGuiWindow* window = g.CurrentWindow;
        const ImRect title_rect = window->TitleBarRect();
        const ImVec2 size(g.FontSize, g.FontSize); // Button size matches font size for aesthetic consistency.
        const ImVec2 pos = window->DC.CursorPos;
        const ImVec2 max_pos = pos + size;
        const ImRect bb(pos.x, title_rect.Min.y, max_pos.x, title_rect.Max.y);

        ImGui::PushClipRect(title_rect.Min, title_rect.Max, false);

        // Draw the label in the center
        ImU32 text_col = GetColorU32(ImGuiCol_Text);
        window->DrawList->AddText(bb.GetCenter() - ImVec2(g.FontSize * 0.45F, g.FontSize * 0.5F), text_col, text);

        // Return the button press state
        ImGui::PopClipRect();
    }

    bool IsDarkBackground(const ImColor& bgColor) {
        // Extract RGB components in 0–255 range
        int r = static_cast<int>(bgColor.Value.x * 255.0F);
        int g = static_cast<int>(bgColor.Value.y * 255.0F);
        int b = static_cast<int>(bgColor.Value.z * 255.0F);

        // Compute brightness using perceived luminance
        int brightness = (r * 299 + g * 587 + b * 114) / 1000;

        // If brightness is below threshold, use white text
        return brightness < 128;
    }



    static bool s_imguiTestEngineEnabled = false;

    void ImGuiTestEngine::setEnabled(bool enabled) {
        s_imguiTestEngineEnabled = enabled;
    }

    bool ImGuiTestEngine::isEnabled() {
        return s_imguiTestEngineEnabled;
    }
}

namespace ImGui {

    bool InputText(const char *label, std::u8string &buffer, ImGuiInputTextFlags flags) {
        return ImGui::InputText(label, reinterpret_cast<char *>(buffer.data()), buffer.size() + 1, ImGuiInputTextFlags_CallbackResize | flags, ImGuiExt::UpdateStringSizeCallback, &buffer);
    }

    bool InputText(const char *label, std::string &buffer, ImGuiInputTextFlags flags) {
        return ImGui::InputText(label, buffer.data(), buffer.size() + 1, ImGuiInputTextFlags_CallbackResize | flags, ImGuiExt::UpdateStringSizeCallback, &buffer);
    }

    bool InputTextMultiline(const char *label, std::string &buffer, const ImVec2 &size, ImGuiInputTextFlags flags) {
        return ImGui::InputTextMultiline(label, buffer.data(), buffer.size() + 1, size, ImGuiInputTextFlags_CallbackResize | flags, ImGuiExt::UpdateStringSizeCallback, &buffer);
    }

    bool InputTextWithHint(const char *label, const char *hint, std::string &buffer, ImGuiInputTextFlags flags) {
        return ImGui::InputTextWithHint(label, hint, buffer.data(), buffer.size() + 1, ImGuiInputTextFlags_CallbackResize | flags, ImGuiExt::UpdateStringSizeCallback, &buffer);
    }

}
