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

#include "acrylic.h"
#include "../schema/ui_schema.h"
#include "../../util/noise_texture.h"
#include "../../util/logger.h"

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

// GL constants that may be missing from minimal GLAD builds
#ifndef GL_VERSION
#define GL_VERSION   0x1F02
#endif
#ifndef GL_RENDERER
#define GL_RENDERER  0x1F01
#endif
#ifndef GL_MAX_TEXTURE_SIZE
#define GL_MAX_TEXTURE_SIZE 0x0D33
#endif
#ifndef GL_SCISSOR_TEST
#define GL_SCISSOR_TEST 0x0C11
#endif

#include <cstdio>
#include <vector>
#include <cmath>
#include <algorithm>

namespace dragonx {
namespace ui {
namespace effects {

// ============================================================================
// Composite Shader Source (GLEW Available)
// ============================================================================

namespace {

const char* COMPOSITE_VERTEX = R"glsl(
#version 330 core

layout (location = 0) in vec2 aPos;
layout (location = 1) in vec2 aTexCoord;

out vec2 vTexCoord;
out vec2 vNoiseCoord;

uniform vec2 uTexScale;  // For noise tiling

void main()
{
    gl_Position = vec4(aPos, 0.0, 1.0);
    vTexCoord = aTexCoord;
    vNoiseCoord = aTexCoord * uTexScale;  // Tile noise
}
)glsl";

const char* COMPOSITE_FRAGMENT = R"glsl(
#version 330 core

in vec2 vTexCoord;
in vec2 vNoiseCoord;

out vec4 FragColor;

uniform sampler2D uBlurTex;
uniform sampler2D uNoiseTex;

uniform vec4 uTintColor;
uniform float uTintOpacity;
uniform float uLuminosity;
uniform float uNoiseOpacity;

void main()
{
    // Sample blurred background
    vec3 blur = texture(uBlurTex, vTexCoord).rgb;
    
    // Apply luminosity (desaturation effect)
    float lum = dot(blur, vec3(0.299, 0.587, 0.114));
    blur = mix(vec3(lum), blur, uLuminosity);
    
    // Apply tint color
    vec3 tinted = mix(blur, uTintColor.rgb, uTintOpacity);
    
    // Sample and apply noise
    float noise = texture(uNoiseTex, vNoiseCoord).r;
    // Noise is centered at 0.5, so we subtract 0.5 and scale
    float noiseOffset = (noise - 0.5) * 2.0 * uNoiseOpacity;
    tinted += vec3(noiseOffset);
    
    // Output with slight transparency
    FragColor = vec4(tinted, uTintColor.a);
}
)glsl";

bool compileShader(GLuint shader, const char* source)
{
    glShaderSource(shader, 1, &source, nullptr);
    glCompileShader(shader);
    
    GLint success;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
    
    if (!success) {
        GLint logLength;
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);
        std::vector<char> log(logLength + 1);
        glGetShaderInfoLog(shader, logLength, nullptr, log.data());
        DEBUG_LOGF("Shader compile error:\n%s\n", log.data());
        return false;
    }
    return true;
}

bool linkProgram(GLuint program)
{
    glLinkProgram(program);
    
    GLint success;
    glGetProgramiv(program, GL_LINK_STATUS, &success);
    
    if (!success) {
        GLint logLength;
        glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);
        std::vector<char> log(logLength + 1);
        glGetProgramInfoLog(program, logLength, nullptr, log.data());
        DEBUG_LOGF("Program link error:\n%s\n", log.data());
        return false;
    }
    return true;
}

} // anonymous namespace

// ============================================================================
// AcrylicMaterial Implementation (GLEW Available)
// ============================================================================

AcrylicMaterial::AcrylicMaterial() = default;

AcrylicMaterial::~AcrylicMaterial()
{
    shutdown();
}

bool AcrylicMaterial::init()
{
    if (initialized_) return true;
    
    DEBUG_LOGF("AcrylicMaterial: Initializing...\n");
    
    // Initialize blur shader
    if (!blurShader_.init()) {
        DEBUG_LOGF("AcrylicMaterial: Failed to init blur shader\n");
        return false;
    }
    
    // Initialize fullscreen quad
    if (!quad_.init()) {
        DEBUG_LOGF("AcrylicMaterial: Failed to init fullscreen quad\n");
        return false;
    }
    
    // Generate noise texture (texture size is a fixed default;
    // noise-tile-scale is read per-frame once schema is loaded)
    if (!noiseTexture_.generate(noiseTextureSize_, 0.5f)) {
        DEBUG_LOGF("AcrylicMaterial: Failed to generate noise texture\n");
        return false;
    }
    
    // Create composite shader
    GLuint vs = glCreateShader(GL_VERTEX_SHADER);
    GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
    
    if (!compileShader(vs, COMPOSITE_VERTEX)) {
        glDeleteShader(vs);
        glDeleteShader(fs);
        return false;
    }
    
    if (!compileShader(fs, COMPOSITE_FRAGMENT)) {
        glDeleteShader(vs);
        glDeleteShader(fs);
        return false;
    }
    
    compositeShader_ = glCreateProgram();
    glAttachShader(compositeShader_, vs);
    glAttachShader(compositeShader_, fs);
    
    if (!linkProgram(compositeShader_)) {
        glDeleteProgram(compositeShader_);
        glDeleteShader(vs);
        glDeleteShader(fs);
        compositeShader_ = 0;
        return false;
    }
    
    glDeleteShader(vs);
    glDeleteShader(fs);
    
    // Get uniform locations
    uCompositeTintColor_ = glGetUniformLocation(compositeShader_, "uTintColor");
    uCompositeTintOpacity_ = glGetUniformLocation(compositeShader_, "uTintOpacity");
    uCompositeLuminosity_ = glGetUniformLocation(compositeShader_, "uLuminosity");
    uCompositeNoiseOpacity_ = glGetUniformLocation(compositeShader_, "uNoiseOpacity");
    uCompositeBlurTex_ = glGetUniformLocation(compositeShader_, "uBlurTex");
    uCompositeNoiseTex_ = glGetUniformLocation(compositeShader_, "uNoiseTex");
    uCompositeTexScale_ = glGetUniformLocation(compositeShader_, "uTexScale");
    
    DEBUG_LOGF("AcrylicMaterial: Composite shader uniforms:\n");
    DEBUG_LOGF("  uTintColor=%d, uTintOpacity=%d, uLuminosity=%d\n",
           uCompositeTintColor_, uCompositeTintOpacity_, uCompositeLuminosity_);
    DEBUG_LOGF("  uNoiseOpacity=%d, uBlurTex=%d, uNoiseTex=%d, uTexScale=%d\n",
           uCompositeNoiseOpacity_, uCompositeBlurTex_, uCompositeNoiseTex_, uCompositeTexScale_);
    
    // Detect system capabilities
    refreshCapabilities();
    currentFallback_ = detectFallback();
    
    initialized_ = true;
    DEBUG_LOGF("AcrylicMaterial: Initialized successfully (fallback mode: %d)\n", 
           static_cast<int>(currentFallback_));
    
    return true;
}

void AcrylicMaterial::shutdown()
{
    if (!initialized_) return;
    
    captureBuffer_.destroy();
    blurBuffers_.destroy();
    blurShader_.destroy();
    quad_.destroy();
    noiseTexture_.destroy();
    
    if (compositeShader_) {
        glDeleteProgram(compositeShader_);
        compositeShader_ = 0;
    }
    
    initialized_ = false;
    viewportWidth_ = 0;
    viewportHeight_ = 0;
    
    DEBUG_LOGF("AcrylicMaterial: Shutdown complete\n");
}

void AcrylicMaterial::resize(int width, int height)
{
    if (width == viewportWidth_ && height == viewportHeight_) {
        return;  // No change
    }
    
    viewportWidth_ = width;
    viewportHeight_ = height;
    blurCacheValid_ = false;  // Invalidate cache on resize
    
    // Determine blur buffer size based on quality
    int blurWidth = width;
    int blurHeight = height;
    
    switch (settings_.quality) {
        case AcrylicQuality::Low:
            blurWidth /= 4;
            blurHeight /= 4;
            break;
        case AcrylicQuality::Medium:
            blurWidth /= 2;
            blurHeight /= 2;
            break;
        case AcrylicQuality::High:
            // Full resolution
            break;
        case AcrylicQuality::Off:
            // No blur buffers needed
            return;
    }
    
    // Ensure minimum size
    blurWidth = std::max(64, blurWidth);
    blurHeight = std::max(64, blurHeight);
    
    // Initialize/resize buffers
    if (!captureBuffer_.isValid()) {
        captureBuffer_.init(width, height);
    } else {
        captureBuffer_.resize(width, height);
    }
    
    if (!blurBuffers_.isValid()) {
        blurBuffers_.init(blurWidth, blurHeight, true);  // float16 to avoid banding
    } else {
        blurBuffers_.resize(blurWidth, blurHeight);
    }
    
    DEBUG_LOGF("AcrylicMaterial: Resized to %dx%d (blur: %dx%d)\n", 
           width, height, blurWidth, blurHeight);
    dirtyFrames_ = 2;  // recapture after resize
}

