// ==========================================================
// Poe2VisualTool — Standalone Visual Mod Engine & HUD Overlay
// Zoom trần 8x (con lăn mượt) + xóa sương mù + Shader Patches
// C++23 Native — Độc lập 100% với Bot Core. Cần quyền Admin (RPM).
//
//   Poe2VisualTool.exe [--max-zoom 8] [--no-fog] [--wheel] [--no-overlay] [--prompt-path]
// ==========================================================

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif

#include <windows.h>
#include <shellapi.h>
#include <timeapi.h>
#pragma comment(lib, "winmm.lib")

#include "memory/memory_reader.hpp"
#include "memory/visual_mods.hpp"
#include "memory/offsets_store.hpp"
#include "common/protocol.hpp"
#include "common/window_utils.hpp"
#include "common/string_utils.hpp"
#include "common/toml_parser.hpp"
#include "visual/world_to_screen.hpp"
#include "visual/poi_radar.hpp"
#include "visual/mod_colorizer.hpp"
#include "visual/particle_reducer.hpp"
#include "security/hwid_generator.hpp"
#include "security/license_validator.hpp"
#include "platform/cloud_sync.hpp"
#include "platform/sound_effects.hpp"
#include "platform/i18n.hpp"
#include "inspector/hover_watcher.hpp"
#include "inspector/item_parser.hpp"
#include "inspector/waystone_rules.hpp"
#include "native_ui/ui_types.hpp"
#include "native_ui/tooltip_view.hpp"
#include "native_ui/radar_hud.hpp"
#include "native_ui/settings_panel.hpp"
#include "native_ui/overlay_window.hpp"
#include "native_ui/system_tray.hpp"

#include <algorithm>
#include <atomic>
#include <chrono>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cwchar>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <memory>
#include <mutex>
#include <sstream>
#include <string>
#include <thread>

namespace {

constexpr UINT kOverlayTimerId = 1;
constexpr int kOverlayW = 860;
constexpr int kOverlayH = 70;

std::mutex g_logMutex;
std::wstring g_overlayLog = L"[KHOI DONG] Dang cho PathOfExile.exe... (Nhan F10/Ctrl+O chon exe) | Num\u00b1: MiniZoom";

void SetOverlayLog(const std::wstring& msg) {
    std::lock_guard<std::mutex> lock(g_logMutex);
    g_overlayLog = msg;
}

std::wstring GetOverlayLog() {
    std::lock_guard<std::mutex> lock(g_logMutex);
    return g_overlayLog;
}

std::atomic<bool> g_running{true};
std::atomic<float> g_pendingZoomDelta{0.0f};
std::atomic<uint32_t> g_hookFlags{0};
std::atomic<float> g_hookCurrentZoom{1.0f};
std::atomic<bool> g_showOverlay{true};
std::atomic<bool> g_isPoeForeground{false};

HWND g_overlayHwnd = nullptr;
HHOOK g_mouseHook = nullptr;
std::atomic<DWORD> g_hookThreadId{0};
HANDLE g_hookStartedEvent = nullptr;
std::thread g_hookThread;
VisualToolSharedState* g_ipc = nullptr;
HANDLE g_map = nullptr;
HANDLE g_mutex = nullptr;
PoiRadarSharedState* g_poiIpc = nullptr;
HANDLE g_poiMap = nullptr;

std::string g_machineHwid;
poe2::security::LicenseInfo g_licenseInfo;
std::atomic<bool> g_streamerMode{false};
std::atomic<bool> g_showSettingsPanel{false};
std::mutex g_tooltipMutex;
poe2::ui::TooltipLayout g_activeTooltip;
std::chrono::steady_clock::time_point g_lastTooltipTime;
std::atomic<bool> g_hasTooltip{false};
std::atomic<bool> g_inspectorActive{false};
std::unique_ptr<inspector::HoverWatcher> g_hoverWatcher;
std::unique_ptr<poe2::ui::OverlayWindow> g_nativeOverlay;
std::unique_ptr<poe2::ui::SystemTray> g_systemTray;
bool g_consoleEnabled = false;

bool IsElevated() {
    BOOL elevated = FALSE;
    HANDLE token = nullptr;
    if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
        TOKEN_ELEVATION elev{};
        DWORD size = sizeof(elev);
        if (GetTokenInformation(token, TokenElevation, &elev, sizeof(elev), &size)) {
            elevated = elev.TokenIsElevated;
        }
        CloseHandle(token);
    }
    return elevated != FALSE;
}

void RelaunchAsAdmin(int argc, char** argv) {
    wchar_t exePath[MAX_PATH] = {};
    GetModuleFileNameW(nullptr, exePath, MAX_PATH);
    std::wstring params;
    for (int i = 1; i < argc; ++i) {
        if (i > 1) params += L" ";
        params += L"\"" + std::wstring(argv[i], argv[i] + std::strlen(argv[i])) + L"\"";
    }
    SHELLEXECUTEINFOW sei{};
    sei.cbSize = sizeof(sei);
    sei.lpVerb = L"runas";
    sei.lpFile = exePath;
    sei.lpParameters = params.empty() ? nullptr : params.c_str();
    sei.nShow = SW_SHOWNORMAL;
    ShellExecuteExW(&sei);
}

void EnsureInteractiveDesktop() {
    HWINSTA hwinsta = OpenWindowStationW(L"WinSta0", FALSE, GENERIC_ALL);
    if (hwinsta) {
        SetProcessWindowStation(hwinsta);
        CloseWindowStation(hwinsta);
    }
    HDESK hdesk = OpenDesktopW(L"Default", 0, FALSE, GENERIC_ALL);
    if (hdesk) {
        SetThreadDesktop(hdesk);
        CloseDesktop(hdesk);
    }
}

bool IsPoe2Foreground() {
    HWND poe = common::FindPoe2Window();
    if (!poe) return false;
    HWND fg = GetForegroundWindow();
    if (!fg) return false; // In Alt-Tab, lock screen, or UAC: immediately false
    return (fg == poe || IsChild(poe, fg));
}

VisualToolSharedState* OpenOrCreateIpc(HANDLE& mapOut) {
    mapOut = CreateFileMappingW(
        INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE,
        0, sizeof(VisualToolSharedState), kVisualToolIpcName);
    if (!mapOut) return nullptr;
    auto* state = static_cast<VisualToolSharedState*>(
        MapViewOfFile(mapOut, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(VisualToolSharedState)));
    return state;
}

PoiRadarSharedState* OpenOrCreatePoiIpc(HANDLE& mapOut) {
    mapOut = CreateFileMappingW(
        INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE,
        0, sizeof(PoiRadarSharedState), kVisualToolPoiIpcName);
    if (!mapOut) return nullptr;
    auto* state = static_cast<PoiRadarSharedState*>(
        MapViewOfFile(mapOut, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(PoiRadarSharedState)));
    return state;
}

LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode >= 0 && wParam == WM_MOUSEWHEEL && g_isPoeForeground.load(std::memory_order_relaxed)) {
        const uint32_t flags = g_hookFlags.load(std::memory_order_relaxed);
        if ((flags & kVisualFlagEnableZoom) && (flags & kVisualFlagWheelDrive)) {
            const auto* hs = reinterpret_cast<MSLLHOOKSTRUCT*>(lParam);
            if (hs) {
                const short wheel = GET_WHEEL_DELTA_WPARAM(hs->mouseData);
                if (wheel != 0) {
                    const float steps = static_cast<float>(wheel) / static_cast<float>(WHEEL_DELTA);
                    // Lăn xuống (wheel < 0) -> zoom xa (delta > 0)
                    // Lăn lên (wheel > 0) -> zoom gần (delta < 0)
                    g_pendingZoomDelta.fetch_add(-steps * kVisualZoomStep, std::memory_order_relaxed);
                }
            }
        }
    }
    return CallNextHookEx(g_mouseHook, nCode, wParam, lParam);
}

void MouseHookThread() {
    EnsureInteractiveDesktop();
    // Force message queue creation for this thread before signaling readiness
    MSG dummyMsg{};
    PeekMessageW(&dummyMsg, nullptr, WM_USER, WM_USER, PM_NOREMOVE);

    g_hookThreadId.store(GetCurrentThreadId(), std::memory_order_release);
    g_mouseHook = SetWindowsHookExW(WH_MOUSE_LL, MouseHookProc, GetModuleHandleW(nullptr), 0);
    if (!g_mouseHook) {
        std::cerr << "[Poe2VisualTool] SetWindowsHookEx WH_MOUSE_LL that bai: " << GetLastError() << "\n";
        return;
    }
    if (g_hookStartedEvent) {
        SetEvent(g_hookStartedEvent);
    }
    std::cout << "[Poe2VisualTool] Dedicated Mouse Hook Thread khoi tao (TID=" << g_hookThreadId.load() << ").\n";

    MSG msg{};
    while (GetMessageW(&msg, nullptr, 0, 0) > 0) {
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }

    if (g_mouseHook) {
        UnhookWindowsHookEx(g_mouseHook);
        g_mouseHook = nullptr;
    }
    std::cout << "[Poe2VisualTool] Dedicated Mouse Hook Thread da ket thuc an toan.\n";
}

// Cached GDI Resources to prevent 288,000 allocations/hour churn and zero memory leaks
class GdiOverlayResources {
public:
    GdiOverlayResources() = default;
    ~GdiOverlayResources() { Cleanup(); }

    void Initialize() {
        if (initialized_) return;
        bgBrush_ = CreateSolidBrush(RGB(15, 23, 42));
        sepPen_  = CreatePen(PS_SOLID, 1, RGB(51, 65, 85));
        font1_   = CreateFontW(-15, 0, 0, 0, FW_SEMIBOLD, 0, 0, 0, DEFAULT_CHARSET,
                               OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH, L"Segoe UI");
        font2_   = CreateFontW(-12, 0, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET,
                               OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH, L"Segoe UI");
        initialized_ = true;
    }

    void Cleanup() {
        if (bgBrush_) { DeleteObject(bgBrush_); bgBrush_ = nullptr; }
        if (sepPen_)  { DeleteObject(sepPen_);  sepPen_ = nullptr; }
        if (font1_)   { DeleteObject(font1_);   font1_ = nullptr; }
        if (font2_)   { DeleteObject(font2_);   font2_ = nullptr; }
        initialized_ = false;
    }

    HBRUSH BgBrush() const noexcept { return bgBrush_; }
    HPEN   SepPen()  const noexcept { return sepPen_; }
    HFONT  Font1()   const noexcept { return font1_; }
    HFONT  Font2()   const noexcept { return font2_; }

private:
    HBRUSH bgBrush_ = nullptr;
    HPEN   sepPen_  = nullptr;
    HFONT  font1_   = nullptr;
    HFONT  font2_   = nullptr;
    bool initialized_ = false;
};

GdiOverlayResources g_gdiOverlayRes;

struct ScopedGdiSelect {
    HDC hdc;
    HGDIOBJ oldObj;
    ScopedGdiSelect(HDC h, HGDIOBJ newObj) : hdc(h), oldObj(SelectObject(h, newObj)) {}
    ~ScopedGdiSelect() { if (hdc && oldObj) SelectObject(hdc, oldObj); }
};

