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

#include "imgui_acrylic.h"
#include "../schema/ui_schema.h"
#include "../../util/noise_texture.h"

#ifdef DRAGONX_HAS_GLAD
#include <glad/gl.h>
#endif

#include <cstdio>
#include <cmath>
#include "../../util/logger.h"

// ============================================================================
// Frosted scrim — shared between GLAD and non-GLAD builds.
// Drawn on the popup's draw list as the first command so it appears behind
// the popup content but over the rest of the UI.  Replaces ImGui's default
// solid-colour ModalWindowDimBg with a translucent frosted overlay + noise.
// ============================================================================
namespace dragonx { namespace ui { namespace effects { namespace ImGuiAcrylic {
static void DrawFrostedScrim(ImDrawList* dl)
{
    const auto& S = ui::schema::UI();
    auto sde = [&](const char* key, float fb) -> float {
        float v = S.drawElement("components.modal-scrim", key).size;
        return v >= 0.0f ? v : fb;
    };

    ImGuiViewport* vp = ImGui::GetMainViewport();
    ImVec2 vpMin = vp->Pos;
    ImVec2 vpMax(vp->Pos.x + vp->Size.x, vp->Pos.y + vp->Size.y);

    // Semi-transparent white fill (the "frost")
    int fillAlpha = (int)sde("fill-alpha", 12.0f);
    dl->AddRectFilled(vpMin, vpMax, IM_COL32(255, 255, 255, fillAlpha));

    // Noise grain overlay — single draw call via pre-tiled texture
    {
        int noiseAlpha = (int)sde("noise-alpha", 14.0f);
        ImU32 noiseTint = IM_COL32(255, 255, 255, noiseAlpha);
        dragonx::util::DrawTiledNoiseRect(dl, vpMin, vpMax, noiseTint);
    }
}
} } } } // namespace dragonx::ui::effects::ImGuiAcrylic

// ============================================================================
// Preset table — shared by all backends
// ============================================================================
namespace dragonx { namespace ui { namespace effects { namespace ImGuiAcrylic {

struct PresetDef {
    const char* label;
    AcrylicQuality quality;
    float blur;
};

static const PresetDef kPresets[kPresetCount] = {
    { "Off",      AcrylicQuality::Off,    0.0f  },
    { "Subtle",   AcrylicQuality::Low,    0.6f  },
    { "Light",    AcrylicQuality::Medium, 1.0f  },
    { "Standard", AcrylicQuality::Medium, 1.5f  },
    { "Strong",   AcrylicQuality::High,   2.5f  },
    { "Frosted",  AcrylicQuality::High,   4.0f  },
};

const char* GetPresetLabel(int preset)
{
    if (preset < 0 || preset >= kPresetCount) return "?";
    return kPresets[preset].label;
}

AcrylicQuality PresetQuality(int preset)
{
    if (preset < 0 || preset >= kPresetCount) return AcrylicQuality::Medium;
    return kPresets[preset].quality;
}

float PresetBlur(int preset)
{
    if (preset < 0 || preset >= kPresetCount) return 1.0f;
    return kPresets[preset].blur;
}

// Find closest preset matching given quality + blur
int MatchPreset(AcrylicQuality q, float blur)
{
    // Exact match first
    for (int i = 0; i < kPresetCount; ++i) {
        if (kPresets[i].quality == q &&
            std::abs(kPresets[i].blur - blur) < 0.01f)
            return i;
    }
    // Closest blur within same quality
    int best = 3; // Standard fallback
    float bestDist = 999.f;
    for (int i = 0; i < kPresetCount; ++i) {
        if (kPresets[i].quality == q) {
            float d = std::abs(kPresets[i].blur - blur);
            if (d < bestDist) { bestDist = d; best = i; }
        }
    }
    return best;
}

} } } } // namespace dragonx::ui::effects::ImGuiAcrylic

