#version 450

// Vulkan GLSL fragment shader — PostFX effects
// Used for SDL3 GPU API (SPIR-V path, Win/Linux).
// Compile: glslc postfx.frag -o postfx.frag.spv
//          xxd -i postfx.frag.spv > ../../source/core/rendering/sdl3gpu/postfx_frag_spv.h
//
// PostFXUniforms must match exactly the C++ struct in sdl3gpu_shader.hpp
// (8 floats, 32 bytes, std140/scalar layout).

layout(location = 0) in  vec2 v_uv;
layout(location = 0) out vec4 out_color;

layout(set = 2, binding = 0) uniform sampler2D scene;

layout(set = 3, binding = 0) uniform PostFXUniforms {
    float vignette_strength;
    float chroma_strength;
    float scanline_strength;
    float screen_height;
    float mask_strength;
    float gamma_strength;
    float curvature;
    float bleeding;
    float pixel_scale;   // physical pixels per logical pixel (vh / tex_height_)
    float time;          // seconds since SDL init
    float oversample;    // supersampling factor (1.0 = off, 3.0 = 3×SS)
    float flicker;       // 0 = off, 1 = phosphor flicker ~50 Hz — 48 bytes total (3 × 16)
} u;

// YCbCr helpers for NTSC bleeding
vec3 rgb_to_ycc(vec3 rgb) {
    return vec3(
         0.299*rgb.r + 0.587*rgb.g + 0.114*rgb.b,
        -0.169*rgb.r - 0.331*rgb.g + 0.500*rgb.b + 0.5,
         0.500*rgb.r - 0.419*rgb.g - 0.081*rgb.b + 0.5
    );
}
vec3 ycc_to_rgb(vec3 ycc) {
    float y  = ycc.x;
    float cb = ycc.y - 0.5;
    float cr = ycc.z - 0.5;
    return clamp(vec3(
        y + 1.402*cr,
        y - 0.344*cb - 0.714*cr,
        y + 1.772*cb
    ), 0.0, 1.0);
}

void main() {
    vec2 uv = v_uv;

    // Curvatura barrel CRT
    if (u.curvature > 0.0) {
        vec2 c = uv - 0.5;
        float rsq = dot(c, c);
        vec2 dist = vec2(0.05, 0.1) * u.curvature;
        vec2 barrelScale = vec2(1.0) - 0.23 * dist;
        c += c * (dist * rsq);
        c *= barrelScale;
        if (abs(c.x) >= 0.5 || abs(c.y) >= 0.5) {
            out_color = vec4(0.0, 0.0, 0.0, 1.0);
            return;
        }
        uv = c + 0.5;
    }

    // Muestra base
    vec3 base = texture(scene, uv).rgb;

    // Sangrado NTSC — difuminado horizontal de crominancia.
    // step = 1 pixel lógico de juego en UV (corrige SS: textureSize.x = game_w * oversample).
    vec3 colour;
    if (u.bleeding > 0.0) {
        float tw   = float(textureSize(scene, 0).x);
        float step = u.oversample / tw;  // 1 pixel lógico en UV
        vec3 ycc    = rgb_to_ycc(base);
        vec3 ycc_l2 = rgb_to_ycc(texture(scene, uv - vec2(2.0*step, 0.0)).rgb);
        vec3 ycc_l1 = rgb_to_ycc(texture(scene, uv - vec2(1.0*step, 0.0)).rgb);
        vec3 ycc_r1 = rgb_to_ycc(texture(scene, uv + vec2(1.0*step, 0.0)).rgb);
        vec3 ycc_r2 = rgb_to_ycc(texture(scene, uv + vec2(2.0*step, 0.0)).rgb);
        ycc.yz = (ycc_l2.yz + ycc_l1.yz*2.0 + ycc.yz*2.0 + ycc_r1.yz*2.0 + ycc_r2.yz) / 8.0;
        colour = mix(base, ycc_to_rgb(ycc), u.bleeding);
    } else {
        colour = base;
    }

    // Aberración cromática (drift animado con time para efecto NTSC real)
    float ca = u.chroma_strength * 0.005 * (1.0 + 0.15 * sin(u.time * 7.3));
    colour.r = texture(scene, uv + vec2(ca, 0.0)).r;
    colour.b = texture(scene, uv - vec2(ca, 0.0)).b;

    // Corrección gamma (linealizar antes de scanlines, codificar después)
    if (u.gamma_strength > 0.0) {
        vec3 lin = pow(colour, vec3(2.4));
        colour = mix(colour, lin, u.gamma_strength);
    }

    // Scanlines — proporción 2/3 brillantes + 1/3 oscuras por fila lógica.
    // Casos especiales: 1 subfila → sin efecto; 2 subfilas → 1+1 (50/50).
    // Constantes ajustables:
    const float SCAN_DARK_RATIO = 0.333;  // fracción de subfilas oscuras (ps >= 3)
    const float SCAN_DARK_FLOOR = 0.42;   // multiplicador de brillo de subfilas oscuras
    if (u.scanline_strength > 0.0) {
        float ps          = max(1.0, round(u.pixel_scale));
        float frac_in_row = fract(uv.y * u.screen_height);
        float row_pos     = floor(frac_in_row * ps);
        // bright_rows: cuántas subfilas son brillantes
        //   ps==1 → ps (todo brillante → is_dark nunca se activa)
        //   ps==2 → 1 brillante + 1 oscura
        //   ps>=3 → floor(ps * (1 - DARK_RATIO)) brillantes
        float bright_rows = (ps < 2.0) ? ps : ((ps < 3.0) ? 1.0 : floor(ps * (1.0 - SCAN_DARK_RATIO)));
        float is_dark     = step(bright_rows, row_pos);
        float scan        = mix(1.0, SCAN_DARK_FLOOR, is_dark);
        colour           *= mix(1.0, scan, u.scanline_strength);
    }

    if (u.gamma_strength > 0.0) {
        vec3 enc = pow(colour, vec3(1.0 / 2.2));
        colour = mix(colour, enc, u.gamma_strength);
    }

    // Viñeta
    vec2 d = uv - 0.5;
    float vignette = 1.0 - dot(d, d) * u.vignette_strength;
    colour *= clamp(vignette, 0.0, 1.0);

    // Máscara de fósforo RGB — después de scanlines (orden original):
    // filas brillantes saturadas → máscara invisible, filas oscuras → RGB visible.
    if (u.mask_strength > 0.0) {
        float whichMask = fract(gl_FragCoord.x * 0.3333333);
        vec3 mask = vec3(0.80);
        if (whichMask < 0.3333333)
            mask.x = 1.0;
        else if (whichMask < 0.6666666)
            mask.y = 1.0;
        else
            mask.z = 1.0;
        colour = mix(colour, colour * mask, u.mask_strength);
    }

    // Parpadeo de fósforo CRT (~50 Hz)
    if (u.flicker > 0.0) {
        float flicker_wave = sin(u.time * 100.0) * 0.5 + 0.5;
        colour *= 1.0 - u.flicker * 0.04 * flicker_wave;
    }

    out_color = vec4(colour, 1.0);
}
