#version 330 core
// Android-aligned dual-clip Zoom transition.
//
// Two style families share this program (`u_variant` 2-bit pack):
//   * style 0 = Zoom1 (ZoomBlur, mac wires 1015 / 1016)
//   * style 1 = Zoom2 (RotateZoom, mac wires 2061 / 2062)
//
// Both use a per-side {scale, blurStep, rotRadFx} triple supplied by
// the CPU. Zoom1 maps to Android's `zoomBlur(progress, isReverse)` in
// `EditorImageTransitionFilterHelper`: two-phase asymmetric Bezier
// (cp1=(0.5,0)cp2=(1,0.5) on the OUT half; cp1=(0,0.5)cp2=(0,1) on
// the IN half) drives scale + radial-zoom-blur amount; A gets the
// effect in phase 1, B in phase 2; hard cut at progress=0.5 hides
// the swap. Zoom2 is the single-pass approximation of Android's
// 4-pass `EditorImageRotateZoomFilterGroup` (transform + soul-red +
// zoom-blur + slabby-swirl); the full multi-pass implementation
// is deferred to the multi-pass infrastructure phase.

out vec4 FragColor;
in  vec2 TexCoord;

uniform sampler2D tex_aY;
uniform sampler2D tex_aU;
uniform sampler2D tex_aV;
uniform sampler2D tex_bY;
uniform sampler2D tex_bU;
uniform sampler2D tex_bV;

uniform float u_progress;
uniform float u_aScale;      // A central-crop scale (1 = no zoom)
uniform float u_aBlurStep;   // A radial zoom-blur step in UV (signed)
uniform float u_aRotRadFx;   // A effect-rotation overlay (Zoom2 only)
uniform float u_aSlabbyRad;  // A Slabby Swirl angle (Zoom2 only, 0 elsewhere)
uniform vec2  u_aSoulShift;  // A Soul-Red R-channel UV shift (Zoom2 only)
uniform float u_bScale;
uniform float u_bBlurStep;
uniform float u_bRotRadFx;
uniform float u_bSlabbyRad;
uniform vec2  u_bSoulShift;
uniform float u_slabbyRadiusUV;  // shared swirl radius (typically ~0.78 UV)

uniform int   u_aFlipX;
uniform int   u_aFlipY;
uniform float u_aRotRad;     // A texture-natural orientation rotation
uniform int   u_bFlipX;
uniform int   u_bFlipY;
uniform float u_bRotRad;

vec2 applyOrient(vec2 uv, int flipX, int flipY, float rotRad) {
    if (rotRad != 0.0) {
        vec2 c = uv - vec2(0.5);
        float s  = sin(rotRad);
        float co = cos(rotRad);
        uv = vec2(co * c.x - s * c.y, s * c.x + co * c.y) + vec2(0.5);
    }
    if (flipX != 0) uv.x = 1.0 - uv.x;
    if (flipY != 0) uv.y = 1.0 - uv.y;
    return uv;
}

vec3 yuvToRgb(float y, float u, float v) {
    y = (y - 16.0/255.0)  * (255.0/219.0);
    u = (u - 128.0/255.0) * (255.0/224.0);
    v = (v - 128.0/255.0) * (255.0/224.0);
    float r = y + 1.5748 * v;
    float g = y - 0.1873 * u - 0.4681 * v;
    float b = y + 1.8556 * u;
    return clamp(vec3(r, g, b), 0.0, 1.0);
}

vec3 sampleClip(sampler2D yT, sampler2D uT, sampler2D vT, vec2 uv) {
    // Edge-clamp on OOR -- textures are GL_CLAMP_TO_EDGE so the
    // explicit clamp here keeps the math defensive but the edge
    // pixel naturally extends instead of cutting to black.
    uv = clamp(uv, 0.0, 1.0);
    return yuvToRgb(texture(yT, uv).r,
                    texture(uT, uv).r,
                    texture(vT, uv).r);
}