void AcrylicMaterial::captureBackground()
{
    if (!initialized_ || shouldSkipEffect()) {
        return;
    }
    
    if (viewportWidth_ <= 0 || viewportHeight_ <= 0) {
        return;
    }
    
    // P7: Only recapture when the background has been marked dirty
    if (dirtyFrames_ <= 0) {
        return;
    }
    --dirtyFrames_;
    
    // Capture current screen content
    captureBuffer_.captureScreen(viewportWidth_, viewportHeight_);
    hasValidCapture_ = true;
    blurCacheValid_ = false;  // New capture invalidates blur cache
}

void AcrylicMaterial::captureBackgroundDirect()
{
    if (!initialized_ || shouldSkipEffect()) {
        return;
    }

    if (viewportWidth_ <= 0 || viewportHeight_ <= 0) {
        return;
    }

    // Only recapture when dirty (resize, theme change, first frame).
    if (dirtyFrames_ <= 0) {
        return;
    }
    --dirtyFrames_;

    // At this point during RenderDrawData(), only the background draw
    // list has been rasterized to framebuffer 0 — the UI windows have
    // not been drawn yet.  blit from FB 0 into our capture buffer.
    // Disable scissor test: glBlitFramebuffer applies scissor to the
    // draw framebuffer, and ImGui has scissor enabled during rendering.
    // The subsequent ImDrawCallback_ResetRenderState re-enables it.
    glDisable(GL_SCISSOR_TEST);
    captureBuffer_.captureScreen(viewportWidth_, viewportHeight_);

    hasValidCapture_ = true;
    blurCacheValid_ = false;  // force re-blur with fresh capture
}

void AcrylicMaterial::captureLiveFramebuffer()
{
    // Like captureBackgroundDirect but with NO dirtyFrames_ gate — refresh every frame the
    // overlay is open so the blur tracks the live UI. Fires mid-RenderDrawData (from a callback
    // inserted at the overlay window's draw-list start), so FB 0 already holds the app UI drawn
    // below the overlay. A paired ImDrawCallback_ResetRenderState restores ImGui's GL state.
    if (!initialized_ || shouldSkipEffect()) return;
    if (viewportWidth_ <= 0 || viewportHeight_ <= 0) return;
    glDisable(GL_SCISSOR_TEST);
    captureBuffer_.captureScreen(viewportWidth_, viewportHeight_);
    hasValidCapture_ = true;
    blurCacheValid_ = false;  // force re-blur with the fresh live capture
}

bool AcrylicMaterial::shouldSkipEffect() const
{
    return !settings_.enabled || 
           settings_.reducedTransparency || 
           settings_.quality == AcrylicQuality::Off;
}

void AcrylicMaterial::setQuality(AcrylicQuality quality)
{
    if (settings_.quality != quality) {
        settings_.quality = quality;
        blurCacheValid_ = false;  // Quality change invalidates cache
        // Trigger resize to adjust buffer sizes
        int w = viewportWidth_, h = viewportHeight_;
        viewportWidth_ = viewportHeight_ = 0;
        resize(w, h);
    }
}

void AcrylicMaterial::applyBlur(float radius, bool ignoreMultiplier)
{
    if (!blurBuffers_.isValid() || !blurShader_.isValid()) {
        return;
    }

    // Apply the user's blur-strength multiplier — unless the caller wants an absolute radius.
    float scaledRadius = ignoreMultiplier ? radius : radius * settings_.blurRadiusMultiplier;
    
    // Skip blur entirely when multiplier is at or near zero — show sharp background
    if (scaledRadius < 0.5f) {
        // Still blit capture to blur source so getBlurredTexture() returns
        // the un-blurred captured background
        int blurW = blurBuffers_.getSource().getWidth();
        int blurH = blurBuffers_.getSource().getHeight();
        glDisable(GL_SCISSOR_TEST);
        glDisable(GL_DEPTH_TEST);
        glDisable(GL_BLEND);
        GLuint srcFbo = captureBuffer_.getFbo();
        GLuint dstFbo = blurBuffers_.getSource().getFbo();
        glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFbo);
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFbo);
        glBlitFramebuffer(0, 0, viewportWidth_, viewportHeight_,
                          0, 0, blurW, blurH,
                          GL_COLOR_BUFFER_BIT, GL_LINEAR);
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        lastBlurRadius_ = scaledRadius;
        blurCacheValid_ = true;
        return;
    }

    // Check cache - skip blur if same radius and cache is valid
    if (blurCacheValid_ && std::abs(scaledRadius - lastBlurRadius_) < 0.1f) {
        return;  // Use cached blur result
    }
    
    int blurWidth = blurBuffers_.getSource().getWidth();
    int blurHeight = blurBuffers_.getSource().getHeight();
    
    // Determine number of passes based on quality
    int passes = 1;
    float effectiveRadius = scaledRadius;
    
    switch (settings_.quality) {
        case AcrylicQuality::Low:
            passes = 1;
            effectiveRadius = scaledRadius * 0.5f;
            break;
        case AcrylicQuality::Medium:
            passes = 2;
            break;
        case AcrylicQuality::High:
            passes = 3;
            break;
        default:
            return;
    }
    
    // Copy captured content into the first blur buffer, scaling down
    // from full viewport resolution to the blur buffer resolution.
    // Disable scissor/depth/blend BEFORE the blit — glBlitFramebuffer
    // applies scissor to the draw FBO, and a stale rect would clip it.
    glDisable(GL_SCISSOR_TEST);
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_BLEND);
    {
        GLuint srcFbo = captureBuffer_.getFbo();
        GLuint dstFbo = blurBuffers_.getSource().getFbo();
        glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFbo);
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFbo);
        glBlitFramebuffer(
            0, 0, viewportWidth_, viewportHeight_,   // src rect (full res)
            0, 0, blurWidth, blurHeight,              // dst rect (blur res)
            GL_COLOR_BUFFER_BIT, GL_LINEAR);
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }
    
    blurShader_.bind();
    blurShader_.setResolution(blurWidth, blurHeight);
    blurShader_.setTexture(0);
    
    for (int pass = 0; pass < passes; pass++) {
        float passRadius = effectiveRadius / passes;
        blurShader_.setRadius(passRadius);
        
        // Horizontal pass
        blurBuffers_.getDest().bind();
        blurShader_.setDirection(true);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, blurBuffers_.getSourceTexture());
        quad_.draw();
        blurBuffers_.swap();
        
        // Vertical pass
        blurBuffers_.getDest().bind();
        blurShader_.setDirection(false);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, blurBuffers_.getSourceTexture());
        quad_.draw();
        blurBuffers_.swap();
    }
    
    blurShader_.unbind();
    blurBuffers_.getDest().unbind();
    
    // Restore viewport
    glViewport(0, 0, viewportWidth_, viewportHeight_);

    static bool s_blurRunTraced = false;
    if (!s_blurRunTraced) {
        s_blurRunTraced = true;
        DEBUG_LOGF("[Acrylic] applyBlur: %d passes, radius=%.1f, blurSize=%dx%d  "
               "srcFBO=%u dstTex=%u\n",
               passes, effectiveRadius, blurWidth, blurHeight,
               captureBuffer_.getFbo(), blurBuffers_.getSourceTexture());
    }
    
    // Update cache state
    lastBlurRadius_ = scaledRadius;
    blurCacheValid_ = true;
}

ImTextureID AcrylicMaterial::getBlurredTexture() const
{
    return (ImTextureID)(intptr_t)blurBuffers_.getSourceTexture();
}

ImTextureID AcrylicMaterial::getNoiseTexture() const
{
    return (ImTextureID)(intptr_t)noiseTexture_.getTexture();
}

