// viewer.jsx — Three.js 3D part viewer for hero & upload preview
// Builds original engineered/sculptural parts. Materials are shared
// across all child meshes so a live color change reaches every face.

const { useEffect, useRef, useState } = React;

// ─── Impeller: a turbine wheel with twisted blades, hub, nose cone, ring ────
function buildImpeller(material) {
  const group = new THREE.Group();

  // Lathed hub (subtle waist + stepped shoulders)
  const hubPts = [];
  const hubProfile = [
    [0.32, -0.55], [0.36, -0.50], [0.36, -0.42], [0.30, -0.40],
    [0.30, -0.20], [0.27, -0.10], [0.27,  0.20], [0.30,  0.24],
    [0.30,  0.30], [0.25,  0.38], [0.14,  0.52], [0.02,  0.58],
  ];
  hubProfile.forEach(([r, y]) => hubPts.push(new THREE.Vector2(r, y)));
  const hubGeo = new THREE.LatheGeometry(hubPts, 96);
  hubGeo.computeVertexNormals();
  group.add(new THREE.Mesh(hubGeo, material));

  // Center bore — visible from below
  const boreGeo = new THREE.CylinderGeometry(0.085, 0.085, 1.2, 48, 1, true);
  group.add(new THREE.Mesh(boreGeo, material));

  // Outer shroud ring at the blade tips
  const ringGeo = new THREE.TorusGeometry(1.05, 0.045, 18, 128);
  const ring = new THREE.Mesh(ringGeo, material);
  ring.rotation.x = Math.PI / 2;
  ring.position.y = 0.02;
  group.add(ring);

  // Base disc / mounting flange
  const flangePts = [
    new THREE.Vector2(0.30, -0.62),
    new THREE.Vector2(0.85, -0.62),
    new THREE.Vector2(0.92, -0.60),
    new THREE.Vector2(0.92, -0.56),
    new THREE.Vector2(0.85, -0.54),
    new THREE.Vector2(0.30, -0.54),
  ];
  const flangeGeo = new THREE.LatheGeometry(flangePts, 96);
  flangeGeo.computeVertexNormals();
  group.add(new THREE.Mesh(flangeGeo, material));

  // Mounting bolt holes — small cylinders punched into the flange (visual only)
  for (let i = 0; i < 6; i++) {
    const a = (i / 6) * Math.PI * 2;
    const boltGeo = new THREE.CylinderGeometry(0.045, 0.045, 0.12, 24);
    const bolt = new THREE.Mesh(boltGeo, material);
    bolt.position.set(Math.cos(a) * 0.74, -0.58, Math.sin(a) * 0.74);
    group.add(bolt);
  }

  // Blades — 9 swept, twisted blades from hub to tip
  const bladeCount = 9;
  for (let i = 0; i < bladeCount; i++) {
    const angle = (i / bladeCount) * Math.PI * 2;

    // start as a thin curved box, then bend + twist along Y
    const bladeGeo = new THREE.BoxGeometry(0.03, 0.95, 0.66, 1, 18, 22);
    const pos = bladeGeo.attributes.position;
    for (let j = 0; j < pos.count; j++) {
      const x = pos.getX(j), y = pos.getY(j), z = pos.getZ(j);
      const t = (y + 0.475) / 0.95; // 0..1 root→tip
      // Outward radial offset — grows from hub radius to shroud radius
      const radial = 0.28 + t * 0.74;
      // Twist amount — more pitch at the root, less at the tip (realistic-ish)
      const twist = (1 - t) * 0.55 - 0.1;
      // Sweep — slight forward sweep at the tip
      const sweep = Math.pow(t, 1.4) * 0.20;
      // First: bend along Z (chord) into an airfoil curve
      const camber = Math.sin(t * Math.PI) * 0.04;
      let nx = x + camber;
      let ny = y * 0.55; // compress vertically — blade is shorter than hub
      let nz = z;
      // Apply twist around Y
      const c = Math.cos(twist), s = Math.sin(twist);
      const tx = nx * c - nz * s;
      const tz = nx * s + nz * c;
      // Push outward radially from origin, then sweep
      const outX = tx + radial;
      pos.setX(j, outX);
      pos.setY(j, ny + sweep * 0.4);
      pos.setZ(j, tz + sweep);
    }
    bladeGeo.computeVertexNormals();
    const blade = new THREE.Mesh(bladeGeo, material);
    blade.rotation.y = angle;
    group.add(blade);
  }

  // Stator vanes on the flange — small fixed guide vanes between the flange and ring
  for (let i = 0; i < 12; i++) {
    const a = (i / 12) * Math.PI * 2 + Math.PI / 24;
    const vaneGeo = new THREE.BoxGeometry(0.02, 0.1, 0.16);
    const vane = new THREE.Mesh(vaneGeo, material);
    vane.position.set(Math.cos(a) * 0.95, -0.5, Math.sin(a) * 0.95);
    vane.rotation.y = a + Math.PI / 2 + 0.3;
    group.add(vane);
  }

  // Re-center vertically
  group.position.y = -0.05;
  return group;
}