// Single ImGuiAcrylic frontend for BOTH GL (GLAD) and DX11: the frontend makes no
// direct GL/DX calls — every backend op is delegated to AcrylicMaterial, which has a
// backend for each API — so it must not fork per-backend.
#if defined(DRAGONX_HAS_GLAD) || defined(DRAGONX_USE_DX11)

namespace dragonx {
namespace ui {
namespace effects {

namespace ImGuiAcrylic {

// ============================================================================
// Static State
// ============================================================================

static bool s_initialized = false;
static int s_viewportWidth = 0;
static int s_viewportHeight = 0;
static bool s_backgroundCaptured = false;

// Track window state for acrylic rendering
struct AcrylicWindowState {
    ImVec2 windowPos;
    ImVec2 windowSize;
    AcrylicParams params;
    bool active;
};

static AcrylicWindowState s_currentWindow = {};

// ============================================================================
// Initialization
// ============================================================================

bool Init()
{
    if (s_initialized) {
        return true;
    }

    AcrylicMaterial& acrylic = getAcrylicMaterial();
    if (!acrylic.init()) {
        DEBUG_LOGF("ImGuiAcrylic: Failed to initialize acrylic material\n");
        return false;
    }

    s_initialized = true;
    DEBUG_LOGF("ImGuiAcrylic: Initialized successfully\n");
    return true;
}

void Shutdown()
{
    if (!s_initialized) {
        return;
    }

    getAcrylicMaterial().shutdown();
    s_initialized = false;
    DEBUG_LOGF("ImGuiAcrylic: Shut down\n");
}

bool IsAvailable()
{
    return s_initialized && getAcrylicMaterial().isInitialized();
}

// ============================================================================
// Frame Operations
// ============================================================================

void BeginFrame(int width, int height)
{
    if (!s_initialized) {
        return;
    }

    // Update viewport dimensions if changed
    if (width != s_viewportWidth || height != s_viewportHeight) {
        s_viewportWidth = width;
        s_viewportHeight = height;
        getAcrylicMaterial().resize(width, height);
    }

    // Capture the first few frames to ensure a clean initial blur,
    // then stop.  The background (gradient/image/noise) is static;
    // re-capturing + re-blurring every frame causes a subtle flicker
    // from GPU floating-point non-determinism in the blur shader.
    // Resize / theme change already sets dirtyFrames_ via markBackgroundDirty().
    static int s_warmupFrames = 3;
    if (s_warmupFrames > 0) {
        getAcrylicMaterial().markBackgroundDirty();
        --s_warmupFrames;
    }

    s_backgroundCaptured = false;
}

void CaptureBackground()
{
    if (!s_initialized || s_backgroundCaptured) {
        return;
    }

    getAcrylicMaterial().captureBackground();
    s_backgroundCaptured = true;
}

void InvalidateCapture()
{
    if (!s_initialized) return;
    getAcrylicMaterial().markBackgroundDirty();
}

// Draw callback: captures framebuffer 0 (which at this point during
// RenderDrawData() contains only the rasterized BackgroundDrawList —
// no UI windows yet) into the acrylic capture buffer.
static void BackgroundCaptureCallback(const ImDrawList*, const ImDrawCmd*)
{
    if (!s_initialized) return;
    getAcrylicMaterial().captureBackgroundDirect();
    s_backgroundCaptured = true;
}

ImDrawCallback GetBackgroundCaptureCallback()
{
    return BackgroundCaptureCallback;
}

static void LiveCaptureCallback(const ImDrawList*, const ImDrawCmd*)
{
    if (!s_initialized) return;
    getAcrylicMaterial().captureLiveFramebuffer();
}

ImDrawCallback GetLiveCaptureCallback()
{
    return LiveCaptureCallback;
}

// ============================================================================
// Drawing Functions
// ============================================================================

void DrawAcrylicRect(ImDrawList* drawList, 
                     const ImVec2& pMin, const ImVec2& pMax,
                     const AcrylicParams& params,
                     float rounding)
{
    if (!drawList) {
        return;
    }

    // If acrylic not available or disabled, draw fallback
    if (!IsAvailable() || !IsEnabled() || !params.enabled) {
        drawList->AddRectFilled(pMin, pMax, 
            ImGui::ColorConvertFloat4ToU32(params.fallbackColor), rounding);
        return;
    }

    // Background capture is now handled by a draw callback inserted
    // into the BackgroundDrawList (see GetBackgroundCaptureCallback()).
    // On the very first frame the callback hasn't fired yet, so
    // drawRect() will fall back to a tinted fill automatically.

    // Draw using acrylic material
    getAcrylicMaterial().drawRect(drawList, pMin, pMax, params, rounding);
}

void DrawAcrylicRect(ImDrawList* drawList,
                     const ImVec2& pMin, const ImVec2& pMax,
                     float rounding)
{
    DrawAcrylicRect(drawList, pMin, pMax, AcrylicMaterial::getDarkPreset(), rounding);
}

// ============================================================================
// Window Wrappers
// ============================================================================

bool BeginAcrylicWindow(const char* name, bool* p_open,
                        ImGuiWindowFlags flags,
                        const AcrylicParams& params)
{
    // Store params for use in EndAcrylicWindow
    s_currentWindow.params = params;
    s_currentWindow.active = true;

    // Ensure dialog windows stay above background windows (MainContent, StatusBar, MenuBar)
    if (!(flags & ImGuiWindowFlags_NoBringToFrontOnFocus))
        ImGui::SetNextWindowFocus();

    // Make window background transparent so acrylic shows through
    ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0));
    
