import { applyModelTransform } from './object-transforms.js';

function loadWith(Loader, path) {
  return new Promise((resolve, reject) => {
    const loader = new Loader();
    loader.load(path, resolve, undefined, reject);
  });
}

function standardizeMaterial(THREE, material) {
  const replacement = new THREE.MeshStandardMaterial({
    color: material.color ? material.color.clone() : new THREE.Color(0xcccccc),
    map: material.map || null,
    metalness: 0.3,
    roughness: 0.7,
  });
  material.dispose?.();
  return replacement;
}

function standardizeObjectMaterials(THREE, root, arrayMaterials) {
  root.traverse(child => {
    if (!child.isMesh) return;
    child.material = arrayMaterials && Array.isArray(child.material)
      ? child.material.map(material => standardizeMaterial(THREE, material))
      : standardizeMaterial(THREE, child.material);
  });
}

function startAnimations(THREE, mesh, clips, animationMixers, isPlaying, label) {
  if (!clips?.length) return;
  const mixer = new THREE.AnimationMixer(mesh);
  clips.forEach(clip => mixer.clipAction(clip).play());
  if (!isPlaying()) mixer.timeScale = 0;
  animationMixers.set(mesh, mixer);
  mesh.userData.hasAnimations = true;
  mesh.userData.animationClips = clips;
  console.log(`[Scene] ${label} model has ${clips.length} animation(s)`);
}

async function loadEnvironmentMap(THREE, path) {
  if (!path) return null;
  return new Promise(resolve => {
    new THREE.TextureLoader().load(path, texture => {
      texture.mapping = THREE.EquirectangularReflectionMapping;
      resolve(texture);
    }, undefined, () => resolve(null));
  });
}

async function applyMaterialOverride(THREE, mesh, objData, objIndex) {
  const override = objData.material;
  const envMap = await loadEnvironmentMap(THREE, override.envMap);
  mesh.traverse(child => {
    if (!child.isMesh) return;
    const material = new THREE.MeshStandardMaterial({
      color: override.color
        ? new THREE.Color(override.color)
        : (child.material.color || new THREE.Color(0xcccccc)),
      metalness: override.metalness ?? 0.5,
      roughness: override.roughness ?? 0.5,
      envMap,
      envMapIntensity: override.envMapIntensity ?? 1.0,
    });
    if (child.material.map && !override.color) material.map = child.material.map;
    if (override.emissive) {
      material.emissive = new THREE.Color(override.emissive);
      material.emissiveIntensity = override.emissiveIntensity ?? 1.0;
    }
    if (override.normalMap && child.material.normalMap) material.normalMap = child.material.normalMap;
    child.material = material;
    child.userData.objIndex = objIndex;
    child.userData.objName = objData.name;
  });
}

function annotateChildMeshes(mesh, objData, objIndex) {
  mesh.traverse(child => {
    if (!child.isMesh) return;
    child.userData.objIndex = objIndex;
    child.userData.objName = objData.name;
  });
}

export async function loadSceneModel(THREE, objData, objIndex, dependencies) {
  const {
    GLTFLoader,
    OBJLoader,
    FBXLoader,
    STLLoader,
    animationMixers,
    isPlaying,
  } = dependencies;
  const extension = objData.file.split('.').pop().toLowerCase();
  let mesh;

  if (extension === 'obj') {
    mesh = await loadWith(OBJLoader, objData.file);
    mesh.userData.isOBJ = true;
    standardizeObjectMaterials(THREE, mesh, false);
  } else if (extension === 'fbx') {
    mesh = await loadWith(FBXLoader, objData.file);
    mesh.userData.isFBX = true;
    startAnimations(THREE, mesh, mesh.animations, animationMixers, isPlaying, 'FBX');
    standardizeObjectMaterials(THREE, mesh, true);
  } else if (extension === 'stl') {
    const geometry = await loadWith(STLLoader, objData.file);
    mesh = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial({
      color: 0xcccccc,
      metalness: 0.3,
      roughness: 0.7,
    }));
    mesh.userData.isSTL = true;
  } else {
    const gltf = await loadWith(GLTFLoader, objData.file);
    mesh = gltf.scene;
    mesh.userData.isGLTF = true;
    mesh.userData.animation = objData.animation;
    if (!objData.preserveOriginalMaterial && objData.file.includes('/assets/')) {
      if (['glb', 'gltf', 'fbx'].includes(extension)) {
        objData.preserveOriginalMaterial = true;
        console.log(`[Scene] Auto-detected uploaded 3D model, preserving original materials: ${objData.file}`);
      }
    }
    startAnimations(THREE, mesh, gltf.animations, animationMixers, isPlaying, 'GLTF');
  }

  mesh.name = objData.name || `model-${objIndex}`;
  mesh.userData.objIndex = objIndex;
  mesh.userData.objName = objData.name;
  applyModelTransform(THREE, mesh, objData);

  if (objData.material && !objData.preserveOriginalMaterial) {
    await applyMaterialOverride(THREE, mesh, objData, objIndex);
  } else {
    annotateChildMeshes(mesh, objData, objIndex);
  }
  return mesh;
}
