#version 330 core
// Phase 3b — directional wipe between clip A and clip B.
//
// Six TransitionType entries share this shader (Wipe_Left, Wipe_Right,
// Wipe_Up, Wipe_Down, Vertical, Horizontal). The shape of the wipe is
// fully controlled by a single screen-space direction vector that the
// CPU sets in `u_direction`:
//
//   (+1, 0) — Wipe_Right / Horizontal: edge moves from left to right.
//   (-1, 0) — Wipe_Left:               edge moves from right to left.
//   ( 0,+1) — Wipe_Up / Vertical:      edge moves from screen-bottom
//                                       to screen-top (visually).
//   ( 0,-1) — Wipe_Down:               edge moves from screen-top to
//                                       screen-bottom.
//
// `u_progress` is normalised to [0, 1] across the full transition
// window (Phase 2 `sampleTransitionCurve` contract). `u_softness`
// gives the edge a small smoothstep band so the wipe doesn't alias
// on near-axis-aligned content; 0 reproduces the canonical hard-edge
// `step(progress, t)` wipe (gl-transitions `directional.fs` style).
//
// Per-clip Orientation (mirror / flip / rotation) is applied to each
// texture's UV sample independently so a wipe between a flipped A
// and an upright B doesn't double-flip one side.

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 vec2  u_direction;
uniform float u_progress;
uniform float u_softness;
// 0 = standard one-edge wipe (Wipe_*).
// 1 = vertical curtain (Vertical Reveal): black bars close from top
//     and bottom over A in phase 1, then retract from B in phase 2.
// 2 = horizontal curtain (Horizontal Reveal): same but bars come from
//     the left and right edges.
uniform int   u_curtainMode;

uniform int   u_aFlipX;
uniform int   u_aFlipY;
uniform float u_aRotRad;
uniform int   u_bFlipX;
uniform int   u_bFlipY;
uniform float u_bRotRad;

// Apply the per-clip Orientation (mirror / flip + rotation around the
// UV centre) to a layer-local UV. Mirrors the math in `yuv420p.fs`'s
// `applyOrient` minus the `u_unrotSize` AABB-expansion branch -- the
// dual-clip path never expands to an AABB because the wipe operates
// on the canvas-base viewport, not on a per-clip rotated dst rect.
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;
}

// BT.709 limited-range YUV to RGB. Identical coefficients to
// `yuv420p.fs` so the wipe boundary doesn't shift colours relative to
// the single-clip path on either side of the transition.
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) {
    // Rotation / flip can sample outside [0,1]; clamp those fragments
    // to opaque black (matches `yuv420p.fs`'s `discard`-then-blend
    // behaviour as seen by the dual-clip mix below -- the clamped
    // black side fades in proportional to the wipe factor).
    if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) {
        return vec3(0.0);
    }
    return yuvToRgb(texture(yT, uv).r,
                    texture(uT, uv).r,
                    texture(vT, uv).r);
}

void main() {
    vec2 uv = TexCoord;

    // Screen-space `u_direction` (Y-up) -> UV-space direction (Y-down).
    // Flipping the Y component lets the caller author directions in
    // the natural screen mental model while the shader works in the
    // project's texture-coordinate convention.
    vec2 uvDir = vec2(u_direction.x, -u_direction.y);

    // Project uv onto the wipe axis. `t == 0` is the edge of the
    // viewport where B starts to grow from; `t == 1` is the opposite
    // edge that holds A the longest. The two `max` terms cover the
    // four cardinal directions without branching:
    //   uvDir = (+1, 0): t = uv.x
    //   uvDir = (-1, 0): t = 1 - uv.x
    //   uvDir = ( 0,+1): t = uv.y
    //   uvDir = ( 0,-1): t = 1 - uv.y
    float t = dot(uv,        max( uvDir, vec2(0.0)))
            + dot(1.0 - uv,  max(-uvDir, vec2(0.0)));

    // `factor = 0` means pure A, `factor = 1` means pure B. The wipe
    // edge satisfies `t == progress`; everything with smaller `t`
    // (already swept) lands on B, everything larger on A. The softness
    // band smooths the boundary across `[progress - s, progress + s]`.
    float factor = smoothstep(t - u_softness, t + u_softness, u_progress);

    vec2 uvA = applyOrient(uv, u_aFlipX, u_aFlipY, u_aRotRad);
    vec2 uvB = applyOrient(uv, u_bFlipX, u_bFlipY, u_bRotRad);

    vec3 aRgb = sampleClip(tex_aY, tex_aU, tex_aV, uvA);
    vec3 bRgb = sampleClip(tex_bY, tex_bU, tex_bV, uvB);
    vec3 rgb  = mix(aRgb, bRgb, factor);

    // Vertical / Horizontal Reveal -- bidirectional curtain. Black
    // bars close in from both edges over A in phase 1 (0..0.5) and
    // retract from B in phase 2 (0.5..1). The bar depth peaks at
    // 0.5 (full cover) at the midpoint, so the user sees an
    // eye-blink-like cover-and-uncover with a clean A->B swap
    // behind the curtain.
    if (u_curtainMode == 1 || u_curtainMode == 2) {
        // Bar depth: triangle 0 -> 0.5 -> 0 across the window.
        float depth = 0.5 - abs(u_progress - 0.5);
        // Soft band to avoid an aliased bar edge.
        float band  = 0.02;
        // Coordinate along the curtain axis: vertical curtain reads
        // from uv.y (top/bottom bars); horizontal from uv.x.
        float axis = (u_curtainMode == 1) ? uv.y : uv.x;
        float distToEdge = min(axis, 1.0 - axis);
        // inBar = 1 inside the bar, 0 outside. smoothstep range is
        // [depth, depth + 2*band] (not [depth-band, depth+band]) so
        // the peak (depth=0.5) fully covers the centre pixel
        // (distToEdge=0.5) instead of leaving a faint half-mix seam.
        // Mirror of transition_wipe_plate.fs.
        float inBar = 1.0 - smoothstep(depth, depth + 2.0 * band, distToEdge);
        // Behind the curtain: A in phase 1, B in phase 2.
        vec3 behind = (u_progress < 0.5) ? aRgb : bRgb;
        rgb = mix(behind, vec3(0.0), inBar);
    }

    FragColor = vec4(rgb, 1.0);
}