void AcrylicMaterial::drawRect(ImDrawList* drawList, const ImVec2& pMin, const ImVec2& pMax,
                                const AcrylicParams& params, float rounding)
{
    if (!initialized_) return;
    
    // Update fallback mode (could change based on settings)
    currentFallback_ = detectFallback();

    
    // Fallback solid fill — scaled by UI opacity for card transparency
    auto scaledFallback = [&]() {
        ImVec4 c = params.fallbackColor;
        c.w *= settings_.uiOpacity;
        return ImGui::ColorConvertFloat4ToU32(c);
    };

    // If acrylic is disabled in params, always use solid fallback
    if (!params.enabled) {
        drawList->AddRectFilled(pMin, pMax, scaledFallback(), rounding);
        return;
    }
    
    // Handle fallback modes
    switch (currentFallback_) {
        case AcrylicFallback::Solid:
            drawList->AddRectFilled(pMin, pMax, scaledFallback(), rounding);
            return;
            
        case AcrylicFallback::TintedOnly:
            drawTintedRect(drawList, pMin, pMax, params, rounding);
            return;
            
        case AcrylicFallback::None:
        default:
            // Continue with full acrylic rendering
            break;
    }

    // No valid background capture yet (first frame before the
    // BackgroundDrawList callback has fired).  Fall back to tinted.
    if (!hasValidCapture_) {
        drawTintedRect(drawList, pMin, pMax, params, rounding);
        return;
    }
    
    // Apply blur to captured background
    applyBlur(params.blurRadius, params.absoluteBlurRadius);
    
    // Calculate UV coordinates for sampling the blur FBO texture.
    // With multi-viewport enabled, ImGui draw coordinates are in
    // absolute OS screen space.  The FBO capture covers only the main
    // window, so we must subtract the main viewport's screen position
    // to get window-local coordinates before dividing by viewport size.
    // OpenGL FBO textures have V=0 at the bottom and V=1 at the top.
    // ImGui screen coordinates have Y=0 at the top and Y=H at bottom.
    // So texture V for screen Y is:  V = 1.0 - localY/H
    ImGuiViewport* mainVp = ImGui::GetMainViewport();
    float localX0 = pMin.x - mainVp->Pos.x;
    float localY0 = pMin.y - mainVp->Pos.y;
    float localX1 = pMax.x - mainVp->Pos.x;
    float localY1 = pMax.y - mainVp->Pos.y;
    float u0 = localX0 / viewportWidth_;
    float v0 = 1.0f - localY0 / viewportHeight_;  // V at top-left (high)
    float u1 = localX1 / viewportWidth_;
    float v1 = 1.0f - localY1 / viewportHeight_;  // V at bottom-right (low)
    
    // Draw the blurred background at (near) full strength so the panel is actually FROSTED — the
    // blurred capture replaces the sharp background in the card. UI opacity is applied only to the
    // tint overlay (in DrawGlassPanel), NOT here: scaling the blur by uiOpacity used to let the
    // SHARP background bleed straight through at low opacity, which made the acrylic slider look
    // like a no-op. A small floor keeps it frosted even for low-alpha presets.
    ImTextureID blurTex = getBlurredTexture();
    uint8_t glassAlpha = static_cast<uint8_t>(
        std::min(255.0f, std::max(0.0f, std::max(params.fallbackColor.w, 0.90f) * 255.0f)));

    if (blurTex) {
        drawList->AddImageRounded(
            blurTex,
            pMin, pMax,
            ImVec2(u0, v0), ImVec2(u1, v1),
            IM_COL32(255, 255, 255, glassAlpha),
            rounding
        );
    }

    // Noise grain overlay — single draw call via pre-tiled texture
    if (params.noiseOpacity > 0.0f && settings_.noiseOpacityMultiplier > 0.0f) {
        float maxAlpha = schema::UI().drawElement("backdrop", "noise-max-alpha").sizeOr(6.0f);
        float scaledNoise = params.noiseOpacity * settings_.noiseOpacityMultiplier;
        uint8_t alpha = static_cast<uint8_t>(std::min(255.0f, maxAlpha * scaledNoise * 50.0f));
        ImU32 col = IM_COL32(255, 255, 255, alpha);
        drawList->PushClipRect(pMin, pMax, true);
        dragonx::util::DrawTiledNoiseRect(drawList, pMin, pMax, col);
        drawList->PopClipRect();
    }
}

// ============================================================================
// Preset Parameters
// ============================================================================

AcrylicParams AcrylicMaterial::getDarkPreset()
{
    AcrylicParams params;
    params.tintColor = ImVec4(0.1f, 0.1f, 0.12f, 0.95f);
    params.tintOpacity = 0.75f;
    params.luminosityOpacity = 0.6f;
    params.blurRadius = 30.0f;
    params.noiseOpacity = 0.02f;
    params.fallbackColor = ImVec4(0.12f, 0.12f, 0.14f, 0.82f);
    return params;
}

AcrylicParams AcrylicMaterial::getLightPreset()
{
    AcrylicParams params;
    params.tintColor = ImVec4(0.95f, 0.95f, 0.97f, 0.95f);
    params.tintOpacity = 0.80f;
    params.luminosityOpacity = 0.7f;
    params.blurRadius = 25.0f;
    params.noiseOpacity = 0.02f;
    params.fallbackColor = ImVec4(0.96f, 0.96f, 0.98f, 0.82f);
    return params;
}

AcrylicParams AcrylicMaterial::getDragonXPreset()
{
    AcrylicParams params;
    // Deep dragon red tint
    params.tintColor = ImVec4(0.6f, 0.15f, 0.1f, 0.95f);  // #993319
    params.tintOpacity = 0.85f;
    params.luminosityOpacity = 0.3f;  // More desaturated for dramatic effect
    params.blurRadius = 40.0f;
    params.noiseOpacity = 0.025f;
    params.fallbackColor = ImVec4(0.5f, 0.12f, 0.08f, 0.82f);
    return params;
}

AcrylicParams AcrylicMaterial::getPopupPreset()
{
    AcrylicParams params;
    params.tintColor = ImVec4(0.15f, 0.15f, 0.18f, 0.95f);
    params.tintOpacity = 0.70f;
    params.luminosityOpacity = 0.5f;
    params.blurRadius = 30.0f;
    params.noiseOpacity = 0.02f;
    params.fallbackColor = ImVec4(0.18f, 0.18f, 0.2f, 0.88f);
    return params;
}

AcrylicParams AcrylicMaterial::getSidebarPreset()
{
    AcrylicParams params;
    params.tintColor = ImVec4(0.08f, 0.08f, 0.1f, 0.95f);
    params.tintOpacity = 0.80f;
    params.luminosityOpacity = 0.4f;
    params.blurRadius = 35.0f;
    params.noiseOpacity = 0.02f;
    params.fallbackColor = ImVec4(0.1f, 0.1f, 0.12f, 0.85f);
    return params;
}

// ============================================================================
// Fallback System
// ============================================================================

void AcrylicMaterial::refreshCapabilities()
{
    // Get OpenGL info
    const char* version = reinterpret_cast<const char*>(glGetString(GL_VERSION));
    const char* renderer = reinterpret_cast<const char*>(glGetString(GL_RENDERER));
    
    capabilities_.glVersion = version ? version : "Unknown";
    capabilities_.glRenderer = renderer ? renderer : "Unknown";
    
    // Get max texture size
    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &capabilities_.maxTextureSize);
    
    // Check for required features
    capabilities_.hasFramebufferSupport = (glGenFramebuffers != nullptr && 
                                           glBindFramebuffer != nullptr);
    capabilities_.hasShaderSupport = (glCreateShader != nullptr && 
                                      glCreateProgram != nullptr);
    capabilities_.hasTextureSupport = (capabilities_.maxTextureSize >= 1024);
    
    // Detect low-end GPU based on renderer string
    std::string rendererLower = capabilities_.glRenderer;
    std::transform(rendererLower.begin(), rendererLower.end(), 
                   rendererLower.begin(), ::tolower);
    
    capabilities_.isLowEndGPU = 
        (rendererLower.find("intel") != std::string::npos && 
         rendererLower.find("hd") != std::string::npos) ||
        (rendererLower.find("mesa") != std::string::npos && 
         rendererLower.find("llvmpipe") != std::string::npos) ||
        (rendererLower.find("software") != std::string::npos) ||
        (rendererLower.find("swrast") != std::string::npos);
    
    // Check battery status on Linux
    capabilities_.isOnBattery = false;
#ifdef __linux__
    // Check /sys/class/power_supply/*/status
    FILE* f = fopen("/sys/class/power_supply/BAT0/status", "r");
    if (f) {
        char status[32] = {0};
        if (fgets(status, sizeof(status), f)) {
            capabilities_.isOnBattery = (strncmp(status, "Discharging", 11) == 0);
        }
        fclose(f);
    }
#endif
    
    DEBUG_LOGF("AcrylicMaterial: Capabilities detected:\n");
    DEBUG_LOGF("  GL Version: %s\n", capabilities_.glVersion.c_str());
    DEBUG_LOGF("  GL Renderer: %s\n", capabilities_.glRenderer.c_str());
    DEBUG_LOGF("  Max Texture Size: %d\n", capabilities_.maxTextureSize);
    DEBUG_LOGF("  Framebuffer Support: %s\n", capabilities_.hasFramebufferSupport ? "Yes" : "No");
    DEBUG_LOGF("  Shader Support: %s\n", capabilities_.hasShaderSupport ? "Yes" : "No");
    DEBUG_LOGF("  Low-End GPU: %s\n", capabilities_.isLowEndGPU ? "Yes" : "No");
    DEBUG_LOGF("  On Battery: %s\n", capabilities_.isOnBattery ? "Yes" : "No");
}

