// 3D truck-loading visual.
// On scroll-in: 11 cargo boxes fall into a trailer in optimized positions
// while volume utilization counters tick up. Built with Three.js.

const TRUCK_BOX_DATA = [
  // Layer 1 — base row, tall heavy crates
  { x: -2.25, y: -0.9, z: 0,    w: 1.5, h: 1.0, d: 2.4, tier: 0 },
  { x: -0.75, y: -0.9, z: 0,    w: 1.5, h: 1.0, d: 2.4, tier: 1 },
  { x:  0.75, y: -0.9, z: 0,    w: 1.5, h: 1.0, d: 2.4, tier: 0 },
  { x:  2.25, y: -0.9, z: 0,    w: 1.5, h: 1.0, d: 2.4, tier: 1 },
  // Layer 2 — mixed sizes, pack around tighter constraints
  { x: -2.0,  y:  0.0, z: 0,    w: 2.0, h: 0.8, d: 2.4, tier: 2 },
  { x: -0.25, y:  0.0, z: 0,    w: 1.5, h: 0.8, d: 2.4, tier: 0 },
  { x:  1.0,  y:  0.0, z: -0.6, w: 1.0, h: 0.8, d: 1.2, tier: 1 },
  { x:  1.0,  y:  0.0, z:  0.6, w: 1.0, h: 0.8, d: 1.2, tier: 1 },
  { x:  2.25, y:  0.0, z: 0,    w: 1.5, h: 0.8, d: 2.4, tier: 2 },
  // Layer 3 — top, full-width slabs
  { x: -1.5,  y:  0.7, z: 0,    w: 3.0, h: 0.6, d: 2.4, tier: 0 },
  { x:  1.5,  y:  0.7, z: 0,    w: 3.0, h: 0.6, d: 2.4, tier: 2 },
];

function _truckEaseOut(t) { return 1 - Math.pow(1 - t, 2.5); }

function _truckParseColor(str) {
  const s = (str || '').trim();
  if (s.startsWith('#')) {
    const hex = s.length === 4
      ? s[1] + s[1] + s[2] + s[2] + s[3] + s[3]
      : s.slice(1);
    return parseInt(hex, 16);
  }
  if (s.startsWith('rgb')) {
    const m = s.match(/[\d.]+/g);
    if (m && m.length >= 3) {
      return (Math.round(+m[0]) << 16) | (Math.round(+m[1]) << 8) | Math.round(+m[2]);
    }
  }
  return 0xcccccc;
}

function _truckMix(c1, c2, t) {
  const r1 = (c1 >> 16) & 0xff, g1 = (c1 >> 8) & 0xff, b1 = c1 & 0xff;
  const r2 = (c2 >> 16) & 0xff, g2 = (c2 >> 8) & 0xff, b2 = c2 & 0xff;
  return (Math.round(r1 + (r2 - r1) * t) << 16)
       | (Math.round(g1 + (g2 - g1) * t) << 8)
       |  Math.round(b1 + (b2 - b1) * t);
}

function _truckClamp(n, min, max) {
  return Math.max(min, Math.min(max, n));
}

