// Real-time 3D pergola preview (Three.js, loaded as global THREE).
// Generated live from the configurator selections — solves the "not enough
// photos of every combo" problem. Drag to orbit; auto-rotates when idle.
// Falls back to a photo if WebGL/THREE is unavailable.

const PERGOLA_COLORS = [
  { id: "white", name: "White", hex: "#e8e7e3" },
  { id: "desert-sand", name: "Desert Sand", hex: "#e6d7bb" },
  { id: "mojave-tan", name: "Mojave Tan", hex: "#d1c2ae" },
  { id: "latte", name: "Latte", hex: "#b7a689" },
  { id: "adobe", name: "Adobe", hex: "#908373" },
  { id: "graphite", name: "Graphite", hex: "#6a6964" },
  { id: "spanish-brown", name: "Spanish Brown", hex: "#2f2216" },
  { id: "black", name: "Black", hex: "#1c1c1d" },
];
const SIZE_DIMS = { s: { w: 10, d: 12 }, m: { w: 12, d: 16 }, l: { w: 16, d: 20 }, xl: { w: 20, d: 24 } };

function Pergola3D({ style, mount, size, addons, color, fallbackImg }) {
  const mountRef = React.useRef(null);
  const stRef = React.useRef(null);
  const [failed, setFailed] = React.useState(false);

  // ---- init scene once ----
  React.useEffect(() => {
    if (typeof THREE === "undefined") { setFailed(true); return; }
    const el = mountRef.current;
    let renderer;
    try {
      renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, preserveDrawingBuffer: true });
    } catch (e) { setFailed(true); return; }
    const W = el.clientWidth || 600, H = el.clientHeight || 560;
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    renderer.setSize(W, H);
    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    renderer.outputEncoding = THREE.sRGBEncoding;
    el.appendChild(renderer.domElement);
    renderer.domElement.style.touchAction = "none";
    renderer.domElement.style.cursor = "grab";

    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(38, W / H, 0.1, 200);

    const hemi = new THREE.HemisphereLight(0xfff4e2, 0x4a3a26, 0.65);
    scene.add(hemi);
    const sun = new THREE.DirectionalLight(0xfff1d6, 1.5);
    sun.position.set(14, 22, 10);
    sun.castShadow = true;
    sun.shadow.mapSize.set(2048, 2048);
    const sc = sun.shadow.camera; sc.left = -22; sc.right = 22; sc.top = 22; sc.bottom = -22; sc.near = 1; sc.far = 80;
    sun.shadow.bias = -0.0004;
    scene.add(sun);
    const fill = new THREE.DirectionalLight(0xdfe7ff, 0.28);
    fill.position.set(-10, 8, -8); scene.add(fill);

    // Lightweight environment so metallic surfaces have something to reflect
    // (satin powder-coat sheen). Equirect canvas → PMREM.
    try {
      const ec = document.createElement("canvas"); ec.width = 128; ec.height = 64;
      const ex = ec.getContext("2d");
      const eg = ex.createLinearGradient(0, 0, 0, 64);
      eg.addColorStop(0.0, "#fbf3e2"); eg.addColorStop(0.45, "#e9dcc4");
      eg.addColorStop(0.5, "#cdbfa6"); eg.addColorStop(1.0, "#6b5b45");
      ex.fillStyle = eg; ex.fillRect(0, 0, 128, 64);
      const envTex = new THREE.CanvasTexture(ec);
      envTex.mapping = THREE.EquirectangularReflectionMapping;
      const pmrem = new THREE.PMREMGenerator(renderer);
      scene.environment = pmrem.fromEquirectangular(envTex).texture;
      envTex.dispose(); pmrem.dispose();
    } catch (e) {}

    const ground = new THREE.Mesh(new THREE.CircleGeometry(40, 64), new THREE.ShadowMaterial({ opacity: 0.24 }));
    ground.rotation.x = -Math.PI / 2; ground.receiveShadow = true; scene.add(ground);

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

    const ctrl = { theta: Math.PI * 0.26, phi: Math.PI * 0.40, radius: 30, targetRadius: 30,
      minR: 12, maxR: 60, dragging: false, fans: [], target: new THREE.Vector3(0, 4.6, 0) };

    function applyCam() {
      const { theta, phi, radius, target } = ctrl;
      camera.position.set(
        target.x + radius * Math.sin(phi) * Math.cos(theta),
        target.y + radius * Math.cos(phi),
        target.z + radius * Math.sin(phi) * Math.sin(theta)
      );
      camera.lookAt(target);
    }

    const dom = renderer.domElement;
    const onDown = (e) => { ctrl.dragging = true; ctrl.px = e.clientX; ctrl.py = e.clientY; dom.style.cursor = "grabbing"; try { dom.setPointerCapture(e.pointerId); } catch (x) {} };
    const onMove = (e) => { if (!ctrl.dragging) return; const dx = e.clientX - ctrl.px, dy = e.clientY - ctrl.py; ctrl.px = e.clientX; ctrl.py = e.clientY; ctrl.theta -= dx * 0.008; ctrl.phi -= dy * 0.006; ctrl.phi = Math.max(0.16, Math.min(Math.PI * 0.49, ctrl.phi)); };
    const onUp = () => { ctrl.dragging = false; dom.style.cursor = "grab"; };
    const onWheel = (e) => { e.preventDefault(); ctrl.targetRadius = Math.max(ctrl.minR, Math.min(ctrl.maxR, ctrl.targetRadius * (1 + Math.sign(e.deltaY) * 0.08))); };
    dom.addEventListener("pointerdown", onDown);
    dom.addEventListener("pointermove", onMove);
    dom.addEventListener("pointerup", onUp);
    dom.addEventListener("pointerleave", onUp);
    dom.addEventListener("wheel", onWheel, { passive: false });

    const onResize = () => { const w = el.clientWidth, h = el.clientHeight; if (!w || !h) return; renderer.setSize(w, h); camera.aspect = w / h; camera.updateProjectionMatrix(); };
    window.addEventListener("resize", onResize);

    let raf;
    const tick = () => {
      raf = requestAnimationFrame(tick);
      ctrl.radius += (ctrl.targetRadius - ctrl.radius) * 0.12;
      if (ctrl.fans) ctrl.fans.forEach((f) => { f.rotation.y += 0.06; });
      applyCam();
      renderer.render(scene, camera);
    };
    applyCam(); tick();

    stRef.current = { renderer, scene, camera, group, ctrl, dom, el };

    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener("resize", onResize);
      dom.removeEventListener("pointerdown", onDown); dom.removeEventListener("pointermove", onMove);
      dom.removeEventListener("pointerup", onUp); dom.removeEventListener("pointerleave", onUp); dom.removeEventListener("wheel", onWheel);
      disposeGroup(group);
      renderer.dispose();
      if (dom.parentNode) dom.parentNode.removeChild(dom);
      stRef.current = null;
    };
  }, []);

  // ---- rebuild geometry on config change ----
  React.useEffect(() => {
    const st = stRef.current; if (!st) return;
    buildPergola(st, { style, mount, size, addons: addons || [], color: color || "sandstone" });
  }, [style, mount, size, (addons || []).join(","), color]);

  if (failed) {
    return <NPImg src={fallbackImg} label="preview" dark />;
  }
  return <div ref={mountRef} style={{ position: "absolute", inset: 0 }} />;
}

