#version 410 core

#ifdef GL_ES
precision mediump float;
#endif

out vec4 FragColor; // Output to screen

uniform vec2 u_resolution;   // <-- screen resolution from CPU
uniform float u_time;        // <-- time from CPU
uniform float u_scale;       // <-- scale from CPU
uniform vec2 u_mouse;       // <-- mouse from CPU

vec3 hsv2rgb(vec3 c) {
    vec3 rgb = clamp(
        abs(mod(c.x*6.0 + vec3(0.0,4.0,2.0),
        6.0) - 3.0) - 1.0,
        0.0,
        1.0
    );
    return c.z * mix(vec3(1.0), rgb, c.y);
}

vec3 getPaletteColor(float idx, float seed) {
    float baseHue = fract(seed);     // 0..1 base hue
    float sat = 0.9;
    float val = 0.9;
    float hue = fract(baseHue + (idx * 0.33));
    return hsv2rgb(vec3(hue, sat, val));
}

vec2 random(vec2 p) {
    return normalize(vec2(
        fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453),
        fract(sin(dot(p, vec2(269.5, 183.3))) * 43758.5453)
    ) * 2.0 - 1.0);
}

float perlinNoise(vec2 st) {
    vec2 i = floor(st);
    vec2 f = fract(st);

    vec2 g00 = random(i + vec2(0.0,0.0));
    vec2 g10 = random(i + vec2(1.0,0.0));
    vec2 g01 = random(i + vec2(0.0,1.0));
    vec2 g11 = random(i + vec2(1.0,1.0));

    float n00 = dot(g00, f - vec2(0.0,0.0));
    float n10 = dot(g10, f - vec2(1.0,0.0));
    float n01 = dot(g01, f - vec2(0.0,1.0));
    float n11 = dot(g11, f - vec2(1.0,1.0));

    vec2 u = f*f*(3.0-2.0*f);
    return mix(mix(n00, n10, u.x), mix(n01, n11, u.x), u.y);
}

mat2 rotate2d(float angle){
    return mat2(cos(angle),-sin(angle),
                sin(angle),cos(angle));
}

vec2 morphingSeed(float t, float interval) {
    float phase = floor(t / interval);
    float blend = fract(t / interval);

    vec2 seedA = vec2(
        sin(phase * 12.9898),
        cos(phase * 78.233)
    );
    vec2 seedB = vec2(
        sin((phase+1.0) * 12.9898),
        cos((phase+1.0) * 78.233)
    );

    blend = smoothstep(0.0, 1.0, blend);
    return mix(seedA, seedB, blend);
}

vec2 morphingDirection(float t, float interval) {
    float phase = floor(t / interval);
    float blend = fract(t / interval);

    // Generate 2 pseudo-random direction vectors
    vec2 dirA = normalize(vec2(
        sin(phase * 12.9898),
        cos(phase * 78.233)
    ));
    vec2 dirB = normalize(vec2(
        sin((phase+1.0) * 93.9898),
        cos((phase+1.0) * 47.233)
    ));

    // Smooth interpolation between them
    blend = smoothstep(0.0, 1.0, blend);
    return normalize(mix(dirA, dirB, blend));
}

void main() {
    vec2 st = gl_FragCoord.xy / u_resolution.xy;            // Normalize coordinates
    st.x *= u_resolution.x / u_resolution.y;                // Correct aspect ratio
    st -= 0.5;                                              // Center coordinates
    st += vec2(100.0,40.0);

    st *= 2.05 * u_scale;                                    // Zoom out
    vec2 seed = morphingSeed(u_time, 10.0); // 10 second interval

    vec2 pos = st + morphingDirection(u_time * 0.2, 15.0); // 15 second interval

    float pre_noise = perlinNoise((pos) * 1.5); // N = [0..1]
    // noise += 0.6 * perlinNoise(pos * 4.0);

    pos += rotate2d(pre_noise) * pos * 0.2;
    pos += u_time * 0.5;

    float noise = perlinNoise(pos + seed * 2.0);
    // noise = smoothstep(0.0, 1.0, noise); // Smooth the noise

    // Normalize noise to [0..1]
    noise = noise + 0.5;

    vec3 color;

    if (noise >= 0.9) {
        color = getPaletteColor(u_time * 0.1, noise);
    } else if (noise >= 0.76 ) {
        color = getPaletteColor((u_time * 0.1) + 1.0, noise);

        // Estimate gradient (2D normal)
        vec2 eps = vec2(0.002, 0.0);
        float n0 = perlinNoise(pos + seed * 2.0);
        float nx = perlinNoise(pos + seed * 2.0 + eps.xy) - n0;
        float ny = perlinNoise(pos + seed * 2.0 + eps.yx) - n0;
        vec2 normal = normalize(vec2(nx, ny));

        // View direction (from pixel to "camera")
        vec2 viewDir = normalize(-st);

        // Light direction (moves over time)
        vec2 lightDir = normalize(vec2(cos(u_time * 0.2), sin(u_time * 0.3)));

        // Fresnel term (stronger highlight at grazing angles)
        float fresnel = pow(1.0 - max(dot(viewDir, normal), 0.0), 3.0);

        // Specular component
        float spec = pow(max(dot(normal, lightDir), 0.0), 32.0);

        // Combine Fresnel + spec
        float gloss = fresnel + 0.5 * spec;

        // Stronger at edges (transition zone of blob)
        float edge = smoothstep(0.8, 0.9, noise) * (1.0 - smoothstep(0.9, 1.0, noise));
        gloss *= edge * 2.0;

        // Add colored glossy highlight (slightly bluish tint)
        color += gloss * vec3(0.8, 0.9, 1.0);
    } else {
        color = vec3(0.0);
    }

    FragColor = vec4(color, 1.0);
}