    bool result = ImGui::Begin(name, p_open, flags);
    
    if (result) {
        // Get window bounds for acrylic rendering
        s_currentWindow.windowPos = ImGui::GetWindowPos();
        s_currentWindow.windowSize = ImGui::GetWindowSize();
        
        // Draw acrylic background
        ImDrawList* drawList = ImGui::GetWindowDrawList();
        ImVec2 pMin = s_currentWindow.windowPos;
        ImVec2 pMax = ImVec2(pMin.x + s_currentWindow.windowSize.x,
                             pMin.y + s_currentWindow.windowSize.y);
        
        // Get window rounding from style
        float rounding = ImGui::GetStyle().WindowRounding;
        
        DrawAcrylicRect(drawList, pMin, pMax, params, rounding);
    }
    
    return result;
}

void EndAcrylicWindow()
{
    ImGui::End();
    ImGui::PopStyleColor(); // WindowBg
    s_currentWindow.active = false;
}

// ============================================================================
// Child Region Wrappers
// ============================================================================

bool BeginAcrylicChild(const char* str_id, 
                       const ImVec2& size,
                       const AcrylicParams& params,
                       ImGuiChildFlags child_flags,
                       ImGuiWindowFlags window_flags)
{
    // Make child background transparent
    ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0, 0, 0, 0));
    
    bool result = ImGui::BeginChild(str_id, size, child_flags, window_flags);
    
    if (result) {
        // Get child bounds
        ImVec2 childPos = ImGui::GetWindowPos();
        ImVec2 childSize = ImGui::GetWindowSize();
        
        // Draw acrylic background
        ImDrawList* drawList = ImGui::GetWindowDrawList();
        ImVec2 pMin = childPos;
        ImVec2 pMax = ImVec2(pMin.x + childSize.x, pMin.y + childSize.y);
        
        float rounding = ImGui::GetStyle().ChildRounding;
        
        DrawAcrylicRect(drawList, pMin, pMax, params, rounding);
    }
    
    return result;
}

void EndAcrylicChild()
{
    ImGui::EndChild();
    ImGui::PopStyleColor(); // ChildBg
}

// ============================================================================
// Popup Wrappers
// ============================================================================

