/* 3D Visualizer — 実図面ベース 1F+2F 二階建て住宅 */
const { useEffect, useRef, useState } = React;

/* ─── DIMENSIONS (実図面: 1F 9.555m×7.735m / 2F 6.825m×5.460m) ─── */
const FLOOR_H = 2.7;       // 各階の高さ
const FLOOR2_OFFSET_Y = FLOOR_H + 0.3;
// 2F配置オフセット: ミラーX後 HOUSE_W - OFFSET_orig - HOUSE_2F_W = 0
// Z方向は変更なし: 2.276m
const FLOOR2_OFFSET_X = 0.000;
const FLOOR2_OFFSET_Z = 2.276;

const HOUSE_W = 9.555;
const HOUSE_D = 7.735;

/* ─── 1F ROOMS (ミラーX座標 / x:0-9.555, z:0-7.735) ─── */
/* ★南カメラ(azimuth=-π/2)に合わせてX座標をミラー反転済み★ */
/* x=0=東端(畳コーナー側), x=9.555=西端(UB・FCL側)          */
/* z=0=南端(手前=LDK), z=7.735=北端(奥=UB)                   */
/* ミラー変換: x_mirror = HOUSE_W - x_orig - w               */
const ROOMS_1F = [
  // 北側水回り列 — z:5.915-7.735 (深さ1.820m) / 西端(高x)に配置
  { id:'ub',     ja:'UB（浴室）',  x:7.735, z:5.915, w:1.820, d:1.820, color:0x9ec5dc },
  { id:'dat',    ja:'脱衣室',      x:6.370, z:5.915, w:1.365, d:1.820, color:0xb8d4e3 },
  { id:'sen',    ja:'洗面',        x:5.005, z:5.915, w:1.365, d:1.820, color:0xc8d6e0 },
  { id:'tlt1',   ja:'トイレ',      x:4.095, z:5.915, w:0.910, d:1.820, color:0xd8e0e8 },
  { id:'kai',    ja:'階段',        x:2.275, z:5.915, w:1.820, d:1.820, color:0xe8d4a8 },
  // 畳コーナー: 東端(低x) x[0,2.275] × z[5.461,7.735] (2.275×2.275m=3.1帖)
  { id:'tatami', ja:'畳コーナー',  x:0.000, z:5.461, w:2.275, d:2.275, color:0xddb877 },

  // 玄関・ホール — z:4.096-5.916 / 西端(高x)に配置
  { id:'genkan', ja:'玄関',        x:7.735, z:4.096, w:1.820, d:1.820, color:0xb8b8b0 },
  { id:'hall',   ja:'ホール',      x:6.825, z:4.096, w:0.910, d:1.820, color:0xe0d8c8 },

  // FCL・パントリー — 西端(段差0.2m後退) x[7.990,9.355]
  { id:'fcl',    ja:'FCL',         x:7.990, z:1.821, w:1.365, d:2.297, color:0xe8b8c8 },
  { id:'pantry', ja:'パントリー',  x:7.990, z:0,     w:1.365, d:1.821, color:0xddc8a0 },

  // LDK（19帖）— L字形を重複なし3ストリップで構成（内部壁なし）
  // Strip A: 南ストリップ x[3.639,7.735]×z[0,2.268] — ラベルここ
  { id:'ldk',  ja:'LDK（19帖）', x:3.639, z:0,     w:4.096, d:2.268, color:0xf5e6c5, skipWalls:['N'] },
  // Strip B: 中央全幅    x[0,7.735]×z[2.268,4.095] — 上下境界壁なし
  { id:'ldk2', ja:'',            x:0.000, z:2.268, w:7.735, d:1.827, color:0xf5e6c5, noLabel:true, skipWalls:['S','N'] },
  // Strip C: 北東ストリップ x[0,6.825]×z[4.095,5.461] — 南境界壁なし
  { id:'ldk3', ja:'',            x:0.000, z:4.095, w:6.825, d:1.366, color:0xf5e6c5, noLabel:true, skipWalls:['S'] },
];