// ─── Bracket: a topology-optimized-looking aerospace bracket ────────────────
function buildBracket(material) {
  const group = new THREE.Group();
  // Base plate
  const plateShape = new THREE.Shape();
  plateShape.moveTo(-1.0, -0.6);
  plateShape.lineTo( 1.0, -0.6);
  plateShape.lineTo( 1.0, -0.35);
  plateShape.lineTo(-1.0, -0.35);
  plateShape.lineTo(-1.0, -0.6);
  // bolt holes
  [-0.8, -0.3, 0.3, 0.8].forEach((x) => {
    const h = new THREE.Path();
    h.absarc(x, -0.475, 0.08, 0, Math.PI * 2, true);
    plateShape.holes.push(h);
  });
  const plateGeo = new THREE.ExtrudeGeometry(plateShape, {
    depth: 0.4, bevelEnabled: true, bevelSize: 0.02, bevelThickness: 0.02,
    bevelSegments: 2, curveSegments: 24,
  });
  plateGeo.translate(0, 0, -0.2);
  plateGeo.rotateX(-Math.PI / 2);
  group.add(new THREE.Mesh(plateGeo, material));

  // Top yoke (the "U")
  const yokeShape = new THREE.Shape();
  yokeShape.moveTo(-0.45, 0.0);
  yokeShape.bezierCurveTo(-0.45, 0.6, 0.45, 0.6, 0.45, 0.0);
  yokeShape.lineTo(0.30, 0.0);
  yokeShape.bezierCurveTo(0.30, 0.45, -0.30, 0.45, -0.30, 0.0);
  yokeShape.lineTo(-0.45, 0.0);
  const yokeGeo = new THREE.ExtrudeGeometry(yokeShape, {
    depth: 0.34, bevelEnabled: true, bevelSize: 0.02, bevelThickness: 0.02,
    bevelSegments: 2, curveSegments: 32,
  });
  yokeGeo.translate(0, 0, -0.17);
  group.add(new THREE.Mesh(yokeGeo, material));

  // Pin through the yoke
  const pinGeo = new THREE.CylinderGeometry(0.07, 0.07, 0.5, 24);
  const pin = new THREE.Mesh(pinGeo, material);
  pin.rotation.x = Math.PI / 2;
  pin.position.y = 0.32;
  group.add(pin);

  // Curved diagonal struts (topology-optimized look) — extruded along a curve
  for (const dir of [-1, 1]) {
    const curve = new THREE.CatmullRomCurve3([
      new THREE.Vector3(dir * 0.45, 0.05, 0),
      new THREE.Vector3(dir * 0.65, -0.05, 0),
      new THREE.Vector3(dir * 0.85, -0.25, 0),
    ]);
    const tube = new THREE.TubeGeometry(curve, 28, 0.07, 14, false);
    group.add(new THREE.Mesh(tube, material));
  }

  return group;
}

// ─── Original variants kept ─────────────────────────────────────────────────
function buildBladePart(material) {
  const pts = [];
  for (let i = 0; i <= 28; i++) {
    const t = i / 28;
    const r = 0.55 + 0.35 * Math.sin(Math.PI * t) - 0.18 * Math.pow(t - 0.5, 2);
    const y = (t - 0.5) * 2.2;
    pts.push(new THREE.Vector2(Math.max(0.04, r), y));
  }
  const lathe = new THREE.LatheGeometry(pts, 96);
  const pos = lathe.attributes.position;
  for (let i = 0; i < pos.count; i++) {
    const x = pos.getX(i), y = pos.getY(i), z = pos.getZ(i);
    const twist = y * 0.85;
    const c = Math.cos(twist), s = Math.sin(twist);
    pos.setX(i, x * c - z * s);
    pos.setZ(i, x * s + z * c);
  }
  lathe.computeVertexNormals();
  const group = new THREE.Group();
  group.add(new THREE.Mesh(lathe, material));
  return group;
}

function buildTorus(material) {
  const geo = new THREE.TorusKnotGeometry(0.7, 0.24, 220, 32, 2, 3);
  const group = new THREE.Group();
  group.add(new THREE.Mesh(geo, material));
  return group;
}

