let gl, program;
let textureInfo = null;
let isRendering = true;
let lastRenderTime = 0;
const fpsInterval = 1000 / 60;
let startOfDay = new Date();
startOfDay.setHours(0, 0, 0, 0);
let currentTime = Date.now() - startOfDay.getTime();

const vertexShaderSource = `#version 300 es
  in vec4 a_position;
  in vec2 a_texcoord;
  uniform float textureScale;
  out vec2 v_texcoord;

  void main() {
    v_texcoord = (a_texcoord - 0.5) / textureScale + 0.5;
    gl_Position = a_position;
  }
`;

const fragmentShaderSource = `#version 300 es
  precision lowp float;
  
  in vec2 v_texcoord;
  uniform sampler2D texture1;
  uniform float time, hue, noiseamount, speed, canvasTop, canvasHeight, viewportHeight, pixelRatio;
  out vec4 fragColor;
  
  #define NUM_OCTAVES 4
  #define PI 3.14159265359
  
  const vec2 offset = vec2(-0.014, -0.02);
  const float zoom = 0.97, ior = 1.18;
  const mat3 hueShiftMatrix = mat3(
      0.167444, 0.329213, -0.496657,
      -0.327948, 0.035669, 0.292279,
      1.250268, -1.047561, -0.202707
  );

  float rand(vec2 n) {
      return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453);
  }

  float noise(vec2 p) {
      vec2 ip = floor(p);
      vec2 u = fract(p);
      u = u * u * (3.0 - 2.0 * u);
      return mix(
          mix(rand(ip), rand(ip + vec2(1.0, 0.0)), u.x),
          mix(rand(ip + vec2(0.0, 1.0)), rand(ip + vec2(1.0, 1.0)), u.x),
          u.y);
  }

  float fbm(vec2 x) {
      float v = 0.0;
      float a = 0.5;
      vec2 shift = vec2(100);
      mat2 rot = mat2(cos(0.7), sin(0.4), -sin(0.5), cos(0.5));
      for (int i = 0; i < NUM_OCTAVES; ++i) {
          v += a * noise(x);
          x = rot * x * 2.0 + shift;
          a *= 0.5;
      }
      return v;
  }

  vec3 hueshift(vec3 color, float dhue) {
      float s = sin(dhue), c = cos(dhue);
      return (color * c) + (color * s) * hueShiftMatrix + dot(vec3(0.299, 0.587, 0.114), color) * (1.0 - c);
  }

  vec2 computeSurface(float strength, float scale, vec2 uv, float timeOffset) {
      vec2 fbmInput = scale * uv + timeOffset;
      return strength * vec2(
          mix(-0.2, 0.4, fbm(fbmInput)),
          mix(-0.4, 0.4, fbm(fbmInput))
      );
  }

  void main() {
      vec2 uv = (v_texcoord - 0.5) * (zoom / (canvasHeight == 256.0 ? 0.95 : 1.0)) + 0.5 + offset;
      float multiplier = canvasHeight == 256.0 ? 8.1 : 1.0;

      float viewportY = canvasTop + canvasHeight - (gl_FragCoord.y / pixelRatio);
      float normalizedViewportY = viewportY / viewportHeight;

      float distortionAreaSize = 0.15;
      float smoothStep1 = smoothstep(0.0, distortionAreaSize, normalizedViewportY);
      float smoothStep2 = smoothstep(1.0 - distortionAreaSize, 1.0, normalizedViewportY);
      float distortionFactor = -1.0 + smoothStep1 + smoothStep2;

      float waveDistortion = fbm(uv * 6.90 * distortionFactor * multiplier * (0.2 * sin(time) + 1.2)) * distortionFactor * 0.13;
      uv.y += waveDistortion;

      float timeValue = time * speed * 2.0;
      float amplitude = 0.13;
      float baseStrength = 0.25;

      vec2 surface1 = computeSurface(baseStrength + amplitude * sin(time), 2.0, uv, 0.1 * timeValue + 0.01);
      vec2 surface2 = computeSurface(baseStrength + amplitude * sin(time + PI/3.0), 11.0, uv, 0.632 * timeValue + 0.82);
      vec2 surface3 = computeSurface(baseStrength + amplitude * sin(time + 2.0*PI/3.0), 18.0, uv, -0.332 * timeValue + 1.03);

      uv += refract(vec2(0.0), surface1, 1.0 / ior);
      vec4 color1 = texture(texture1, uv);
      uv += refract(vec2(0.0), surface2, 1.0 / ior);
      vec4 color2 = texture(texture1, uv);
      uv += refract(vec2(0.0), surface3, 1.0 / ior);
      vec4 color3 = texture(texture1, uv);

      float x = (uv.x * 40.0) * (uv.y + 5.0) * mix(abs(sin(time) * 0.203), 0.205, 0.4);
      vec4 grain = vec4(mod((mod(x, 13.0) + 11.0) * (mod(x, 71.0) + 1.0), 0.01) - 0.005);

      color3.rgb = hueshift(color3.rgb, -150.0);
      color2.rgb = hueshift(color2.rgb, 150.0);

      vec4 colorout = mix(color1  * color1.a, 
                          mix(color2, color3 + grain * noiseamount * color3.a, 0.40) * color1.a, 0.22) - grain * noiseamount * 1.3;

      if (hue != 0.0) {
          colorout.rgb = hueshift(colorout.rgb, hue);
      }

      fragColor = colorout;
  }
`;