/* ─── 2F ROOMS（ミラーX座標 / x:0-6.825, z:0-5.460）─── */
/* ★X座標をミラー反転済み: x_mirror = 6.825 - x_orig - w ★ */
/* x=0=2F東端(洋室A/B側), x=6.825=2F西端(WIC側)            */
const ROOMS_2F = [
  // WIC: x[5.005,6.825] × z[2.730,5.460] (西端・高x)
  { id:'wic',    ja:'WIC',           x:5.005, z:2.730, w:1.820, d:2.730, color:0xe8c8d8 },
  // トイレ: x[4.095,5.005] × z[4.460,5.460]
  { id:'tlt2',   ja:'トイレ',        x:4.095, z:4.460, w:0.910, d:1.000, color:0xd8e0e8 },
  // 階段: x[3.185,5.005] × z[2.730,4.460]
  { id:'kai2',   ja:'階段',          x:3.185, z:2.730, w:1.820, d:1.730, color:0xe8d4a8 },
  // ローカ: x[2.275,5.005] × z[2.275,2.730]
  { id:'roka',   ja:'ローカ',        x:2.275, z:2.275, w:2.730, d:0.455, color:0xe0d8c8 },
  // 洋室A(4.4帖): x[0,2.275] × z[2.275,5.460] (東端・低x)
  { id:'ya',     ja:'洋室A（4.4帖）',x:0.000, z:2.275, w:2.275, d:3.185, color:0xc8d8b8 },
  // 寝室(5.3帖): x[3.640,6.825] × z[0,2.275] (西端寄り)
  { id:'bed',    ja:'寝室（5.3帖）', x:3.640, z:0,     w:3.185, d:2.275, color:0xd4b8a8 },
  // 洋室B(5帖): x[0,3.640] × z[0,2.275] (東端寄り)
  { id:'yb',     ja:'洋室B（5帖）',  x:0.000, z:0,     w:3.640, d:2.275, color:0xb8c8d8 },
];

/* ─── EQUIPMENT (緑=必須/推奨 橙=任意 青=検討) ─── */
/* ★X座標ミラー済み: x_1F_mirror = 9.555 - x_orig / x_2F_mirror = 6.825 - x_orig ★ */
const EQUIPMENT_1F = [
  { icon:'🚿', name:'エコキュート',  tag:'最優先',   color:0x2dd98f, x:8.655, z:7.5, room:'ub',    pos:'浴室外壁' },
  { icon:'👕', name:'ドラム式洗乾',  tag:'採用',     color:0x2dd98f, x:7.055, z:6.8, room:'dat',   pos:'脱衣室' },
  { icon:'🍳', name:'200V IH',       tag:'採用',     color:0x2dd98f, x:4.055, z:3.25, room:'ldk',  pos:'キッチン' },
  { icon:'🍽️', name:'大容量食洗機', tag:'採用',     color:0x2dd98f, x:4.455, z:3.5, room:'ldk',   pos:'キッチン' },
  { icon:'🤖', name:'ロボ基地',      tag:'採用',     color:0x2dd98f, x:4.755, z:4.55, room:'ldk',  pos:'LDK壁際' },
  { icon:'🚽', name:'1F節水トイレ',  tag:'調整',     color:0x5b9cf6, x:4.555, z:6.8, room:'tlt1',  pos:'1Fトイレ' },
  { icon:'💡', name:'LED照明',       tag:'必須',     color:0x2dd98f, x:3.555, z:2.5, room:'ldk',   pos:'全室' },
  { icon:'🌡️', name:'床暖房LDK',     tag:'任意',     color:0xf0a500, x:2.355, z:2.0, room:'ldk',   pos:'LDKのみ' },
  { icon:'🏠', name:'断熱UA0.46',    tag:'最優先',   color:0xf0a500, x:0.355, z:3.8, room:'env',   pos:'外皮全体' },
];

const EQUIPMENT_2F = [
  // 2Fローカル座標で指定（ミラー済み: x_mirror = 6.825 - x_orig）
  // トイレ中心: x_orig=2.275 → x_mirror=4.550, z=4.960
  { icon:'🚽', name:'2F節水トイレ',  tag:'調整',     color:0x5b9cf6, x:4.550, z:4.960, room:'tlt2',  pos:'2Fトイレ' },
  // 寝室中心: x_orig=1.593 → x_mirror=5.232, z=1.138
  { icon:'💡', name:'LED 2F',        tag:'必須',     color:0x2dd98f, x:5.232, z:1.138, room:'bed' },
];

/* ─── NG設備 (X座標ミラー済み) ─── */
const NG_MARKERS_1F = [
  { icon:'💨', name:'浴室乾燥機', x:8.555, z:6.5, room:'ub' },
  { icon:'♨️', name:'ミストサウナ',x:8.955, z:7.0, room:'ub' },
  { icon:'🔋', name:'蓄電池',     x:9.055, z:0.8, room:'外部' },
];
const NG_MARKERS_2F = [
  { icon:'🔥', name:'床暖房全室', x:5.325, z:1.0, room:'bed' },
];