// ---- helpers ----
function disposeGroup(g) {
  for (let i = g.children.length - 1; i >= 0; i--) {
    const c = g.children[i];
    c.traverse((o) => {
      if (o.geometry) o.geometry.dispose();
      if (o.material) (Array.isArray(o.material) ? o.material : [o.material]).forEach((m) => m.dispose());
    });
    g.remove(c);
  }
}
// Subtle procedural Alumawood grain — near-white streaks so material.color
// tints accurately; reused as both map (faint) and bumpMap (emboss).
function woodTexture() {
  if (window.__npWood) return window.__npWood;
  const c = document.createElement("canvas"); c.width = 256; c.height = 256;
  const x = c.getContext("2d");
  x.fillStyle = "#ffffff"; x.fillRect(0, 0, 256, 256);
  for (let i = 0; i < 230; i++) {
    const gx = Math.random() * 256;
    x.strokeStyle = "rgba(74,58,38," + (0.04 + Math.random() * 0.13) + ")";
    x.lineWidth = 0.5 + Math.random() * 2.0;
    x.beginPath(); x.moveTo(gx, 0);
    let cx = gx;
    for (let y = 0; y <= 256; y += 12) { cx += (Math.random() - 0.5) * 4.0; x.lineTo(cx, y); }
    x.stroke();
  }
  const tex = new THREE.CanvasTexture(c);
  tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
  tex.repeat.set(1, 3);
  window.__npWood = tex;
  return tex;
}
function beam(mat, w, h, d, x, y, z) {
  const m = new THREE.Mesh(new THREE.BoxGeometry(w, h, d), mat);
  m.position.set(x, y, z); m.castShadow = true; m.receiveShadow = true;
  return m;
}

