// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3

#include "dx11_context.h"

#ifdef _WIN32

#include <SDL3/SDL.h>
#include <dcomp.h>
#include "../util/logger.h"

// Not defined in older MinGW SDK headers
#ifndef WS_EX_NOREDIRECTIONBITMAP
#define WS_EX_NOREDIRECTIONBITMAP 0x00200000L
#endif

namespace dragonx {
namespace platform {

// Helper to get HWND from SDL window
static HWND getHWND(SDL_Window* window) {
    if (!window) return nullptr;
    SDL_PropertiesID props = SDL_GetWindowProperties(window);
    if (props == 0) return nullptr;
    return (HWND)SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr);
}

bool DX11Context::init(SDL_Window* window)
{
    HWND hwnd = getHWND(window);
    if (!hwnd) {
        DEBUG_LOGF("DX11: Failed to get HWND from SDL window\n");
        return false;
    }
    hwnd_ = hwnd;

    // Ensure WS_EX_NOREDIRECTIONBITMAP is set.  The main code creates the
    // HWND with this style, but if someone passes a different window we try
    // to set it here as a fallback.  We also call SetWindowPos with
    // SWP_FRAMECHANGED so the DWM re-evaluates the extended style.
    LONG_PTR exStyle = GetWindowLongPtrW(hwnd, GWL_EXSTYLE);
    if (!(exStyle & WS_EX_NOREDIRECTIONBITMAP)) {
        SetWindowLongPtrW(hwnd, GWL_EXSTYLE, exStyle | WS_EX_NOREDIRECTIONBITMAP);
        SetWindowPos(hwnd, nullptr, 0, 0, 0, 0,
                     SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
        LONG_PTR verify = GetWindowLongPtrW(hwnd, GWL_EXSTYLE);
        if (verify & WS_EX_NOREDIRECTIONBITMAP)
            DEBUG_LOGF("DX11: WS_EX_NOREDIRECTIONBITMAP set via fallback path\n");
        else
            DEBUG_LOGF("DX11: WARNING - WS_EX_NOREDIRECTIONBITMAP could NOT be set!\n");
    } else {
        DEBUG_LOGF("DX11: WS_EX_NOREDIRECTIONBITMAP already present (good)\n");
    }
    fflush(stdout);

    // Create D3D11 device and context
    D3D_FEATURE_LEVEL featureLevel;
    const D3D_FEATURE_LEVEL featureLevelArray[] = {
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_0,
    };

    // Need BGRA support for DirectComposition
    UINT createDeviceFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;

    HRESULT hr = D3D11CreateDevice(
        nullptr,                    // Default adapter
        D3D_DRIVER_TYPE_HARDWARE,   // Hardware rendering
        nullptr,                    // No software module
        createDeviceFlags,
        featureLevelArray,
        2,
        D3D11_SDK_VERSION,
        &device_,
        &featureLevel,
        &deviceContext_
    );

    if (FAILED(hr)) {
        DEBUG_LOGF("DX11: D3D11CreateDevice failed (HRESULT 0x%08lx)\n", hr);
        return false;
    }

    DEBUG_LOGF("DX11: Device created (feature level %d.%d)\n",
           (featureLevel >> 12) & 0xF, (featureLevel >> 8) & 0xF);

    // Get DXGI device for factory access and DirectComposition
    IDXGIDevice* dxgiDevice = nullptr;
    hr = device_->QueryInterface(IID_PPV_ARGS(&dxgiDevice));
    if (FAILED(hr)) {
        DEBUG_LOGF("DX11: QueryInterface IDXGIDevice failed\n");
        shutdown();
        return false;
    }

    IDXGIAdapter* dxgiAdapter = nullptr;
    hr = dxgiDevice->GetAdapter(&dxgiAdapter);
    if (FAILED(hr)) {
        dxgiDevice->Release();
        DEBUG_LOGF("DX11: GetAdapter failed\n");
        shutdown();
        return false;
    }

    IDXGIFactory2* dxgiFactory = nullptr;
    hr = dxgiAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory));
    dxgiAdapter->Release();
    if (FAILED(hr)) {
        dxgiDevice->Release();
        DEBUG_LOGF("DX11: GetParent IDXGIFactory2 failed\n");
        shutdown();
        return false;
    }

