// @name Cinematic Lens Flare
// @group Flare Source
// @param uFlareX "Position X" 0.72 0.0 1.0 0.01
// @param uFlareY "Position Y" 0.38 0.0 1.0 0.01
// @param uAutoResponse "Highlight Response" 0.80 0.0 1.0 0.01
// @param uSourceThreshold "Highlight Threshold" 0.55 0.0 2.0 0.01
// @param uSourceGain "Highlight Gain" 2.50 0.0 8.0 0.05
// @param uManualGlow "Manual Glow" 0.14 0.0 1.0 0.01
// @param uProbeRadius "Source Probe Radius" 22.0 1.0 120.0 1.0
// @group Core & Halo
// @param uCoreIntensity "Core Intensity" 1.10 0.0 4.0 0.05
// @param uCoreSize "Core Size" 0.030 0.002 0.15 0.001
// @param uHaloIntensity "Halo Intensity" 0.42 0.0 3.0 0.02
// @param uHaloSize "Halo Size" 0.16 0.02 0.60 0.01
// @group Anamorphic Streak
// @param uStreakIntensity "Streak Intensity" 0.85 0.0 4.0 0.05
// @param uStreakLength "Streak Length" 0.55 0.05 1.50 0.01
// @param uStreakWidth "Streak Width" 0.0040 0.0005 0.0400 0.0005
// @param uHighlightStreak "Scene Highlight Streaks" 0.55 0.0 3.0 0.05
// @param uStarburst "Diffraction Star" 0.30 0.0 2.0 0.02
// @group Optical Ghosts
// @param uGhostIntensity "Ghost Intensity" 0.55 0.0 3.0 0.05
// @param uGhostScale "Ghost Scale" 1.00 0.25 3.0 0.01
// @param uGhostSpread "Ghost Spread" 1.00 0.20 2.0 0.01
// @param uRingIntensity "Aperture Rings" 0.40 0.0 2.0 0.02
// @param uDispersion "Spectral Dispersion" 0.008 0.0 0.040 0.001
// @group Color & Finish
// @param uWarmth "Core Warmth" 0.58 0.0 1.0 0.01
// @param uSpectral "Spectral Color" 0.65 0.0 1.5 0.01
// @param uFlareExposure "Flare Exposure" 1.00 0.0 4.0 0.05
// @param uAdditiveMix "Additive Blend" 0.35 0.0 1.0 0.01
// @group Lens Character
// @param uDirtAmount "Lens Dirt" 0.08 0.0 1.0 0.01
// @param uDirtScale "Dirt Scale" 14.0 3.0 40.0 1.0
// @param uGrain "Optical Grain" 0.012 0.0 0.10 0.001
// @group Motion
// @param uDrift "Source Drift" 0.0 0.0 0.20 0.001
// @param uDriftSpeed "Drift Speed" 0.20 0.0 2.0 0.01
// @param uFlicker "Flare Flicker" 0.025 0.0 0.30 0.005

float flareLuma(vec3 color) {
    return dot(color, vec3(0.2126, 0.7152, 0.0722));
}

float flareHash21(vec2 p) {
    p = fract(p * vec2(123.34, 456.21));
    p += dot(p, p + 45.32);
    return fract(p.x * p.y);
}

vec2 flareAspectPoint(vec2 uv) {
    vec2 p = uv - 0.5;
    p.x *= uResolution.x / max(uResolution.y, 1.0);
    return p;
}

vec3 flareBrightPass(vec2 uv) {
    vec3 color = texture(uTexture, clamp(uv, vec2(0.001), vec2(0.999))).rgb;
    float light = flareLuma(color);
    float pass = max(light - uSourceThreshold, 0.0) / max(light, 0.0001);
    return color * pass;
}

float flareGhostShape(vec2 p, vec2 center, float radius, float ringMix) {
    float d = length(p - center);
    float body = 1.0 - smoothstep(radius * 0.10, radius, d);
    float edge = exp(-abs(d - radius * 0.72) / max(radius * 0.065, 0.0001));
    float inner = exp(-abs(d - radius * 0.34) / max(radius * 0.10, 0.0001));
    return body * 0.10 + edge * ringMix + inner * ringMix * 0.18;
}

vec3 flareSpectralGhost(
    vec2 p,
    vec2 center,
    vec2 axis,
    float radius,
    float dispersion,
    float ringMix,
    vec3 tint
) {
    float red = flareGhostShape(p + axis * dispersion, center, radius, ringMix);
    float green = flareGhostShape(p, center, radius, ringMix);
    float blue = flareGhostShape(p - axis * dispersion, center, radius, ringMix);
    vec3 spectral = vec3(red, green, blue);
    return spectral * mix(vec3(1.0), tint, uSpectral);
}

