const BLEND_MODES = Object.freeze({
  normal: 0,
  multiply: 1,
  screen: 2,
  overlay: 3,
  add: 4,
  darken: 5,
  lighten: 6,
  difference: 7,
});

export function blendModeIndex(mode) {
  return BLEND_MODES[mode] ?? 0;
}

export function blendRgbSample(base, source, mode = 'normal', opacity = 1, sourceAlpha = 1, destAlpha = 1) {
  const effectiveAlpha = sourceAlpha * opacity;
  const blend = source.map((channel, index) => {
    const destination = base[index];
    if (mode === 'multiply') return destination * channel;
    if (mode === 'screen') return 1 - (1 - destination) * (1 - channel);
    if (mode === 'overlay') {
      return destination < 0.5
        ? 2 * destination * channel
        : 1 - 2 * (1 - destination) * (1 - channel);
    }
    if (mode === 'add') return Math.min(destination + channel, 1);
    if (mode === 'darken') return Math.min(destination, channel);
    if (mode === 'lighten') return Math.max(destination, channel);
    if (mode === 'difference') return Math.abs(destination - channel);
    return channel;
  });
  return {
    rgb: base.map((channel, index) => channel + (blend[index] - channel) * effectiveAlpha),
    alpha: mode === 'normal'
      ? destAlpha + effectiveAlpha * (1 - destAlpha)
      : Math.max(destAlpha, effectiveAlpha),
  };
}

export function createSceneCompositor(THREE) {
  // Create orthographic camera and scene for compositing scene textures
  const sceneCompositeCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
  const sceneCompositeScene = new THREE.Scene();
  
  // Material for blitting a scene texture with blend mode support
  const sceneCompositeMaterial = new THREE.ShaderMaterial({
    uniforms: {
      uTexture: { value: null },
      uOpacity: { value: 1.0 },
      uBlendMode: { value: 0 }, // 0=normal, 1=multiply, 2=screen, 3=overlay, 4=add, 5=darken, 6=lighten, 7=difference
      uDestTexture: { value: null }, // For blend modes that need destination
    },
    vertexShader: `
      varying vec2 vUv;
      void main() {
        vUv = uv;
        gl_Position = vec4(position, 1.0);
      }
    `,
    fragmentShader: `
      uniform sampler2D uTexture;
      uniform sampler2D uDestTexture;
      uniform float uOpacity;
      uniform int uBlendMode;
      varying vec2 vUv;
      
      // Blend mode functions
      vec3 blendMultiply(vec3 base, vec3 blend) {
        return base * blend;
      }
      
      vec3 blendScreen(vec3 base, vec3 blend) {
        return 1.0 - (1.0 - base) * (1.0 - blend);
      }
      
      vec3 blendOverlay(vec3 base, vec3 blend) {
        return mix(
          2.0 * base * blend,
          1.0 - 2.0 * (1.0 - base) * (1.0 - blend),
          step(0.5, base)
        );
      }
      
      vec3 blendAdd(vec3 base, vec3 blend) {
        return min(base + blend, vec3(1.0));
      }
      
      vec3 blendDarken(vec3 base, vec3 blend) {
        return min(base, blend);
      }
      
      vec3 blendLighten(vec3 base, vec3 blend) {
        return max(base, blend);
      }
      
      vec3 blendDifference(vec3 base, vec3 blend) {
        return abs(base - blend);
      }
      
      void main() {
        vec4 srcColor = texture2D(uTexture, vUv);
        vec4 dstColor = texture2D(uDestTexture, vUv);
        
        // Effective alpha combines source alpha with opacity
        // For scenes with backgrounds removed, srcColor.a will be 0 in empty areas
        float effectiveAlpha = srcColor.a * uOpacity;
        
        // For normal blend mode
        if (uBlendMode == 0) {
          // Simple alpha blend: mix dst and src based on effective alpha
          vec3 blended = mix(dstColor.rgb, srcColor.rgb, effectiveAlpha);
          float outAlpha = dstColor.a + effectiveAlpha * (1.0 - dstColor.a);
          gl_FragColor = vec4(blended, outAlpha);
          return;
        }
        
        // For other blend modes, apply the blend function
        vec3 blendResult;
        
        if (uBlendMode == 1) {
          blendResult = blendMultiply(dstColor.rgb, srcColor.rgb);
        } else if (uBlendMode == 2) {
          blendResult = blendScreen(dstColor.rgb, srcColor.rgb);
        } else if (uBlendMode == 3) {
          blendResult = blendOverlay(dstColor.rgb, srcColor.rgb);
        } else if (uBlendMode == 4) {
          blendResult = blendAdd(dstColor.rgb, srcColor.rgb);
        } else if (uBlendMode == 5) {
          blendResult = blendDarken(dstColor.rgb, srcColor.rgb);
        } else if (uBlendMode == 6) {
          blendResult = blendLighten(dstColor.rgb, srcColor.rgb);
        } else if (uBlendMode == 7) {
          blendResult = blendDifference(dstColor.rgb, srcColor.rgb);
        } else {
          blendResult = srcColor.rgb;
        }
        
        // Mix based on source alpha and opacity
        vec3 finalColor = mix(dstColor.rgb, blendResult, effectiveAlpha);
        float outAlpha = max(dstColor.a, effectiveAlpha);
        gl_FragColor = vec4(finalColor, outAlpha);
      }
    `,
    transparent: true,
    depthTest: false,
    depthWrite: false,
    // Use custom blending to write directly (shader handles all blending)
    blending: THREE.CustomBlending,
    blendEquation: THREE.AddEquation,
    blendSrc: THREE.OneFactor,
    blendDst: THREE.ZeroFactor,
  });
  
  const geometry = new THREE.PlaneGeometry(2, 2);
  const sceneCompositeQuad = new THREE.Mesh(geometry, sceneCompositeMaterial);
  sceneCompositeScene.add(sceneCompositeQuad);
  return { sceneCompositeCamera, sceneCompositeScene, sceneCompositeMaterial, sceneCompositeQuad };
}
