#include "vkcheck.hpp"

#include "extensions.hpp"

#include <algorithm>
#include <format>
#include <functional>
#include <iomanip>
#include <ios>
#include <mutex>
#include <print>
#include <ranges>
#include <set>
#include <sstream>
#include <string>
#include <utility>
#include <vector>

#include <imgui.h>
#include <imgui_impl_vulkan.h>
#include <misc/cpp/imgui_stdlib.h>

namespace {
std::ostringstream vp_log_buf;
} // namespace

void vp_debug(const char* msg) {
    vp_log_buf << msg << "\n";
}

#define VP_DEBUG_MESSAGE_CALLBACK vp_debug
#define VP_USE_OBJECT
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#pragma GCC diagnostic ignored "-Wtype-limits"
#include "vulkan_profiles.hpp"
#pragma GCC diagnostic pop

namespace {
inline void check_vk_result(VkResult err) {
    if (err == 0) {
        return;
    }
    std::println(stderr, "[vulkan] Error: VkResult = {}", static_cast<int>(err));
    if (err < 0) {
        std::abort();
    }
}

std::vector<VkExtensionProperties> get_instance_extension_properties() {
    uint32_t num_properties = 0;
    std::vector<VkExtensionProperties> properties;
    vkEnumerateInstanceExtensionProperties(nullptr, &num_properties, nullptr);
    properties.resize(num_properties);
    check_vk_result(vkEnumerateInstanceExtensionProperties(nullptr, &num_properties, properties.data()));
    return properties;
}

std::vector<VkExtensionProperties> get_device_extension_properties(VkPhysicalDevice device) {
    uint32_t num_properties = 0;
    std::vector<VkExtensionProperties> properties;
    vkEnumerateDeviceExtensionProperties(device, nullptr, &num_properties, nullptr);
    properties.resize(num_properties);
    check_vk_result(vkEnumerateDeviceExtensionProperties(device, nullptr, &num_properties, properties.data()));
    return properties;
}

std::optional<VkPhysicalDeviceDriverProperties> get_device_driver_properties(VkPhysicalDevice device) {
    VkPhysicalDeviceDriverProperties driver_properties = {};
    driver_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES;

    VkPhysicalDeviceProperties2 properties;
    properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
    properties.pNext = &driver_properties;

    vkGetPhysicalDeviceProperties2(device, &properties);

    auto* p_next = reinterpret_cast<VkBaseOutStructure*>(properties.pNext);
    while (p_next != nullptr && p_next->sType != VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES) {
        p_next = reinterpret_cast<VkBaseOutStructure*>(p_next->pNext);
    };

    if (p_next == nullptr) {
        return {};
    }

    return *reinterpret_cast<VkPhysicalDeviceDriverProperties*>(p_next);
}

const char* device_type_str(const VkPhysicalDeviceType device_type) {
    const auto type_int = static_cast<int32_t>(device_type);

    if (std::clamp(type_int, 0, 4) != type_int) {
        return "Other";
    }

    std::array<const char* const, 5> device_types = {"Other", "Integrated", "Discrete", "Virtual", "CPU"};
    return device_types.at(type_int);
}

std::string api_version_str(const uint32_t api_version) {
    const uint8_t variant = api_version >> 29;
    const uint8_t major = (api_version >> 22) & 0b1111111;
    const uint16_t minor = (api_version >> 12) & 0b1111111111;
    const uint16_t patch = api_version & 0b111111111111;

    if (variant > 0) {
        return std::format("{}.{}.{} (variant)", major, minor, patch);
    }

    return std::format("{}.{}.{}", major, minor, patch);
}

VpCapabilities create_profile_capabilities(VkAllocationCallbacks* allocator) {
    VpCapabilities capabilities = VK_NULL_HANDLE;

    VpCapabilitiesCreateInfo create_info;
    create_info.apiVersion = VK_API_VERSION_1_0;
    create_info.flags = VP_PROFILE_CREATE_STATIC_BIT;
    create_info.pVulkanFunctions = nullptr;

    check_vk_result(vpCreateCapabilities(&create_info, allocator, &capabilities));

    return capabilities;
}

bool instance_supports_profile(VpCapabilities capabilities, VpProfileProperties profile) {
    VkBool32 supported = VK_FALSE;
    check_vk_result(vpGetInstanceProfileSupport(capabilities, nullptr, &profile, &supported));
    return supported == VK_TRUE;
}

bool device_supports_profile(
    VpCapabilities capabilities, VkInstance instance, VkPhysicalDevice device, VpProfileProperties profile) {
    VkBool32 supported = VK_FALSE;
    check_vk_result(vpGetPhysicalDeviceProfileSupport(capabilities, instance, device, &profile, &supported));
    return supported == VK_TRUE;
}

std::string& status_to_string(const vkcheck::ExtensionStatus status) {
    static std::array<std::string, static_cast<uint8_t>(vkcheck::ExtensionStatus::UNKNOWN) + 1> status_strings = {"Core (1.1)",
        "Core (1.2)", "Core (1.3)", "Core (1.4)", "Optional (1.1)", "Optional (1.2)", "Optional (1.3)", "Optional (1.4)",
        "Current", "Provisional", "Deprecated", "Unregistered", "Unknown"};

    return status_strings.at(static_cast<size_t>(status));
}

void draw_conformance_bar(const std::pair<float, std::string>& conformance) noexcept {
    if (conformance.first == 1.0F) {
        ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImGui::GetColorU32(ImVec4(0.5F, 1.0F, 0.5F, 0.5F)));
    } else {
        ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImGui::GetColorU32(ImVec4(0.2F, 0.5F, 1.0F, 0.5F)));
    }

    ImGui::TableNextColumn();
    ImGui::ProgressBar(conformance.first, ImVec2(-1.0F, 0.0F), conformance.second.c_str());
    ImGui::PopStyleColor();
}

