// GLB Spine — loads the uploaded GLB model via Three.js GLTFLoader
// Renders in a Three.js scene with auto-rotation + drag-to-look
const { useEffect: useEffectU, useRef: useRefU, useState: useStateU } = React;

async function mountGLBSpine({ container, palette, modelUrl, onReady, onError }) {
  const THREE = window.THREE;
  if (!THREE) throw new Error("THREE not loaded");
  const { GLTFLoader } = window.GLTF_NS || {};
  if (!GLTFLoader) throw new Error("GLTFLoader not available");

  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(35, 1, 0.01, 1000);
  camera.position.set(0, 0, 3);

  const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, preserveDrawingBuffer: true });
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  renderer.setClearColor(0x000000, 0);
  renderer.toneMapping = THREE.ACESFilmicToneMapping;
  renderer.outputColorSpace = THREE.SRGBColorSpace;
  container.appendChild(renderer.domElement);
  renderer.domElement.style.cssText = "width:100%;height:100%;display:block;cursor:grab;touch-action:none;";

  // Lighting
  const key = new THREE.DirectionalLight(0xffffff, 1.8);
  key.position.set(2, 4, 5);
  scene.add(key);
  const rim = new THREE.DirectionalLight(palette.accentLight || 0xff8a72, 1.2);
  rim.position.set(-3, 1, -2);
  scene.add(rim);
  const back = new THREE.DirectionalLight(0xffffff, 0.5);
  back.position.set(0, -3, -2);
  scene.add(back);
  scene.add(new THREE.AmbientLight(0xffffff, 0.6));

  const modelGroup = new THREE.Group();
  scene.add(modelGroup);

  let modelLoaded = false;
  const loader = new GLTFLoader();
  loader.load(
    modelUrl,
    (gltf) => {
      const model = gltf.scene;
      // Validate
      let meshCount = 0;
      model.traverse((o) => { if (o.isMesh) meshCount++; });
      if (meshCount === 0) {
        console.warn("[GLB] no meshes");
        if (onError) onError(new Error("empty_model"));
        return;
      }

      // Normalize: center & scale to fit
      const box = new THREE.Box3().setFromObject(model);
      const size = new THREE.Vector3();
      box.getSize(size);
      const center = new THREE.Vector3();
      box.getCenter(center);

      const maxDim = Math.max(size.x, size.y, size.z) || 1;
      const targetSize = 2.4;
      const scale = targetSize / maxDim;

      // Wrap in a group: center the model, then scale the group.
      // (Scaling the model directly without re-centering puts it off-screen
      //  because position translation isn't affected by scale.setScalar.)
      const wrapper = new THREE.Group();
      model.position.sub(center);
      wrapper.add(model);
      wrapper.scale.setScalar(scale);

      // Many medical/scan exports use Z-up; rotate to stand the model upright
      // and detect orientation by which axis was largest.
      const longestAxis = size.x > size.y && size.x > size.z ? 'x'
                        : size.z > size.y ? 'z' : 'y';
      if (longestAxis === 'z') wrapper.rotation.x = -Math.PI / 2;
      else if (longestAxis === 'x') wrapper.rotation.z = Math.PI / 2;

      // Tint materials toward bone color
      model.traverse((obj) => {
        if (obj.isMesh && obj.material) {
          const mats = Array.isArray(obj.material) ? obj.material : [obj.material];
          mats.forEach((m) => {
            if (m.color && palette.bone) {
              m.color.setHex(palette.bone);
            }
            if ('roughness' in m) m.roughness = 0.55;
            if ('metalness' in m) m.metalness = 0.05;
            m.needsUpdate = true;
          });
        }
      });

      modelGroup.add(wrapper);
      modelLoaded = true;
      // DEBUG: expose for inspection
      window.__spine = { scene, camera, renderer, modelGroup, model, wrapper, modelLoaded: true };
      if (onReady) onReady();
    },
    undefined,
    (err) => {
      console.error("[GLB] load error", err);
      if (onError) onError(err);
    }
  );

  // Interaction
  let rotation = { x: 0, y: 0 };
  let targetRot = { x: 0, y: 0 };
  let dragging = false;
  let lastP = { x: 0, y: 0 };
  let autoRot = true;

  function onPointerDown(e) {
    dragging = true; autoRot = false;
    lastP.x = e.clientX; lastP.y = e.clientY;
    renderer.domElement.style.cursor = "grabbing";
  }
  function onPointerMove(e) {
    if (!dragging) return;
    const dx = e.clientX - lastP.x, dy = e.clientY - lastP.y;
    targetRot.y += dx * 0.01;
    targetRot.x += dy * 0.005;
    targetRot.x = Math.max(-0.6, Math.min(0.6, targetRot.x));
    lastP.x = e.clientX; lastP.y = e.clientY;
  }
  function onPointerUp() {
    dragging = false; renderer.domElement.style.cursor = "grab";
  }
  renderer.domElement.addEventListener("pointerdown", onPointerDown);
  window.addEventListener("pointermove", onPointerMove);
  window.addEventListener("pointerup", onPointerUp);

  function resize() {
    const w = container.clientWidth, h = container.clientHeight;
    if (w === 0 || h === 0) return;
    renderer.setSize(w, h, false);
    camera.aspect = w / h;
    camera.updateProjectionMatrix();
  }
  const ro = new ResizeObserver(resize);
  ro.observe(container);
  resize();

  let raf;
  let frames = 0;
  function animate() {
    raf = requestAnimationFrame(animate);
    frames++;
    if (window.__spine) window.__spine.frames = frames;
    if (autoRot && !dragging && modelLoaded) targetRot.y += 0.005;
    rotation.x += (targetRot.x - rotation.x) * 0.08;
    rotation.y += (targetRot.y - rotation.y) * 0.08;
    modelGroup.rotation.x = rotation.x;
    modelGroup.rotation.y = rotation.y;
    renderer.render(scene, camera);
  }
  animate();

  return {
    dispose() {
      cancelAnimationFrame(raf);
      ro.disconnect();
      renderer.domElement.removeEventListener("pointerdown", onPointerDown);
      window.removeEventListener("pointermove", onPointerMove);
      window.removeEventListener("pointerup", onPointerUp);
      renderer.dispose();
      try { container.removeChild(renderer.domElement); } catch (e) {}
    },
  };
}

