#include <algorithm>
#include <assert.h>
#include <atomic>
#include <chrono>
#include <inttypes.h>
#define IMGUI_DEFINE_MATH_OPERATORS 1
#include <imgui.h>
#include <mutex>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <unordered_map>
#include <memory>
#include <vector>
#include <sys/stat.h>
#include <locale.h>

#ifdef _WIN32
#  include <windows.h>
#endif

#define STB_IMAGE_IMPLEMENTATION
#define STBI_ONLY_PNG
#include "stb_image.h"

#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "stb_image_resize.h"

#include "../../public/common/TracyProtocol.hpp"
#include "../../public/common/TracyVersion.hpp"
#include "profiler/TracyAchievements.hpp"
#include "profiler/TracyBadVersion.hpp"
#include "profiler/TracyConfig.hpp"
#include "profiler/TracyFileselector.hpp"
#include "profiler/TracyImGui.hpp"
#include "profiler/TracyMarkdown.hpp"
#include "profiler/TracyMouse.hpp"
#include "profiler/TracyProtoHistory.hpp"
#include "profiler/TracyTexture.hpp"
#include "profiler/TracyView.hpp"
#include "profiler/TracyWeb.hpp"
#include "profiler/IconsFontAwesome7.h"
#include "../../server/tracy_pdqsort.h"
#include "../../server/tracy_robin_hood.h"
#include "../../server/TracyFileHeader.hpp"
#include "../../server/TracyFileRead.hpp"
#include "../../server/TracyBroadcast.hpp"
#include "../../server/TracyPrint.hpp"
#include "../../server/TracySysUtil.hpp"
#include "../../server/TracyWorker.hpp"

#include "icon.hpp"
#include "zigzag01.hpp"
#include "zigzag02.hpp"
#include "zigzag04.hpp"
#include "zigzag08.hpp"
#include "zigzag16.hpp"
#include "zigzag32.hpp"

#include "Backend.hpp"
#include "ConnectionHistory.hpp"
#include "EmscriptenShim.hpp"
#include "Filters.hpp"
#include "Fonts.hpp"
#include "HttpRequest.hpp"
#include "IsElevated.hpp"
#include "ImGuiContext.hpp"
#include "ResolvService.hpp"
#include "RunQueue.hpp"

#include "GitRef.hpp"


struct ClientData
{
    int64_t time;
    uint64_t order;
    uint32_t protocolVersion;
    int32_t activeTime;
    uint16_t port;
    uint64_t pid;
    std::string procName;
    std::string address;
};

enum class ViewShutdown { False, True, Join };

static tracy::unordered_flat_map<uint64_t, ClientData> clients;
static uint64_t clientOrder = 0;
static std::atomic<std::shared_ptr<tracy::View>> view;
static tracy::BadVersionState badVer;
static uint16_t port = 8086;
static const char* connectTo = nullptr;
static char title[128];
static std::thread loadThread, updateThread, updateNotesThread;
static std::unique_ptr<tracy::UdpListen> broadcastListen;
static std::mutex resolvLock;
static tracy::unordered_flat_map<std::string, std::string> resolvMap;
static ResolvService resolv( port );
static char addr[1024] = { "127.0.0.1" };
static ConnectionHistory* connHist;
static std::atomic<ViewShutdown> viewShutdown { ViewShutdown::False };
static double animTime = 0;
static float dpiScale = -1.f;
static bool dpiScaleOverriddenFromEnv = false;
static float prevScale = 1.f;
static int dpiChanged = 0;
static bool dpiFirstSetup = true;
static Filters* filt;
static RunQueue mainThreadTasks;
static uint32_t updateVersion = 0;
static bool showReleaseNotes = false;
static std::string releaseNotes;
static uint8_t* iconPx;
static int iconX, iconY;
static ImTextureID iconTex;
static int iconTexSz;
static uint8_t* zigzagPx[6];
static int zigzagX[6], zigzagY[6];
ImTextureID zigzagTex;
static Backend* bptr;
static bool s_customTitle = false;
static bool s_isElevated = false;
static size_t s_totalMem = tracy::GetPhysicalMemorySize();
tracy::AchievementsMgr* s_achievements;
static const tracy::data::AchievementItem* s_achievementItem = nullptr;
static bool s_switchAchievementCategory = false;

ImTextureID GetProfilerIconTexture() { return iconTex; }

static float smoothstep( float x )
{
    return x * x * ( 3.0f - 2.0f * x );
}

static void SetWindowTitleCallback( const char* title )
{
    char tmp[1024];
    sprintf( tmp, "%s - Tracy Profiler %i.%i.%i", title, tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch );
    bptr->SetTitle( tmp );
    s_customTitle = true;
}

static void AttentionCallback()
{
    bptr->Attention();
}

static void DrawContents();

static void RunOnMainThread( const std::function<void()>& cb, bool forceDelay = false )
{
    mainThreadTasks.Queue( cb, forceDelay );
}

static void ScaleWindow(ImGuiWindow* window, float scale)
{
    ImVec2 origin = window->Viewport->Pos;
    window->Pos = ImFloor((window->Pos - origin) * scale + origin);
    window->Size = ImTrunc(window->Size * scale);
    window->SizeFull = ImTrunc(window->SizeFull * scale);
    window->ContentSize = ImTrunc(window->ContentSize * scale);
}

static void SetupDPIScale()
{
    auto scale = dpiScale * tracy::s_config.userScale;

#ifdef __APPLE__
    scale = tracy::s_config.userScale;
#endif

    if( !dpiFirstSetup && prevScale == scale ) return;
    dpiFirstSetup = false;
    dpiChanged = 2;

    LoadFonts( scale );

    auto& style = ImGui::GetStyle();
    style = ImGuiStyle();
    ImGui::StyleColorsDark();
    style.WindowBorderSize = 1.f;
    style.FrameBorderSize = 1.f;
    style.FrameRounding = 5.f;
    style.Colors[ImGuiCol_ScrollbarBg] = ImVec4( 1, 1, 1, 0.03f );
    style.Colors[ImGuiCol_Header] = ImVec4(0.26f, 0.59f, 0.98f, 0.25f);
    style.Colors[ImGuiCol_HeaderHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f);
    style.Colors[ImGuiCol_HeaderActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.45f);
    style.Colors[ImGuiCol_TitleBgCollapsed] = style.Colors[ImGuiCol_TitleBg];
    style.ScaleAllSizes( scale );

    const auto ty = int( 80 * scale );
    iconTexSz = ty;
    auto scaleIcon = new uint8_t[4*ty*ty];
    stbir_resize_uint8( iconPx, iconX, iconY, 0, scaleIcon, ty, ty, 0, 4 );
    tracy::UpdateTextureRGBA( iconTex, scaleIcon, ty, ty );
    delete[] scaleIcon;

    const auto ratio = scale / prevScale;
    prevScale = scale;
    auto ctx = ImGui::GetCurrentContext();
    for( auto& w : ctx->Windows ) ScaleWindow( w, ratio );

    auto viewPtr = view.load( std::memory_order_acquire );
    if( viewPtr ) viewPtr->DpiScaleChanged();
}

static int IsBusy()
{
    if( loadThread.joinable() ) return 2;
    auto ptr = view.load( std::memory_order_acquire );
    if( ptr && !ptr->IsBackgroundDone() ) return 1;
    return 0;
}

static void SetupScaleCallback( float scale )
{
    tracy::s_config.userScale = scale;
    if ( tracy::s_config.saveUserScale ) tracy::SaveConfig();
    RunOnMainThread( []{ SetupDPIScale(); }, true );
}