/* ─── MOTION PATHS (1F) ─── */
/* ★X座標ミラー済み: x_mirror = 9.555 - x_orig ★ */
const MOTIONS = [
  {
    id:'laundry', label:'洗濯動線（−22秒）',
    before: [[7.055,6.8],[6.055,5.0],[4.555,3.0],[1.555,1.0]],
    after:  [[7.055,6.8],[7.055,6.0],[7.055,5.5]],
    color:0x9b7bf4,
  },
  {
    id:'shopping', label:'買い物搬入（−37秒）',
    before: [[8.655,4.0],[8.055,4.5],[6.555,4.0],[4.055,3.5]],
    after:  [[6.605,0.5],[6.555,1.2],[6.055,2.5],[4.055,3.5]],
    color:0x5b9cf6,
  },
  {
    id:'trash', label:'ゴミ出し（−45秒）',
    before: [[3.555,3.0],[5.555,3.5],[7.555,4.0],[8.655,4.0]],
    after:  [[3.555,3.0],[1.555,1.5],[0.155,0.5]],
    color:0xf0a500,
  },
];

function loadThree() {
  return new Promise((resolve, reject) => {
    if (window.THREE) return resolve(window.THREE);
    const s = document.createElement('script');
    s.src = 'https://unpkg.com/three@0.160.0/build/three.min.js';
    s.onload = () => resolve(window.THREE);
    s.onerror = reject;
    document.head.appendChild(s);
  });
}