float flareDirtLayer(vec2 uv, float scale, float seed) {
    vec2 cellUv = uv * scale;
    vec2 cell = floor(cellUv);
    vec2 local = fract(cellUv) - 0.5;
    float randomA = flareHash21(cell + seed);
    float randomB = flareHash21(cell + seed + 17.31);
    vec2 center = vec2(randomA, randomB) - 0.5;
    center *= 0.65;
    float radius = mix(0.035, 0.16, flareHash21(cell + seed + 9.17));
    float spot = 1.0 - smoothstep(radius, radius + 0.035, length(local - center));
    return spot * smoothstep(0.72, 0.98, flareHash21(cell + seed + 33.70));
}

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 uv = fragCoord / uResolution.xy;
    vec4 sourceColor = texture(uTexture, uv);
    float aspect = uResolution.x / max(uResolution.y, 1.0);

    float driftPhase = uTime * uDriftSpeed;
    vec2 drift = vec2(
        sin(driftPhase * 0.93) + sin(driftPhase * 0.37 + 1.70) * 0.35,
        cos(driftPhase * 0.71 + 0.40) + sin(driftPhase * 0.29) * 0.30
    ) * uDrift;
    vec2 flareUv = clamp(vec2(uFlareX, uFlareY) + drift, vec2(0.01), vec2(0.99));

    vec2 probe = vec2(uProbeRadius) / max(uResolution, vec2(1.0));
    float sourceLight = flareLuma(texture(uTexture, flareUv).rgb);
    sourceLight = max(sourceLight, flareLuma(texture(uTexture, flareUv + vec2(probe.x, 0.0)).rgb));
    sourceLight = max(sourceLight, flareLuma(texture(uTexture, flareUv - vec2(probe.x, 0.0)).rgb));
    sourceLight = max(sourceLight, flareLuma(texture(uTexture, flareUv + vec2(0.0, probe.y)).rgb));
    sourceLight = max(sourceLight, flareLuma(texture(uTexture, flareUv - vec2(0.0, probe.y)).rgb));
    sourceLight = max(sourceLight, flareLuma(texture(uTexture, flareUv + probe * 0.72).rgb));
    sourceLight = max(sourceLight, flareLuma(texture(uTexture, flareUv - probe * 0.72).rgb));
    sourceLight = max(sourceLight, flareLuma(texture(uTexture, flareUv + vec2(probe.x, -probe.y) * 0.72).rgb));
    sourceLight = max(sourceLight, flareLuma(texture(uTexture, flareUv + vec2(-probe.x, probe.y) * 0.72).rgb));

    float thresholdWidth = max(0.04, 0.20 + uSourceThreshold * 0.05);
    float autoLight = smoothstep(
        uSourceThreshold,
        uSourceThreshold + thresholdWidth,
        sourceLight
    ) * uSourceGain;
    float activation = clamp(uManualGlow + autoLight * uAutoResponse, 0.0, 2.5);

    vec2 p = flareAspectPoint(uv);
    vec2 sourceP = flareAspectPoint(flareUv);
    vec2 relative = p - sourceP;
    float distanceToSource = length(relative);
    vec2 opticalAxis = normalize(-sourceP + vec2(0.0001, 0.0001));

    float coreSize = max(uCoreSize, 0.0001);
    float core = exp(-distanceToSource * distanceToSource / (coreSize * coreSize));
    float hotCore = exp(-distanceToSource / max(coreSize * 0.18, 0.0001));

    float haloSize = max(uHaloSize, 0.0001);
    float halo = 1.0 / (1.0 + pow(distanceToSource / haloSize, 3.2));
    float haloRing = exp(
        -abs(distanceToSource - haloSize * 0.66) / max(haloSize * 0.075, 0.0001)
    );

    float beamWidth = max(uStreakWidth, 0.0001);
    float horizontalBeam = exp(-abs(relative.y) / beamWidth)
        * exp(-pow(abs(relative.x) / max(uStreakLength, 0.001), 1.25));
    float needleBeam = exp(-abs(relative.y) / max(beamWidth * 0.16, 0.0001))
        * exp(-abs(relative.x) / max(uStreakLength * 1.35, 0.001));

    float angle = atan(relative.y, relative.x);
    float starShape = pow(abs(cos(angle * 2.0)), 44.0);
    float starFade = exp(-distanceToSource / max(haloSize * 1.25, 0.001));
    float star = starShape * starFade;

    vec3 highlightStreak = vec3(0.0);
    float streakWeight = 0.0;
    for (int i = -6; i <= 6; i++) {
        float samplePosition = float(i) / 6.0;
        float weight = exp(-samplePosition * samplePosition * 3.2);
        vec2 offset = vec2(samplePosition * uStreakLength * 0.34 / max(aspect, 0.001), 0.0);
        vec3 bright = flareBrightPass(uv + offset);
        highlightStreak += bright * weight;
        streakWeight += weight;
    }
    highlightStreak /= max(streakWeight, 0.0001);
    highlightStreak *= uHighlightStreak;

    float ghostScale = uGhostScale;
    float ghostDispersion = uDispersion * ghostScale;
    vec3 ghosts = vec3(0.0);
    ghosts += flareSpectralGhost(
        p,
        -sourceP * (0.18 * uGhostSpread),
        opticalAxis,
        0.055 * ghostScale,
        ghostDispersion,
        uRingIntensity,
        vec3(0.42, 0.86, 1.22)
    ) * 0.75;
    ghosts += flareSpectralGhost(
        p,
        -sourceP * (0.52 * uGhostSpread),
        opticalAxis,
        0.095 * ghostScale,
        ghostDispersion * 1.35,
        uRingIntensity,
        vec3(1.18, 0.42, 0.90)
    ) * 0.48;
    ghosts += flareSpectralGhost(
        p,
        -sourceP * (0.91 * uGhostSpread),
        opticalAxis,
        0.037 * ghostScale,
        ghostDispersion * 0.80,
        uRingIntensity,
        vec3(0.55, 1.12, 0.66)
    ) * 0.82;
    ghosts += flareSpectralGhost(
        p,
        sourceP * (0.34 * uGhostSpread),
        opticalAxis,
        0.022 * ghostScale,
        ghostDispersion * 0.55,
        uRingIntensity,
        vec3(1.24, 0.72, 0.30)
    ) * 0.95;

    vec3 coolCore = vec3(0.55, 0.78, 1.28);
    vec3 warmCore = vec3(1.36, 0.68, 0.28);
    vec3 coreTint = mix(coolCore, warmCore, uWarmth);
    vec3 streakTint = mix(vec3(0.34, 0.72, 1.25), vec3(1.15, 0.48, 0.72), uWarmth);

    vec3 flare = vec3(0.0);
    flare += coreTint * (core * uCoreIntensity + hotCore * uCoreIntensity * 0.85);
    flare += mix(coreTint, vec3(0.35, 0.68, 1.05), 0.48)
        * (halo * uHaloIntensity + haloRing * uHaloIntensity * 0.24);
    flare += streakTint * (
        horizontalBeam * uStreakIntensity
        + needleBeam * uStreakIntensity * 0.72
    );
    flare += mix(coreTint, vec3(0.70, 0.82, 1.20), 0.60) * star * uStarburst;
    flare += ghosts * uGhostIntensity;
    flare *= activation;

    vec3 sceneStreakTint = mix(
        highlightStreak,
        highlightStreak * vec3(0.74, 0.88, 1.20),
        uSpectral * 0.40
    );
    flare += sceneStreakTint;

    float dirt = flareDirtLayer(uv, uDirtScale, 2.10);
    dirt += flareDirtLayer(uv + vec2(0.17, -0.08), uDirtScale * 0.57, 11.80) * 0.60;
    float flareField = clamp(halo * activation + flareLuma(highlightStreak) * 0.8, 0.0, 1.0);
    flare += dirt * flareField * uDirtAmount * mix(coreTint, vec3(0.70, 0.90, 1.15), 0.55);

    float flickerWave = sin(uTime * 11.70) * 0.55 + sin(uTime * 23.13 + 1.40) * 0.30;
    flickerWave += sin(uTime * 4.71 + 0.80) * 0.15;
    float flicker = max(0.0, 1.0 + flickerWave * uFlicker);
    flare *= uFlareExposure * uIntensity * flicker;

    vec3 base = max(sourceColor.rgb, vec3(0.0));
    vec3 screenBlend = base + flare * (1.0 - clamp(base, vec3(0.0), vec3(1.0)));
    vec3 additiveBlend = base + flare;
    vec3 result = mix(screenBlend, additiveBlend, uAdditiveMix);

    float grain = flareHash21(fragCoord + floor(uTime * 24.0)) - 0.5;
    result += grain * uGrain * clamp(flareLuma(flare) * 2.0, 0.0, 1.0);

    fragColor = vec4(max(result, vec3(0.0)), sourceColor.a);
}