static void ScaleChanged( float scale )
{
    if( dpiScaleOverriddenFromEnv ) return;
    if( dpiScale == scale ) return;

    dpiScale = scale;
    SetupDPIScale();
}

int main( int argc, char** argv )
{
    sprintf( title, "Tracy Profiler %i.%i.%i", tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch );

    std::unique_ptr<tracy::FileRead> initFileOpen;
#ifdef __EMSCRIPTEN__
    initFileOpen = std::unique_ptr<tracy::FileRead>( tracy::FileRead::Open( "embed.tracy" ) );
#endif
    if( argc == 2 )
    {
        if( strcmp( argv[1], "--help" ) == 0 )
        {
            printf( "%s / %s\n\n", title, tracy::GitRef );
            printf( "Usage:\n\n" );
            printf( "    Open trace file stored on disk:\n" );
            printf( "      %s file.tracy\n\n", argv[0] );
            printf( "    Connect to a running client:\n" );
            printf( "      %s -a address [-p port]\n", argv[0] );
            exit( 0 );
        }
        try
        {
            initFileOpen = std::unique_ptr<tracy::FileRead>( tracy::FileRead::Open( argv[1] ) );
        }
        catch( const tracy::UnsupportedVersion& e )
        {
            fprintf( stderr, "The file you are trying to open is from the future version.\n" );
            exit( 1 );
        }
        catch( const tracy::NotTracyDump& e )
        {
            fprintf( stderr, "The file you are trying to open is not a tracy dump.\n" );
            exit( 1 );
        }
        catch( const tracy::LegacyVersion& e )
        {
            fprintf( stderr, "The file you are trying to open is from a legacy version.\n" );
            exit( 1 );
        }
        if( !initFileOpen )
        {
            fprintf( stderr, "Cannot open trace file: %s\n", argv[1] );
            exit( 1 );
        }
    }
    else
    {
        while( argc >= 3 )
        {
            if( strcmp( argv[1], "-a" ) == 0 )
            {
                connectTo = argv[2];
            }
            else if( strcmp( argv[1], "-p" ) == 0 )
            {
                port = (uint16_t)atoi( argv[2] );
            }
            else
            {
                fprintf( stderr, "Bad parameter: %s", argv[1] );
                exit( 1 );
            }
            argc -= 2;
            argv += 2;
        }
    }

    ConnectionHistory connHistory;
    Filters filters;
    tracy::AchievementsMgr achievements;

    connHist = &connHistory;
    filt = &filters;
    s_achievements = &achievements;

#ifndef __EMSCRIPTEN__
    updateThread = std::thread( [] {
        HttpRequest( "nereid.pl", "/tracy/version", 8099, [] ( int size, char* data ) {
            if( size == 4 )
            {
                uint32_t ver;
                memcpy( &ver, data, 4 );
                RunOnMainThread( [ver] { updateVersion = ver; tracy::s_wasActive = true; } );
            }
            delete[] data;
        } );
    } );
#endif

    auto iconThread = std::thread( [] {
        iconPx = stbi_load_from_memory( (const stbi_uc*)Icon_data, Icon_size, &iconX, &iconY, nullptr, 4 );
        zigzagPx[0] = stbi_load_from_memory( (const stbi_uc*)ZigZag32_data, ZigZag32_size, &zigzagX[0], &zigzagY[0], nullptr, 4 );
        zigzagPx[1] = stbi_load_from_memory( (const stbi_uc*)ZigZag16_data, ZigZag16_size, &zigzagX[1], &zigzagY[1], nullptr, 4 );
        zigzagPx[2] = stbi_load_from_memory( (const stbi_uc*)ZigZag08_data, ZigZag08_size, &zigzagX[2], &zigzagY[2], nullptr, 4 );
        zigzagPx[3] = stbi_load_from_memory( (const stbi_uc*)ZigZag04_data, ZigZag04_size, &zigzagX[3], &zigzagY[3], nullptr, 4 );
        zigzagPx[4] = stbi_load_from_memory( (const stbi_uc*)ZigZag02_data, ZigZag02_size, &zigzagX[4], &zigzagY[4], nullptr, 4 );
        zigzagPx[5] = stbi_load_from_memory( (const stbi_uc*)ZigZag01_data, ZigZag01_size, &zigzagX[5], &zigzagY[5], nullptr, 4 );
    } );

    tracy::LoadConfig();

    ImGuiTracyContext imguiContext;
    Backend backend( title, DrawContents, ScaleChanged, IsBusy, &mainThreadTasks );
    tracy::InitTexture();
    iconTex = tracy::MakeTexture();
    zigzagTex = tracy::MakeTexture( true );
    iconThread.join();
    backend.SetIcon( iconPx, iconX, iconY );
    bptr = &backend;

    const auto envDpiScale = getenv( "TRACY_DPI_SCALE" );
    if( envDpiScale )
    {
        const auto cnv = atof( envDpiScale );
        if( cnv != 0 )
        {
            dpiScale = cnv;
            dpiScaleOverriddenFromEnv = true;
            SetupDPIScale();
        }
    }

    s_achievements->Achieve( "achievementsIntro" );

    tracy::UpdateTextureRGBAMips( zigzagTex, (void**)zigzagPx, zigzagX, zigzagY, 6 );
    for( auto& v : zigzagPx ) free( v );

    if( initFileOpen )
    {
        view.store( std::make_shared<tracy::View>( RunOnMainThread, *initFileOpen, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release );
        initFileOpen.reset();
    }
    else if( connectTo )
    {
        view.store( std::make_shared<tracy::View>( RunOnMainThread, connectTo, port, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release );
    }

    tracy::Fileselector::Init( backend.HandleType(), backend.Handle() );
    s_isElevated = IsElevated();

    backend.Show();
    backend.Run();

    if( loadThread.joinable() ) loadThread.join();
    if( updateThread.joinable() ) updateThread.join();
    if( updateNotesThread.joinable() ) updateNotesThread.join();
    view.store( nullptr, std::memory_order_release );

    tracy::FreeTexture( zigzagTex, RunOnMainThread );
    tracy::FreeTexture( iconTex, RunOnMainThread );
    free( iconPx );

    tracy::Fileselector::Shutdown();

    return 0;
}

static void UpdateBroadcastClients()
{
    auto ptr = view.load( std::memory_order_acquire );
    if( !ptr )
    {
        const auto time = std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::system_clock::now().time_since_epoch() ).count();
        if( !broadcastListen )
        {
            broadcastListen = std::make_unique<tracy::UdpListen>();
            if( !broadcastListen->Listen( port ) )
            {
                broadcastListen.reset();
            }
        }
        else
        {
            tracy::IpAddress addr;
            size_t len;
            for(;;)
            {
                auto msg = broadcastListen->Read( len, addr, 0 );
                if( !msg ) break;
                auto parsed = tracy::ParseBroadcastMessage( msg, len );
                if( parsed.has_value() )
                {
                    auto& pm = parsed.value();
                    auto address = addr.GetText();
                    const auto ipNumerical = addr.GetNumber();
                    const auto clientId = tracy::ClientUniqueID( addr, pm.listenPort );
                    auto it = clients.find( clientId );
                    if( pm.activeTime >= 0 )
                    {
                        if( it == clients.end() )
                        {
                            std::string ip( address );
                            resolvLock.lock();
                            if( resolvMap.find( ip ) == resolvMap.end() )
                            {
                                resolvMap.emplace( ip, ip );
                                resolv.Query( ipNumerical, [ip] ( std::string&& name ) {
                                    std::lock_guard<std::mutex> lock( resolvLock );
                                    auto it = resolvMap.find( ip );
                                    assert( it != resolvMap.end() );
                                    std::swap( it->second, name );
                                } );
                            }
                            resolvLock.unlock();
                            clients.emplace( clientId, ClientData { time, clientOrder++, pm.protocolVersion, pm.activeTime, pm.listenPort, pm.pid, pm.programName, std::move( ip ) } );
                        }
                        else
                        {
                            it->second.time = time;
                            it->second.activeTime = pm.activeTime;
                            it->second.port = pm.listenPort;
                            it->second.pid = pm.pid;
                            it->second.protocolVersion = pm.protocolVersion;
                            if( strcmp( it->second.procName.c_str(), pm.programName ) != 0 ) it->second.procName = pm.programName;
                        }
                    }
                    else if( it != clients.end() )
                    {
                        clients.erase( it );
                    }
                }
            }
            auto it = clients.begin();
            while( it != clients.end() )
            {
                const auto diff = time - it->second.time;
                if( diff > 4000 )  // 4s
                {
                    it = clients.erase( it );
                }
                else
                {
                    ++it;
                }
            }
        }
    }
    else if( !clients.empty() )
    {
        clients.clear();
    }
}

static void TextComment( const char* str )
{
    ImGui::SameLine();
    ImGui::PushFont( g_fonts.normal, FontSmall );
    ImGui::AlignTextToFramePadding();
    tracy::TextDisabledUnformatted( str );
    ImGui::PopFont();
}

static void DrawAchievements( tracy::data::AchievementItem** items )
{
    while( *items )
    {
        auto& it = *items++;
        if( it->unlockTime > 0 )
        {
            if( it->doneTime > 0 ) ImGui::PushStyleColor( ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled] );
            bool isSelected = s_achievementItem == it;
            if( isSelected )
            {
                if( !it->hideNew ) it->hideNew = true;
                if( !it->hideCompleted && it->doneTime > 0 ) it->hideCompleted = true;
            }
            if( ImGui::Selectable( it->name, isSelected ) )
            {
                s_achievementItem = it;
            }
            if( it->doneTime > 0 ) ImGui::PopStyleColor();
            if( !it->hideNew )
            {
                ImGui::SameLine();
                tracy::TextColoredUnformatted( 0xFF4488FF, ICON_FA_CIRCLE_EXCLAMATION );
            }
            if( !it->hideCompleted && it->doneTime > 0 )
            {
                ImGui::SameLine();
                tracy::TextColoredUnformatted( 0xFF44FF44, ICON_FA_CIRCLE_CHECK );
            }
            if( it->items )
            {
                ImGui::Indent();
                DrawAchievements( it->items );
                ImGui::Unindent();
            }
        }
    }
}

