// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// GPU alpha mask — the ImGui equivalent of CSS mask-image: linear-gradient().
// Uses AddCallback to switch the GPU blend mode so that gradient quads
// multiply the framebuffer's alpha (and RGB) by the source alpha, producing
// a smooth per-pixel fade without vertex-spacing artefacts.

#pragma once

#include "imgui.h"

#ifdef DRAGONX_USE_DX11
  #include <d3d11.h>
#else
  #ifdef DRAGONX_HAS_GLAD
    #include <glad/gl.h>
  #else
    #include <SDL3/SDL_opengl.h>
  #endif
  #include <SDL3/SDL.h>  // for SDL_GL_GetProcAddress
#endif

namespace dragonx {
namespace ui {
namespace material {

// ============================================================================
// Blend-mode callbacks — called by ImGui's backend during draw list rendering
// ============================================================================

#ifdef DRAGONX_USE_DX11

// Cached DX11 blend state for the mask pass
inline ID3D11BlendState* GetMaskBlendState() {
    static ID3D11BlendState* s_maskBlend = nullptr;
    if (!s_maskBlend) {
        ImGuiIO& io = ImGui::GetIO();
        if (!io.BackendRendererUserData) return nullptr;
        ID3D11Device* dev = *reinterpret_cast<ID3D11Device**>(io.BackendRendererUserData);
        if (!dev) return nullptr;

        D3D11_BLEND_DESC desc = {};
        desc.RenderTarget[0].BlendEnable    = TRUE;
        desc.RenderTarget[0].SrcBlend       = D3D11_BLEND_ZERO;
        desc.RenderTarget[0].DestBlend      = D3D11_BLEND_SRC_ALPHA;
        desc.RenderTarget[0].BlendOp        = D3D11_BLEND_OP_ADD;
        desc.RenderTarget[0].SrcBlendAlpha  = D3D11_BLEND_ZERO;
        desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_SRC_ALPHA;
        desc.RenderTarget[0].BlendOpAlpha   = D3D11_BLEND_OP_ADD;
        desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
        dev->CreateBlendState(&desc, &s_maskBlend);
    }
    return s_maskBlend;
}

// Switch to mask blend: dst *= srcAlpha (both RGB and A)
inline void MaskBlendCallback(const ImDrawList*, const ImDrawCmd*) {
    ImGuiIO& io = ImGui::GetIO();
    if (!io.BackendRendererUserData) return;
    // The ImGui DX11 backend stores the device as the first pointer
    ID3D11Device* dev = *reinterpret_cast<ID3D11Device**>(io.BackendRendererUserData);
    if (!dev) return;
    ID3D11DeviceContext* ctx = nullptr;
    dev->GetImmediateContext(&ctx);
    if (!ctx) return;

    ID3D11BlendState* bs = GetMaskBlendState();
    if (bs) {
        float blendFactor[4] = {0, 0, 0, 0};
        ctx->OMSetBlendState(bs, blendFactor, 0xFFFFFFFF);
    }
    ctx->Release();
}

// Restore normal ImGui blend: src*srcA + dst*(1-srcA)
inline void RestoreBlendCallback(const ImDrawList*, const ImDrawCmd*) {
    ImGuiIO& io = ImGui::GetIO();
    if (!io.BackendRendererUserData) return;
    ID3D11Device* dev = *reinterpret_cast<ID3D11Device**>(io.BackendRendererUserData);
    if (!dev) return;
    ID3D11DeviceContext* ctx = nullptr;
    dev->GetImmediateContext(&ctx);
    if (!ctx) return;
    // Setting nullptr restores the default blend state that ImGui's DX11
    // backend configures at the start of each frame.
    ctx->OMSetBlendState(nullptr, nullptr, 0xFFFFFFFF);
    ctx->Release();
}

#else // OpenGL

// glBlendFuncSeparate may not be in the GLAD profile — load it once via SDL.
typedef void (*PFN_glBlendFuncSeparate)(GLenum, GLenum, GLenum, GLenum);
inline PFN_glBlendFuncSeparate GetBlendFuncSeparate() {
    static PFN_glBlendFuncSeparate fn = nullptr;
    static bool resolved = false;
    if (!resolved) {
        resolved = true;
        fn = (PFN_glBlendFuncSeparate)(void*)SDL_GL_GetProcAddress("glBlendFuncSeparate");
    }
    return fn;
}

inline void MaskBlendCallback(const ImDrawList*, const ImDrawCmd*) {
    // dst.rgb = dst.rgb * srcAlpha  (erase content where mask alpha < 1)
    // dst.a   = dst.a   * srcAlpha  (match alpha channel)
    auto fn = GetBlendFuncSeparate();
    if (fn)
        fn(GL_ZERO, GL_SRC_ALPHA, GL_ZERO, GL_SRC_ALPHA);
    else
        glBlendFunc(GL_ZERO, GL_SRC_ALPHA);
}

inline void RestoreBlendCallback(const ImDrawList*, const ImDrawCmd*) {
    // Restore ImGui's exact blend state:
    //   RGB:   src*srcA + dst*(1-srcA)
    //   Alpha: src*1    + dst*(1-srcA)
    auto fn = GetBlendFuncSeparate();
    if (fn)
        fn(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
    else
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}

#endif

// ============================================================================
// DrawScrollFadeMask — draw gradient quads that mask the top/bottom edges
// of a scrollable child region, producing a smooth per-pixel fade.
//
// Call this on the child's draw list BEFORE EndChild().
// The gradient quads use the mask blend mode to multiply the existing
// framebuffer content by their alpha, so alpha=1 means "keep" and
// alpha=0 means "erase to transparent/black".
//
// Parameters:
//   dl          — the child window's draw list (ImGui::GetWindowDrawList())
//   clipMin/Max — the child window's visible area (screen coords)
//   fadeH       — the height of the fade zone in pixels
//   scrollY     — current scroll offset (ImGui::GetScrollY())
//   scrollMaxY  — maximum scroll offset (ImGui::GetScrollMaxY())
// ============================================================================
inline void DrawScrollFadeMask(ImDrawList* dl,
                               const ImVec2& clipMin, const ImVec2& clipMax,
                               float fadeH,
                               float scrollY, float scrollMaxY)
{
    if (fadeH <= 0.0f) return;

    bool needTop    = scrollY > 1.0f;
    bool needBottom = scrollMaxY > 0 && scrollY < scrollMaxY - 1.0f;
    if (!needTop && !needBottom) return;

    float left  = clipMin.x;
    float right = clipMax.x;

    // Switch to mask blend mode
    dl->AddCallback(MaskBlendCallback, nullptr);

    if (needTop) {
        // Top gradient: alpha=0 at top edge (erase) → alpha=1 at top+fadeH (keep)
        ImVec2 tMin(left, clipMin.y);
        ImVec2 tMax(right, clipMin.y + fadeH);
        ImU32 transparent = IM_COL32(0, 0, 0, 0);
        ImU32 opaque      = IM_COL32(0, 0, 0, 255);
        dl->AddRectFilledMultiColor(tMin, tMax,
            transparent, transparent,  // top-left, top-right
            opaque, opaque);           // bottom-left, bottom-right
    }

    if (needBottom) {
        // Bottom gradient: alpha=1 at bottom-fadeH (keep) → alpha=0 at bottom (erase)
        ImVec2 bMin(left, clipMax.y - fadeH);
        ImVec2 bMax(right, clipMax.y);
        ImU32 opaque      = IM_COL32(0, 0, 0, 255);
        ImU32 transparent = IM_COL32(0, 0, 0, 0);
        dl->AddRectFilledMultiColor(bMin, bMax,
            opaque, opaque,            // top-left, top-right
            transparent, transparent); // bottom-left, bottom-right
    }

    // Restore normal blend mode
    dl->AddCallback(RestoreBlendCallback, nullptr);
}

} // namespace material
} // namespace ui
} // namespace dragonx