function TruckVisual() {
  const mountRef = React.useRef(null);
  const [loaded, setLoaded] = React.useState(0);
  const [progress, setProgress] = React.useState(0);

  React.useEffect(() => {
    if (!mountRef.current || typeof THREE === 'undefined') return;
    const mount = mountRef.current;

    const cs = getComputedStyle(document.body);
    const accentHex = _truckParseColor(cs.getPropertyValue('--accent'));
    const bgHex     = _truckParseColor(cs.getPropertyValue('--bg-2'));
    const fgDimHex  = _truckParseColor(cs.getPropertyValue('--fg-2'));

    const tierColor = (t) => {
      if (t === 0) return accentHex;
      if (t === 1) return _truckMix(accentHex, bgHex, 0.55);
      return _truckMix(fgDimHex, bgHex, 0.5);
    };

    let w = mount.clientWidth || 600;
    let h = mount.clientHeight || Math.round(w * 0.6);

    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(34, w / h, 0.1, 100);
    const orbitTarget = new THREE.Vector3(0.4, -0.2, 0);
    const orbit = {
      radius: 12.8,
      yaw: 0.76,
      pitch: 0.45,
      pointerId: null,
      lastX: 0,
      lastY: 0,
      userControlled: false,
    };
    const updateCamera = () => {
      const cp = Math.cos(orbit.pitch);
      camera.position.set(
        orbitTarget.x + Math.sin(orbit.yaw) * cp * orbit.radius,
        orbitTarget.y + Math.sin(orbit.pitch) * orbit.radius,
        orbitTarget.z + Math.cos(orbit.yaw) * cp * orbit.radius
      );
      camera.lookAt(orbitTarget);
    };
    updateCamera();

    const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
    renderer.setSize(w, h, false);
    renderer.domElement.style.width = '100%';
    renderer.domElement.style.height = '100%';
    renderer.domElement.style.display = 'block';
    renderer.domElement.style.cursor = 'grab';
    mount.appendChild(renderer.domElement);

    const render = () => renderer.render(scene, camera);

    const onPointerDown = (e) => {
      orbit.pointerId = e.pointerId;
      orbit.lastX = e.clientX;
      orbit.lastY = e.clientY;
      orbit.userControlled = true;
      renderer.domElement.setPointerCapture(e.pointerId);
      renderer.domElement.style.cursor = 'grabbing';
    };
    const onPointerMove = (e) => {
      if (orbit.pointerId !== e.pointerId) return;
      const dx = e.clientX - orbit.lastX;
      const dy = e.clientY - orbit.lastY;
      orbit.lastX = e.clientX;
      orbit.lastY = e.clientY;
      orbit.yaw -= dx * 0.006;
      orbit.pitch = _truckClamp(orbit.pitch + dy * 0.005, -0.25, 1.05);
      updateCamera();
      render();
    };
    const endPointer = (e) => {
      if (orbit.pointerId !== e.pointerId) return;
      orbit.pointerId = null;
      if (renderer.domElement.hasPointerCapture(e.pointerId)) {
        renderer.domElement.releasePointerCapture(e.pointerId);
      }
      renderer.domElement.style.cursor = 'grab';
    };
    const onWheel = (e) => {
      e.preventDefault();
      orbit.userControlled = true;
      orbit.radius = _truckClamp(orbit.radius + e.deltaY * 0.01, 6.5, 18);
      updateCamera();
      render();
    };

    renderer.domElement.addEventListener('pointerdown', onPointerDown);
    renderer.domElement.addEventListener('pointermove', onPointerMove);
    renderer.domElement.addEventListener('pointerup', endPointer);
    renderer.domElement.addEventListener('pointercancel', endPointer);
    renderer.domElement.addEventListener('wheel', onWheel, { passive: false });

    scene.add(new THREE.AmbientLight(0xffffff, 0.55));
    const key = new THREE.DirectionalLight(0xffffff, 0.85);
    key.position.set(5, 10, 6);
    scene.add(key);
    const fill = new THREE.DirectionalLight(0xffffff, 0.28);
    fill.position.set(-7, 4, -4);
    scene.add(fill);

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

    // Trailer interior dimensions
    const TW = 6, TH = 2.4, TD = 2.4;
    const wallT = 0.04;

    const wallMat = new THREE.MeshStandardMaterial({
      color: bgHex, transparent: true, opacity: 0.42,
      roughness: 0.85, metalness: 0.0, side: THREE.DoubleSide,
    });
    const solidMat = new THREE.MeshStandardMaterial({
      color: bgHex, roughness: 0.92, metalness: 0.0,
    });

    const floor = new THREE.Mesh(new THREE.BoxGeometry(TW, wallT, TD), solidMat);
    floor.position.y = -TH / 2;
    truckGroup.add(floor);

    const back = new THREE.Mesh(new THREE.BoxGeometry(TW, TH, wallT), wallMat);
    back.position.z = -TD / 2;
    truckGroup.add(back);

    const sideL = new THREE.Mesh(new THREE.BoxGeometry(wallT, TH, TD), wallMat);
    sideL.position.x = -TW / 2;
    truckGroup.add(sideL);
    const sideR = sideL.clone();
    sideR.position.x = TW / 2;
    truckGroup.add(sideR);

    const trailerEdges = new THREE.LineSegments(
      new THREE.EdgesGeometry(new THREE.BoxGeometry(TW, TH, TD)),
      new THREE.LineBasicMaterial({ color: accentHex, transparent: true, opacity: 0.7 })
    );
    truckGroup.add(trailerEdges);

    // Wheels for trailer + cab
    const wheelMat = new THREE.MeshStandardMaterial({ color: 0x0e0e11, roughness: 0.9 });
    const wheelGeo = new THREE.CylinderGeometry(0.34, 0.34, 0.22, 24);
    const addWheel = (x, z) => {
      const wheel = new THREE.Mesh(wheelGeo, wheelMat);
      wheel.position.set(x, -1.55, z);
      wheel.rotation.x = Math.PI / 2;
      truckGroup.add(wheel);
    };
    [-2.0, 2.0].forEach(x => {
      addWheel(x, -TD / 2 - 0.05);
      addWheel(x,  TD / 2 + 0.05);
    });

    // Cab
    const cabW = 1.4, cabH = 1.5, cabD = TD * 0.85;
    const cabBody = new THREE.Mesh(
      new THREE.BoxGeometry(cabW, cabH, cabD),
      new THREE.MeshStandardMaterial({ color: bgHex, roughness: 0.85 })
    );
    cabBody.position.set(TW / 2 + cabW / 2 + 0.05, -TH / 2 + cabH / 2, 0);
    truckGroup.add(cabBody);
    const cabEdges = new THREE.LineSegments(
      new THREE.EdgesGeometry(cabBody.geometry),
      new THREE.LineBasicMaterial({ color: accentHex, transparent: true, opacity: 0.55 })
    );
    cabEdges.position.copy(cabBody.position);
    truckGroup.add(cabEdges);
    const cabAxleX = TW / 2 + 0.6;
    addWheel(cabAxleX, -TD / 2 - 0.05);
    addWheel(cabAxleX,  TD / 2 + 0.05);

    // Cargo boxes
    const boxes = TRUCK_BOX_DATA.map((b) => {
      const geo = new THREE.BoxGeometry(b.w, b.h, b.d);
      const mat = new THREE.MeshStandardMaterial({
        color: tierColor(b.tier), roughness: 0.65, metalness: 0.0,
        transparent: true, opacity: 0,
      });
      const mesh = new THREE.Mesh(geo, mat);
      const edge = new THREE.LineSegments(
        new THREE.EdgesGeometry(geo),
        new THREE.LineBasicMaterial({ color: 0x000000, transparent: true, opacity: 0 })
      );
      mesh.add(edge);
      mesh.position.set(b.x, b.y + 4.5, b.z);
      mesh.userData = { target: { x: b.x, y: b.y, z: b.z }, mat, edgeMat: edge.material };
      mesh.visible = false;
      truckGroup.add(mesh);
      return mesh;
    });

    const loadedRef = { current: 0 };
    const progressRef = { current: 0 };
    const STAGGER = 0.22;
    const FALL = 0.7;
    const TOTAL = boxes.length * STAGGER + FALL;

    let started = false;
    let raf = null;
    let t0 = null;
    let lastFrame = null;

    const tick = (now) => {
      if (t0 === null) t0 = now;
      if (lastFrame === null) lastFrame = now;
      const t = (now - t0) / 1000;
      const dt = Math.min(0.05, (now - lastFrame) / 1000);
      lastFrame = now;

      boxes.forEach((mesh, i) => {
        const startT = i * STAGGER;
        const localT = Math.min(1, Math.max(0, (t - startT) / FALL));
        if (localT > 0) {
          mesh.visible = true;
          const e = 1 - Math.pow(1 - localT, 3);
          const tgt = mesh.userData.target;
          mesh.position.set(tgt.x, tgt.y + (1 - e) * 4.5, tgt.z);
          mesh.userData.mat.opacity = e;
          mesh.userData.edgeMat.opacity = e * 0.18;
        }
      });

      const placed = Math.max(0, Math.min(boxes.length, Math.floor((t - FALL) / STAGGER) + 1));
      const animProgress = Math.min(1, t / TOTAL);

      if (placed !== loadedRef.current) {
        loadedRef.current = placed;
        setLoaded(placed);
      }
      if (Math.abs(animProgress - progressRef.current) > 0.005 ||
          (animProgress >= 1 && progressRef.current < 1)) {
        progressRef.current = animProgress;
        setProgress(animProgress);
      }

      if (orbit.pointerId === null) truckGroup.rotation.y += dt * 0.18;

      render();
      raf = requestAnimationFrame(tick);
    };

    const io = new IntersectionObserver((entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting && !started) {
          started = true;
          raf = requestAnimationFrame(tick);
        }
      });
    }, { threshold: 0.25 });
    io.observe(mount);

    render();

    const onResize = () => {
      const w2 = mount.clientWidth;
      const h2 = mount.clientHeight || Math.round(w2 * 0.6);
      if (!w2 || !h2) return;
      camera.aspect = w2 / h2;
      camera.updateProjectionMatrix();
      renderer.setSize(w2, h2, false);
      render();
    };
    window.addEventListener('resize', onResize);

    return () => {
      if (raf) cancelAnimationFrame(raf);
      io.disconnect();
      window.removeEventListener('resize', onResize);
      renderer.domElement.removeEventListener('pointerdown', onPointerDown);
      renderer.domElement.removeEventListener('pointermove', onPointerMove);
      renderer.domElement.removeEventListener('pointerup', endPointer);
      renderer.domElement.removeEventListener('pointercancel', endPointer);
      renderer.domElement.removeEventListener('wheel', onWheel);
      renderer.dispose();
      if (mount.contains(renderer.domElement)) mount.removeChild(renderer.domElement);
    };
  }, []);

  const utilization = Math.round(52 + 39 * _truckEaseOut(progress));
  const wasted      = Math.max(0, Math.round(28 - 20 * _truckEaseOut(progress)));
  const isOptimal   = progress >= 1;

  return (
    <div className="truck-vis">
      <div className="vis-tag">
        <i></i>3D Loading · {isOptimal ? 'optimal pack' : 'packing...'}
      </div>
      <div className="truck-canvas" ref={mountRef}></div>
      <div className="alloc-stats">
        <div className="alloc-stat">
          <span>Boxes loaded</span>
          <strong className={isOptimal ? 'pv-good' : ''}>
            {loaded}/{TRUCK_BOX_DATA.length}
          </strong>
        </div>
        <div className="alloc-stat">
          <span>Volume utilization</span>
          <strong className={isOptimal ? 'pv-good' : ''}>{utilization}%</strong>
        </div>
        <div className="alloc-stat">
          <span>Wasted space</span>
          <strong className={isOptimal ? 'pv-good' : (wasted > 14 ? 'pv-bad' : '')}>
            {wasted}%
          </strong>
        </div>
      </div>
    </div>
  );
}

window.TruckVisual = TruckVisual;
