#ifndef ASTRAL_SUN_LENS_FLARE_GLSL
#define ASTRAL_SUN_LENS_FLARE_GLSL

/* A restrained analytic daylight lens response. Geometry is expressed in
 * aspect-correct screen space, so no bitmap is dragged across the display.
 * The caller owns weather, horizon, depth and behind-camera visibility. */
vec3 getAstralSunLensFlare(
        vec2 screenUv,
        vec2 sunScreen,
        float aspectRatio,
        float visibility,
        vec3 sceneColor,
        float sunDiscRadius,
        float sunCycle) {
    #ifndef ASTRAL_SUN_LENS_FLARE
        return vec3(0.0);
    #else
        if (visibility < 0.001)
            return vec3(0.0);

        vec2 sunDelta = screenUv - sunScreen;
        sunDelta.x *= aspectRatio;
        float sunDistance = length(sunDelta);

        // Never add flare energy over the painted solar surface. The optical
        // response rises just outside the disc, preserving granulation and
        // colour instead of burning the texture into a white circle.
        float outsideSunDisc = smoothstep(
            sunDiscRadius * 0.96,
            sunDiscRadius * 1.28,
            sunDistance);

        // Soft core and a thin anamorphic streak. The streak is deliberately
        // restrained: readable against the sky without becoming a sci-fi bar.
        float core = exp(-sunDistance * sunDistance / 0.00185);
        float innerCore = exp(-sunDistance * sunDistance / 0.00024);
        float horizontalStreak = exp(-abs(sunDelta.y) * 92.0)
            * exp(-abs(sunDelta.x) * 3.55);
        float verticalNeedle = exp(-abs(sunDelta.x) * 155.0)
            * exp(-abs(sunDelta.y) * 17.0);
        float diagonalRayA = exp(-abs(
            sunDelta.y - sunDelta.x * 0.31) * 118.0)
            * exp(-sunDistance * 5.5);
        float diagonalRayB = exp(-abs(
            sunDelta.y + sunDelta.x * 0.31) * 118.0)
            * exp(-sunDistance * 5.5);

        // Two modest optical ghosts travel along the sun-to-frame-center axis.
        // Their ring response and unequal spacing sell a lens system while
        // keeping the effect simple and cinematic.
        vec2 opticalAxis = sunScreen - vec2(0.5);
        vec2 axisMetric = vec2(opticalAxis.x * aspectRatio, opticalAxis.y);
        float axisLength = length(axisMetric);
        // A centered sun used to collapse every ghost into the bright disc.
        // Preserve a small physical lens axis so the optics remain readable
        // while the player looks directly at it.
        vec2 axisDirection = axisLength > 0.018
            ? opticalAxis / axisLength
            : vec2(-0.54 / aspectRatio, 0.32);
        vec2 readableAxis = axisDirection * max(axisLength, 0.115);
        vec2 ghostCenterA = vec2(0.5) - readableAxis * 0.46;
        vec2 ghostCenterB = vec2(0.5) - readableAxis * 1.08;
        vec2 ghostDeltaA = screenUv - ghostCenterA;
        vec2 ghostDeltaB = screenUv - ghostCenterB;
        ghostDeltaA.x *= aspectRatio;
        ghostDeltaB.x *= aspectRatio;
        float ghostDistanceA = length(ghostDeltaA);
        float ghostDistanceB = length(ghostDeltaB);
        float ghostA = exp(-ghostDistanceA * ghostDistanceA / 0.00058);
        float ghostRing = exp(-pow(
            abs(ghostDistanceB - 0.030), 2.0) / 0.000030);

        // True Minecraft solar-cycle palette. day_moment is deliberately
        // symmetric in this pack and made dawn and dusk look identical, so
        // use worldTime phase: 0 sunrise, .25 noon and .50 sunset.
        float cycle = fract(sunCycle);
        float sunriseDistance = min(abs(cycle), abs(cycle - 1.0));
        float noonDistance = abs(cycle - 0.25);
        float sunsetDistance = abs(cycle - 0.50);
        float sunriseWeight = 1.0 - smoothstep(
            0.055, 0.195, sunriseDistance);
        float noonWeight = 1.0 - smoothstep(
            0.075, 0.225, noonDistance);
        float sunsetWeight = 1.0 - smoothstep(
            0.045, 0.180, sunsetDistance);
        vec3 sunriseFlare = vec3(1.24, 0.55, 0.16);
        vec3 noonFlare = vec3(0.38, 0.82, 1.22);
        vec3 sunsetFlare = vec3(1.18, 0.23, 0.52);
        vec3 flareColor = vec3(0.52, 0.80, 1.10);
        flareColor = mix(flareColor, noonFlare, noonWeight);
        flareColor = mix(flareColor, sunriseFlare, sunriseWeight);
        flareColor = mix(flareColor, sunsetFlare, sunsetWeight);
        vec3 ghostColor = mix(
            flareColor.bgr, vec3(0.40, 0.92, 1.18), 0.48);

        // Bright clouds and a saturated sky previously swallowed the flare.
        // Use scene luminance to lift only the structured optics, and mix in a
        // warm/cool opponent hue so contrast survives the later ACES curve.
        float sceneLuma = dot(max(sceneColor, vec3(0.0)),
            vec3(0.2126, 0.7152, 0.0722));
        float sceneMax = max(sceneColor.r,
            max(sceneColor.g, sceneColor.b));
        float sceneMin = min(sceneColor.r,
            min(sceneColor.g, sceneColor.b));
        float sceneChroma = max(sceneMax - sceneMin, 0.0);
        float brightBackgroundLift = mix(
            1.35, 2.20, smoothstep(0.32, 1.35, sceneLuma));
        vec3 opponentColor = sceneColor.b > sceneColor.r
            ? vec3(1.22, 0.51, 0.20)
            : vec3(0.34, 0.91, 1.20);
        vec3 contrastFlare = mix(flareColor, opponentColor,
            0.24 + smoothstep(0.10, 0.55, sceneChroma) * 0.16);

        float intensity = visibility * ASTRAL_SUN_FLARE_INTENSITY;
        vec3 radiance = contrastFlare
            * ((core * 0.24 + innerCore * 0.10)
                    * outsideSunDisc
                + horizontalStreak * 0.135 * outsideSunDisc
                + verticalNeedle * 0.044 * outsideSunDisc
                + (diagonalRayA + diagonalRayB)
                    * 0.024 * outsideSunDisc);
        radiance += ghostColor * ghostA * 0.135;
        radiance += mix(ghostColor, contrastFlare, 0.42)
            * ghostRing * 0.082;
        // A neutral fringe gives the coloured optics separation from bright
        // cloud edges, but remains strictly outside the painted disc.
        radiance += vec3(1.04, 1.01, 0.92)
            * (core * 0.045 + horizontalStreak * 0.018)
            * outsideSunDisc;
        return max(radiance * intensity * brightBackgroundLift, vec3(0.0));
    #endif
}

#endif // ASTRAL_SUN_LENS_FLARE_GLSL