void render_conformance_tree(const vkcheck::internal::ApiConformance& conformance) noexcept {
    if (ImGui::TreeNodeEx("Conformance", ImGuiTreeNodeFlags_Framed)) {
        if (ImGui::BeginTable("conformance-table", conformance.required_extensions.size() + 1,
                ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_NoPadInnerX)) {
            ImGui::TableSetupColumn("none", ImGuiTableColumnFlags_NoHeaderLabel | ImGuiTableColumnFlags_WidthFixed);
            ImGui::TableSetupColumn("Vulkan 1.1");
            ImGui::TableSetupColumn("Vulkan 1.2");
            ImGui::TableSetupColumn("Vulkan 1.3");
            ImGui::TableSetupColumn("Vulkan 1.4");
            ImGui::TableHeadersRow();

            ImGui::TableNextColumn();
            ImGui::Text(" Required ");
            for (const auto& conformance : conformance.required_extensions) {
                draw_conformance_bar(conformance);
            }

            ImGui::TableNextColumn();
            ImGui::Text(" Optional ");
            for (const auto& conformance : conformance.optional_extensions) {
                draw_conformance_bar(conformance);
            }

            ImGui::EndTable();
        }

        ImGui::TreePop();
    }
}
} // namespace



namespace vkcheck {
struct PhysicalDevice {
    VkPhysicalDevice device = VK_NULL_HANDLE;
    VkPhysicalDeviceProperties properties;
    std::string uuid;
    std::string label;
};

struct ProfileSupport {
    VpProfileProperties properties;
    bool instance;
    std::string instance_errors;
    bool device;
    std::string device_errors;
};

struct ExtensionRow {
    ExtensionStatus status;
    std::string name;
    std::optional<uint32_t> spec_version;

    static const ImGuiTableSortSpecs* current_sort_specs;
    std::string name_upper;
    std::string spec_version_str;

    explicit ExtensionRow(ExtensionStatus status, std::string name, std::optional<uint32_t> spec_version);

