/* Grundriss-Viewer · Phase 3
   Dynamisches Layout aus Konfigurator-Daten.
   - Räume aus useKonfiguratorState (gehoben in App)
   - Squarified-Treemap-Algorithmus pro Stockwerk
   - Feature-Mapping zu Konfigurator-Modulen mit Preisen
   - WiFi-AP-Auto-Empfehlung skaliert mit Fläche
*/

const GV_PALETTE = {
  bg:     "#071520",
  card:   "#0D2030",
  accent: "#3ABDD0",
  soft:   "#8DDBEA",
  text:   "#ffffff",
  muted:  "rgba(141,219,234,0.55)",
  line:   "rgba(141,219,234,0.18)",
  dim:    "rgba(141,219,234,0.25)",
};

/* Modul-Metadaten — verbinden Konfigurator-Keys mit Anzeige */
const GV_MODULE_META = {
  licht:       { lbl:"Lichtsteuerung",       tier:"€€"  },
  beschattung: { lbl:"Beschattung / Raffstore", tier:"€€€" },
  heizung:     { lbl:"Heizungsregelung",     tier:"€"   },
  klima:       { lbl:"Klima / Lüftung",      tier:"€€"  },
  zutritt:     { lbl:"Zutritt / Sicherheit", tier:"€€"  },
  multimedia:  { lbl:"Multiroom Audio",      tier:"€€"  },
  sensorik:    { lbl:"Sensorik (Präsenz/CO₂)", tier:"€" },
  steckdosen:  { lbl:"Schaltbare Steckdosen", tier:"€"  },
  energie:     { lbl:"Energiemonitoring",     tier:"€€" },
  alarm:       { lbl:"Einbruch / Brand",      tier:"€€" },
};

/* AP-Modelle */
const GV_AP_MODELS = {
  small: { name:"WLAN AP · Indoor (Decke)",         price: 220, radius: 8 },
  large: { name:"WLAN AP · Indoor Long-Range",      price: 320, radius: 11 },
};

/* ============================================================
   Squarified-Treemap-Algorithmus
   Verteilt Räume eines Stockwerks proportional zu ihrer Fläche.
============================================================ */
function squarify(rooms, x, y, w, h) {
  if (rooms.length === 0) return [];
  if (rooms.length === 1) {
    return [{ ...rooms[0], rect: { x, y, w, h } }];
  }

  // Sortiere nach Fläche absteigend
  const sorted = [...rooms].sort((a, b) => b.area - a.area);
  const totalArea = sorted.reduce((s, r) => s + r.area, 0);

  // Skalierung: Mappe Räume auf Pixel-Werte
  const scale = (w * h) / totalArea;
  const items = sorted.map(r => ({ ...r, value: r.area * scale }));

  return layoutSquarify(items, x, y, w, h);
}

function layoutSquarify(items, x, y, w, h) {
  const result = [];
  let remaining = [...items];
  let curX = x, curY = y, curW = w, curH = h;

  while (remaining.length > 0) {
    const isHorizontal = curW >= curH;
    const shortSide = Math.min(curW, curH);

    // Greedy: füge Items zu Row hinzu, solange aspect ratio sich verbessert
    let row = [];
    let bestRatio = Infinity;
    let i = 0;
    while (i < remaining.length) {
      const candidate = [...row, remaining[i]];
      const ratio = worstRatio(candidate, shortSide);
      if (ratio > bestRatio) break;
      bestRatio = ratio;
      row = candidate;
      i++;
    }

    // Layout der Row
    const rowSum = row.reduce((s, r) => s + r.value, 0);
    const longSide = rowSum / shortSide;

    let pos = 0;
    for (const r of row) {
      const segment = r.value / shortSide;
      if (isHorizontal) {
        result.push({ ...r, rect: { x: curX, y: curY + pos, w: longSide, h: segment } });
      } else {
        result.push({ ...r, rect: { x: curX + pos, y: curY, w: segment, h: longSide } });
      }
      pos += segment;
    }

    // Verschiebe verbleibenden Bereich
    if (isHorizontal) {
      curX += longSide;
      curW -= longSide;
    } else {
      curY += longSide;
      curH -= longSide;
    }
    remaining = remaining.slice(row.length);
  }

  return result;
}

function worstRatio(items, side) {
  if (items.length === 0) return Infinity;
  const sum = items.reduce((s, r) => s + r.value, 0);
  let max = 0;
  for (const r of items) {
    const a = (side * side * r.value) / (sum * sum);
    const b = (sum * sum) / (side * side * r.value);
    max = Math.max(max, a, b);
  }
  return max;
}