function buildGear(material) {
  const shape = new THREE.Shape();
  const teeth = 14;
  const rOut = 1.0, rIn = 0.78, rHub = 0.32;
  for (let i = 0; i <= teeth * 2; i++) {
    const a = (i / (teeth * 2)) * Math.PI * 2;
    const r = i % 2 === 0 ? rOut : rIn;
    const x = Math.cos(a) * r, y = Math.sin(a) * r;
    if (i === 0) shape.moveTo(x, y); else shape.lineTo(x, y);
  }
  const hole = new THREE.Path();
  hole.absarc(0, 0, rHub, 0, Math.PI * 2, true);
  shape.holes.push(hole);
  const geo = new THREE.ExtrudeGeometry(shape, {
    depth: 0.38, bevelEnabled: true, bevelSize: 0.04, bevelThickness: 0.04, bevelSegments: 4, curveSegments: 24,
  });
  geo.translate(0, 0, -0.19);
  geo.rotateX(Math.PI / 2);
  const group = new THREE.Group();
  group.add(new THREE.Mesh(geo, material));
  return group;
}

// ─── Fluted vessel: twisted, ribbed spiral-vase showpiece ───────────────────
// The classic 3D-printing hero piece — an S-curved vessel with 14 fluted
// ribs that corkscrew up the body. Reads instantly as "printed", catches
// light dramatically while auto-rotating.
function buildVase(material) {
  const group = new THREE.Group();

  // S-curve profile: flared foot → bulge → waist → trumpet lip
  const profilePts = [];
  const N = 70;
  for (let i = 0; i <= N; i++) {
    const t = i / N;                     // 0 bottom → 1 top
    const body = 0.40 * Math.sin(Math.PI * Math.min(1, t * 1.15)) * (1 - t * 0.55);
    const foot = 0.20 * Math.pow(1 - t, 6);
    const lip  = 0.46 * Math.pow(t, 8);
    const r = 0.33 + body + foot + lip;
    profilePts.push(new THREE.Vector2(r, t * 2.2 - 1.1));
  }
  // curl the rim inward for visible wall thickness
  profilePts.push(new THREE.Vector2(profilePts[N].x - 0.045, 1.06));
  profilePts.push(new THREE.Vector2(profilePts[N].x - 0.065, 0.98));

  const geo = new THREE.LatheGeometry(profilePts, 220);

  // Fluted ribs, twisted up the height
  const pos = geo.attributes.position;
  const RIBS = 14, AMP = 0.05, TURNS = 1.15;
  for (let i = 0; i < pos.count; i++) {
    const x = pos.getX(i), y = pos.getY(i), z = pos.getZ(i);
    const yn = (y + 1.1) / 2.2;                       // 0..1
    const theta = Math.atan2(z, x);
    const taper = 0.30 + 0.70 * Math.min(1, yn * 2.4); // calm the foot
    const rib = 1 + AMP * taper * Math.sin(RIBS * theta + TURNS * Math.PI * 2 * yn);
    pos.setX(i, x * rib);
    pos.setZ(i, z * rib);
  }
  geo.computeVertexNormals();
  group.add(new THREE.Mesh(geo, material));
  return group;
}

function buildKnotArt(material) {
  // A dense (3,4) torus knot — thick rounded tube, smooth and sculptural.
  // Kept compact so the whole form frames with margin in the hero.
  const geo = new THREE.TorusKnotGeometry(0.5, 0.17, 320, 44, 3, 4);
  geo.computeVertexNormals();
  const group = new THREE.Group();
  group.add(new THREE.Mesh(geo, material));
  return group;
}

function buildPart(variant, material) {
  switch (variant) {
    case "knot":     return buildKnotArt(material);
    case "torus":    return buildTorus(material);
    case "gear":     return buildGear(material);
    case "blade":    return buildBladePart(material);
    case "bracket":  return buildBracket(material);
    case "vase":     return buildVase(material);
    case "impeller":
    default:         return buildImpeller(material);
  }
}

