#include "OverlayWindow.h"

#include <imgui.h>
#include <imgui_impl_win32.h>
#include <imgui_impl_dx11.h>

// Vendored Lumin framework — provides the FreeType-backed font system (`font`),
// shared state (`var`) and the embedded font data (inter_*, icon_font).
#include "framework/headers/includes.h"

#include <dwmapi.h>
#pragma comment(lib, "dwmapi.lib")

#include <spdlog/spdlog.h>

extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND, UINT, WPARAM, LPARAM);

static constexpr wchar_t kWndClass[] = L"DayZEspWindow";

// ESP fonts go through the Lumin font system so they survive its atlas
// rebuilds.  dpi is locked to 1.0 (variables.h) so these map to exact pixels.
static const char* kEspFontPath = "C:\\Windows\\Fonts\\tahoma.ttf";

// Register every font size the Lumin menu can request, so the atlas is built
// up front and (ideally) never rebuilt mid-run.  Any size missed here is still
// handled gracefully by the per-frame rebuild check in Run().
static void WarmMenuFonts() {
    font->get(inter_medium, 10);
    font->get(inter_medium, 11);
    font->get(inter_semibold, 8);
    font->get(inter_semibold, 10);
    font->get(inter_semibold, 11);
    font->get(inter_semibold, 12);
    font->get(inter_semibold, 13);
    font->get(inter_semibold, 16);
    font->get(inter_semibold, 18);
    font->get(icon_font, 13);
    font->get(icon_font, 14);
    font->get(icon_font, 15);
    font->get_file(flaticon_uicons_regular_rounded_path, 12.0f, true);
    font->get_file(flaticon_uicons_regular_rounded_path, 12.5f, true);
    font->get_file("C:\\Windows\\Fonts\\segmdl2.ttf", 11.f, true);
}

// -------------------------------------------------------------------------
// WndProc
// -------------------------------------------------------------------------

LRESULT WINAPI OverlayWindow::WndProc(HWND h, UINT msg, WPARAM w, LPARAM l) {
    if (ImGui_ImplWin32_WndProcHandler(h, msg, w, l)) return true;
    switch (msg) {
    case WM_SIZE:
        if (w != SIZE_MINIMIZED) {
            // Store pending resize; handled at top of render loop
            if (auto* self = reinterpret_cast<OverlayWindow*>(GetWindowLongPtrW(h, GWLP_USERDATA))) {
                self->m_resizeW = LOWORD(l);
                self->m_resizeH = HIWORD(l);
            }
        }
        return 0;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    case WM_SYSCOMMAND:
        if ((w & 0xFFF0) == SC_KEYMENU) return 0;
        break;
    }
    return DefWindowProcW(h, msg, w, l);
}

// -------------------------------------------------------------------------
// CreateWin32Window
// -------------------------------------------------------------------------