static void DrawContents()
{
    static bool reconnect = false;
    static std::string reconnectAddr;
    static uint16_t reconnectPort;
    static bool showFilter = false;

#ifndef __EMSCRIPTEN__
    UpdateBroadcastClients();
#endif

    int display_w, display_h;
    bptr->NewFrame( display_w, display_h );

    const bool achievementsAttention = tracy::s_config.achievements ? s_achievements->NeedsAttention() : false;

    static int activeFrames = 3;
    auto viewPtr = view.load( std::memory_order_acquire );
    if( tracy::WasActive() || !clients.empty() || ( viewPtr && viewPtr->WasActive() ) || achievementsAttention )
    {
        activeFrames = 3;
    }
    else
    {
        auto ctx = ImGui::GetCurrentContext();
        if( ctx->NavWindowingTarget || ( ctx->DimBgRatio != 0 && ctx->DimBgRatio != 1 ) )
        {
            activeFrames = 3;
        }
        else
        {
            auto& inputQueue = ctx->InputEventsQueue;
            if( !inputQueue.empty() )
            {
                for( auto& v : inputQueue )
                {
                    if( v.Type != ImGuiInputEventType_MouseViewport )
                    {
                        activeFrames = 3;
                        break;
                    }
                }
            }
        }
    }
    if( activeFrames == 0 )
    {
        std::this_thread::sleep_for( std::chrono::milliseconds( 16 ) );
        return;
    }
    activeFrames--;

    ImGui::NewFrame();
    tracy::MouseFrame();

    setlocale( LC_NUMERIC, "C" );

    if( !viewPtr )
    {
        if( s_customTitle )
        {
            s_customTitle = false;
            bptr->SetTitle( title );
        }

        auto& style = ImGui::GetStyle();
        style.Colors[ImGuiCol_WindowBg] = ImVec4( 0.129f, 0.137f, 0.11f, 1.f );
        ImGui::Begin( "Get started", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings );
        char buf[128];
        sprintf( buf, "Tracy Profiler %i.%i.%i", tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch );
        ImGui::PushFont( g_fonts.bold, FontNormal * 1.6f );
        tracy::TextCentered( buf );
        ImGui::PopFont();
        if( dpiChanged == 0 )
        {
            ImGui::SameLine( ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize( ICON_FA_WRENCH ).x );
            if( ImGui::Button( ICON_FA_WRENCH ) )
            {
                ImGui::OpenPopup( "About Tracy" );
            }
        }
        bool keepOpenAbout = true;
        if( ImGui::BeginPopupModal( "About Tracy", &keepOpenAbout, ImGuiWindowFlags_AlwaysAutoResize ) )
        {
            tracy::ImageCentered( iconTex, ImVec2( iconTexSz, iconTexSz ) );
            ImGui::Spacing();
            ImGui::PushFont( g_fonts.bold, FontNormal * 2.f );
            tracy::TextCentered( buf );
            ImGui::Spacing();
            ImGui::PopFont();
            ImGui::PushFont( g_fonts.normal, FontSmall );
            ImGui::PushStyleColor( ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled] );
            tracy::TextCentered( tracy::GitRef );
            ImGui::PopStyleColor();
            ImGui::PopFont();
            if( ImGui::IsItemHovered() )
            {
                ImGui::BeginTooltip();
                ImGui::TextUnformatted( "Click to copy git reference to clipboard" );
                ImGui::EndTooltip();
                if( ImGui::IsItemClicked() )
                {
                    ImGui::SetClipboardText( tracy::GitRef );
                }
            }
#ifndef NDEBUG
            ImGui::PushFont( g_fonts.normal, FontSmall );
            ImGui::PushStyleColor( ImGuiCol_Text, ImVec4( 1.f, 0.5f, 0.5f, 1.f ) );
            tracy::TextCentered( "Debug build" );
            ImGui::PopStyleColor();
            ImGui::PopFont();
#endif
            ImGui::Spacing();
            ImGui::TextUnformatted( "A real time, nanosecond resolution, remote telemetry, hybrid\nframe and sampling profiler for games and other applications." );
            ImGui::Spacing();
            ImGui::TextUnformatted( "Created by Bartosz Taudul" );
            ImGui::SameLine();
            tracy::TextDisabledUnformatted( "<wolf@nereid.pl>" );
            tracy::TextDisabledUnformatted( "Additional authors listed in git history." );
            ImGui::Separator();
            if( ImGui::TreeNode( ICON_FA_TOOLBOX " Global settings" ) )
            {
                s_achievements->Achieve( "globalSettings" );

                ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 0, 0 ) );

                ImGui::TextUnformatted( "Threaded rendering" );
                ImGui::Indent();
                if( ImGui::RadioButton( "Enabled", tracy::s_config.threadedRendering ) ) { tracy::s_config.threadedRendering = true; tracy::SaveConfig(); }
                ImGui::SameLine();
                tracy::DrawHelpMarker( "Uses multiple CPU cores for rendering. May affect performance of the profiled application when running on the same machine." );
                if( ImGui::RadioButton( "Disabled", !tracy::s_config.threadedRendering ) ) { tracy::s_config.threadedRendering = false; tracy::SaveConfig(); }
                ImGui::SameLine();
                tracy::DrawHelpMarker( "Restricts rendering to a single CPU core. Can reduce profiler frame rate." );
                ImGui::Unindent();

                ImGui::Spacing();
                if( ImGui::Checkbox( "Reduce render rate when focus is lost", &tracy::s_config.focusLostLimit ) ) tracy::SaveConfig();

                ImGui::Spacing();
                ImGui::TextUnformatted( "Target FPS" );
                ImGui::SameLine();
                tracy::DrawHelpMarker( "The default target frame rate for your application. Frames displayed in the frame time graph will be colored accordingly if they are within the target frame rate. This can be adjusted later for each individual trace." );
                ImGui::SameLine();
                int tmp = tracy::s_config.targetFps;
                ImGui::SetNextItemWidth( 90 * dpiScale );
                if( ImGui::InputInt( "##targetfps", &tmp ) ) { tracy::s_config.targetFps = std::clamp( tmp, 1, 9999 ); tracy::SaveConfig(); }

                ImGui::Spacing();
                ImGui::TextUnformatted( ICON_FA_PALETTE " Zone colors" );
                ImGui::SameLine();
                tracy::SmallCheckbox( "Ignore custom", &tracy::s_config.forceColors );
                ImGui::Indent();
                ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 0, 0 ) );
                ImGui::RadioButton( "Static", &tracy::s_config.dynamicColors, 0 );
                ImGui::RadioButton( "Thread dynamic", &tracy::s_config.dynamicColors, 1 );
                ImGui::RadioButton( "Source location dynamic", &tracy::s_config.dynamicColors, 2 );
                ImGui::PopStyleVar();
                ImGui::Unindent();
                ImGui::TextUnformatted( ICON_FA_RULER_HORIZONTAL " Zone name shortening" );
                ImGui::Indent();
                ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 0, 0 ) );
                ImGui::RadioButton( "Disabled##zns", &tracy::s_config.shortenName, (uint8_t)tracy::ShortenName::Never );
                ImGui::RadioButton( "Minimal length", &tracy::s_config.shortenName, (uint8_t)tracy::ShortenName::Always );
                ImGui::RadioButton( "Only normalize", &tracy::s_config.shortenName, (uint8_t)tracy::ShortenName::OnlyNormalize );
                ImGui::RadioButton( "As needed", &tracy::s_config.shortenName, (uint8_t)tracy::ShortenName::NoSpace );
                ImGui::RadioButton( "As needed + normalize", &tracy::s_config.shortenName, (uint8_t)tracy::ShortenName::NoSpaceAndNormalize );
                ImGui::PopStyleVar();
                ImGui::Unindent();

                ImGui::Spacing();
                ImGui::TextUnformatted( "Scroll multipliers" );
                ImGui::SameLine();
                tracy::DrawHelpMarker( "The multipliers to the amount to scroll by horizontally and vertically. This is used in the timeline and setting this value can help compensate for scroll wheel sensitivity." );
                ImGui::SameLine();
                double tmpScroll = tracy::s_config.horizontalScrollMultiplier;
                ImGui::SetNextItemWidth( 45 * dpiScale );
                if( ImGui::InputDouble( "##horizontalscrollmultiplier", &tmpScroll ) ) { tracy::s_config.horizontalScrollMultiplier = std::max( tmpScroll, 0.01 ); tracy::SaveConfig(); }
                tmpScroll = tracy::s_config.verticalScrollMultiplier;
                ImGui::SameLine();
                ImGui::SetNextItemWidth( 45 * dpiScale );
                if( ImGui::InputDouble( "##verticalscrollmultiplier", &tmpScroll ) ) { tracy::s_config.verticalScrollMultiplier = std::max( tmpScroll, 0.01 ); tracy::SaveConfig(); }

                if( s_totalMem == 0 )
                {
                    ImGui::BeginDisabled();
                    tracy::s_config.memoryLimit = false;
                }

                ImGui::Spacing();
                if( ImGui::Checkbox( "Memory limit", &tracy::s_config.memoryLimit ) ) tracy::SaveConfig();
                ImGui::SameLine();
                tracy::DrawHelpMarker( "When enabled, profiler will stop recording data when memory usage exceeds the specified percentage of available memory. Values greater than 100% will rely on swap. You need to make sure that memory is actually available." );
                ImGui::SameLine();
                ImGui::SetNextItemWidth( 70 * dpiScale );
                if( ImGui::InputInt( "##memorylimit", &tracy::s_config.memoryLimitPercent ) ) { tracy::s_config.memoryLimitPercent = std::clamp( tracy::s_config.memoryLimitPercent, 1, 999 ); tracy::SaveConfig(); }
                ImGui::SameLine();
                ImGui::TextUnformatted( "%" );
                if( s_totalMem != 0 )
                {
                    ImGui::SameLine();
                    ImGui::TextDisabled( "(%s)", tracy::MemSizeToString( s_totalMem * tracy::s_config.memoryLimitPercent / 100 ) );
                }
                else
                {
                    ImGui::EndDisabled();
                }

                ImGui::Spacing();
                if( ImGui::Checkbox( "Enable achievements", &tracy::s_config.achievements ) ) tracy::SaveConfig();
                ImGui::Spacing();
                if( ImGui::Checkbox( "Save UI scale", &tracy::s_config.saveUserScale) ) tracy::SaveConfig();
