/* global React, THREE */
// 3D calpinage studio — Three.js rooftop with real tilt, sun-driven shadows, orbit camera,
// auto-pack + manual drag placement of PV panels.

const { useRef, useEffect, useState, useMemo } = React;

function Solar3DStudio({ project, onBack }) {
  const mountRef = useRef(null);
  const stateRef = useRef({});
  const [sunHour, setSunHour] = useState(13);
  const [panelCount, setPanelCount] = useState(0);
  const [mode, setMode] = useState('auto'); // auto | manual | delete
  const [dragging, setDragging] = useState(false);
  const [stats, setStats] = useState({ kwc: 0, prod: 0 });
  const [orbitLock, setOrbitLock] = useState(false);
  const [showGhost, setShowGhost] = useState(true);

  // Roof geometry constants — shared between auto-pack and 3D build
  const ROOF = useMemo(() => ({
    w: 28, d: 18, h: 6, tilt: (project?.tilt || 12) * Math.PI / 180,
    orientation: (project?.orientation || 180) * Math.PI / 180,
  }), [project?.ref]);

  const PANEL = { w: 1.72, h: 1.13, wc: 400 }; // Trina Vertex S+

  // ── Three.js setup ───────────────────────────────────────
  useEffect(() => {
    if (!window.THREE) 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(0x0e1010);
    scene.fog = new THREE.Fog(0x0e1010, 80, 180);

    const camera = new THREE.PerspectiveCamera(45, W / H, 0.1, 500);
    camera.position.set(38, 30, 38);
    camera.lookAt(0, 2, 0);

    const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false });
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    renderer.setSize(W, H);
    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    renderer.outputColorSpace = THREE.SRGBColorSpace;
    renderer.toneMapping = THREE.ACESFilmicToneMapping;
    renderer.toneMappingExposure = 1.05;
    mount.appendChild(renderer.domElement);

    // ── Lights
    const ambient = new THREE.HemisphereLight(0x88aacc, 0x1a1a22, 0.35);
    scene.add(ambient);

    const sun = new THREE.DirectionalLight(0xffe8b8, 2.2);
    sun.castShadow = true;
    sun.shadow.mapSize.set(2048, 2048);
    sun.shadow.camera.near = 5;
    sun.shadow.camera.far = 150;
    sun.shadow.camera.left = -40;
    sun.shadow.camera.right = 40;
    sun.shadow.camera.top = 40;
    sun.shadow.camera.bottom = -40;
    sun.shadow.bias = -0.0005;
    scene.add(sun);
    scene.add(sun.target);

    // Sun visual (disc)
    const sunDisc = new THREE.Mesh(
      new THREE.SphereGeometry(1.2, 24, 24),
      new THREE.MeshBasicMaterial({ color: 0xfff1b0 })
    );
    scene.add(sunDisc);

    const sunGlow = new THREE.Mesh(
      new THREE.SphereGeometry(2.2, 24, 24),
      new THREE.MeshBasicMaterial({ color: 0xffd95e, transparent: true, opacity: 0.22, side: THREE.BackSide })
    );
    scene.add(sunGlow);

    // ── Ground
    const groundGeo = new THREE.PlaneGeometry(200, 200, 40, 40);
    const groundMat = new THREE.MeshStandardMaterial({
      color: 0x2a3528, roughness: 0.95, metalness: 0,
    });
    const ground = new THREE.Mesh(groundGeo, groundMat);
    ground.rotation.x = -Math.PI / 2;
    ground.receiveShadow = true;
    scene.add(ground);

    // Grid overlay
    const grid = new THREE.GridHelper(200, 40, 0x1a2018, 0x151a14);
    grid.position.y = 0.01;
    scene.add(grid);

    // ── Building block
    const bldgHeight = ROOF.h;
    const bldg = new THREE.Mesh(
      new THREE.BoxGeometry(ROOF.w, bldgHeight, ROOF.d),
      new THREE.MeshStandardMaterial({ color: 0xb8a88a, roughness: 0.88 })
    );
    bldg.position.y = bldgHeight / 2;
    bldg.castShadow = true;
    bldg.receiveShadow = true;
    scene.add(bldg);

    // ── Roof plane (tilted)
    const roofGroup = new THREE.Group();
    roofGroup.position.y = bldgHeight;
    scene.add(roofGroup);

    const roofGeo = new THREE.BoxGeometry(ROOF.w, 0.3, ROOF.d);
    const roofMat = new THREE.MeshStandardMaterial({
      color: 0x6d5c48, roughness: 0.78, metalness: 0.05,
    });
    const roof = new THREE.Mesh(roofGeo, roofMat);
    roof.position.y = 0.15;
    roof.castShadow = true;
    roof.receiveShadow = true;
    roofGroup.add(roof);

    // Tilt the roof around X axis (pente vers le sud)
    roofGroup.rotation.x = -ROOF.tilt;
    roofGroup.rotation.y = ROOF.orientation - Math.PI; // align to orientation

    // Roof edges / trim
    const edges = new THREE.LineSegments(
      new THREE.EdgesGeometry(roofGeo),
      new THREE.LineBasicMaterial({ color: 0x3a2e22 })
    );
    edges.position.y = 0.15;
    roofGroup.add(edges);

    // ── Obstacles on roof (velux, cheminée)
    const obstacles = [
      { x: -4, z: -2, w: 2.5, h: 0.5, d: 1.2, color: 0x2a3a4a, label: 'velux ×3' },
      { x: 8, z: 4, w: 1.2, h: 1.8, d: 1.2, color: 0x888078, label: 'cheminée' },
      { x: -10, z: 5, w: 1.6, h: 0.8, d: 1.0, color: 0x4a5560, label: 'VMC' },
    ];
    obstacles.forEach(o => {
      const m = new THREE.Mesh(
        new THREE.BoxGeometry(o.w, o.h, o.d),
        new THREE.MeshStandardMaterial({ color: o.color, roughness: 0.7 })
      );
      m.position.set(o.x, 0.3 + o.h / 2, o.z);
      m.castShadow = true;
      m.receiveShadow = true;
      m.userData.obstacle = true;
      m.userData.bounds = { x: o.x, z: o.z, w: o.w + 0.3, d: o.d + 0.3 };
      roofGroup.add(m);
    });

    // ── Neighbor trees (cast shadows)
    const treeMat = new THREE.MeshStandardMaterial({ color: 0x2f4a2a, roughness: 0.95 });
    [[-22, -18], [24, 20], [-28, 12], [30, -20], [-18, 22]].forEach(([x, z]) => {
      const trunk = new THREE.Mesh(
        new THREE.CylinderGeometry(0.3, 0.4, 3.5, 8),
        new THREE.MeshStandardMaterial({ color: 0x3a2818, roughness: 1 })
      );
      trunk.position.set(x, 1.75, z);
      trunk.castShadow = true;
      scene.add(trunk);
      const crown = new THREE.Mesh(new THREE.IcosahedronGeometry(2.6, 1), treeMat);
      crown.position.set(x, 5, z);
      crown.castShadow = true;
      scene.add(crown);
    });

    // ── Neighbor buildings
    [{ x: -30, z: 8, w: 12, h: 4, d: 8 }, { x: 28, z: -12, w: 10, h: 5, d: 14 }].forEach(b => {
      const m = new THREE.Mesh(
        new THREE.BoxGeometry(b.w, b.h, b.d),
        new THREE.MeshStandardMaterial({ color: 0x8a7a68, roughness: 0.9 })
      );
      m.position.set(b.x, b.h / 2, b.z);
      m.castShadow = true;
      m.receiveShadow = true;
      scene.add(m);
    });

    // ── Panel group
    const panelGroup = new THREE.Group();
    roofGroup.add(panelGroup);

    // Ghost (cursor follower in manual mode)
    const ghostGeo = new THREE.BoxGeometry(PANEL.w, 0.04, PANEL.h);
    const ghostMat = new THREE.MeshBasicMaterial({ color: 0x3fe07c, transparent: true, opacity: 0.4 });
    const ghost = new THREE.Mesh(ghostGeo, ghostMat);
    ghost.visible = false;
    roofGroup.add(ghost);

    // ── Orbit controls (hand-rolled — no OrbitControls import)
    let theta = Math.PI / 4, phi = Math.PI / 3.2, dist = 55;
    const target = new THREE.Vector3(0, 3, 0);
    let pointerDown = false, lastX = 0, lastY = 0, pointerMode = null;

    function updateCamera() {
      camera.position.x = target.x + dist * Math.sin(phi) * Math.cos(theta);
      camera.position.y = target.y + dist * Math.cos(phi);
      camera.position.z = target.z + dist * Math.sin(phi) * Math.sin(theta);
      camera.lookAt(target);
    }
    updateCamera();

    renderer.domElement.addEventListener('pointerdown', (e) => {
      pointerDown = true; lastX = e.clientX; lastY = e.clientY;
      pointerMode = e.button === 2 || e.shiftKey ? 'pan' : 'orbit';
    });
    renderer.domElement.addEventListener('contextmenu', e => e.preventDefault());
    window.addEventListener('pointerup', () => { pointerDown = false; pointerMode = null; });
    renderer.domElement.addEventListener('pointermove', (e) => {
      if (!pointerDown || stateRef.current.orbitLock) {
        if (stateRef.current.mode === 'manual') onHoverManual(e);
        return;
      }
      const dx = e.clientX - lastX, dy = e.clientY - lastY;
      lastX = e.clientX; lastY = e.clientY;
      if (pointerMode === 'orbit') {
        theta -= dx * 0.005;
        phi = Math.max(0.1, Math.min(Math.PI / 2 - 0.05, phi - dy * 0.005));
      } else {
        const panSpeed = dist * 0.0015;
        const right = new THREE.Vector3(Math.sin(theta + Math.PI / 2), 0, -Math.cos(theta + Math.PI / 2));
        const forward = new THREE.Vector3(-Math.sin(theta), 0, Math.cos(theta));
        target.addScaledVector(right, -dx * panSpeed);
        target.addScaledVector(forward, -dy * panSpeed);
      }
      updateCamera();
    });
    renderer.domElement.addEventListener('wheel', (e) => {
      e.preventDefault();
      dist = Math.max(20, Math.min(120, dist + e.deltaY * 0.05));
      updateCamera();
    }, { passive: false });

    // Raycaster for click/hover on roof
    const raycaster = new THREE.Raycaster();
    const pointer = new THREE.Vector2();

    function pointerToRoofLocal(clientX, clientY) {
      const rect = renderer.domElement.getBoundingClientRect();
      pointer.x = ((clientX - rect.left) / rect.width) * 2 - 1;
      pointer.y = -((clientY - rect.top) / rect.height) * 2 + 1;
      raycaster.setFromCamera(pointer, camera);
      const hits = raycaster.intersectObject(roof, false);
      if (!hits.length) return null;
      const worldPoint = hits[0].point;
      // transform to roofGroup local space
      const local = roofGroup.worldToLocal(worldPoint.clone());
      return local;
    }

    function onHoverManual(e) {
      const p = pointerToRoofLocal(e.clientX, e.clientY);
      if (!p) { ghost.visible = false; return; }
      // snap to grid
      const gridX = 2.0, gridZ = 1.4;
      const sx = Math.round(p.x / gridX) * gridX;
      const sz = Math.round(p.z / gridZ) * gridZ;
      ghost.position.set(sx, 0.32, sz);
      ghost.visible = stateRef.current.showGhost !== false;
      // collision check
      const inObstacle = obstacles.some(o => Math.abs(sx - o.x) < (o.w / 2 + PANEL.w / 2) && Math.abs(sz - o.z) < (o.d / 2 + PANEL.h / 2));
      const inBounds = Math.abs(sx) < ROOF.w / 2 - PANEL.w / 2 && Math.abs(sz) < ROOF.d / 2 - PANEL.h / 2;
      ghost.material.color.setHex(!inObstacle && inBounds ? 0x3fe07c : 0xd94646);
    }

    renderer.domElement.addEventListener('click', (e) => {
      if (stateRef.current.mode !== 'manual' && stateRef.current.mode !== 'delete') return;
      const p = pointerToRoofLocal(e.clientX, e.clientY);
      if (!p) return;
      if (stateRef.current.mode === 'manual') {
        const gridX = 2.0, gridZ = 1.4;
        const sx = Math.round(p.x / gridX) * gridX;
        const sz = Math.round(p.z / gridZ) * gridZ;
        const inObstacle = obstacles.some(o => Math.abs(sx - o.x) < (o.w / 2 + PANEL.w / 2) && Math.abs(sz - o.z) < (o.d / 2 + PANEL.h / 2));
        const inBounds = Math.abs(sx) < ROOF.w / 2 - PANEL.w / 2 && Math.abs(sz) < ROOF.d / 2 - PANEL.h / 2;
        const overlap = panelGroup.children.some(pan => Math.abs(sx - pan.position.x) < 1.9 && Math.abs(sz - pan.position.z) < 1.3);
        if (!inObstacle && inBounds && !overlap) addPanel(sx, sz);
      } else if (stateRef.current.mode === 'delete') {
        // raycast against panels
        raycaster.setFromCamera(pointer, camera);
        const hits = raycaster.intersectObjects(panelGroup.children.map(g => g.children[0]), false);
        if (hits.length) {
          const root = hits[0].object.parent;
          panelGroup.remove(root);
          updateStats();
        }
      }
    });

    // ── Panel builder
    function buildPanel() {
      const g = new THREE.Group();
      // Glass face
      const face = new THREE.Mesh(
        new THREE.BoxGeometry(PANEL.w, 0.04, PANEL.h),
        new THREE.MeshStandardMaterial({
          color: 0x0f2340, roughness: 0.2, metalness: 0.8,
          emissive: 0x071530, emissiveIntensity: 0.15,
        })
      );
      face.castShadow = true;
      face.receiveShadow = true;
      g.add(face);
      // Cell grid lines
      const lineMat = new THREE.LineBasicMaterial({ color: 0x2a4a7a, transparent: true, opacity: 0.6 });
      for (let i = 1; i < 6; i++) {
        const x = -PANEL.w / 2 + i * PANEL.w / 6;
        const pts = [new THREE.Vector3(x, 0.021, -PANEL.h / 2), new THREE.Vector3(x, 0.021, PANEL.h / 2)];
        g.add(new THREE.Line(new THREE.BufferGeometry().setFromPoints(pts), lineMat));
      }
      for (let j = 1; j < 10; j++) {
        const z = -PANEL.h / 2 + j * PANEL.h / 10;
        const pts = [new THREE.Vector3(-PANEL.w / 2, 0.021, z), new THREE.Vector3(PANEL.w / 2, 0.021, z)];
        g.add(new THREE.Line(new THREE.BufferGeometry().setFromPoints(pts), lineMat));
      }
      // Frame
      const frameMat = new THREE.MeshStandardMaterial({ color: 0xaaaaaa, metalness: 0.7, roughness: 0.4 });
      const frameThickness = 0.04;
      [
        [PANEL.w, frameThickness, 0, 0, 0.02, -PANEL.h / 2],
        [PANEL.w, frameThickness, 0, 0, 0.02, PANEL.h / 2],
        [frameThickness, frameThickness, PANEL.h, -PANEL.w / 2, 0.02, 0],
        [frameThickness, frameThickness, PANEL.h, PANEL.w / 2, 0.02, 0],
      ].forEach(([w, h, d, x, y, z]) => {
        const m = new THREE.Mesh(new THREE.BoxGeometry(w, h, d || frameThickness), frameMat);
        m.position.set(x, y, z);
        g.add(m);
      });
      return g;
    }

    function addPanel(x, z) {
      const p = buildPanel();
      p.position.set(x, 0.32, z);
      p.userData.isPanel = true;
      panelGroup.add(p);
      updateStats();
    }

    function clearPanels() {
      while (panelGroup.children.length) panelGroup.remove(panelGroup.children[0]);
      updateStats();
    }

    function autoPack() {
      clearPanels();
      const gridX = 2.0, gridZ = 1.4;
      const cols = Math.floor((ROOF.w - 1) / gridX);
      const rows = Math.floor((ROOF.d - 1) / gridZ);
      const startX = -(cols - 1) * gridX / 2;
      const startZ = -(rows - 1) * gridZ / 2;
      for (let r = 0; r < rows; r++) {
        for (let c = 0; c < cols; c++) {
          const x = startX + c * gridX;
          const z = startZ + r * gridZ;
          const inObstacle = obstacles.some(o => Math.abs(x - o.x) < (o.w / 2 + PANEL.w / 2) && Math.abs(z - o.z) < (o.d / 2 + PANEL.h / 2));
          if (!inObstacle) addPanel(x, z);
        }
      }
    }

    function updateStats() {
      const n = panelGroup.children.length;
      const kwc = n * PANEL.wc / 1000;
      const prod = Math.round(kwc * 1150); // ~1150 kWh/kWc Le Mans
      setPanelCount(n);
      setStats({ kwc: kwc.toFixed(1), prod });
    }

    // Auto-pack on mount
    autoPack();

    // ── Sun positioning — formule astronomique réelle (NREL simplifié)
    // lat depuis project, date = aujourd'hui (équinoxe si pas de date réelle)
    const projectLat = (project && project.lat != null) ? project.lat : 48.0;
    function sunAngles(hour, dayOfYear) {
      const latRad = projectLat * Math.PI / 180;
      const decl = 23.45 * Math.PI / 180 * Math.sin(2 * Math.PI * (284 + dayOfYear) / 365);
      const hourAngle = (hour - 12) * 15 * Math.PI / 180;
      const sinAlt = Math.sin(latRad) * Math.sin(decl) + Math.cos(latRad) * Math.cos(decl) * Math.cos(hourAngle);
      const altitude = Math.asin(Math.max(-1, Math.min(1, sinAlt)));
      const cosAlt = Math.cos(altitude);
      let azimuth = 0;
      if (cosAlt > 0.001) {
        const cosAz = (Math.sin(decl) - Math.sin(altitude) * Math.sin(latRad)) / (cosAlt * Math.cos(latRad));
        azimuth = Math.acos(Math.max(-1, Math.min(1, cosAz)));
        if (hourAngle > 0) azimuth = 2 * Math.PI - azimuth; // après-midi à l'ouest
      }
      return { altitude, azimuth };
    }
    function setSunTime(hour) {
      // Date actuelle — jour de l'année
      const now = new Date();
      const start = new Date(now.getFullYear(), 0, 0);
      const dayOfYear = Math.floor((now - start) / 86400000);
      const { altitude, azimuth } = sunAngles(hour, dayOfYear);
      const r = 80;
      // En 3D : X = est (+), Z = sud (-), Y = haut
      // azimuth 0 = nord, π = sud, π/2 = est, 3π/2 = ouest
      const sx = Math.sin(azimuth) * Math.cos(altitude) * r;
      const sz = -Math.cos(azimuth) * Math.cos(altitude) * r;
      const sy = Math.sin(altitude) * r;
      if (sy > 0) {
        sun.position.set(sx, sy, sz);
        sun.visible = true;
        sunDisc.visible = true;
        sunGlow.visible = true;
      } else {
        sun.visible = false;
        sunDisc.visible = false;
        sunGlow.visible = false;
      }
      sun.target.position.set(0, 3, 0);
      const elev01 = Math.max(0, Math.sin(altitude));
      sun.intensity = 0.3 + elev01 * 2.2;
      sunDisc.position.copy(sun.position);
      sunGlow.position.copy(sun.position);
      // Sky tint — warmer au lever/coucher
      const warmth = 1 - Math.min(1, elev01 * 1.2);
      const skyColor = new THREE.Color().setHSL(0.58 - warmth * 0.08, 0.25 + warmth * 0.2, 0.06 + elev01 * 0.04);
      scene.background = skyColor;
      scene.fog.color = skyColor;
      ground.material.color.setHSL(0.25, 0.15, 0.08 + elev01 * 0.05);
    }
    setSunTime(13);

    // ── Animate
    let raf;
    function tick() {
      renderer.render(scene, camera);
      raf = requestAnimationFrame(tick);
    }
    tick();

    // ── Resize
    function onResize() {
      const w = mount.clientWidth, h = mount.clientHeight;
      camera.aspect = w / h; camera.updateProjectionMatrix();
      renderer.setSize(w, h);
    }
    window.addEventListener('resize', onResize);

    // Expose to outer React
    stateRef.current = {
      mode: 'auto', setSunTime, autoPack, clearPanels, orbitLock: false, showGhost: true,
      setMode: (m) => { stateRef.current.mode = m; ghost.visible = m === 'manual'; },
      getPanelCount: () => panelGroup.children.length,
    };
    updateStats();

    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener('resize', onResize);
      renderer.dispose();
      mount.removeChild(renderer.domElement);
    };
  }, [project?.ref]);

  // Sync reactive props
  useEffect(() => { stateRef.current.setSunTime?.(sunHour); }, [sunHour]);
  useEffect(() => { stateRef.current.setMode?.(mode); }, [mode]);
  useEffect(() => { stateRef.current.orbitLock = orbitLock; }, [orbitLock]);
  useEffect(() => { stateRef.current.showGhost = showGhost; }, [showGhost]);

  return (
    <div style={{ position: 'absolute', inset: 0, background: '#0e1010' }}>
      <div ref={mountRef} style={{ position: 'absolute', inset: 0 }} />

      {/* Top-left — back + title */}
      <div style={{ position: 'absolute', left: 16, top: 16, display: 'flex', alignItems: 'center', gap: 10 }}>
        <button onClick={onBack} style={{
          padding: '6px 12px', background: 'rgba(14,16,16,0.82)', color: '#fff',
          border: '1px solid rgba(255,255,255,0.12)', borderRadius: 6, fontSize: 12,
          display: 'flex', alignItems: 'center', gap: 6, backdropFilter: 'blur(6px)',
        }}>
          <svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M10 3 5 8l5 5"/></svg>
          Vue 2D
        </button>
        <div style={{
          padding: '4px 10px', background: 'rgba(63,224,124,0.18)',
          border: '1px solid rgba(63,224,124,0.4)', borderRadius: 4,
          color: '#9ff29a', fontSize: 10, fontFamily: 'var(--font-mono)',
          textTransform: 'uppercase', letterSpacing: 0.6, fontWeight: 600,
        }}>3D Studio · WebGL</div>
      </div>

      {/* Top-right — stats HUD */}
      <div style={{
        position: 'absolute', right: 16, top: 16,
        padding: '12px 14px',
        background: 'rgba(14,16,16,0.82)', border: '1px solid rgba(255,255,255,0.12)',
        borderRadius: 6, color: '#fff', backdropFilter: 'blur(6px)',
        fontFamily: 'var(--font-mono)', fontSize: 11, minWidth: 180,
        boxShadow: '0 10px 24px rgba(0,0,0,0.35)',
      }}>
        <div style={{ fontSize: 9, color: 'rgba(255,255,255,0.55)', textTransform: 'uppercase', letterSpacing: 0.6, fontWeight: 600, marginBottom: 6 }}>Calpinage live</div>
        <Line3D k="panneaux" v={panelCount} c="#9ff29a" />
        <Line3D k="puiss. crête" v={`${stats.kwc} kWc`} c="#9ff29a" />
        <Line3D k="prod. estim." v={`${Math.round(stats.prod / 1000).toLocaleString('fr-FR')} MWh/an`} />
        <Line3D k="surface util." v={`${(panelCount * 1.94).toFixed(0)} m²`} />
        <div style={{ height: 1, background: 'rgba(255,255,255,0.08)', margin: '6px 0' }} />
        <Line3D k="pente" v={`${project?.tilt || 12}°`} />
        <Line3D k="orient." v={`${project?.orientation || 180}°`} />
      </div>

      {/* Bottom-left — tool palette */}
      <div style={{
        position: 'absolute', left: 16, bottom: 16,
        padding: 4, background: 'rgba(14,16,16,0.85)',
        border: '1px solid rgba(255,255,255,0.12)', borderRadius: 6,
        backdropFilter: 'blur(6px)', display: 'flex', gap: 2,
        boxShadow: '0 10px 24px rgba(0,0,0,0.4)',
      }}>
        <ModeBtn active={mode === 'auto'} onClick={() => { setMode('auto'); stateRef.current.autoPack?.(); }} icon="⊞" label="Auto-pack" desc="Calpinage auto" />
        <ModeBtn active={mode === 'manual'} onClick={() => setMode('manual')} icon="+" label="Placer" desc="Clic pour poser" />
        <ModeBtn active={mode === 'delete'} onClick={() => setMode('delete')} icon="⊘" label="Supprimer" desc="Clic pour retirer" />
        <div style={{ width: 1, background: 'rgba(255,255,255,0.1)', margin: '0 2px' }} />
        <ModeBtn onClick={() => stateRef.current.clearPanels?.()} icon="⎚" label="Vider" desc="Retirer tout" />
        <ModeBtn onClick={() => setOrbitLock(l => !l)} icon={orbitLock ? '🔒' : '🔓'} label={orbitLock ? 'Orbite bloquée' : 'Orbite libre'} desc="Caméra" active={orbitLock} noIconFont />
      </div>

      {/* Bottom-center — sun timeline */}
      <div style={{
        position: 'absolute', left: '50%', bottom: 20, transform: 'translateX(-50%)',
        padding: '10px 16px', background: 'rgba(14,16,16,0.85)',
        border: '1px solid rgba(255,255,255,0.12)', borderRadius: 8,
        backdropFilter: 'blur(6px)', color: '#fff', minWidth: 380,
        boxShadow: '0 10px 24px rgba(0,0,0,0.4)',
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 6 }}>
          <div style={{ fontSize: 10, color: 'rgba(255,255,255,0.5)', textTransform: 'uppercase', letterSpacing: 0.7, fontWeight: 600 }}>course du soleil · 21 juin</div>
          <div style={{ flex: 1 }} />
          <div className="mono" style={{ fontSize: 12, color: '#ffe08a', fontWeight: 600 }}>
            {String(Math.floor(sunHour)).padStart(2, '0')}:{String(Math.round((sunHour % 1) * 60)).padStart(2, '0')}
          </div>
          <button onClick={() => {
            const id = setInterval(() => {
              setSunHour(h => {
                if (h >= 20) { clearInterval(id); return 20; }
                return h + 0.15;
              });
            }, 50);
          }} title="Lecture timelapse" style={{ color: '#9ff29a', fontSize: 11, padding: '2px 6px', border: '1px solid rgba(159,242,154,0.3)', borderRadius: 3 }}>
            ▶ lecture
          </button>
        </div>
        <div style={{ position: 'relative', height: 32 }}>
          <input
            type="range" min={5.5} max={20.5} step={0.1} value={sunHour}
            onChange={e => setSunHour(parseFloat(e.target.value))}
            style={{ width: '100%', accentColor: '#ffe08a' }}
          />
          <div style={{ position: 'absolute', bottom: -4, left: 0, right: 0, display: 'flex', justifyContent: 'space-between', fontSize: 9, color: 'rgba(255,255,255,0.35)', fontFamily: 'var(--font-mono)' }}>
            <span>06h</span><span>09h</span><span>12h</span><span>15h</span><span>18h</span><span>21h</span>
          </div>
        </div>
      </div>

      {/* Controls hint */}
      <div style={{
        position: 'absolute', right: 16, bottom: 140,
        fontSize: 10, color: 'rgba(255,255,255,0.5)',
        fontFamily: 'var(--font-mono)', textAlign: 'right',
        lineHeight: 1.6,
      }}>
        <div>drag · orbite</div>
        <div>shift+drag · pan</div>
        <div>scroll · zoom</div>
      </div>

      {mode === 'manual' && (
        <div style={{
          position: 'absolute', left: '50%', top: 16, transform: 'translateX(-50%)',
          padding: '6px 12px', background: 'rgba(63,224,124,0.2)',
          border: '1px solid rgba(63,224,124,0.5)', borderRadius: 999,
          color: '#9ff29a', fontSize: 11, fontWeight: 500,
          backdropFilter: 'blur(6px)',
        }}>
          Mode placement · clique sur la toiture pour poser un panneau
        </div>
      )}
    </div>
  );
}

function Line3D({ k, v, c }) {
  return (
    <div style={{ display: 'flex', justifyContent: 'space-between', padding: '2px 0', fontSize: 11 }}>
      <span style={{ color: 'rgba(255,255,255,0.5)' }}>{k}</span>
      <span style={{ color: c || '#fff', fontWeight: 600 }}>{v}</span>
    </div>
  );
}

function ModeBtn({ active, onClick, icon, label, desc, noIconFont }) {
  return (
    <button onClick={onClick} title={desc} style={{
      padding: '8px 12px', background: active ? 'rgba(63,224,124,0.22)' : 'transparent',
      color: active ? '#9ff29a' : 'rgba(255,255,255,0.75)',
      border: active ? '1px solid rgba(63,224,124,0.5)' : '1px solid transparent',
      borderRadius: 4, fontSize: 11, fontWeight: 500,
      display: 'flex', alignItems: 'center', gap: 6,
      transition: 'all 120ms',
    }}>
      <span style={{ fontFamily: noIconFont ? 'inherit' : 'var(--font-mono)', fontSize: 14 }}>{icon}</span>
      <span>{label}</span>
    </button>
  );
}

Object.assign(window, { Solar3DStudio });
