// @name Bloom & Glow
// @group Threshold
// @param uBloomThreshold "Brightness Threshold" 0.5 0.0 1.0 0.01
// @param uKnee "Soft Knee" 0.5 0.0 1.0 0.05
// @param uThresholdColor "Color Threshold" 0.0 0.0 1.0 0.05
// @group Bloom Size
// @param uBloomRadius "Bloom Radius" 4.0 1.0 15.0 0.5
// @param uBloomSamples "Quality" 16.0 4.0 32.0 4.0
// @param uBloomFalloff "Falloff" 2.0 0.5 4.0 0.1
// @group Intensity
// @param uBloomIntensity "Bloom Intensity" 0.8 0.0 3.0 0.05
// @param uGlowIntensity "Glow Intensity" 0.3 0.0 2.0 0.05
// @param uOriginalMix "Original Mix" 1.0 0.0 1.0 0.05
// @group Layers
// @param uLayer1Size "Layer 1 (Fine)" 1.0 0.0 3.0 0.1
// @param uLayer2Size "Layer 2 (Medium)" 1.0 0.0 3.0 0.1
// @param uLayer3Size "Layer 3 (Large)" 1.0 0.0 3.0 0.1
// @group Color
// @param uBloomTintR "Bloom Tint R" 1.0 0.0 2.0 0.05
// @param uBloomTintG "Bloom Tint G" 1.0 0.0 2.0 0.05
// @param uBloomTintB "Bloom Tint B" 1.0 0.0 2.0 0.05
// @param uBloomSaturation "Bloom Saturation" 1.0 0.0 2.0 0.05
// @group Anamorphic
// @param uAnamorphic "Anamorphic Streak" 0.0 0.0 1.0 0.05
// @param uAnamorphicAngle "Streak Angle" 0.0 -1.57 1.57 0.1
// @param uAnamorphicLength "Streak Length" 0.3 0.0 1.0 0.05
// @group Lens Effects
// @param uLensFlare "Lens Flare" 0.0 0.0 1.0 0.05
// @param uFlareDispersion "Flare Dispersion" 0.3 0.0 1.0 0.05
// @param uGhosts "Ghosts" 0.0 0.0 1.0 0.05
// @param uGhostSpacing "Ghost Spacing" 0.4 0.1 1.0 0.05
// @group Chromatic
// @param uChromaticBloom "Chromatic Bloom" 0.0 0.0 0.02 0.001
// @group Dirt
// @param uLensDirt "Lens Dirt" 0.0 0.0 1.0 0.05
// @param uDirtScale "Dirt Scale" 3.0 1.0 10.0 0.5
// @group Vignette
// @param uVignetteBloom "Vignette" 0.0 0.0 1.0 0.05

// Attempt to extract bright pixels with soft knee
vec3 extractBright(vec3 col, float threshold, float knee) {
    float brightness = dot(col, vec3(0.2126, 0.7152, 0.0722));
    float soft = brightness - threshold + knee;
    soft = clamp(soft, 0.0, 2.0 * knee);
    soft = soft * soft / (4.0 * knee + 0.00001);
    float contribution = max(soft, brightness - threshold) / max(brightness, 0.00001);
    return col * max(contribution, 0.0);
}

// Sample bloom at a specific radius
vec3 sampleBloom(vec2 uv, float radius, float samples, vec2 texelSize, float falloff) {
    vec3 bloom = vec3(0.0);
    float totalWeight = 0.0;
    
    float angleStep = 6.28318 / samples;
    
    for (float i = 0.0; i < 32.0; i++) {
        if (i >= samples) break;
        
        float angle = i * angleStep;
        vec2 dir = vec2(cos(angle), sin(angle));
        
        // Multiple distance samples
        for (float d = 1.0; d <= 4.0; d++) {
            float dist = d / 4.0 * radius;
            float weight = pow(1.0 - (d - 1.0) / 4.0, falloff);
            
            vec2 offset = dir * texelSize * dist * 50.0;
            vec3 samp = texture(uTexture, clamp(uv + offset, 0.0, 1.0)).rgb;
            
            bloom += samp * weight;
            totalWeight += weight;
        }
    }
    
    return bloom / max(totalWeight, 1.0);
}

