// Glitch family: horizontal block displacement, chroma tearing and scanlines,
// built on the feedback buffer so the corruption smears across frames.
//
// Everything here is quantised to a beat- or bar-locked step rather than driven
// continuously. Free-running glitch reads as a broken renderer; glitch that
// lands on the grid reads as an effect.

export const scanTear = {
    name: 'Scan Tear',
    family: 'glitch',
    kind: 'fragment',
    // Personality: see look/Personality.js.
    traits: ['style'],

    params: {
        slices:   { type: 'float', range: [4, 48],   default: 16,  uniform: 'u_slices', bias: 'density' },
        shift:    { type: 'float', range: [0, 0.4],  default: 0.12, uniform: 'u_shift', bias: 'energy' },
        tear:     { type: 'float', range: [0, 1],    default: 0.4, uniform: 'u_tear' },
        chroma:   { type: 'float', range: [0, 0.08], default: 0.02, uniform: 'u_chromaSplit' },
        scan:     { type: 'float', range: [0, 1],    default: 0.35, uniform: 'u_scan' },
        persist:  { type: 'float', range: [0, 0.9],  default: 0.45, uniform: 'u_persist' },
        speed:    { type: 'float', range: [0.05, 0.8], default: 0.25, uniform: 'u_speed', bias: 'motion', rate: true },
        palette:  { type: 'palette', count: 5 },
    },

    reactive: {
        shift: { feature: 'beat',      amount: 0.5,  response: 'spike' },
        tear:  { feature: 'flux',      amount: 0.35, response: 'spike' },
        slices:{ feature: 'bandHigh',  amount: 0.25 },
    },

    shader: `
vec4 scene(vec2 uv, vec2 p) {
    float t = u_time * u_speed + u_seed;

    // Quantise to the bar so displacement steps in time with the music rather
    // than crawling. floor() of the bar phase gives a stable step per bar.
    float step_ = floor(u_barPhase * 8.0) + floor(t * 4.0) * 8.0;

    float row = floor(uv.y * u_slices);
    float rowRandom = hash12(vec2(row, step_));

    // Only some rows tear, and only above the tear threshold.
    // Slice weight follows the track's line weight — the same art direction
    // that thickens a grid line thickens a tear.
    float torn = step(1.0 - u_tear * (0.6 + u_sigLine), rowRandom);
    float offset = (rowRandom - 0.5) * 2.0 * u_shift * torn;

    // The rows tear in SCREEN space — that is what a tear is — but the image
    // being torn is filmed at the shot's framing. See framedUv.
    vec2 fuv = framedUv(p);
    vec2 q = vec2(fract(fuv.x + offset), fuv.y);

    // Base image: a banded field, so the scene stands alone rather than needing
    // something underneath it.
    float band = fbm(vec2(q.x * 3.0, q.y * 6.0 + t), 4);
    float ramp = fract(band * 2.0 + q.y * 2.0 - t * 0.5);
    vec3 col = palRamp(ramp * 0.7 + row * 0.02);
    col *= 0.4 + 0.6 * smoothstep(0.1, 0.9, band);

    // Chroma split, strongest on torn rows.
    float split = u_chromaSplit * (0.35 + torn);
    col.r = mix(col.r, palRamp(ramp + split).r, 0.6);
    col.b = mix(col.b, palRamp(ramp - split).b, 0.6);

    // Scanlines, in normalised space so they survive a resolution change.
    float lines = 0.5 + 0.5 * sin(uv.y * 900.0 * u_pixelScale);
    col *= 1.0 - u_scan * 0.45 * lines;

    // Smear the previous frame along the displacement.
    vec3 ghost = prev(vec2(fract(uv.x + offset * 0.6), uv.y));
    col = max(col, ghost * u_persist);

    col += pal(4) * torn * u_shift * 0.6;
    col += sigGrain(uv);
    return vec4(col, 1.0);
}
`,
};

export default scanTear;