bool BeginAcrylicPopup(const char* str_id,
                       ImGuiWindowFlags flags,
                       const AcrylicParams& params)
{
    ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0, 0, 0, 0));
    
    bool result = ImGui::BeginPopup(str_id, flags);
    
    if (result) {
        ImVec2 popupPos = ImGui::GetWindowPos();
        ImVec2 popupSize = ImGui::GetWindowSize();
        
        ImDrawList* drawList = ImGui::GetWindowDrawList();
        ImVec2 pMin = popupPos;
        ImVec2 pMax = ImVec2(pMin.x + popupSize.x, pMin.y + popupSize.y);
        
        float rounding = ImGui::GetStyle().PopupRounding;
        
        DrawAcrylicRect(drawList, pMin, pMax, params, rounding);
    } else {
        ImGui::PopStyleColor(); // Only pop if not opened
    }
    
    return result;
}

void EndAcrylicPopup()
{
    ImGui::EndPopup();
    ImGui::PopStyleColor(); // PopupBg
}

bool BeginAcrylicPopupModal(const char* name, bool* p_open,
                            ImGuiWindowFlags flags,
                            const AcrylicParams& params)
{
    // Suppress ImGui's built-in dark scrim — we draw our own frosted one
    ImGui::PushStyleColor(ImGuiCol_ModalWindowDimBg, ImVec4(0, 0, 0, 0));
    ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0, 0, 0, 0));
    
    bool result = ImGui::BeginPopupModal(name, p_open, flags);
    ImGui::PopStyleColor(); // ModalWindowDimBg (only needed during Begin)
    
    if (result) {
        // Draw frosted scrim behind the popup on its own draw list
        ImDrawList* drawList = ImGui::GetWindowDrawList();
        DrawFrostedScrim(drawList);

        ImVec2 popupPos = ImGui::GetWindowPos();
        ImVec2 popupSize = ImGui::GetWindowSize();
        
        ImVec2 pMin = popupPos;
        ImVec2 pMax = ImVec2(pMin.x + popupSize.x, pMin.y + popupSize.y);
        
        float rounding = ImGui::GetStyle().PopupRounding;
        
        DrawAcrylicRect(drawList, pMin, pMax, params, rounding);
    } else {
        ImGui::PopStyleColor();
    }
    
    return result;
}

// ============================================================================
// Context Menu Helpers
// ============================================================================

bool BeginAcrylicContextItem(const char* str_id,
                             ImGuiPopupFlags popup_flags,
                             const AcrylicParams& params)
{
    ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0, 0, 0, 0));
    
    bool result = ImGui::BeginPopupContextItem(str_id, popup_flags);
    
    if (result) {
        ImVec2 popupPos = ImGui::GetWindowPos();
        ImVec2 popupSize = ImGui::GetWindowSize();
        
        ImDrawList* drawList = ImGui::GetWindowDrawList();
        ImVec2 pMin = popupPos;
        ImVec2 pMax = ImVec2(pMin.x + popupSize.x, pMin.y + popupSize.y);
        
        float rounding = ImGui::GetStyle().PopupRounding;
        
        DrawAcrylicRect(drawList, pMin, pMax, params, rounding);
    } else {
        ImGui::PopStyleColor();
    }
    
    return result;
}

bool BeginAcrylicContextWindow(const char* str_id,
                               ImGuiPopupFlags popup_flags,
                               const AcrylicParams& params)
{
    ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0, 0, 0, 0));
    
    bool result = ImGui::BeginPopupContextWindow(str_id, popup_flags);
    
    if (result) {
        ImVec2 popupPos = ImGui::GetWindowPos();
        ImVec2 popupSize = ImGui::GetWindowSize();
        
        ImDrawList* drawList = ImGui::GetWindowDrawList();
        ImVec2 pMin = popupPos;
        ImVec2 pMax = ImVec2(pMin.x + popupSize.x, pMin.y + popupSize.y);
        
        float rounding = ImGui::GetStyle().PopupRounding;
        
        DrawAcrylicRect(drawList, pMin, pMax, params, rounding);
    } else {
        ImGui::PopStyleColor();
    }
    
    return result;
}

// ============================================================================
// Sidebar Helper
// ============================================================================