function Visualizer3D() {
  const mountRef = useRef(null);
  const sceneRef = useRef(null);
  const [ready, setReady] = useState(false);
  const [showEquip, setShowEquip] = useState(true);
  const [showNG, setShowNG] = useState(false);
  const [showRoof, setShowRoof] = useState(false);
  const [show2F, setShow2F] = useState(false); // デフォルトOFF（1F表示）
  const [activeMotion, setActiveMotion] = useState('laundry');
  const [floorView, setFloorView] = useState('both'); // both / 1f / 2f

  useEffect(() => {
    let renderer, scene, camera, animId;
    let isDragging = false, lastX = 0, lastY = 0;
    // azimuth=-π/2: 南側からLDKを手前にして北を向く
    // X座標をミラー反転済みなので: 左=西(UB・玄関・FCL), 右=東(畳コーナー)
    // 手前(near)=南(LDK・パントリー), 奥(far)=北(水回り) — 平面図と一致
    let azimuth = -Math.PI / 2, polar = Math.PI / 4, distance = 18;
    let target = { x: HOUSE_W/2, y: 1.5, z: HOUSE_D/2 };
    let group1F, group2F, equip1FGrp, equip2FGrp, ng1FGrp, ng2FGrp, motionGroup, roofGroup;

    loadThree().then(THREE => {
      const mount = mountRef.current;
      if (!mount) return;
      const w = mount.clientWidth, h = mount.clientHeight;

      scene = new THREE.Scene();
      scene.background = new THREE.Color(0x0b0d12);
      scene.fog = new THREE.Fog(0x0b0d12, 30, 60);
      camera = new THREE.PerspectiveCamera(45, w / h, 0.1, 200);

      renderer = new THREE.WebGLRenderer({ antialias: true });
      renderer.setSize(w, h);
      renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
      renderer.shadowMap.enabled = true;
      renderer.shadowMap.type = THREE.PCFSoftShadowMap;
      mount.appendChild(renderer.domElement);

      // ── lights ──
      scene.add(new THREE.AmbientLight(0xffffff, 0.55));
      const sun = new THREE.DirectionalLight(0xffeecc, 0.9);
      sun.position.set(12, 22, 8);
      sun.castShadow = true;
      sun.shadow.mapSize.set(1024,1024);
      Object.assign(sun.shadow.camera, {left:-15,right:15,top:15,bottom:-15});
      scene.add(sun);
      const rim = new THREE.DirectionalLight(0x5b9cf6, 0.35);
      rim.position.set(-8, 6, -10);
      scene.add(rim);

      // ── ground ──
      const grid = new THREE.GridHelper(28, 28, 0x252b3b, 0x1a1e2a);
      grid.position.set(HOUSE_W/2, -0.001, HOUSE_D/2);
      scene.add(grid);
      const ground = new THREE.Mesh(
        new THREE.PlaneGeometry(40, 40),
        new THREE.MeshStandardMaterial({ color: 0x0f1218, roughness: 1 })
      );
      ground.rotation.x = -Math.PI / 2;
      ground.position.set(HOUSE_W/2, -0.01, HOUSE_D/2);
      ground.receiveShadow = true;
      scene.add(ground);

      // ── floor builder ──
      function buildFloor(rooms, yBase, offsetX=0, offsetZ=0, labelFontPx=24) {
        const grp = new THREE.Group();
        rooms.forEach(r => {
          // floor slab
          const floorGeo = new THREE.BoxGeometry(r.w - 0.04, 0.08, r.d - 0.04);
          const floorMat = new THREE.MeshStandardMaterial({ color: r.color, roughness: 0.85 });
          const floor = new THREE.Mesh(floorGeo, floorMat);
          floor.position.set(r.x + r.w/2 + offsetX, yBase + 0.04, r.z + r.d/2 + offsetZ);
          floor.receiveShadow = true;
          grp.add(floor);

          // walls (low: 0.5m for visibility)
          const wallH = 0.5;
          const wallMat = new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.6, transparent:true, opacity:0.88 });
          const mkWall = (cx, cz, sw, sd) => {
            const m = new THREE.Mesh(new THREE.BoxGeometry(sw, wallH, sd), wallMat);
            m.position.set(cx + offsetX, yBase + wallH/2 + 0.08, cz + offsetZ);
            m.castShadow = true;
            grp.add(m);
          };
          const T = 0.06;
          const skip = r.skipWalls || [];
          if (!skip.includes('S')) mkWall(r.x + r.w/2,       r.z + T/2,       r.w, T);
          if (!skip.includes('N')) mkWall(r.x + r.w/2,       r.z + r.d - T/2, r.w, T);
          if (!skip.includes('W')) mkWall(r.x + T/2,         r.z + r.d/2,     T,   r.d);
          if (!skip.includes('E')) mkWall(r.x + r.w - T/2,   r.z + r.d/2,     T,   r.d);

          // label sprite — noLabel:true の部屋はスキップ
          if (r.noLabel) return;
          const c = document.createElement('canvas');
          c.width = 320; c.height = 80;
          const ctx = c.getContext('2d');
          ctx.fillStyle = 'rgba(13,16,24,0.92)';
          ctx.beginPath();
          ctx.roundRect(8, 8, 304, 64, 10);
          ctx.fill();
          ctx.strokeStyle = '#2f3647';
          ctx.lineWidth = 2; ctx.stroke();
          ctx.fillStyle = '#dde2f0';
          ctx.font = `bold ${labelFontPx}px "Noto Sans JP", sans-serif`;
          ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
          ctx.fillText(r.ja, 160, 42);
          const tex = new THREE.CanvasTexture(c);
          tex.minFilter = THREE.LinearFilter;
          const spr = new THREE.Sprite(new THREE.SpriteMaterial({ map: tex, transparent: true }));
          spr.scale.set(2.2, 0.55, 1);
          spr.position.set(r.x + r.w/2 + offsetX, yBase + 1.0, r.z + r.d/2 + offsetZ);
          grp.add(spr);
        });
        return grp;
      }

      group1F = buildFloor(ROOMS_1F, 0);
      scene.add(group1F);
      group2F = buildFloor(ROOMS_2F, FLOOR2_OFFSET_Y, FLOOR2_OFFSET_X, FLOOR2_OFFSET_Z);
      scene.add(group2F);

      // ── roof ──
      roofGroup = new THREE.Group();
      const roof = new THREE.Mesh(
        new THREE.BoxGeometry(HOUSE_W + 0.4, 0.15, HOUSE_D + 0.4),
        new THREE.MeshStandardMaterial({ color: 0x2f3647, roughness:0.7 })
      );
      roof.position.set(HOUSE_W/2, FLOOR2_OFFSET_Y + FLOOR_H + 0.1, HOUSE_D/2);
      roof.castShadow = true;
      roofGroup.add(roof);
      // solar panels
      const panelMat = new THREE.MeshStandardMaterial({ color: 0x1a3a5c, metalness:0.6, roughness:0.3, emissive:0x0a1a2c });
      for (let i = 0; i < 3; i++) for (let j = 0; j < 2; j++) {
        const p = new THREE.Mesh(new THREE.BoxGeometry(2.4, 0.04, 1.6), panelMat);
        p.position.set(1.8 + i*2.6, FLOOR2_OFFSET_Y + FLOOR_H + 0.22, 1.4 + j*2.1);
        roofGroup.add(p);
      }
      roofGroup.visible = false;
      scene.add(roofGroup);

      // ── equipment builder ──
      function buildEquip(items, yBase, offsetX=0, offsetZ=0) {
        const grp = new THREE.Group();
        items.forEach(e => {
          const ring = new THREE.Mesh(
            new THREE.RingGeometry(0.22, 0.30, 32),
            new THREE.MeshBasicMaterial({ color: e.color, side: THREE.DoubleSide, transparent: true, opacity: 0.8 })
          );
          ring.rotation.x = -Math.PI / 2;
          ring.position.set(e.x + offsetX, yBase + 0.1, e.z + offsetZ);
          ring.userData.isRing = true;
          grp.add(ring);

          const pillar = new THREE.Mesh(
            new THREE.CylinderGeometry(0.035, 0.035, 1.0, 12),
            new THREE.MeshStandardMaterial({ color: e.color, emissive: e.color, emissiveIntensity: 0.4 })
          );
          pillar.position.set(e.x + offsetX, yBase + 0.55, e.z + offsetZ);
          grp.add(pillar);

          const c = document.createElement('canvas');
          c.width = 128; c.height = 128;
          const ctx = c.getContext('2d');
          ctx.fillStyle = '#' + e.color.toString(16).padStart(6,'0');
          ctx.beginPath(); ctx.arc(64, 64, 50, 0, Math.PI*2); ctx.fill();
          ctx.font = '64px sans-serif';
          ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
          ctx.fillText(e.icon, 64, 72);
          const tex = new THREE.CanvasTexture(c);
          const spr = new THREE.Sprite(new THREE.SpriteMaterial({ map: tex, transparent: true }));
          spr.scale.set(0.55, 0.55, 1);
          spr.position.set(e.x + offsetX, yBase + 1.2, e.z + offsetZ);
          grp.add(spr);
        });
        return grp;
      }

      equip1FGrp = buildEquip(EQUIPMENT_1F, 0);
      scene.add(equip1FGrp);
      equip2FGrp = buildEquip(EQUIPMENT_2F, FLOOR2_OFFSET_Y, FLOOR2_OFFSET_X, FLOOR2_OFFSET_Z);
      scene.add(equip2FGrp);

      // ── NG builder ──
      function buildNG(items, yBase, offsetX=0, offsetZ=0) {
        const grp = new THREE.Group();
        items.forEach(e => {
          const c = document.createElement('canvas');
          c.width = 128; c.height = 128;
          const ctx = c.getContext('2d');
          ctx.fillStyle = '#f05858';
          ctx.beginPath(); ctx.arc(64, 64, 50, 0, Math.PI*2); ctx.fill();
          ctx.font = '64px sans-serif';
          ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
          ctx.fillText(e.icon, 64, 72);
          ctx.strokeStyle = '#ffffff'; ctx.lineWidth = 10;
          ctx.beginPath();
          ctx.moveTo(28, 28); ctx.lineTo(100, 100);
          ctx.moveTo(100, 28); ctx.lineTo(28, 100);
          ctx.stroke();
          const tex = new THREE.CanvasTexture(c);
          const spr = new THREE.Sprite(new THREE.SpriteMaterial({ map: tex, transparent: true }));
          spr.scale.set(0.5, 0.5, 1);
          spr.position.set(e.x + offsetX, yBase + 0.9, e.z + offsetZ);
          grp.add(spr);
        });
        return grp;
      }
      ng1FGrp = buildNG(NG_MARKERS_1F, 0);
      ng1FGrp.visible = false;
      scene.add(ng1FGrp);
      ng2FGrp = buildNG(NG_MARKERS_2F, FLOOR2_OFFSET_Y, FLOOR2_OFFSET_X, FLOOR2_OFFSET_Z);
      ng2FGrp.visible = false;
      scene.add(ng2FGrp);

      // ── motion arrows ──
      motionGroup = new THREE.Group();
      MOTIONS.forEach(m => {
        const buildPath = (pts, color, dashed=false) => {
          const g = new THREE.Group();
          for (let i = 0; i < pts.length - 1; i++) {
            const [x1, z1] = pts[i], [x2, z2] = pts[i+1];
            const len = Math.hypot(x2-x1, z2-z1);
            const tube = new THREE.Mesh(
              new THREE.CylinderGeometry(0.05, 0.05, len, 8),
              new THREE.MeshBasicMaterial({ color, transparent:true, opacity: dashed?0.5:0.95 })
            );
            tube.position.set((x1+x2)/2, 0.18, (z1+z2)/2);
            const angle = Math.atan2(z2-z1, x2-x1);
            tube.rotation.z = -Math.PI/2;
            tube.rotation.y = -angle;
            g.add(tube);
            if (i === pts.length - 2) {
              const cone = new THREE.Mesh(
                new THREE.ConeGeometry(0.18, 0.4, 12),
                new THREE.MeshBasicMaterial({ color, transparent:true, opacity: dashed?0.5:0.95 })
              );
              cone.position.set(x2, 0.18, z2);
              cone.rotation.z = -angle - Math.PI/2;
              g.add(cone);
            }
          }
          return g;
        };
        const before = buildPath(m.before, 0xf05858, true);
        before.userData = { motionId: m.id, kind: 'before' };
        before.visible = false;
        motionGroup.add(before);
        const after = buildPath(m.after, m.color, false);
        after.userData = { motionId: m.id, kind: 'after' };
        after.visible = false;
        motionGroup.add(after);
      });
      scene.add(motionGroup);

      sceneRef.current = { scene, camera, renderer, THREE, group1F, group2F, equip1FGrp, equip2FGrp, ng1FGrp, ng2FGrp, motionGroup, roofGroup };

      // ── interaction ──
      const dom = renderer.domElement;
      dom.style.cursor = 'grab';
      const onDown = (e) => { isDragging = true; lastX = e.clientX; lastY = e.clientY; dom.style.cursor='grabbing'; };
      const onMove = (e) => {
        if (isDragging) {
          azimuth -= (e.clientX - lastX) * 0.008;
          polar = Math.max(0.1, Math.min(Math.PI/2 - 0.02, polar - (e.clientY - lastY) * 0.006));
          lastX = e.clientX; lastY = e.clientY;
        }
      };
      const onUp = () => { isDragging = false; dom.style.cursor='grab'; };
      const onWheel = (e) => { e.preventDefault(); distance = Math.max(6, Math.min(40, distance + e.deltaY * 0.015)); };
      dom.addEventListener('mousedown', onDown);
      window.addEventListener('mousemove', onMove);
      window.addEventListener('mouseup', onUp);
      dom.addEventListener('wheel', onWheel, { passive: false });
      dom.addEventListener('touchstart', (e) => { if (e.touches.length === 1) onDown({clientX:e.touches[0].clientX, clientY:e.touches[0].clientY}); });
      dom.addEventListener('touchmove', (e) => { if (e.touches.length === 1 && isDragging) onMove({clientX:e.touches[0].clientX, clientY:e.touches[0].clientY}); });
      dom.addEventListener('touchend', onUp);

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

      let pulseT = 0;
      const animate = () => {
        pulseT += 0.02;
        camera.position.set(
          target.x + distance * Math.sin(polar) * Math.cos(azimuth),
          target.y + distance * Math.cos(polar),
          target.z + distance * Math.sin(polar) * Math.sin(azimuth)
        );
        camera.lookAt(target.x, target.y, target.z);
        [equip1FGrp, equip2FGrp].forEach(g => g.children.forEach(c => {
          if (c.userData && c.userData.isRing) {
            const s = 1 + Math.sin(pulseT * 2) * 0.15;
            c.scale.set(s, s, s);
          }
        }));
        renderer.render(scene, camera);
        animId = requestAnimationFrame(animate);
      };
      animate();
      setReady(true);

      sceneRef.current.cleanup = () => {
        cancelAnimationFrame(animId);
        window.removeEventListener('mousemove', onMove);
        window.removeEventListener('mouseup', onUp);
        window.removeEventListener('resize', onResize);
        if (mount && renderer.domElement.parentNode === mount) mount.removeChild(renderer.domElement);
        renderer.dispose();
      };
    }).catch(err => console.error('Three.js load failed', err));

    return () => { if (sceneRef.current?.cleanup) sceneRef.current.cleanup(); };
  }, []);

  useEffect(() => {
    if (!sceneRef.current) return;
    sceneRef.current.equip1FGrp.visible = showEquip;
    sceneRef.current.equip2FGrp.visible = showEquip && show2F;
  }, [showEquip, show2F, ready]);

  useEffect(() => {
    if (!sceneRef.current) return;
    sceneRef.current.ng1FGrp.visible = showNG;
    sceneRef.current.ng2FGrp.visible = showNG && show2F;
  }, [showNG, show2F, ready]);

  useEffect(() => {
    if (!sceneRef.current) return;
    sceneRef.current.roofGroup.visible = showRoof;
  }, [showRoof, ready]);

  useEffect(() => {
    if (!sceneRef.current) return;
    sceneRef.current.group2F.visible = show2F;
    sceneRef.current.equip2FGrp.visible = show2F && showEquip;
    sceneRef.current.ng2FGrp.visible = show2F && showNG;
  }, [show2F, ready]);

  useEffect(() => {
    if (!sceneRef.current) return;
    sceneRef.current.motionGroup.children.forEach(g => {
      g.visible = (g.userData.motionId === activeMotion);
    });
  }, [activeMotion, ready]);

  return (
    <div>
      <div style={{marginBottom:'22px'}}>
        <div style={{display:'flex',alignItems:'center',gap:'10px',marginBottom:'4px'}}>
          <span style={{fontSize:'22px'}}>🏠</span>
          <h2 style={{fontSize:'17px',fontWeight:700,letterSpacing:'-.025em'}}>3D ビジュアライザ <span style={{fontSize:'12px',color:'var(--text3)',fontFamily:'var(--mono)',marginLeft:'8px'}}>1F+2F · 実図面準拠</span></h2>
        </div>
        <p style={{fontSize:'12px',color:'var(--text2)',marginLeft:'32px',lineHeight:1.6}}>
          延床98.12㎡（1F 60.86㎡ / 2F 37.26㎡）・LDK19帖の二階建てプランを立体表示。ドラッグで回転・スクロールでズーム。
        </p>
      </div>

      {/* Control bar */}
      <div style={{display:'flex',flexWrap:'wrap',gap:'8px',marginBottom:'14px'}}>
        <button onClick={()=>setShowEquip(!showEquip)} style={btn(showEquip,'#2dd98f')}>⚙ 推奨設備 {showEquip?'ON':'OFF'}</button>
        <button onClick={()=>setShow2F(!show2F)} style={btn(show2F,'#5b9cf6')}>🏢 2F表示 {show2F?'ON':'OFF'}</button>
      </div>

      {/* 3D canvas */}
      <div style={{position:'relative',background:'var(--surface)',border:'1px solid var(--border)',borderRadius:'12px',overflow:'hidden'}}>
        <div className="cost-3d-canvas" ref={mountRef} style={{width:'100%',height:'560px'}} />
        {!ready && (
          <div style={{position:'absolute',inset:0,display:'flex',alignItems:'center',justifyContent:'center',color:'var(--text2)',fontFamily:'var(--mono)',fontSize:'13px'}}>
            Loading 3D scene…
          </div>
        )}
        <div style={{position:'absolute',top:'12px',left:'12px',background:'rgba(13,16,24,0.85)',border:'1px solid var(--border)',borderRadius:'8px',padding:'8px 10px',fontFamily:'var(--mono)',fontSize:'10px',color:'var(--text3)',backdropFilter:'blur(8px)'}}>
          <div style={{color:'var(--text2)',fontWeight:600,marginBottom:'2px'}}>2F戸建て · 延床98.12㎡</div>
          <div>DRAG: rotate · WHEEL: zoom</div>
        </div>
        <div style={{position:'absolute',bottom:'12px',right:'12px',background:'rgba(13,16,24,0.85)',border:'1px solid var(--border)',borderRadius:'8px',padding:'10px 12px',fontSize:'10px',backdropFilter:'blur(8px)',display:'flex',flexDirection:'column',gap:'4px'}}>
          <Legend dot="#2dd98f" label="採用設備（必須・推奨）" />
          <Legend dot="#f0a500" label="採用設備（条件付・任意）" />
          <Legend dot="#5b9cf6" label="検討設備" />
        </div>
      </div>

      {/* Motion path selector */}
      <div style={{marginTop:'16px',background:'var(--surface)',border:'1px solid var(--border)',borderRadius:'12px',padding:'16px'}}>
        <div style={{fontSize:'11px',fontFamily:'var(--mono)',color:'var(--text3)',fontWeight:600,marginBottom:'10px',letterSpacing:'.06em'}}>動線オーバーレイ（1F平面）</div>
        <div style={{display:'flex',flexWrap:'wrap',gap:'6px'}}>
          <button onClick={()=>setActiveMotion(null)} style={btn(!activeMotion,'#8892aa')}>非表示</button>
          {MOTIONS.map(m => (
            <button key={m.id} onClick={()=>setActiveMotion(m.id)} style={btn(activeMotion===m.id,'#9b7bf4')}>
              {m.label}
            </button>
          ))}
        </div>
        {activeMotion && (() => {
          const m = MOTIONS.find(x=>x.id===activeMotion);
          return (
            <div className="mobile-stack" style={{marginTop:'12px',display:'grid',gridTemplateColumns:'1fr 1fr',gap:'8px'}}>
              <div style={{background:'rgba(240,88,88,.07)',border:'1px solid rgba(240,88,88,.18)',borderRadius:'8px',padding:'10px 12px'}}>
                <div style={{display:'flex',alignItems:'center',gap:'6px',marginBottom:'4px'}}>
                  <div style={{width:'12px',height:'2px',background:'#f05858',opacity:.6}} />
                  <span style={{fontSize:'10px',color:'#f05858',fontFamily:'var(--mono)',fontWeight:600}}>BEFORE</span>
                </div>
                <div style={{fontSize:'12px',color:'var(--text2)'}}>長距離・複数部屋を経由</div>
              </div>
              <div style={{background:'rgba(155,123,244,.07)',border:'1px solid rgba(155,123,244,.18)',borderRadius:'8px',padding:'10px 12px'}}>
                <div style={{display:'flex',alignItems:'center',gap:'6px',marginBottom:'4px'}}>
                  <div style={{width:'12px',height:'2px',background:'#9b7bf4'}} />
                  <span style={{fontSize:'10px',color:'#9b7bf4',fontFamily:'var(--mono)',fontWeight:600}}>AFTER</span>
                </div>
                <div style={{fontSize:'12px',color:'var(--text2)'}}>短距離・直結動線</div>
              </div>
            </div>
          );
        })()}
      </div>

      {/* Equipment list */}
      <div style={{marginTop:'16px',background:'var(--surface)',border:'1px solid var(--border)',borderRadius:'12px',padding:'16px'}}>
        <div style={{fontSize:'11px',fontFamily:'var(--mono)',color:'var(--text3)',fontWeight:600,marginBottom:'12px',letterSpacing:'.06em'}}>設備配置リスト — 3Dシーンと連動</div>
        <div className="mobile-stack" style={{display:'grid',gridTemplateColumns:'repeat(2,1fr)',gap:'6px'}}>
          {[...EQUIPMENT_1F.map(e=>({...e,floor:'1F'})), ...EQUIPMENT_2F.map(e=>({...e,floor:'2F'}))].map((e,i) => {
            const colorHex = '#' + e.color.toString(16).padStart(6,'0');
            return (
              <div key={i} style={{display:'flex',alignItems:'center',gap:'10px',padding:'8px 10px',background:'var(--surface2)',border:'1px solid var(--border)',borderRadius:'8px'}}>
                <span style={{fontSize:'16px'}}>{e.icon}</span>
                <div style={{flex:1,minWidth:0}}>
                  <div style={{fontSize:'12px',fontWeight:600,color:'var(--text)'}}>{e.name}</div>
                  <div style={{fontSize:'10px',color:'var(--text3)',fontFamily:'var(--mono)'}}>{e.floor} · {e.pos || e.room}</div>
                </div>
                <span style={{fontSize:'9px',fontFamily:'var(--mono)',fontWeight:700,padding:'2px 6px',borderRadius:'4px',background:`${colorHex}22`,color:colorHex,border:`1px solid ${colorHex}44`}}>{e.tag}</span>
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
}

function btn(active, accent) {
  return {
    padding:'7px 12px',
    fontSize:'12px',
    fontWeight: active ? 600 : 400,
    border:'1px solid',
    borderColor: active ? accent : 'var(--border)',
    background: active ? `${accent}22` : 'var(--surface)',
    color: active ? accent : 'var(--text2)',
    borderRadius:'7px',
    transition:'all .15s',
    fontFamily:'var(--sans)',
    cursor:'pointer',
  };
}

function Legend({dot, label}) {
  return (
    <div style={{display:'flex',alignItems:'center',gap:'6px'}}>
      <span style={{width:'8px',height:'8px',borderRadius:'50%',background:dot,boxShadow:`0 0 6px ${dot}80`}} />
      <span style={{color:'var(--text2)'}}>{label}</span>
    </div>
  );
}

window.Visualizer3D = Visualizer3D;