    // ---------------------------------------------------------------
    // Strategy: CreateSwapChainForComposition + DirectComposition
    //
    // DXGI_ALPHA_MODE_PREMULTIPLIED only works with composition swap chains.
    // CreateSwapChainForHwnd does NOT support premultiplied alpha.
    // We must use DirectComposition to bind the composition swap chain
    // to the window. This is how Windows Terminal achieves transparency.
    // ---------------------------------------------------------------

    DXGI_SWAP_CHAIN_DESC1 sd = {};
    sd.Width = 0;   // Auto-detect from HWND
    sd.Height = 0;
    sd.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    sd.Stereo = FALSE;
    sd.SampleDesc.Count = 1;
    sd.SampleDesc.Quality = 0;
    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    sd.BufferCount = 2;
    sd.Scaling = DXGI_SCALING_STRETCH;
    sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
    sd.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
    sd.Flags = 0;

    // CreateSwapChainForComposition needs explicit width/height
    RECT clientRect;
    GetClientRect(hwnd, &clientRect);
    sd.Width = clientRect.right - clientRect.left;
    sd.Height = clientRect.bottom - clientRect.top;
    if (sd.Width == 0) sd.Width = 1400;
    if (sd.Height == 0) sd.Height = 900;

    hr = dxgiFactory->CreateSwapChainForComposition(
        device_,
        &sd,
        nullptr,
        &swapChain_
    );

    if (SUCCEEDED(hr)) {
        DEBUG_LOGF("DX11: Composition swap chain created (%ux%u, PREMULTIPLIED alpha)\n",
               sd.Width, sd.Height);

        // Create DirectComposition device and bind swap chain to HWND
        hr = DCompositionCreateDevice(dxgiDevice, IID_PPV_ARGS(&dcompDevice_));
        if (SUCCEEDED(hr)) {
            hr = dcompDevice_->CreateTargetForHwnd(hwnd, TRUE, &dcompTarget_);
        }
        if (SUCCEEDED(hr)) {
            hr = dcompDevice_->CreateVisual(&dcompVisual_);
        }
        if (SUCCEEDED(hr)) {
            hr = dcompVisual_->SetContent(swapChain_);
        }
        if (SUCCEEDED(hr)) {
            hr = dcompTarget_->SetRoot(dcompVisual_);
        }
        if (SUCCEEDED(hr)) {
            hr = dcompDevice_->Commit();
        }

        if (SUCCEEDED(hr)) {
            hasAlpha_ = true;
            DEBUG_LOGF("DX11: DirectComposition bound to HWND (alpha compositing active)\n");
            fflush(stdout);
        } else {
            DEBUG_LOGF("DX11: DirectComposition setup failed (0x%08lx), falling back\n", hr);
            fflush(stderr);
            // Clean up the composition objects and swap chain
            if (dcompVisual_) { dcompVisual_->Release(); dcompVisual_ = nullptr; }
            if (dcompTarget_) { dcompTarget_->Release(); dcompTarget_ = nullptr; }
            if (dcompDevice_) { dcompDevice_->Release(); dcompDevice_ = nullptr; }
            swapChain_->Release();
            swapChain_ = nullptr;
        }
    } else {
        DEBUG_LOGF("DX11: CreateSwapChainForComposition failed (0x%08lx)\n", hr);
    }

    // Fallback: standard HWND swap chain (no alpha transparency)
    if (!swapChain_) {
        sd.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
        sd.Width = 0;   // Auto-detect
        sd.Height = 0;
        hr = dxgiFactory->CreateSwapChainForHwnd(
            device_,
            hwnd,
            &sd,
            nullptr,
            nullptr,
            &swapChain_
        );
        if (SUCCEEDED(hr)) {
            DEBUG_LOGF("DX11: HWND swap chain created (opaque, no alpha)\n");
        }
    }

    dxgiFactory->Release();
    dxgiDevice->Release();

    if (FAILED(hr) || !swapChain_) {
        DEBUG_LOGF("DX11: All swap chain creation paths failed\n");
        shutdown();
        return false;
    }

