#version 330 core

// Beat-synced VHS tracking glitch: a small horizontal band (or a few) of the frame is displaced
// sideways on every beat and settles before the next. Which sub-effects are used is parametrized from
// the demo JSON via the node's "uniforms" — set an amount to 0 to disable that part:
//   tearAmount    hard horizontal band shift (the classic VHS tracking "tear")
//   wobbleAmount  sine horizontal ripple inside the band ("warp/melt" instead of a clean shift)
//   rgbSplit      chromatic red/blue split inside the glitched band
//   noiseAmount   scanline noise + flicker inside the glitched band
//   bandHeight    band half-height as a fraction of the screen (e.g. 0.045 = a thin band)
//   bandCount     number of glitch bands, 0..8
//   strength      master intensity multiplier
// The beat built-ins (uBeatPulse / uKick / uBar / uBeatInBar) are supplied automatically by
// PostProcessGraph from the shared BeatClock, so the whole thing is deterministic under --record.

in vec2 ex_Uv;
out vec4 fragColor;

uniform sampler2D iChannel0;
uniform vec2 iResolution;
uniform float iTime;

uniform float uBeatPulse; // 1.0 at a beat onset -> ~0 before the next beat
uniform float uKick;      // low-band energy [0,1]
uniform int uBar;         // bar index
uniform int uBeatInBar;   // beat within the bar (0 == downbeat)

uniform float tearAmount;
uniform float wobbleAmount;
uniform float rgbSplit;
uniform float noiseAmount;
uniform float bandHeight;
uniform float bandCount;
uniform float strength;

float hash11(float p) {
	p = fract(p * 0.1031);
	p *= p + 33.33;
	p *= p + p;
	return fract(p);
}

float hash21(vec2 p) {
	vec3 p3 = fract(vec3(p.xyx) * 0.1031);
	p3 += dot(p3, p3.yzx + 33.33);
	return fract((p3.x + p3.y) * p3.z);
}

void main() {
	vec2 uv = ex_Uv;

	// Burst on each beat, decaying before the next (clean between beats). beatSeed changes every beat,
	// so the bands jump position. This node runs before the bloom (see osmium.json), so the tear is
	// baked into the high-contrast content and stays visible once the content blooms.
	float g = clamp(uBeatPulse * strength, 0.0, 1.0);
	float beatSeed = float(uBar * 4 + uBeatInBar);
	float bh = max(bandHeight, 0.001);
	int count = int(clamp(bandCount, 0.0, 8.0));

	float shift = 0.0; // horizontal UV displacement
	float mask = 0.0;  // "inside a glitch band" amount, for the rgb split / noise
	for (int i = 0; i < 8; ++i) {
		if (i >= count) {
			break;
		}
		float seed = beatSeed + float(i) * 13.71;
		float bandY = hash11(seed);
		float inBand = smoothstep(bh, 0.0, abs(uv.y - bandY));
		float tear = (hash11(seed + 7.0) - 0.5) * 2.0 * tearAmount;
		float wob = sin(uv.y * 120.0 + iTime * 50.0 + seed * 6.2831) * wobbleAmount;
		shift += inBand * (tear + wob);
		mask = max(mask, inBand);
	}
	shift *= g;
	mask *= g;

	uv.x += shift;

	// Sample, with an optional chromatic split concentrated in the glitched band.
	vec3 col;
	float split = rgbSplit * mask;
	if (split > 0.0) {
		col.r = texture(iChannel0, uv + vec2(split, 0.0)).r;
		col.g = texture(iChannel0, uv).g;
		col.b = texture(iChannel0, uv - vec2(split, 0.0)).b;
	} else {
		col = texture(iChannel0, uv).rgb;
	}

	// Scanline noise + flicker, concentrated in the glitched band.
	if (noiseAmount > 0.0) {
		float n = hash21(vec2(floor(uv.y * iResolution.y), floor(iTime * 55.0)));
		float scan = 0.5 + 0.5 * sin(uv.y * iResolution.y * 3.14159);
		col += (n - 0.5) * noiseAmount * mask;
		col *= mix(1.0, scan, noiseAmount * mask * 0.6);
	}

	fragColor = vec4(clamp(col, 0.0, 1.0), 1.0);
}