    static void sort(ImGuiTableSortSpecs* sort_specs, std::vector<std::reference_wrapper<ExtensionRow>>& rows);
};

const ImGuiTableSortSpecs* ExtensionRow::current_sort_specs = nullptr;

ExtensionRow::ExtensionRow(ExtensionStatus status, std::string name, std::optional<uint32_t> spec_version)
    : status(status), name(std::move(name)), spec_version(spec_version) {
    if (spec_version.has_value()) {
        this->spec_version_str = std::to_string(spec_version.value());
    }

    name_upper = this->name;
    std::ranges::transform(name_upper, name_upper.begin(), ::toupper);
}

void ExtensionRow::sort(ImGuiTableSortSpecs* sort_specs, std::vector<std::reference_wrapper<ExtensionRow>>& rows) {
    auto custom_less = [](const ExtensionRow& lhs, const ExtensionRow& rhs) {
        for (int i = 0; i < current_sort_specs->SpecsCount; i++) {
            const auto& sort_spec = current_sort_specs->Specs[i];

            int64_t delta = 0;
            switch (sort_spec.ColumnIndex) {
                case 0:
                    delta = static_cast<uint8_t>(lhs.status) - static_cast<uint8_t>(rhs.status);
                    break;
                case 1:
                    delta = lhs.name.compare(rhs.name);
                    break;
                case 2:
                    delta = lhs.spec_version.value_or(UINT16_MAX) - rhs.spec_version.value_or(UINT16_MAX);
                    break;
                default:
                    assert(false);
                    break;
            }

            if (delta != 0) {
                return (sort_spec.SortDirection == ImGuiSortDirection_Ascending) ? delta < 0 : delta > 0;
            }
        }

        return false;
    };

    current_sort_specs = sort_specs;
    if (!rows.empty()) {
        std::ranges::stable_sort(rows, custom_less);
    }
    current_sort_specs = nullptr;
}

namespace {
void render_extension_table(
    const char* const table_id, std::vector<std::reference_wrapper<ExtensionRow>>& rows, const bool force_sort) noexcept {
    if (rows.empty()) {
        ImGui::Text("No extensions to display");
        return;
    }

    if (!ImGui::BeginTable(table_id, 3, ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY | ImGuiTableFlags_Sortable)) {
        return;
    }

    ImGui::TableSetupColumn("Status");
    ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch);
    ImGui::TableSetupColumn("Version");
    ImGui::TableSetupScrollFreeze(0, 1);
    ImGui::TableHeadersRow();

    if (auto* sort_specs = ImGui::TableGetSortSpecs()) {
        if (sort_specs->SpecsDirty || force_sort) {
            ExtensionRow::sort(sort_specs, rows);
            sort_specs->SpecsDirty = false;
        }
    }

    const uint32_t implemented_bg_color = ImGui::GetColorU32(ImVec4(0.5F, 1.0F, 0.5F, 0.15F));

    ImGuiListClipper clipper;
    clipper.Begin(rows.size());
    while (clipper.Step()) {
        for (auto row_idx = clipper.DisplayStart; row_idx < clipper.DisplayEnd; row_idx++) {
            const auto& row = rows[row_idx].get();

            ImGui::TableNextColumn();
            ImGui::Text("%s", status_to_string(row.status).c_str());
            ImGui::TableNextColumn();
            ImGui::Text("%s", row.name.c_str());
            ImGui::TableNextColumn();
            if (row.spec_version.has_value()) {
                ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1, implemented_bg_color, -1);
                ImGui::Text("%s", row.spec_version_str.c_str());
            }
        }
    }

    ImGui::EndTable();
}

std::array<std::pair<float, std::string>, 4> determine_device_conformance(
    const std::vector<ExtensionRow>& rows, bool optional) noexcept {
    std::array<std::pair<float, std::string>, 4> ret;

    size_t offset = optional ? ret.size() : 0;

    for (size_t i = 0; i < ret.size(); i++) {
        size_t numerator = 0;
        size_t denominator = 0;
        const auto status = static_cast<ExtensionStatus>(i + offset);

        for (const auto& row : rows) {
            if (row.status == status) {
                denominator++;
                if (row.spec_version.has_value()) {
                    numerator++;
                }
            }
        }

        if (denominator == 0) {
            ret.at(i) = {0.0F, "0/0"};
        } else {
            ret.at(i) = {(float) numerator / (float) denominator, std::format("{}/{}", numerator, denominator)};
        }
    }

    return ret;
}
} // namespace