function GLBSpine({ palette, modelUrl, fallback }) {
  const ref = useRefU(null);
  const apiRef = useRefU(null);
  const [status, setStatus] = useStateU("loading"); // loading | ready | error

  useEffectU(() => {
    let cancelled = false;
    const tryMount = async () => {
      let waited = 0;
      while (!window.GLTF_NS?.GLTFLoader || !window.THREE) {
        if (cancelled) return;
        if (waited > 6000) { setStatus("error"); return; }
        await new Promise((r) => setTimeout(r, 100));
        waited += 100;
      }
      try {
        const api = await mountGLBSpine({
          container: ref.current,
          palette, modelUrl,
          onReady: () => { if (!cancelled) setStatus("ready"); },
          onError: () => { if (!cancelled) setStatus("error"); },
        });
        if (cancelled) api.dispose();
        else apiRef.current = api;
      } catch (e) {
        console.error(e);
        if (!cancelled) setStatus("error");
      }
    };
    tryMount();
    return () => { cancelled = true; if (apiRef.current) apiRef.current.dispose(); };
  }, [modelUrl]);

  if (status === "error" && fallback) return fallback;

  return (
    <div style={{ width: "100%", height: "100%", position: "relative" }}>
      <div ref={ref} style={{ width: "100%", height: "100%" }} />
      {status === "loading" && (
        <div style={{
          position: "absolute", inset: 0, display: "grid", placeItems: "center",
          color: palette.muted || "rgba(255,255,255,0.5)",
          fontFamily: "'JetBrains Mono', monospace", fontSize: 10,
          letterSpacing: "0.2em", textTransform: "uppercase", pointerEvents: "none",
        }}>
          <div style={{ textAlign: "center" }}>
            <div style={{ display: "flex", gap: 4, justifyContent: "center", marginBottom: 12 }}>
              {[0, 1, 2].map((i) => (
                <div key={i} style={{
                  width: 6, height: 6, borderRadius: 3,
                  background: palette.accent || "#ff6b4a",
                  animation: `pulse 1.2s ease-in-out infinite`,
                  animationDelay: `${i * 0.15}s`,
                }} />
              ))}
            </div>
            Carregando modelo 3D
          </div>
        </div>
      )}
    </div>
  );
}