bool OverlayWindow::CreateWin32Window() {
    m_w = (m_overrideW > 0) ? m_overrideW : GetSystemMetrics(SM_CXSCREEN);
    m_h = (m_overrideH > 0) ? m_overrideH : GetSystemMetrics(SM_CYSCREEN);

    WNDCLASSEXW wc{};
    wc.cbSize        = sizeof(wc);
    wc.style         = CS_CLASSDC;
    wc.lpfnWndProc   = WndProc;
    wc.hInstance     = GetModuleHandle(nullptr);
    wc.hCursor       = LoadCursor(nullptr, IDC_ARROW);
    wc.lpszClassName = kWndClass;
    if (!RegisterClassExW(&wc)) return false;

    m_hwnd = CreateWindowExW(
        WS_EX_APPWINDOW,
        kWndClass, L"DayZ DMA",
        WS_POPUP | WS_VISIBLE,
        0, 0, m_w, m_h,
        nullptr, nullptr, wc.hInstance, nullptr
    );
    if (!m_hwnd) return false;

    // Store 'this' so WndProc can find us for resize events
    SetWindowLongPtrW(m_hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
    ShowWindow(m_hwnd, SW_SHOWDEFAULT);
    UpdateWindow(m_hwnd);
    return true;
}

// -------------------------------------------------------------------------
// ResizeTo
// -------------------------------------------------------------------------

void OverlayWindow::ResizeTo(int w, int h) {
    if (!m_hwnd || w <= 0 || h <= 0) return;
    SetWindowPos(m_hwnd, nullptr, 0, 0, w, h,
                 SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
    // WM_SIZE will be posted; the render loop picks it up via m_resizeW/m_resizeH.
}

// -------------------------------------------------------------------------
// CreateDX11
// -------------------------------------------------------------------------

bool OverlayWindow::CreateDX11() {
    DXGI_SWAP_CHAIN_DESC sd{};
    sd.BufferCount                        = 2;
    sd.BufferDesc.Width                   = 0;
    sd.BufferDesc.Height                  = 0;
    sd.BufferDesc.Format                  = DXGI_FORMAT_R8G8B8A8_UNORM;
    sd.BufferDesc.RefreshRate.Numerator   = 60;
    sd.BufferDesc.RefreshRate.Denominator = 1;
    sd.Flags                              = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
    sd.BufferUsage                        = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    sd.OutputWindow                       = m_hwnd;
    sd.SampleDesc.Count                   = 1;
    sd.Windowed                           = TRUE;
    sd.SwapEffect                         = DXGI_SWAP_EFFECT_FLIP_DISCARD;

    const D3D_FEATURE_LEVEL fla[] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_0 };
    D3D_FEATURE_LEVEL fl{};

    HRESULT hr = D3D11CreateDeviceAndSwapChain(
        nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0,
        fla, 2, D3D11_SDK_VERSION,
        &sd, &m_sc, &m_dev, &fl, &m_ctx
    );
    if (FAILED(hr))
        hr = D3D11CreateDeviceAndSwapChain(
            nullptr, D3D_DRIVER_TYPE_WARP, nullptr, 0,
            fla, 2, D3D11_SDK_VERSION,
            &sd, &m_sc, &m_dev, &fl, &m_ctx);

    return SUCCEEDED(hr);
}

// -------------------------------------------------------------------------
// RTV helpers
// -------------------------------------------------------------------------

void OverlayWindow::CreateRTV() {
    ID3D11Texture2D* back = nullptr;
    if (SUCCEEDED(m_sc->GetBuffer(0, IID_PPV_ARGS(&back)))) {
        m_dev->CreateRenderTargetView(back, nullptr, &m_rtv);
        back->Release();
    }
}

void OverlayWindow::CleanupRTV() {
    if (m_rtv) { m_rtv->Release(); m_rtv = nullptr; }
}

// -------------------------------------------------------------------------
// Destroy
// -------------------------------------------------------------------------

void OverlayWindow::Destroy() {
    ImGui_ImplDX11_Shutdown();
    ImGui_ImplWin32_Shutdown();
    if (ImGui::GetCurrentContext()) ImGui::DestroyContext();
    CleanupRTV();
    if (m_sc)  { m_sc->Release();  m_sc  = nullptr; }
    if (m_ctx) { m_ctx->Release(); m_ctx = nullptr; }
    if (m_dev) { m_dev->Release(); m_dev = nullptr; }
    if (m_hwnd){ DestroyWindow(m_hwnd); m_hwnd = nullptr; }
    UnregisterClassW(kWndClass, GetModuleHandle(nullptr));
}

// -------------------------------------------------------------------------
// Run
// -------------------------------------------------------------------------

void OverlayWindow::Run(std::atomic<bool>& stopFlag) {
    if (!CreateWin32Window() || !CreateDX11()) {
        spdlog::error("OverlayWindow: init failed.");
        Destroy();
        stopFlag.store(true);
        return;
    }
    CreateRTV();

    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO& io = ImGui::GetIO();
    io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange;

    ImGui::StyleColorsDark();
    // Make window backgrounds semi-transparent so the game is partially visible
    ImGui::GetStyle().Colors[ImGuiCol_WindowBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.0f);

    ImGui_ImplWin32_Init(m_hwnd);
    ImGui_ImplDX11_Init(m_dev, m_ctx);

    // ---- Fonts via the Lumin FreeType atlas (must run after backend init) ----
    WarmMenuFonts();
    font->get_file(kEspFontPath, 14.f);   // ESP base  -> io.FontDefault
    font->get_file(kEspFontPath, 16.f);   // loot close (<50 m)
    font->get_file(kEspFontPath, 11.f);   // loot far  (>=50 m)

    var->gui.dpi_changed = true;
    font->update();                       // build the atlas once

    if (ImFont* base = font->get_file(kEspFontPath, 14.f)) io.FontDefault = base;
    ImFont* fontLootClose = font->get_file(kEspFontPath, 16.f);
    ImFont* fontLootFar   = font->get_file(kEspFontPath, 11.f);

    // Notify caller that fonts are ready — must be done after the atlas build
    // so the ImFont pointers are fully populated.
    if (m_onFontReady) m_onFontReady(fontLootClose, fontLootFar);

    spdlog::info("ESP window running ({}x{}).", m_w, m_h);

    MSG msg{};
    bool done = false;
    while (!stopFlag.load() && !done) {
        while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            if (msg.message == WM_QUIT) { done = true; break; }
        }
        if (done) break;

        // Handle deferred resize
        if (m_resizeW != 0 && m_resizeH != 0) {
            CleanupRTV();
            m_sc->ResizeBuffers(0, m_resizeW, m_resizeH, DXGI_FORMAT_UNKNOWN, 0);
            m_w = static_cast<int>(m_resizeW);
            m_h = static_cast<int>(m_resizeH);
            m_resizeW = m_resizeH = 0;
            CreateRTV();
        }

        // The Lumin menu registers font sizes lazily; rebuild the atlas when a
        // new one appears (must happen outside a frame) and refresh the cached
        // ESP font pointers so they never dangle across a rebuild.
        if (var->gui.dpi_changed) {
            font->update();
            if (ImFont* base = font->get_file(kEspFontPath, 14.f)) io.FontDefault = base;
            if (m_onFontReady)
                m_onFontReady(font->get_file(kEspFontPath, 16.f),
                              font->get_file(kEspFontPath, 11.f));
        }

        ImGui_ImplDX11_NewFrame();
        ImGui_ImplWin32_NewFrame();
        ImGui::NewFrame();

        if (m_draw) m_draw(static_cast<float>(m_w), static_cast<float>(m_h));

        // Toggle interactivity: remove WS_EX_NOACTIVATE when the menu is open
        // so ImGui can receive mouse/keyboard input, restore it when closed so
        // the overlay doesn't steal focus during gameplay.
        if (m_queryInput) {
            bool wantInput = m_queryInput();
            if (wantInput != m_prevWantInput) {
                LONG_PTR ex = GetWindowLongPtrW(m_hwnd, GWL_EXSTYLE);
                if (wantInput) {
                    ex &= ~static_cast<LONG_PTR>(WS_EX_NOACTIVATE);
                    SetWindowLongPtrW(m_hwnd, GWL_EXSTYLE, ex);
                    SetForegroundWindow(m_hwnd);
                } else {
                    ex |= WS_EX_NOACTIVATE;
                    SetWindowLongPtrW(m_hwnd, GWL_EXSTYLE, ex);
                }
                m_prevWantInput = wantInput;
            }
        }

        ImGui::Render();

        // Pure black background
        const float clear[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
        m_ctx->OMSetRenderTargets(1, &m_rtv, nullptr);
        m_ctx->ClearRenderTargetView(m_rtv, clear);
        ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
        DwmFlush();            // sync to DWM compositor vblank before present
        m_sc->Present(1, 0);  // 1 = vsync interval
    }

    Destroy();
    stopFlag.store(true);
}