void DrawSidebarBackground(float width, const AcrylicParams& params)
{
    ImGuiViewport* viewport = ImGui::GetMainViewport();
    
    // Sidebar starts below menu bar
    float menuBarHeight = ImGui::GetFrameHeight();
    
    ImVec2 pMin = ImVec2(viewport->WorkPos.x, viewport->WorkPos.y + menuBarHeight);
    ImVec2 pMax = ImVec2(pMin.x + width, viewport->WorkPos.y + viewport->WorkSize.y);
    
    // Draw to background draw list
    ImDrawList* drawList = ImGui::GetBackgroundDrawList();
    
    DrawAcrylicRect(drawList, pMin, pMax, params, 0.0f);
}

// ============================================================================
// Settings
// ============================================================================

void SetQuality(AcrylicQuality quality)
{
    if (s_initialized) {
        getAcrylicMaterial().setQuality(quality);
    }
}

AcrylicQuality GetQuality()
{
    if (s_initialized) {
        return getAcrylicMaterial().getQuality();
    }
    return AcrylicQuality::Off;
}

void SetEnabled(bool enabled)
{
    if (s_initialized) {
        getAcrylicMaterial().setEnabled(enabled);
    }
}

bool IsEnabled()
{
    if (s_initialized) {
        return getAcrylicMaterial().isEnabled();
    }
    return false;
}

void SetBlurMultiplier(float multiplier)
{
    if (s_initialized) {
        getAcrylicMaterial().setBlurMultiplier(multiplier);
    }
}

float GetBlurMultiplier()
{
    if (s_initialized) {
        return getAcrylicMaterial().getBlurMultiplier();
    }
    return 1.0f;
}

void SetReducedTransparency(bool reduced)
{
    if (s_initialized) {
        getAcrylicMaterial().setReducedTransparency(reduced);
    }
}

bool GetReducedTransparency()
{
    if (s_initialized) {
        return getAcrylicMaterial().getReducedTransparency();
    }
    return false;
}

void SetUIOpacity(float opacity)
{
    if (s_initialized) {
        getAcrylicMaterial().setUIOpacity(opacity);
    }
}

float GetUIOpacity()
{
    if (s_initialized) {
        return getAcrylicMaterial().getUIOpacity();
    }
    return 1.0f;
}

void SetNoiseOpacity(float multiplier)
{
    if (s_initialized) {
        getAcrylicMaterial().setNoiseOpacityMultiplier(multiplier);
    }
}

float GetNoiseOpacity()
{
    if (s_initialized) {
        return getAcrylicMaterial().getNoiseOpacityMultiplier();
    }
    return 1.0f;
}

const AcrylicSettings& GetSettings()
{
    return getAcrylicMaterial().getSettings();
}

void SetSettings(const AcrylicSettings& settings)
{
    if (s_initialized) {
        getAcrylicMaterial().setSettings(settings);
    }
}

void SetPreset(int preset)
{
    if (preset < 0 || preset >= kPresetCount) preset = 3;
    SetEnabled(preset != 0);
    SetQuality(PresetQuality(preset));
    SetBlurMultiplier(PresetBlur(preset));
}

int GetPreset()
{
    return MatchPreset(GetQuality(), GetBlurMultiplier());
}

void ApplyBlurAmount(float blur)
{
    if (blur < 0.001f) {
        SetEnabled(false);
        SetQuality(AcrylicQuality::Off);
        SetBlurMultiplier(0.0f);
    } else {
        SetEnabled(true);
        SetQuality(AcrylicQuality::Low);
        SetBlurMultiplier(blur);
    }
}

} // namespace ImGuiAcrylic

} // namespace effects
} // namespace ui
} // namespace dragonx

#else // neither GLAD nor DX11

