import { loadSceneModel } from './model-loader.js';
import { applyPrimitiveTransform, createPrimitiveGeometry } from './object-transforms.js';

export async function createSceneObjects(THREE, scene, objects, dependencies) {
  const {
    GLTFLoader,
    OBJLoader,
    FBXLoader,
    STLLoader,
    animationMixers,
    isPlaying,
    isVideoFile,
    documentTarget = document,
  } = dependencies;
  for (let objIndex = 0; objIndex < objects.length; objIndex++) {
    const objData = objects[objIndex];
    let geometry, material, mesh;
    geometry = createPrimitiveGeometry(THREE, objData);
    
    // Handle 3D model files (GLTF, GLB, OBJ, FBX, STL)
    if (objData.geometry === 'gltf' && objData.file) {
      try {
        mesh = await loadSceneModel(THREE, objData, objIndex, {
          GLTFLoader,
          OBJLoader,
          FBXLoader,
          STLLoader,
          animationMixers,
          isPlaying,
        });
        scene.add(mesh);
        console.log(`Loaded GLTF: ${objData.file}`);
        continue;
      } catch (err) {
        console.warn(`Failed to load GLTF ${objData.file}:`, err);
        geometry = new THREE.BoxGeometry(1, 1, 1);
      }
    }
  
    // Material (support both flat and nested material properties)
    const matData = typeof objData.material === 'object' ? objData.material : {};
    const matType = matData.type || (typeof objData.material === 'string' ? objData.material : 'standard');
    const matColor = new THREE.Color(matData.color || objData.color || 0xcccccc);
    const emissiveColor = matData.emissive || objData.emissive;
    const matProps = {
      color: matColor,
      metalness: matData.metalness ?? objData.metalness ?? 0.5,
      roughness: matData.roughness ?? objData.roughness ?? 0.5,
      wireframe: matData.wireframe || objData.wireframe || false,
      transparent: matData.transparent || objData.transparent || false,
      opacity: matData.opacity ?? objData.opacity ?? 1,
      side: matData.side === 'back' ? THREE.BackSide : (matData.side === 'double' ? THREE.DoubleSide : THREE.FrontSide),
      flatShading: matData.flatShading || objData.flatShading || false,
      envMapIntensity: matData.envMapIntensity ?? objData.envMapIntensity ?? 1.0,
    };
    
    // Additional numeric properties
    if (matData.normalScale !== undefined) matProps.normalScale = new THREE.Vector2(matData.normalScale, matData.normalScale);
    if (matData.bumpScale !== undefined) matProps.bumpScale = matData.bumpScale;
    if (matData.displacementScale !== undefined) matProps.displacementScale = matData.displacementScale;
    if (matData.displacementBias !== undefined) matProps.displacementBias = matData.displacementBias;
    if (matData.aoMapIntensity !== undefined) matProps.aoMapIntensity = matData.aoMapIntensity;
    if (matData.lightMapIntensity !== undefined) matProps.lightMapIntensity = matData.lightMapIntensity;
    
    // Only add emissive if defined (Three.js doesn't like undefined values)
    if (emissiveColor) {
      matProps.emissive = new THREE.Color(emissiveColor);
      matProps.emissiveIntensity = matData.emissiveIntensity ?? objData.emissiveIntensity ?? 1;
    }
  
    // Check if displacement is enabled
    const displacement = matData.displacement || objData.displacement;
    
    if (displacement) {
      // Create displacement shader material
      material = new THREE.ShaderMaterial({
        uniforms: {
          uTime: { value: 0 },
          uColor: { value: matColor },
          uDisplacementStrength: { value: displacement.strength || 0.5 },
          uDisplacementFrequency: { value: displacement.frequency || 2.0 },
          uDisplacementSpeed: { value: displacement.speed || 0.5 },
          uNoiseScale: { value: displacement.noiseScale || 1.0 },
        },
        vertexShader: `
          uniform float uTime;
          uniform float uDisplacementStrength;
          uniform float uDisplacementFrequency;
          uniform float uDisplacementSpeed;
          uniform float uNoiseScale;
          
          varying vec3 vNormal;
          varying vec3 vPosition;
          varying vec2 vUv;
          
          // Simplex-like noise function
          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)));
          }
          
    void main() {
            vUv = uv;
            vNormal = normal;
            
            // Calculate displacement using layered noise
            vec3 noisePos = position * uNoiseScale * uDisplacementFrequency;
            noisePos.z += uTime * uDisplacementSpeed;
            
            // Layer multiple octaves for more organic look
            float noise1 = snoise(noisePos) * 0.5;
            float noise2 = snoise(noisePos * 2.0 + 100.0) * 0.25;
            float noise3 = snoise(noisePos * 4.0 + 200.0) * 0.125;
            float totalNoise = noise1 + noise2 + noise3;
            
            // Add gentle sine waves for flowing effect
            float wave1 = sin(position.y * uDisplacementFrequency * 3.0 + uTime * uDisplacementSpeed * 2.0) * 0.3;
            float wave2 = sin(position.y * uDisplacementFrequency * 5.0 - uTime * uDisplacementSpeed * 1.5 + 2.0) * 0.15;
            
            float displacement = (totalNoise + wave1 + wave2) * uDisplacementStrength;
            
            // Displace along normal
            vec3 newPosition = position + normal * displacement;
            vPosition = newPosition;
            
            gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
          }
        `,
        fragmentShader: `
          uniform vec3 uColor;
          uniform float uTime;
          
          varying vec3 vNormal;
          varying vec3 vPosition;
          varying vec2 vUv;
  
  void main() {
            // Simple lighting
            vec3 lightDir = normalize(vec3(0.5, 1.0, 0.3));
            float diff = max(dot(normalize(vNormal), lightDir), 0.0) * 0.5 + 0.5;
            
            vec3 finalColor = uColor * diff;
            
            gl_FragColor = vec4(finalColor, 1.0);
          }
        `,
        side: matProps.side,
        wireframe: matProps.wireframe,
      });
      
      // Store displacement settings for UI updates
      mesh = new THREE.Mesh(geometry, material);
      mesh.userData.displacement = displacement;
      mesh.userData.displacementMaterial = material;
    } else {
      switch (matType) {
        case 'basic':
          material = new THREE.MeshBasicMaterial({ 
            color: matColor, 
            wireframe: matProps.wireframe,
            transparent: matProps.transparent,
            opacity: matProps.opacity,
            side: matProps.side
          });
          break;
        case 'phong':
          material = new THREE.MeshPhongMaterial({ 
            color: matColor, 
            shininess: matData.shininess || objData.shininess || 30,
            side: matProps.side
          });
          break;
        case 'lambert':
          material = new THREE.MeshLambertMaterial({ 
            color: matColor,
            side: matProps.side
          });
          break;
        case 'standard':
        default:
          material = new THREE.MeshStandardMaterial(matProps);
          break;
      }
      mesh = new THREE.Mesh(geometry, material);
    }
    
    applyPrimitiveTransform(mesh, objData);
    mesh.castShadow = objData.castShadow ?? true;
    mesh.receiveShadow = objData.receiveShadow ?? true;
    mesh.visible = objData.visible !== false; // Default to visible
    // Preserve existing userData (like displacement) while adding original data
    // Also store the object index for reliable lookup
    mesh.userData = { ...mesh.userData, ...objData, objIndex };
    
    // Load any textures defined in material data
    if (mesh.material && mesh.material.isMeshStandardMaterial) {
      const texLoader = new THREE.TextureLoader();
      const loadTex = (path, prop, isEnvMap = false) => {
        if (!path) return;
        const fullPath = path.startsWith('/') ? path : '/' + path;
        
        // Check if it's a video file
        if (isVideoFile(path)) {
          const video = documentTarget.createElement('video');
          video.src = fullPath;
          video.crossOrigin = 'anonymous';
          video.loop = true;
          video.muted = true;
          video.playsInline = true;
          video.preload = 'auto';
          
          video.onloadeddata = () => {
            const tex = new THREE.VideoTexture(video);
            tex.minFilter = THREE.LinearFilter;
            tex.magFilter = THREE.LinearFilter;
            tex.format = THREE.RGBAFormat;
            if (isEnvMap) {
              tex.mapping = THREE.EquirectangularReflectionMapping;
            } else {
              tex.wrapS = THREE.RepeatWrapping;
              tex.wrapT = THREE.RepeatWrapping;
            }
            mesh.material[prop] = tex;
            mesh.material.needsUpdate = true;
            video.play().catch(err => console.warn('Video autoplay blocked:', err));
            
            // Store video for cleanup
            if (!mesh.userData.videoTextures) mesh.userData.videoTextures = {};
            mesh.userData.videoTextures[prop] = video;
          };
          video.onerror = () => console.warn(`Failed to load video texture ${path}`);
          video.load();
        } else {
          // Load regular image texture
          texLoader.load(
            fullPath,
            (tex) => {
              if (isEnvMap) {
                tex.mapping = THREE.EquirectangularReflectionMapping;
              } else {
                tex.wrapS = THREE.RepeatWrapping;
                tex.wrapT = THREE.RepeatWrapping;
              }
              mesh.material[prop] = tex;
              mesh.material.needsUpdate = true;
            },
            undefined,
            (err) => console.warn(`Failed to load texture ${path}:`, err)
          );
        }
      };
      
      // Load all texture types from material data
      loadTex(matData.map, 'map');
      loadTex(matData.normalMap, 'normalMap');
      loadTex(matData.roughnessMap, 'roughnessMap');
      loadTex(matData.metalnessMap, 'metalnessMap');
      loadTex(matData.aoMap, 'aoMap');
      loadTex(matData.emissiveMap, 'emissiveMap');
      loadTex(matData.bumpMap, 'bumpMap');
      loadTex(matData.displacementMap, 'displacementMap');
      loadTex(matData.lightMap, 'lightMap');
      loadTex(matData.alphaMap, 'alphaMap');
      loadTex(matData.envMap, 'envMap', true);
    }
  
    scene.add(mesh);
  }
}
