import { applyProceduralPlacementRotation } from './procedural-placement.js';

export function sphereClonerPlacement(config = {}) {
  const numberOrZero = value => {
    const number = Number(value);
    return Number.isFinite(number) ? number : 0;
  };
  return {
    x: numberOrZero(config.offsetX),
    y: numberOrZero(config.offsetY),
    z: numberOrZero(config.offsetZ),
  };
}

export function applySphereClonerPlacement(config, target) {
  const placement = sphereClonerPlacement(config);
  if (target?.position?.set) {
    target.position.set(placement.x, placement.y, placement.z);
  } else if (target?.position) {
    target.position.x = placement.x;
    target.position.y = placement.y;
    target.position.z = placement.z;
  }
  applyProceduralPlacementRotation(config, target);
  return placement;
}

export function createSphereCloner(THREE, config, scene, { loadCustomCloneModel, createAnimatedClones, loadCustomDistributionSurface }) {
  // Use ?? (nullish coalescing) to preserve 0 values - || would treat 0 as falsy
  const count = config.count ?? 1000;
  const baseRadius = config.baseRadius ?? 5;
  const cloneRadius = config.cloneRadius ?? 0.15;
  const cloneDetail = config.cloneDetail ?? 1;
  const sizeVariation = config.sizeVariation ?? 0.5;
  const offsetAmount = config.offsetAmount ?? 0.5;
  const offsetSpeed = config.offsetSpeed ?? 1.0;
  const rotationSpeed = config.rotationSpeed ?? 1.0;
  const orbitSpeed = config.orbitSpeed ?? 0.2;
  const pulseAmount = config.pulseAmount ?? 0.1;
  const pulseSpeed = config.pulseSpeed ?? 2.0;
  const breathAmount = config.breathAmount ?? 0.2;
  const breathSpeed = config.breathSpeed ?? 0.5;
  const colorMixSpeed = config.colorMixSpeed ?? 0.5;
  const emissivePulseSpeed = config.emissivePulseSpeed ?? 2.0;
  
  // Colors
  const baseColor = new THREE.Color(config.colors?.base || '#8866ff');
  const accentColor = new THREE.Color(config.colors?.accent || '#ff66aa');
  const emissiveColor = new THREE.Color(config.colors?.emissive || '#4422aa');
  const emissiveIntensity = config.colors?.emissiveIntensity ?? 0.3;
  
  // Material settings
  const metalness = config.material?.metalness ?? 0.9;
  const roughness = config.material?.roughness ?? 0.1;
  const envMapIntensity = config.material?.envMapIntensity ?? 1.5;
  const fresnelPower = config.material?.fresnelPower ?? 3.0;
  const envMapPath = config.material?.envMap || 'images/sphere-stars.jpg';
  
  // === EFFECTOR SETTINGS ===
  const eff = config.effectors || {};
  
  // Plain Effector - uniform offset/scale/rotation
  const plainEnabled = eff.plain?.enabled ? 1.0 : 0.0;
  const plainPosX = eff.plain?.position?.x ?? 0.0;
  const plainPosY = eff.plain?.position?.y ?? 0.0;
  const plainPosZ = eff.plain?.position?.z ?? 0.0;
  const plainScale = eff.plain?.scale ?? 0.0;
  const plainRotX = eff.plain?.rotation?.x ?? 0.0;
  const plainRotY = eff.plain?.rotation?.y ?? 0.0;
  const plainRotZ = eff.plain?.rotation?.z ?? 0.0;
  
  // Noise Effector - 3D noise displacement
  const noiseEnabled = eff.noise?.enabled ? 1.0 : 0.0;
  const noiseStrength = eff.noise?.strength ?? 0.5;
  const noiseScale = eff.noise?.scale ?? 1.0;
  const noiseSpeed = eff.noise?.speed ?? 0.5;
  const noiseOctaves = eff.noise?.octaves ?? 3;
  const noiseScaleEffect = eff.noise?.scaleEffect ?? 0.0;
  const noiseRotationEffect = eff.noise?.rotationEffect ?? 0.0;
  
  // Delay/Step Effector - staggered animation
  const delayEnabled = eff.delay?.enabled ? 1.0 : 0.0;
  const delayAmount = eff.delay?.amount ?? 0.5;
  const delayMode = eff.delay?.mode ?? 0; // 0=linear, 1=radial, 2=random
  const delayOffset = eff.delay?.offset ?? 0.0;
  
  // Spherical Field - affect by distance from point
  const fieldEnabled = eff.field?.enabled ? 1.0 : 0.0;
  const fieldPosX = eff.field?.position?.x ?? 0.0;
  const fieldPosY = eff.field?.position?.y ?? 0.0;
  const fieldPosZ = eff.field?.position?.z ?? 0.0;
  const fieldRadius = eff.field?.radius ?? 5.0;
  const fieldFalloff = eff.field?.falloff ?? 1.0;
  const fieldStrength = eff.field?.strength ?? 1.0;
  const fieldInvert = eff.field?.invert ? 1.0 : 0.0;
  const fieldScaleEffect = eff.field?.scaleEffect ?? 0.5;
  const fieldPushEffect = eff.field?.pushEffect ?? 0.5;
  
  // Formula Effector - wave patterns
  const formulaEnabled = eff.formula?.enabled ? 1.0 : 0.0;
  const formulaType = eff.formula?.type ?? 0; // 0=sine, 1=cosine, 2=radial, 3=spiral
  const formulaFrequency = eff.formula?.frequency ?? 2.0;
  const formulaAmplitude = eff.formula?.amplitude ?? 1.0;
  const formulaSpeed = eff.formula?.speed ?? 1.0;
  const formulaAxis = eff.formula?.axis ?? 1; // 0=X, 1=Y, 2=Z
  const formulaScaleEffect = eff.formula?.scaleEffect ?? 0.3;
  const formulaPosEffect = eff.formula?.positionEffect ?? 0.5;
  
  // Random Effector - randomization with seed
  const randomEnabled = eff.random?.enabled ? 1.0 : 0.0;
  const randomSeed = eff.random?.seed ?? 42.0;
  const randomPosStrength = eff.random?.positionStrength ?? 0.0;
  const randomScaleStrength = eff.random?.scaleStrength ?? 0.0;
  const randomScaleUniform = eff.random?.scaleUniform ? 1.0 : 0.0;
  const randomRotStrength = eff.random?.rotationStrength ?? 0.0;
  
  // Load environment map
  const textureLoader = new THREE.TextureLoader();
  const envMapTexture = textureLoader.load(
    envMapPath,
    (tex) => { console.log('Sphere cloner env map loaded'); },
    undefined,
    (err) => { console.warn('Failed to load sphere cloner env map'); }
  );
  envMapTexture.mapping = THREE.EquirectangularReflectionMapping;
  
  // Create clone geometry based on type
  let cloneGeo;
  const cloneType = config.cloneType || 'icosahedron';
  
  switch (cloneType) {
    case 'icosahedron':
    cloneGeo = new THREE.IcosahedronGeometry(cloneRadius, cloneDetail);
      break;
    case 'octahedron':
    cloneGeo = new THREE.OctahedronGeometry(cloneRadius, cloneDetail);
      break;
    case 'dodecahedron':
    cloneGeo = new THREE.DodecahedronGeometry(cloneRadius, cloneDetail);
      break;
    case 'tetrahedron':
      cloneGeo = new THREE.TetrahedronGeometry(cloneRadius, cloneDetail);
      break;
    case 'cube':
      cloneGeo = new THREE.BoxGeometry(cloneRadius * 1.5, cloneRadius * 1.5, cloneRadius * 1.5, 
        Math.max(1, cloneDetail), Math.max(1, cloneDetail), Math.max(1, cloneDetail));
      break;
    case 'sphere':
      cloneGeo = new THREE.SphereGeometry(cloneRadius, 16 + cloneDetail * 8, 12 + cloneDetail * 6);
      break;
    case 'torus':
      cloneGeo = new THREE.TorusGeometry(cloneRadius, cloneRadius * 0.4, 12 + cloneDetail * 4, 24 + cloneDetail * 8);
      break;
    case 'cone':
      cloneGeo = new THREE.ConeGeometry(cloneRadius, cloneRadius * 2, 12 + cloneDetail * 6, 1);
      break;
    case 'cylinder':
      cloneGeo = new THREE.CylinderGeometry(cloneRadius * 0.8, cloneRadius * 0.8, cloneRadius * 2, 12 + cloneDetail * 6);
      break;
    case 'custom':
      // Custom geometry will be loaded separately - use placeholder for now
      // The actual custom geometry loading happens asynchronously after mesh creation
      cloneGeo = new THREE.IcosahedronGeometry(cloneRadius, 0);
      console.log('[Cloner] Custom clone type - will load model:', config.customCloneModel);
      break;
    default:
      // Default: icosahedron
    cloneGeo = new THREE.IcosahedronGeometry(cloneRadius, 0);
  }
  
  // Light settings from config
  const lightDir = config.light?.direction || { x: 1, y: 1, z: 0.5 };
  const lightColor = new THREE.Color(config.light?.color || '#ffffff');
  const lightIntensity = config.light?.intensity ?? 1.0;
  const ambientColor = new THREE.Color(config.light?.ambientColor || '#1a1a3a');
  const ambientIntensity = config.light?.ambientIntensity ?? 0.4;
  
  // Create shader material with env mapping and effectors
  const clonerMaterial = new THREE.ShaderMaterial({
    uniforms: {
      uTime: { value: 0 },
      uEnvMap: { value: envMapTexture },
      uBaseColor: { value: baseColor },
      uAccentColor: { value: accentColor },
      uEmissiveColor: { value: emissiveColor },
      uEmissiveIntensity: { value: emissiveIntensity },
      uMetalness: { value: metalness },
      uRoughness: { value: roughness },
      uEnvMapIntensity: { value: envMapIntensity },
      uFresnelPower: { value: fresnelPower },
      uOffsetAmount: { value: offsetAmount },
      uOffsetSpeed: { value: offsetSpeed },
      uRotationSpeed: { value: rotationSpeed },
      uOrbitSpeed: { value: orbitSpeed },
      uPulseAmount: { value: pulseAmount },
      uPulseSpeed: { value: pulseSpeed },
      uBreathAmount: { value: breathAmount },
      uBreathSpeed: { value: breathSpeed },
      uBaseRadius: { value: baseRadius },
      // Additional animation controls
      uColorMixSpeed: { value: colorMixSpeed },
      uEmissivePulseSpeed: { value: emissivePulseSpeed },
      // Light uniforms
      uLightDir: { value: new THREE.Vector3(lightDir.x, lightDir.y, lightDir.z).normalize() },
      uLightColor: { value: lightColor },
      uLightIntensity: { value: lightIntensity },
      uAmbientColor: { value: ambientColor },
      uAmbientIntensity: { value: ambientIntensity },
      // === EFFECTOR UNIFORMS ===
      // Plain Effector
      uPlainEnabled: { value: plainEnabled },
      uPlainPosition: { value: new THREE.Vector3(plainPosX, plainPosY, plainPosZ) },
      uPlainScale: { value: plainScale },
      uPlainRotation: { value: new THREE.Vector3(plainRotX, plainRotY, plainRotZ) },
      // Noise Effector
      uNoiseEnabled: { value: noiseEnabled },
      uNoiseStrength: { value: noiseStrength },
      uNoiseScale: { value: noiseScale },
      uNoiseSpeed: { value: noiseSpeed },
      uNoiseOctaves: { value: noiseOctaves },
      uNoiseScaleEffect: { value: noiseScaleEffect },
      uNoiseRotEffect: { value: noiseRotationEffect },
      // Delay Effector
      uDelayEnabled: { value: delayEnabled },
      uDelayAmount: { value: delayAmount },
      uDelayMode: { value: delayMode },
      uDelayOffset: { value: delayOffset },
      // Spherical Field
      uFieldEnabled: { value: fieldEnabled },
      uFieldPosition: { value: new THREE.Vector3(fieldPosX, fieldPosY, fieldPosZ) },
      uFieldRadius: { value: fieldRadius },
      uFieldFalloff: { value: fieldFalloff },
      uFieldStrength: { value: fieldStrength },
      uFieldInvert: { value: fieldInvert },
      uFieldScaleEffect: { value: fieldScaleEffect },
      uFieldPushEffect: { value: fieldPushEffect },
      // Formula Effector
      uFormulaEnabled: { value: formulaEnabled },
      uFormulaType: { value: formulaType },
      uFormulaFrequency: { value: formulaFrequency },
      uFormulaAmplitude: { value: formulaAmplitude },
      uFormulaSpeed: { value: formulaSpeed },
      uFormulaAxis: { value: formulaAxis },
      uFormulaScaleEffect: { value: formulaScaleEffect },
      uFormulaPosEffect: { value: formulaPosEffect },
      // Random Effector
      uRandomEnabled: { value: randomEnabled },
      uRandomSeed: { value: randomSeed },
      uRandomPosStrength: { value: randomPosStrength },
      uRandomScaleStrength: { value: randomScaleStrength },
      uRandomScaleUniform: { value: randomScaleUniform },
      uRandomRotStrength: { value: randomRotStrength },
    },
    vertexShader: `
      attribute vec3 instanceOffset;
      attribute float instanceScale;
      attribute float instancePhase;
      attribute vec3 instanceNormal;
      attribute vec3 instanceRotAxis;
      attribute float instanceRotSpeed;
      attribute float instanceIndex;
      
      uniform float uTime;
      uniform float uOffsetAmount;
      uniform float uOffsetSpeed;
      uniform float uRotationSpeed;
      uniform float uOrbitSpeed;
      uniform float uPulseAmount;
      uniform float uPulseSpeed;
      uniform float uBreathAmount;
      uniform float uBreathSpeed;
      uniform float uBaseRadius;
      uniform float uColorMixSpeed;
      
      // Effector uniforms
      uniform float uPlainEnabled;
      uniform vec3 uPlainPosition;
      uniform float uPlainScale;
      uniform vec3 uPlainRotation;
      
      uniform float uNoiseEnabled;
      uniform float uNoiseStrength;
      uniform float uNoiseScale;
      uniform float uNoiseSpeed;
      uniform float uNoiseOctaves;
      uniform float uNoiseScaleEffect;
      uniform float uNoiseRotEffect;
      
      uniform float uDelayEnabled;
      uniform float uDelayAmount;
      uniform float uDelayMode;
      uniform float uDelayOffset;
      
      uniform float uFieldEnabled;
      uniform vec3 uFieldPosition;
      uniform float uFieldRadius;
      uniform float uFieldFalloff;
      uniform float uFieldStrength;
      uniform float uFieldInvert;
      uniform float uFieldScaleEffect;
      uniform float uFieldPushEffect;
      
      uniform float uFormulaEnabled;
      uniform float uFormulaType;
      uniform float uFormulaFrequency;
      uniform float uFormulaAmplitude;
      uniform float uFormulaSpeed;
      uniform float uFormulaAxis;
      uniform float uFormulaScaleEffect;
      uniform float uFormulaPosEffect;
      
      uniform float uRandomEnabled;
      uniform float uRandomSeed;
      uniform float uRandomPosStrength;
      uniform float uRandomScaleStrength;
      uniform float uRandomScaleUniform;
      uniform float uRandomRotStrength;
      
      varying vec3 vNormal;
      varying vec3 vWorldPos;
      varying vec3 vViewDir;
      varying float vPhase;
      varying float vColorMix;
      varying float vEffectorStrength;
      
      // Rotation matrix from axis and angle
      mat3 rotationMatrix(vec3 axis, float angle) {
        float c = cos(angle);
        float s = sin(angle);
        float t = 1.0 - c;
        return mat3(
          t * axis.x * axis.x + c, t * axis.x * axis.y - s * axis.z, t * axis.x * axis.z + s * axis.y,
          t * axis.x * axis.y + s * axis.z, t * axis.y * axis.y + c, t * axis.y * axis.z - s * axis.x,
          t * axis.x * axis.z - s * axis.y, t * axis.y * axis.z + s * axis.x, t * axis.z * axis.z + c
        );
      }
      
      // 3D Simplex-like noise
      vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
      vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
      vec4 permute(vec4 x) { return mod289(((x * 34.0) + 1.0) * x); }
      vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }
      
      float snoise(vec3 v) {
        const vec2 C = vec2(1.0 / 6.0, 1.0 / 3.0);
        const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
        vec3 i = floor(v + dot(v, C.yyy));
        vec3 x0 = v - i + dot(i, C.xxx);
        vec3 g = step(x0.yzx, x0.xyz);
        vec3 l = 1.0 - g;
        vec3 i1 = min(g.xyz, l.zxy);
        vec3 i2 = max(g.xyz, l.zxy);
        vec3 x1 = x0 - i1 + C.xxx;
        vec3 x2 = x0 - i2 + C.yyy;
        vec3 x3 = x0 - D.yyy;
        i = mod289(i);
        vec4 p = permute(permute(permute(
          i.z + vec4(0.0, i1.z, i2.z, 1.0))
          + i.y + vec4(0.0, i1.y, i2.y, 1.0))
          + i.x + vec4(0.0, i1.x, i2.x, 1.0));
        float n_ = 0.142857142857;
        vec3 ns = n_ * D.wyz - D.xzx;
        vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
        vec4 x_ = floor(j * ns.z);
        vec4 y_ = floor(j - 7.0 * x_);
        vec4 x = x_ * ns.x + ns.yyyy;
        vec4 y = y_ * ns.x + ns.yyyy;
        vec4 h = 1.0 - abs(x) - abs(y);
        vec4 b0 = vec4(x.xy, y.xy);
        vec4 b1 = vec4(x.zw, y.zw);
        vec4 s0 = floor(b0) * 2.0 + 1.0;
        vec4 s1 = floor(b1) * 2.0 + 1.0;
        vec4 sh = -step(h, vec4(0.0));
        vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
        vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
        vec3 p0 = vec3(a0.xy, h.x);
        vec3 p1 = vec3(a0.zw, h.y);
        vec3 p2 = vec3(a1.xy, h.z);
        vec3 p3 = vec3(a1.zw, h.w);
        vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3)));
        p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w;
        vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
        m = m * m;
        return 42.0 * dot(m*m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)));
      }
      
      // FBM noise
      float fbm(vec3 p, int octaves) {
        float value = 0.0;
        float amplitude = 0.5;
        float frequency = 1.0;
        for (int i = 0; i < 6; i++) {
          if (i >= octaves) break;
          value += amplitude * snoise(p * frequency);
          frequency *= 2.0;
          amplitude *= 0.5;
        }
        return value;
      }
      
      // Hash function for random
      float hash(float n) { return fract(sin(n) * 43758.5453123); }
      vec3 hash3(float n) {
        return vec3(hash(n), hash(n + 1.0), hash(n + 2.0));
      }
      
      void main() {
        vPhase = instancePhase;
        float idx = instanceIndex;
        float totalCount = float(${count});
        float normalizedIdx = idx / totalCount;
        
        // Calculate effective time with delay
        float effectiveTime = uTime;
        if (uDelayEnabled > 0.5) {
          float delayT = 0.0;
          if (uDelayMode < 0.5) {
            // Linear delay based on index
            delayT = normalizedIdx;
          } else if (uDelayMode < 1.5) {
            // Radial delay based on Y position
            delayT = (instanceOffset.y / uBaseRadius + 1.0) * 0.5;
          } else {
            // Random delay
            delayT = hash(idx + uRandomSeed);
          }
          effectiveTime = uTime - delayT * uDelayAmount + uDelayOffset;
        }
        
        // Base position on sphere surface
        vec3 basePos = instanceOffset;
        float effectorScale = 1.0;
        vec3 effectorRotation = vec3(0.0);
        vec3 effectorPosition = vec3(0.0);
        float effectorStrength = 0.0;
        
        // === PLAIN EFFECTOR ===
        if (uPlainEnabled > 0.5) {
          effectorPosition += uPlainPosition;
          effectorScale += uPlainScale;
          effectorRotation += uPlainRotation;
        }
        
        // === NOISE EFFECTOR ===
        if (uNoiseEnabled > 0.5) {
          vec3 noisePos = basePos * uNoiseScale + effectiveTime * uNoiseSpeed;
          float noiseVal = fbm(noisePos, int(uNoiseOctaves));
          vec3 noiseDir = normalize(instanceNormal + vec3(
            snoise(noisePos + vec3(100.0, 0.0, 0.0)),
            snoise(noisePos + vec3(0.0, 100.0, 0.0)),
            snoise(noisePos + vec3(0.0, 0.0, 100.0))
          ) * 0.5);
          effectorPosition += noiseDir * noiseVal * uNoiseStrength;
          effectorScale += noiseVal * uNoiseScaleEffect;
          effectorRotation += vec3(noiseVal) * uNoiseRotEffect;
        }
        
        // === SPHERICAL FIELD EFFECTOR ===
        if (uFieldEnabled > 0.5) {
          vec3 toField = basePos - uFieldPosition;
          float dist = length(toField);
          float fieldEffect = 1.0 - smoothstep(0.0, uFieldRadius, dist);
          fieldEffect = pow(fieldEffect, uFieldFalloff);
          if (uFieldInvert > 0.5) fieldEffect = 1.0 - fieldEffect;
          fieldEffect *= uFieldStrength;
          effectorStrength = max(effectorStrength, fieldEffect);
          
          // Push away from field center
          vec3 pushDir = normalize(toField);
          effectorPosition += pushDir * fieldEffect * uFieldPushEffect;
          effectorScale += fieldEffect * uFieldScaleEffect;
        }
        
        // === FORMULA EFFECTOR ===
        if (uFormulaEnabled > 0.5) {
          float axisValue = 0.0;
          if (uFormulaAxis < 0.5) axisValue = basePos.x;
          else if (uFormulaAxis < 1.5) axisValue = basePos.y;
          else axisValue = basePos.z;
          
          float formulaVal = 0.0;
          float phase = axisValue * uFormulaFrequency + effectiveTime * uFormulaSpeed;
          
          if (uFormulaType < 0.5) {
            // Sine wave
            formulaVal = sin(phase) * uFormulaAmplitude;
          } else if (uFormulaType < 1.5) {
            // Cosine wave
            formulaVal = cos(phase) * uFormulaAmplitude;
          } else if (uFormulaType < 2.5) {
            // Radial wave
            float r = length(basePos.xz);
            formulaVal = sin(r * uFormulaFrequency + effectiveTime * uFormulaSpeed) * uFormulaAmplitude;
          } else {
            // Spiral
            float angle = atan(basePos.z, basePos.x);
            float r = length(basePos.xz);
            formulaVal = sin(angle * uFormulaFrequency + r * 2.0 + effectiveTime * uFormulaSpeed) * uFormulaAmplitude;
          }
          
          effectorScale += formulaVal * uFormulaScaleEffect;
          effectorPosition += instanceNormal * formulaVal * uFormulaPosEffect;
        }
        
        // === RANDOM EFFECTOR ===
        if (uRandomEnabled > 0.5) {
          vec3 rnd = hash3(idx * 3.7 + uRandomSeed) * 2.0 - 1.0;
          effectorPosition += rnd * uRandomPosStrength;
          
          if (uRandomScaleUniform > 0.5) {
            effectorScale += (hash(idx + uRandomSeed * 2.0) * 2.0 - 1.0) * uRandomScaleStrength;
          } else {
            effectorScale += rnd.x * uRandomScaleStrength;
          }
          
          effectorRotation += rnd * uRandomRotStrength * 3.14159;
        }
        
        // Apply effector transforms
        basePos += effectorPosition;
        
        // Breathing effect (expand/contract the whole sphere)
        float breath = sin(effectiveTime * uBreathSpeed) * uBreathAmount;
        float breathScale = 1.0 + breath;
        basePos *= breathScale;
        
        // Offset animation (push in/out from surface)
        float offsetNoise = sin(effectiveTime * uOffsetSpeed + instancePhase * 10.0) * 0.5 + 0.5;
        float dynamicOffset = offsetNoise * uOffsetAmount;
        basePos += instanceNormal * dynamicOffset;
        
        // Orbit around base sphere
        float orbitAngle = effectiveTime * uOrbitSpeed + instancePhase;
        mat3 orbitRot = rotationMatrix(vec3(0.0, 1.0, 0.0), orbitAngle);
        basePos = orbitRot * basePos;
        
        // Apply effector rotation
        if (length(effectorRotation) > 0.001) {
          mat3 effRot = rotationMatrix(vec3(1.0, 0.0, 0.0), effectorRotation.x);
          effRot *= rotationMatrix(vec3(0.0, 1.0, 0.0), effectorRotation.y);
          effRot *= rotationMatrix(vec3(0.0, 0.0, 1.0), effectorRotation.z);
          basePos = effRot * basePos;
        }
        
        // Pulse scale
        float pulse = sin(effectiveTime * uPulseSpeed + instancePhase * 5.0) * uPulseAmount;
        float finalScale = instanceScale * (1.0 + pulse) * max(0.01, effectorScale);
        
        // Rotate the clone itself
        float rotAngle = effectiveTime * uRotationSpeed * instanceRotSpeed + instancePhase;
        mat3 selfRot = rotationMatrix(instanceRotAxis, rotAngle);
        
        // Transform position
        vec3 rotatedPos = selfRot * (position * finalScale);
        vec3 worldPos = rotatedPos + basePos;
        
        vWorldPos = (modelMatrix * vec4(worldPos, 1.0)).xyz;
        vNormal = normalize(normalMatrix * selfRot * normal);
        vViewDir = normalize(cameraPosition - vWorldPos);
        
        // Color mixing based on position and time (controllable speed)
        vColorMix = sin(instancePhase * 3.0 + effectiveTime * uColorMixSpeed) * 0.5 + 0.5;
        vEffectorStrength = effectorStrength;
        
        gl_Position = projectionMatrix * modelViewMatrix * vec4(worldPos, 1.0);
      }
    `,
    fragmentShader: `
      uniform float uTime;
      uniform sampler2D uEnvMap;
      uniform vec3 uBaseColor;
      uniform vec3 uAccentColor;
      uniform vec3 uEmissiveColor;
      uniform float uEmissiveIntensity;
      uniform float uMetalness;
      uniform float uRoughness;
      uniform float uEnvMapIntensity;
      uniform float uFresnelPower;
      // Light uniforms
      uniform vec3 uLightDir;
      uniform vec3 uLightColor;
      uniform float uLightIntensity;
      uniform vec3 uAmbientColor;
      uniform float uAmbientIntensity;
      uniform float uEmissivePulseSpeed;
      
      varying vec3 vNormal;
      varying vec3 vWorldPos;
      varying vec3 vViewDir;
      varying float vPhase;
      varying float vColorMix;
      varying float vEffectorStrength;
      
      #define PI 3.14159265359
      
      // Sample environment map with equirectangular projection
      vec3 sampleEnvMap(vec3 dir) {
        float phi = atan(dir.z, dir.x);
        float theta = acos(clamp(dir.y, -1.0, 1.0));
        vec2 uv = vec2(phi / (2.0 * PI) + 0.5, theta / PI);
        return texture(uEnvMap, uv).rgb;
      }
      
      // Fresnel approximation
      float fresnelSchlick(float cosTheta, float F0) {
        return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
      }
      
      void main() {
        vec3 N = normalize(vNormal);
        vec3 V = normalize(vViewDir);
        vec3 R = reflect(-V, N);
        
        // Mix colors based on instance, time, and effector strength
        float colorMixFinal = mix(vColorMix, vEffectorStrength, vEffectorStrength * 0.5);
        vec3 baseColor = mix(uBaseColor, uAccentColor, colorMixFinal);
        
        // Environment reflection
        vec3 envColor = sampleEnvMap(R);
        envColor *= uEnvMapIntensity;
        
        // Fresnel for edge glow
        float NdotV = max(dot(N, V), 0.0);
        float fresnel = fresnelSchlick(NdotV, 0.04);
        fresnel = pow(fresnel, 1.0 / uFresnelPower);
        
        // Main directional light (using uniforms)
        vec3 lightDir = normalize(uLightDir);
        float NdotL = max(dot(N, lightDir), 0.0);
        
        // Diffuse with light color and intensity
        vec3 lightContrib = uLightColor * uLightIntensity * NdotL;
        vec3 ambientContrib = uAmbientColor * uAmbientIntensity;
        vec3 diffuse = baseColor * (lightContrib + ambientContrib);
        
        // Specular
        vec3 H = normalize(V + lightDir);
        float NdotH = max(dot(N, H), 0.0);
        float specPower = mix(16.0, 128.0, 1.0 - uRoughness);
        float spec = pow(NdotH, specPower) * (1.0 - uRoughness) * uLightIntensity;
        vec3 specColor = uLightColor * spec;
        
        // Combine with metalness
        vec3 metalDiffuse = diffuse * (1.0 - uMetalness * 0.8);
        vec3 reflection = envColor * fresnel * uMetalness;
        
        // Final color
        vec3 finalColor = metalDiffuse + reflection + specColor * 0.5;
        
        // Add emissive glow - enhanced by effector (controllable pulse speed)
        float pulse = uEmissivePulseSpeed > 0.0 ? sin(uTime * uEmissivePulseSpeed + vPhase * 5.0) * 0.5 + 0.5 : 0.5;
        float emissiveBoost = 1.0 + vEffectorStrength * 2.0;
        finalColor += uEmissiveColor * uEmissiveIntensity * (0.5 + pulse * 0.5) * emissiveBoost;
        
        // Rim glow
        float rim = pow(1.0 - NdotV, 3.0) * 0.5;
        finalColor += uAccentColor * rim * (1.0 + vEffectorStrength);
        
        gl_FragColor = vec4(finalColor, 1.0);
      }
    `,
    side: THREE.DoubleSide,
  });
  
  // Create instanced mesh
  const instancedMesh = new THREE.InstancedMesh(cloneGeo, clonerMaterial, count);
  applySphereClonerPlacement(config, instancedMesh);
  
  // Instance attributes
  const offsets = new Float32Array(count * 3);
  const scales = new Float32Array(count);
  const phases = new Float32Array(count);
  const normals = new Float32Array(count * 3);
  const rotAxes = new Float32Array(count * 3);
  const rotSpeeds = new Float32Array(count);
  const indices = new Float32Array(count);
  
  // Distribute points on sphere using fibonacci spiral
  const goldenRatio = (1 + Math.sqrt(5)) / 2;
  
  // Distribution type: sphere, cube, cylinder, torus, plane, ring, line
  const distributionType = config.distributionType || 'sphere';
  
  // Additional distribution parameters
  const torusInnerRadius = config.torusInnerRadius ?? (baseRadius * 0.3);
  const planeWidth = config.planeWidth ?? (baseRadius * 2);
  const planeDepth = config.planeDepth ?? (baseRadius * 2);
  const cylinderHeight = config.cylinderHeight ?? (baseRadius * 2);
  const lineLength = config.lineLength ?? (baseRadius * 2);
  const lineAxis = config.lineAxis ?? 'y';
  
  for (let i = 0; i < count; i++) {
    // Store index for effector calculations
    indices[i] = i;
    
    let x = 0, y = 0, z = 0;
    let nx = 0, ny = 1, nz = 0;
    
    if (distributionType === 'sphere') {
    // Fibonacci sphere distribution for even spacing
    const theta = 2 * Math.PI * i / goldenRatio;
    const phi = Math.acos(1 - 2 * (i + 0.5) / count);
      x = Math.sin(phi) * Math.cos(theta) * baseRadius;
      y = Math.cos(phi) * baseRadius;
      z = Math.sin(phi) * Math.sin(theta) * baseRadius;
      // Normal points outward from center
      const len = Math.sqrt(x*x + y*y + z*z) || 1;
      nx = x / len; ny = y / len; nz = z / len;
      
    } else if (distributionType === 'cube') {
      // Distribute on cube surface
      const face = Math.floor(Math.random() * 6);
      const u = (Math.random() - 0.5) * 2 * baseRadius;
      const v = (Math.random() - 0.5) * 2 * baseRadius;
      switch (face) {
        case 0: x = baseRadius; y = u; z = v; nx = 1; ny = 0; nz = 0; break;
        case 1: x = -baseRadius; y = u; z = v; nx = -1; ny = 0; nz = 0; break;
        case 2: x = u; y = baseRadius; z = v; nx = 0; ny = 1; nz = 0; break;
        case 3: x = u; y = -baseRadius; z = v; nx = 0; ny = -1; nz = 0; break;
        case 4: x = u; y = v; z = baseRadius; nx = 0; ny = 0; nz = 1; break;
        case 5: x = u; y = v; z = -baseRadius; nx = 0; ny = 0; nz = -1; break;
      }
      
    } else if (distributionType === 'cylinder') {
      // Distribute on cylinder surface
      const theta = Math.random() * Math.PI * 2;
      const h = (Math.random() - 0.5) * cylinderHeight;
      x = Math.cos(theta) * baseRadius;
      y = h;
      z = Math.sin(theta) * baseRadius;
      nx = Math.cos(theta); ny = 0; nz = Math.sin(theta);
      
    } else if (distributionType === 'torus') {
      // Distribute on torus surface
      const theta = Math.random() * Math.PI * 2; // Around the ring
      const phi = Math.random() * Math.PI * 2;   // Around the tube
      const ringR = baseRadius;
      const tubeR = torusInnerRadius;
      x = (ringR + tubeR * Math.cos(phi)) * Math.cos(theta);
      y = tubeR * Math.sin(phi);
      z = (ringR + tubeR * Math.cos(phi)) * Math.sin(theta);
      // Normal for torus
      nx = Math.cos(phi) * Math.cos(theta);
      ny = Math.sin(phi);
      nz = Math.cos(phi) * Math.sin(theta);
      
    } else if (distributionType === 'plane') {
      // Distribute on a flat plane (XZ)
      x = (Math.random() - 0.5) * planeWidth;
      y = 0;
      z = (Math.random() - 0.5) * planeDepth;
      nx = 0; ny = 1; nz = 0;
      
    } else if (distributionType === 'grid') {
      // Distribute in a 3D grid
      const gridSize = Math.ceil(Math.cbrt(count));
      const ix = i % gridSize;
      const iy = Math.floor(i / gridSize) % gridSize;
      const iz = Math.floor(i / (gridSize * gridSize));
      const spacing = baseRadius * 2 / (gridSize - 1 || 1);
      x = (ix - gridSize / 2) * spacing;
      y = (iy - gridSize / 2) * spacing;
      z = (iz - gridSize / 2) * spacing;
      // Random normal for grid
      const rn = Math.random() * 6;
      if (rn < 1) { nx = 1; } else if (rn < 2) { nx = -1; }
      else if (rn < 3) { ny = 1; } else if (rn < 4) { ny = -1; }
      else if (rn < 5) { nz = 1; } else { nz = -1; }
      
    } else if (distributionType === 'ring') {
      // Distribute on a ring (circle)
      const theta = (i / count) * Math.PI * 2;
      x = Math.cos(theta) * baseRadius;
      y = 0;
      z = Math.sin(theta) * baseRadius;
      nx = Math.cos(theta); ny = 0; nz = Math.sin(theta);
      
    } else if (distributionType === 'line') {
      // Distribute along a line
      const t = count > 1 ? i / (count - 1) : 0.5;
      const pos = (t - 0.5) * lineLength;
      if (lineAxis === 'x') { x = pos; nx = 1; }
      else if (lineAxis === 'y') { y = pos; ny = 1; }
      else { z = pos; nz = 1; }
      
    } else if (distributionType === 'helix') {
      // Distribute along a helix
      const turns = config.helixTurns ?? 3;
      const t = i / count;
      const theta = t * Math.PI * 2 * turns;
      const h = (t - 0.5) * cylinderHeight;
      x = Math.cos(theta) * baseRadius;
      y = h;
      z = Math.sin(theta) * baseRadius;
      nx = Math.cos(theta); ny = 0; nz = Math.sin(theta);
      
    } else if (distributionType === 'spiral') {
      // Distribute in a flat spiral
      const t = i / count;
      const theta = t * Math.PI * 2 * (config.spiralTurns ?? 5);
      const r = t * baseRadius;
      x = Math.cos(theta) * r;
      y = 0;
      z = Math.sin(theta) * r;
      nx = 0; ny = 1; nz = 0;
    }
    
    offsets[i * 3] = x;
    offsets[i * 3 + 1] = y;
    offsets[i * 3 + 2] = z;
    
    // Store computed normal
    normals[i * 3] = nx;
    normals[i * 3 + 1] = ny;
    normals[i * 3 + 2] = nz;
    
    // Scale with variation
    scales[i] = 0.5 + Math.random() * sizeVariation + 0.5;
    
    // Random phase for animation variety
    phases[i] = Math.random() * Math.PI * 2;
    
    // Random rotation axis
    const rx = Math.random() - 0.5;
    const ry = Math.random() - 0.5;
    const rz = Math.random() - 0.5;
    const rlen = Math.sqrt(rx*rx + ry*ry + rz*rz) || 1;
    rotAxes[i * 3] = rx / rlen;
    rotAxes[i * 3 + 1] = ry / rlen;
    rotAxes[i * 3 + 2] = rz / rlen;
    
    // Random rotation speed
    rotSpeeds[i] = 0.3 + Math.random() * 1.4;
  }
  
  // Add instance attributes
  instancedMesh.geometry.setAttribute('instanceOffset', new THREE.InstancedBufferAttribute(offsets, 3));
  instancedMesh.geometry.setAttribute('instanceScale', new THREE.InstancedBufferAttribute(scales, 1));
  instancedMesh.geometry.setAttribute('instancePhase', new THREE.InstancedBufferAttribute(phases, 1));
  instancedMesh.geometry.setAttribute('instanceNormal', new THREE.InstancedBufferAttribute(normals, 3));
  instancedMesh.geometry.setAttribute('instanceRotAxis', new THREE.InstancedBufferAttribute(rotAxes, 3));
  instancedMesh.geometry.setAttribute('instanceRotSpeed', new THREE.InstancedBufferAttribute(rotSpeeds, 1));
  instancedMesh.geometry.setAttribute('instanceIndex', new THREE.InstancedBufferAttribute(indices, 1));
  
  // Set identity matrices
  const dummy = new THREE.Matrix4();
  for (let i = 0; i < count; i++) {
    dummy.identity();
    instancedMesh.setMatrixAt(i, dummy);
  }
  instancedMesh.instanceMatrix.needsUpdate = true;
  
  instancedMesh.frustumCulled = false;
  instancedMesh.castShadow = true;
  instancedMesh.receiveShadow = true;
  scene.add(instancedMesh);
  
  console.log(`Sphere cloner: placed ${count} clones on ${distributionType} radius ${baseRadius}`);
  
  // Handle custom clone geometry loading (async)
  if (cloneType === 'custom' && config.customCloneModel) {
    loadCustomCloneModel(config.customCloneModel, cloneRadius).then(modelResult => {
      // Check if model has animations - use animated clones approach
      if (modelResult.hasAnimations && modelResult.scene && modelResult.animations.length > 0) {
        console.log('[Cloner] Animated model detected - switching to individual clone approach');
        
        // Remove the instanced mesh since we'll use individual clones
        scene.remove(instancedMesh);
        instancedMesh.geometry.dispose();
        instancedMesh.material.dispose();
        
        // Mark the instancedMesh as replaced (in case any code still holds reference)
        instancedMesh.userData.replacedByAnimatedCloner = true;
        
        // Generate positions for clones
        const positions = generateClonerPositions(config);
        
        // Create animated clones
        const animatedClonerGroup = createAnimatedClones(config, scene, modelResult.scene, modelResult.animations, positions);
        applySphereClonerPlacement(config, animatedClonerGroup);
        
        // Store reference on the scene for cleanup and management
        scene.userData.animatedClonerGroup = animatedClonerGroup;
        
      } else if (modelResult.geometry) {
        // Non-animated model - use standard instanced approach
        const newGeo = modelResult.geometry;
        // Copy instance attributes to new geometry
        const attrs = ['instanceOffset', 'instanceScale', 'instancePhase', 'instanceNormal', 'instanceRotAxis', 'instanceRotSpeed', 'instanceIndex'];
        attrs.forEach(attr => {
          const oldAttr = instancedMesh.geometry.getAttribute(attr);
          if (oldAttr) newGeo.setAttribute(attr, oldAttr);
        });
        instancedMesh.geometry.dispose();
        instancedMesh.geometry = newGeo;
        console.log('[Cloner] Custom clone geometry loaded successfully');
      }
    }).catch(err => console.warn('[Cloner] Failed to load custom clone model:', err));
  }
  
  // Handle custom distribution model loading (async)
  if (distributionType === 'custom' && config.customDistModel) {
    loadCustomDistributionSurface(config.customDistModel, baseRadius, count, instancedMesh).then(result => {
      if (result && instancedMesh.geometry) {
        // Update instance attributes with new positions from custom surface
        const offAttr = instancedMesh.geometry.getAttribute('instanceOffset');
        const normAttr = instancedMesh.geometry.getAttribute('instanceNormal');
        if (offAttr && normAttr && result.positions && result.normals) {
          for (let i = 0; i < count && i < result.positions.length / 3; i++) {
            offAttr.setXYZ(i, result.positions[i * 3], result.positions[i * 3 + 1], result.positions[i * 3 + 2]);
            normAttr.setXYZ(i, result.normals[i * 3], result.normals[i * 3 + 1], result.normals[i * 3 + 2]);
          }
          offAttr.needsUpdate = true;
          normAttr.needsUpdate = true;
          console.log('[Cloner] Custom distribution surface loaded successfully');
        }
      }
    }).catch(err => console.warn('[Cloner] Failed to load custom distribution model:', err));
  }
  
  return instancedMesh;
}