window.GLBSpine = GLBSpine;

// ─────────────────────────────────────────────────────────────────────
// SPINE (GLB, interactive) — same welcome model, but pickable + highlightable.
// A single-mesh GLB has no named vertebrae, so we map vertical position to
// vertebra names by anatomical region fractions. Click → onPick(name);
// setHighlight({name:0..1}) drops glowing markers at the right heights.
// API mirrors SpineKit.mountSpine: { dispose, setHighlight, getVertebrae }.
// ─────────────────────────────────────────────────────────────────────
function buildVertebraBands() {
  // top (C1) → bottom (S1). Region spans as fraction of spine height.
  const regions = [
    { region: "cervical", names: ["C1","C2","C3","C4","C5","C6","C7"], a: 0.00, b: 0.22 },
    { region: "thoracic", names: ["T1","T2","T3","T4","T5","T6","T7","T8","T9","T10","T11","T12"], a: 0.22, b: 0.60 },
    { region: "lumbar", names: ["L1","L2","L3","L4","L5"], a: 0.60, b: 0.82 },
    { region: "sacrum", names: ["S1"], a: 0.82, b: 0.97 },
  ];
  const bands = [];
  regions.forEach(({ region, names, a, b }) => {
    names.forEach((name, i) => {
      const f = a + ((i + 0.5) / names.length) * (b - a);
      bands.push({ name, region, f });
    });
  });
  return bands;
}