let shaderUniformValues = {
    canvasTop: 0,
    canvasHeight: 0,
    viewportHeight: 0,
    pixelRatio: 1
};

function createShader(gl, type, source) {
    const shader = gl.createShader(type);
    gl.shaderSource(shader, source);
    gl.compileShader(shader);
    
    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        console.error(gl.getShaderInfoLog(shader));
        gl.deleteShader(shader);
        return null;
    }
    return shader;
}

function createProgram(gl, vertexShader, fragmentShader) {
    const program = gl.createProgram();
    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);
    gl.linkProgram(program);
    
    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
        console.error(gl.getProgramInfoLog(program));
        gl.deleteProgram(program);
        return null;
    }
    return program;
}

function setupAttributes() {
    const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
    const positionBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    const positions = new Float32Array([
        -1, -1,
         1, -1,
        -1,  1,
         1,  1,
    ]);
    gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
    gl.enableVertexAttribArray(positionAttributeLocation);
    gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);

    const texCoordAttributeLocation = gl.getAttribLocation(program, 'a_texcoord');
    const texCoordBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
    const texCoords = new Float32Array([
        0, 0,
        1, 0,
        0, 1,
        1, 1,
    ]);
    gl.bufferData(gl.ARRAY_BUFFER, texCoords, gl.STATIC_DRAW);
    gl.enableVertexAttribArray(texCoordAttributeLocation);
    gl.vertexAttribPointer(texCoordAttributeLocation, 2, gl.FLOAT, false, 0, 0);
}

function setupTexture(imageBitmap) {
    const texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, imageBitmap);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    return texture;
}

async function initializeScene(data) {
    const { offscreen, imgWidth, imgHeight, imageBitmap, hue, noiseAmount, speed } = data;
    
    gl = offscreen.getContext('webgl2');
    if (!gl) throw new Error('WebGL2 not supported');

    const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
    const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
    program = createProgram(gl, vertexShader, fragmentShader);

    setupAttributes();
    const texture = setupTexture(imageBitmap);

    // Set up uniforms
    const uniformLocations = {
        texture1: gl.getUniformLocation(program, 'texture1'),
        time: gl.getUniformLocation(program, 'time'),
        hue: gl.getUniformLocation(program, 'hue'),
        noiseamount: gl.getUniformLocation(program, 'noiseamount'),
        speed: gl.getUniformLocation(program, 'speed'),
        textureScale: gl.getUniformLocation(program, 'textureScale'),
        canvasTop: gl.getUniformLocation(program, 'canvasTop'),
        canvasHeight: gl.getUniformLocation(program, 'canvasHeight'),
        viewportHeight: gl.getUniformLocation(program, 'viewportHeight'),
        pixelRatio: gl.getUniformLocation(program, 'pixelRatio')
    };

    gl.useProgram(program);
    gl.uniform1i(uniformLocations.texture1, 0);
    gl.uniform1f(uniformLocations.hue, hue);
    gl.uniform1f(uniformLocations.noiseamount, noiseAmount);
    gl.uniform1f(uniformLocations.speed, speed);
    gl.uniform1f(uniformLocations.textureScale, imgWidth === 512 ? 1.0 : 1.05);

    textureInfo = {
        width: imgWidth,
        height: imgHeight,
        uniformLocations
    };

    render();
    self.postMessage({ action: 'canvasReady' });
}

function updateViewportData(data) {
    shaderUniformValues = { ...shaderUniformValues, ...data };
    if (gl && program && textureInfo) {
        gl.useProgram(program);
        Object.entries(shaderUniformValues).forEach(([key, value]) => {
            const location = textureInfo.uniformLocations[key];
            if (location !== null) {
                gl.uniform1f(location, value);
            }
        });
    }
}

function render() {
    if (!isRendering || !gl || !program || !textureInfo) return;
    
    requestAnimationFrame(render);
    const elapsed = currentTime - lastRenderTime;

    if (elapsed > fpsInterval) {
        lastRenderTime = currentTime - (elapsed % fpsInterval);
        currentTime += fpsInterval;

        gl.viewport(0, 0, textureInfo.width, textureInfo.height);
        gl.clearColor(0, 0, 0, 0);
        gl.clear(gl.COLOR_BUFFER_BIT);

        gl.useProgram(program);
        gl.uniform1f(textureInfo.uniformLocations.time, currentTime / 1000);

        Object.entries(shaderUniformValues).forEach(([key, value]) => {
            const location = textureInfo.uniformLocations[key];
            if (location !== null) {
                gl.uniform1f(location, value);
            }
        });

        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
    }
}

self.onmessage = async function(event) {
    const { action } = event.data;
    
    switch (action) {
        case 'init':
            await initializeScene(event.data);
            break;
        case 'resume':
            isRendering = true;
            if (gl && program) render();
            break;
        case 'stop':
            isRendering = false;
            break;
        case 'updateViewport':
            updateViewportData(event.data);
            break;
    }
};