import React, { useEffect, useRef, useCallback } from 'react';
import { cn } from '@/components/lib/utils';
import * as THREE from 'three';

export interface WoofyHoverImageProps {
  src: string;
  alt?: string;
  width?: number | string;
  height?: number | string;
  className?: string;
  // Changed 'blackwhite' to 'blackWhite' to match common casing and usage
  effectType?: 'inversion' | 'blackWhite' | 'sepia' | 'duotone' | 'pixelate' | 'blur';
  maskRadius?: number;
  turbulenceIntensity?: number;
  animationSpeed?: number;
  appearDuration?: number;
  disappearDuration?: number;
  effectIntensity?: number;
  invertMask?: boolean;
  duotoneColor1?: string;
  duotoneColor2?: string;
  onHover?: () => void;
  onLeave?: () => void;
}

const WoofyHoverImage: React.FC<WoofyHoverImageProps> = ({
  src,
  alt = '',
  width = 'auto',
  height = 400,
  className,
  effectType = 'inversion',
  maskRadius = 0.35,
  turbulenceIntensity = 0.225,
  animationSpeed = 1.0,
  appearDuration = 0.4,
  disappearDuration = 0.3,
  effectIntensity = 0.5,
  invertMask = false,
  duotoneColor1 = '#3366cc',
  duotoneColor2 = '#e63333',
  onHover,
  onLeave,
}) => {
  const containerRef = useRef<HTMLDivElement>(null);
  const sceneRef = useRef<THREE.Scene | null>(null);
  const rendererRef = useRef<THREE.WebGLRenderer | null>(null);
  const uniformsRef = useRef<any>(null);
  const animationIdRef = useRef<number | null>(null);
  const isMouseInsideRef = useRef(false);
  const targetMouseRef = useRef(new THREE.Vector2(0.5, 0.5));
  const lerpedMouseRef = useRef(new THREE.Vector2(0.5, 0.5));

  const vertexShader = `
    varying vec2 v_uv;
    
    void main() {
      v_uv = uv;
      gl_Position = vec4(position, 1.0);
    }
  `;

  const fragmentShader = `
    precision highp float;

    uniform sampler2D u_texture;
    uniform vec2 u_mouse;
    uniform float u_time;
    uniform vec2 u_resolution;
    uniform float u_radius;
    uniform float u_speed;
    uniform float u_imageAspect;
    uniform float u_turbulenceIntensity;
    uniform int u_effectType;
    uniform vec3 u_effectColor1;
    uniform vec3 u_effectColor2;
    uniform float u_effectIntensity;
    uniform bool u_invertMask;

    varying vec2 v_uv;

    vec3 hash33(vec3 p) {
      p = fract(p * vec3(443.8975, 397.2973, 491.1871));
      p += dot(p.zxy, p.yxz + 19.27);
      return fract(vec3(p.x * p.y, p.z * p.x, p.y * p.z));
    }

    float simplex_noise(vec3 p) {
      const float K1 = 0.333333333;
      const float K2 = 0.166666667;
      
      vec3 i = floor(p + (p.x + p.y + p.z) * K1);
      vec3 d0 = p - (i - (i.x + i.y + i.z) * K2);
      
      vec3 e = step(vec3(0.0), d0 - d0.yzx);
      vec3 i1 = e * (1.0 - e.zxy);
      vec3 i2 = 1.0 - e.zxy * (1.0 - e);
      
      vec3 d1 = d0 - (i1 - K2);
      vec3 d2 = d0 - (i2 - K2 * 2.0);
      vec3 d3 = d0 - (1.0 - 3.0 * K2);
      
      vec3 x0 = d0;
      vec3 x1 = d1;
      vec3 x2 = d2;
      vec3 x3 = d3;
      
      vec4 h = max(0.6 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);
      vec4 n = h * h * h * h * vec4(
        dot(x0, hash33(i) * 2.0 - 1.0),
        dot(x1, hash33(i + i1) * 2.0 - 1.0),
        dot(x2, hash33(i + i2) * 2.0 - 1.0),
        dot(x3, hash33(i + 1.0) * 2.0 - 1.0)
      );
      
      return 0.5 + 0.5 * 31.0 * dot(n, vec4(1.0));
    }

    vec2 curl(vec2 p, float time) {
      const float epsilon = 0.001;
      
      float n1 = simplex_noise(vec3(p.x, p.y + epsilon, time));
      float n2 = simplex_noise(vec3(p.x, p.y - epsilon, time));
      float n3 = simplex_noise(vec3(p.x + epsilon, p.y, time));
      float n4 = simplex_noise(vec3(p.x - epsilon, p.y, time));
      
      float x = (n2 - n1) / (2.0 * epsilon);
      float y = (n4 - n3) / (2.0 * epsilon);
      
      return vec2(x, y);
    }

    float inkMarbling(vec2 p, float time, float intensity) {
      float result = 0.0;
      
      vec2 flow = curl(p * 1.5, time * 0.1) * intensity * 2.0;
      vec2 p1 = p + flow * 0.3;
      result += simplex_noise(vec3(p1 * 2.0, time * 0.15)) * 0.5;
      
      vec2 flow2 = curl(p * 3.0 + vec2(sin(time * 0.2), cos(time * 0.15)), time * 0.2) * intensity;
      vec2 p2 = p + flow2 * 0.2;
      result += simplex_noise(vec3(p2 * 4.0, time * 0.25)) * 0.3;
      
      vec2 flow3 = curl(p * 6.0 + vec2(cos(time * 0.3), sin(time * 0.25)), time * 0.3) * intensity * 0.5;
      vec2 p3 = p + flow3 * 0.1;
      result += simplex_noise(vec3(p3 * 8.0, time * 0.4)) * 0.2;
      
      float dist = length(p - vec2(0.5));
      float angle = atan(p.y - 0.5, p.x - 0.5);
      float spiral = sin(dist * 15.0 - angle * 2.0 + time * 0.3) * 0.5 + 0.5;
      
      result = mix(result, spiral, 0.3);
      result = result * 0.5 + 0.5;
      
      return result;
    }

    vec3 applySepia(vec3 color) {
      float r = color.r * 0.393 + color.g * 0.769 + color.b * 0.189;
      float g = color.r * 0.349 + color.g * 0.686 + color.b * 0.168;
      float b = color.r * 0.272 + color.g * 0.534 + color.b * 0.131;
      return vec3(r, g, b);
    }

    vec3 applyDuotone(vec3 color, vec3 color1, vec3 color2) {
      float gray = dot(color, vec3(0.299, 0.587, 0.114));
      return mix(color1, color2, gray);
    }

    vec3 applyPixelate(sampler2D tex, vec2 uv, float pixelSize) {
      float dx = pixelSize * (1.0 / u_resolution.x);
      float dy = pixelSize * (1.0 / u_resolution.y);
      vec2 pixelatedUV = vec2(dx * floor(uv.x / dx), dy * floor(uv.y / dy));
      return texture2D(tex, pixelatedUV).rgb;
    }

    vec3 applyBlur(sampler2D tex, vec2 uv, float blurAmount) {
      float dx = blurAmount * (1.0 / u_resolution.x);
      float dy = blurAmount * (1.0 / u_resolution.y);
      
      vec3 sum = vec3(0.0);
      sum += texture2D(tex, uv + vec2(-dx, -dy)).rgb * 0.0625;
      sum += texture2D(tex, uv + vec2(0.0, -dy)).rgb * 0.125;
      sum += texture2D(tex, uv + vec2(dx, -dy)).rgb * 0.0625;
      sum += texture2D(tex, uv + vec2(-dx, 0.0)).rgb * 0.125;
      sum += texture2D(tex, uv).rgb * 0.25;
      sum += texture2D(tex, uv + vec2(dx, 0.0)).rgb * 0.125;
      sum += texture2D(tex, uv + vec2(-dx, dy)).rgb * 0.0625;
      sum += texture2D(tex, uv + vec2(0.0, dy)).rgb * 0.125;
      sum += texture2D(tex, uv + vec2(dx, dy)).rgb * 0.0625;
      
      return sum;
    }

    void main() {
      vec2 uv = v_uv;
      float screenAspect = u_resolution.x / u_resolution.y;
      float ratio = u_imageAspect / screenAspect;

      vec2 texCoord = vec2(
        mix(0.5 - 0.5 / ratio, 0.5 + 0.5 / ratio, uv.x),
        uv.y
      );

      vec4 tex = texture2D(u_texture, texCoord);
      vec3 originalColor = tex.rgb;
      vec3 effectColor = originalColor;
      
      if (u_effectType == 1) {
        float gray = dot(originalColor, vec3(0.299, 0.587, 0.114));
        effectColor = vec3(gray);
      } 
      else if (u_effectType == 2) {
        effectColor = applySepia(originalColor);
      }
      else if (u_effectType == 3) {
        effectColor = applyDuotone(originalColor, u_effectColor1, u_effectColor2);
      }
      else if (u_effectType == 4) {
        effectColor = applyPixelate(u_texture, texCoord, u_effectIntensity * 20.0);
      }
      else if (u_effectType == 5) {
        effectColor = applyBlur(u_texture, texCoord, u_effectIntensity * 5.0);
      }
      
      vec2 correctedUV = uv;
      correctedUV.x *= screenAspect;
      vec2 correctedMouse = u_mouse;
      correctedMouse.x *= screenAspect;

      float dist = distance(correctedUV, correctedMouse);
      
      float marbleEffect = inkMarbling(uv * 2.0 + u_time * u_speed * 0.1, u_time, u_turbulenceIntensity * 2.0);
      float jaggedDist = dist + (marbleEffect - 0.5) * u_turbulenceIntensity * 2.0;
      
      float mask = u_radius > 0.001 ? step(jaggedDist, u_radius) : 0.0;

      vec3 invertedColor = vec3(0.0);
      if (u_effectType == 0) {
        float gray = dot(originalColor, vec3(0.299, 0.587, 0.114));
        invertedColor = vec3(1.0 - gray);
      } else {
        invertedColor = originalColor;
      }

      vec3 finalColor;
      if (u_invertMask) {
        finalColor = mix(invertedColor, effectColor, mask);
      } else {
        finalColor = mix(effectColor, invertedColor, mask);
      }
      
      gl_FragColor = vec4(finalColor, 1.0);
    }
  `;

  const getEffectTypeValue = (type: string): number => {
    switch (type) {
      case 'blackwhite': return 1;
      case 'sepia': return 2;
      case 'duotone': return 3;
      case 'pixelate': return 4;
      case 'blur': return 5;
      default: return 0; // inversion
    }
  };

  const hexToRgb = (hex: string): THREE.Color => {
    return new THREE.Color(hex);
  };

  const initializeEffect = useCallback(() => {
    if (!containerRef.current) return;

    const container = containerRef.current;
    const loader = new THREE.TextureLoader();

    loader.load(src, (texture) => {
      const imageAspect = texture.image.width / texture.image.height;
      
      texture.minFilter = THREE.LinearFilter;
      texture.magFilter = THREE.LinearFilter;
      texture.anisotropy = 8;
      texture.generateMipmaps = false;

      const scene = new THREE.Scene();
      sceneRef.current = scene;

      const containerWidth = container.clientWidth;
      const containerHeight = container.clientHeight;

      const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);

      const uniforms = {
        u_texture: { value: texture },
        u_mouse: { value: new THREE.Vector2(0.5, 0.5) },
        u_time: { value: 0.0 },
        u_resolution: { value: new THREE.Vector2(containerWidth, containerHeight) },
        u_radius: { value: 0.0 },
        u_speed: { value: 0.75 },
        u_imageAspect: { value: imageAspect },
        u_turbulenceIntensity: { value: turbulenceIntensity },
        u_effectType: { value: getEffectTypeValue(effectType) },
        u_effectIntensity: { value: effectIntensity },
        u_invertMask: { value: invertMask },
        u_effectColor1: { value: hexToRgb(duotoneColor1) },
        u_effectColor2: { value: hexToRgb(duotoneColor2) },
      };

      uniformsRef.current = uniforms;

      const geometry = new THREE.PlaneGeometry(2, 2);
      const material = new THREE.ShaderMaterial({
        uniforms,
        vertexShader,
        fragmentShader,
        depthTest: false,
        depthWrite: false,
      });

      const mesh = new THREE.Mesh(geometry, material);
      scene.add(mesh);

      const renderer = new THREE.WebGLRenderer({
        antialias: false,
        powerPreference: "high-performance",
        alpha: true,
      });

      renderer.setPixelRatio(1);
      renderer.setSize(containerWidth, containerHeight);
      rendererRef.current = renderer;

      // Clear any existing canvas
      const existingCanvas = container.querySelector('canvas');
      if (existingCanvas) {
        existingCanvas.remove();
      }

      container.appendChild(renderer.domElement);
      renderer.domElement.style.position = 'absolute';
      renderer.domElement.style.top = '0';
      renderer.domElement.style.left = '0';
      renderer.domElement.style.width = '100%';
      renderer.domElement.style.height = '100%';
      renderer.domElement.style.zIndex = '1';

      // Animation loop
      const animate = () => {
        if (!uniformsRef.current || !rendererRef.current || !sceneRef.current) return;

        lerpedMouseRef.current.lerp(targetMouseRef.current, 0.1);
        uniformsRef.current.u_mouse.value.copy(lerpedMouseRef.current);

        if (isMouseInsideRef.current) {
          uniformsRef.current.u_time.value += 0.01 * animationSpeed;
        }

        rendererRef.current.render(sceneRef.current, camera);
        animationIdRef.current = requestAnimationFrame(animate);
      };

      animate();
    });
  }, [src, effectType, maskRadius, turbulenceIntensity, animationSpeed, effectIntensity, invertMask, duotoneColor1, duotoneColor2]);

  const handleMouseMove = useCallback((e: MouseEvent) => {
    if (!containerRef.current || !uniformsRef.current) return;

    const rect = containerRef.current.getBoundingClientRect();
    const inside = e.clientX >= rect.left && e.clientX <= rect.right && 
                  e.clientY >= rect.top && e.clientY <= rect.bottom;

    if (inside) {
      targetMouseRef.current.x = (e.clientX - rect.left) / rect.width;
      targetMouseRef.current.y = 1.0 - (e.clientY - rect.top) / rect.height;

      if (!isMouseInsideRef.current) {
        isMouseInsideRef.current = true;
        onHover?.();
        
        // Animate radius to target value
        const startRadius = uniformsRef.current.u_radius.value;
        const targetRadius = maskRadius;
        const startTime = Date.now();
        
        const animateRadius = () => {
          const elapsed = (Date.now() - startTime) / 1000;
          const progress = Math.min(elapsed / appearDuration, 1);
          const easeProgress = 1 - Math.pow(1 - progress, 3); // ease-out cubic
          
          uniformsRef.current.u_radius.value = startRadius + (targetRadius - startRadius) * easeProgress;
          
          if (progress < 1) {
            requestAnimationFrame(animateRadius);
          }
        };
        
        animateRadius();
      }
    } else if (isMouseInsideRef.current) {
      isMouseInsideRef.current = false;
      onLeave?.();
      
      // Animate radius to zero
      const startRadius = uniformsRef.current.u_radius.value;
      const startTime = Date.now();
      
      const animateRadius = () => {
        const elapsed = (Date.now() - startTime) / 1000;
        const progress = Math.min(elapsed / disappearDuration, 1);
        const easeProgress = Math.pow(progress, 3); // ease-in cubic
        
        uniformsRef.current.u_radius.value = startRadius * (1 - easeProgress);
        
        if (progress < 1) {
          requestAnimationFrame(animateRadius);
        }
      };
      
      animateRadius();
    }
  }, [maskRadius, appearDuration, disappearDuration, onHover, onLeave]);

  useEffect(() => {
    initializeEffect();

    document.addEventListener('mousemove', handleMouseMove, { passive: true });

    return () => {
      document.removeEventListener('mousemove', handleMouseMove);
      
      if (animationIdRef.current) {
        cancelAnimationFrame(animationIdRef.current);
      }
      
      if (rendererRef.current) {
        rendererRef.current.dispose();
      }
    };
  }, [initializeEffect, handleMouseMove]);

  return (
    <div
      ref={containerRef}
      className={cn(`relative overflow-hidden flex 
        items-center justify-center`, className)}
      style={{ width, height }}
    >
      <img
        src={src}
        alt={alt}
        className="w-full h-full object-cover"
        style={{ position: 'relative', zIndex: 0 }}
      />
    </div>
  );
};

export default WoofyHoverImage;
