#version 330 core
// Android-aligned dual-clip Rotate transition.
//
// Two style families share this program (`u_variant` 2-bit pack):
//   * style 0 = Rotate1 (RotateBlur, mac wires 1017 / 1018)
//   * style 1 = Rotate2 (Spin,        mac wires 2041 / 2042)
//
// Rotate1 maps to Android's `rotate(progress, isReverse)`: bezier-
// eased two-phase symmetric rotation (0 -> 180 -> 0 / forward,
// reverse winds the long way around), scale 1 -> 2.8 -> 1, with
// a radial angular blur around the canvas center; A gets the
// effect in phase 1, B in phase 2; hard cut at progress = 0.5.
//
// Rotate2 maps to Android's `spin(progress, isReverse)`: small
// rotation (max ~30 degrees = angle/6 of Rotate1's full angle)
// pivoted around the bottom canvas edge, with the radial blur
// center placed off-canvas below so the angular blur reads as
// a tangential smear at the frame.
//
// Shared bezier control points (0.88, 0) and (0.92, 0.68) -- very
// flat ease through most of the curve, sharp finish.

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;
uniform float u_aAngleRad;       // signed
uniform float u_aBlurAngleRad;   // signed angular half-spread
uniform float u_bScale;
uniform float u_bAngleRad;
uniform float u_bBlurAngleRad;
uniform vec2  u_rotPivot;        // family-shared rotation pivot in UV
uniform vec2  u_blurCenter;      // family-shared angular-blur center

uniform int   u_aFlipX;
uniform int   u_aFlipY;
uniform float u_aRotRad;
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 (the source textures are GL_CLAMP_TO_EDGE).
    // Rotation can push corners well outside [0, 1]; clamping fills
    // those areas with the nearest edge pixel rather than the
    // letterbox black the other dual-clip families use. Matches
    // Android's EditorImageTransformWithEightReflectFilter intent
    // (no visible black bars during the spin).
    uv = clamp(uv, 0.0, 1.0);
    return yuvToRgb(texture(yT, uv).r,
                    texture(uT, uv).r,
                    texture(vT, uv).r);
}

// Apply rotation around `pivot`, then central scale around (0.5),
// then per-side natural orientation, then N-tap angular blur around
// `blurCenter` with half-spread `blurAngle`.
vec3 sampleSpunBlur(sampler2D yT, sampler2D uT, sampler2D vT,
                    vec2 uv, float scale, float angle,
                    vec2 pivot, vec2 blurCenter, float blurAngle,
                    int orientFlipX, int orientFlipY, float orientRotRad) {
    // 1. Rotate around pivot.
    vec2 d = uv - pivot;
    float ca = cos(angle), sa = sin(angle);
    vec2 dr = vec2(ca * d.x - sa * d.y, sa * d.x + ca * d.y);
    vec2 uvRot = dr + pivot;

    // 2. Scale around canvas center.
    vec2 dc = uvRot - vec2(0.5);
    vec2 uvScaled = dc / max(scale, 0.0001) + vec2(0.5);

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

    if (abs(blurAngle) < 0.0001) {
        return sampleClip(yT, uT, vT, uvScaled);
    }

    // 4. Angular blur around blurCenter.
    const int kTaps = 9;
    vec3 acc = vec3(0.0);
    float total = 0.0;
    vec2 db = uvScaled - blurCenter;
    float r = length(db);
    float baseAngle = atan(db.y, db.x);
    for (int i = 0; i < kTaps; ++i) {
        float t = (float(i) - float(kTaps - 1) * 0.5) / float(kTaps - 1);  // -0.5..+0.5
        float a = baseAngle + t * blurAngle * 2.0;
        vec2 sampUv = blurCenter + vec2(cos(a), sin(a)) * r;
        float w = 1.0 - abs(t * 2.0);
        acc   += w * sampleClip(yT, uT, vT, sampUv);
        total += w;
    }
    return acc / total;
}

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

    vec3 aRgb = sampleSpunBlur(tex_aY, tex_aU, tex_aV, uv,
                               u_aScale, u_aAngleRad,
                               u_rotPivot, u_blurCenter, u_aBlurAngleRad,
                               u_aFlipX, u_aFlipY, u_aRotRad);
    vec3 bRgb = sampleSpunBlur(tex_bY, tex_bU, tex_bV, uv,
                               u_bScale, u_bAngleRad,
                               u_rotPivot, u_blurCenter, u_bBlurAngleRad,
                               u_bFlipX, u_bFlipY, u_bRotRad);

    float cut = smoothstep(0.48, 0.52, p);
    vec3 rgb = mix(aRgb, bRgb, cut);
    FragColor = vec4(rgb, 1.0);
}