template <typename T>
std::vector<ExtensionRow> Gui::build_extension_rows(const std::vector<Extension>& all, std::vector<T> implemented) noexcept {
    std::vector<ExtensionRow> ret;
    ret.reserve(all.size());

    std::string search_upper = current_search;
    if (!search_upper.empty()) {
        std::ranges::transform(search_upper, search_upper.begin(), ::toupper);
    }

    for (const auto& ext : all) {
        std::optional<uint32_t> spec_version = {};
        for (auto impl = implemented.begin(); impl < implemented.end(); impl++) {
            if (ext.name == impl->extensionName) {
                spec_version = impl->specVersion;
                implemented.erase(impl);
                break;
            }
        }

        ret.emplace_back(ext.status, ext.name, spec_version);
    }

    for (const auto& ext : implemented) {
        ret.emplace_back(ExtensionStatus::UNKNOWN, ext.extensionName, ext.specVersion);
    }

    return ret;
}

void Gui::update_displayed_extension_rows() noexcept {
    std::string search_upper = current_search;

    if (!search_upper.empty()) {
        std::ranges::transform(search_upper, search_upper.begin(), ::toupper);
    }

    const auto filter = [search_upper, this](ExtensionRow& row) {
        bool matches_search = search_upper.empty() || row.name_upper.contains(search_upper);
        bool matches_filter =
            support_filter == internal::SupportFilter::SHOW_ALL ||
            (((support_filter == internal::SupportFilter::SHOW_UNSUPPORTED) ^ row.spec_version.has_value()) != 0);

        return matches_search && matches_filter;
    };

    view.device_extension_rows = device_extensions | std::ranges::views::filter(filter) |
                                 std::ranges::to<std::vector<std::reference_wrapper<ExtensionRow>>>();
    view.instance_extension_rows = instance_extensions | std::ranges::views::filter(filter) |
                                   std::ranges::to<std::vector<std::reference_wrapper<ExtensionRow>>>();
}

void Gui::render_profile_support_cell(const char* const profile_name, const bool supports, const std::string& log) noexcept {
    ImGui::TableNextColumn();
    ImGui::Text("%s", supports ? "Yes" : "No");
    if (!log.empty()) {
        ImGui::SameLine();
        ImGui::PushID(profile_name);
        if (ImGui::SmallButton("(?)")) {
            if (log_current_profile != profile_name) {
                log_current_profile = profile_name;
                view.profile_support_log = log;
            } else {
                log_current_profile = "";
                view.profile_support_log = "";
            }
        }
        if (ImGui::IsItemHovered()) {
            ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
        }

        ImGui::PopID();
    }
}

Gui::Gui(VkInstance instance, float main_scale, bool fullscreen) noexcept
    : instance(instance), capabilities(create_profile_capabilities(allocator)), fullscreen(fullscreen) {
    uint32_t num_devices = 0;
    check_vk_result(vkEnumeratePhysicalDevices(instance, &num_devices, nullptr));
    std::vector<VkPhysicalDevice> devices(num_devices);
    check_vk_result(vkEnumeratePhysicalDevices(instance, &num_devices, devices.data()));

    uint32_t num_profiles = 0;
    check_vk_result(vpGetProfiles(capabilities, &num_profiles, nullptr));

    profiles = std::vector<VpProfileProperties>(num_profiles);
    check_vk_result(vpGetProfiles(capabilities, &num_profiles, profiles.data()));

    auto* imgui_selected_device = ImGui_ImplVulkanH_SelectPhysicalDevice(instance);

    std::set<std::string> device_uuids = {};
    uint32_t i = 0;

    for (auto& dev : devices) {
        VkPhysicalDeviceIDProperties id_properties = {};
        id_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES;

        VkPhysicalDeviceProperties2 properties2 = {};
        properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
        properties2.pNext = &id_properties;

        vkGetPhysicalDeviceProperties2(dev, &properties2);
        VkPhysicalDeviceProperties properties = properties2.properties;

        std::stringstream uuid_stream;
        uuid_stream << std::hex << std::setfill('0');

        for (auto c : id_properties.deviceUUID) {
            uuid_stream << std::hex << std::setw(2) << static_cast<int32_t>(c);
        }

        std::string uuid = uuid_stream.str();
        if (!device_uuids.contains(uuid)) {
            device_uuids.insert(uuid);

            std::string label =
                std::format("{}. {} [{:x}:{:x}]", i, properties.deviceName, properties.vendorID, properties.deviceID, uuid);

            auto new_physical_device = PhysicalDevice{
                .device = dev,
                .properties = properties,
                .uuid = uuid,
                .label = label,
            };

            physical_devices.push_back(new_physical_device);

            // default the combo box selection to the device that imgui would have used
            if (dev == imgui_selected_device) {
                current_device = physical_devices.size() - 1;
            }

            i++;
        }
    }

    get_instance_extension_properties();

    IMGUI_CHECKVERSION();
    this->imgui_context = ImGui::CreateContext();

    auto& io = ImGui::GetIO();
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls

    ImGui::StyleColorsDark();
    ImGuiStyle& style = ImGui::GetStyle();
    style.ScaleAllSizes(main_scale);
    style.FontScaleDpi = main_scale;

    refresh();
}