// Anamorphic streak sampling
vec3 sampleAnamorphic(vec2 uv, float angle, float length, vec2 texelSize) {
    vec3 streak = vec3(0.0);
    float totalWeight = 0.0;
    
    vec2 dir = vec2(cos(angle), sin(angle));
    
    for (float i = -16.0; i <= 16.0; i++) {
        float t = i / 16.0;
        float weight = 1.0 - abs(t);
        weight = weight * weight;
        
        vec2 offset = dir * t * length * 200.0 * texelSize;
        vec3 samp = texture(uTexture, clamp(uv + offset, 0.0, 1.0)).rgb;
        
        streak += samp * weight;
        totalWeight += weight;
    }
    
    return streak / totalWeight;
}

// Pseudo-random for procedural dirt
float hash(vec2 p) {
    return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
}

float noise(vec2 p) {
    vec2 i = floor(p);
    vec2 f = fract(p);
    f = f * f * (3.0 - 2.0 * f);
    
    float a = hash(i);
    float b = hash(i + vec2(1.0, 0.0));
    float c = hash(i + vec2(0.0, 1.0));
    float d = hash(i + vec2(1.0, 1.0));
    
    return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}

float lensDirt(vec2 uv, float scale) {
    float dirt = 0.0;
    dirt += noise(uv * scale * 10.0) * 0.5;
    dirt += noise(uv * scale * 20.0) * 0.3;
    dirt += noise(uv * scale * 40.0) * 0.2;
    
    // Add some circular spots
    for (float i = 0.0; i < 8.0; i++) {
        vec2 pos = vec2(hash(vec2(i, 0.0)), hash(vec2(0.0, i)));
        float size = 0.02 + hash(vec2(i, i)) * 0.05;
        float spot = 1.0 - smoothstep(0.0, size, length(uv - pos));
        dirt += spot * 0.3;
    }
    
    return clamp(dirt, 0.0, 1.0);
}

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 uv = fragCoord / uResolution.xy;
    vec2 texelSize = 1.0 / uResolution.xy;
    vec2 center = vec2(0.5);
    
    // Original color
    vec4 original = texture(uTexture, uv);
    vec3 col = original.rgb;
    
    // Extract bright pixels
    vec3 bright = extractBright(col, uBloomThreshold, uKnee * 0.5);
    
    // Color-based threshold (extract saturated colors too)
    if (uThresholdColor > 0.0) {
        float maxC = max(col.r, max(col.g, col.b));
        float minC = min(col.r, min(col.g, col.b));
        float sat = (maxC - minC) / (maxC + 0.001);
        bright += col * sat * uThresholdColor;
    }
    
    // === MULTI-LAYER BLOOM ===
    vec3 bloom = vec3(0.0);
    
    // Layer 1: Fine detail bloom
    if (uLayer1Size > 0.0) {
        vec3 layer1 = sampleBloom(uv, uBloomRadius * 0.3, uBloomSamples, texelSize, uBloomFalloff);
        layer1 = extractBright(layer1, uBloomThreshold * 0.8, uKnee * 0.3);
        bloom += layer1 * uLayer1Size;
    }
    
    // Layer 2: Medium bloom
    if (uLayer2Size > 0.0) {
        vec3 layer2 = sampleBloom(uv, uBloomRadius * 0.7, uBloomSamples, texelSize, uBloomFalloff);
        layer2 = extractBright(layer2, uBloomThreshold * 0.9, uKnee * 0.4);
        bloom += layer2 * uLayer2Size;
    }
    
    // Layer 3: Large soft bloom
    if (uLayer3Size > 0.0) {
        vec3 layer3 = sampleBloom(uv, uBloomRadius, uBloomSamples * 0.5, texelSize, uBloomFalloff * 0.7);
        layer3 = extractBright(layer3, uBloomThreshold, uKnee * 0.5);
        bloom += layer3 * uLayer3Size;
    }
    
    // Normalize bloom layers
    float layerSum = uLayer1Size + uLayer2Size + uLayer3Size;
    if (layerSum > 0.0) {
        bloom /= layerSum;
    }
    
    // === CHROMATIC BLOOM ===
    if (uChromaticBloom > 0.001) {
        float chromaOffset = uChromaticBloom * 30.0;
        vec3 chromaBloom;
        chromaBloom.r = sampleBloom(uv + vec2(chromaOffset, 0.0) * texelSize, uBloomRadius, uBloomSamples * 0.5, texelSize, uBloomFalloff).r;
        chromaBloom.g = bloom.g;
        chromaBloom.b = sampleBloom(uv - vec2(chromaOffset, 0.0) * texelSize, uBloomRadius, uBloomSamples * 0.5, texelSize, uBloomFalloff).b;
        bloom = mix(bloom, chromaBloom, 0.5);
    }
    
    // === ANAMORPHIC STREAK ===
    vec3 streak = vec3(0.0);
    if (uAnamorphic > 0.0) {
        streak = sampleAnamorphic(uv, uAnamorphicAngle, uAnamorphicLength, texelSize);
        streak = extractBright(streak, uBloomThreshold * 0.7, uKnee * 0.3);
    }
    
    // === LENS FLARE ===
    vec3 flare = vec3(0.0);
    if (uLensFlare > 0.0) {
        // Ghost images (reflections from lens elements)
        vec2 ghostVec = (center - uv) * uGhostSpacing;
        
        for (float i = 0.0; i < 5.0; i++) {
            vec2 ghostUV = uv + ghostVec * (i + 1.0);
            
            if (ghostUV.x >= 0.0 && ghostUV.x <= 1.0 && ghostUV.y >= 0.0 && ghostUV.y <= 1.0) {
                vec3 ghostSamp = texture(uTexture, ghostUV).rgb;
                ghostSamp = extractBright(ghostSamp, uBloomThreshold * 1.2, 0.1);
                
                // Chromatic dispersion for ghosts
                float dispersion = uFlareDispersion * 0.02 * (i + 1.0);
                vec3 dispersed;
                dispersed.r = texture(uTexture, ghostUV + vec2(dispersion, 0.0)).r;
                dispersed.g = ghostSamp.g;
                dispersed.b = texture(uTexture, ghostUV - vec2(dispersion, 0.0)).b;
                
                float ghostWeight = 1.0 / (i + 1.0);
                flare += dispersed * ghostWeight * uGhosts;
            }
        }
        
        // Halo around bright spots
        float haloDist = length(uv - center);
        float halo = 1.0 - smoothstep(0.2, 0.5, haloDist);
        halo *= smoothstep(0.1, 0.2, haloDist);
        
        vec3 haloColor = vec3(0.6, 0.4, 1.0); // Purple tint
        flare += haloColor * halo * dot(bright, vec3(0.33)) * 0.5;
    }
    
    // === BLOOM COLOR PROCESSING ===
    // Saturation adjustment
    float bloomLuma = dot(bloom, vec3(0.299, 0.587, 0.114));
    bloom = mix(vec3(bloomLuma), bloom, uBloomSaturation);
    
    // Color tint
    vec3 bloomTint = vec3(uBloomTintR, uBloomTintG, uBloomTintB);
    bloom *= bloomTint;
    streak *= bloomTint;
    
    // === LENS DIRT ===
    float dirt = 0.0;
    if (uLensDirt > 0.0) {
        dirt = lensDirt(uv, uDirtScale);
        // Dirt reveals bloom more
        bloom *= 1.0 + dirt * uLensDirt * 2.0;
        streak *= 1.0 + dirt * uLensDirt;
    }
    
    // === COMBINE EVERYTHING ===
    vec3 result = col * uOriginalMix;
    
    // Add bloom
    result += bloom * uBloomIntensity * uIntensity;
    
    // Add glow (additive bright areas directly)
    result += bright * uGlowIntensity * uIntensity;
    
    // Add anamorphic streak
    result += streak * uAnamorphic * uIntensity;
    
    // Add lens flare
    result += flare * uLensFlare * uIntensity;
    
    // === VIGNETTE ===
    if (uVignetteBloom > 0.0) {
        float vigDist = length(uv - center);
        float vig = 1.0 - smoothstep(0.3, 0.9, vigDist) * uVignetteBloom;
        result *= vig;
    }
    
    fragColor = vec4(clamp(result, 0.0, 1.0), 1.0);
}