#ifndef __EMSCRIPTEN__
                ImGui::Spacing();
                if( ImGui::Checkbox( "Enable Tracy Assist", &tracy::s_config.llm ) ) tracy::SaveConfig();
#endif

                ImGui::PopStyleVar();
                ImGui::TreePop();
            }
            ImGui::Separator();
            ImGui::PushFont( g_fonts.normal, FontSmall );
            tracy::TextFocused( "Protocol version", tracy::RealToString( tracy::ProtocolVersion ) );
            ImGui::SameLine();
            ImGui::SeparatorEx( ImGuiSeparatorFlags_Vertical );
            ImGui::SameLine();
            tracy::TextFocused( "Broadcast version", tracy::RealToString( tracy::BroadcastVersion ) );
            ImGui::SameLine();
            ImGui::SeparatorEx( ImGuiSeparatorFlags_Vertical );
            ImGui::SameLine();
            tracy::TextFocused( "Build date", __DATE__ ", " __TIME__ );
            ImGui::PopFont();
            ImGui::EndPopup();
        }
        ImGui::Spacing();
        if( ImGui::Button( ICON_FA_BOOK " Manual" ) )
        {
            tracy::OpenWebpage( "https://github.com/wolfpld/tracy/releases" );
        }
        ImGui::SameLine();
        if( ImGui::Button( ICON_FA_EARTH_AMERICAS " Web" ) )
        {
            ImGui::OpenPopup( "web" );
        }
        if( ImGui::BeginPopup( "web" ) )
        {
            if( ImGui::Selectable( ICON_FA_HOUSE_CHIMNEY " Tracy Profiler home page" ) )
            {
                tracy::OpenWebpage( "https://github.com/wolfpld/tracy" );
            }
            ImGui::Separator();
            if( ImGui::Selectable( ICON_FA_VIDEO " Performance profiling Mutter, GNOME Shell & apps with Tracy – Ivan Molodetskikh – GUADEC 2026" ) )
            {
                tracy::OpenWebpage( "https://youtu.be/KFp3iCdlP7c" );
            }
            if( ImGui::Selectable( ICON_FA_VIDEO " An Introduction to Tracy Profiler in C++ – Marcos Slomp – CppCon 2023" ) )
            {
                tracy::OpenWebpage( "https://youtu.be/ghXk3Bk5F2U?t=37" );
            }
            ImGui::Separator();
            if( ImGui::Selectable( ICON_FA_VIDEO " New features in v0.8" ) )
            {
                tracy::OpenWebpage( "https://www.youtube.com/watch?v=30wpRpHTTag" );
            }
            TextComment( "2022-03-28" );
            if( ImGui::Selectable( ICON_FA_VIDEO " New features in v0.7" ) )
            {
                tracy::OpenWebpage( "https://www.youtube.com/watch?v=_hU7vw00MZ4" );
            }
            TextComment( "2020-06-11" );
            if( ImGui::Selectable( ICON_FA_VIDEO " New features in v0.6" ) )
            {
                tracy::OpenWebpage( "https://www.youtube.com/watch?v=uJkrFgriuOo" );
            }
            TextComment( "2019-11-17" );
            if( ImGui::Selectable( ICON_FA_VIDEO " New features in v0.5" ) )
            {
                tracy::OpenWebpage( "https://www.youtube.com/watch?v=P6E7qLMmzTQ" );
            }
            TextComment( "2019-08-10" );
            if( ImGui::Selectable( ICON_FA_VIDEO " New features in v0.4" ) )
            {
                tracy::OpenWebpage( "https://www.youtube.com/watch?v=eAkgkaO8B9o" );
            }
            TextComment( "2018-10-09" );
            if( ImGui::Selectable( ICON_FA_VIDEO " New features in v0.3" ) )
            {
                tracy::OpenWebpage( "https://www.youtube.com/watch?v=3SXpDpDh2Uo" );
            }
            TextComment( "2018-07-03" );
            if( ImGui::Selectable( ICON_FA_VIDEO " Overview of v0.2" ) )
            {
                tracy::OpenWebpage( "https://www.youtube.com/watch?v=fB5B46lbapc" );
            }
            TextComment( "2018-03-25" );
            ImGui::EndPopup();
        }
        ImGui::SameLine();
        if( ImGui::Button( ICON_FA_COMMENTS " Chat" ) )
        {
            tracy::OpenWebpage( "https://discord.gg/pk78auc" );
        }
        ImGui::SameLine();
        if( ImGui::Button( ICON_FA_HEART " Sponsor" ) )
        {
            tracy::OpenWebpage( "https://github.com/sponsors/wolfpld/" );
        }
        if( updateVersion > tracy::FileVersion( tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch ) )
        {
            ImGui::Separator();
            ImGui::TextColored( ImVec4( 1, 1, 0, 1 ), ICON_FA_EXCLAMATION " Update to %i.%i.%i is available!", ( updateVersion >> 16 ) & 0xFF, ( updateVersion >> 8 ) & 0xFF, updateVersion & 0xFF );
            ImGui::SameLine();
            if( ImGui::SmallButton( ICON_FA_GIFT " Get it!" ) )
            {
                showReleaseNotes = true;
                if( !updateNotesThread.joinable() )
                {
                    updateNotesThread = std::thread( [] {
                        HttpRequest( "nereid.pl", "/tracy/notes", 8099, [] ( int size, char* data ) {
                            std::string notes( data, data+size );
                            delete[] data;
                            RunOnMainThread( [notes = std::move( notes )] () mutable { releaseNotes = std::move( notes ); tracy::s_wasActive = true; } );
                        } );
                    } );
                }
            }
        }
        if( s_isElevated )
        {
            ImGui::Separator();
            ImGui::PushStyleColor( ImGuiCol_Text, ImVec4( 1.f, 0.25f, 0.25f, 1.f ) );
            tracy::TextCentered( ICON_FA_TRIANGLE_EXCLAMATION " Profiler has elevated privileges! " ICON_FA_TRIANGLE_EXCLAMATION );
            ImGui::PushFont( g_fonts.normal, FontSmall );
            tracy::TextCentered( "You are running the profiler interface with admin privileges. This is" );
            tracy::TextCentered( "most likely a mistake, as there is no reason to do so. Instead, you" );
            tracy::TextCentered( "probably wanted to run the client (the application you are profiling)" );
            tracy::TextCentered( "with elevated privileges." );
            ImGui::PopFont();
            ImGui::PopStyleColor();
        }
        ImGui::Separator();
        ImGui::TextUnformatted( "Client address" );
        bool connectClicked = false;
        connectClicked |= ImGui::InputTextWithHint( "###connectaddress", "Enter address", addr, 1024, ImGuiInputTextFlags_EnterReturnsTrue );
        if( !connHist->empty() )
        {
            ImGui::SameLine();
            if( ImGui::BeginCombo( "##frameCombo", nullptr, ImGuiComboFlags_NoPreview ) )
            {
                int idxRemove = -1;
                const auto sz = std::min<size_t>( 5, connHist->size() );
                for( size_t i=0; i<sz; i++ )
                {
                    const auto& str = connHist->Name( i );
                    if( ImGui::Selectable( str.c_str() ) )
                    {
                        memcpy( addr, str.c_str(), str.size() + 1 );
                    }
                    if( ImGui::IsItemHovered() && ImGui::IsKeyPressed( ImGuiKey_Delete, false ) )
                    {
                        idxRemove = (int)i;
                    }
                }
                if( idxRemove >= 0 )
                {
                    connHist->Erase( idxRemove );
                }
                ImGui::EndCombo();
            }
        }
