import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

const loader = new GLTFLoader();
const cache = new Map<string, Promise<THREE.Group>>();

/**
 * Load a generated GLB and make it usable without touching the file.
 *
 * Meshes that came out of an image-to-3D pipeline arrive at an arbitrary scale,
 * with an arbitrary pivot, sitting wherever the mesher felt like — so every one
 * is normalised on load: recentre on its own bounding box, scale so the longest
 * axis is `size`, and drop it so it sits on y=0. Hand-fixing the assets instead
 * would mean re-fixing them every time they're regenerated.
 */
export function loadProp(url: string, size: number): Promise<THREE.Group> {
  const key = `${url}@${size}`;
  let p = cache.get(key);
  if (!p) {
    p = loader.loadAsync(url).then((gltf) => {
      const root = gltf.scene;
      const box = new THREE.Box3().setFromObject(root);
      const dim = box.getSize(new THREE.Vector3());
      const centre = box.getCenter(new THREE.Vector3());
      const longest = Math.max(dim.x, dim.y, dim.z) || 1;
      const s = size / longest;

      // Wrap it: scaling the loaded scene directly fights whatever transforms
      // the exporter already baked into it.
      const wrap = new THREE.Group();
      root.position.set(-centre.x, -box.min.y, -centre.z);
      root.scale.setScalar(1);
      const inner = new THREE.Group();
      inner.add(root);
      inner.scale.setScalar(s);
      wrap.add(inner);

      root.traverse((o) => {
        if ((o as THREE.Mesh).isMesh) {
          o.castShadow = true;
          o.receiveShadow = true;
        }
      });
      return wrap;
    });
    cache.set(key, p);
  }
  return p;
}
