text: import { Color } from '../../math/Color.js';
  import { Matrix4 } from '../../math/Matrix4.js';
  import { Vector2 } from '../../math/Vector2.js';
  import { Vector3 } from '../../math/Vector3.js';
  import { UniformsLib } from '../shaders/UniformsLib.js';
  
  function UniformsCache() {
    const lights = {};
  
    return {
      get(light) {
        if (lights[light.id] !== undefined) {
          return lights[light.id];
        }
  
        let uniforms;
  
        switch (light.type) {
          case 'DirectionalLight':
            uniforms = {
              direction: new Vector3(),
              color: new Color(),
            };
            break;
  
          case 'SpotLight':
            uniforms = {
              position: new Vector3(),
              direction: new Vector3(),
              color: new Color(),
              distance: 0,
              coneCos: 0,
              penumbraCos: 0,
              decay: 0,
            };
            break;
  
          case 'PointLight':
            uniforms = {
              position: new Vector3(),
              color: new Color(),
              distance: 0,
              decay: 0,
            };
            break;
  
          case 'HemisphereLight':
            uniforms = {
              direction: new Vector3(),
              skyColor: new Color(),
              groundColor: new Color(),
            };
            break;
  
          case 'RectAreaLight':
            uniforms = {
              color: new Color(),
              position: new Vector3(),
              halfWidth: new Vector3(),
              halfHeight: new Vector3(),
            };
            break;
        }
  
        lights[light.id] = uniforms;
  
        return uniforms;
      },
    };
  }
  
  function ShadowUniformsCache() {
    const lights = {};
  
    return {
      get(light) {
        if (lights[light.id] !== undefined) {
          return lights[light.id];
        }
  
        let uniforms;
  
        switch (light.type) {
          case 'DirectionalLight':
            uniforms = {
              shadowBias: 0,
              shadowNormalBias: 0,
              shadowRadius: 1,
              shadowMapSize: new Vector2(),
            };
            break;
  
          case 'SpotLight':
            uniforms = {
              shadowBias: 0,
              shadowNormalBias: 0,
              shadowRadius: 1,
              shadowMapSize: new Vector2(),
            };
            break;
  
          case 'PointLight':
            uniforms = {
              shadowBias: 0,
              shadowNormalBias: 0,
              shadowRadius: 1,
              shadowMapSize: new Vector2(),
              shadowCameraNear: 1,
              shadowCameraFar: 1000,
            };
            break;
  
          // TODO (abelnation): set RectAreaLight shadow uniforms
        }
  
        lights[light.id] = uniforms;
  
        return uniforms;
      },
    };
  }
  
  let nextVersion = 0;
  
  function shadowCastingLightsFirst(lightA, lightB) {
    return (lightB.castShadow ? 1 : 0) - (lightA.castShadow ? 1 : 0);
  }
  
  function WebGLLights(extensions, capabilities) {
    const cache = new UniformsCache();
  
    const shadowCache = ShadowUniformsCache();
  
    const state = {
      version: 0,
  
      hash: {
        directionalLength: -1,
        pointLength: -1,
        spotLength: -1,
        rectAreaLength: -1,
        hemiLength: -1,
  
        numDirectionalShadows: -1,
        numPointShadows: -1,
        numSpotShadows: -1,
      },
  
      ambient: [0, 0, 0],
      probe: [],
      directional: [],
      directionalShadow: [],
      directionalShadowMap: [],
      directionalShadowMatrix: [],
      spot: [],
      spotShadow: [],
      spotShadowMap: [],
      spotShadowMatrix: [],
      rectArea: [],
      rectAreaLTC1: null,
      rectAreaLTC2: null,
      point: [],
      pointShadow: [],
      pointShadowMap: [],
      pointShadowMatrix: [],
      hemi: [],
    };
  
    for (let i = 0; i < 9; i++) state.probe.push(new Vector3());
  
    const vector3 = new Vector3();
    const matrix4 = new Matrix4();
    const matrix42 = new Matrix4();
  
    function setup(lights) {
      let r = 0;
      let g = 0;
      let b = 0;
  
      for (let i = 0; i < 9; i++) state.probe[i].set(0, 0, 0);
  
      let directionalLength = 0;
      let pointLength = 0;
      let spotLength = 0;
      let rectAreaLength = 0;
      let hemiLength = 0;
  
      let numDirectionalShadows = 0;
      let numPointShadows = 0;
      let numSpotShadows = 0;
  
      lights.sort(shadowCastingLightsFirst);
  
      for (let i = 0, l = lights.length; i < l; i++) {
        const light = lights[i];
  
        const color = light.color;
        const intensity = light.intensity;
        const distance = light.distance;
  
        const shadowMap = light.shadow && light.shadow.map ? light.shadow.map.texture : null;
  
        if (light.isAmbientLight) {
          r += color.r * intensity;
          g += color.g * intensity;
          b += color.b * intensity;
        } else if (light.isLightProbe) {
          for (let j = 0; j < 9; j++) {
            state.probe[j].addScaledVector(light.sh.coefficients[j], intensity);
          }
        } else if (light.isDirectionalLight) {
          const uniforms = cache.get(light);
  
          uniforms.color.copy(light.color).multiplyScalar(light.intensity);
  
          if (light.castShadow) {
            const shadow = light.shadow;
  
            const shadowUniforms = shadowCache.get(light);
  
            shadowUniforms.shadowBias = shadow.bias;
            shadowUniforms.shadowNormalBias = shadow.normalBias;
            shadowUniforms.shadowRadius = shadow.radius;
            shadowUniforms.shadowMapSize = shadow.mapSize;
  
            state.directionalShadow[directionalLength] = shadowUniforms;
            state.directionalShadowMap[directionalLength] = shadowMap;
            state.directionalShadowMatrix[directionalLength] = light.shadow.matrix;
  
            numDirectionalShadows++;
          }
  
          state.directional[directionalLength] = uniforms;
  
          directionalLength++;
        } else if (light.isSpotLight) {
          const uniforms = cache.get(light);
  
          uniforms.position.setFromMatrixPosition(light.matrixWorld);
  
          uniforms.color.copy(color).multiplyScalar(intensity);
          uniforms.distance = distance;
  
          uniforms.coneCos = Math.cos(light.angle);
          uniforms.penumbraCos = Math.cos(light.angle * (1 - light.penumbra));
          uniforms.decay = light.decay;
  
          if (light.castShadow) {
            const shadow = light.shadow;
  
            const shadowUniforms = shadowCache.get(light);
  
            shadowUniforms.shadowBias = shadow.bias;
            shadowUniforms.shadowNormalBias = shadow.normalBias;
            shadowUniforms.shadowRadius = shadow.radius;
            shadowUniforms.shadowMapSize = shadow.mapSize;
  
            state.spotShadow[spotLength] = shadowUniforms;
            state.spotShadowMap[spotLength] = shadowMap;
            state.spotShadowMatrix[spotLength] = light.shadow.matrix;
  
            numSpotShadows++;
          }
  
          state.spot[spotLength] = uniforms;
  
          spotLength++;
        } else if (light.isRectAreaLight) {
          const uniforms = cache.get(light);
  
          // (a) intensity is the total visible light emitted
          // uniforms.color.copy( color ).multiplyScalar( intensity / ( light.width * light.height * Math.PI ) );
  
          // (b) intensity is the brightness of the light
          uniforms.color.copy(color).multiplyScalar(intensity);
  
          uniforms.halfWidth.set(light.width * 0.5, 0.0, 0.0);
          uniforms.halfHeight.set(0.0, light.height * 0.5, 0.0);
  
          state.rectArea[rectAreaLength] = uniforms;
  
          rectAreaLength++;
        } else if (light.isPointLight) {
          const uniforms = cache.get(light);
  
          uniforms.color.copy(light.color).multiplyScalar(light.intensity);
          uniforms.distance = light.distance;
          uniforms.decay = light.decay;
  
          if (light.castShadow) {
            const shadow = light.shadow;
  
            const shadowUniforms = shadowCache.get(light);
  
            shadowUniforms.shadowBias = shadow.bias;
            shadowUniforms.shadowNormalBias = shadow.normalBias;
            shadowUniforms.shadowRadius = shadow.radius;
            shadowUniforms.shadowMapSize = shadow.mapSize;
            shadowUniforms.shadowCameraNear = shadow.camera.near;
            shadowUniforms.shadowCameraFar = shadow.camera.far;
  
            state.pointShadow[pointLength] = shadowUniforms;
            state.pointShadowMap[pointLength] = shadowMap;
            state.pointShadowMatrix[pointLength] = light.shadow.matrix;
  
            numPointShadows++;
          }
  
          state.point[pointLength] = uniforms;
  
          pointLength++;
        } else if (light.isHemisphereLight) {
          const uniforms = cache.get(light);
  
          uniforms.skyColor.copy(light.color).multiplyScalar(intensity);
          uniforms.groundColor.copy(light.groundColor).multiplyScalar(intensity);
  
          state.hemi[hemiLength] = uniforms;
  
          hemiLength++;
        }
      }
  
      if (rectAreaLength > 0) {
        if (capabilities.isWebGL2) {
          // WebGL 2
  
          state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1;
          state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2;
        } else {
          // WebGL 1
  
          if (extensions.has('OES_texture_float_linear') === true) {
            state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1;
            state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2;
          } else if (extensions.has('OES_texture_half_float_linear') === true) {
            state.rectAreaLTC1 = UniformsLib.LTC_HALF_1;
            state.rectAreaLTC2 = UniformsLib.LTC_HALF_2;
          } else {
            console.error('THREE.WebGLRenderer: Unable to use RectAreaLight. Missing WebGL extensions.');
          }
        }
      }
  
      state.ambient[0] = r;
      state.ambient[1] = g;
      state.ambient[2] = b;
  
      const hash = state.hash;
  
      if (
        hash.directionalLength !== directionalLength ||
        hash.pointLength !== pointLength ||
        hash.spotLength !== spotLength ||
        hash.rectAreaLength !== rectAreaLength ||
        hash.hemiLength !== hemiLength ||
        hash.numDirectionalShadows !== numDirectionalShadows ||
        hash.numPointShadows !== numPointShadows ||
        hash.numSpotShadows !== numSpotShadows
      ) {
        state.directional.length = directionalLength;
        state.spot.length = spotLength;
        state.rectArea.length = rectAreaLength;
        state.point.length = pointLength;
        state.hemi.length = hemiLength;
  
        state.directionalShadow.length = numDirectionalShadows;
        state.directionalShadowMap.length = numDirectionalShadows;
        state.pointShadow.length = numPointShadows;
        state.pointShadowMap.length = numPointShadows;
        state.spotShadow.length = numSpotShadows;
        state.spotShadowMap.length = numSpotShadows;
        state.directionalShadowMatrix.length = numDirectionalShadows;
        state.pointShadowMatrix.length = numPointShadows;
        state.spotShadowMatrix.length = numSpotShadows;
  
        hash.directionalLength = directionalLength;
        hash.pointLength = pointLength;
        hash.spotLength = spotLength;
        hash.rectAreaLength = rectAreaLength;
        hash.hemiLength = hemiLength;
  
        hash.numDirectionalShadows = numDirectionalShadows;
        hash.numPointShadows = numPointShadows;
        hash.numSpotShadows = numSpotShadows;
  
        state.version = nextVersion++;
      }
    }
  
    function setupView(lights, camera) {
      let directionalLength = 0;
      let pointLength = 0;
      let spotLength = 0;
      let rectAreaLength = 0;
      let hemiLength = 0;
  
      const viewMatrix = camera.matrixWorldInverse;
  
      for (let i = 0, l = lights.length; i < l; i++) {
        const light = lights[i];
  
        if (light.isDirectionalLight) {
          const uniforms = state.directional[directionalLength];
  
          uniforms.direction.setFromMatrixPosition(light.matrixWorld);
          vector3.setFromMatrixPosition(light.target.matrixWorld);
          uniforms.direction.sub(vector3);
          uniforms.direction.transformDirection(viewMatrix);
  
          directionalLength++;
        } else if (light.isSpotLight) {
          const uniforms = state.spot[spotLength];
  
          uniforms.position.setFromMatrixPosition(light.matrixWorld);
          uniforms.position.applyMatrix4(viewMatrix);
  
          uniforms.direction.setFromMatrixPosition(light.matrixWorld);
          vector3.setFromMatrixPosition(light.target.matrixWorld);
          uniforms.direction.sub(vector3);
          uniforms.direction.transformDirection(viewMatrix);
  
          spotLength++;
        } else if (light.isRectAreaLight) {
          const uniforms = state.rectArea[rectAreaLength];
  
          uniforms.position.setFromMatrixPosition(light.matrixWorld);
          uniforms.position.applyMatrix4(viewMatrix);
  
          // extract local rotation of light to derive width/height half vectors
          matrix42.identity();
          matrix4.copy(light.matrixWorld);
          matrix4.premultiply(viewMatrix);
          matrix42.extractRotation(matrix4);
  
          uniforms.halfWidth.set(light.width * 0.5, 0.0, 0.0);
          uniforms.halfHeight.set(0.0, light.height * 0.5, 0.0);
  
          uniforms.halfWidth.applyMatrix4(matrix42);
          uniforms.halfHeight.applyMatrix4(matrix42);
  
          rectAreaLength++;
        } else if (light.isPointLight) {
          const uniforms = state.point[pointLength];
  
          uniforms.position.setFromMatrixPosition(light.matrixWorld);
          uniforms.position.applyMatrix4(viewMatrix);
  
          pointLength++;
        } else if (light.isHemisphereLight) {
          const uniforms = state.hemi[hemiLength];
  
          uniforms.direction.setFromMatrixPosition(light.matrixWorld);
          uniforms.direction.transformDirection(viewMatrix);
          uniforms.direction.normalize();
  
          hemiLength++;
        }
      }
    }
  
    return {
      setup,
      setupView,
      state,
    };
  }
  
  export { WebGLLights };
  