#ifdef __EMSCRIPTEN__
        ImGui::BeginDisabled();
#endif
        connectClicked |= ImGui::Button( ICON_FA_WIFI " Connect" );
#ifdef __EMSCRIPTEN__
        ImGui::EndDisabled();
        connectClicked = false;
#endif
        if( connectClicked && *addr && !loadThread.joinable() )
        {
            auto aptr = addr;
            while( *aptr == ' ' || *aptr == '\t' ) aptr++;
            auto aend = aptr;
            while( *aend && *aend != ' ' && *aend != '\t' ) aend++;

            if( aptr != aend )
            {
                std::string address( aptr, aend );
                connHist->Count( address );

                auto adata = address.data();
                auto ptr = adata + address.size() - 1;
                while( ptr > adata && *ptr != ':' ) ptr--;
                if( *ptr == ':' )
                {
                    std::string addrPart = std::string( adata, ptr );
                    uint16_t portPart = (uint16_t)atoi( ptr+1 );
                    view.store( std::make_shared<tracy::View>( RunOnMainThread, addrPart.c_str(), portPart, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release );
                }
                else
                {
                    view.store( std::make_shared<tracy::View>( RunOnMainThread, address.c_str(), port, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release );
                }
            }
        }
        if( tracy::s_config.memoryLimit )
        {
            ImGui::SameLine();
            tracy::TextColoredUnformatted( 0xFF00FFFF, ICON_FA_TRIANGLE_EXCLAMATION );
            tracy::TooltipIfHovered( "Memory limit is active" );
        }
        ImGui::SameLine( 0, ImGui::GetTextLineHeight() * 2 );

#ifndef TRACY_NO_FILESELECTOR
        if( ImGui::Button( ICON_FA_FOLDER_OPEN " Open saved trace" ) && !loadThread.joinable() )
        {
            tracy::Fileselector::OpenFile( "tracy", "Tracy Profiler trace file", []( const char* fn ) {
                try
                {
                    auto f = std::shared_ptr<tracy::FileRead>( tracy::FileRead::Open( fn ) );
                    if( f )
                    {
                        loadThread = std::thread( [f] {
                            try
                            {
                                view.store( std::make_shared<tracy::View>( RunOnMainThread, *f, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release );
                            }
                            catch( const tracy::UnsupportedVersion& e )
                            {
                                badVer.state = tracy::BadVersionState::UnsupportedVersion;
                                badVer.version = e.version;
                            }
                            catch( const tracy::LegacyVersion& e )
                            {
                                badVer.state = tracy::BadVersionState::LegacyVersion;
                                badVer.version = e.version;
                            }
                            catch( const tracy::LoadFailure& e )
                            {
                                badVer.state = tracy::BadVersionState::LoadFailure;
                                badVer.msg = e.msg;
                            }
                        } );
                    }
                }
                catch( const tracy::NotTracyDump& )
                {
                    badVer.state = tracy::BadVersionState::BadFile;
                }
                catch( const tracy::FileReadError& )
                {
                    badVer.state = tracy::BadVersionState::ReadError;
                }
            } );
        }
#endif

        if( badVer.state != tracy::BadVersionState::Ok )
        {
            if( loadThread.joinable() ) { loadThread.join(); }
            tracy::BadVersion( badVer );
        }

        if( !clients.empty() )
        {
            ImGui::Separator();
            ImGui::TextUnformatted( "Discovered clients:" );
            ImGui::SameLine();
            tracy::SmallToggleButton( ICON_FA_FILTER " Filter", showFilter );
            if( filt->IsActive() )
            {
                ImGui::SameLine();
                tracy::TextColoredUnformatted( 0xFF00FFFF, ICON_FA_TRIANGLE_EXCLAMATION );
                tracy::TooltipIfHovered( "Filters are active" );
                if( showFilter )
                {
                    ImGui::SameLine();
                    if( ImGui::SmallButton( ICON_FA_DELETE_LEFT " Clear" ) )
                    {
                        filt->Clear();
                    }
                }
            }
            if( showFilter )
            {
                const auto w = ImGui::GetTextLineHeight() * 12;
                ImGui::Separator();
                filt->Draw( w );
            }
            ImGui::Separator();
            static bool widthSet = false;
            ImGui::Columns( 3 );
            if( !widthSet )
            {
                widthSet = true;
                const auto w = ImGui::GetWindowWidth();
                ImGui::SetColumnWidth( 0, w * 0.35f );
                ImGui::SetColumnWidth( 1, w * 0.175f );
                ImGui::SetColumnWidth( 2, w * 0.425f );
            }
            const auto time = std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::system_clock::now().time_since_epoch() ).count();
            int idx = 0;
            int passed = 0;
            std::vector<const ClientData*> sorted;
            sorted.reserve( clients.size() );
            for( auto& v : clients ) sorted.emplace_back( &v.second );
            tracy::pdqsort( sorted.begin(), sorted.end(), []( const ClientData* l, const ClientData* r ) { return l->order < r->order; } );
            std::lock_guard<std::mutex> lock( resolvLock );
            for( auto v : sorted )
            {
                const bool badProto = v->protocolVersion != tracy::ProtocolVersion;
                bool sel = false;
                const auto& name = resolvMap.find( v->address );
                assert( name != resolvMap.end() );
                if( filt->FailAddr( name->second.c_str() ) && filt->FailAddr( v->address.c_str() ) ) continue;
                if( filt->FailPort( v->port ) ) continue;
                if( filt->FailProg( v->procName.c_str() ) ) continue;
                ImGuiSelectableFlags flags = ImGuiSelectableFlags_SpanAllColumns;
                if( badProto ) flags |= ImGuiSelectableFlags_Disabled;
                ImGui::PushID( idx++ );
                const bool selected = ImGui::Selectable( name->second.c_str(), &sel, flags );
                ImGui::PopID();
                if( ImGui::IsItemHovered( ImGuiHoveredFlags_AllowWhenDisabled ) )
                {
                    char portstr[32];
                    sprintf( portstr, "%" PRIu16, v->port );
                    ImGui::BeginTooltip();
                    if( badProto )
                    {
                        tracy::TextColoredUnformatted( 0xFF0000FF, "Incompatible protocol!" );
                        ImGui::SameLine();
                        auto ph = tracy::ProtocolHistory;
                        ImGui::TextDisabled( "(used: %i, required: %i)", v->protocolVersion, tracy::ProtocolVersion );
                        while( ph->protocol && ph->protocol != v->protocolVersion ) ph++;
                        if( ph->protocol )
                        {
                            if( ph->maxVer )
                            {
                                ImGui::TextDisabled( "Compatible Tracy versions: %i.%i.%i to %i.%i.%i", ph->minVer >> 16, ( ph->minVer >> 8 ) & 0xFF, ph->minVer & 0xFF, ph->maxVer >> 16, ( ph->maxVer >> 8 ) & 0xFF, ph->maxVer & 0xFF );
                            }
                            else
                            {
                                ImGui::TextDisabled( "Compatible Tracy version: %i.%i.%i", ph->minVer >> 16, ( ph->minVer >> 8 ) & 0xFF, ph->minVer & 0xFF );
                            }
                        }
                        ImGui::Separator();
                    }
                    tracy::TextFocused( "IP:", v->address.c_str() );
                    tracy::TextFocused( "Port:", portstr );
                    if( v->pid != 0 )
                    {
                        tracy::TextFocused( "PID:", tracy::RealToString( v->pid ) );
                    }
                    ImGui::EndTooltip();
                }
                if( v->port != port )
                {
                    ImGui::SameLine();
                    ImGui::TextDisabled( ":%" PRIu16, v->port );
                }
                if( selected && !loadThread.joinable() )
                {
                    view.store( std::make_shared<tracy::View>( RunOnMainThread, v->address.c_str(), v->port, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release );
                }
                ImGui::NextColumn();
                const auto acttime = ( v->activeTime + ( time - v->time ) / 1000 ) * 1000000000ll;
                if( badProto )
                {
                    tracy::TextDisabledUnformatted( tracy::TimeToString( acttime ) );
                }
                else
                {
                    ImGui::TextUnformatted( tracy::TimeToString( acttime ) );
                }
                ImGui::NextColumn();
                if( badProto )
                {
                    tracy::TextDisabledUnformatted( v->procName.c_str() );
                }
                else
                {
                    ImGui::TextUnformatted( v->procName.c_str() );
                }
                ImGui::NextColumn();
                passed++;
            }
            ImGui::EndColumns();
            if( passed == 0 )
            {
                ImGui::TextUnformatted( "All clients are filtered." );
            }
        }
        ImGui::End();

        if( showReleaseNotes )
        {
            assert( updateNotesThread.joinable() );
            ImGui::SetNextWindowSize( ImVec2( 600 * dpiScale, 400 * dpiScale ), ImGuiCond_FirstUseEver );
            ImGui::Begin( "Update available!", &showReleaseNotes );
            if( ImGui::Button( ICON_FA_DOWNLOAD " Download" ) )
            {
                tracy::OpenWebpage( "https://github.com/wolfpld/tracy/releases" );
            }
            ImGui::BeginChild( "###notes", ImVec2( 0, 0 ), true );
            if( releaseNotes.empty() )
            {
                static float rnTime = 0;
                rnTime += ImGui::GetIO().DeltaTime;
                tracy::TextCentered( "Fetching release notes…" );
                tracy::DrawWaitingDotsCentered( rnTime );
            }
            else
            {
                ImGui::PushFont( g_fonts.mono, FontNormal );
                ImGui::TextUnformatted( releaseNotes.c_str() );
                ImGui::PopFont();
            }
            ImGui::EndChild();
            ImGui::End();
        }
    }
    else
    {
        if( showReleaseNotes ) showReleaseNotes = false;
        if( broadcastListen )
        {
            broadcastListen.reset();
            clients.clear();
        }
        if( loadThread.joinable() ) loadThread.join();
        viewPtr->NotifyRootWindowSize( display_w, display_h );
        if( !viewPtr->Draw() )
        {
            viewShutdown.store( ViewShutdown::True, std::memory_order_relaxed );
            reconnect = viewPtr->ReconnectRequested();
            if( reconnect )
            {
                reconnectAddr = viewPtr->GetAddress();
                reconnectPort = viewPtr->GetPort();
            }

            view.store( nullptr, std::memory_order_release );
            loadThread = std::thread( [view = std::move( viewPtr )] () mutable {
                view.reset();
                viewShutdown.store( ViewShutdown::Join, std::memory_order_relaxed );
            } );
        }
    }
    auto& progress = tracy::Worker::GetLoadProgress();
    auto totalProgress = progress.total.load( std::memory_order_relaxed );
    if( totalProgress != 0 )
    {
        ImGui::OpenPopup( "Loading trace…" );
    }
    if( ImGui::BeginPopupModal( "Loading trace…", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings) )
    {
        ImGui::PushFont( g_fonts.normal, FontNormal * 2.f );
        ImGui::Spacing();
        tracy::TextCentered( ICON_FA_HOURGLASS_HALF );
        ImGui::Spacing();
        ImGui::PopFont();

        animTime += ImGui::GetIO().DeltaTime;
        tracy::DrawWaitingDotsCentered( animTime );

        auto currProgress = progress.progress.load( std::memory_order_relaxed );
        if( totalProgress == 0 )
        {
            ImGui::CloseCurrentPopup();
            totalProgress = currProgress;
        }
        switch( currProgress )
        {
        case tracy::LoadProgress::Initialization:
            ImGui::TextUnformatted( "Initialization…" );
            break;
        case tracy::LoadProgress::Locks:
            ImGui::TextUnformatted( "Locks…" );
            break;
        case tracy::LoadProgress::Messages:
            ImGui::TextUnformatted( "Messages…" );
            break;
        case tracy::LoadProgress::Zones:
            ImGui::TextUnformatted( "CPU zones…" );
            break;
        case tracy::LoadProgress::GpuZones:
            ImGui::TextUnformatted( "GPU zones…" );
            break;
        case tracy::LoadProgress::Plots:
            ImGui::TextUnformatted( "Plots…" );
            break;
        case tracy::LoadProgress::Memory:
            ImGui::TextUnformatted( "Memory…" );
            break;
        case tracy::LoadProgress::CallStacks:
            ImGui::TextUnformatted( "Call stacks…" );
            break;
        case tracy::LoadProgress::FrameImages:
            ImGui::TextUnformatted( "Frame images…" );
            break;
        case tracy::LoadProgress::ContextSwitches:
            ImGui::TextUnformatted( "Context switches…" );
            break;
        case tracy::LoadProgress::ContextSwitchesPerCpu:
            ImGui::TextUnformatted( "CPU context switches…" );
            break;
        default:
            assert( false );
            break;
        }
        ImGui::ProgressBar( float( currProgress ) / totalProgress, ImVec2( 200 * dpiScale, 0 ) );

        ImGui::TextUnformatted( "Progress…" );
        auto subTotal = progress.subTotal.load( std::memory_order_relaxed );
        auto subProgress = progress.subProgress.load( std::memory_order_relaxed );
        if( subTotal == 0 )
        {
            ImGui::ProgressBar( 1.f, ImVec2( 200 * dpiScale, 0 ) );
        }
        else
        {
            ImGui::ProgressBar( float( subProgress ) / subTotal, ImVec2( 200 * dpiScale, 0 ) );
        }
        ImGui::EndPopup();
    }
    switch( viewShutdown.load( std::memory_order_relaxed ) )
    {
    case ViewShutdown::True:
        ImGui::OpenPopup( "Capture cleanup…" );
        break;
    case ViewShutdown::Join:
        loadThread.join();
        viewShutdown.store( ViewShutdown::False, std::memory_order_relaxed );
        if( reconnect )
        {
            view.store( std::make_unique<tracy::View>( RunOnMainThread, reconnectAddr.c_str(), reconnectPort, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release );
        }
        break;
    default:
        break;
    }
    if( ImGui::BeginPopupModal( "Capture cleanup…", nullptr, ImGuiWindowFlags_AlwaysAutoResize ) )
    {
        if( viewShutdown.load( std::memory_order_relaxed ) != ViewShutdown::True ) ImGui::CloseCurrentPopup();
        ImGui::PushFont( g_fonts.normal, FontNormal * 2.f );
        ImGui::Spacing();
        tracy::TextCentered( ICON_FA_BROOM );
        ImGui::Spacing();
        ImGui::PopFont();
        animTime += ImGui::GetIO().DeltaTime;
        tracy::DrawWaitingDotsCentered( animTime );
        ImGui::TextUnformatted( "Please wait, cleanup is in progress" );
        ImGui::EndPopup();
    }

    if( tracy::Fileselector::HasFailed() ) ImGui::OpenPopup( "File selector is not available" );
    if( ImGui::BeginPopupModal( "File selector is not available", nullptr, ImGuiWindowFlags_AlwaysAutoResize ) )
    {
        ImGui::TextUnformatted( "File selector cannot be displayed." );
        ImGui::TextUnformatted( "Check nfd library implementation for details." );
        ImGui::Separator();
        if( ImGui::Button( "Ok" ) ) ImGui::CloseCurrentPopup();
        ImGui::EndPopup();
    }

#ifndef __EMSCRIPTEN__
    if( !tracy::s_config.achievementsAsked )
    {
        tracy::s_config.achievementsAsked = true;
        ImGui::OpenPopup( ICON_FA_STAR " Achievements" );
    }
#endif

    ImGui::SetNextWindowSize( ImVec2( 325 * dpiScale, 0 ) );
    if( ImGui::BeginPopupModal( ICON_FA_STAR " Achievements", nullptr, ImGuiWindowFlags_AlwaysAutoResize ) )
    {
        constexpr const char* text = R"(
**Tracy Profiler** is a complex tool with many features. It can be difficult to discover all of them on your own.

The *Achievements* system will guide you through the main features and teach you how to use them in an easy-to-handle manner. You can use this system as a **tutorial** to learn how to use Tracy Profiler.

---

Would you like to enable achievements?
)";

        tracy::Markdown md( nullptr, nullptr );
        md.Print( text, strlen( text ) );
        ImGui::Spacing();
        ImGui::PushFont( g_fonts.normal, FontSmall );
        tracy::TextDisabledUnformatted( "You can change this setting later in the global settings." );
        ImGui::PopFont();
        ImGui::Separator();
        if( ImGui::Button( "Yes" ) )
        {
            tracy::s_config.achievements = true;
            tracy::SaveConfig();
            ImGui::CloseCurrentPopup();
        }
        ImGui::SameLine();
        if( ImGui::Button( "No" ) )
        {
            tracy::s_config.achievements = false;
            tracy::SaveConfig();
            ImGui::CloseCurrentPopup();
        }
        ImGui::EndPopup();
    }

    if( tracy::s_config.achievements )
    {
        ImGui::PushStyleVar( ImGuiStyleVar_WindowRounding, 16 * dpiScale );

        ImGui::PushFont( g_fonts.normal, FontBig );
        const auto starSize = ImGui::CalcTextSize( ICON_FA_STAR );
        ImGui::PopFont();

        static int animStage = 0;
        static float animProgress = 0;
        static bool showAchievements = false;
        static float openTimeLeft = 0;

        float aSize = 0;
        const auto aItem = s_achievements->GetNextQueue();

        if( aItem )
        {
            aSize = ImGui::CalcTextSize( aItem->name ).x + ImGui::GetStyle().ItemSpacing.x + ImGui::GetStyle().WindowPadding.x * 0.5f;
            if( animStage == 0 )
            {
                animStage = 1;
            }
        }

        if( animStage > 0 )
        {
            tracy::s_wasActive = true;

            if( animStage == 1 )
            {
                animProgress = std::min( animProgress + ImGui::GetIO().DeltaTime / 0.3f, 1.f );
                if( animProgress == 1 )
                {
                    animStage = 2;
                    openTimeLeft = 8;
                }
                tracy::s_wasActive = true;
            }
            else if( animStage == 3 )
            {
                animProgress = std::max( animProgress - ImGui::GetIO().DeltaTime / 0.3f, 0.f );
                if( animProgress == 0 )
                {
                    s_achievements->PopQueue();
                    animStage = 0;
                }
            }
        }

        ImGui::SetNextWindowPos( ImVec2( display_w - starSize.x - ImGui::GetStyle().WindowPadding.x * 1.5f - aSize * smoothstep( animProgress ), display_h - starSize.y * 2 - ImGui::GetStyle().WindowPadding.y * 2 ) );
        ImGui::SetNextWindowSize( ImVec2( starSize.x + aSize + 100, starSize.y + ImGui::GetStyle().WindowPadding.y * 2 ) );
        ImGui::Begin( "###achievements", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_Tooltip );

        const auto cursor = ImGui::GetCursorPos();
        const auto cursorScreen = ImGui::GetCursorScreenPos();
        uint32_t color = 0xFF888888;
        if( achievementsAttention && !showAchievements )
        {
            const auto t = sin( ImGui::GetTime() * 4 ) * 0.5f + 0.5f;
            const auto c0 = uint32_t( std::lerp( 0x88, 0x00, t ) );
            const auto c1 = uint32_t( std::lerp( 0x88, 0xFF, t ) );
            color = 0xFF000000 | ( c0 << 16 ) | ( c1 << 8 ) | c1;
        }
        if( ( animStage == 0 || animStage == 2 ) && ImGui::IsMouseHoveringRect( cursorScreen - ImVec2( dpiScale * 2, dpiScale * 2 ), cursorScreen + starSize + ImVec2( dpiScale * 4, dpiScale * 4 ) ) )
        {
            color = 0xFFFFFFFF;
            if( ImGui::IsMouseClicked( ImGuiMouseButton_Left ) )
            {
                if( animStage == 0 )
                {
                    showAchievements = !showAchievements;
                }
                else
                {
                    showAchievements = true;
                    animStage = 3;
                    s_achievementItem = aItem;
                }
            }
        }
        ImGui::PushFont( g_fonts.normal, FontBig );
        tracy::TextColoredUnformatted( color, ICON_FA_STAR );
        ImGui::PopFont();

        if( aItem )
        {
            ImGui::SameLine();
            const auto dismiss = ImGui::GetCursorScreenPos();
            const auto th = ImGui::GetTextLineHeight();
            ImGui::SetCursorPosY( cursor.y - th * 0.175f );
            ImGui::TextUnformatted( aItem->name );
            ImGui::PushFont( g_fonts.normal, FontSmall );
            ImGui::SetCursorPos( cursor + ImVec2( starSize.x + ImGui::GetStyle().ItemSpacing.x, th ) );
            tracy::TextDisabledUnformatted( "Click to open" );
            ImGui::PopFont();
            if( animStage == 2 )
            {
                if( ImGui::IsMouseHoveringRect( dismiss - ImVec2( 0, dpiScale * 6 ), dismiss + ImVec2( aSize, th * 1.5f + dpiScale * 4 ) ) && ImGui::IsMouseClicked( ImGuiMouseButton_Left ) )
                {
                    s_achievementItem = aItem;
                    s_switchAchievementCategory = true;
                    showAchievements = true;
                    animStage = 3;
                }
                if( !aItem->keepOpen )
                {
                    openTimeLeft -= ImGui::GetIO().DeltaTime;
                    if( openTimeLeft < 0 ) animStage = 3;
                }
            }
        }

        ImGui::End();
        ImGui::PopStyleVar();

        if( showAchievements )
        {
            const tracy::data::AchievementCategory* targetCategory = nullptr;
            if( s_switchAchievementCategory )
            {
                s_switchAchievementCategory = false;
                assert( s_achievementItem );
                targetCategory = s_achievements->GetCategoryForAchievement( s_achievementItem->id );
            }

            ImGui::SetNextWindowSize( ImVec2( 700 * dpiScale, 450 * dpiScale ), ImGuiCond_FirstUseEver );
            ImGui::Begin( "Achievements List", &showAchievements, ImGuiWindowFlags_NoDocking );
            ImGui::BeginTabBar( "###categories" );
            auto categories = s_achievements->GetCategories();
            while( *categories )
            {
                auto& c = *categories++;
                if( c->unlockTime > 0 )
                {
                    char tmp[256];
                    if( s_achievements->CategoryNeedsAttention( c->id ) )
                    {
                        snprintf( tmp, 256, ICON_FA_CIRCLE_EXCLAMATION " %s###%s", c->name, c->id );
                    }
                    else
                    {
                        snprintf( tmp, 256, "%s###%s", c->name, c->id );
                    }
                    ImGuiTabItemFlags flags = 0;
                    if( targetCategory == c ) flags |= ImGuiTabItemFlags_SetSelected;
                    if( ImGui::BeginTabItem( tmp, nullptr, flags ) )
                    {
                        ImGui::Columns( 2 );
                        ImGui::SetColumnWidth( 0, 300 * dpiScale );
                        ImGui::BeginChild( "##achievementtoc", ImVec2( 0, 0 ), ImGuiChildFlags_AlwaysUseWindowPadding );
                        DrawAchievements( c->items );
                        ImGui::EndChild();
                        ImGui::NextColumn();
                        ImGui::BeginChild( "##achievementtext", ImVec2( 0, 0 ), ImGuiChildFlags_AlwaysUseWindowPadding );
                        if( s_achievementItem )
                        {
                            tracy::Markdown md( nullptr, nullptr );
                            md.Print( s_achievementItem->text.c_str(), s_achievementItem->text.size() );
                        }
                        ImGui::EndChild();
                        ImGui::EndColumns();
                        ImGui::EndTabItem();
                    }
                }
            }
            ImGui::EndTabBar();
            ImGui::End();
        }
    }

    bptr->EndFrame();
    if( dpiChanged > 0 ) dpiChanged--;
}