// Slabby swirl: per-pixel angular shift `slabbyRadian * (dist/radius)^1.5`
// around `center`. Ported from Android's `EditorImageSlabbySwirlFilter`.
// Pixels near the center barely move; pixels near `radius` swirl by the
// full slabby radian.
vec2 slabbySwirl(vec2 uv, vec2 center, float radius, float slabbyRadian) {
    if (abs(slabbyRadian) < 0.0001 || radius < 0.0001) return uv;
    vec2 d = uv - center;
    float dist = length(d);
    if (dist < 0.00001) return uv;
    float pointAngle = atan(d.y, d.x);
    float percent = dist / radius;
    float p2 = pow(percent, 1.5);
    float subAngle = slabbyRadian * p2;
    float finalAngle = pointAngle + subAngle;
    return center + vec2(cos(finalAngle), sin(finalAngle)) * dist;
}

// Apply (in order): slabby swirl -> scale -> effect-rotation -> natural
// orient -> N-tap radial zoom blur -> Soul-Red R-channel substitution.
// `slabbyRad` and `soulShift` are zero for the Zoom1 path so the new
// stages collapse to identity transforms; Zoom2 lights them up to chain
// Android's 4-filter RotateZoom group in a single pass.
vec3 sampleZoomedBlur(sampler2D yT, sampler2D uT, sampler2D vT,
                      vec2 uv, float scale, float blurStep, float rotRadFx,
                      float slabbyRad, vec2 soulShift, float slabbyRadiusUV,
                      int orientFlipX, int orientFlipY, float orientRotRad) {
    // 1. Slabby swirl on the canvas UV (before scale/rotation).
    vec2 uvSwirl = slabbySwirl(uv, vec2(0.5), slabbyRadiusUV, slabbyRad);

    // 2. Effect rotation (Zoom2 overlay) + central scale.
    vec2 c = uvSwirl - vec2(0.5);
    if (rotRadFx != 0.0) {
        float s  = sin(rotRadFx);
        float co = cos(rotRadFx);
        c = vec2(co * c.x - s * c.y, s * c.x + co * c.y);
    }
    vec2 uvScaled = c / max(scale, 0.0001) + vec2(0.5);

    // 3. Natural orient remap.
    uvScaled = applyOrient(uvScaled, orientFlipX, orientFlipY, orientRotRad);

    // 4. Radial zoom blur (or single-tap fast path).
    vec3 rgb;
    if (abs(blurStep) < 0.0001) {
        rgb = sampleClip(yT, uT, vT, uvScaled);
    } else {
        const int kTaps = 9;
        vec3 acc = vec3(0.0);
        float total = 0.0;
        vec2 radial = uvScaled - vec2(0.5);
        for (int i = 0; i < kTaps; ++i) {
            float t = (float(i) - float(kTaps - 1) * 0.5) /
                      float(kTaps - 1);            // -0.5..+0.5
            vec2 sampUv = uvScaled + t * blurStep * radial;
            float w = 1.0 - abs(t * 2.0);          // triangular
            acc   += w * sampleClip(yT, uT, vT, sampUv);
            total += w;
        }
        rgb = acc / total;
    }

    // 5. Soul-Red: replace R channel with R sampled at +offset.
    if (length(soulShift) > 0.0001) {
        vec3 offRgb = sampleClip(yT, uT, vT, uvScaled + soulShift);
        rgb.r = offRgb.r;
    }
    return rgb;
}

void main() {
    vec2 uv = TexCoord;
    float p = clamp(u_progress, 0.0, 1.0);

    vec3 aRgb = sampleZoomedBlur(tex_aY, tex_aU, tex_aV,
                                 uv, u_aScale, u_aBlurStep, u_aRotRadFx,
                                 u_aSlabbyRad, u_aSoulShift, u_slabbyRadiusUV,
                                 u_aFlipX, u_aFlipY, u_aRotRad);
    vec3 bRgb = sampleZoomedBlur(tex_bY, tex_bU, tex_bV,
                                 uv, u_bScale, u_bBlurStep, u_bRotRadFx,
                                 u_bSlabbyRad, u_bSoulShift, u_slabbyRadiusUV,
                                 u_bFlipX, u_bFlipY, u_bRotRad);

    // Hard cut at progress = 0.5 with a narrow smoothstep band --
    // matches Android's "one frame at a time" filter group model.
    // The peak blur + scale mismatch at the boundary hides the cut.
    float cut = smoothstep(0.48, 0.52, p);
    vec3 rgb = mix(aRgb, bRgb, cut);
    FragColor = vec4(rgb, 1.0);
}