void DrawOverlay(HDC targetHdc = nullptr) {
    if (!g_overlayHwnd || !g_ipc) return;
    HDC hdc = targetHdc ? targetHdc : GetDC(g_overlayHwnd);
    if (!hdc) return;

    g_gdiOverlayRes.Initialize();

    RECT rc{};
    GetClientRect(g_overlayHwnd, &rc);
    FillRect(hdc, &rc, g_gdiOverlayRes.BgBrush());

    SetBkMode(hdc, TRANSPARENT);

    VisualToolSharedState snap{};
    if (!ReadIpcSharedStateSafely(g_ipc, snap)) {
        std::memcpy(&snap, g_ipc, sizeof(snap));
    }

    // Dòng 1 (y: 0 -> 36): Cached Font 1
    {
        ScopedGdiSelect fontGuard(hdc, g_gdiOverlayRes.Font1());
        SetTextColor(hdc, RGB(248, 250, 252));

        const float cap = snap.zoom_cap;
        const float cur = snap.current_zoom;
        const bool fogOff = (snap.flags & kVisualFlagDisableFog) != 0;

        wchar_t line1[256];
        swprintf_s(line1, L"Poe2VisualTool  |  Zoom %.1fx/%.1fx  |  Fog %s  |  Auto-Reveal: %s  |  Num±/Ctrl+[]: MiniZoom",
                   cur, cap, fogOff ? L"OFF" : L"ON",
                   ((snap.flags & kVisualFlagAutoReveal) != 0) ? L"ON" : L"OFF");

        RECT rc1 = { rc.left, 0, rc.right, 36 };
        DrawTextW(hdc, line1, -1, &rc1, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
    }

    // Đường kẻ mờ phân tách: Cached Pen
    {
        ScopedGdiSelect penGuard(hdc, g_gdiOverlayRes.SepPen());
        MoveToEx(hdc, rc.left + 16, 37, nullptr);
        LineTo(hdc, rc.right - 16, 37);
    }

    // Dòng 2 (y: 38 -> 68): Cached Font 2
    {
        ScopedGdiSelect fontGuard(hdc, g_gdiOverlayRes.Font2());

        const std::wstring logMsg = GetOverlayLog();
        const bool attached = (snap.status & kVisualStatusAttached) != 0;
        const bool camOk = (snap.status & kVisualStatusCameraOk) != 0;
        COLORREF logColor = RGB(56, 189, 248); // Sky Blue
        if (logMsg.find(L"POE2 NATIVE") != std::wstring::npos || logMsg.find(L"OK") != std::wstring::npos || (attached && camOk)) {
            logColor = RGB(74, 222, 128); // Emerald Green
        }
        SetTextColor(hdc, logColor);

        RECT rc2 = { rc.left, 38, rc.right, 68 };
        DrawTextW(hdc, logMsg.c_str(), static_cast<int>(logMsg.length()), &rc2, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
    }

    if (!targetHdc) {
        ReleaseDC(g_overlayHwnd, hdc);
    }
}

void PositionOverlay() {
    if (!g_overlayHwnd) return;
    HWND poe = common::FindPoe2Window();
    RECT wr{};
    if (poe && !IsIconic(poe) && GetWindowRect(poe, &wr)) {
        const int x = wr.left + ((wr.right - wr.left) - kOverlayW) / 2;
        const int y = wr.top + 8;
        SetWindowPos(g_overlayHwnd, HWND_TOPMOST, x, y, kOverlayW, kOverlayH, SWP_NOACTIVATE);
    } else {
        SetWindowPos(g_overlayHwnd, HWND_TOPMOST, 40, 12, kOverlayW, kOverlayH, SWP_NOACTIVATE);
    }
}

void RenderNativeHud() {
    if (!g_nativeOverlay || !g_nativeOverlay->IsCreated()) return;
    HWND poe = common::FindPoe2Window();
    if (!poe || !g_isPoeForeground.load(std::memory_order_relaxed) || IsIconic(poe)) {
        ShowWindow(g_nativeOverlay->GetHwnd(), SW_HIDE);
        return;
    }

    g_nativeOverlay->FollowGameWindow(poe);
    ShowWindow(g_nativeOverlay->GetHwnd(), SW_SHOWNOACTIVATE);

    g_nativeOverlay->BeginFrame();

    // 1. Render Bảng Soi Đồ Tooltip nếu bật inspector và có tooltip (< 5s kể từ lúc hover)
    if (g_inspectorActive.load(std::memory_order_relaxed) && g_hasTooltip.load(std::memory_order_relaxed)) {
        std::lock_guard<std::mutex> lock(g_tooltipMutex);
        auto now = std::chrono::steady_clock::now();
        auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - g_lastTooltipTime).count();
        if (elapsed < 5) {
            g_nativeOverlay->RenderTooltip(g_activeTooltip);
        } else {
            g_hasTooltip.store(false, std::memory_order_release);
        }
    }

    // 2. Render Bảng Điều Khiển In-Game Dashboard nếu bật F11
    if (g_showSettingsPanel.load(std::memory_order_relaxed) && g_ipc) {
        poe2::ui::SettingsPanelState st;
        st.zoomEnabled = (g_ipc->flags & kVisualFlagEnableZoom) != 0;
        st.fogDisabled = (g_ipc->flags & kVisualFlagDisableFog) != 0;
        st.poiRadar = (g_ipc->flags & kVisualFlagMapContentRadar) != 0;
        st.licenseInfo = g_licenseInfo;
        auto panelLayout = poe2::ui::SettingsPanel::BuildLayout(st, g_nativeOverlay->GetWidth(), g_nativeOverlay->GetHeight());
        g_nativeOverlay->RenderSettings(panelLayout);
    }

    // 3. Render Radar POI nếu bật và có dữ liệu IPC
    if (g_ipc && (g_ipc->flags & kVisualFlagMapContentRadar) && g_poiIpc) {
        PoiRadarSharedState poiSnap{};
        if (ReadPoiRadarSharedStateSafely(g_poiIpc, poiSnap) && poiSnap.count > 0) {
            std::vector<PoiSharedEntity> entities(poiSnap.entities, poiSnap.entities + poiSnap.count);
            auto radarLayout = poe2::ui::RadarHud::ComputeLayout(
                entities,
                poiSnap.player_x,
                poiSnap.player_z,
                poiSnap.camera_yaw,
                g_nativeOverlay->GetWidth(),
                g_nativeOverlay->GetHeight()
            );
            g_nativeOverlay->RenderRadar(radarLayout);
        }
    }

    g_nativeOverlay->EndFrame();
}

LRESULT CALLBACK OverlayProc(HWND hwnd, UINT msg, WPARAM w, LPARAM l) {
    if (msg == WM_TIMER && w == kOverlayTimerId) {
        bool fg = IsPoe2Foreground();
        g_isPoeForeground.store(fg, std::memory_order_relaxed);
        HWND poe = common::FindPoe2Window();

        if (!fg || (poe && IsIconic(poe))) {
            ShowWindow(hwnd, SW_HIDE);
            if (g_nativeOverlay && g_nativeOverlay->GetHwnd()) {
                ShowWindow(g_nativeOverlay->GetHwnd(), SW_HIDE);
            }
        } else {
            ShowWindow(hwnd, SW_SHOWNOACTIVATE);
            PositionOverlay();
            DrawOverlay();
            RenderNativeHud();
        }

        // F12 / Pause: Instant 1-Frame Panic Restore (< 8.33ms)
        const bool f12 = (GetAsyncKeyState(VK_F12) & 0x8000) != 0;
        const bool pauseKey = (GetAsyncKeyState(VK_PAUSE) & 0x8000) != 0;
        if ((f12 || pauseKey) && g_ipc) {
            InterlockedOr(reinterpret_cast<volatile LONG*>(&g_ipc->flags), kVisualFlagPanic);
            poe2::platform::SoundEffects::Instance().PlayPanicRestore();
            SetOverlayLog(L"[PANIC 1-FRAME] Da hoan nguyen toan bo bo nho ve mac dinh goc (F12)!");
        }

        // F11: Toggle Settings Dashboard
        static bool prevF11 = false;
        bool curF11 = (GetAsyncKeyState(VK_F11) & 0x8000) != 0;
        if (curF11 && !prevF11) {
            bool nextSettings = !g_showSettingsPanel.load();
            g_showSettingsPanel.store(nextSettings);
            poe2::platform::SoundEffects::Instance().PlayToggle(nextSettings);
        }
        prevF11 = curF11;

        // F9: Toggle OBS Streamer Mode (WDA_EXCLUDEFROMCAPTURE)
        static bool prevF9 = false;
        bool curF9 = (GetAsyncKeyState(VK_F9) & 0x8000) != 0;
        if (curF9 && !prevF9) {
            bool nextStreamer = !g_streamerMode.load();
            g_streamerMode.store(nextStreamer);
            const DWORD WDA_EXCLUDE_FROM_CAPTURE = 0x00000011;
            SetWindowDisplayAffinity(hwnd, nextStreamer ? WDA_EXCLUDE_FROM_CAPTURE : 0);
            if (g_nativeOverlay) {
                g_nativeOverlay->SetStreamerMode(nextStreamer);
            }
            poe2::platform::SoundEffects::Instance().PlayToggle(nextStreamer);
            SetOverlayLog(nextStreamer
                ? L"[STREAMER MODE: ON] Da an overlay khoi OBS & Discord (WDA 0x11)!"
                : L"[STREAMER MODE: OFF] Da hien thi overlay tren OBS.");
        }
        prevF9 = curF9;

        // Cập nhật trạng thái System Tray Tooltip mỗi 500ms
        if (g_systemTray && g_ipc) {
            static auto lastTrayUpdate = std::chrono::steady_clock::now() - std::chrono::seconds(1);
            auto now = std::chrono::steady_clock::now();
            if (now - lastTrayUpdate >= std::chrono::milliseconds(500)) {
                lastTrayUpdate = now;
                bool attached = (g_ipc->status & kVisualStatusAttached) != 0;
                g_systemTray->UpdateStatus(attached, g_ipc->pid, g_ipc->current_zoom, g_ipc->zoom_cap);
            }
        }

        return 0;
    }
    if (msg == poe2::ui::WM_TRAYICON) {
        if (l == WM_RBUTTONUP || l == WM_CONTEXTMENU) {
            if (g_systemTray) {
                bool attached = false;
                if (g_ipc && (g_ipc->status & kVisualStatusAttached)) {
                    attached = true;
                }
                g_systemTray->ShowContextMenu(hwnd, g_streamerMode.load(), attached);
            }
            return 0;
        } else if (l == WM_LBUTTONUP || l == WM_LBUTTONDBLCLK) {
            bool nextSettings = !g_showSettingsPanel.load();
            g_showSettingsPanel.store(nextSettings);
            poe2::platform::SoundEffects::Instance().PlayToggle(nextSettings);
            return 0;
        }
        return 0;
    }
    if (msg == WM_COMMAND) {
        switch (LOWORD(w)) {
            case poe2::ui::ID_TRAY_SETTINGS: {
                bool nextSettings = !g_showSettingsPanel.load();
                g_showSettingsPanel.store(nextSettings);
                poe2::platform::SoundEffects::Instance().PlayToggle(nextSettings);
                break;
            }
            case poe2::ui::ID_TRAY_PANIC: {
                if (g_ipc) {
                    InterlockedOr(reinterpret_cast<volatile LONG*>(&g_ipc->flags), kVisualFlagPanic);
                    poe2::platform::SoundEffects::Instance().PlayPanicRestore();
                    SetOverlayLog(L"[PANIC 1-FRAME] Da hoan nguyen toan bo bo nho ve mac dinh goc (F12)!");
                }
                break;
            }
            case poe2::ui::ID_TRAY_STREAMER: {
                bool nextStreamer = !g_streamerMode.load();
                g_streamerMode.store(nextStreamer);
                const DWORD WDA_EXCLUDE_FROM_CAPTURE = 0x00000011;
                SetWindowDisplayAffinity(hwnd, nextStreamer ? WDA_EXCLUDE_FROM_CAPTURE : 0);
                if (g_nativeOverlay) {
                    g_nativeOverlay->SetStreamerMode(nextStreamer);
                }
                poe2::platform::SoundEffects::Instance().PlayToggle(nextStreamer);
                SetOverlayLog(nextStreamer
                    ? L"[STREAMER MODE: ON] Da an overlay khoi OBS & Discord (WDA 0x11)!"
                    : L"[STREAMER MODE: OFF] Da hien thi overlay tren OBS.");
                break;
            }
            case poe2::ui::ID_TRAY_RESET_ZOOM: {
                if (g_ipc) {
                    g_ipc->current_zoom = 1.0f;
                    poe2::platform::SoundEffects::Instance().PlayToggle(false);
                    SetOverlayLog(L"[ZOOM] Da reset camera zoom ve 1.0x!");
                }
                break;
            }
            case poe2::ui::ID_TRAY_EXIT: {
                g_running.store(false);
                PostQuitMessage(0);
                break;
            }
            default:
                break;
        }
        return 0;
    }
    if (msg == WM_PAINT) {
        PAINTSTRUCT ps{};
        BeginPaint(hwnd, &ps);
        DrawOverlay(ps.hdc);
        EndPaint(hwnd, &ps);
        return 0;
    }
    if (msg == WM_DESTROY) {
        g_running = false;
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProcW(hwnd, msg, w, l);
}

void CreateOverlay(HINSTANCE inst) {
    WNDCLASSW wc{};
    wc.lpfnWndProc = OverlayProc;
    wc.hInstance = inst;
    wc.lpszClassName = L"Poe2VisualToolOverlay";
    wc.hbrBackground = static_cast<HBRUSH>(GetStockObject(BLACK_BRUSH));
    RegisterClassW(&wc);

    g_overlayHwnd = CreateWindowExW(
        WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOPMOST | WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW,
        wc.lpszClassName, L"Poe2VisualTool Overlay",
        WS_POPUP,
        40, 12, kOverlayW, kOverlayH,
        nullptr, nullptr, inst, nullptr);
    if (!g_overlayHwnd) return;
    SetLayeredWindowAttributes(g_overlayHwnd, 0, 220, LWA_ALPHA);
    if (g_showOverlay.load()) {
        ShowWindow(g_overlayHwnd, SW_SHOWNOACTIVATE);
    } else {
        ShowWindow(g_overlayHwnd, SW_HIDE);
    }
    SetTimer(g_overlayHwnd, kOverlayTimerId, 50, nullptr);

    // Khởi tạo System Tray icon gắn với g_overlayHwnd
    g_systemTray = std::make_unique<poe2::ui::SystemTray>();
    g_systemTray->Create(g_overlayHwnd, inst, poe2::ui::WM_TRAYICON);
}

void EngineLoop(float initialCap) {
    EnsureInteractiveDesktop();
    MemoryReader reader;
    std::unique_ptr<VisualModsEngine> engine;
    std::unique_ptr<visual::PoiRadar> radar;
    std::unique_ptr<visual::ParticleReducer> reducer;
    std::unique_ptr<visual::ModColorizer> colorizer;
    uint32_t attachedPid = 0;
    uintptr_t lastLoggedCamAddr = 0;
    bool pidZeroLogged = false;
    auto lastDetect = std::chrono::steady_clock::now() - std::chrono::seconds(5);

    while (g_running.load()) {
        g_isPoeForeground.store(IsPoe2Foreground(), std::memory_order_relaxed);
        VisualToolSharedState snap{};
        if (g_ipc) {
            std::memcpy(&snap, g_ipc, sizeof(snap));
        }

        const bool wantZoom = (snap.flags & kVisualFlagEnableZoom) != 0;
        const bool wantFog = (snap.flags & kVisualFlagDisableFog) != 0;
        bool panic = (snap.flags & kVisualFlagPanic) != 0;
        const bool redetect = (snap.flags & kVisualFlagRedetect) != 0;
        float cap = ClampVisualZoom(snap.zoom_cap > 0.1f ? snap.zoom_cap : initialCap);
        g_hookFlags.store(snap.flags, std::memory_order_relaxed);

        std::wstring procName;
        uint32_t pid = 0;
        if (reader.IsAttached() && reader.RawHandle() != nullptr) {
            DWORD exitCode = 0;
            if (GetExitCodeProcess(reader.RawHandle(), &exitCode) && exitCode == STILL_ACTIVE) {
                pid = attachedPid;
            } else {
                pid = 0;
            }
        } else {
            static auto lastScan = std::chrono::steady_clock::now() - std::chrono::seconds(1);
            auto nowScan = std::chrono::steady_clock::now();
            if (nowScan - lastScan >= std::chrono::milliseconds(500)) {
                pid = FindPoeProcessId(&procName);
                lastScan = nowScan;
            } else {
                pid = attachedPid;
            }
        }

        if (pid == 0) {
            if (!pidZeroLogged) {
                SetOverlayLog(L"[CHO GAME] Dang doi PathOfExile.exe... (Nhan F10/Ctrl+O chon exe)");
                pidZeroLogged = true;
            }
            if (engine) {
                engine->PanicRestoreAll();
                engine.reset();
            }
            if (radar) radar.reset();
            if (reducer) {
                reducer->PanicRestoreAll();
                reducer.reset();
            }
            if (colorizer) {
                colorizer->PanicRestoreAll();
                colorizer.reset();
            }
            if (reader.IsAttached()) reader.Detach();
            attachedPid = 0;
            lastLoggedCamAddr = 0;
            if (g_ipc) {
                g_ipc->status = kVisualStatusRunning;
                g_ipc->pid = 0;
                g_ipc->current_zoom = 1.0f;
            }

            // Hỗ trợ người dùng bấm F10 hoặc Ctrl+O để chọn exe khi game chưa chạy
            if ((GetAsyncKeyState(VK_F10) & 0x8000) ||
                (((GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0) && ((GetAsyncKeyState('O') & 0x8000) != 0))) {
                const std::wstring cfg = common::ResolveConfigPath();
                const std::wstring chosen = common::FindOrPromptPoe2ClientExe(cfg);
                if (!chosen.empty()) {
                    SetOverlayLog(L"[CONFIG] Da cap nhat duong dan game vao config.toml!");
                }
                std::this_thread::sleep_for(std::chrono::milliseconds(300));
            }

            std::this_thread::sleep_for(std::chrono::milliseconds(400));
            continue;
        }
        pidZeroLogged = false;

        if (attachedPid != pid || !reader.IsAttached()) {
            if (engine) {
                engine->PanicRestoreAll();
                engine.reset();
            }
            if (radar) radar.reset();
            if (reducer) {
                reducer->PanicRestoreAll();
                reducer.reset();
            }
            if (colorizer) {
                colorizer->PanicRestoreAll();
                colorizer.reset();
            }
            if (!reader.Attach(pid)) {
                if (g_ipc) g_ipc->last_error = GetLastError();
                std::this_thread::sleep_for(std::chrono::milliseconds(500));
                continue;
            }
            attachedPid = pid;
            lastLoggedCamAddr = 0;
            std::wostringstream oss;
            oss << L"[ATTACH] Da gan PID " << pid << L" | Dang tim kiem Camera...";
            SetOverlayLog(oss.str());

            engine = std::make_unique<VisualModsEngine>(reader);
            radar = std::make_unique<visual::PoiRadar>(reader);
            reducer = std::make_unique<visual::ParticleReducer>(reader);
            colorizer = std::make_unique<visual::ModColorizer>(reader);
            engine->SetOnMinimapRevealed([]() {
                SetOverlayLog(L"[AUTO-MINIMAP] Da mo sang toan bo ban do & xoa vien xanh (GPU Shader & CPU Grid)!");
            });
            OffsetsStore store;
            std::wstring exeDirW = common::GetExecutableDirectory();
            char exeDir[MAX_PATH] = {};
            WideCharToMultiByte(CP_UTF8, 0, exeDirW.c_str(), -1, exeDir, sizeof(exeDir), nullptr, nullptr);
            std::string baseDir = exeDir;
            store.Load(baseDir + "\\offsets.toml");
            engine->LoadFromOffsetsStore(store);
            engine->AutoDetect(pid, &store);
            lastDetect = std::chrono::steady_clock::now();
            poe2::platform::SoundEffects::Instance().PlayAttachSuccess();

            if (wantZoom) {
                engine->ApplyZoomCodePatch(cap);
            }
            if (wantFog) {
                engine->DisableFog();
            }
            if (snap.flags & kVisualFlagAutoReveal) {
                engine->RevealEntireMinimap();
            }
            oss.str(L"");
            oss.clear();
            oss << std::fixed << std::setprecision(1);
            oss << L"[POE2 NATIVE] Zoom " << cap << L"x & Fog OFF OK | Auto-Reveal: ON | Num\u00b1: Zoom Map";
            SetOverlayLog(oss.str());
        }

        if (engine) {
            auto now = std::chrono::steady_clock::now();
            static int camInvalidStreak = 0;
            const bool camInvalid = engine->CameraAddress() != 0 && !engine->CameraLooksValid();
            if (camInvalid) {
                if (++camInvalidStreak >= 60) { // ~500ms thay vì 80ms
                    if (g_consoleEnabled) {
                        std::cout << "[Poe2VisualTool] Camera heap khong con hop le — Tu dong AutoDetect lai.\n";
                    }
                    SetOverlayLog(L"[MAP TRANSITION] Camera thay doi \u2014 Tu dong AutoDetect lai...");
                    engine->SetCameraAddress(0);
                    lastLoggedCamAddr = 0;
                    camInvalidStreak = 0;
                }
            } else {
                camInvalidStreak = 0;
            }

            if (redetect || engine->CameraAddress() == 0) {
                if (redetect || now - lastDetect > std::chrono::seconds(3)) {
                    if (redetect) {
                        SetOverlayLog(L"[MAP TRANSITION] Camera thay doi \u2014 Tu dong AutoDetect lai...");
                    }
                    OffsetsStore store;
                    engine->AutoDetect(pid, &store);
                    lastDetect = now;
                    if (g_ipc && redetect) {
                        g_ipc->flags &= ~kVisualFlagRedetect;
                    }
                }
            }

            // Fast direct panic polling (VK_F12 / VK_PAUSE) inside EngineLoop (< 8.33ms)
            const bool hardwarePanic = (GetAsyncKeyState(VK_F12) & 0x8000) != 0 ||
                                       (GetAsyncKeyState(VK_PAUSE) & 0x8000) != 0;
            if (hardwarePanic && !panic) {
                panic = true;
                if (g_ipc) {
                    InterlockedOr(reinterpret_cast<volatile LONG*>(&g_ipc->flags), kVisualFlagPanic);
                }
            }

            if (panic) {
                poe2::platform::SoundEffects::Instance().PlayPanicRestore();
                engine->PanicRestoreAll();
                if (radar) radar->Clear();
                if (reducer) reducer->PanicRestoreAll();
                if (colorizer) colorizer->PanicRestoreAll();
                if (g_ipc) {
                    InterlockedAnd(reinterpret_cast<volatile LONG*>(&g_ipc->flags),
                        ~(kVisualFlagPanic | kVisualFlagEnableZoom | kVisualFlagDisableFog |
                          kVisualFlagParticleReducer | kVisualFlagModColorizer));
                    g_ipc->current_zoom = 1.0f;
                }
                SetOverlayLog(L"[PANIC 1-FRAME] Da hoan nguyen toan bo Camera, Shader Patches, Fog & Visuals ve mac dinh!");
            } else {
                if (wantZoom) {
                    if (!engine->IsCodePatchActive()) {
                        engine->ApplyZoomCodePatch(cap);
                    }
                } else if (engine->IsCodePatchActive() || engine->IsZoomActive()) {
                    engine->RestoreZoomCodePatch();
                    engine->RestoreCamera();
                }

                if (wantFog) {
                    if (!engine->IsFogDisabled()) {
                        engine->DisableFog();
                    }
                } else if (engine->IsFogDisabled()) {
                    engine->RestoreFog();
                }

                if (snap.flags & kVisualFlagAutoReveal) {
                    engine->SetAutoRevealMinimap(true);
                } else {
                    engine->SetAutoRevealMinimap(false);
                }

                if (reducer) {
                    if (snap.flags & kVisualFlagParticleReducer) {
                        if (!reducer->IsActive()) {
                            visual::ReducerConfig rcfg;
                            rcfg.disableWeather = true;
                            rcfg.disableScreenShake = true;
                            rcfg.reduceBloom = true;
                            reducer->ApplyOptimizations(rcfg);
                        }
                    } else if (reducer->IsActive()) {
                        reducer->RestoreAll();
                    }
                }

                if (colorizer) {
                    if (snap.flags & kVisualFlagModColorizer) {
                        if (!colorizer->IsPatched()) {
                            colorizer->ApplyColorPatches();
                        }
                    } else if (colorizer->IsPatched()) {
                        colorizer->RestoreColorPatches();
                    }
                }

                if (radar) {
                    HWND poeHwnd = common::FindPoe2Window();
                    if (poeHwnd) {
                        RECT rc{};
                        if (GetClientRect(poeHwnd, &rc)) {
                            int rw = rc.right - rc.left;
                            int rh = rc.bottom - rc.top;
                            if (rw > 0 && rh > 0) {
                                radar->SetScreenDimensions(rw, rh);
                            }
                        }
                    }
                    if (engine && engine->CameraAddress() != 0) {
                        float camMat[16] = {0};
                        if (reader.Read(engine->CameraAddress() + kPoe2CameraMatrixOffset, camMat, sizeof(camMat))) {
                            radar->SetCameraMatrix(camMat);
                        }
                    }
                    if ((snap.flags & kVisualFlagMapContentRadar) && g_poiIpc) {
                        radar->ExportToSharedState(*g_poiIpc);
                    }
                }

                engine->Maintain();
            }

            const float pending = g_pendingZoomDelta.exchange(0.0f, std::memory_order_relaxed);
            if (std::abs(pending) > 0.001f && wantZoom) {
                engine->StepZoom(pending);
            }

            // Phím tắt Minimap Zoom
            if (g_isPoeForeground.load(std::memory_order_relaxed)) {
                static auto lastKeyZoomTime = std::chrono::steady_clock::now();
                auto keyNow = std::chrono::steady_clock::now();
                if (keyNow - lastKeyZoomTime > std::chrono::milliseconds(120)) {
                    const bool ctrlDown = (GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0;
                    if ((GetAsyncKeyState(VK_ADD) & 0x8000) || (ctrlDown && (GetAsyncKeyState(VK_OEM_6) & 0x8000))) {
                        engine->StepMinimapZoom(1);
                        lastKeyZoomTime = keyNow;
                    } else if ((GetAsyncKeyState(VK_SUBTRACT) & 0x8000) || (ctrlDown && (GetAsyncKeyState(VK_OEM_4) & 0x8000))) {
                        engine->StepMinimapZoom(-1);
                        lastKeyZoomTime = keyNow;
                    }
                }
            }

            if (g_ipc) {
                // Seqlock write sequence:
                // 1. Begin write by making seqlock odd
                g_ipc->seqlock = (g_ipc->seqlock + 1) | 1;
                std::atomic_thread_fence(std::memory_order_release);

                const float liveZ = engine->ReadLiveZoomMultiplier();
                g_ipc->current_zoom = liveZ;
                g_hookCurrentZoom.store(liveZ, std::memory_order_relaxed);
                g_ipc->pid = pid;
                g_ipc->camera_addr = engine->CameraAddress();
                g_ipc->fog_addr = engine->FogAddress();

                uint32_t st = kVisualStatusRunning | kVisualStatusAttached;
                if (engine->CameraAddress() != 0) st |= kVisualStatusCameraOk;
                if (engine->IsFogDisabled()) st |= kVisualStatusFogOk;
                if (engine->IsAutoRevealMinimap()) st |= kVisualStatusPoiRadarOk;
                if (reducer && reducer->IsActive()) st |= kVisualStatusParticleReducerOk;
                if (colorizer && colorizer->IsPatched()) st |= kVisualStatusModColorizerOk;
                g_ipc->status = st;

                // 2. End write by making seqlock even with release fence
                std::atomic_thread_fence(std::memory_order_release);
                g_ipc->seqlock = (g_ipc->seqlock + 1) & ~1u;
            }
        }

        std::this_thread::sleep_for(std::chrono::milliseconds(8)); // ~120Hz Loop
    }

    if (engine) engine->PanicRestoreAll();
    if (reducer) reducer->PanicRestoreAll();
    if (colorizer) colorizer->PanicRestoreAll();
}

} // namespace

int main(int argc, char** argv) {
    SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);

    for (int i = 1; i < argc; ++i) {
        if (std::strcmp(argv[i], "--console") == 0 ||
            std::strcmp(argv[i], "--debug") == 0 ||
            std::strcmp(argv[i], "-v") == 0 ||
            std::strcmp(argv[i], "--verbose") == 0) {
            g_consoleEnabled = true;
        }
    }

    for (int i = 1; i < argc; ++i) {
        if (std::strcmp(argv[i], "--help") == 0) {
            if (!GetConsoleWindow()) {
                AllocConsole();
                FILE* fp = nullptr;
                freopen_s(&fp, "CONOUT$", "w", stdout);
            }
            std::cout << "Poe2VisualTool v2.0 Commercial\n"
                      << "  --console / --debug   Mo cua so dong lenh debug console\n"
                      << "  --max-zoom <1.0-8.0>  Tran zoom camera (mac dinh 8.0)\n"
                      << "  --no-fog / --fog       Tat/bat suong mu the tich\n"
                      << "  --wheel / --no-wheel  Con lan chuot trong game dieu khien zoom\n"
                      << "  --radar / --no-radar  Bat/tat quet POI Radar (Boss, Strongbox, Shrine)\n"
                      << "  --fps-booster         Giam thieu hat thoi tiet, bloom de tang FPS\n"
                      << "  --mod-colors          To mau phan loai mod trang bi trong RAM\n"
                      << "  --no-overlay          Khong hien thi HUD Overlay tren man hinh\n"
                      << "  --prompt-path         Bat hop thoai chon duong dan game PathOfExile*.exe\n";
            return 0;
        }
    }

    if (!IsElevated()) {
        if (g_consoleEnabled) {
            std::cout << "[Poe2VisualTool] Yeu cau quyen Administrator (RPM/WPM). Dang tai khoi dong lai voi UAC...\n";
        }
        RelaunchAsAdmin(argc, argv);
        return 0;
    }

    if (g_consoleEnabled && !GetConsoleWindow()) {
        AllocConsole();
        FILE* fp = nullptr;
        freopen_s(&fp, "CONOUT$", "w", stdout);
        freopen_s(&fp, "CONOUT$", "w", stderr);
        setvbuf(stdout, nullptr, _IONBF, 0);
    }

    EnsureInteractiveDesktop();
    timeBeginPeriod(1);

    float maxZoom = kVisualZoomMax;
    bool noFog = true;
    bool wheel = true;
    bool overlay = true;
    bool promptPathExplicit = false;
    bool radarEnabled = true;
    bool fpsBoosterEnabled = false;
    bool modColorsEnabled = false;
    bool inspectorEnabled = false;
    int inspectorDwellMs = 180;

    const std::wstring cfgPath = common::ResolveConfigPath();
    if (!cfgPath.empty()) {
        std::ifstream cfgFile(cfgPath);
        if (cfgFile.is_open()) {
            std::string line;
            std::string currentSection;
            while (std::getline(cfgFile, line)) {
                auto trimmed = common::Trim(line);
                if (trimmed.empty() || trimmed.starts_with("#") || trimmed.starts_with(";")) continue;
                if (trimmed.front() == '[' && trimmed.back() == ']') {
                    currentSection = std::string(trimmed.substr(1, trimmed.size() - 2));
                    continue;
                }
                if (currentSection == "visual") {
                    float fVal = 0.0f;
                    bool bVal = false;
                    if (common::ParseTomlFloat(line, "zoom_cap", fVal)) maxZoom = ClampVisualZoom(fVal);
                    if (common::ParseTomlBool(line, "disable_fog", bVal)) noFog = bVal;
                    if (common::ParseTomlBool(line, "wheel_drive", bVal)) wheel = bVal;
                } else if (currentSection == "inspector") {
                    bool bVal = false;
                    int iVal = 180;
                    if (common::ParseTomlBool(line, "enabled", bVal)) inspectorEnabled = bVal;
                    if (common::ParseTomlInt(line, "dwell_time_ms", iVal)) inspectorDwellMs = iVal;
                }
            }
        }
    }

    for (int i = 1; i < argc; ++i) {
        if (std::strcmp(argv[i], "--max-zoom") == 0 && i + 1 < argc) {
            maxZoom = ClampVisualZoom(std::strtof(argv[++i], nullptr));
        } else if (std::strcmp(argv[i], "--no-fog") == 0 || std::strcmp(argv[i], "--disable-fog") == 0) {
            noFog = true;
        } else if (std::strcmp(argv[i], "--fog") == 0) {
            noFog = false;
        } else if (std::strcmp(argv[i], "--wheel") == 0) {
            wheel = true;
        } else if (std::strcmp(argv[i], "--no-wheel") == 0) {
            wheel = false;
        } else if (std::strcmp(argv[i], "--no-overlay") == 0) {
            overlay = false;
        } else if (std::strcmp(argv[i], "--radar") == 0) {
            radarEnabled = true;
        } else if (std::strcmp(argv[i], "--no-radar") == 0) {
            radarEnabled = false;
        } else if (std::strcmp(argv[i], "--fps-booster") == 0 || std::strcmp(argv[i], "--no-particles") == 0) {
            fpsBoosterEnabled = true;
        } else if (std::strcmp(argv[i], "--mod-colors") == 0) {
            modColorsEnabled = true;
        } else if (std::strcmp(argv[i], "--inspector") == 0) {
            inspectorEnabled = true;
        } else if (std::strcmp(argv[i], "--no-inspector") == 0) {
            inspectorEnabled = false;
        } else if (std::strcmp(argv[i], "--prompt-path") == 0) {
            promptPathExplicit = true;
        } else if (std::strcmp(argv[i], "--help") == 0) {
            std::cout << "Poe2VisualTool v2.0 Commercial In-Game HUD & Visual Engine\n"
                      << "  --max-zoom <1.0-8.0>  Tran zoom camera (mac dinh 8.0)\n"
                      << "  --no-fog / --fog       Tat/bat suong mu the tich\n"
                      << "  --wheel / --no-wheel  Con lan chuot trong game dieu khien zoom\n"
                      << "  --radar / --no-radar  Bat/tat quet POI Radar (Boss, Strongbox, Shrine)\n"
                      << "  --fps-booster         Giam thieu hat thoi tiet, bloom de tang FPS\n"
                      << "  --mod-colors          To mau phan loai mod trang bi trong RAM\n"
                      << "  --inspector           Bat chuc nang soi do hover inspector (mac dinh: tat)\n"
                      << "  --no-inspector        Tat chuc nang soi do hover inspector\n"
                      << "  --no-overlay          Khong hien thi HUD Overlay tren man hinh\n"
                      << "  --prompt-path         Bat hop thoai chon duong dan game PathOfExile*.exe\n";
            return 0;
        }
    }
    g_showOverlay.store(overlay);
    g_inspectorActive.store(inspectorEnabled, std::memory_order_release);

    if (promptPathExplicit) {
        std::wstring chosen = common::PromptUserForGameExe(nullptr);
        if (!chosen.empty()) {
            common::SaveClientExeToConfig(cfgPath, chosen);
            std::wcout << L"[Poe2VisualTool] Da luu duong dan game: " << chosen << std::endl;
        }
    } else {
        // Tự động tìm đường dẫn game 4 tầng (hoặc prompt nếu chưa có cấu hình)
        std::wstring detected = common::FindOrPromptPoe2ClientExe(cfgPath);
        if (!detected.empty()) {
            if (g_consoleEnabled) {
                std::wcout << L"[Poe2VisualTool] Phat hien duong dan game: " << detected << std::endl;
            }
        }
    }

    HANDLE hLegacyMutex = OpenMutexW(SYNCHRONIZE, FALSE, L"AutoPOE2_VisualTool_Singleton");
    if (hLegacyMutex != nullptr) {
        CloseHandle(hLegacyMutex);
        if (g_consoleEnabled) {
            std::cout << "[Poe2VisualTool] Phat hien AutoPOE2_VisualTool dang chay -> Poe2VisualTool chiem quyen uu tien kiem soat Camera & Fog (Standalone Mode)." << std::endl;
        }
    }

    g_mutex = CreateMutexW(nullptr, TRUE, kVisualToolMutexName);
    if (g_mutex && GetLastError() == ERROR_ALREADY_EXISTS) {
        if (g_consoleEnabled) {
            std::cout << "[Poe2VisualTool] Tien trinh Poe2VisualTool da chay. Ket noi IPC thanh cong.\n";
        }
        CloseHandle(g_mutex);
        return 0;
    }

    g_ipc = OpenOrCreateIpc(g_map);
    if (!g_ipc) {
        if (g_consoleEnabled) {
            std::cerr << "[Poe2VisualTool] Khong tao duoc IPC mapping.\n";
        }
        return 1;
    }
    std::memset(g_ipc, 0, sizeof(*g_ipc));
    g_ipc->magic = kVisualToolMagic;
    g_ipc->zoom_cap = maxZoom;
    g_ipc->current_zoom = 1.0f;
    g_ipc->flags = kVisualFlagEnableZoom | kVisualFlagAutoReveal;
    if (noFog) g_ipc->flags |= kVisualFlagDisableFog;
    if (wheel) g_ipc->flags |= kVisualFlagWheelDrive;
    if (radarEnabled) g_ipc->flags |= kVisualFlagMapContentRadar;
    if (fpsBoosterEnabled) g_ipc->flags |= kVisualFlagParticleReducer;
    if (modColorsEnabled) g_ipc->flags |= kVisualFlagModColorizer;
    g_ipc->status = kVisualStatusRunning;

    g_poiIpc = OpenOrCreatePoiIpc(g_poiMap);
    if (g_poiIpc) {
        std::memset(g_poiIpc, 0, sizeof(*g_poiIpc));
        g_poiIpc->magic = kVisualToolPoiMagic;
    }

    if (g_consoleEnabled) {
        std::cout << "[Poe2VisualTool v2.0] Tran zoom " << maxZoom << "x"
                  << " | fog " << (noFog ? "OFF" : "ON")
                  << " | wheel " << (wheel ? "ON" : "OFF")
                  << " | radar " << (radarEnabled ? "ON" : "OFF")
                  << " | booster " << (fpsBoosterEnabled ? "ON" : "OFF") << std::endl;
    }

    // 1. Khoi tao Security & HWID License Engine
    g_machineHwid = poe2::security::GetMachineHWID();
    auto licPath = poe2::security::GetDefaultLicenseFilePath();
    std::string savedKey = poe2::security::LoadLicenseFile(licPath);
    g_licenseInfo = poe2::security::VerifyProductKey(savedKey, g_machineHwid);

    if (g_consoleEnabled) {
        std::cout << "[Poe2VisualTool v2.0 Native] HWID: " << g_machineHwid
                  << " | License: " << poe2::security::GetTierName(g_licenseInfo.tier)
                  << (g_licenseInfo.isValid ? " [ACTIVE]" : " [TRIAL/FREE]") << std::endl;
    }

    // 2. Kiem tra cap nhat Offsets tu Cloud (WinHTTP Background)
    std::thread([]() {
        poe2::platform::CloudOffsetsSync sync("offsets.toml");
        auto res = sync.CheckAndUpdate();
        if (res.updated && g_consoleEnabled) {
            std::cout << "[CloudSync] " << res.message << std::endl;
        }
    }).detach();

    // 3. Khoi dong Native Item Inspector & Hover Watcher (Chi khi duoc bat trong config/CLI)
    if (inspectorEnabled) {
        g_hoverWatcher = std::make_unique<inspector::HoverWatcher>();
        g_hoverWatcher->SetDwellTimeMs(static_cast<uint32_t>(inspectorDwellMs));
        g_hoverWatcher->SetCallbacks(
            [](const inspector::ItemInspectionReport& report, int mx, int my) {
                if (report.isBrick) {
                    poe2::platform::SoundEffects::Instance().PlayBrickAlert();
                }

                HWND poeHwnd = common::FindPoe2Window();
                int screenW = GetSystemMetrics(SM_CXSCREEN);
                int screenH = GetSystemMetrics(SM_CYSCREEN);
                int clientX = mx;
                int clientY = my;

                if (poeHwnd) {
                    POINT pt = { mx, my };
                    ScreenToClient(poeHwnd, &pt);
                    clientX = pt.x;
                    clientY = pt.y;
                    RECT rc{};
                    if (GetClientRect(poeHwnd, &rc)) {
                        screenW = rc.right - rc.left;
                        screenH = rc.bottom - rc.top;
                    }
                }

                // Build layout OUTSIDE mutex to eliminate UI thread hitch
                auto layout = poe2::ui::TooltipView::BuildLayout(report, clientX, clientY, screenW, screenH);

                {
                    std::lock_guard<std::mutex> lock(g_tooltipMutex);
                    g_activeTooltip = std::move(layout);
                    g_lastTooltipTime = std::chrono::steady_clock::now();
                }
                g_hasTooltip.store(true, std::memory_order_release);
            },
            []() {
                // Con tro di chuyen khoi item -> an tooltip ngay lap tuc
                g_hasTooltip.store(false, std::memory_order_release);
            }
        );
        g_hoverWatcher->Start();
    }

    std::thread worker(EngineLoop, maxZoom);

    if (wheel) {
        g_hookStartedEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
        g_hookThread = std::thread(MouseHookThread);
    }

    CreateOverlay(GetModuleHandleW(nullptr));
    if (overlay) {
        g_nativeOverlay = std::make_unique<poe2::ui::OverlayWindow>();
        if (!g_nativeOverlay->Create()) {
            if (g_consoleEnabled) {
                std::cerr << "[Poe2VisualTool] Khong the khoi tao Native HUD Overlay!\n";
            }
        }
    }

    MSG msg{};
    while (g_running.load()) {
        while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) {
            if (msg.message == WM_QUIT) {
                g_running = false;
                break;
            }
            TranslateMessage(&msg);
            DispatchMessageW(&msg);
        }
        if (!overlay) {
            if (GetAsyncKeyState(VK_PAUSE) & 0x8000) {
                if (g_ipc) InterlockedOr(reinterpret_cast<volatile LONG*>(&g_ipc->flags), kVisualFlagPanic);
                SetOverlayLog(L"[PANIC] Da hoan nguyen camera ve mac dinh goc (1.0x)!");
            }
            const bool ctrl = (GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0;
            const bool shift = (GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0;
            const bool f12 = (GetAsyncKeyState(VK_F12) & 0x8000) != 0;
            if (ctrl && shift && f12 && g_ipc) {
                InterlockedOr(reinterpret_cast<volatile LONG*>(&g_ipc->flags), kVisualFlagPanic);
                SetOverlayLog(L"[PANIC] Da hoan nguyen camera ve mac dinh goc (1.0x)!");
            }
        }
        Sleep(10);
    }

    // 1. Dừng nguồn sự kiện chuột & hover watcher đầu tiên (LIFO)
    if (g_hoverWatcher) {
        g_hoverWatcher->Stop();
        g_hoverWatcher.reset();
    }

    g_running = false;

    // 2. Dừng luồng hook chuột an toàn sau khi đã tạo message queue
    if (g_hookThread.joinable()) {
        if (g_hookStartedEvent) {
            WaitForSingleObject(g_hookStartedEvent, 1000);
        }
        DWORD tid = g_hookThreadId.load(std::memory_order_acquire);
        if (tid != 0) {
            PostThreadMessageW(tid, WM_QUIT, 0, 0);
        }
        g_hookThread.join();
    }
    if (g_hookStartedEvent) {
        CloseHandle(g_hookStartedEvent);
        g_hookStartedEvent = nullptr;
    }

    // 3. Hủy System Tray, Overlay và timers
    if (g_systemTray) {
        g_systemTray->Destroy();
        g_systemTray.reset();
    }
    if (g_overlayHwnd) {
        KillTimer(g_overlayHwnd, kOverlayTimerId);
        DestroyWindow(g_overlayHwnd);
    }
    if (g_nativeOverlay) {
        g_nativeOverlay->Destroy();
        g_nativeOverlay.reset();
    }

    // 4. Chờ luồng worker EngineLoop hoàn tất
    if (worker.joinable()) worker.join();

    // 5. Giải phóng bộ nhớ IPC và mutex
    if (g_ipc) {
        UnmapViewOfFile(g_ipc);
        g_ipc = nullptr;
    }
    if (g_poiIpc) {
        UnmapViewOfFile(g_poiIpc);
        g_poiIpc = nullptr;
    }
    if (g_map) CloseHandle(g_map);
    if (g_poiMap) CloseHandle(g_poiMap);
    if (g_mutex) CloseHandle(g_mutex);
    poe2::platform::SoundEffects::Instance().Stop();
    timeEndPeriod(1);
    return 0;
}