AcrylicFallback AcrylicMaterial::detectFallback() const
{
    // Check if forced fallback is set
    if (hasForcedFallback_) {
        return forcedFallback_;
    }
    
    // User preference: reduced transparency
    if (settings_.reducedTransparency) {
        return AcrylicFallback::Solid;
    }
    
    // Check OpenGL capabilities
    if (!capabilities_.hasFramebufferSupport || !capabilities_.hasShaderSupport) {
        return AcrylicFallback::Solid;
    }
    
    // Texture size too small
    if (capabilities_.maxTextureSize < 1024) {
        return AcrylicFallback::Solid;
    }
    
    // Quality set to Off
    if (settings_.quality == AcrylicQuality::Off) {
        return AcrylicFallback::TintedOnly;
    }
    
    // Low-end GPU detection
    if (capabilities_.isLowEndGPU && settings_.quality == AcrylicQuality::High) {
        return AcrylicFallback::TintedOnly;
    }
    
    // Battery mode (if user enabled)
    if (settings_.disableOnBattery && capabilities_.isOnBattery) {
        return AcrylicFallback::TintedOnly;
    }
    
    // Global disable
    if (!settings_.enabled) {
        return AcrylicFallback::TintedOnly;
    }
    
    return AcrylicFallback::None;
}

void AcrylicMaterial::drawTintedRect(ImDrawList* drawList, const ImVec2& pMin, const ImVec2& pMax,
                                      const AcrylicParams& params, float rounding)
{
    // Draw semi-transparent tint without blur — scale by UI opacity
    // so the tinted fallback also respects the card transparency slider.
    ImU32 tintCol = ImGui::ColorConvertFloat4ToU32(
        ImVec4(params.tintColor.x, params.tintColor.y, params.tintColor.z, 
               params.tintColor.w * params.tintOpacity * 0.9f * settings_.uiOpacity)
    );
    drawList->AddRectFilled(pMin, pMax, tintCol, rounding);
    
    // Still add noise overlay for visual interest (W11-style)
    ImTextureID noiseTex = getNoiseTexture();
    if (noiseTex && params.noiseOpacity > 0.0f && settings_.noiseOpacityMultiplier > 0.0f) {
        float maxAlpha = schema::UI().drawElement("backdrop", "noise-max-alpha").sizeOr(6.0f);
        float scaledNoise = params.noiseOpacity * settings_.noiseOpacityMultiplier;
        uint8_t alpha = static_cast<uint8_t>(std::min(255.0f, maxAlpha * scaledNoise * 50.0f));
        float tilePx = static_cast<float>(noiseTextureSize_);
        float uvScale = 1.0f / tilePx;
        ImVec2 uv0(pMin.x * uvScale, pMin.y * uvScale);
        ImVec2 uv1(pMax.x * uvScale, pMax.y * uvScale);
        
        drawList->AddImageRounded(
            noiseTex,
            pMin, pMax,
            uv0, uv1,
            IM_COL32(255, 255, 255, alpha),
            rounding
        );
    }
}

// ============================================================================
// Global Instance
// ============================================================================

AcrylicMaterial& getAcrylicMaterial()
{
    static AcrylicMaterial instance;
    return instance;
}

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

#else // !DRAGONX_HAS_GLAD

// ============================================================================
// DX11 Implementation (Windows)
// ============================================================================

#ifdef DRAGONX_USE_DX11

#include <d3d11.h>
#include <d3dcompiler.h>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
#include <cstring>

// Get D3D11 device from ImGui backend (same pattern as texture_loader.cpp)
static ID3D11Device* GetImGuiD3D11Device()
{
    ImGuiIO& io = ImGui::GetIO();
    if (!io.BackendRendererUserData) return nullptr;
    return *reinterpret_cast<ID3D11Device**>(io.BackendRendererUserData);
}

static ID3D11DeviceContext* GetImGuiD3D11DeviceContext()
{
    ID3D11Device* dev = GetImGuiD3D11Device();
    if (!dev) return nullptr;
    ID3D11DeviceContext* ctx = nullptr;
    dev->GetImmediateContext(&ctx);
    // GetImmediateContext adds a ref, release the extra ref.
    if (ctx) ctx->Release();
    return ctx;
}