/* ============================================================
   AP-Empfehlung berechnen
============================================================ */
function recommendAPs(rectsByFloor) {
  const aps = [];
  let idx = 1;
  for (const [floor, rects] of Object.entries(rectsByFloor)) {
    if (rects.length === 0) continue;
    const totalArea = rects.reduce((s, r) => s + (r.area || 0), 0);
    if (totalArea < 25) continue;

    // 1 AP / ~80m² (gerundet, mind. 1)
    const count = Math.max(1, Math.ceil(totalArea / 80));
    const model = totalArea > 120 ? "large" : "small";

    // Bounding box der Räume
    const minX = Math.min(...rects.map(r => r.rect.x));
    const minY = Math.min(...rects.map(r => r.rect.y));
    const maxX = Math.max(...rects.map(r => r.rect.x + r.rect.w));
    const maxY = Math.max(...rects.map(r => r.rect.y + r.rect.h));

    if (count === 1) {
      aps.push({
        id: `ap-${floor}-${idx++}`,
        floor, model,
        x: (minX + maxX) / 2,
        y: (minY + maxY) / 2,
      });
    } else {
      // Verteile entlang der längeren Achse
      const spanX = maxX - minX, spanY = maxY - minY;
      const horizontal = spanX >= spanY;
      for (let i = 0; i < count; i++) {
        const t = (i + 0.5) / count;
        aps.push({
          id: `ap-${floor}-${idx++}`,
          floor, model,
          x: horizontal ? minX + t * spanX : (minX + maxX) / 2,
          y: horizontal ? (minY + maxY) / 2 : minY + t * spanY,
        });
      }
    }
  }
  return aps;
}