    createRenderTarget();
    return true;
}

void DX11Context::shutdown()
{
    cleanupRenderTarget();

    // Release DirectComposition objects
    if (dcompVisual_) { dcompVisual_->Release(); dcompVisual_ = nullptr; }
    if (dcompTarget_) { dcompTarget_->Release(); dcompTarget_ = nullptr; }
    if (dcompDevice_) { dcompDevice_->Release(); dcompDevice_ = nullptr; }

    if (swapChain_) {
        swapChain_->Release();
        swapChain_ = nullptr;
    }
    if (deviceContext_) {
        deviceContext_->Release();
        deviceContext_ = nullptr;
    }
    if (device_) {
        device_->Release();
        device_ = nullptr;
    }
    hasAlpha_ = false;
}

void DX11Context::resize(int width, int height)
{
    (void)width;
    (void)height;
    if (!swapChain_ || !deviceContext_) return;

    cleanupRenderTarget();

    // Unbind the render target from the pipeline — ResizeBuffers requires
    // ALL outstanding references to back-buffer resources to be released.
    // Without this, ResizeBuffers fails with DXGI_ERROR_INVALID_CALL when
    // growing the window (shrinking appears fine because the old larger
    // buffer simply gets cropped by DWM).
    deviceContext_->OMSetRenderTargets(0, nullptr, nullptr);
    deviceContext_->Flush();

    // For composition swap chains (CreateSwapChainForComposition),
    // passing 0,0 means "keep current size" — NOT auto-detect from HWND.
    // We must pass the actual pixel dimensions from GetClientRect.
    UINT w = 0, h = 0;
    if (hwnd_) {
        RECT rc;
        GetClientRect(hwnd_, &rc);
        w = static_cast<UINT>(rc.right - rc.left);
        h = static_cast<UINT>(rc.bottom - rc.top);
    }
    if (w == 0 || h == 0) return;

    HRESULT hr = swapChain_->ResizeBuffers(0, w, h, DXGI_FORMAT_UNKNOWN, 0);
    if (FAILED(hr)) {
        DEBUG_LOGF("DX11: ResizeBuffers(%u x %u) failed (0x%08lx)\n", w, h, hr);
    }
    createRenderTarget();

    // Commit DirectComposition so it picks up the new buffer size
    if (dcompDevice_) {
        dcompDevice_->Commit();
    }
}

void DX11Context::ensureSize()
{
    if (!swapChain_ || !hwnd_) return;

    // Query current swap chain buffer dimensions
    DXGI_SWAP_CHAIN_DESC1 desc;
    if (FAILED(swapChain_->GetDesc1(&desc))) return;

    // Query actual HWND client area
    RECT rc;
    GetClientRect(hwnd_, &rc);
    UINT clientW = static_cast<UINT>(rc.right - rc.left);
    UINT clientH = static_cast<UINT>(rc.bottom - rc.top);

    // Resize only when there's a mismatch
    if (clientW > 0 && clientH > 0 &&
        (desc.Width != clientW || desc.Height != clientH)) {
        resize(static_cast<int>(clientW), static_cast<int>(clientH));
    }
}

void DX11Context::clear(float r, float g, float b, float a)
{
    if (!deviceContext_ || !renderTargetView_) return;
    const float clearColor[4] = { r, g, b, a };
    deviceContext_->ClearRenderTargetView(renderTargetView_, clearColor);
}

void DX11Context::present(int vsync)
{
    if (!swapChain_) return;
    swapChain_->Present(vsync ? 1 : 0, 0);
}

void DX11Context::createRenderTarget()
{
    if (!swapChain_ || !device_) return;

    ID3D11Texture2D* backBuffer = nullptr;
    swapChain_->GetBuffer(0, IID_PPV_ARGS(&backBuffer));
    if (backBuffer) {
        device_->CreateRenderTargetView(backBuffer, nullptr, &renderTargetView_);
        backBuffer->Release();
    }
}

void DX11Context::cleanupRenderTarget()
{
    if (renderTargetView_) {
        renderTargetView_->Release();
        renderTargetView_ = nullptr;
    }
}

} // namespace platform
} // namespace dragonx

#endif // _WIN32
