/* ===== Three.js 3D scene for container loading ===== */
const Scene3D = ({ items, container, colorMode, selectedId, onSelect, viewMode = '3d', xray = false, layerSlice = 1.0 }) => {
  const mountRef = React.useRef(null);
  const stateRef = React.useRef({});
  const [hover, setHover] = React.useState(null);

  // build / rebuild scene
  React.useEffect(() => {
    if (!window.THREE || !mountRef.current) return;
    const THREE = window.THREE;
    const mount = mountRef.current;
    const w = mount.clientWidth, h = mount.clientHeight;

    const scene = new THREE.Scene();
    scene.background = new THREE.Color('#ECE6DF');

    const camera = new THREE.PerspectiveCamera(38, w/h, 0.1, 200);
    const renderer = new THREE.WebGLRenderer({ antialias: true, preserveDrawingBuffer: true });
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    renderer.setSize(w, h);
    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    mount.appendChild(renderer.domElement);

    // lights
    scene.add(new THREE.AmbientLight(0xffffff, 0.55));
    const dir = new THREE.DirectionalLight(0xffffff, 0.85);
    dir.position.set(8, 14, 6);
    dir.castShadow = true;
    dir.shadow.mapSize.set(1024, 1024);
    dir.shadow.camera.left = -15; dir.shadow.camera.right = 15;
    dir.shadow.camera.top = 15; dir.shadow.camera.bottom = -15;
    scene.add(dir);
    const fill = new THREE.DirectionalLight(0xffe2c2, 0.20);
    fill.position.set(-6, 5, -4);
    scene.add(fill);

    // ground
    const ground = new THREE.Mesh(
      new THREE.PlaneGeometry(80, 60),
      new THREE.MeshStandardMaterial({ color: 0xd9d0c4, roughness: 0.95 })
    );
    ground.rotation.x = -Math.PI/2;
    ground.position.y = -0.001;
    ground.receiveShadow = true;
    scene.add(ground);

    // grid
    const grid = new THREE.GridHelper(60, 60, 0xb0a497, 0xc6bdb0);
    grid.position.y = 0.002;
    scene.add(grid);

    stateRef.current = { THREE, scene, camera, renderer, mount, items: new Map(), raycaster: new THREE.Raycaster(), mouse: new THREE.Vector2() };

    // camera target — fit container
    const cL = container.L/1000, cW = container.W/1000, cH = container.H/1000 || 0.05;
    const camTarget = new THREE.Vector3(cL/2, cH*0.35, cW/2);
    const fitR = Math.max(cL, cW * 2.5, 6) * 1.35;
    const orbit = { az: -0.95, el: 0.55, r: fitR };
    function applyCam() {
      const x = camTarget.x + orbit.r * Math.cos(orbit.el) * Math.sin(orbit.az);
      const y = camTarget.y + orbit.r * Math.sin(orbit.el);
      const z = camTarget.z + orbit.r * Math.cos(orbit.el) * Math.cos(orbit.az);
      camera.position.set(x, y, z);
      camera.lookAt(camTarget);
    }
    if (viewMode === 'top') { orbit.az = 0; orbit.el = 1.40; orbit.r = fitR; camTarget.set(cL/2, 0, cW/2); }
    if (viewMode === 'side') { orbit.az = Math.PI/2; orbit.el = 0.02; orbit.r = fitR; camTarget.set(cL/2, cH/2, cW/2); }
    applyCam();
    stateRef.current.applyCam = applyCam;
    stateRef.current.orbit = orbit;
    stateRef.current.camTarget = camTarget;

    // controls
    let dragging = false, mid = false, last = {x:0,y:0};
    const dom = renderer.domElement;
    dom.style.cursor = 'grab';
    dom.addEventListener('mousedown', e => { dragging = true; mid = e.button === 1 || e.shiftKey; last = {x:e.clientX, y:e.clientY}; dom.style.cursor='grabbing'; });
    window.addEventListener('mouseup', () => { dragging = false; dom.style.cursor='grab'; });
    window.addEventListener('mousemove', e => {
      if (!dragging) return;
      const dx = e.clientX - last.x, dy = e.clientY - last.y;
      last = {x:e.clientX, y:e.clientY};
      if (mid) {
        const f = orbit.r * 0.002;
        camTarget.x -= dx * f * Math.cos(orbit.az);
        camTarget.z += dx * f * Math.sin(orbit.az);
        camTarget.y += dy * f;
      } else {
        orbit.az -= dx * 0.005;
        orbit.el = Math.max(0.02, Math.min(1.5, orbit.el + dy * 0.005));
      }
      applyCam();
    });
    dom.addEventListener('wheel', e => {
      e.preventDefault();
      orbit.r = Math.max(2, Math.min(50, orbit.r * (1 + e.deltaY * 0.001)));
      applyCam();
    }, { passive: false });

    // pick handling
    dom.addEventListener('mousemove', (e) => {
      const r = dom.getBoundingClientRect();
      stateRef.current.mouse.x = ((e.clientX - r.left) / r.width) * 2 - 1;
      stateRef.current.mouse.y = -((e.clientY - r.top) / r.height) * 2 + 1;
    });
    dom.addEventListener('click', (e) => {
      const { raycaster, mouse, camera, scene } = stateRef.current;
      raycaster.setFromCamera(mouse, camera);
      const hits = raycaster.intersectObjects(scene.children, true).filter(h => h.object.userData?.itemId);
      if (hits.length) onSelect && onSelect(hits[0].object.userData.itemId, { x: e.clientX, y: e.clientY });
    });

    // animate
    let alive = true;
    function frame() {
      if (!alive) return;
      renderer.render(scene, camera);
      requestAnimationFrame(frame);
    }
    frame();

    function onResize() {
      const w = mount.clientWidth, h = mount.clientHeight;
      camera.aspect = w/h;
      camera.updateProjectionMatrix();
      renderer.setSize(w, h);
    }
    const ro = new ResizeObserver(onResize);
    ro.observe(mount);

    return () => {
      alive = false;
      ro.disconnect();
      mount.removeChild(renderer.domElement);
      renderer.dispose();
    };
  }, [container.id]);

  // (re)build items + container whenever data changes
  React.useEffect(() => {
    const s = stateRef.current;
    if (!s?.scene) return;
    const THREE = s.THREE;
    // remove old item meshes & container
    [...s.scene.children].forEach(c => {
      if (c.userData?.role === 'cargo' || c.userData?.role === 'container') {
        s.scene.remove(c);
        c.geometry?.dispose?.();
        if (Array.isArray(c.material)) c.material.forEach(m=>m.dispose()); else c.material?.dispose?.();
      }
    });

    const cL = container.L/1000, cW = container.W/1000, cH = container.H/1000 || 0.05;

    // container wireframe
    const cgeo = new THREE.BoxGeometry(cL, cH, cW);
    const cmat = new THREE.MeshStandardMaterial({ color: 0x2c1010, transparent: true, opacity: xray ? 0.04 : 0.10, side: THREE.DoubleSide });
    const cbox = new THREE.Mesh(cgeo, cmat);
    cbox.position.set(cL/2, cH/2, cW/2);
    cbox.userData.role = 'container';
    s.scene.add(cbox);
    // edges
    const edges = new THREE.LineSegments(
      new THREE.EdgesGeometry(cgeo),
      new THREE.LineBasicMaterial({ color: 0x2c1010, transparent: true, opacity: 0.85 })
    );
    edges.position.copy(cbox.position);
    edges.userData.role = 'container';
    s.scene.add(edges);

    // floor of container
    const cfloor = new THREE.Mesh(
      new THREE.PlaneGeometry(cL, cW),
      new THREE.MeshStandardMaterial({ color: 0xf0e8de, roughness: 0.9 })
    );
    cfloor.rotation.x = -Math.PI/2;
    cfloor.position.set(cL/2, 0.003, cW/2);
    cfloor.userData.role = 'container';
    cfloor.receiveShadow = true;
    s.scene.add(cfloor);

    // colors helper
    function colorFor(it) {
      if (colorMode === 'category') {
        const cat = (window.Mojro.CATEGORIES.find(c=>c.id===it.category) || {});
        return cat.color || '#7a6a64';
      }
      if (colorMode === 'weight') {
        const w = (it.weight || 0) * (it.qty||1);
        const t = Math.min(1, w / 800);
        // blue→amber→red
        if (t < 0.5) {
          const k = t / 0.5;
          return mixHex('#2E5FA8', '#F0B830', k);
        } else {
          const k = (t - 0.5) / 0.5;
          return mixHex('#F0B830', '#C13333', k);
        }
      }
      if (colorMode === 'stop') {
        const palette = ['#C13333','#E8893C','#F0B830','#1F8A5B','#2E5FA8'];
        return palette[(it.stop||1) - 1] || '#7a6a64';
      }
      return it.color || '#C13333';
    }

    // pack items as rows along container length (simple visual layout)
    let cx = 0.1, cz = 0.05, cy = 0;
    let rowH = 0;
    items.forEach((it, idx) => {
      const L = it.L/1000, W = it.W/1000, H = it.H/1000;
      const stack = Math.min(it.qty || 1, 4);
      const acrossW = Math.max(1, Math.floor((cW - 0.1) / Math.max(W, 0.4)));
      const groupL = L;
      for (let i=0; i<(it.qty||1); i++) {
        const idxInLayer = i % (acrossW * stack);
        const layer = Math.floor(idxInLayer / acrossW);
        const acrossI = idxInLayer % acrossW;
        // new row?
        if (i % (acrossW * stack) === 0 && i !== 0) {
          cx += groupL + 0.08;
        }
        const x = cx + L/2;
        const y = H/2 + layer * (H + 0.005);
        const z = 0.05 + acrossI * (W + 0.04) + W/2;
        if (x + L/2 > cL - 0.05) break;
        const slice = layerSlice * cH;
        if (y - H/2 > slice) continue;
        const color = colorFor(it);
        const opacity = xray ? 0.28 : 1;
        const mat = new THREE.MeshStandardMaterial({
          color, transparent: xray, opacity, roughness: 0.6, metalness: 0.05,
          emissive: selectedId === it.id ? new THREE.Color(color).multiplyScalar(0.35) : 0x000000,
        });
        let geo;
        if (it.type === 'barrel') geo = new THREE.CylinderGeometry(L/2, L/2, H, 16);
        else if (it.type === 'roll' || it.type === 'pipe') {
          geo = new THREE.CylinderGeometry(W/2, W/2, L, 14);
          geo.rotateZ(Math.PI/2);
        }
        else geo = new THREE.BoxGeometry(L, H, W);

        const m = new THREE.Mesh(geo, mat);
        m.position.set(x, y, z);
        m.castShadow = true;
        m.receiveShadow = true;
        m.userData.itemId = it.id;
        m.userData.role = 'cargo';
        s.scene.add(m);

        // edges
        const ge = new THREE.LineSegments(
          new THREE.EdgesGeometry(geo),
          new THREE.LineBasicMaterial({ color: 0x2c1010, transparent: true, opacity: xray ? 0.5 : 0.5 })
        );
        ge.position.copy(m.position);
        ge.userData.role = 'cargo';
        s.scene.add(ge);

        // selection halo
        if (selectedId === it.id) {
          const halo = new THREE.LineSegments(
            new THREE.EdgesGeometry(new THREE.BoxGeometry(L*1.04, H*1.04, W*1.04)),
            new THREE.LineBasicMaterial({ color: 0xc13333, linewidth: 2 })
          );
          halo.position.copy(m.position);
          halo.userData.role = 'cargo';
          s.scene.add(halo);
        }
        // violation pulse
        if (it.violation) {
          const vh = new THREE.LineSegments(
            new THREE.EdgesGeometry(new THREE.BoxGeometry(L*1.06, H*1.06, W*1.06)),
            new THREE.LineBasicMaterial({ color: it.violation === 'bad' ? 0xc13333 : 0xc98a1b })
          );
          vh.position.copy(m.position);
          vh.userData.role = 'cargo';
          s.scene.add(vh);
        }
      }
      cx += groupL + 0.08;
    });
  }, [items, colorMode, selectedId, xray, layerSlice, container.id]);

  // recompute view mode without rebuild
  React.useEffect(() => {
    const s = stateRef.current;
    if (!s?.applyCam) return;
    const cL = container.L/1000, cW = container.W/1000, cH = container.H/1000 || 0.05;
    const fitR = Math.max(cL, cW * 2.5, 6) * 1.35;
    if (viewMode === 'top')      { s.orbit.az = 0;          s.orbit.el = 1.40; s.orbit.r = fitR; s.camTarget.set(cL/2, 0, cW/2); }
    else if (viewMode === 'side'){ s.orbit.az = Math.PI/2;  s.orbit.el = 0.02; s.orbit.r = fitR; s.camTarget.set(cL/2, cH/2, cW/2); }
    else                          { s.orbit.az = -0.95;      s.orbit.el = 0.55; s.orbit.r = fitR; s.camTarget.set(cL/2, cH*0.35, cW/2); }
    s.applyCam();
  }, [viewMode]);

  return <div ref={mountRef} style={{ position:'absolute', inset:0 }} />;
};

// hex mixer
function mixHex(a, b, t) {
  const pa = a.match(/\w\w/g).map(x=>parseInt(x,16));
  const pb = b.match(/\w\w/g).map(x=>parseInt(x,16));
  const r = pa.map((c,i) => Math.round(c + (pb[i]-c)*t));
  return '#' + r.map(c => c.toString(16).padStart(2,'0')).join('');
}

window.Scene3D = Scene3D;
window.mixHex = mixHex;