Gui::~Gui() = default;

void Gui::refresh() noexcept {
    static std::mutex vp_log;

    const auto& instance_extension_props = get_instance_extension_properties();
    instance_extensions = build_extension_rows(known_instance_extensions, instance_extension_props);

    const auto& device_extension_props = get_device_extension_properties(physical_devices[current_device].device);
    device_extensions = build_extension_rows(known_device_extensions, device_extension_props);
    view.device_conformance.required_extensions = determine_device_conformance(device_extensions, false);
    view.device_conformance.optional_extensions = determine_device_conformance(device_extensions, true);

    profile_support.clear();
    for (const auto& profile : profiles) {
        ProfileSupport ret = {};
        ret.properties = profile;

        {
            std::scoped_lock lock(vp_log);

            vp_log_buf.str("");
            ret.instance = instance_supports_profile(capabilities, profile);
            if (!ret.instance) {
                ret.instance_errors = vp_log_buf.str();
            } else {
                vp_log_buf.str("");
                ret.device = device_supports_profile(capabilities, instance, physical_devices[current_device].device, profile);
                if (!ret.device) {
                    ret.device_errors = vp_log_buf.str();
                }
            }
        }

        profile_support.push_back(ret);
    }

    log_current_profile = "";
    view.profile_support_log = "";

    const auto& device = physical_devices[current_device];

    const auto& device_type = device_type_str(device.properties.deviceType);
    const auto& api_version = api_version_str(device.properties.apiVersion);

    std::string driver = "Unknown";
    if (std::ranges::any_of(instance_extension_props.cbegin(), instance_extension_props.cend(), [](VkExtensionProperties p) {
            return strcmp(static_cast<const char*>(p.extensionName), "VK_KHR_get_physical_device_properties2") == 0;
        })) {
        const auto driver_properties = get_device_driver_properties(device.device);
        if (driver_properties.has_value()) {
            driver = std::format("{} ({})", driver_properties->driverName, driver_properties->driverInfo);
        }
    }

    view.device_info =
        std::format("Type: {}\nDriver: {}\nAPI version: {}\nUUID: 0x{}", device_type, driver, api_version, device.uuid);

    update_displayed_extension_rows();
}

bool Gui::render_toolbar() noexcept {
    bool force_sort = false;

    ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F);
    if (ImGui::InputTextWithHint("##search", "Search extensions...", &current_search, ImGuiInputTextFlags_CharsNoBlank)) {
        force_sort = true;
        update_displayed_extension_rows();
    }

    ImGui::SameLine(0, 10);

    std::array<const char*, 3> options = {"Show all", "Show supported", "Show unsupported"};
    if (ImGui::BeginCombo(
            "##support-filter", options.at(static_cast<size_t>(support_filter)), ImGuiComboFlags_WidthFitPreview)) {
        for (size_t n = 0; n < options.size(); n++) {
            const bool is_selected = current_device == n;
            if (ImGui::Selectable(options.at(n), is_selected)) {
                support_filter = static_cast<internal::SupportFilter>(n);
                force_sort = true;
                update_displayed_extension_rows();
            }

            // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
            if (is_selected) {
                ImGui::SetItemDefaultFocus();
            }
        }
        ImGui::EndCombo();
    }

    return force_sort;
}

void Gui::render_instance_extensions_tree(ImGuiWindowFlags window_flags, bool force_sort) noexcept {
    if (ImGui::TreeNodeEx("Instance Extensions", ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_Framed)) {
        ImGui::SetNextWindowSizeConstraints(
            ImVec2(0, 0), ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y * 0.2F));
        ImGui::BeginChild("ChildA", ImVec2(ImGui::GetContentRegionAvail().x, 0),
            ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_ResizeY, window_flags);

        render_extension_table("instance-table", view.instance_extension_rows, force_sort);

        ImGui::EndChild();
        ImGui::TreePop();
    }
}