function Viewer({ variant = "impeller", color = "#2a2a2a", bg = "transparent", autoRotate = true, interactive = true, materialKind = "matte", className, style }) {
  const mountRef = useRef(null);
  const stateRef = useRef({});

  useEffect(() => {
    const mount = mountRef.current;
    if (!mount) return;
    let w = mount.clientWidth, h = mount.clientHeight;
    if (!w || !h) { w = 480; h = 480; }

    const scene = new THREE.Scene();
    if (bg && bg !== "transparent") scene.background = new THREE.Color(bg);

    const camera = new THREE.PerspectiveCamera(34, w / h, 0.1, 100);
    // Pulled back + slightly elevated for the impeller's vertical form
    camera.position.set(0.5, 0.9, 4.8);
    camera.lookAt(0, 0, 0);

    const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    renderer.setSize(w, h);
    renderer.outputColorSpace = THREE.SRGBColorSpace;
    mount.appendChild(renderer.domElement);

    // Lighting — studio 4-point
    const key = new THREE.DirectionalLight(0xffffff, 1.6);
    key.position.set(3, 4, 3); scene.add(key);
    const fill = new THREE.DirectionalLight(0xffeed8, 0.45);
    fill.position.set(-3.5, 1.2, 2); scene.add(fill);
    const rim = new THREE.DirectionalLight(0xcfd9ff, 0.9);
    rim.position.set(-1, -2, -3.5); scene.add(rim);
    const top = new THREE.DirectionalLight(0xffffff, 0.6);
    top.position.set(0, 6, 0.5); scene.add(top);
    const amb = new THREE.AmbientLight(0xffffff, 0.35);
    scene.add(amb);

    // Material — shared across the part's meshes so color/material live-updates apply everywhere
    let mat;
    if (materialKind === "metallic") {
      mat = new THREE.MeshStandardMaterial({ color, roughness: 0.32, metalness: 0.88 });
    } else if (materialKind === "shiny") {
      mat = new THREE.MeshStandardMaterial({ color, roughness: 0.22, metalness: 0.1 });
    } else {
      // matte PLA-like
      mat = new THREE.MeshStandardMaterial({ color, roughness: 0.72, metalness: 0.0 });
    }

    const part = buildPart(variant, mat);
    part.rotation.x = 0.12;
    scene.add(part);

    // ground shadow disc
    const discGeo = new THREE.CircleGeometry(1.7, 64);
    const discMat = new THREE.MeshBasicMaterial({ color: 0x000000, transparent: true, opacity: 0.07 });
    const disc = new THREE.Mesh(discGeo, discMat);
    disc.rotation.x = -Math.PI / 2;
    disc.position.y = -1.35;
    scene.add(disc);

    stateRef.current = { scene, camera, renderer, part, mat, mount };

    // pointer interactions
    let dragging = false;
    let lastX = 0, lastY = 0;
    let velX = 0.005, velY = 0;
    let targetVelX = 0.005;
    function onDown(e) {
      if (!interactive) return;
      dragging = true;
      lastX = e.clientX ?? e.touches?.[0]?.clientX ?? 0;
      lastY = e.clientY ?? e.touches?.[0]?.clientY ?? 0;
      mount.style.cursor = "grabbing";
    }
    function onMove(e) {
      if (!dragging) return;
      const x = e.clientX ?? e.touches?.[0]?.clientX ?? 0;
      const y = e.clientY ?? e.touches?.[0]?.clientY ?? 0;
      const dx = x - lastX, dy = y - lastY;
      part.rotation.y += dx * 0.01;
      part.rotation.x += dy * 0.008;
      part.rotation.x = Math.max(-1.2, Math.min(1.2, part.rotation.x));
      lastX = x; lastY = y;
      targetVelX = 0;
    }
    function onUp() {
      dragging = false;
      mount.style.cursor = interactive ? "grab" : "default";
      if (autoRotate) targetVelX = 0.005;
    }
    if (interactive) {
      mount.style.cursor = "grab";
      mount.addEventListener("mousedown", onDown);
      window.addEventListener("mousemove", onMove);
      window.addEventListener("mouseup", onUp);
      mount.addEventListener("touchstart", onDown, { passive: true });
      mount.addEventListener("touchmove", onMove, { passive: true });
      mount.addEventListener("touchend", onUp);
    }

    let raf;
    function tick() {
      velX += (targetVelX - velX) * 0.04;
      if (!dragging && autoRotate) {
        part.rotation.y += velX;
      }
      renderer.render(scene, camera);
      raf = requestAnimationFrame(tick);
    }
    tick();

    const ro = new ResizeObserver(() => {
      const W = mount.clientWidth, H = mount.clientHeight;
      if (!W || !H) return;
      camera.aspect = W / H;
      camera.updateProjectionMatrix();
      renderer.setSize(W, H);
    });
    ro.observe(mount);

    return () => {
      cancelAnimationFrame(raf);
      ro.disconnect();
      if (interactive) {
        mount.removeEventListener("mousedown", onDown);
        window.removeEventListener("mousemove", onMove);
        window.removeEventListener("mouseup", onUp);
        mount.removeEventListener("touchstart", onDown);
        mount.removeEventListener("touchmove", onMove);
        mount.removeEventListener("touchend", onUp);
      }
      // dispose all geometries in the group
      part.traverse((o) => { if (o.geometry) o.geometry.dispose(); });
      mat.dispose();
      discGeo.dispose();
      discMat.dispose();
      renderer.dispose();
      if (renderer.domElement.parentNode === mount) mount.removeChild(renderer.domElement);
    };
  }, [variant, materialKind]);

  // update color live
  useEffect(() => {
    const s = stateRef.current;
    if (s && s.mat) s.mat.color.set(color);
  }, [color]);

  return (
    <div ref={mountRef} className={className} style={{ width: "100%", height: "100%", ...style }} />
  );
}

window.Viewer = Viewer;