/* ============================================================
   Hauptkomponente
============================================================ */
function GrundrissViewerSection({ pathKey, kfgState }) {
  const rooms       = kfgState?.rooms || [];
  const assignments = kfgState?.assignments || {};
  const modules     = kfgState?.modules || {};

  const indoorRooms = rooms.filter(r => !r.outdoor);
  const floors = Array.from(new Set(indoorRooms.map(r => r.floor)));
  const [activeFloor, setActiveFloor] = React.useState(floors[0] || "EG");
  const [hovered, setHovered] = React.useState(null);
  const [showWifi, setShowWifi] = React.useState(false);
  const [showLabels, setShowLabels] = React.useState(true);
  const [apDetail, setApDetail] = React.useState(null);

  React.useEffect(() => {
    if (floors.length && !floors.includes(activeFloor)) setActiveFloor(floors[0]);
  }, [floors.join(",")]);

  // Layout-Strategie pro Stockwerk:
  //   1. BASTING_GEOM (Demo-Plan): hardcoded Pixel-Koords des EFH-Basting-Plans
  //   2. Squarified-Treemap: für hochgeladene Pläne UND manuelle Erfassung.
  //      Ordnet flächentreu an, Fenster/Türen werden generisch auf den
  //      passenden Wand-Seiten platziert (windowSides/doorSide vom LLM).
  const VB_W = 1000, VB_H = 560, PAD = 50;
  const layoutByFloor = React.useMemo(() => {
    const out = {};
    const BG = window.BASTING_GEOM;
    const BV = window.BASTING_GEOM_VIEWBOX;

    for (const f of floors) {
      const fr = indoorRooms.filter(r => r.floor === f);
      const allHaveBasting = BG && BV && fr.length > 0 && fr.every(r => BG[r.id]);

      if (allHaveBasting) {
        const sx = (VB_W - 2*PAD) / BV.w;
        const sy = (VB_H - 2*PAD) / BV.h;
        const s = Math.min(sx, sy);
        const offX = (VB_W - BV.w * s) / 2;
        const offY = (VB_H - BV.h * s) / 2;
        out[f] = fr.map(r => {
          const g = BG[r.id];
          return { ...r, rect: {
            x: offX + g.x * s,
            y: offY + g.y * s,
            w: g.w * s,
            h: g.h * s,
          }};
        });
      } else {
        out[f] = squarify(fr, PAD, PAD, VB_W - 2 * PAD, VB_H - 2 * PAD);
      }
    }
    return out;
  }, [JSON.stringify(rooms.map(r => [r.id, r.area, r.floor]))]);

  const aps = React.useMemo(() => recommendAPs(layoutByFloor), [layoutByFloor]);
  const floorAPs = aps.filter(a => a.floor === activeFloor);

  const layout = layoutByFloor[activeFloor] || [];
  const hoveredRoom = layout.find(r => r.id === hovered);

  // Gesamtbudget über alle Räume
  const totalBudget = React.useMemo(() => {
    let total = 0;
    for (const r of indoorRooms) {
      const mods = assignments[r.id] || [];
      for (const m of mods) {
        total += modules[m]?.price?.(r) || 0;
      }
    }
    // + AP-Kosten falls Wifi aktiv
    if (showWifi) {
      for (const ap of aps) {
        total += GV_AP_MODELS[ap.model]?.price || 0;
      }
    }
    return total;
  }, [indoorRooms, assignments, modules, showWifi, aps]);

  // Empty State — Konfigurator noch nicht durchlaufen
  if (indoorRooms.length === 0) {
    return (
      <section id="grundriss" className="gv-section">
        <GvStyles />
        <div className="gv-container">
          <div className="gv-eyebrow"><span className="gv-dot"></span>04 · GRUNDRISS-EXPLORER</div>
          <h2 className="gv-title">Wo lebt was im Haus?</h2>
          <div className="gv-empty-section">
            <svg viewBox="0 0 80 80" width="80" height="80" fill="none">
              <rect x="14" y="14" width="52" height="52" stroke={GV_PALETTE.accent}
                    strokeWidth="1.5" opacity="0.4" strokeDasharray="4 4" />
              <path d="M 30 40 L 38 48 L 52 32" stroke={GV_PALETTE.accent} strokeWidth="2"
                    fill="none" strokeLinecap="round" strokeLinejoin="round" />
            </svg>
            <h3>Erst Konfigurator durchlaufen</h3>
            <p>Sobald du oben Räume erfasst und Module zugeordnet hast, zeichnet sich hier dein
               persönlicher Grundriss — mit allen Smart-Funktionen pro Raum und vorgeschlagener WiFi-Abdeckung.</p>
            <a href="#konfigurator" className="gv-cta">Zum Konfigurator</a>
          </div>
        </div>
      </section>
    );
  }

  // Raum-Detail aufbereiten
  let detailData = null;
  if (hoveredRoom) {
    const activeMods  = assignments[hoveredRoom.id] || [];
    const moduleKeys  = Object.keys(modules);
    const features = moduleKeys.map(k => {
      const isActive = activeMods.includes(k);
      const meta = GV_MODULE_META[k] || { lbl: modules[k]?.label || k, tier:"€" };
      const price = isActive ? (modules[k]?.price?.(hoveredRoom) || 0) : 0;
      const disabled = modules[k]?.requiresWindows && hoveredRoom.windows === 0;
      return { key: k, lbl: meta.lbl, tier: meta.tier, isActive, price, disabled };
    });
    const roomTotal = features.filter(f => f.isActive).reduce((s, f) => s + f.price, 0);
    detailData = { room: hoveredRoom, features, roomTotal, activeCount: activeMods.length };
  }

  return (
    <section id="grundriss" className="gv-section">
      <GvStyles />
      <div className="gv-container">
        <div className="gv-head">
          <div>
            <div className="gv-eyebrow"><span className="gv-dot"></span>04 · GRUNDRISS-EXPLORER</div>
            <h2 className="gv-title">Dein Haus · live visualisiert</h2>
            <p className="gv-sub">
              Räume aus deinem Konfigurator, automatisch flächentreu angeordnet.
              Hover zeigt deine gewählten Module und welche zusätzlich möglich wären.
              Mit dem WiFi-Layer siehst du eine vorgeschlagene AP-Platzierung.
            </p>
          </div>
          <div className="gv-controls">
            <button className={`gv-toggle ${showLabels?"on":""}`} onClick={() => setShowLabels(s => !s)} type="button">
              <span className="gv-toggle-dot"></span><span>Raum-Labels</span>
            </button>
            <button className={`gv-toggle ${showWifi?"on":""}`} onClick={() => setShowWifi(s => !s)} type="button">
              <span className="gv-toggle-dot"></span><span>WiFi-Planung</span>
            </button>
          </div>
        </div>

        {floors.length > 1 && (
          <div className="gv-floor-tabs">
            {floors.map(f => {
              const count = indoorRooms.filter(r => r.floor === f).length;
              return (
                <button key={f} className={`gv-floor-tab ${activeFloor===f?"active":""}`}
                        onClick={() => setActiveFloor(f)} type="button">
                  <span className="lvl">{f}</span>
                  <span className="cnt">{count}</span>
                </button>
              );
            })}
          </div>
        )}

        <div className="gv-stage">
          <div className="gv-canvas-wrap">
            <svg className="gv-canvas" viewBox={`0 0 ${VB_W} ${VB_H}`} preserveAspectRatio="xMidYMid meet">
              <defs>
                <pattern id="gv-grid" width="40" height="40" patternUnits="userSpaceOnUse">
                  <path d="M 40 0 L 0 0 0 40" fill="none" stroke={GV_PALETTE.soft}
                        strokeWidth="0.4" opacity="0.08" />
                </pattern>
                <radialGradient id="gv-ap-grad" cx="50%" cy="50%" r="50%">
                  <stop offset="0%"   stopColor="#ff5252" stopOpacity="0.55" />
                  <stop offset="22%"  stopColor="#ff9b3d" stopOpacity="0.45" />
                  <stop offset="42%"  stopColor="#ffd84d" stopOpacity="0.40" />
                  <stop offset="62%"  stopColor="#9bd75a" stopOpacity="0.35" />
                  <stop offset="82%"  stopColor={GV_PALETTE.accent} stopOpacity="0.22" />
                  <stop offset="100%" stopColor={GV_PALETTE.accent} stopOpacity="0" />
                </radialGradient>
                <filter id="gv-ap-blur" x="-20%" y="-20%" width="140%" height="140%">
                  <feGaussianBlur stdDeviation="4" />
                </filter>
              </defs>
              <rect width={VB_W} height={VB_H} fill="url(#gv-grid)" />

              {/* WiFi-Heatmap (unter Räumen) */}
              {showWifi && floorAPs.map(ap => {
                const radiusPx = (GV_AP_MODELS[ap.model]?.radius || 8) * 22;
                return (
                  <g key={ap.id+"-r"} style={{ pointerEvents:"none", mixBlendMode:"screen" }}>
                    <circle cx={ap.x} cy={ap.y} r={radiusPx} fill="url(#gv-ap-grad)"
                            filter="url(#gv-ap-blur)" />
                    <circle cx={ap.x} cy={ap.y} r={radiusPx} fill="none"
                            stroke={GV_PALETTE.accent} strokeWidth="1"
                            strokeDasharray="3 4" opacity="0.35" />
                  </g>
                );
              })}

              {/* Räume */}
              {layout.map(r => {
                const isHovered = hovered === r.id;
                const modCount = (assignments[r.id] || []).length;
                const hasModules = modCount > 0;
                const fillColor = isHovered ? "rgba(58,189,208,0.12)"
                                            : hasModules ? "rgba(13,32,48,0.85)"
                                                         : "rgba(13,32,48,0.5)";
                const strokeColor = isHovered ? GV_PALETTE.accent
                                              : hasModules ? GV_PALETTE.line
                                                           : GV_PALETTE.dim;
                const strokeW = isHovered ? 2 : 1;

                // Fenster auf den passenden Wand-Seiten platzieren.
                // windowSides ist Array von 'top'|'right'|'bottom'|'left'.
                // Pro Seite mehrere Fenster gleichmaessig verteilt.
                const wins = (r.windowSides || []).slice(0, 8);
                const sideGroups = { top: [], right: [], bottom: [], left: [] };
                wins.forEach((s, i) => {
                  if (sideGroups[s]) sideGroups[s].push(i);
                });
                const winShapes = [];
                for (const side of ["top","right","bottom","left"]) {
                  const n = sideGroups[side].length;
                  if (n === 0) continue;
                  const horiz = (side === "top" || side === "bottom");
                  const wallLen = horiz ? r.rect.w : r.rect.h;
                  const winLen = Math.min(28, Math.max(10, wallLen / (n + 1) * 0.55));
                  for (let k = 0; k < n; k++) {
                    const t = (k + 1) / (n + 1);
                    let wx, wy, ww, wh;
                    if (side === "top")    { wx = r.rect.x + wallLen*t - winLen/2; wy = r.rect.y - 1.5; ww = winLen; wh = 3; }
                    else if (side === "bottom") { wx = r.rect.x + wallLen*t - winLen/2; wy = r.rect.y + r.rect.h - 1.5; ww = winLen; wh = 3; }
                    else if (side === "left")   { wx = r.rect.x - 1.5; wy = r.rect.y + wallLen*t - winLen/2; ww = 3; wh = winLen; }
                    else                        { wx = r.rect.x + r.rect.w - 1.5; wy = r.rect.y + wallLen*t - winLen/2; ww = 3; wh = winLen; }
                    winShapes.push({ x: wx, y: wy, w: ww, h: wh, key: `${side}-${k}` });
                  }
                }

                // Tuer auf doorSide -- Anchor + Boegen vereinfacht
                const doorSide = r.doorSide;
                const doorLen = Math.min(18, Math.max(8, Math.min(r.rect.w, r.rect.h) * 0.18));
                let doorRect = null;
                if (doorSide) {
                  if (doorSide === "top")    doorRect = { x: r.rect.x + r.rect.w*0.3 - doorLen/2, y: r.rect.y - 1.5, w: doorLen, h: 3 };
                  else if (doorSide === "bottom") doorRect = { x: r.rect.x + r.rect.w*0.3 - doorLen/2, y: r.rect.y + r.rect.h - 1.5, w: doorLen, h: 3 };
                  else if (doorSide === "left")   doorRect = { x: r.rect.x - 1.5, y: r.rect.y + r.rect.h*0.3 - doorLen/2, w: 3, h: doorLen };
                  else                            doorRect = { x: r.rect.x + r.rect.w - 1.5, y: r.rect.y + r.rect.h*0.3 - doorLen/2, w: 3, h: doorLen };
                }

                return (
                  <g key={r.id}
                     onMouseEnter={() => setHovered(r.id)}
                     onMouseLeave={() => setHovered(null)}
                     style={{ cursor: "pointer" }}>
                    <rect
                      x={r.rect.x} y={r.rect.y} width={r.rect.w} height={r.rect.h}
                      fill={fillColor} stroke={strokeColor} strokeWidth={strokeW}
                      style={{ transition: "fill 0.2s, stroke 0.2s, stroke-width 0.2s" }}
                    />
                    {winShapes.map(w => (
                      <rect key={w.key} x={w.x} y={w.y} width={w.w} height={w.h}
                            fill="#E8C547" opacity={isHovered ? 0.9 : 0.65}
                            style={{ pointerEvents: "none" }} />
                    ))}
                    {doorRect && (
                      <rect x={doorRect.x} y={doorRect.y}
                            width={doorRect.w} height={doorRect.h}
                            fill={GV_PALETTE.bg} stroke={strokeColor} strokeWidth="0.5"
                            style={{ pointerEvents: "none" }} />
                    )}
                    {showLabels && r.rect.w > 60 && r.rect.h > 36 && (
                      <>
                        <text x={r.rect.x + r.rect.w/2} y={r.rect.y + r.rect.h/2 - 4}
                              textAnchor="middle" dominantBaseline="middle"
                              fontFamily="Inter, sans-serif"
                              fontSize={Math.min(16, Math.max(10, r.rect.w / 12))}
                              fontWeight="500"
                              fill={isHovered ? GV_PALETTE.text : GV_PALETTE.soft}
                              style={{ pointerEvents:"none", letterSpacing:"0.02em" }}>
                          {r.name}
                        </text>
                        <text x={r.rect.x + r.rect.w/2} y={r.rect.y + r.rect.h/2 + 12}
                              textAnchor="middle" dominantBaseline="middle"
                              fontFamily="JetBrains Mono, monospace" fontSize="9"
                              fill={GV_PALETTE.muted}
                              style={{ pointerEvents:"none", letterSpacing:"0.08em" }}>
                          {r.area}m² · {modCount} mod
                        </text>
                      </>
                    )}
                  </g>
                );
              })}

              {/* AP-Symbole (über Räumen) */}
              {showWifi && floorAPs.map(ap => (
                <g key={ap.id} onClick={() => setApDetail(ap)}
                   style={{ cursor: "pointer" }}>
                  <circle cx={ap.x} cy={ap.y} r="16" fill={GV_PALETTE.bg}
                          stroke={GV_PALETTE.accent} strokeWidth="1.5" />
                  <g stroke={GV_PALETTE.accent} strokeWidth="1.4" fill="none"
                     strokeLinecap="round" style={{ pointerEvents:"none" }}>
                    <path d={`M ${ap.x-8} ${ap.y+3} Q ${ap.x} ${ap.y-6} ${ap.x+8} ${ap.y+3}`} />
                    <path d={`M ${ap.x-5} ${ap.y+5} Q ${ap.x} ${ap.y} ${ap.x+5} ${ap.y+5}`} />
                    <circle cx={ap.x} cy={ap.y+5} r="1.4" fill={GV_PALETTE.accent} stroke="none" />
                  </g>
                  <text x={ap.x} y={ap.y+34}
                        fontFamily="JetBrains Mono, monospace" fontSize="9"
                        fill={GV_PALETTE.accent} textAnchor="middle"
                        style={{ letterSpacing: "0.1em", pointerEvents:"none" }}>
                    {ap.id.toUpperCase()}
                  </text>
                </g>
              ))}
            </svg>
          </div>

          {/* Sidebar */}
          <aside className="gv-sidebar">
            {/* Budget-Header */}
            <div className="gv-budget">
              <div className="gv-budget-lbl">PROJEKT-BUDGET (LIVE)</div>
              <div className="gv-budget-val">€{Math.round(totalBudget).toLocaleString('de-AT')}</div>
              <div className="gv-budget-sub">
                {indoorRooms.length} Räume · {Object.values(assignments).flat().length} Module
                {showWifi && ` · ${aps.length} APs`}
              </div>
            </div>

            {!detailData && !apDetail && (
              <div className="gv-empty">
                <h4>Über einen Raum fahren</h4>
                <p>Du siehst dann deine Module und welche zusätzlich noch möglich wären.</p>
                {showWifi && (
                  <div className="gv-wifi-summary">
                    <div className="gv-wifi-stat">
                      <span className="num">{aps.length}</span>
                      <span className="lbl">Access Points gesamt</span>
                    </div>
                    <div className="gv-wifi-stat">
                      <span className="num">€{aps.reduce((s,a) => s + (GV_AP_MODELS[a.model]?.price||0), 0).toLocaleString('de-AT')}</span>
                      <span className="lbl">WiFi-Kosten</span>
                    </div>
                    <div className="gv-wifi-hint">Klick auf einen AP für Details</div>
                  </div>
                )}
              </div>
            )}

            {apDetail && (
              <div className="gv-detail">
                <div className="gv-detail-eyebrow">ACCESS POINT · {apDetail.floor}</div>
                <h3>{apDetail.id.toUpperCase()}</h3>
                <div className="gv-detail-meta">{GV_AP_MODELS[apDetail.model]?.name}</div>
                <ul className="gv-feat-list" style={{ marginTop: 22 }}>
                  <li className="gv-feat active">
                    <span className="gv-feat-bullet"></span>
                    <span className="gv-feat-lbl">Reichweite (Indoor)</span>
                    <span className="gv-feat-price">~{GV_AP_MODELS[apDetail.model]?.radius}&nbsp;m</span>
                  </li>
                  <li className="gv-feat active">
                    <span className="gv-feat-bullet"></span>
                    <span className="gv-feat-lbl">Hardware + Montage</span>
                    <span className="gv-feat-price">€{GV_AP_MODELS[apDetail.model]?.price}</span>
                  </li>
                  <li className="gv-feat active">
                    <span className="gv-feat-bullet"></span>
                    <span className="gv-feat-lbl">PoE-Versorgung</span>
                    <span className="gv-feat-price">inkl.</span>
                  </li>
                </ul>
                <button className="gv-cta" onClick={() => setApDetail(null)}>← Zurück</button>
              </div>
            )}

            {detailData && !apDetail && (
              <div className="gv-detail">
                <div className="gv-detail-eyebrow">RAUM · {detailData.room.floor}</div>
                <h3>{detailData.room.name}</h3>
                <div className="gv-detail-meta">
                  {detailData.room.area}&nbsp;m² · {detailData.room.windows} Fenster · {detailData.activeCount}/{detailData.features.length} Module aktiv
                </div>
                <ul className="gv-feat-list">
                  {detailData.features.map(f => (
                    <li key={f.key} className={`gv-feat ${f.isActive?"active":"inactive"} ${f.disabled?"disabled":""}`}>
                      <span className="gv-feat-bullet"></span>
                      <span className="gv-feat-lbl">
                        {f.lbl}
                        {!f.isActive && !f.disabled && <span className="gv-feat-hint"> · nicht im Paket</span>}
                        {f.disabled && <span className="gv-feat-hint"> · ohne Fenster nicht möglich</span>}
                      </span>
                      <span className="gv-feat-price">
                        {f.isActive ? `€${f.price.toLocaleString('de-AT')}` : f.tier}
                      </span>
                    </li>
                  ))}
                </ul>
                <div className="gv-room-total">
                  <span>Raum-Gesamt</span>
                  <span className="val">€{detailData.roomTotal.toLocaleString('de-AT')}</span>
                </div>
              </div>
            )}
          </aside>
        </div>

        {showWifi && (
          <div className="gv-export-bar">
            <div>
              <span className="gv-export-lbl">WLAN SITE-SURVEY · EMPFEHLUNG</span>
              <span className="gv-export-meta">
                {aps.length} APs auf {floors.length} Stockwerk{floors.length>1?"en":""} ·
                Gesamtkosten €{aps.reduce((s,a) => s + (GV_AP_MODELS[a.model]?.price||0), 0).toLocaleString('de-AT')}
              </span>
            </div>
            <button className="gv-export-btn" onClick={() => alert("Export folgt: PDF mit AP-Liste, Positionen & Reichweiten.")}>
              Site-Survey exportieren →
            </button>
          </div>
        )}
      </div>
    </section>
  );
}

