<script setup lang="ts">
import { onMounted, onUnmounted, watch, useTemplateRef } from 'vue';
import { Renderer, Program, Mesh, Triangle } from 'ogl';

interface LiquidChromeProps {
  baseColor?: number[];
  speed?: number;
  amplitude?: number;
  frequencyX?: number;
  frequencyY?: number;
  interactive?: boolean;
}

const props = withDefaults(defineProps<LiquidChromeProps>(), {
  baseColor: () => [0.1, 0.1, 0.1],
  speed: 0.2,
  amplitude: 0.5,
  frequencyX: 3,
  frequencyY: 2,
  interactive: true
});

const containerRef = useTemplateRef<HTMLDivElement>('containerRef');

let cleanupAnimation: (() => void) | null = null;

const setupAnimation = () => {
  const container = containerRef.value;
  if (!container) return;

  const renderer = new Renderer({ antialias: true });
  const gl = renderer.gl;
  gl.clearColor(1, 1, 1, 1);

  const vertexShader = `
      attribute vec2 position;
      attribute vec2 uv;
      varying vec2 vUv;
      void main() {
        vUv = uv;
        gl_Position = vec4(position, 0.0, 1.0);
      }
    `;

  const fragmentShader = `
      precision highp float;
      uniform float uTime;
      uniform vec3 uResolution;
      uniform vec3 uBaseColor;
      uniform float uAmplitude;
      uniform float uFrequencyX;
      uniform float uFrequencyY;
      uniform vec2 uMouse;
      varying vec2 vUv;

      vec4 renderImage(vec2 uvCoord) {
          vec2 fragCoord = uvCoord * uResolution.xy;
          vec2 uv = (2.0 * fragCoord - uResolution.xy) / min(uResolution.x, uResolution.y);

          for (float i = 1.0; i < 10.0; i++){
              uv.x += uAmplitude / i * cos(i * uFrequencyX * uv.y + uTime + uMouse.x * 3.14159);
              uv.y += uAmplitude / i * cos(i * uFrequencyY * uv.x + uTime + uMouse.y * 3.14159);
          }

          vec2 diff = (uvCoord - uMouse);
          float dist = length(diff);
          float falloff = exp(-dist * 20.0);
          float ripple = sin(10.0 * dist - uTime * 2.0) * 0.03;
          uv += (diff / (dist + 0.0001)) * ripple * falloff;

          vec3 color = uBaseColor / abs(sin(uTime - uv.y - uv.x));
          return vec4(color, 1.0);
      }

      void main() {
          vec4 col = vec4(0.0);
          int samples = 0;
          for (int i = -1; i <= 1; i++){
              for (int j = -1; j <= 1; j++){
                  vec2 offset = vec2(float(i), float(j)) * (1.0 / min(uResolution.x, uResolution.y));
                  col += renderImage(vUv + offset);
                  samples++;
              }
          }
          gl_FragColor = col / float(samples);
      }
    `;

  const geometry = new Triangle(gl);
  const program = new Program(gl, {
    vertex: vertexShader,
    fragment: fragmentShader,
    uniforms: {
      uTime: { value: 0 },
      uResolution: {
        value: new Float32Array([gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height])
      },
      uBaseColor: { value: new Float32Array(props.baseColor) },
      uAmplitude: { value: props.amplitude },
      uFrequencyX: { value: props.frequencyX },
      uFrequencyY: { value: props.frequencyY },
      uMouse: { value: new Float32Array([0, 0]) }
    }
  });
  const mesh = new Mesh(gl, { geometry, program });

  function resize() {
    const scale = 1;
    if (!container) return;

    renderer.setSize(container.offsetWidth * scale, container.offsetHeight * scale);
    const resUniform = program.uniforms.uResolution.value as Float32Array;
    resUniform[0] = gl.canvas.width;
    resUniform[1] = gl.canvas.height;
    resUniform[2] = gl.canvas.width / gl.canvas.height;
  }
  window.addEventListener('resize', resize);
  resize();

  function handleMouseMove(event: MouseEvent) {
    if (!container) return;

    const rect = container.getBoundingClientRect();
    const x = (event.clientX - rect.left) / rect.width;
    const y = 1 - (event.clientY - rect.top) / rect.height;
    const mouseUniform = program.uniforms.uMouse.value as Float32Array;
    mouseUniform[0] = x;
    mouseUniform[1] = y;
  }

  function handleTouchMove(event: TouchEvent) {
    if (event.touches.length > 0) {
      const touch = event.touches[0];
      if (!container) return;

      const rect = container.getBoundingClientRect();
      const x = (touch.clientX - rect.left) / rect.width;
      const y = 1 - (touch.clientY - rect.top) / rect.height;
      const mouseUniform = program.uniforms.uMouse.value as Float32Array;
      mouseUniform[0] = x;
      mouseUniform[1] = y;
    }
  }

  if (props.interactive) {
    container.addEventListener('mousemove', handleMouseMove);
    container.addEventListener('touchmove', handleTouchMove);
  }

  let animationId: number;
  function update(t: number) {
    animationId = requestAnimationFrame(update);
    program.uniforms.uTime.value = t * 0.001 * props.speed;
    renderer.render({ scene: mesh });
  }
  animationId = requestAnimationFrame(update);

  container.appendChild(gl.canvas);

  cleanupAnimation = () => {
    cancelAnimationFrame(animationId);
    window.removeEventListener('resize', resize);
    if (props.interactive) {
      container.removeEventListener('mousemove', handleMouseMove);
      container.removeEventListener('touchmove', handleTouchMove);
    }
    if (gl.canvas.parentElement) {
      gl.canvas.parentElement.removeChild(gl.canvas);
    }
    gl.getExtension('WEBGL_lose_context')?.loseContext();
  };
};

onMounted(() => {
  setupAnimation();
});

onUnmounted(() => {
  if (cleanupAnimation) {
    cleanupAnimation();
    cleanupAnimation = null;
  }
});

watch(
  () => props,
  () => {
    if (cleanupAnimation) {
      cleanupAnimation();
      setupAnimation();
    }
  },
  { deep: true }
);
</script>

<template>
  <div ref="containerRef" class="w-full h-full" v-bind="$attrs" />
</template>