void Gui::render_profile_support_tree() noexcept {
    if (ImGui::TreeNodeEx("Profiles", ImGuiTreeNodeFlags_Framed)) {
        if (ImGui::BeginTable("profile-table", 4, ImGuiTableFlags_Borders)) {
            ImGui::TableSetupColumn("Profile Name", ImGuiTableColumnFlags_WidthStretch);
            ImGui::TableSetupColumn("Version");
            ImGui::TableSetupColumn("Instance Supports");
            ImGui::TableSetupColumn("Device Supports");
            ImGui::TableHeadersRow();

            for (const auto& profile : profile_support) {
                const char* const profile_name = static_cast<const char*>(profile.properties.profileName);

                ImGui::TableNextColumn();
                ImGui::Text("%s", profile_name);
                ImGui::TableNextColumn();
                ImGui::Text("%u", profile.properties.specVersion);

                render_profile_support_cell(profile_name, profile.instance, profile.instance_errors);
                render_profile_support_cell(profile_name, profile.device, profile.device_errors);
            }

            ImGui::EndTable();
        }

        if (!view.profile_support_log.empty()) {
            ImGui::InputTextMultiline("##profile-support-log", &view.profile_support_log, ImVec2(-FLT_MIN, 0),
                ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_WordWrap);
        }

        ImGui::TreePop();
    }
}

void Gui::render_device_extensions_tree(ImGuiWindowFlags window_flags, bool force_sort) noexcept {
    if (ImGui::TreeNodeEx("Device Extensions", ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_Framed)) {
        ImGui::BeginChild("ChildB", ImVec2(ImGui::GetContentRegionAvail()), ImGuiChildFlags_AutoResizeY, window_flags);

        std::vector<const char*> dev_labels;
        dev_labels.reserve(physical_devices.size());

        for (const auto& dev : physical_devices) {
            dev_labels.push_back(dev.label.c_str());
        }

        if (ImGui::BeginCombo("##device-selector", dev_labels[current_device], ImGuiComboFlags_WidthFitPreview)) {
            for (size_t n = 0; n < dev_labels.size(); n++) {
                const bool is_selected = current_device == n;
                if (ImGui::Selectable(dev_labels[n], is_selected)) {
                    force_sort = true;
                    current_device = n;
                    refresh();
                }

                // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
                if (is_selected) {
                    ImGui::SetItemDefaultFocus();
                }
            }
            ImGui::EndCombo();
        }

        ImGui::SameLine(0, 10);

        if (ImGui::TreeNodeEx("Device Info")) {
            ImGui::Text("%s", view.device_info.c_str());

            ImGui::TreePop();
        }

        render_conformance_tree(view.device_conformance);
        render_profile_support_tree();
        render_extension_table("device-table", view.device_extension_rows, force_sort);

        ImGui::EndChild();
        ImGui::TreePop();
    }
}

void Gui::render() noexcept {
    const ImGuiViewport* main_viewport = ImGui::GetMainViewport();

    ImGuiWindowFlags window_flags = ImGuiWindowFlags_None;
    ImVec2 window_size = ImVec2(800, 800);
    ImVec2 window_pos = ImVec2(main_viewport->WorkPos.x + ((main_viewport->WorkSize.x - window_size.x) / 2),
        main_viewport->WorkPos.y + ((main_viewport->WorkSize.y - window_size.y) / 2));
    auto window_cond = ImGuiCond_FirstUseEver;

    if (fullscreen) {
        window_flags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize;
        window_size = main_viewport->WorkSize;
        window_pos = main_viewport->WorkPos;
        window_cond = ImGuiCond_Always;
    }

    ImGui::SetNextWindowSize(window_size, window_cond);
    ImGui::SetNextWindowPos(window_pos, window_cond);

    bool* open = nullptr;

    if (!ImGui::Begin("VKcheck", open, window_flags)) {
        // Early out if the window is collapsed, as an optimization.
        ImGui::End();
        return;
    }

    bool force_sort = render_toolbar();

    render_instance_extensions_tree(window_flags, force_sort);
    render_device_extensions_tree(window_flags, force_sort);

    ImGui::End();
}
} // namespace vkcheck