function GvStyles() {
  return (
    <style>{`
      .gv-section{ background: ${GV_PALETTE.bg}; padding: 120px 0 140px;
        position: relative; scroll-margin-top: 80px; }
      .gv-section::before{ content:""; position:absolute; left:0; right:0; top:0; height:1px;
        background: linear-gradient(90deg, transparent, ${GV_PALETTE.line}, transparent); }
      .gv-container{ max-width: 1280px; margin: 0 auto; padding: 0 32px; }

      .gv-head{ display:flex; justify-content: space-between; align-items: flex-end;
        gap: 40px; margin-bottom: 32px; flex-wrap: wrap; }
      .gv-head > div:first-child{ max-width: 640px; }
      .gv-eyebrow{ font-family: 'JetBrains Mono', monospace; font-size: 11px;
        letter-spacing: 0.18em; text-transform: uppercase; color: ${GV_PALETTE.accent};
        margin-bottom: 14px; display:inline-flex; align-items:center; gap: 10px; }
      .gv-dot{ width: 6px; height: 6px; border-radius: 50%; background: ${GV_PALETTE.accent};
        box-shadow: 0 0 0 4px rgba(58,189,208,0.18); }
      .gv-title{ font-family: 'Outfit', sans-serif; font-size: clamp(34px, 4.4vw, 56px);
        font-weight: 500; letter-spacing: -0.02em; line-height: 1.05;
        margin: 0 0 18px; color: ${GV_PALETTE.text}; }
      .gv-sub{ font-family: 'Inter', sans-serif; font-size: 16px; line-height: 1.55;
        color: ${GV_PALETTE.soft}; opacity: 0.8; margin: 0; }

      .gv-controls{ display:flex; gap: 10px; }
      .gv-toggle{ display:inline-flex; align-items:center; gap: 10px;
        padding: 10px 16px; background: transparent; border: 1px solid ${GV_PALETTE.line};
        border-radius: 100px; color: ${GV_PALETTE.soft}; cursor: pointer;
        font-family: 'Inter', sans-serif; font-size: 13px; font-weight: 500; transition: all 0.18s; }
      .gv-toggle:hover{ border-color: ${GV_PALETTE.accent}; color: ${GV_PALETTE.text}; }
      .gv-toggle-dot{ width: 8px; height: 8px; border-radius: 50%;
        background: ${GV_PALETTE.line}; transition: all 0.2s; }
      .gv-toggle.on{ border-color: ${GV_PALETTE.accent};
        background: rgba(58,189,208,0.08); color: ${GV_PALETTE.text}; }
      .gv-toggle.on .gv-toggle-dot{ background: ${GV_PALETTE.accent};
        box-shadow: 0 0 0 4px rgba(58,189,208,0.2); }

      .gv-floor-tabs{ display:flex; gap: 4px; padding: 4px;
        background: ${GV_PALETTE.card}; border: 1px solid ${GV_PALETTE.line};
        border-radius: 100px; width: fit-content; margin-bottom: 24px; }
      .gv-floor-tab{ padding: 8px 18px; background: transparent; border: none;
        border-radius: 100px; cursor: pointer; color: ${GV_PALETTE.soft};
        font-family: 'Inter', sans-serif; font-size: 13px; font-weight: 500;
        display:flex; align-items:center; gap: 8px; transition: all 0.18s; }
      .gv-floor-tab:hover{ color: ${GV_PALETTE.text}; }
      .gv-floor-tab.active{ background: ${GV_PALETTE.accent}; color: ${GV_PALETTE.bg}; }
      .gv-floor-tab .cnt{ font-family: 'JetBrains Mono', monospace; font-size: 10px;
        opacity: 0.6; }
      .gv-floor-tab.active .cnt{ opacity: 0.7; }

      .gv-stage{ display:grid; grid-template-columns: 1fr 360px; gap: 28px; align-items: start; }
      .gv-canvas-wrap{ background: ${GV_PALETTE.card}; border: 1px solid ${GV_PALETTE.line};
        border-radius: 4px; padding: 16px; aspect-ratio: 1000 / 560; }
      .gv-canvas{ width: 100%; height: 100%; display: block; }

      .gv-sidebar{ background: ${GV_PALETTE.card}; border: 1px solid ${GV_PALETTE.line};
        border-radius: 4px; padding: 0; min-height: 360px;
        position: sticky; top: 96px; overflow: hidden; }

      .gv-budget{ padding: 22px 26px; border-bottom: 1px solid ${GV_PALETTE.line};
        background: linear-gradient(180deg, rgba(58,189,208,0.08), transparent); }
      .gv-budget-lbl{ font-family: 'JetBrains Mono', monospace; font-size: 10px;
        letter-spacing: 0.16em; color: ${GV_PALETTE.accent}; opacity: 0.85; }
      .gv-budget-val{ font-family: 'Outfit', sans-serif; font-size: 36px; font-weight: 500;
        letter-spacing: -0.02em; color: ${GV_PALETTE.text}; margin: 4px 0 2px; }
      .gv-budget-sub{ font-family: 'JetBrains Mono', monospace; font-size: 11px;
        color: ${GV_PALETTE.soft}; opacity: 0.6; letter-spacing: 0.05em; }

      .gv-empty{ padding: 26px; color: ${GV_PALETTE.soft}; }
      .gv-empty h4{ font-family: 'Outfit', sans-serif; font-size: 17px; font-weight: 500;
        margin: 0 0 8px; color: ${GV_PALETTE.text}; }
      .gv-empty p{ font-family: 'Inter', sans-serif; font-size: 13px; line-height: 1.6;
        opacity: 0.7; margin: 0 0 18px; }

      .gv-wifi-summary{ padding-top: 18px; border-top: 1px solid ${GV_PALETTE.line};
        display: flex; flex-direction: column; gap: 12px; }
      .gv-wifi-stat{ display:flex; justify-content: space-between; align-items: baseline; }
      .gv-wifi-stat .num{ font-family: 'Outfit', sans-serif; font-size: 20px;
        font-weight: 500; color: ${GV_PALETTE.accent}; }
      .gv-wifi-stat .lbl{ font-family: 'JetBrains Mono', monospace; font-size: 10px;
        letter-spacing: 0.1em; text-transform: uppercase;
        color: ${GV_PALETTE.soft}; opacity: 0.65; }
      .gv-wifi-hint{ font-family: 'JetBrains Mono', monospace; font-size: 10px;
        letter-spacing: 0.06em; color: ${GV_PALETTE.muted}; margin-top: 6px; opacity: 0.7; }

      .gv-detail{ padding: 26px; }
      .gv-detail-eyebrow{ font-family: 'JetBrains Mono', monospace; font-size: 10px;
        letter-spacing: 0.18em; color: ${GV_PALETTE.accent}; margin-bottom: 6px; }
      .gv-detail h3{ font-family: 'Outfit', sans-serif; font-size: 26px; font-weight: 500;
        letter-spacing: -0.02em; margin: 0 0 4px; color: ${GV_PALETTE.text}; }
      .gv-detail-meta{ font-family: 'Inter', sans-serif; font-size: 12px;
        color: ${GV_PALETTE.soft}; opacity: 0.6; margin-bottom: 22px; }
      .gv-feat-list{ list-style: none; padding: 0; margin: 0 0 18px;
        display: flex; flex-direction: column; gap: 0; }
      .gv-feat{ display: grid; grid-template-columns: 8px 1fr auto;
        align-items: center; gap: 12px; padding: 11px 0;
        border-bottom: 1px solid ${GV_PALETTE.line}; }
      .gv-feat:last-child{ border-bottom: none; }
      .gv-feat-bullet{ width: 6px; height: 6px; border-radius: 50%; background: ${GV_PALETTE.dim}; }
      .gv-feat.active .gv-feat-bullet{ background: ${GV_PALETTE.accent};
        box-shadow: 0 0 0 3px rgba(58,189,208,0.2); }
      .gv-feat-lbl{ font-family: 'Inter', sans-serif; font-size: 13px;
        color: ${GV_PALETTE.text}; }
      .gv-feat.inactive .gv-feat-lbl{ color: ${GV_PALETTE.soft}; opacity: 0.45; }
      .gv-feat.disabled .gv-feat-lbl{ color: ${GV_PALETTE.soft}; opacity: 0.3; }
      .gv-feat-hint{ font-family: 'JetBrains Mono', monospace; font-size: 10px;
        letter-spacing: 0.05em; opacity: 0.7; }
      .gv-feat-price{ font-family: 'JetBrains Mono', monospace; font-size: 12px;
        font-weight: 500; color: ${GV_PALETTE.accent}; letter-spacing: 0.05em; }
      .gv-feat.inactive .gv-feat-price{ color: ${GV_PALETTE.muted}; opacity: 0.55; }

      .gv-room-total{ display: flex; justify-content: space-between; align-items: baseline;
        padding: 14px 0 0; border-top: 1px solid ${GV_PALETTE.accent};
        font-family: 'Inter', sans-serif; font-size: 13px; color: ${GV_PALETTE.text}; }
      .gv-room-total .val{ font-family: 'Outfit', sans-serif; font-size: 22px;
        font-weight: 500; color: ${GV_PALETTE.accent}; }

      .gv-cta{ display: inline-flex; align-items: center; gap: 8px;
        margin-top: 14px; padding: 10px 18px; background: transparent;
        border: 1px solid ${GV_PALETTE.line}; border-radius: 100px;
        color: ${GV_PALETTE.soft}; font-family: 'Inter', sans-serif; font-size: 13px;
        font-weight: 500; cursor: pointer; text-decoration: none; transition: all 0.18s; }
      .gv-cta:hover{ border-color: ${GV_PALETTE.accent}; color: ${GV_PALETTE.text}; }

      .gv-empty-section{ display: flex; flex-direction: column; align-items: center;
        justify-content: center; padding: 60px 32px; text-align: center;
        background: ${GV_PALETTE.card}; border: 1px dashed ${GV_PALETTE.line};
        border-radius: 4px; }
      .gv-empty-section svg{ margin-bottom: 18px; opacity: 0.7; }
      .gv-empty-section h3{ font-family: 'Outfit', sans-serif; font-size: 26px;
        font-weight: 500; color: ${GV_PALETTE.text}; margin: 0 0 8px; }
      .gv-empty-section p{ font-family: 'Inter', sans-serif; font-size: 14px;
        color: ${GV_PALETTE.soft}; opacity: 0.75; line-height: 1.6;
        max-width: 480px; margin: 0 0 24px; }

      .gv-export-bar{ margin-top: 24px; padding: 18px 26px;
        background: ${GV_PALETTE.card}; border: 1px solid ${GV_PALETTE.accent};
        border-radius: 4px; display: flex; justify-content: space-between;
        align-items: center; gap: 20px; flex-wrap: wrap; }
      .gv-export-lbl{ display: block; font-family: 'JetBrains Mono', monospace;
        font-size: 10px; letter-spacing: 0.16em; color: ${GV_PALETTE.accent};
        margin-bottom: 4px; }
      .gv-export-meta{ font-family: 'Inter', sans-serif; font-size: 13px;
        color: ${GV_PALETTE.soft}; }
      .gv-export-btn{ padding: 12px 22px; background: ${GV_PALETTE.accent};
        color: ${GV_PALETTE.bg}; border: none; border-radius: 100px;
        font-family: 'Inter', sans-serif; font-size: 13px; font-weight: 500;
        cursor: pointer; transition: all 0.2s; }
      .gv-export-btn:hover{ background: ${GV_PALETTE.soft}; transform: translateY(-1px); }

      @media (max-width: 1000px){
        .gv-stage{ grid-template-columns: 1fr; }
        .gv-sidebar{ position: static; min-height: 200px; }
      }
      @media (max-width: 640px){
        .gv-section{ padding: 80px 0 100px; }
        .gv-container{ padding: 0 20px; }
        .gv-head{ flex-direction: column; align-items: stretch; }
        .gv-controls{ flex-wrap: wrap; }
      }
    `}</style>
  );
}

window.GrundrissViewerSection = GrundrissViewerSection;