async function mountSpineGLB({ container, palette, interactive = true, onPick, modelUrl = "spine-model-2.glb" }) {
  const THREE = window.THREE;
  if (!THREE) throw new Error("THREE not loaded");
  const { GLTFLoader } = window.GLTF_NS || {};
  if (!GLTFLoader) throw new Error("GLTFLoader not available");

  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(35, 1, 0.01, 1000);
  camera.position.set(0, 0, 3);

  const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, preserveDrawingBuffer: true });
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  renderer.setClearColor(0x000000, 0);
  renderer.toneMapping = THREE.ACESFilmicToneMapping;
  renderer.outputColorSpace = THREE.SRGBColorSpace;
  container.appendChild(renderer.domElement);
  renderer.domElement.style.cssText = "width:100%;height:100%;display:block;cursor:grab;touch-action:none;";

  const key = new THREE.DirectionalLight(0xffffff, 1.8); key.position.set(2, 4, 5); scene.add(key);
  const rim = new THREE.DirectionalLight(palette.accentLight || 0xff8a72, 1.2); rim.position.set(-3, 1, -2); scene.add(rim);
  const back = new THREE.DirectionalLight(0xffffff, 0.5); back.position.set(0, -3, -2); scene.add(back);
  scene.add(new THREE.AmbientLight(0xffffff, 0.6));

  const modelGroup = new THREE.Group();
  scene.add(modelGroup);

  const accentColor = new THREE.Color(palette.accent || 0xff6b4a);
  const bands = buildVertebraBands();
  const meshes = [];
  const meshInfo = [];   // { mesh, y, highlightable, orig:Color }
  let modelLoaded = false;
  let yMin = -1, yMax = 1, cx = 0, cz = 0, zFront = 0.3;

  function yForFraction(f) { return yMax - f * (yMax - yMin); }
  function fractionForY(y) {
    const f = (yMax - y) / ((yMax - yMin) || 1);
    return Math.max(0, Math.min(1, f));
  }
  function vertebraForY(y) {
    const f = fractionForY(y);
    let best = bands[0], bd = Infinity;
    bands.forEach((b) => { const d = Math.abs(b.f - f); if (d < bd) { bd = d; best = b; } });
    return best;
  }

  const loader = new GLTFLoader();
  if (window.GLTF_NS && window.GLTF_NS.dracoLoader) loader.setDRACOLoader(window.GLTF_NS.dracoLoader);
  loader.load(
    modelUrl,
    (gltf) => {
      const model = gltf.scene;
      let mc = 0;
      model.traverse((o) => { if (o.isMesh) { mc++; meshes.push(o); } });
      if (mc === 0) return;

      const box = new THREE.Box3().setFromObject(model);
      const size = new THREE.Vector3(); box.getSize(size);
      const center = new THREE.Vector3(); box.getCenter(center);

      const maxDim = Math.max(size.x, size.y, size.z) || 1;
      const scale = 2.4 / maxDim;

      const wrapper = new THREE.Group();
      model.position.sub(center);
      wrapper.add(model);
      wrapper.scale.setScalar(scale);

      const longestAxis = size.x > size.y && size.x > size.z ? "x" : size.z > size.y ? "z" : "y";
      if (longestAxis === "z") wrapper.rotation.x = -Math.PI / 2;
      else if (longestAxis === "x") wrapper.rotation.z = Math.PI / 2;

      // Tint toward bone
      model.traverse((obj) => {
        if (obj.isMesh && obj.material) {
          const mats = Array.isArray(obj.material) ? obj.material : [obj.material];
          mats.forEach((m) => {
            if (m.color && palette.bone) m.color.setHex(palette.bone);
            if ("roughness" in m) m.roughness = 0.55;
            if ("metalness" in m) m.metalness = 0.05;
            m.needsUpdate = true;
          });
        }
      });

      modelGroup.add(wrapper);

      // Bounding box + per-mesh centroids in modelGroup-local space (rotation neutralized)
      const savedY = modelGroup.rotation.y, savedX = modelGroup.rotation.x;
      modelGroup.rotation.set(0, 0, 0);
      modelGroup.updateMatrixWorld(true);
      const lbox = new THREE.Box3().setFromObject(wrapper);
      meshes.forEach((m) => {
        const mb = new THREE.Box3().setFromObject(m);
        const mcen = new THREE.Vector3(); mb.getCenter(mcen);
        const msz = new THREE.Vector3(); mb.getSize(msz);
        // Real vertebra parts are compact; skip large overlay/cord/sacrum meshes.
        const highlightable = msz.x <= 0.34 && msz.y <= 0.30;
        if (highlightable) {
          if (Array.isArray(m.material)) m.material = m.material.map((x) => x.clone());
          else m.material = m.material.clone();
        }
        const mat = Array.isArray(m.material) ? m.material[0] : m.material;
        meshInfo.push({ mesh: m, y: mcen.y, highlightable, orig: (mat && mat.color) ? mat.color.clone() : new THREE.Color(palette.bone || 0xf2e6d4) });
      });
      modelGroup.rotation.set(savedX, savedY, 0);
      yMin = lbox.min.y; yMax = lbox.max.y;
      cx = (lbox.min.x + lbox.max.x) / 2;
      cz = (lbox.min.z + lbox.max.z) / 2;
      zFront = lbox.max.z + (lbox.max.z - lbox.min.z) * 0.12;

      // Frame the full column with a little margin
      const H = yMax - yMin;
      const fitDist = (H / 2) / Math.tan((35 * Math.PI / 180) / 2) * 1.3;
      camera.position.z = Math.max(3, fitDist);

      modelLoaded = true;
      applyColors();
    },
    undefined,
    (err) => { console.error("[spineGLB]", err); }
  );

  // ── highlight: recolor the actual vertebra meshes by vertical position ──
  let currentMap = {};
  let currentSel = null;
  function setMeshGlow(mi, intensity, white) {
    const mats = Array.isArray(mi.mesh.material) ? mi.mesh.material : [mi.mesh.material];
    mats.forEach((mat) => {
      if (!mat.emissive) return;
      if (white) {
        mat.emissive.copy(accentColor).lerp(new THREE.Color(0xffffff), 0.5).multiplyScalar(0.95);
        mat.color.copy(mi.orig).lerp(accentColor, 0.7);
      } else {
        mat.emissive.copy(accentColor).multiplyScalar(intensity * 0.85);
        mat.color.copy(mi.orig).lerp(accentColor, intensity * 0.55);
      }
      mat.needsUpdate = true;
    });
  }
  function resetMesh(mi) {
    const mats = Array.isArray(mi.mesh.material) ? mi.mesh.material : [mi.mesh.material];
    mats.forEach((mat) => {
      if (mat.emissive) mat.emissive.setHex(0x000000);
      if (mat.color) mat.color.copy(mi.orig);
      mat.needsUpdate = true;
    });
  }
  function applyColors() {
    if (!modelLoaded) return;
    const win = ((yMax - yMin) / bands.length) * 0.55; // half-window around a vertebra band
    meshInfo.forEach((mi) => {
      if (!mi.highlightable) return;
      resetMesh(mi);
      // symptom-derived highlights
      let best = 0;
      Object.entries(currentMap).forEach(([name, intensity]) => {
        if (!intensity) return;
        const band = bands.find((b) => b.name === name);
        if (!band) return;
        if (Math.abs(mi.y - yForFraction(band.f)) <= win) best = Math.max(best, intensity);
      });
      if (best > 0) setMeshGlow(mi, Math.max(0, Math.min(1, best)), false);
      // selected vertebra (brighter, wins)
      if (currentSel) {
        const sb = bands.find((b) => b.name === currentSel);
        if (sb && Math.abs(mi.y - yForFraction(sb.f)) <= win) setMeshGlow(mi, 1, true);
      }
    });
  }
  function setHighlight(map) { currentMap = map || {}; applyColors(); }
  function setSelection(name) { currentSel = name; applyColors(); }

  // ── interaction ──
  let rotation = { x: 0, y: 0 }, targetRot = { x: 0, y: 0 };
  let dragging = false, lastP = { x: 0, y: 0 }, autoRot = true;
  let downX = 0, downY = 0;
  const raycaster = new THREE.Raycaster();
  const mouse = new THREE.Vector2();

  function onPointerDown(e) {
    dragging = true; autoRot = false;
    lastP.x = e.clientX; lastP.y = e.clientY;
    downX = e.clientX; downY = e.clientY;
    renderer.domElement.style.cursor = "grabbing";
  }
  function onPointerMove(e) {
    if (!dragging) return;
    targetRot.y += (e.clientX - lastP.x) * 0.01;
    targetRot.x += (e.clientY - lastP.y) * 0.005;
    targetRot.x = Math.max(-0.6, Math.min(0.6, targetRot.x));
    lastP.x = e.clientX; lastP.y = e.clientY;
  }
  function onPointerUp() { dragging = false; renderer.domElement.style.cursor = "grab"; }
  function onClick(e) {
    if (!interactive || !onPick || !modelLoaded) return;
    if (Math.abs(e.clientX - downX) > 4 || Math.abs(e.clientY - downY) > 4) return;
    const rect = renderer.domElement.getBoundingClientRect();
    mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
    mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
    raycaster.setFromCamera(mouse, camera);
    const hits = raycaster.intersectObjects(meshes, true);
    if (!hits.length) return;
    const p = hits[0].point.clone();
    modelGroup.worldToLocal(p);
    const band = vertebraForY(p.y);
    autoRot = false;
    setSelection(band.name);
    onPick(band.name);
  }

  if (interactive) {
    renderer.domElement.addEventListener("pointerdown", onPointerDown);
    window.addEventListener("pointermove", onPointerMove);
    window.addEventListener("pointerup", onPointerUp);
    renderer.domElement.addEventListener("click", onClick);
  }

  function resize() {
    const w = container.clientWidth, h = container.clientHeight;
    if (!w || !h) return;
    renderer.setSize(w, h, false);
    camera.aspect = w / h;
    camera.updateProjectionMatrix();
  }
  const ro = new ResizeObserver(resize);
  ro.observe(container);
  resize();

  let raf, t = 0;
  function animate() {
    raf = requestAnimationFrame(animate);
    t += 0.02;
    if (autoRot && !dragging && modelLoaded) targetRot.y += 0.004;
    rotation.x += (targetRot.x - rotation.x) * 0.08;
    rotation.y += (targetRot.y - rotation.y) * 0.08;
    modelGroup.rotation.x = rotation.x;
    modelGroup.rotation.y = rotation.y;
    renderer.render(scene, camera);
  }
  animate();

  return {
    dispose() {
      cancelAnimationFrame(raf);
      ro.disconnect();
      renderer.domElement.removeEventListener("pointerdown", onPointerDown);
      window.removeEventListener("pointermove", onPointerMove);
      window.removeEventListener("pointerup", onPointerUp);
      renderer.domElement.removeEventListener("click", onClick);
      renderer.dispose();
      try { container.removeChild(renderer.domElement); } catch (e) {}
    },
    setHighlight,
    setSelection,
    getVertebrae: () => bands.map((b) => b.name),
  };
}

window.SpineKitGLB = { mountSpine: mountSpineGLB };