namespace dragonx {
namespace ui {
namespace effects {

// ---- HLSL shader source (compiled at runtime via D3DCompile) ----

static const char* DX_BLUR_VS = R"hlsl(
struct VS_IN  { float2 pos : POSITION; float2 uv : TEXCOORD0; };
struct VS_OUT { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; };
VS_OUT main(VS_IN i) {
    VS_OUT o;
    o.pos = float4(i.pos, 0.0, 1.0);
    o.uv  = i.uv;
    return o;
}
)hlsl";

static const char* DX_BLUR_PS = R"hlsl(
cbuffer BlurCB : register(b0) {
    float2 direction;
    float2 resolution;
    float  radius;     // Gaussian sigma in texels
    float3 _pad;
};
Texture2D    tex : register(t0);
SamplerState smp : register(s0);

// Screen-space hash for dithering (returns 0..1)
float hash12(float2 p) {
    float3 p3 = frac(float3(p.xyx) * 0.1031);
    p3 += dot(p3, p3.yzx + 33.33);
    return frac((p3.x + p3.y) * p3.z);
}

float4 main(float4 svpos : SV_POSITION, float2 uv : TEXCOORD0) : SV_TARGET {
    float2 texelSize = 1.0 / resolution;
    float2 step = direction * texelSize;

    // Dynamic Gaussian kernel: radius = sigma, extend to ~2.5 sigma
    float sigma = max(radius, 0.5);
    int iRadius = clamp(int(sigma * 2.5 + 0.5), 1, 40);
    float inv2sigma2 = 1.0 / (2.0 * sigma * sigma);

    float3 sum = float3(0, 0, 0);
    float wSum = 0;

    [loop] for (int i = -iRadius; i <= iRadius; i++) {
        float w = exp(-float(i * i) * inv2sigma2);
        sum += tex.Sample(smp, uv + step * float(i)).rgb * w;
        wSum += w;
    }

    float3 color = sum / wSum;

    // Triangular-PDF dithering to break up 8-bit quantization banding.
    // Two uniform hashes combined give a triangular distribution (-1..+1).
    float d = hash12(svpos.xy) + hash12(svpos.xy + 71.37) - 1.0;
    color += d / 255.0;

    return float4(color, 1.0);
}
)hlsl";

// ---- Constant buffer layout (must be 16-byte aligned) ----
struct BlurCB {
    float dirX, dirY;
    float resX, resY;
    float radius;
    float _pad[3];
};

// ----------------------------------------------------------------
// Helper: create a 2-D render-target + SRV pair
// ----------------------------------------------------------------
void AcrylicMaterial::dx_createRenderTarget(
        ID3D11Texture2D*& tex,
        ID3D11ShaderResourceView*& srv,
        ID3D11RenderTargetView*& rtv,
        int w, int h,
        DXGI_FORMAT format)
{
    D3D11_TEXTURE2D_DESC td = {};
    td.Width  = w;
    td.Height = h;
    td.MipLevels = 1;
    td.ArraySize = 1;
    td.Format    = format;
    td.SampleDesc.Count = 1;
    td.Usage     = D3D11_USAGE_DEFAULT;
    td.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;

    HRESULT hr = dx_device_->CreateTexture2D(&td, nullptr, &tex);
    if (FAILED(hr)) { DEBUG_LOGF("[Acrylic DX11] CreateTexture2D failed 0x%08lx\n", hr); return; }

    dx_device_->CreateShaderResourceView(tex, nullptr, &srv);
    dx_device_->CreateRenderTargetView(tex, nullptr, &rtv);
}

void AcrylicMaterial::dx_releaseRenderTarget(
        ID3D11Texture2D*& tex,
        ID3D11ShaderResourceView*& srv,
        ID3D11RenderTargetView*& rtv)
{
    if (srv) { srv->Release(); srv = nullptr; }
    if (rtv) { rtv->Release(); rtv = nullptr; }
    if (tex) { tex->Release(); tex = nullptr; }
}

// ----------------------------------------------------------------
// Compile shaders, create pipeline objects
// ----------------------------------------------------------------
bool AcrylicMaterial::dx_initPipeline()
{
    HRESULT hr;

    // ---- Compile vertex shader ----
    ID3DBlob* vsBlob = nullptr;
    ID3DBlob* errBlob = nullptr;
    hr = D3DCompile(DX_BLUR_VS, strlen(DX_BLUR_VS), "blur_vs",
                    nullptr, nullptr, "main", "vs_4_0",
                    D3DCOMPILE_OPTIMIZATION_LEVEL3, 0,
                    &vsBlob, &errBlob);
    if (FAILED(hr)) {
        if (errBlob) { DEBUG_LOGF("[Acrylic DX11] VS compile: %s\n", (char*)errBlob->GetBufferPointer()); errBlob->Release(); }
        return false;
    }
    hr = dx_device_->CreateVertexShader(vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(), nullptr, &dx_blurVS_);
    if (FAILED(hr)) { vsBlob->Release(); return false; }

    // ---- Input layout ----
    D3D11_INPUT_ELEMENT_DESC layout[] = {
        { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0,  D3D11_INPUT_PER_VERTEX_DATA, 0 },
        { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 8,  D3D11_INPUT_PER_VERTEX_DATA, 0 },
    };
    hr = dx_device_->CreateInputLayout(layout, 2, vsBlob->GetBufferPointer(),
                                       vsBlob->GetBufferSize(), &dx_inputLayout_);
    vsBlob->Release();
    if (FAILED(hr)) return false;

    // ---- Compile pixel shader ----
    ID3DBlob* psBlob = nullptr;
    hr = D3DCompile(DX_BLUR_PS, strlen(DX_BLUR_PS), "blur_ps",
                    nullptr, nullptr, "main", "ps_4_0",
                    D3DCOMPILE_OPTIMIZATION_LEVEL3, 0,
                    &psBlob, &errBlob);
    if (FAILED(hr)) {
        if (errBlob) { DEBUG_LOGF("[Acrylic DX11] PS compile: %s\n", (char*)errBlob->GetBufferPointer()); errBlob->Release(); }
        return false;
    }
    hr = dx_device_->CreatePixelShader(psBlob->GetBufferPointer(), psBlob->GetBufferSize(), nullptr, &dx_blurPS_);
    psBlob->Release();
    if (FAILED(hr)) return false;

    // ---- Constant buffer ----
    D3D11_BUFFER_DESC cbd = {};
    cbd.ByteWidth      = sizeof(BlurCB);
    cbd.Usage           = D3D11_USAGE_DYNAMIC;
    cbd.BindFlags       = D3D11_BIND_CONSTANT_BUFFER;
    cbd.CPUAccessFlags  = D3D11_CPU_ACCESS_WRITE;
    hr = dx_device_->CreateBuffer(&cbd, nullptr, &dx_blurCB_);
    if (FAILED(hr)) return false;

    // ---- Fullscreen quad vertex buffer ----
    struct Vert { float x, y, u, v; };
    Vert verts[] = {
        {-1, 1, 0, 0}, {-1,-1, 0, 1}, { 1,-1, 1, 1},
        {-1, 1, 0, 0}, { 1,-1, 1, 1}, { 1, 1, 1, 0},
    };
    D3D11_BUFFER_DESC vbd = {};
    vbd.ByteWidth = sizeof(verts);
    vbd.Usage     = D3D11_USAGE_IMMUTABLE;
    vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    D3D11_SUBRESOURCE_DATA vi = {};
    vi.pSysMem = verts;
    hr = dx_device_->CreateBuffer(&vbd, &vi, &dx_vertexBuf_);
    if (FAILED(hr)) return false;

    // ---- Linear clamp sampler ----
    D3D11_SAMPLER_DESC sd = {};
    sd.Filter   = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
    sd.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
    sd.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
    sd.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
    hr = dx_device_->CreateSamplerState(&sd, &dx_sampler_);
    if (FAILED(hr)) return false;

    // ---- Rasterizer state: scissor OFF, no culling, solid fill ----
    D3D11_RASTERIZER_DESC rd = {};
    rd.FillMode = D3D11_FILL_SOLID;
    rd.CullMode = D3D11_CULL_NONE;
    rd.ScissorEnable = FALSE;
    rd.DepthClipEnable = TRUE;
    hr = dx_device_->CreateRasterizerState(&rd, &dx_blurRS_);
    if (FAILED(hr)) return false;

    // ---- Blend state: opaque overwrite (no alpha blending) ----
    D3D11_BLEND_DESC bd = {};
    bd.RenderTarget[0].BlendEnable = FALSE;
    bd.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
    hr = dx_device_->CreateBlendState(&bd, &dx_blurBS_);
    if (FAILED(hr)) return false;

    // ---- Depth-stencil state: depth test OFF ----
    D3D11_DEPTH_STENCIL_DESC dsd = {};
    dsd.DepthEnable = FALSE;
    dsd.StencilEnable = FALSE;
    hr = dx_device_->CreateDepthStencilState(&dsd, &dx_blurDSS_);
    if (FAILED(hr)) return false;

    DEBUG_LOGF("[Acrylic DX11] Pipeline created successfully\n");
    return true;
}

void AcrylicMaterial::dx_releasePipeline()
{
    if (dx_blurVS_)      { dx_blurVS_->Release();      dx_blurVS_ = nullptr; }
    if (dx_blurPS_)      { dx_blurPS_->Release();       dx_blurPS_ = nullptr; }
    if (dx_inputLayout_) { dx_inputLayout_->Release();  dx_inputLayout_ = nullptr; }
    if (dx_blurCB_)      { dx_blurCB_->Release();       dx_blurCB_ = nullptr; }
    if (dx_vertexBuf_)   { dx_vertexBuf_->Release();    dx_vertexBuf_ = nullptr; }
    if (dx_sampler_)     { dx_sampler_->Release();       dx_sampler_ = nullptr; }
    if (dx_blurRS_)      { dx_blurRS_->Release();       dx_blurRS_ = nullptr; }
    if (dx_blurBS_)      { dx_blurBS_->Release();       dx_blurBS_ = nullptr; }
    if (dx_blurDSS_)     { dx_blurDSS_->Release();      dx_blurDSS_ = nullptr; }
    dx_releaseRenderTarget(dx_captureTex_, dx_captureSRV_, dx_captureRTV_);
    for (int i = 0; i < 2; ++i)
        dx_releaseRenderTarget(dx_blurTex_[i], dx_blurSRV_[i], dx_blurRTV_[i]);
    if (dx_noiseSRV_) { dx_noiseSRV_->Release(); dx_noiseSRV_ = nullptr; }
    if (dx_noiseTex_) { dx_noiseTex_->Release(); dx_noiseTex_ = nullptr; }
}

// ============================================================================
// AcrylicMaterial — DX11 implementations of the public API
// ============================================================================

AcrylicMaterial::AcrylicMaterial() = default;

AcrylicMaterial::~AcrylicMaterial()
{
    shutdown();
}

bool AcrylicMaterial::init()
{
    if (initialized_) return true;

    dx_device_  = GetImGuiD3D11Device();
    dx_context_ = GetImGuiD3D11DeviceContext();
    if (!dx_device_ || !dx_context_) {
        DEBUG_LOGF("[Acrylic DX11] No D3D11 device available\n");
        return false;
    }

    if (!dx_initPipeline()) {
        DEBUG_LOGF("[Acrylic DX11] Pipeline init failed\n");
        dx_releasePipeline();
        return false;
    }

    // Generate noise texture (CPU-side, then upload)
    // Pure white noise — no coherent patterns, perfectly seamless tiling
    {
        const int SZ = noiseTextureSize_;
        std::vector<uint8_t> data(SZ * SZ * 4);
        uint32_t seed = 12345;
        auto rng = [&]() -> float {
            seed ^= seed << 13; seed ^= seed >> 17; seed ^= seed << 5;
            return float(seed) / float(0xFFFFFFFF);
        };
        for (int y = 0; y < SZ; ++y)
            for (int x = 0; x < SZ; ++x) {
                float v = 0.5f + (rng() - 0.5f) * 0.5f;
                v = std::max(0.f, std::min(1.f, v));
                uint8_t g = uint8_t(v * 255.f);
                int i = (y * SZ + x) * 4;
                data[i] = g; data[i+1] = g; data[i+2] = g; data[i+3] = 255;
            }

        D3D11_TEXTURE2D_DESC td = {};
        td.Width = SZ; td.Height = SZ;
        td.MipLevels = 1; td.ArraySize = 1;
        td.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
        td.SampleDesc.Count = 1;
        td.Usage = D3D11_USAGE_DEFAULT;
        td.BindFlags = D3D11_BIND_SHADER_RESOURCE;
        D3D11_SUBRESOURCE_DATA init = {};
        init.pSysMem = data.data();
        init.SysMemPitch = SZ * 4;
        dx_device_->CreateTexture2D(&td, &init, &dx_noiseTex_);
        if (dx_noiseTex_)
            dx_device_->CreateShaderResourceView(dx_noiseTex_, nullptr, &dx_noiseSRV_);
    }

    // Detect capabilities (simplified for DX11 — always capable)
    refreshCapabilities();
    currentFallback_ = detectFallback();

    initialized_ = true;
    DEBUG_LOGF("[Acrylic DX11] Initialized (fallback=%d)\n", (int)currentFallback_);
    return true;
}

void AcrylicMaterial::shutdown()
{
    if (!initialized_) return;
    dx_releasePipeline();
    dx_device_  = nullptr;
    dx_context_ = nullptr;
    initialized_ = false;
    viewportWidth_ = viewportHeight_ = 0;
    DEBUG_LOGF("[Acrylic DX11] Shutdown\n");
}

void AcrylicMaterial::resize(int width, int height)
{
    if (width == viewportWidth_ && height == viewportHeight_) return;
    viewportWidth_  = width;
    viewportHeight_ = height;
    blurCacheValid_ = false;

    if (settings_.quality == AcrylicQuality::Off) return;

    // (Re)create capture buffer at full resolution — must be B8G8R8A8 to match swap chain for CopyResource
    dx_releaseRenderTarget(dx_captureTex_, dx_captureSRV_, dx_captureRTV_);
    dx_createRenderTarget(dx_captureTex_, dx_captureSRV_, dx_captureRTV_, width, height,
                          DXGI_FORMAT_B8G8R8A8_UNORM);

    // Blur buffers — size depends on quality
    int bw = width, bh = height;
    switch (settings_.quality) {
        case AcrylicQuality::Low:    bw /= 4; bh /= 4; break;
        case AcrylicQuality::Medium: bw /= 2; bh /= 2; break;
        default: break;
    }
    bw = std::max(64, bw);
    bh = std::max(64, bh);

    for (int i = 0; i < 2; ++i)
        dx_releaseRenderTarget(dx_blurTex_[i], dx_blurSRV_[i], dx_blurRTV_[i]);
    // Use R16G16B16A16_FLOAT for blur intermediates to avoid banding from precision loss
    for (int i = 0; i < 2; ++i)
        dx_createRenderTarget(dx_blurTex_[i], dx_blurSRV_[i], dx_blurRTV_[i], bw, bh,
                              DXGI_FORMAT_R16G16B16A16_FLOAT);

    dx_blurWidth_  = bw;
    dx_blurHeight_ = bh;
    dx_blurCurrent_ = 0;
    dirtyFrames_ = 2;
    VERBOSE_LOGF("[Acrylic DX11] Resized %dx%d (blur %dx%d)\n", width, height, bw, bh);
}

void AcrylicMaterial::captureBackground()
{
    // Unused on DX11 — capture is done via captureBackgroundDirect()
}

void AcrylicMaterial::captureBackgroundDirect()
{
    if (!initialized_ || shouldSkipEffect()) return;
    if (viewportWidth_ <= 0 || viewportHeight_ <= 0) return;
    if (dirtyFrames_ <= 0) return;
    --dirtyFrames_;

    if (!dx_captureTex_ || !dx_context_) return;

    // Get the current render target (swap-chain back buffer) and
    // copy it into our capture texture.
    ID3D11RenderTargetView* curRTV = nullptr;
    dx_context_->OMGetRenderTargets(1, &curRTV, nullptr);
    if (!curRTV) return;

    ID3D11Resource* bbRes = nullptr;
    curRTV->GetResource(&bbRes);
    curRTV->Release();
    if (!bbRes) return;

    dx_context_->CopyResource(dx_captureTex_, bbRes);
    bbRes->Release();

    static bool s_traced = false;
    if (!s_traced) { s_traced = true; DEBUG_LOGF("[Acrylic DX11] captureBackgroundDirect OK %dx%d\n", viewportWidth_, viewportHeight_); }

    hasValidCapture_ = true;
    blurCacheValid_  = false;
}

void AcrylicMaterial::captureLiveFramebuffer()
{
    // DX11 twin of the GL live capture: copy the currently-bound backbuffer (which holds the app
    // UI drawn below the overlay) into the capture texture, with NO dirtyFrames_ gate.
    if (!initialized_ || shouldSkipEffect()) return;
    if (viewportWidth_ <= 0 || viewportHeight_ <= 0) return;
    if (!dx_captureTex_ || !dx_context_) return;
    ID3D11RenderTargetView* curRTV = nullptr;
    dx_context_->OMGetRenderTargets(1, &curRTV, nullptr);
    if (!curRTV) return;
    ID3D11Resource* bbRes = nullptr;
    curRTV->GetResource(&bbRes);
    curRTV->Release();
    if (!bbRes) return;
    dx_context_->CopyResource(dx_captureTex_, bbRes);
    bbRes->Release();
    hasValidCapture_ = true;
    blurCacheValid_  = false;
}

void AcrylicMaterial::applyBlur(float radius, bool ignoreMultiplier)
{
    if (!dx_blurSRV_[0] || !dx_blurPS_ || !dx_context_) return;

    float scaledRadius = ignoreMultiplier ? radius : radius * settings_.blurRadiusMultiplier;
    if (blurCacheValid_ && std::abs(scaledRadius - lastBlurRadius_) < 0.1f) return;

    int passes = 1;
    float effectiveRadius = scaledRadius;
    switch (settings_.quality) {
        case AcrylicQuality::Low:    passes = 1; effectiveRadius *= 0.5f; break;
        case AcrylicQuality::Medium: passes = 2; break;
        case AcrylicQuality::High:   passes = 3; break;
        default: return;
    }

    // ---- Save D3D11 state that ImGui might need later ----
    ID3D11RenderTargetView*  savedRTV = nullptr;
    ID3D11DepthStencilView*  savedDSV = nullptr;
    ID3D11RasterizerState*   savedRS  = nullptr;
    ID3D11BlendState*        savedBS  = nullptr;
    FLOAT                    savedBlendFactor[4] = {};
    UINT                     savedSampleMask = 0;
    ID3D11DepthStencilState* savedDSS = nullptr;
    UINT                     savedStencilRef = 0;
    D3D11_VIEWPORT savedVP = {};
    UINT numVP = 1;
    dx_context_->OMGetRenderTargets(1, &savedRTV, &savedDSV);
    dx_context_->RSGetState(&savedRS);
    dx_context_->OMGetBlendState(&savedBS, savedBlendFactor, &savedSampleMask);
    dx_context_->OMGetDepthStencilState(&savedDSS, &savedStencilRef);
    dx_context_->RSGetViewports(&numVP, &savedVP);

    // ---- Setup blur pipeline ----
    dx_context_->IASetInputLayout(dx_inputLayout_);
    dx_context_->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    UINT stride = 16, offset = 0;
    dx_context_->IASetVertexBuffers(0, 1, &dx_vertexBuf_, &stride, &offset);
    dx_context_->VSSetShader(dx_blurVS_, nullptr, 0);
    dx_context_->PSSetShader(dx_blurPS_, nullptr, 0);
    dx_context_->PSSetSamplers(0, 1, &dx_sampler_);
    dx_context_->PSSetConstantBuffers(0, 1, &dx_blurCB_);

    // Set clean rasterizer/blend/depth states for blur passes.
    // ImGui leaves ScissorEnable=TRUE with a small scissor rect which
    // would clip our fullscreen quads; alpha-blending would blend with
    // stale buffer contents instead of overwriting.
    dx_context_->RSSetState(dx_blurRS_);
    dx_context_->OMSetBlendState(dx_blurBS_, nullptr, 0xFFFFFFFF);
    dx_context_->OMSetDepthStencilState(dx_blurDSS_, 0);

    // Viewport for blur buffers
    D3D11_VIEWPORT vp = {};
    vp.Width  = (float)dx_blurWidth_;
    vp.Height = (float)dx_blurHeight_;
    vp.MaxDepth = 1.0f;
    dx_context_->RSSetViewports(1, &vp);

    // First pass: downsample capture → blurBuf[0] using a pass-through
    // (horizontal blur with radius → serves as downscale + first H-pass)
    dx_blurCurrent_ = 0;

    // Initial downscale: capture → blur[0]  (horizontal pass)
    // Use blur-buffer resolution for step size so the blur kernel covers the
    // same fraction of the image as subsequent passes.  The linear sampler
    // handles the implicit downscale from capture resolution.  Using the
    // capture resolution here would make each step only 1 capture texel,
    // producing a horizontal blur ~Nx weaker than the vertical passes at
    // blur resolution (N = downscale factor).
    {
        // Unbind blur[0] as input if it's bound
        ID3D11ShaderResourceView* nullSRV = nullptr;
        dx_context_->PSSetShaderResources(0, 1, &nullSRV);

        dx_context_->OMSetRenderTargets(1, &dx_blurRTV_[0], nullptr);
        dx_context_->PSSetShaderResources(0, 1, &dx_captureSRV_);

        D3D11_MAPPED_SUBRESOURCE mapped;
        dx_context_->Map(dx_blurCB_, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped);
        BlurCB* cb = (BlurCB*)mapped.pData;
        cb->dirX = 1.f; cb->dirY = 0.f;
        cb->resX = (float)dx_blurWidth_; cb->resY = (float)dx_blurHeight_;
        cb->radius = effectiveRadius / passes;
        dx_context_->Unmap(dx_blurCB_, 0);

        dx_context_->Draw(6, 0);
    }

    // Remaining blur passes on the blur-sized buffers
    for (int pass = 0; pass < passes; ++pass) {
        float passRadius = effectiveRadius / passes;

        // Vertical pass:  blur[0] → blur[1]
        {
            ID3D11ShaderResourceView* nullSRV = nullptr;
            dx_context_->PSSetShaderResources(0, 1, &nullSRV);

            dx_context_->OMSetRenderTargets(1, &dx_blurRTV_[1], nullptr);
            dx_context_->PSSetShaderResources(0, 1, &dx_blurSRV_[0]);

            D3D11_MAPPED_SUBRESOURCE mapped;
            dx_context_->Map(dx_blurCB_, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped);
            BlurCB* cb = (BlurCB*)mapped.pData;
            cb->dirX = 0.f; cb->dirY = 1.f;
            cb->resX = (float)dx_blurWidth_; cb->resY = (float)dx_blurHeight_;
            cb->radius = passRadius;
            dx_context_->Unmap(dx_blurCB_, 0);

            dx_context_->Draw(6, 0);
        }

        // Horizontal pass (if not last in this pass):  blur[1] → blur[0]
        if (pass < passes - 1) {
            ID3D11ShaderResourceView* nullSRV = nullptr;
            dx_context_->PSSetShaderResources(0, 1, &nullSRV);

            dx_context_->OMSetRenderTargets(1, &dx_blurRTV_[0], nullptr);
            dx_context_->PSSetShaderResources(0, 1, &dx_blurSRV_[1]);

            D3D11_MAPPED_SUBRESOURCE mapped;
            dx_context_->Map(dx_blurCB_, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped);
            BlurCB* cb = (BlurCB*)mapped.pData;
            cb->dirX = 1.f; cb->dirY = 0.f;
            cb->resX = (float)dx_blurWidth_; cb->resY = (float)dx_blurHeight_;
            cb->radius = passRadius;
            dx_context_->Unmap(dx_blurCB_, 0);

            dx_context_->Draw(6, 0);
        }
    }

    // The final result is in blur[1] (last pass was vertical blur[0]→blur[1])
    dx_blurCurrent_ = 1;

    // ---- Restore saved D3D11 state ----
    {
        ID3D11ShaderResourceView* nullSRV = nullptr;
        dx_context_->PSSetShaderResources(0, 1, &nullSRV);
    }
    dx_context_->OMSetRenderTargets(1, &savedRTV, savedDSV);
    dx_context_->RSSetState(savedRS);
    dx_context_->OMSetBlendState(savedBS, savedBlendFactor, savedSampleMask);
    dx_context_->OMSetDepthStencilState(savedDSS, savedStencilRef);
    dx_context_->RSSetViewports(1, &savedVP);
    if (savedRTV) savedRTV->Release();
    if (savedDSV) savedDSV->Release();
    if (savedRS)  savedRS->Release();
    if (savedBS)  savedBS->Release();
    if (savedDSS) savedDSS->Release();

    static bool s_traced = false;
    if (!s_traced) {
        s_traced = true;
        VERBOSE_LOGF("[Acrylic DX11] applyBlur: %d passes, radius=%.1f, blurSize=%dx%d\n",
               passes, effectiveRadius, dx_blurWidth_, dx_blurHeight_);
    }

    lastBlurRadius_ = scaledRadius;
    blurCacheValid_ = true;
}

ImTextureID AcrylicMaterial::getBlurredTexture() const
{
    return (ImTextureID)dx_blurSRV_[dx_blurCurrent_];
}

ImTextureID AcrylicMaterial::getNoiseTexture() const
{
    return (ImTextureID)dx_noiseSRV_;
}

void AcrylicMaterial::refreshCapabilities()
{
    capabilities_.glVersion  = "DX11";
    capabilities_.glRenderer = "Direct3D 11";
    capabilities_.hasFramebufferSupport = true;
    capabilities_.hasShaderSupport      = true;
    capabilities_.hasTextureSupport     = true;
    capabilities_.maxTextureSize        = 16384;
    capabilities_.isLowEndGPU           = false;
    capabilities_.isOnBattery           = false;

#ifdef _WIN32
    // Check battery status (Windows)
    SYSTEM_POWER_STATUS sps;
    if (GetSystemPowerStatus(&sps)) {
        capabilities_.isOnBattery = (sps.ACLineStatus == 0);
    }
#endif

    DEBUG_LOGF("[Acrylic DX11] Capabilities: DX11 renderer\n");
}

AcrylicFallback AcrylicMaterial::detectFallback() const
{
    if (hasForcedFallback_) return forcedFallback_;
    if (settings_.reducedTransparency) return AcrylicFallback::Solid;
    if (settings_.quality == AcrylicQuality::Off) return AcrylicFallback::TintedOnly;
    if (!settings_.enabled) return AcrylicFallback::TintedOnly;
    if (settings_.disableOnBattery && capabilities_.isOnBattery) return AcrylicFallback::TintedOnly;
    return AcrylicFallback::None;
}

void AcrylicMaterial::setQuality(AcrylicQuality quality)
{
    if (settings_.quality != quality) {
        settings_.quality = quality;
        blurCacheValid_ = false;
        int w = viewportWidth_, h = viewportHeight_;
        viewportWidth_ = viewportHeight_ = 0;
        resize(w, h);
    }
}

bool AcrylicMaterial::shouldSkipEffect() const
{
    return !settings_.enabled ||
           settings_.reducedTransparency ||
           settings_.quality == AcrylicQuality::Off;
}

void AcrylicMaterial::drawRect(ImDrawList* drawList, const ImVec2& pMin, const ImVec2& pMax,
                                const AcrylicParams& params, float rounding)
{
    if (!initialized_) return;

    currentFallback_ = detectFallback();

    static bool s_traced = false;
    if (!s_traced) {
        s_traced = true;
        VERBOSE_LOGF("[Acrylic DX11] drawRect first call: fallback=%d enabled=%d quality=%d "
               "hasCapture=%d blurValid=%d viewport=%dx%d\n",
               (int)currentFallback_, (int)settings_.enabled,
               (int)settings_.quality, (int)hasValidCapture_,
               (int)blurCacheValid_, viewportWidth_, viewportHeight_);
    }

    // Fallback solid fill for DX11 — scaled by UI opacity for card transparency
    auto dx_scaledFallback = [&]() {
        ImVec4 c = params.fallbackColor;
        c.w *= settings_.uiOpacity;
        return ImGui::ColorConvertFloat4ToU32(c);
    };

    if (!params.enabled) {
        drawList->AddRectFilled(pMin, pMax, dx_scaledFallback(), rounding);
        return;
    }

    switch (currentFallback_) {
        case AcrylicFallback::Solid:
            drawList->AddRectFilled(pMin, pMax, dx_scaledFallback(), rounding);
            return;
        case AcrylicFallback::TintedOnly:
            drawTintedRect(drawList, pMin, pMax, params, rounding);
            return;
        default: break;
    }

    if (!hasValidCapture_) {
        drawTintedRect(drawList, pMin, pMax, params, rounding);
        return;
    }

    // Run DX11 blur pipeline
    applyBlur(params.blurRadius, params.absoluteBlurRadius);

    // DX11 UV: top-left = (0,0), bottom-right = (1,1)  — same as ImGui
    // With multi-viewport, pMin/pMax are in OS screen space; subtract
    // the main viewport position to get window-local coords for UVs.
    ImGuiViewport* mainVp = ImGui::GetMainViewport();
    float localX0 = pMin.x - mainVp->Pos.x;
    float localY0 = pMin.y - mainVp->Pos.y;
    float localX1 = pMax.x - mainVp->Pos.x;
    float localY1 = pMax.y - mainVp->Pos.y;
    float u0 = localX0 / viewportWidth_;
    float v0 = localY0 / viewportHeight_;
    float u1 = localX1 / viewportWidth_;
    float v1 = localY1 / viewportHeight_;

    ImTextureID blurTex = getBlurredTexture();

    static bool s_blurTraced = false;
    if (!s_blurTraced && blurTex) {
        s_blurTraced = true;
        VERBOSE_LOGF("[Acrylic DX11] blur tex=%p  UV: (%.3f,%.3f)-(%.3f,%.3f)\n",
               (void*)blurTex, u0, v0, u1, v1);
    }

    // Draw the blurred background at (near) full strength so the panel is actually FROSTED — UI
    // opacity is applied only to the tint overlay, NOT here (scaling the blur by uiOpacity let the
    // SHARP background bleed through at low opacity, making the acrylic slider a no-op). Mirrors the
    // GL drawRect fix above.
    uint8_t glassAlpha = (uint8_t)std::min(255.f,
        std::max(0.f, std::max(params.fallbackColor.w, 0.90f) * 255.f));

    if (blurTex) {
        drawList->AddImageRounded(
            blurTex, pMin, pMax,
            ImVec2(u0, v0), ImVec2(u1, v1),
            IM_COL32(255, 255, 255, glassAlpha),
            rounding);
    }

    // Noise grain overlay — single draw call via pre-tiled texture
    if (params.noiseOpacity > 0.0f && settings_.noiseOpacityMultiplier > 0.0f) {
        float maxAlpha = schema::UI().drawElement("backdrop", "noise-max-alpha").sizeOr(6.0f);
        float scaledNoise = params.noiseOpacity * settings_.noiseOpacityMultiplier;
        uint8_t alpha = (uint8_t)std::min(255.f, maxAlpha * scaledNoise * 50.f);
        ImU32 col = IM_COL32(255, 255, 255, alpha);
        drawList->PushClipRect(pMin, pMax, true);
        dragonx::util::DrawTiledNoiseRect(drawList, pMin, pMax, col);
        drawList->PopClipRect();
    }
}

void AcrylicMaterial::drawTintedRect(ImDrawList* drawList, const ImVec2& pMin, const ImVec2& pMax,
                                      const AcrylicParams& params, float rounding)
{
    ImU32 tintCol = ImGui::ColorConvertFloat4ToU32(
        ImVec4(params.tintColor.x, params.tintColor.y, params.tintColor.z,
               params.tintColor.w * params.tintOpacity * 0.9f * settings_.uiOpacity));
    drawList->AddRectFilled(pMin, pMax, tintCol, rounding);

    // Noise grain overlay — single draw call via pre-tiled texture
    if (params.noiseOpacity > 0.0f && settings_.noiseOpacityMultiplier > 0.0f) {
        float maxAlpha = schema::UI().drawElement("backdrop", "noise-max-alpha").sizeOr(6.0f);
        float scaledNoise = params.noiseOpacity * settings_.noiseOpacityMultiplier;
        uint8_t alpha = (uint8_t)std::min(255.f, maxAlpha * scaledNoise * 50.f);
        ImGuiViewport* vp = ImGui::GetMainViewport();
        int vpW = (int)vp->Size.x, vpH = (int)vp->Size.y;
        int texW = 0, texH = 0;
        ImTextureID noiseTex = dragonx::util::GetTiledNoiseTexture(vpW, vpH, &texW, &texH);
        if (noiseTex && texW > 0 && texH > 0) {
            float u0 = (pMin.x - vp->Pos.x) / (float)texW;
            float v0 = (pMin.y - vp->Pos.y) / (float)texH;
            float u1 = (pMax.x - vp->Pos.x) / (float)texW;
            float v1 = (pMax.y - vp->Pos.y) / (float)texH;
            drawList->AddImageRounded(
                noiseTex, pMin, pMax,
                ImVec2(u0, v0), ImVec2(u1, v1),
                IM_COL32(255, 255, 255, alpha),
                rounding);
        }
    }
}

// ============================================================================
// Preset Parameters (same values as GL build)
// ============================================================================

AcrylicParams AcrylicMaterial::getDarkPreset()
{
    AcrylicParams p;
    p.tintColor = ImVec4(0.1f, 0.1f, 0.12f, 0.95f);
    p.tintOpacity = 0.75f;  p.luminosityOpacity = 0.6f;
    p.blurRadius = 30.f;    p.noiseOpacity = 0.02f;
    p.fallbackColor = ImVec4(0.12f, 0.12f, 0.14f, 1.f);
    return p;
}

AcrylicParams AcrylicMaterial::getLightPreset()
{
    AcrylicParams p;
    p.tintColor = ImVec4(0.95f, 0.95f, 0.97f, 0.95f);
    p.tintOpacity = 0.80f;  p.luminosityOpacity = 0.7f;
    p.blurRadius = 25.f;    p.noiseOpacity = 0.02f;
    p.fallbackColor = ImVec4(0.96f, 0.96f, 0.98f, 1.f);
    return p;
}

AcrylicParams AcrylicMaterial::getDragonXPreset()
{
    AcrylicParams p;
    p.tintColor = ImVec4(0.6f, 0.15f, 0.1f, 0.95f);
    p.tintOpacity = 0.85f;  p.luminosityOpacity = 0.3f;
    p.blurRadius = 40.f;    p.noiseOpacity = 0.025f;
    p.fallbackColor = ImVec4(0.5f, 0.12f, 0.08f, 1.f);
    return p;
}

AcrylicParams AcrylicMaterial::getPopupPreset()
{
    AcrylicParams p;
    p.tintColor = ImVec4(0.15f, 0.15f, 0.18f, 0.95f);
    p.tintOpacity = 0.70f;  p.luminosityOpacity = 0.5f;
    p.blurRadius = 30.f;    p.noiseOpacity = 0.02f;
    p.fallbackColor = ImVec4(0.18f, 0.18f, 0.2f, 1.f);
    return p;
}

AcrylicParams AcrylicMaterial::getSidebarPreset()
{
    AcrylicParams p;
    p.tintColor = ImVec4(0.08f, 0.08f, 0.1f, 0.95f);
    p.tintOpacity = 0.80f;  p.luminosityOpacity = 0.4f;
    p.blurRadius = 35.f;    p.noiseOpacity = 0.02f;
    p.fallbackColor = ImVec4(0.1f, 0.1f, 0.12f, 1.f);
    return p;
}

AcrylicMaterial& getAcrylicMaterial()
{
    static AcrylicMaterial instance;
    return instance;
}

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

#else // neither GLAD nor DX11

// ============================================================================
// Stub Implementation (No graphics backend)
// ============================================================================

namespace dragonx {
namespace ui {
namespace effects {

AcrylicMaterial::AcrylicMaterial() = default;
AcrylicMaterial::~AcrylicMaterial() = default;
bool AcrylicMaterial::init() { return false; }
void AcrylicMaterial::shutdown() {}
void AcrylicMaterial::resize(int, int) {}
void AcrylicMaterial::captureBackground() {}
void AcrylicMaterial::captureBackgroundDirect() {}
void AcrylicMaterial::captureLiveFramebuffer() {}
void AcrylicMaterial::applyBlur(float, bool) {}
ImTextureID AcrylicMaterial::getBlurredTexture() const { return 0; }
ImTextureID AcrylicMaterial::getNoiseTexture() const { return 0; }
void AcrylicMaterial::refreshCapabilities() {}
AcrylicFallback AcrylicMaterial::detectFallback() const { return AcrylicFallback::Solid; }
void AcrylicMaterial::drawTintedRect(ImDrawList* dl, const ImVec2& a, const ImVec2& b,
                                      const AcrylicParams& p, float r) {
    dl->AddRectFilled(a, b, ImGui::ColorConvertFloat4ToU32(p.fallbackColor), r);
}
void AcrylicMaterial::drawRect(ImDrawList* dl, const ImVec2& a, const ImVec2& b,
                                const AcrylicParams& p, float r) {
    dl->AddRectFilled(a, b, ImGui::ColorConvertFloat4ToU32(p.fallbackColor), r);
}
AcrylicParams AcrylicMaterial::getDarkPreset() { AcrylicParams p; p.enabled=false; return p; }
AcrylicParams AcrylicMaterial::getLightPreset() { AcrylicParams p; p.enabled=false; return p; }
AcrylicParams AcrylicMaterial::getDragonXPreset() { AcrylicParams p; p.enabled=false; return p; }
AcrylicParams AcrylicMaterial::getPopupPreset() { AcrylicParams p; p.enabled=false; return p; }
AcrylicParams AcrylicMaterial::getSidebarPreset() { AcrylicParams p; p.enabled=false; return p; }
AcrylicMaterial& getAcrylicMaterial() { static AcrylicMaterial i; return i; }

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

#endif // DRAGONX_USE_DX11

#endif // DRAGONX_HAS_GLAD
