import { useEffect, useRef, useState } from "react";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader.js";
import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader.js";
import { STLLoader } from "three/examples/jsm/loaders/STLLoader.js";
import { PLYLoader } from "three/examples/jsm/loaders/PLYLoader.js";
import { ColladaLoader } from "three/examples/jsm/loaders/ColladaLoader.js";
import { ThreeMFLoader } from "three/examples/jsm/loaders/3MFLoader.js";
import { RoomEnvironment } from "three/examples/jsm/environments/RoomEnvironment.js";
import { p4 } from "./p4";
import { t } from "./i18n";

type Api = { toggleWire: (b: boolean) => void; toggleGrid: (b: boolean) => void; reset: () => void };

export default function ModelViewer({ depot, name, spec, bytes, forceExt }: { depot: string; name: string; spec?: string; bytes?: ArrayBuffer; forceExt?: string }) {
  const mount = useRef<HTMLDivElement>(null);
  const api = useRef<Api | null>(null);
  const [status, setStatus] = useState<"loading" | "ok" | "error">("loading");
  const [err, setErr] = useState("");
  const [stats, setStats] = useState({ tris: 0, verts: 0, meshes: 0, mats: 0 });
  const [wire, setWire] = useState(false);
  const [grid, setGrid] = useState(true);

  useEffect(() => {
    let disposed = false;
    let raf = 0;
    const el = mount.current!;
    setStatus("loading"); setErr("");

    const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, powerPreference: "high-performance" });
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    renderer.setSize(el.clientWidth || 600, el.clientHeight || 400);
    renderer.toneMapping = THREE.ACESFilmicToneMapping;
    renderer.toneMappingExposure = 1.05;
    renderer.outputColorSpace = THREE.SRGBColorSpace;
    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    el.appendChild(renderer.domElement);

    const scene = new THREE.Scene();
    // image-based lighting (soft studio environment) — this is what makes PBR look good
    const pmrem = new THREE.PMREMGenerator(renderer);
    scene.environment = pmrem.fromScene(new RoomEnvironment(), 0.04).texture;

    const camera = new THREE.PerspectiveCamera(50, 1, 0.01, 100000);

    // key light with shadows + soft fill
    const key = new THREE.DirectionalLight(0xffffff, 2.2);
    key.position.set(5, 8, 6);
    key.castShadow = true;
    key.shadow.mapSize.set(2048, 2048);
    key.shadow.bias = -0.0004;
    scene.add(key);
    scene.add(new THREE.DirectionalLight(0x8b7bf7, 0.35).translateX(-6));

    const controls = new OrbitControls(camera, renderer.domElement);
    controls.enableDamping = true; controls.dampingFactor = 0.08;
    controls.autoRotate = true; controls.autoRotateSpeed = 1.1;
    renderer.domElement.addEventListener("pointerdown", () => (controls.autoRotate = false));

    // ground + grid (added after fit, sized to model)
    const ground = new THREE.Mesh(
      new THREE.PlaneGeometry(1, 1),
      new THREE.ShadowMaterial({ opacity: 0.28 })
    );
    ground.rotation.x = -Math.PI / 2; ground.receiveShadow = true; scene.add(ground);
    let gridHelper: THREE.GridHelper | null = null;
    let home = { pos: new THREE.Vector3(3, 2, 3), tgt: new THREE.Vector3() };

    const ext = (forceExt || name.toLowerCase().split(".").pop() || "");

    function collectStats(obj: THREE.Object3D) {
      let tris = 0, verts = 0, meshes = 0; const mats = new Set<THREE.Material>();
      obj.traverse((c) => {
        const m = c as THREE.Mesh;
        if ((m as unknown as { isMesh?: boolean }).isMesh && m.geometry) {
          meshes++; const pos = m.geometry.getAttribute("position");
          verts += pos ? pos.count : 0;
          tris += m.geometry.index ? m.geometry.index.count / 3 : (pos ? pos.count / 3 : 0);
          (Array.isArray(m.material) ? m.material : [m.material]).forEach((x) => x && mats.add(x));
        }
      });
      setStats({ tris: Math.round(tris), verts, meshes, mats: mats.size });
    }

    function place(obj: THREE.Object3D) {
      obj.traverse((c) => {
        const m = c as THREE.Mesh;
        if ((m as unknown as { isMesh?: boolean }).isMesh) {
          m.castShadow = true; m.receiveShadow = true;
          const has = m.material && !(Array.isArray(m.material) && m.material.length === 0);
          if (!has) m.material = new THREE.MeshStandardMaterial({ color: 0x9a8bf9, roughness: 0.5, metalness: 0.05 });
        }
      });
      const box = new THREE.Box3().setFromObject(obj);
      const size = box.getSize(new THREE.Vector3());
      const center = box.getCenter(new THREE.Vector3());
      const maxDim = Math.max(size.x, size.y, size.z) || 1;
      obj.position.sub(center);
      obj.position.y += size.y / 2; // sit on the ground
      scene.add(obj);

      // ground + grid
      ground.scale.set(maxDim * 8, maxDim * 8, 1);
      gridHelper = new THREE.GridHelper(maxDim * 6, 30, 0x7c6ef6, 0x333344);
      (gridHelper.material as THREE.Material).transparent = true;
      (gridHelper.material as THREE.Material).opacity = 0.35;
      scene.add(gridHelper); gridHelper.visible = grid;

      // shadow camera frustum
      const s = maxDim * 2.5;
      Object.assign(key.shadow.camera, { left: -s, right: s, top: s, bottom: -s, near: 0.1, far: maxDim * 40 });
      key.shadow.camera.updateProjectionMatrix();

      const dist = maxDim * 2.1;
      home = { pos: new THREE.Vector3(dist * 0.8, dist * 0.65, dist), tgt: new THREE.Vector3(0, size.y / 2, 0) };
      camera.position.copy(home.pos);
      camera.near = maxDim / 200; camera.far = maxDim * 200; camera.updateProjectionMatrix();
      controls.target.copy(home.tgt); controls.maxDistance = maxDim * 25; controls.update();

      collectStats(obj);
      setStatus("ok");

      // expose controls to React buttons
      api.current = {
        toggleWire: (b) => obj.traverse((c) => {
          const m = c as THREE.Mesh; if (!(m as unknown as { isMesh?: boolean }).isMesh) return;
          (Array.isArray(m.material) ? m.material : [m.material]).forEach((mt) => { const s = mt as THREE.MeshStandardMaterial; if (s) s.wireframe = b; });
        }),
        toggleGrid: (b) => { if (gridHelper) gridHelper.visible = b; },
        reset: () => { camera.position.copy(home.pos); controls.target.copy(home.tgt); controls.update(); controls.autoRotate = false; },
      };
    }

    function fail(e: unknown) { if (!disposed) { setErr(String((e as { message?: string })?.message || e)); setStatus("error"); } }

    (async () => {
      try {
        // pre-supplied bytes (e.g. an FBX exported from Unreal) skip the p4 fetch
        const buf = bytes ? bytes : spec ? await p4.printDepot(spec) : await p4.readDepot(depot);
        if (disposed) return;
        const manager = new THREE.LoadingManager();
        if (ext === "glb" || ext === "gltf") new GLTFLoader(manager).parse(buf, "", (g) => !disposed && place(g.scene), fail);
        else if (ext === "obj") place(new OBJLoader(manager).parse(new TextDecoder().decode(buf)));
        else if (ext === "fbx") place(new FBXLoader(manager).parse(buf, ""));
        else if (ext === "dae") { const c = new ColladaLoader(manager).parse(new TextDecoder().decode(buf), ""); if (c && c.scene) place(c.scene); else fail(t("Collada: empty scene")); }
        else if (ext === "3mf") place(new ThreeMFLoader(manager).parse(buf));
        else if (ext === "stl") place(new THREE.Mesh(new STLLoader().parse(buf), new THREE.MeshStandardMaterial({ color: 0x9a8bf9, roughness: 0.5 })));
        else if (ext === "ply") { const g = new PLYLoader().parse(buf); g.computeVertexNormals(); place(new THREE.Mesh(g, new THREE.MeshStandardMaterial({ color: 0x9a8bf9, roughness: 0.5, vertexColors: !!g.getAttribute("color") }))); }
        else { setErr(t("Format not supported by the viewer")); setStatus("error"); }
      } catch (e) { fail(e); }
    })();

    const animate = () => { controls.update(); renderer.render(scene, camera); raf = requestAnimationFrame(animate); };
    animate();

    const onResize = () => {
      const w = el.clientWidth || 600, h = el.clientHeight || 400;
      renderer.setSize(w, h); camera.aspect = w / h; camera.updateProjectionMatrix();
    };
    const ro = new ResizeObserver(onResize); ro.observe(el);

    return () => {
      disposed = true; cancelAnimationFrame(raf); ro.disconnect(); controls.dispose(); pmrem.dispose();
      scene.traverse((o) => {
        const m = o as THREE.Mesh;
        if (m.geometry) m.geometry.dispose();
        const mat = m.material; if (mat) (Array.isArray(mat) ? mat : [mat]).forEach((x) => x && x.dispose());
      });
      renderer.dispose();
      if (renderer.domElement.parentNode === el) el.removeChild(renderer.domElement);
      api.current = null;
    };
    // eslint-disable-next-line
  }, [depot, name, spec, bytes, forceExt]);

  const fmt = (n: number) => n.toLocaleString("ru");
  return (
    <div className="asset">
      <div className="stage" style={{ padding: 0, position: "relative" }}>
        <div ref={mount} style={{ position: "absolute", inset: 0 }} />
        <span className="lbl2">
          <svg viewBox="0 0 24 24" fill="none"><path d="M12 2 3 7v10l9 5 9-5V7l-9-5Z" stroke="currentColor" strokeWidth="1.6" strokeLinejoin="round" /><path d="m3 7 9 5 9-5" stroke="currentColor" strokeWidth="1.6" /></svg>
          3D · {(name.split(".").pop() || "").toUpperCase()}
        </span>
        {status === "ok" && (
          <div className="v3d-tools">
            <button className={"v3d-btn" + (wire ? " on" : "")} onClick={() => { setWire(!wire); api.current?.toggleWire(!wire); }} title={t("Wireframe")}>◫</button>
            <button className={"v3d-btn" + (grid ? " on" : "")} onClick={() => { setGrid(!grid); api.current?.toggleGrid(!grid); }} title={t("Grid")}>▦</button>
            <button className="v3d-btn" onClick={() => api.current?.reset()} title={t("Reset view")}>⟲</button>
          </div>
        )}
        {status === "loading" && <div className="viewer-msg"><span className="ldr" />{t("Loading model…")}</div>}
        {status === "error" && <div className="viewer-msg err">{err}</div>}
        {status === "ok" && <span className="hint">{t("drag — rotate · wheel — zoom · right-click — pan")}</span>}
      </div>
      <div className="assetmeta">
        <div className="mi"><span className="k">{t("Triangles")}</span><span className="v">{fmt(stats.tris)}</span></div>
        <div className="mi"><span className="k">{t("Vertices")}</span><span className="v">{fmt(stats.verts)}</span></div>
        <div className="mi"><span className="k">{t("Meshes · Materials")}</span><span className="v">{stats.meshes} · {stats.mats}</span></div>
        <div className="mi"><span className="k">{t("Render")}</span><span className="v acc">three.js · PBR</span></div>
      </div>
    </div>
  );
}