namespace dragonx {
namespace ui {
namespace effects {

namespace ImGuiAcrylic {

bool Init() { return false; }
void Shutdown() {}
bool IsAvailable() { return false; }

void BeginFrame(int, int) {}
void CaptureBackground() {}
void InvalidateCapture() {}
ImDrawCallback GetBackgroundCaptureCallback() { return nullptr; }
ImDrawCallback GetLiveCaptureCallback() { return nullptr; }

void DrawAcrylicRect(ImDrawList* drawList, 
                     const ImVec2& pMin, const ImVec2& pMax,
                     const AcrylicParams& params,
                     float rounding)
{
    if (drawList) {
        drawList->AddRectFilled(pMin, pMax, 
            ImGui::ColorConvertFloat4ToU32(params.fallbackColor), rounding);
    }
}

void DrawAcrylicRect(ImDrawList* drawList,
                     const ImVec2& pMin, const ImVec2& pMax,
                     float rounding)
{
    if (drawList) {
        AcrylicParams params;
        drawList->AddRectFilled(pMin, pMax, 
            ImGui::ColorConvertFloat4ToU32(params.fallbackColor), rounding);
    }
}

bool BeginAcrylicWindow(const char* name, bool* p_open,
                        ImGuiWindowFlags flags,
                        const AcrylicParams&)
{
    if (!(flags & ImGuiWindowFlags_NoBringToFrontOnFocus))
        ImGui::SetNextWindowFocus();
    return ImGui::Begin(name, p_open, flags);
}

void EndAcrylicWindow()
{
    ImGui::End();
}

bool BeginAcrylicChild(const char* str_id, 
                       const ImVec2& size,
                       const AcrylicParams&,
                       ImGuiChildFlags child_flags,
                       ImGuiWindowFlags window_flags)
{
    return ImGui::BeginChild(str_id, size, child_flags, window_flags);
}

void EndAcrylicChild()
{
    ImGui::EndChild();
}

bool BeginAcrylicPopup(const char* str_id,
                       ImGuiWindowFlags flags,
                       const AcrylicParams&)
{
    return ImGui::BeginPopup(str_id, flags);
}

void EndAcrylicPopup()
{
    ImGui::EndPopup();
}

bool BeginAcrylicPopupModal(const char* name, bool* p_open,
                            ImGuiWindowFlags flags,
                            const AcrylicParams&)
{
    ImGui::PushStyleColor(ImGuiCol_ModalWindowDimBg, ImVec4(0, 0, 0, 0));
    bool result = ImGui::BeginPopupModal(name, p_open, flags);
    ImGui::PopStyleColor();
    if (result) {
        DrawFrostedScrim(ImGui::GetWindowDrawList());
    }
    return result;
}

bool BeginAcrylicContextItem(const char* str_id,
                             ImGuiPopupFlags popup_flags,
                             const AcrylicParams&)
{
    return ImGui::BeginPopupContextItem(str_id, popup_flags);
}

bool BeginAcrylicContextWindow(const char* str_id,
                               ImGuiPopupFlags popup_flags,
                               const AcrylicParams&)
{
    return ImGui::BeginPopupContextWindow(str_id, popup_flags);
}

void DrawSidebarBackground(float, const AcrylicParams&) {}

void SetQuality(AcrylicQuality) {}
AcrylicQuality GetQuality() { return AcrylicQuality::Off; }
void SetEnabled(bool) {}
bool IsEnabled() { return false; }
void SetBlurMultiplier(float) {}
float GetBlurMultiplier() { return 1.0f; }
void SetReducedTransparency(bool) {}
bool GetReducedTransparency() { return false; }
void SetUIOpacity(float) {}
float GetUIOpacity() { return 1.0f; }
void SetNoiseOpacity(float) {}
float GetNoiseOpacity() { return 1.0f; }
static AcrylicSettings s_stubSettings;
const AcrylicSettings& GetSettings() { return s_stubSettings; }
void SetSettings(const AcrylicSettings&) {}
void SetPreset(int) {}
int GetPreset() { return 0; }
void ApplyBlurAmount(float) {}

} // namespace ImGuiAcrylic

} // namespace effects
} // namespace ui
} // namespace dragonx


#endif // DRAGONX_HAS_GLAD || DRAGONX_USE_DX11