function buildPergola(st, p) {
  const { group, ctrl } = st;
  disposeGroup(group);
  ctrl.fans = [];

  const dims = SIZE_DIMS[p.size] || SIZE_DIMS.m;
  const w = dims.w, d = dims.d, postH = 9, bT = 0.5;
  const hw = w / 2, hd = d / 2;
  const colHex = (PERGOLA_COLORS.find((c) => c.id === p.color) || PERGOLA_COLORS[0]).hex;

  const wood = woodTexture();
  const frameMat = new THREE.MeshStandardMaterial({ color: new THREE.Color(colHex), roughness: 0.48, metalness: 0.55, map: wood, bumpMap: wood, bumpScale: 0.05, envMapIntensity: 0.9 });
  const topMat = new THREE.MeshStandardMaterial({ color: new THREE.Color(colHex).multiplyScalar(1.06), roughness: 0.46, metalness: 0.55, map: wood, bumpMap: wood, bumpScale: 0.05, envMapIntensity: 0.9 });

  const attached = p.mount === "attached";
  const backZ = -hd, frontZ = hd;

  // House wall (attached only)
  if (attached) {
    const wallMat = new THREE.MeshStandardMaterial({ color: 0xcabfa8, roughness: 0.95 });
    const wall = new THREE.Mesh(new THREE.BoxGeometry(w + 8, postH + 6, 0.6), wallMat);
    wall.position.set(0, (postH + 6) / 2, backZ - 0.5); wall.receiveShadow = true; wall.castShadow = true;
    group.add(wall);
  }

  // Posts — 2 on attached (front corners), 4 on freestanding (all corners)
  const postXs = [-hw + bT / 2, hw - bT / 2];
  const postZs = attached ? [frontZ - bT / 2] : [frontZ - bT / 2, backZ + bT / 2];
  postXs.forEach((px) => postZs.forEach((pz) => group.add(beam(frameMat, bT, postH, bT, px, postH / 2, pz))));

  // Top perimeter beams
  const yTop = postH + bT / 2;
  group.add(beam(frameMat, w, bT, bT, 0, yTop, frontZ - bT / 2));
  group.add(beam(frameMat, w, bT, bT, 0, yTop, backZ + bT / 2));
  group.add(beam(frameMat, bT, bT, d, -hw + bT / 2, yTop, 0));
  group.add(beam(frameMat, bT, bT, d, hw - bT / 2, yTop, 0));

  const yRaft = postH + bT;       // top of beams
  const roof = p.style;

  if (roof === "insulated") {
    // clean thick slab, smooth underside
    const slab = beam(topMat, w, 0.6, d, 0, yRaft + 0.3, 0);
    group.add(slab);
  } else {
    // rafters run along depth (Z), spaced along width (X)
    const nR = Math.max(3, Math.round(w / 2));
    for (let i = 0; i <= nR; i++) {
      const x = -hw + (w * i) / nR;
      group.add(beam(frameMat, bT * 0.7, bT * 0.8, d, x, yRaft + 0.1, 0));
    }
    if (roof === "solid") {
      group.add(beam(topMat, w, 0.28, d, 0, yRaft + 0.55, 0));
    } else if (roof === "lattice") {
      // lattice strips run along width (X), spaced along depth (Z), with gaps
      const nL = Math.max(4, Math.round(d / 1.6));
      for (let j = 0; j <= nL; j++) {
        const z = -hd + (d * j) / nL;
        group.add(beam(frameMat, w, 0.32, 0.16, 0, yRaft + 0.45, z));
      }
    } else if (roof === "louvered") {
      // tilted louver slats along width (X), spaced along depth (Z)
      const nLv = Math.max(5, Math.round(d / 1.25));
      const slatMat = new THREE.MeshStandardMaterial({ color: new THREE.Color(colHex), roughness: 0.5, metalness: 0.15, map: wood, bumpMap: wood, bumpScale: 0.015 });
      for (let j = 0; j < nLv; j++) {
        const z = -hd + 0.6 + (d - 1.2) * (j / (nLv - 1));
        const s = beam(slatMat, w - 0.6, 0.1, 0.85, 0, yRaft + 0.5, z);
        s.rotation.x = -0.5;
        group.add(s);
      }
    }
  }

  // ---- add-ons ----
  const addons = p.addons || [];
  if (addons.includes("privacy")) {
    // horizontal privacy slats on the left side (x = -hw)
    const n = 9;
    for (let i = 0; i < n; i++) {
      const y = 1.2 + (postH - 2.4) * (i / (n - 1));
      group.add(beam(frameMat, 0.16, 0.45, d - bT, -hw + bT / 2, y, 0));
    }
  }
  if (addons.includes("rolldown")) {
    const screenMat = new THREE.MeshStandardMaterial({ color: 0x2a2620, roughness: 0.9, transparent: true, opacity: 0.55, side: THREE.DoubleSide });
    const drop = postH * 0.62;
    const screen = new THREE.Mesh(new THREE.PlaneGeometry(w - bT, drop), screenMat);
    screen.position.set(0, postH - drop / 2, frontZ - bT); screen.castShadow = false;
    group.add(screen);
  }
  if (addons.includes("lighting")) {
    const bulbMat = new THREE.MeshStandardMaterial({ color: 0xffe6b8, emissive: 0xffcf87, emissiveIntensity: 1.4 });
    const n = Math.max(3, Math.round(w / 3));
    for (let i = 0; i < n; i++) {
      const x = -hw + 1 + (w - 2) * (i / (n - 1));
      const bulb = new THREE.Mesh(new THREE.SphereGeometry(0.16, 12, 12), bulbMat);
      bulb.position.set(x, postH - 0.2, frontZ - bT - 0.1);
      group.add(bulb);
    }
    const warm = new THREE.PointLight(0xffd9a0, 0.6, 30); warm.position.set(0, postH - 1, 0); group.add(warm);
  }
  if (addons.includes("fans")) {
    const fanCount = w >= 16 ? 2 : 1;
    for (let f = 0; f < fanCount; f++) {
      const fan = new THREE.Group();
      const hubMat = new THREE.MeshStandardMaterial({ color: 0x3a3a3a, roughness: 0.4, metalness: 0.5 });
      const hub = new THREE.Mesh(new THREE.CylinderGeometry(0.3, 0.3, 0.3, 16), hubMat);
      fan.add(hub);
      const bladeMat = new THREE.MeshStandardMaterial({ color: 0x5a4a35, roughness: 0.6 });
      for (let b = 0; b < 4; b++) {
        const blade = beam(bladeMat, 2.4, 0.05, 0.5, 1.3, 0, 0);
        blade.rotation.y = (b / 4) * Math.PI * 2;
        const holder = new THREE.Group(); holder.add(blade); holder.rotation.y = (b / 4) * Math.PI * 2; fan.add(holder);
      }
      const fx = fanCount === 1 ? 0 : (f === 0 ? -w / 4 : w / 4);
      fan.position.set(fx, postH - 0.6, 0);
      group.add(fan);
      ctrl.fans.push(fan);
    }
  }

  // camera framing for size
  const maxDim = Math.max(w, d);
  ctrl.targetRadius = maxDim * 1.85 + 16;
  ctrl.minR = maxDim * 0.7; ctrl.maxR = maxDim * 2.6 + 20;
}

Object.assign(window, { Pergola3D, PERGOLA_COLORS });
