import { useEffect, useRef } from "react";
import * as THREE from "three";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { OBJLoader } from "three/addons/loaders/OBJLoader.js";
import { FBXLoader } from "three/addons/loaders/FBXLoader.js";
import { PLYLoader } from "three/addons/loaders/PLYLoader.js";
import { STLLoader } from "three/addons/loaders/STLLoader.js";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";

export default function Viewer({ url, ext }) {
  const mountRef = useRef(null);

  useEffect(() => {
    const mount = mountRef.current;
    if (!mount || !url) return;
    const scene = new THREE.Scene();
    scene.background = new THREE.Color(0x101014);
    const camera = new THREE.PerspectiveCamera(50, 1, 0.01, 1000);
    const renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.outputColorSpace = THREE.SRGBColorSpace;
    mount.appendChild(renderer.domElement);

    const resize = () => {
      const w = mount.clientWidth, h = mount.clientHeight;
      renderer.setSize(w, h);
      renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
      camera.aspect = w / h;
      camera.updateProjectionMatrix();
    };
    resize();
    const ro = new ResizeObserver(resize);
    ro.observe(mount);

    scene.add(new THREE.HemisphereLight(0xffffff, 0x334, 1.2));
    const dir = new THREE.DirectionalLight(0xffffff, 2.0);
    dir.position.set(3, 5, 4);
    scene.add(dir);
    const grid = new THREE.GridHelper(10, 20, 0x333344, 0x22222c);
    scene.add(grid);

    const controls = new OrbitControls(camera, renderer.domElement);
    controls.enableDamping = true;

    let mixer = null;
    const clock = new THREE.Clock();

    const frame = (obj) => {
      const box = new THREE.Box3().setFromObject(obj);
      const size = box.getSize(new THREE.Vector3()).length() || 1;
      const center = box.getCenter(new THREE.Vector3());
      obj.position.sub(center);
      obj.position.y += (box.getSize(new THREE.Vector3()).y / 2);
      camera.position.set(size * 0.7, size * 0.5, size * 0.9);
      controls.target.set(0, box.getSize(new THREE.Vector3()).y / 4, 0);
      camera.near = size / 100;
      camera.far = size * 20;
      camera.updateProjectionMatrix();
    };

    const addDefaultMaterial = (geometry) => {
      const hasColor = !!geometry.attributes.color;
      const mat = new THREE.MeshStandardMaterial({
        color: hasColor ? 0xffffff : 0x8899bb,
        vertexColors: hasColor,
        roughness: 0.7,
      });
      const mesh = new THREE.Mesh(geometry, mat);
      scene.add(mesh);
      frame(mesh);
    };

    const onLoad = (obj) => {
      scene.add(obj.scene || obj);
      const target = obj.scene || obj;
      if (obj.animations?.length) {
        mixer = new THREE.AnimationMixer(target);
        mixer.clipAction(obj.animations[0]).play();
      }
      frame(target);
    };
    const err = (e) => console.error("viewer load error", e);

    if (ext === "glb" || ext === "gltf") new GLTFLoader().load(url, onLoad, undefined, err);
    else if (ext === "obj") new OBJLoader().load(url, onLoad, undefined, err);
    else if (ext === "fbx") new FBXLoader().load(url, onLoad, undefined, err);
    else if (ext === "ply") new PLYLoader().load(url, (g) => { g.computeVertexNormals(); addDefaultMaterial(g); }, undefined, err);
    else if (ext === "stl") new STLLoader().load(url, (g) => { g.computeVertexNormals(); addDefaultMaterial(g); }, undefined, err);

    let raf;
    const loop = () => {
      raf = requestAnimationFrame(loop);
      if (mixer) mixer.update(clock.getDelta());
      controls.update();
      renderer.render(scene, camera);
    };
    loop();

    return () => {
      cancelAnimationFrame(raf);
      ro.disconnect();
      renderer.dispose();
      mount.removeChild(renderer.domElement);
    };
  }, [url, ext]);

  return <div className="viewer" ref={mountRef} />;
}
