#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
// (16 floats = 4 × vec4 = 64 bytes, std140/scalar layout).
// IMPORTANT: Qualsevol canvi ací cal replicar-lo a mà a
// source/core/rendering/sdl3gpu/msl/postfx_frag.msl.h (no hi ha generador).

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_min;       // intensitat mínima de l'aberració cromàtica
    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 flicker;       // 0 = off, 1 = phosphor flicker ~50 Hz
    float chroma_max;    // intensitat màxima; si == chroma_min → chroma estàtic
    // vec4 #3 — paràmetres de scanlines (exposats per preset YAML)
    float scan_dark_ratio;  // fracció de subfila fosca per fila lògica (1/3 ≈ 0.333)
    float scan_dark_floor;  // multiplicador de brillantor de la subfila fosca
    float scan_edge_soft;   // 0 = step dur; 1 = suavitzat d'1 píxel físic (estil crtpi)
    float pad3;             // padding per tancar a 64 bytes (4 × vec4)
} u;

// Mostreig bilinear horitzontal d'un canal RGB. Evita el "tic-tac" del sampler
// NEAREST quan l'offset de chroma és subpíxel: sense interpolar, l'offset
// arrodonia entre 1 i 2 píxels i el drift temporal feia un parpelleig discret.
float sampleBilinearX(vec2 uv_target, int channel) {
    vec2 tex_size = vec2(textureSize(scene, 0));
    float px = uv_target.x * tex_size.x - 0.5;
    float p_floor = floor(px);
    float f = px - p_floor;
    vec4 c0 = texture(scene, vec2((p_floor + 0.5) / tex_size.x, uv_target.y));
    vec4 c1 = texture(scene, vec2((p_floor + 1.5) / tex_size.x, uv_target.y));
    return mix(c0[channel], c1[channel], f);
}

// 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.
    vec3 colour;
    if (u.bleeding > 0.0) {
        float tw   = float(textureSize(scene, 0).x);
        float step = 1.0 / 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 — intensitat varia entre chroma_min i chroma_max amb
    // una sinusoidal (si min == max, queda estàtica). Mostreig bilinear horitzontal
    // per evitar el "tic-tac" del NEAREST sampler quan l'offset és subpíxel.
    if (u.chroma_min > 0.0 || u.chroma_max > 0.0) {
        float ca = mix(u.chroma_min, u.chroma_max, 0.5 + 0.5 * sin(u.time * 7.3)) * 0.005;
        colour.r = sampleBilinearX(uv + vec2(ca, 0.0), 0);
        colour.b = sampleBilinearX(uv - vec2(ca, 0.0), 2);
    }

    // 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 — tècnica dels 3 subpíxels verticals per píxel lògic (aee/projecte_2026):
    // franja fosca ocupant `scan_dark_ratio` al final de cada fila lògica. La transició es
    // suavitza amb smoothstep d'ample ≈ 1 píxel físic (estil crtpi: filtratge analític
    // continu), controlat per `scan_edge_soft`. A 0 és equivalent al step dur antic.
    if (u.scanline_strength > 0.0) {
        float ps          = max(u.pixel_scale, 1.0);
        float sub         = fract(uv.y * u.screen_height);     // [0,1) dins la fila lògica
        float dark_center = 1.0 - u.scan_dark_ratio * 0.5;     // centre de la franja fosca
        float d           = abs(sub - dark_center);
        d                 = min(d, 1.0 - d);                   // wrap a la fila següent
        float half_width  = u.scan_dark_ratio * 0.5;
        float softness    = u.scan_edge_soft * 0.5 / ps;       // mig píxel físic a cada costat
        float band        = 1.0 - smoothstep(half_width - softness, half_width + softness, d);
        float scan        = mix(1.0, u.scan_dark_floor, band);
        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);
}
