// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Karim Gabriele Varano
import { useEffect, useRef, useState } from 'react'
import Icon from './components/Icon'
import { BarToggle, BarButton, BarCombo } from './components/BarControls'
import {
  onMessage, notifyReady,
  createWall, createDecke, createDach,
  createFenster, createTuer, createAussparung, createTreppe,
  createStuetze, createTraeger, createRaum,
  openSwisstopo, openSwisstopoDialog, openOsmDialog,
  updateElement, deleteElement, openElementeUebersicht, openElementeProperties,
  saveOeffStyle, deleteOeffStyle,
  saveRaumStil, deleteRaumStil, applyRaumStil,
  listLibrary,
} from './lib/rhinoBridge'

const labelXs = {
  fontSize: 10, fontWeight: 600, color: 'var(--text-muted)',
  letterSpacing: '0.06em', textTransform: 'uppercase',
}

function fmtNum(v) {
  if (v == null || v === '') return ''
  const n = Number(v)
  if (Number.isNaN(n)) return String(v)
  return Number(n.toFixed(4)).toString()
}

function ReferenzSelector({ value, onChange }) {
  const opts = [
    { code: 'left',  label: 'Links',  hint: 'Achse auf linker Aussenseite' },
    { code: 'mid',   label: 'Mittig', hint: 'Achse zentriert (Standard)' },
    { code: 'right', label: 'Rechts', hint: 'Achse auf rechter Aussenseite' },
  ]
  return (
    <div style={{ flex: 1, display: 'flex', gap: 3 }}>
      {opts.map(o => (
        <BarToggle
          key={o.code}
          label={o.label}
          active={value === o.code}
          onClick={() => onChange(o.code)}
          title={o.hint}
        />
      ))}
    </div>
  )
}

// Pill-Button — kompakt, Icon + Label horizontal, abgerundet
function PillButton({ icon, label, hint, onClick, onContextMenu, disabled,
                      hasMenu, badge }) {
  return (
    <button
      onClick={onClick}
      onContextMenu={onContextMenu}
      disabled={disabled}
      title={hint}
      style={{
        display: 'flex', alignItems: 'center', gap: 6,
        padding: '5px 10px 5px 8px',
        background: 'var(--bg-input)',
        border: '1px solid var(--border-light)',
        borderRadius: 999,
        cursor: disabled ? 'not-allowed' : 'pointer',
        opacity: disabled ? 0.4 : 1,
        transition: 'background 0.1s, border-color 0.1s',
        fontSize: 11, fontWeight: 500,
        color: 'var(--text-primary)',
        whiteSpace: 'nowrap',
        position: 'relative',
      }}
      onMouseEnter={(e) => { if (!disabled) {
        e.currentTarget.style.background = 'var(--bg-item-hover)'
        e.currentTarget.style.borderColor = 'var(--accent)'
      }}}
      onMouseLeave={(e) => {
        e.currentTarget.style.background = 'var(--bg-input)'
        e.currentTarget.style.borderColor = 'var(--border-light)'
      }}
    >
      <Icon name={icon} size={14} style={{ color: 'var(--accent)' }} />
      <span>{label}</span>
      {hasMenu && (
        <Icon name="expand_more" size={12}
              style={{ color: 'var(--text-muted)', marginLeft: -2 }} />
      )}
      {badge && (
        <span style={{
          fontSize: 9, padding: '1px 5px', borderRadius: 8,
          background: 'var(--bg-section)', color: 'var(--text-muted)',
          marginLeft: 2,
        }}>{badge}</span>
      )}
    </button>
  )
}

// Vertikale Kategorie-Gruppe mit Label + Pills, die wrappen
function PillGroup({ label, children }) {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
      <span style={{
        fontSize: 9, color: 'var(--text-muted)',
        letterSpacing: '0.08em', textTransform: 'uppercase',
        fontWeight: 600,
      }}>
        {label}
      </span>
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 5 }}>
        {children}
      </div>
    </div>
  )
}

// Popup-Menue (relativ positioniert) fuer Untertypen wie Treppen-Art
function PopupMenu({ items, onClose }) {
  useEffect(() => {
    const onDocClick = () => onClose()
    document.addEventListener('click', onDocClick)
    return () => document.removeEventListener('click', onDocClick)
  }, [onClose])
  return (
    <div style={{
      position: 'absolute',
      top: '100%', left: 0,
      marginTop: 4,
      background: 'var(--bg-section)',
      border: '1px solid var(--border)',
      borderRadius: 6,
      boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
      padding: 4,
      zIndex: 100,
      minWidth: 140,
    }}>
      {items.map((it, i) => (
        <button key={i}
          onClick={(e) => { e.stopPropagation(); it.onClick(); onClose() }}
          disabled={it.disabled}
          title={it.hint || ''}
          style={{
            display: 'flex', alignItems: 'center', gap: 6,
            width: '100%', padding: '6px 8px',
            background: 'transparent',
            border: 'none', borderRadius: 4,
            cursor: it.disabled ? 'not-allowed' : 'pointer',
            opacity: it.disabled ? 0.4 : 1,
            color: 'var(--text-primary)',
            fontSize: 11, textAlign: 'left',
          }}
          onMouseEnter={(e) => { if (!it.disabled) {
            e.currentTarget.style.background = 'var(--bg-item-hover)'
          }}}
          onMouseLeave={(e) => {
            e.currentTarget.style.background = 'transparent'
          }}>
          {it.icon && <Icon name={it.icon} size={12}
                            style={{ color: 'var(--accent)' }} />}
          <span style={{ flex: 1 }}>{it.label}</span>
          {it.badge && (
            <span style={{ fontSize: 9, color: 'var(--text-muted)' }}>{it.badge}</span>
          )}
        </button>
      ))}
    </div>
  )
}

const KIND_META = {
  wand:     { icon: 'view_week',     label: 'Wand',     color: '#a8b8c8' },
  decke:    { icon: 'layers',        label: 'Decke',    color: '#b8a890' },
  dach:     { icon: 'roofing',       label: 'Dach',     color: '#c89878' },
  fenster:  { icon: 'window',        label: 'Fenster',  color: '#90b8d0' },
  tuer:     { icon: 'sensor_door',   label: 'Tür',      color: '#c8a878' },
  treppe:   { icon: 'stairs',        label: 'Treppe',   color: '#a0c0a0' },
  stuetze:  { icon: 'square_foot',   label: 'Stütze',   color: '#5fa896' },
  traeger:  { icon: 'horizontal_rule', label: 'Träger', color: '#7fc8a8' },
  raum:     { icon: 'crop_free',     label: 'Raum',     color: '#a0a8b0' },
  aussparung: { icon: 'rectangle',  label: 'Aussparung', color: '#9090a0' },
}

const RAUM_RUNDUNGEN = ['exakt', '0.01', '0.1', '0.5', '1']
const RAUM_ALIGN = [
  { code: 'links',  label: 'Links',  icon: 'format_align_left' },
  { code: 'mid',    label: 'Mitte',  icon: 'format_align_center' },
  { code: 'rechts', label: 'Rechts', icon: 'format_align_right' },
]
const RAUM_SIA_KINDS = [
  { code: '',    label: '—',    color: 'transparent', hint: '' },
  { code: 'hnf', label: 'HNF',  color: '#e8a8a8', hint: 'Hauptnutzfläche' },
  { code: 'nnf', label: 'NNF',  color: '#e8c498', hint: 'Nebennutzfläche' },
  { code: 'vf',  label: 'VF',   color: '#e8d878', hint: 'Verkehrsfläche' },
  { code: 'ff',  label: 'FF',   color: '#a8c8e0', hint: 'Funktionsfläche' },
]

const PROFIL_META = {
  quadrat:  { label: 'Quadrat',  icon: 'square' },
  rechteck: { label: 'Rechteck', icon: 'rectangle' },
  rund:     { label: 'Rund',     icon: 'circle' },
  i_profil: { label: 'I-Profil', icon: 'view_column' },
  rohr:     { label: 'Rohr',     icon: 'radio_button_unchecked' },
}

function ElementList({ elements }) {
  // Gruppiert nach kind, dann nach geschoss-Reihenfolge wie sie reinkommen
  const grouped = {}
  for (const el of elements) {
    const k = el.kind || 'unknown'
    if (!grouped[k]) grouped[k] = []
    grouped[k].push(el)
  }
  const kindOrder = ['wand', 'decke', 'dach', 'fenster', 'tuer', 'aussparung',
                      'treppe', 'stuetze', 'traeger', 'raum']
  return (
    <div style={{
      display: 'flex', flexDirection: 'column', gap: 8,
    }}>
      <div style={{ display: 'flex', alignItems: 'center' }}>
        <span style={{ ...labelXs, flex: 1 }}>Alle Elemente</span>
        <span style={{ fontSize: 9, color: 'var(--text-muted)' }}>{elements.length}</span>
      </div>
      {kindOrder.map(k => {
        const arr = grouped[k]
        if (!arr || arr.length === 0) return null
        const meta = KIND_META[k] || { icon: 'help', label: k, color: '#888' }
        return (
          <div key={k} style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
            <div style={{
              display: 'flex', alignItems: 'center', gap: 5,
              fontSize: 9, color: 'var(--text-muted)',
              letterSpacing: '0.06em', textTransform: 'uppercase',
              fontWeight: 600,
              paddingLeft: 2,
            }}>
              <Icon name={meta.icon} size={10}
                    style={{ color: meta.color }} />
              <span>{meta.label}</span>
              <span style={{ color: 'var(--text-muted)', opacity: 0.6 }}>·</span>
              <span style={{ opacity: 0.8 }}>{arr.length}</span>
            </div>
            {arr.map(el => (
              <ElementListRow key={el.id} el={el} meta={meta} />
            ))}
          </div>
        )
      })}
    </div>
  )
}

function ElementListRow({ el, meta }) {
  const secondary = (() => {
    if (el.kind === 'fenster' || el.kind === 'tuer')
      return `${fmtNum(el.breite)}×${fmtNum(el.hoehe)} m`
    if (el.kind === 'treppe')
      return `${el.nStufen} St · H ${fmtNum(el.ok - el.uk)} m`
    if (el.kind === 'dach' && el.neigung != null)
      return `d ${fmtNum(el.dicke)} · ${fmtNum(el.neigung)}°`
    if (el.kind === 'stuetze' || el.kind === 'traeger') {
      const pl = (PROFIL_META[el.profil] || {}).label || el.profil
      const dim = (el.profil === 'rund' || el.profil === 'rohr')
        ? `Ø${fmtNum(el.d)}`
        : `${fmtNum(el.b)}×${fmtNum(el.h)}`
      return `${pl} ${dim}`
    }
    if (el.kind === 'raum') {
      const label = el.nummer ? `${el.nummer} ${el.name}` : el.name
      return label || 'Raum'
    }
    if (el.kind === 'aussparung')
      return `${fmtNum(el.area)} m²`
    return `d ${fmtNum(el.dicke)} m`
  })()
  const tertiary = (() => {
    if (el.kind === 'fenster')
      return `Br ${fmtNum(el.brueest)}`
    if (el.kind === 'tuer' || el.kind === 'treppe')
      return ''
    if (el.kind === 'stuetze')
      return `H ${fmtNum(el.ok - el.uk)} m`
    if (el.kind === 'traeger')
      return `L ${fmtNum(el.axisLen)} m`
    if (el.kind === 'raum') {
      const siaInfo = RAUM_SIA_KINDS.find(s => s.code === (el.sia || ''))
      const tag = siaInfo && siaInfo.code ? `${siaInfo.label} · ` : ''
      return `${tag}${el.areaFmt} m²`
    }
    if (el.kind === 'aussparung')
      return `U ${fmtNum(el.umfang)} m`
    return `UK ${fmtNum(el.uk)} · OK ${fmtNum(el.ok)}`
  })()
  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 8,
      padding: '5px 8px',
      background: el.selected ? 'var(--bg-item-active)' : 'transparent',
      border: '1px solid ' + (el.selected ? 'var(--accent)' : 'transparent'),
      borderLeft: '3px solid ' + (el.selected ? 'var(--accent)' : meta.color),
      borderRadius: 'var(--r)',
      fontSize: 10,
    }}>
      <span style={{
        fontFamily: 'DM Mono, monospace', fontWeight: 500,
        color: 'var(--text-primary)',
        minWidth: 40,
      }}>
        {el.geschossName || '—'}
      </span>
      <span style={{
        flex: 1, fontFamily: 'DM Mono, monospace',
        color: 'var(--text-primary)',
      }}>
        {secondary}
      </span>
      {tertiary && (
        <span style={{
          color: 'var(--text-muted)', fontFamily: 'DM Mono, monospace',
          fontSize: 9,
        }}>
          {tertiary}
        </span>
      )}
    </div>
  )
}


function NeuesElementSection({ noGeschoss, activeName, elementsCount }) {
  const [treppeMenuOpen, setTreppeMenuOpen] = useState(false)
  const [stuetzeMenuOpen, setStuetzeMenuOpen] = useState(false)
  const [traegerMenuOpen, setTraegerMenuOpen] = useState(false)
  const treppeWrapperRef = useRef(null)
  const dis = noGeschoss
  const baseHint = (label) =>
    noGeschoss ? 'Erst im Ebenen-Manager ein Geschoss aktivieren'
               : `${label} auf ${activeName}`

  const openTreppeMenu = (e) => {
    e.preventDefault()
    setTreppeMenuOpen(true)
  }
  const openStuetzeMenu = (e) => { e.preventDefault(); setStuetzeMenuOpen(true) }
  const openTraegerMenu = (e) => { e.preventDefault(); setTraegerMenuOpen(true) }

  const treppeItems = [
    { icon: 'stairs', label: 'Gerade Treppe',
      hint: 'Lauflinie mit 2 Punkten',
      onClick: () => createTreppe({ treppeArt: 'gerade' }) },
    { icon: 'turn_right', label: 'L-Treppe',
      hint: '3 Punkte: Start, Podest-Ecke, Ende',
      onClick: () => createTreppe({ treppeArt: 'l' }) },
    { icon: 'rotate_right', label: 'Wendeltreppe',
      hint: '3 Punkte: Mittelpunkt, Start-Lauflinie, End-Lauflinie',
      onClick: () => createTreppe({ treppeArt: 'wendel' }) },
  ]

  const profilItems = (factory) => [
    { icon: 'square',  label: 'Quadrat',
      hint: 'B × B', onClick: () => factory('quadrat') },
    { icon: 'rectangle', label: 'Rechteck',
      hint: 'B × H', onClick: () => factory('rechteck') },
    { icon: 'circle', label: 'Rund',
      hint: 'Durchmesser D', onClick: () => factory('rund') },
    { icon: 'view_column', label: 'I-Profil',
      hint: 'Stahl HEB-Stil — Flansch B, Höhe H, Wand t',
      onClick: () => factory('i_profil') },
    { icon: 'radio_button_unchecked', label: 'Rohr',
      hint: 'Hohlzylinder — D Aussen, t Wanddicke',
      onClick: () => factory('rohr') },
  ]

  const stuetzeItems = profilItems((profil) =>
    createStuetze({ profil }))
  const traegerItems = profilItems((profil) =>
    createTraeger({ profil }))

  return (
    <div style={{
      display: 'flex', flexDirection: 'column', gap: 10,
      marginBottom: 8,
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 10 }}>
        <BarToggle
          icon="account_tree"
          label={`Projektübersicht${elementsCount > 0 ? ' · ' + elementsCount : ''}`}
          onClick={() => openElementeUebersicht()}
          disabled={elementsCount === 0}
          title={elementsCount > 0
            ? `Projektübersicht öffnen — ${elementsCount} Elemente`
            : 'Noch keine Elemente vorhanden'} />
        <div style={{ flex: 1 }} />
        <span style={{ color: noGeschoss ? 'var(--danger)' : 'var(--text-muted)' }}>
          {noGeschoss ? 'Kein Geschoss aktiv' : 'auf'}
        </span>
        {!noGeschoss && (
          <span className="chip chip-accent" style={{
            fontSize: 10, fontFamily: 'DM Mono, monospace', fontWeight: 600,
          }}>
            {activeName}
          </span>
        )}
      </div>

      <PillGroup label="Konstruktion">
        <PillButton icon="view_week" label="Wand"
          hint={baseHint('Wand zeichnen')} disabled={dis}
          onClick={() => createWall({ geschoss: '' })} />
        <PillButton icon="layers" label="Decke"
          hint={baseHint('Decke zeichnen')} disabled={dis}
          onClick={() => createDecke({ geschoss: '' })} />
        <PillButton icon="roofing" label="Dach"
          hint={baseHint('Pultdach zeichnen — Traufe = 1. Kante')} disabled={dis}
          onClick={() => createDach({ geschoss: '' })} />
      </PillGroup>

      <PillGroup label="Öffnungen">
        <PillButton icon="window" label="Fenster"
          hint={dis ? baseHint('Fenster') :
            'Erst Wand-Achse wählen, dann Punkt darauf'} disabled={dis}
          onClick={() => createFenster({})} />
        <PillButton icon="sensor_door" label="Tür"
          hint={dis ? baseHint('Tür') :
            'Erst Wand-Achse wählen, dann Punkt darauf'} disabled={dis}
          onClick={() => createTuer({})} />
        <PillButton icon="rectangle" label="Aussparung"
          hint={dis ? baseHint('Aussparung') :
            'Outline auf einer Decke zeichnen — wird automatisch ausgeschnitten'}
          disabled={dis}
          onClick={() => createAussparung({})} />
      </PillGroup>

      <PillGroup label="Erschliessung">
        <div ref={treppeWrapperRef} style={{ position: 'relative' }}>
          <PillButton icon="stairs" label="Treppe" hasMenu
            hint={dis ? baseHint('Treppe') :
              'Klick: gerade Treppe · Rechtsklick: Typ wählen'}
            disabled={dis}
            onClick={() => createTreppe({ treppeArt: 'gerade' })}
            onContextMenu={openTreppeMenu} />
          {treppeMenuOpen && (
            <PopupMenu items={treppeItems}
                       onClose={() => setTreppeMenuOpen(false)} />
          )}
        </div>
      </PillGroup>

      <PillGroup label="Tragwerk">
        <div style={{ position: 'relative' }}>
          <PillButton icon="square_foot" label="Stütze" hasMenu
            hint={dis ? baseHint('Stütze') :
              'Klick: Quadrat-Stütze · Rechtsklick: Profil wählen'}
            disabled={dis}
            onClick={() => createStuetze({})}
            onContextMenu={openStuetzeMenu} />
          {stuetzeMenuOpen && (
            <PopupMenu items={stuetzeItems}
                       onClose={() => setStuetzeMenuOpen(false)} />
          )}
        </div>
        <div style={{ position: 'relative' }}>
          <PillButton icon="horizontal_rule" label="Träger" hasMenu
            hint={dis ? baseHint('Träger') :
              'Klick: Rechteck-Träger · Rechtsklick: Profil wählen'}
            disabled={dis}
            onClick={() => createTraeger({})}
            onContextMenu={openTraegerMenu} />
          {traegerMenuOpen && (
            <PopupMenu items={traegerItems}
                       onClose={() => setTraegerMenuOpen(false)} />
          )}
        </div>
      </PillGroup>

      <PillGroup label="Raeume">
        <PillButton icon="crop_free" label="Raum"
          hint={dis ? baseHint('Raum') :
            'Outline zeichnen · Stempel zeigt Name + Fläche'}
          disabled={dis}
          onClick={() => createRaum({})} />
      </PillGroup>

      <PillGroup label="Library">
        <PillButton icon="inventory_2" label="Symbol"
          hint={dis ? baseHint('Symbol') :
            'Library-Picker oeffnen · Item waehlen · im Viewport Punkt klicken zum Platzieren'}
          disabled={dis}
          onClick={() => listLibrary()} />
      </PillGroup>

      <PillGroup label="Importer">
        <PillButton icon="map" label="Swisstopo"
          hint="Vollautomatischer Import via swisstopo STAC-API: Adresse suchen, Radius wählen, Gebäude + Terrain + Luftbild holen"
          onClick={() => openSwisstopoDialog()} />
        <PillButton icon="public" label="OSM"
          hint="OpenStreetMap-Daten via Overpass-API als 2D-Linien: Strassen, Gebäudeumrisse, Wasser, Grünflächen, Wege"
          onClick={() => openOsmDialog()} />
      </PillGroup>
    </div>
  )
}


// PropertiesView: gemeinsame Komponente, rendert die passende Property-
// Form je nach Element-Typ. Wiederverwendbar in Inline + Satellite-Window.
export function PropertiesView({ selected, geschosse, materials, hatchPatterns, oeffStyles, fonts, raumStempelStile }) {
  if (!selected) return null
  const upd = (p) => updateElement(selected.id, p)
  const del = (label) => () => { if (window.confirm(`${label} löschen?`)) deleteElement(selected.id) }
  if (selected.kind === 'wand')
    return <WallProperties wall={selected} geschosse={geschosse} materials={materials || []}
              onUpdate={upd} onDelete={del('Wand')} />
  if (selected.kind === 'decke')
    return <DeckenProperties decke={selected} geschosse={geschosse}
              onUpdate={upd} onDelete={del('Decke')} />
  if (selected.kind === 'dach')
    return <DachProperties dach={selected} geschosse={geschosse}
              onUpdate={upd} onDelete={del('Dach')} />
  if (selected.kind === 'treppe')
    return <TreppeProperties treppe={selected} geschosse={geschosse}
              onUpdate={upd} onDelete={del('Treppe')} />
  if (selected.kind === 'stuetze' || selected.kind === 'traeger') {
    const lbl = (KIND_META[selected.kind] || {}).label || 'Element'
    return <TragwerkProperties el={selected} onUpdate={upd} onDelete={del(lbl)} />
  }
  if (selected.kind === 'raum')
    return <RaumProperties raum={selected} geschosse={geschosse}
              hatchPatterns={hatchPatterns} fonts={fonts || []}
              raumStempelStile={raumStempelStile || []}
              onUpdate={upd} onDelete={del('Raum')} />

  if (selected.kind === 'aussparung')
    return <AussparungProperties aussp={selected} onDelete={del('Aussparung')} />
  // fenster/tuer
  return <OeffnungProperties oeff={selected} onUpdate={upd}
            oeffStyles={oeffStyles || []}
            onDelete={del(selected.kind === 'fenster' ? 'Fenster' : 'Tür')} />
}


export default function ElementeApp() {
  const [state, setState] = useState({
    elements: [], geschosse: [], selection: null,
    activeGeschoss: '', activeGeschossName: '',
  })
  // Defaults werden vom Backend (sticky) verwaltet — letzte Werte aus
  // dem Rhino-Prompt bleiben fuer den naechsten Klick erhalten.

  useEffect(() => {
    onMessage('STATE', (s) => setState(prev => ({ ...prev, ...s })))
    notifyReady()
  }, [])

  const elements = state.elements || []
  const geschosse = state.geschosse || []
  const selected = elements.find(el => el.id === state.selection)
  const activeName = state.activeGeschossName || ''
  const noGeschoss = !state.activeGeschoss

  return (
    <div style={{
      display: 'flex', flexDirection: 'column',
      height: '100vh', overflow: 'hidden',
      background: 'var(--bg-base)', color: 'var(--text-primary)',
      fontFamily: 'var(--font)', fontSize: 11,
    }}>
      <div style={{ flex: 1, overflowY: 'auto', overflowX: 'hidden', padding: 8 }}>
        {/* Bei Selektion: Properties OBEN, NeuesElement darunter.
            Ohne Selektion: nur NeuesElement. Die volle Element-Liste
            kommt jetzt aus der Projekt-Uebersicht (account_tree-Button). */}
        {selected && (
          <div style={{ position: 'relative' }}>
            <div style={{ position: 'absolute', top: 8, right: 38, zIndex: 1 }}>
              <BarButton icon="open_in_new"
                onClick={() => openElementeProperties()}
                title="Eigenschaften in eigenem Fenster öffnen" />
            </div>
            <PropertiesView
              selected={selected}
              geschosse={geschosse}
              materials={state.materials || []}
              hatchPatterns={state.hatchPatterns}
              fonts={state.fonts || []}
              oeffStyles={state.oeffStyles || []}
              raumStempelStile={state.raumStempelStile || []} />
          </div>
        )}
        <NeuesElementSection
          noGeschoss={noGeschoss}
          activeName={activeName}
          elementsCount={elements.length}
        />
      </div>
    </div>
  )
}

function NumberField({ label, value, onCommit, width, step }) {
  const [raw, setRaw] = useState(String(value))
  useEffect(() => { setRaw(String(value)) }, [value])
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
      <span style={{ fontSize: 10, color: 'var(--text-muted)',
                      width: width || 60 }}>{label}</span>
      <input type="text" value={raw}
             onChange={(e) => setRaw(e.target.value)}
             onBlur={() => {
               const v = parseFloat(raw)
               if (!Number.isNaN(v) && v !== value) onCommit(v)
               else setRaw(String(value))
             }}
             onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
             style={{ flex: 1, fontSize: 11, fontFamily: 'DM Mono, monospace' }} />
    </div>
  )
}

function TragwerkProperties({ el, onUpdate, onDelete }) {
  const meta = KIND_META[el.kind] || { icon: 'square_foot', label: 'Element' }
  const profil = el.profil || 'quadrat'
  const profilOpts = ['quadrat', 'rechteck', 'rund', 'i_profil', 'rohr']
  const isRound = (profil === 'rund' || profil === 'rohr')
  const showH = (profil === 'rechteck' || profil === 'i_profil')
  const showT = (profil === 'rohr' || profil === 'i_profil')
  const isStuetze = (el.kind === 'stuetze')
  const [zRaw, setZRaw] = useState(el.zOver || '')
  useEffect(() => { setZRaw(el.zOver || '') }, [el.id, el.zOver])

  return (
    <div style={{
      display: 'flex', flexDirection: 'column', gap: 8,
      padding: 10, marginBottom: 8,
      background: 'var(--bg-section)',
      borderRadius: 'var(--r-lg)',
    }}>
      <div style={{ display: 'flex', alignItems: 'center' }}>
        <Icon name={meta.icon} size={13}
              style={{ color: 'var(--accent)', marginRight: 6 }} />
        <span style={{ ...labelXs, flex: 1, color: 'var(--accent)' }}>
          {meta.label} · {el.geschossName}
        </span>
        <button className="btn-icon-sm btn-icon-danger" onClick={onDelete}
                title="Löschen">
          <Icon name="delete" size={12} />
        </button>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>
          Profil
        </span>
        <select value={profil}
                onChange={(e) => onUpdate({ profil: e.target.value })}
                style={{ flex: 1, fontSize: 11 }}>
          {profilOpts.map(p => (
            <option key={p} value={p}>
              {(PROFIL_META[p] || {}).label || p}
            </option>
          ))}
        </select>
      </div>

      {!isRound && (
        <NumberField label="Breite" value={el.b}
                      onCommit={(v) => onUpdate({ b: v })} />
      )}
      {showH && (
        <NumberField label="Höhe" value={el.h}
                      onCommit={(v) => onUpdate({ h: v })} />
      )}
      {isRound && (
        <NumberField label="Durchm." value={el.d}
                      onCommit={(v) => onUpdate({ d: v })} />
      )}
      {showT && (
        <NumberField label="Wanddicke" value={el.t}
                      onCommit={(v) => onUpdate({ t: v })} />
      )}
      <NumberField label="Drehung°" value={el.angle}
                    onCommit={(v) => onUpdate({ angle: v })} />

      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>
          {isStuetze ? 'Höhe' : 'OK über UK'}
        </span>
        <input type="text" value={zRaw}
                placeholder={`auto (${fmtNum(el.ok - el.uk)} m)`}
                onChange={(e) => setZRaw(e.target.value)}
                onBlur={() => {
                  const v = zRaw.trim()
                  if (v === '') {
                    if ((el.zOver || '') !== '') onUpdate({ zOver: '' })
                  } else {
                    const n = parseFloat(v)
                    if (!Number.isNaN(n)) onUpdate({ zOver: n })
                  }
                }}
                onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
                style={{ flex: 1, fontSize: 11,
                          fontFamily: 'DM Mono, monospace' }} />
      </div>

      <div style={{
        display: 'flex', justifyContent: 'space-between',
        fontSize: 10, color: 'var(--text-muted)',
        fontFamily: 'DM Mono, monospace',
        paddingTop: 4, borderTop: '1px dashed var(--border)',
      }}>
        <span>UK {fmtNum(el.uk)} · OK {fmtNum(el.ok)}</span>
        {!isStuetze && <span>L {fmtNum(el.axisLen)} m</span>}
      </div>
    </div>
  )
}

// Field-Definitionen fuer den Stempel-Layout-Builder. Symmetrisch zu
// _RAUM_FIELD_IDS im Backend (elemente.py).
const RAUM_LAYOUT_FIELDS = [
  { id: 'nummer',   label: 'Nummer',   icon: 'tag' },
  { id: 'name',     label: 'Name',     icon: 'label' },
  { id: 'funktion', label: 'Funktion', icon: 'category' },
  { id: 'area',     label: 'Fläche',   icon: 'square_foot' },
  { id: 'sia',      label: 'SIA-Tag',  icon: 'class' },
]

// Layout-Builder mit Drag-and-Drop. Rows = Textzeilen, Felder in einer
// Row stehen nebeneinander. Drag-Quelle ist ein "active field" Pill oder
// ein "verfuegbares" Pill. Drop-Ziel ist eine Row (= an die Row anhaengen)
// oder die neue-Row-Drop-Zone unten.
function StempelLayoutBuilder({ layout, availableFields, onChange }) {
  const [dragging, setDragging] = useState(null)  // { id, fromRow|null }

  const FIELD_META = Object.fromEntries(RAUM_LAYOUT_FIELDS.map(f => [f.id, f]))

  const removeFromLayout = (fid) => {
    const next = layout.map(row => row.filter(f => f !== fid))
                       .filter(row => row.length > 0)
    return next
  }

  const handleDragStart = (e, fid, fromRow) => {
    setDragging({ id: fid, fromRow })
    try { e.dataTransfer.effectAllowed = 'move' } catch (_) {}
    try { e.dataTransfer.setData('text/plain', fid) } catch (_) {}
  }

  const handleDragEnd = () => setDragging(null)

  const handleDragOver = (e) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move' }

  const handleDropOnRow = (e, rowIdx) => {
    e.preventDefault()
    if (!dragging) return
    const next = removeFromLayout(dragging.id)
    // Wenn die ge-droppte Source und das Ziel dieselbe Row ist → no-op
    // (Within-Row-Reorder waere komplexer; ignorieren wir vorerst)
    if (next[rowIdx]) next[rowIdx] = [...next[rowIdx], dragging.id]
    else next.push([dragging.id])
    onChange(next)
    setDragging(null)
  }

  const handleDropOnNewRow = (e) => {
    e.preventDefault()
    if (!dragging) return
    const next = removeFromLayout(dragging.id)
    next.push([dragging.id])
    onChange(next)
    setDragging(null)
  }

  const handleRemove = (fid) => {
    onChange(removeFromLayout(fid))
  }

  const handleAddFromAvailable = (fid) => {
    onChange([...layout, [fid]])
  }

  const pillStyle = (isDragging) => ({
    display: 'inline-flex', alignItems: 'center', gap: 4,
    padding: '4px 8px', fontSize: 10,
    background: isDragging ? 'var(--accent)' : 'var(--bg-input)',
    color: isDragging ? 'var(--bg-panel)' : 'var(--text-primary)',
    border: '1px solid var(--border)', borderRadius: 999,
    cursor: 'grab', userSelect: 'none',
    fontFamily: 'DM Mono, monospace',
  })

  const rowStyle = {
    display: 'flex', flexWrap: 'wrap', gap: 4,
    padding: '6px 8px',
    background: 'var(--bg-panel)',
    border: '1px dashed var(--border)', borderRadius: 'var(--r)',
    minHeight: 28, alignItems: 'center',
  }

  return (
    <div style={{
      display: 'flex', flexDirection: 'column', gap: 6,
      paddingTop: 6, borderTop: '1px dashed var(--border)',
    }}>
      <span style={{ ...labelXs, marginBottom: 0 }}>Stempel-Layout</span>
      <div style={{ fontSize: 9, color: 'var(--text-muted)', marginBottom: 2 }}>
        Drag Felder zwischen Zeilen — eine Zeile = eine Textzeile im Stempel.
      </div>

      {/* Verfuegbare Felder (Drag-Quelle) */}
      {availableFields.length > 0 && (
        <div style={{
          display: 'flex', flexWrap: 'wrap', gap: 4,
          padding: '4px 0', marginBottom: 2,
        }}>
          <span style={{ fontSize: 9, color: 'var(--text-muted)',
                          alignSelf: 'center', marginRight: 4 }}>+</span>
          {availableFields.map(f => (
            <span key={f.id}
              draggable
              onDragStart={(e) => handleDragStart(e, f.id, null)}
              onDragEnd={handleDragEnd}
              onClick={() => handleAddFromAvailable(f.id)}
              title={`${f.label} hinzufügen (klick) oder in Zeile ziehen`}
              style={{
                ...pillStyle(dragging && dragging.id === f.id),
                opacity: 0.65, cursor: 'pointer',
              }}>
              <Icon name={f.icon} size={11} />{f.label}
            </span>
          ))}
        </div>
      )}

      {/* Rows */}
      {layout.map((row, ri) => (
        <div key={ri}
          onDragOver={handleDragOver}
          onDrop={(e) => handleDropOnRow(e, ri)}
          style={rowStyle}>
          {row.map(fid => {
            const meta = FIELD_META[fid] || { label: fid, icon: 'label' }
            return (
              <span key={fid}
                draggable
                onDragStart={(e) => handleDragStart(e, fid, ri)}
                onDragEnd={handleDragEnd}
                style={pillStyle(dragging && dragging.id === fid)}>
                <Icon name={meta.icon} size={11} />
                {meta.label}
                <button onClick={() => handleRemove(fid)}
                  title="Entfernen"
                  style={{
                    background: 'transparent', border: 'none',
                    cursor: 'pointer', padding: 0, marginLeft: 2,
                    color: 'inherit', opacity: 0.6,
                  }}>
                  <Icon name="close" size={10} />
                </button>
              </span>
            )
          })}
          <span style={{ flex: 1, fontSize: 9, color: 'var(--text-muted)',
                          textAlign: 'right' }}>Zeile {ri + 1}</span>
        </div>
      ))}

      {/* Neue-Row Drop-Zone (nur wenn was dragable ist) */}
      <div
        onDragOver={handleDragOver}
        onDrop={handleDropOnNewRow}
        style={{
          fontSize: 9, color: 'var(--text-muted)', textAlign: 'center',
          padding: '6px 0',
          border: '1px dashed transparent',
          borderColor: dragging ? 'var(--accent)' : 'var(--border)',
          borderRadius: 'var(--r)',
          background: dragging ? 'var(--bg-item-hover)' : 'transparent',
          transition: 'all 0.15s',
        }}>
        {dragging ? '↓ Hier ablegen für neue Zeile' : 'Drop hier für neue Zeile'}
      </div>
    </div>
  )
}

function RaumProperties({ raum, geschosse, onUpdate, onDelete, hatchPatterns, fonts, raumStempelStile }) {
  const stilList = raumStempelStile || []
  // Match: aktueller Raum-Stempel-Aktiv-Stil-id wird per UserString
  // dossier_raum_stil_id gespeichert wenn ein Stil applied wurde. Fuer
  // jetzt: nicht-persistent — Match anhand visueller Settings (font + layout)
  // koennten wir tun, aber zu fragil. Default: kein Stil markiert.
  const activeStilId = raum.stilId || ''
  const handleStilChange = (val) => {
    if (val === '__save__') {
      const n = (window.prompt('Name für neuen Stempel-Stil:', 'Stil') || '').trim()
      if (!n) return
      saveRaumStil('', n, {
        font:     raum.font || '',
        bold:     !!raum.bold,
        italic:   !!raum.italic,
        txtH:     raum.txtH,
        txtModus: raum.txtModus || 'fix',
        align:    raum.align || 'mid',
        rundung:  raum.rundung || '',
        fuellung: raum.fuellung || '',
        showSia:  !!raum.showSia,
        layout:   Array.isArray(raum.layout) ? raum.layout : [],
      }, [raum.id])  // direkt aktuellen Raum mit der neuen Stil-ID verknuepfen
      return
    }
    if (val === '__delete__') {
      if (activeStilId &&
          window.confirm(`Stil "${stilList.find(s => s.id === activeStilId)?.name}" löschen?`))
        deleteRaumStil(activeStilId)
      return
    }
    if (val) applyRaumStil(val, [raum.id])
  }

  const [name, setName] = useState(raum.name || 'Raum')
  const [nummer, setNummer] = useState(raum.nummer || '')
  const [funktion, setFunktion] = useState(raum.funktion || '')
  // Texthoehe (raum.txtH) + Ausrichtung (raum.align) werden via Oberleiste
  // gesetzt — kein Local-State noetig, Stil-Speichern liest direkt aus raum.
  const txtModus = raum.txtModus || 'fix'
  useEffect(() => {
    setName(raum.name || 'Raum')
    setNummer(raum.nummer || '')
    setFunktion(raum.funktion || '')
  }, [raum.id, raum.name, raum.nummer, raum.funktion])

  // Aktueller Wert von raum_fuellung: "" | "Solid" | "Hatch1" | … | "ByLayer"
  const fuell = raum.fuellung || ''
  const patternList = hatchPatterns || []
  // Layout aus State, mit Fallback auf show_*-Flags (backwards-compat
  // fuer Raeume ohne explizites Layout). Backend faellt ebenfalls auf
  // dieselbe Konvention zurueck.
  const rawLayout = Array.isArray(raum.layout) ? raum.layout : []
  const layout = rawLayout.length > 0 ? rawLayout : (() => {
    const head = []
    if (raum.showNummer !== false) head.push('nummer')
    if (raum.showName   !== false) head.push('name')
    const rows = []
    if (head.length) rows.push(head)
    if (raum.showFunktion !== false) rows.push(['funktion'])
    const tail = []
    if (raum.showArea !== false) tail.push('area')
    if (raum.showSia) tail.push('sia')
    if (tail.length) rows.push(tail)
    return rows.length ? rows : [['name']]
  })()
  const usedFields = new Set(layout.flat())
  const availableFields = RAUM_LAYOUT_FIELDS.filter(f => !usedFields.has(f.id))

  return (
    <div style={{
      display: 'flex', flexDirection: 'column', gap: 8,
      padding: 10, marginBottom: 8,
      background: 'var(--bg-section)',
      borderRadius: 'var(--r-lg)',
    }}>
      <div style={{ display: 'flex', alignItems: 'center' }}>
        <Icon name="crop_free" size={13}
              style={{ color: 'var(--accent)', marginRight: 6 }} />
        <span style={{ ...labelXs, flex: 1, color: 'var(--accent)' }}>
          Raum · {raum.geschossName}
        </span>
        <button className="btn-icon-sm btn-icon-danger" onClick={onDelete}
                title="Löschen">
          <Icon name="delete" size={12} />
        </button>
      </div>

      {/* Stempel-Stil Preset-Picker — apply einer gespeicherten Visual-Vorlage
          auf den Raum. "+ Aktuell speichern" frischt die Liste mit den
          jetzigen Settings als neuen Stil. Stile sind per-Doc. */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Stil</span>
        <select value={activeStilId}
                onChange={(e) => handleStilChange(e.target.value)}
                style={{ flex: 1, fontSize: 11 }}
                title="Gespeicherter Stempel-Stil — Klick wendet die Vorlage an">
          <option value="">— Stil wählen —</option>
          {stilList.map(s => (
            <option key={s.id} value={s.id}>{s.name}</option>
          ))}
          <option disabled>──────────</option>
          <option value="__save__">+ Aktuelle Settings als Stil speichern…</option>
          {activeStilId && <option value="__delete__">🗑 Aktiven Stil löschen</option>}
        </select>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Geschoss</span>
        <select value={raum.geschoss}
                onChange={(e) => onUpdate({ geschoss: e.target.value })}
                style={{ flex: 1, fontSize: 11 }}>
          {geschosse.map(g => <option key={g.id} value={g.id}>{g.name}</option>)}
        </select>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Nummer</span>
        <input type="text" value={nummer}
               onChange={(e) => setNummer(e.target.value)}
               onBlur={() => {
                 if (nummer !== (raum.nummer || '')) onUpdate({ nummer })
               }}
               onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
               style={{ flex: 1, fontSize: 11,
                         fontFamily: 'DM Mono, monospace' }} />
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Name</span>
        <input type="text" value={name}
               onChange={(e) => setName(e.target.value)}
               onBlur={() => {
                 const v = (name || 'Raum').trim()
                 if (v !== raum.name) onUpdate({ name: v })
                 else setName(v)
               }}
               onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
               style={{ flex: 1, fontSize: 11 }} />
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Typ</span>
        <select value={raum.sia || ''}
                onChange={(e) => onUpdate({ sia: e.target.value })}
                title="SIA 416 Flaechenklassifikation"
                style={{ flex: 1, fontSize: 11 }}>
          {RAUM_SIA_KINDS.map(s => (
            <option key={s.code} value={s.code}>
              {s.code === '' ? '—'
                : `${s.label} — ${s.hint}`}
            </option>
          ))}
        </select>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Füllung</span>
        <select value={fuell}
                onChange={(e) => onUpdate({ fuellung: e.target.value })}
                title="Hatch-Pattern im Normalmodus. Bei aktivem SIA-Modus wird klassifizierten Raeumen automatisch Solid forciert."
                style={{ flex: 1, fontSize: 11 }}>
          <option value="">Keine</option>
          <option value="ByLayer">Ebene (folgt Layer-Farbe)</option>
          {patternList.length > 0 && <option disabled>──────</option>}
          {patternList.map(p => (
            <option key={p} value={p}>{p}</option>
          ))}
        </select>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Rundung</span>
        <select value={raum.rundung || ''}
                onChange={(e) => onUpdate({ rundung: e.target.value })}
                style={{ flex: 1, fontSize: 11 }}
                title="Rundung der Flächenangabe. Leer = Default aus Mass-Style.">
          <option value="">Aus Mass-Style</option>
          <option value="exakt">Exakt (2 Nachkommastellen)</option>
          <option value="0.01">auf 0.01 m²</option>
          <option value="0.1">auf 0.1 m²</option>
          <option value="0.5">auf 0.5 m²</option>
          <option value="1">auf 1 m²</option>
        </select>
      </div>

      {/* Skala-Modus: fix = Modellhoehe in m (Oberleiste setzt direkt);
          masstab = Paper-mm @ Plan-Massstab (Oberleiste-Hoehe wird zur
          Render-Zeit berechnet, nicht gespiegelt). Hoehe + Ausrichtung
          werden via Oberleiste-Text-Block gesetzt wenn der Stempel
          selektiert ist — _sync_raum_stamps_to_source spiegelt die
          Werte automatisch auf die Source-UserStrings. */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Skala</span>
        <select value={txtModus}
                onChange={(e) => onUpdate({ txtModus: e.target.value })}
                title="fix: Hoehe in m via Oberleiste · masstab: Paper-mm via Massstab"
                style={{ flex: 1, fontSize: 11 }}>
          <option value="fix">fix · Hoehe = Oberleiste-Wert (m)</option>
          <option value="masstab">masstab · skaliert mit Plan-Massstab</option>
        </select>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Funktion</span>
        <input type="text" value={funktion}
               onChange={(e) => setFunktion(e.target.value)}
               onBlur={() => {
                 const v = (funktion || '').trim()
                 if (v !== (raum.funktion || '')) onUpdate({ funktion: v })
               }}
               onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
               placeholder="z.B. Wohnen, Bad, Büro …"
               style={{ flex: 1, fontSize: 11 }} />
      </div>

      {/* Stempel-Layout — Drag-and-Drop. Jede Row ist eine Textzeile,
          Felder innerhalb einer Row landen in derselben Zeile. Drag
          zwischen Rows um umzuordnen. Klick auf Field oben fügt es in
          eine eigene neue Row hinzu. */}
      <StempelLayoutBuilder
        layout={layout}
        availableFields={availableFields}
        onChange={(newLayout) => onUpdate({ layout: newLayout })} />

      {/* Hinweis: Typografie (Font/Stil/Höhe) wird in der OBERLEISTE
          gesetzt — den Stempel im Viewport anklicken. Aenderungen werden
          automatisch auf die Raum-Outline gespiegelt damit sie beim
          naechsten Regen erhalten bleiben. */}
      <div style={{
        fontSize: 9, color: 'var(--text-muted)',
        paddingTop: 4, borderTop: '1px dashed var(--border)',
        display: 'flex', alignItems: 'center', gap: 4,
      }}>
        <Icon name="info" size={11} style={{ color: 'var(--text-muted)' }} />
        Typografie (Font/Stil/Höhe): Stempel im Viewport selektieren →
        Oberleiste.
      </div>

      <div style={{
        display: 'flex', justifyContent: 'space-between',
        fontSize: 10, color: 'var(--text-muted)',
        fontFamily: 'DM Mono, monospace',
        paddingTop: 4, borderTop: '1px dashed var(--border)',
      }}>
        <span>Flaeche: <strong style={{ color: 'var(--text-primary)' }}>
          {raum.areaFmt} m²</strong></span>
        <span>Umfang: {fmtNum(raum.umfang)} m</span>
      </div>
    </div>
  )
}

function AussparungProperties({ aussp, onDelete }) {
  return (
    <div style={{
      display: 'flex', flexDirection: 'column', gap: 8,
      padding: 10, marginBottom: 8,
      background: 'var(--bg-section)',
      borderRadius: 'var(--r-lg)',
    }}>
      <div style={{ display: 'flex', alignItems: 'center' }}>
        <Icon name="rectangle" size={13}
              style={{ color: 'var(--accent)', marginRight: 6 }} />
        <span style={{ ...labelXs, flex: 1, color: 'var(--accent)' }}>
          Aussparung · {aussp.geschossName}
        </span>
        <button className="btn-icon-sm btn-icon-danger" onClick={onDelete}
                title="Löschen">
          <Icon name="delete" size={12} />
        </button>
      </div>
      <div style={{
        display: 'flex', justifyContent: 'space-between',
        fontSize: 10, color: 'var(--text-muted)',
        fontFamily: 'DM Mono, monospace',
        paddingTop: 4, borderTop: '1px dashed var(--border)',
      }}>
        <span>Fläche: {fmtNum(aussp.area)} m²</span>
        <span>Umfang: {fmtNum(aussp.umfang)} m</span>
      </div>
      <div style={{
        fontSize: 9, color: 'var(--text-muted)', fontStyle: 'italic',
        lineHeight: 1.4,
      }}>
        Outline in Rhino editieren (Punkte ziehen, _Reshape, …) — die
        Eltern-Decke wird automatisch nachgerechnet.
      </div>
    </div>
  )
}

function WallProperties({ wall, geschosse, materials, onUpdate, onDelete }) {
  const [dicke, setDicke] = useState(String(wall.dicke))
  const [ukOver, setUkOver] = useState(wall.ukOverride)
  const [okOver, setOkOver] = useState(wall.okOverride)
  useEffect(() => {
    setDicke(String(wall.dicke))
    setUkOver(wall.ukOverride)
    setOkOver(wall.okOverride)
  }, [wall.id, wall.dicke, wall.ukOverride, wall.okOverride])

  const ukAuto = ukOver === '' || ukOver == null
  const okAuto = okOver === '' || okOver == null

  return (
    <div style={{
      display: 'flex', flexDirection: 'column', gap: 8,
      padding: 10, marginBottom: 8,
      background: 'var(--bg-section)',
      borderRadius: 'var(--r-lg)',
    }}>
      <div style={{ display: 'flex', alignItems: 'center' }}>
        <Icon name="view_week" size={13} style={{ color: 'var(--accent)', marginRight: 6 }} />
        <span style={{ ...labelXs, flex: 1, color: 'var(--accent)' }}>
          Wand · {wall.geschossName}
        </span>
        <button className="btn-icon-sm btn-icon-danger" onClick={onDelete} title="Löschen">
          <Icon name="delete" size={12} />
        </button>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Geschoss</span>
        <select value={wall.geschoss}
                onChange={(e) => onUpdate({ geschoss: e.target.value })}
                style={{ flex: 1, fontSize: 11 }}>
          {geschosse.map(g => <option key={g.id} value={g.id}>{g.name}</option>)}
        </select>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Aufbau</span>
        <div style={{ flex: 1, display: 'flex', gap: 3 }}>
          <BarToggle label="Solid" active={!wall.layered}
            onClick={() => onUpdate({ layered: false })}
            title="Eine homogene Wand-Schicht (Standard)" />
          <BarToggle label="Mehrschichtig" active={wall.layered}
            onClick={() => onUpdate({ layered: true })}
            title="Mehrere Schichten mit individuellen Dicken und Farben" />
        </div>
      </div>

      {!wall.layered && (
        <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
          <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Dicke</span>
          <input type="text" value={dicke}
                 onChange={(e) => setDicke(e.target.value)}
                 onBlur={() => {
                   const v = parseFloat(dicke)
                   if (v > 0 && v !== wall.dicke) onUpdate({ dicke: v })
                   else setDicke(String(wall.dicke))
                 }}
                 onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
                 style={{ flex: 1, fontSize: 11, fontFamily: 'DM Mono, monospace' }} />
        </div>
      )}

      {wall.layered && (
        <LayersEditor layers={wall.layers || []}
                       materials={materials}
                       onChange={(layers) => onUpdate({ layers })} />
      )}

      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Referenz</span>
        <ReferenzSelector value={wall.referenz || 'mid'}
                          onChange={(v) => onUpdate({ referenz: v })} />
      </div>

      <AutoOverrideField label="UK" auto={ukAuto} autoValue={wall.uk}
        rawValue={ukOver} onChangeRaw={setUkOver}
        onToggle={() => onUpdate({ ukOverride: ukAuto ? wall.uk : '' })}
        onCommit={() => {
          if (ukAuto) return
          const v = parseFloat(ukOver)
          if (!Number.isNaN(v)) onUpdate({ ukOverride: v })
        }} />

      <AutoOverrideField label="OK" auto={okAuto} autoValue={wall.ok}
        rawValue={okOver} onChangeRaw={setOkOver}
        onToggle={() => onUpdate({ okOverride: okAuto ? wall.ok : '' })}
        onCommit={() => {
          if (okAuto) return
          const v = parseFloat(okOver)
          if (!Number.isNaN(v)) onUpdate({ okOverride: v })
        }} />
    </div>
  )
}

function LayersEditor({ layers, onChange, materials }) {
  const total = (layers || []).reduce((s, l) => s + (parseFloat(l.dicke) || 0), 0)
  const updateAt = (idx, patch) => {
    const next = (layers || []).map((l, i) => (i === idx ? { ...l, ...patch } : l))
    onChange(next)
  }
  const removeAt = (idx) => {
    const next = (layers || []).filter((_, i) => i !== idx)
    onChange(next.length ? next : [{ name: 'Schicht 1', dicke: 0.20, color: '#cccccc', material: '' }])
  }
  const addLayer = () => {
    const idx = (layers || []).length + 1
    onChange([ ...(layers || []),
      { name: `Schicht ${idx}`, dicke: 0.05, color: '#dddddd', material: '' } ])
  }
  return (
    <div style={{
      display: 'flex', flexDirection: 'column', gap: 4,
      padding: '6px 8px',
      background: 'var(--bg-input)',
      border: '1px solid var(--border-light)',
      borderRadius: 'var(--r)',
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 4,
                     fontSize: 9, color: 'var(--text-muted)',
                     letterSpacing: '0.06em', textTransform: 'uppercase',
                     fontWeight: 600 }}>
        <span style={{ flex: 1 }}>Schichten (links → rechts)</span>
        <span style={{ fontFamily: 'DM Mono, monospace', fontSize: 10,
                        color: 'var(--text-primary)', textTransform: 'none' }}>
          Σ {total.toFixed(3)} m
        </span>
      </div>
      {(layers || []).map((ly, i) => (
        <LayerRow key={i} layer={ly}
                   materials={materials}
                   onChange={(patch) => updateAt(i, patch)}
                   onRemove={() => removeAt(i)} />
      ))}
      <div style={{ marginTop: 2 }}>
        <BarToggle label="+ Schicht" onClick={addLayer}
          title="Neue Schicht unten anfügen" />
      </div>
    </div>
  )
}

function LayerRow({ layer, materials, onChange, onRemove }) {
  const [name, setName] = useState(layer.name || '')
  const [dicke, setDicke] = useState(String(layer.dicke || 0))
  useEffect(() => {
    setName(layer.name || '')
    setDicke(String(layer.dicke || 0))
  }, [layer.name, layer.dicke])
  // Wenn ein Material gewaehlt ist, kommt die Farbe von dort — der Color-
  // Picker wird ausgegraut und zeigt die Material-Farbe an.
  const matList = materials || []
  const matName = layer.material || ''
  const matDef = matList.find(m => m.name === matName)
  const effectiveColor = matDef ? matDef.color : (layer.color || '#cccccc')
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
      <input type="color"
             value={effectiveColor}
             onChange={(e) => onChange({ color: e.target.value })}
             disabled={!!matDef}
             title={matDef
               ? `Farbe aus Material "${matDef.name}"`
               : 'Schicht-Farbe'}
             style={{
               width: 22, height: 22, padding: 0, border: 'none',
               background: 'transparent',
               cursor: matDef ? 'not-allowed' : 'pointer',
               opacity: matDef ? 0.5 : 1,
             }} />
      <select value={matName}
              onChange={(e) => {
                const m = matList.find(x => x.name === e.target.value)
                if (m) onChange({ material: m.name, color: m.color })
                else onChange({ material: '' })
              }}
              title="Material aus Bibliothek (steuert Farbe + Section-Hatch)"
              style={{ width: 92, fontSize: 10, padding: '2px 4px' }}>
        <option value="">(eigene)</option>
        {matList.map(m => (
          <option key={m.name} value={m.name}>{m.name}</option>
        ))}
      </select>
      <input type="text" value={name}
             placeholder="Name"
             onChange={(e) => setName(e.target.value)}
             onBlur={() => { if (name !== layer.name) onChange({ name }) }}
             onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
             style={{ flex: 1, fontSize: 10, padding: '2px 4px' }} />
      <input type="text" value={dicke}
             onChange={(e) => setDicke(e.target.value)}
             onBlur={() => {
               const v = parseFloat(dicke)
               if (v > 0 && v !== layer.dicke) onChange({ dicke: v })
               else setDicke(String(layer.dicke))
             }}
             onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
             style={{ width: 56, fontSize: 10, padding: '2px 4px',
                      fontFamily: 'DM Mono, monospace' }}
             title="Dicke in Metern" />
      <button onClick={onRemove} className="btn-icon-sm btn-icon-danger"
              title="Schicht entfernen" style={{ width: 18, height: 18 }}>
        <Icon name="close" size={10} />
      </button>
    </div>
  )
}

function DeckenProperties({ decke, geschosse, onUpdate, onDelete }) {
  const [dicke, setDicke] = useState(String(decke.dicke))
  const [ukOver, setUkOver] = useState(decke.ukOverride)
  const [okOver, setOkOver] = useState(decke.okOverride)
  useEffect(() => {
    setDicke(String(decke.dicke))
    setUkOver(decke.ukOverride)
    setOkOver(decke.okOverride)
  }, [decke.id, decke.dicke, decke.ukOverride, decke.okOverride])

  const ukAuto = ukOver === '' || ukOver == null
  const okAuto = okOver === '' || okOver == null

  return (
    <div style={{
      display: 'flex', flexDirection: 'column', gap: 8,
      padding: 10, marginBottom: 8,
      background: 'var(--bg-section)',
      borderRadius: 'var(--r-lg)',
    }}>
      <div style={{ display: 'flex', alignItems: 'center' }}>
        <Icon name="layers" size={13} style={{ color: 'var(--accent)', marginRight: 6 }} />
        <span style={{ ...labelXs, flex: 1, color: 'var(--accent)' }}>
          Decke · {decke.geschossName}
        </span>
        <button className="btn-icon-sm btn-icon-danger" onClick={onDelete} title="Löschen">
          <Icon name="delete" size={12} />
        </button>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Geschoss</span>
        <select value={decke.geschoss}
                onChange={(e) => onUpdate({ geschoss: e.target.value })}
                style={{ flex: 1, fontSize: 11 }}>
          {geschosse.map(g => <option key={g.id} value={g.id}>{g.name}</option>)}
        </select>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Dicke</span>
        <input type="text" value={dicke}
               onChange={(e) => setDicke(e.target.value)}
               onBlur={() => {
                 const v = parseFloat(dicke)
                 if (v > 0 && v !== decke.dicke) onUpdate({ dicke: v })
                 else setDicke(String(decke.dicke))
               }}
               onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
               style={{ flex: 1, fontSize: 11, fontFamily: 'DM Mono, monospace' }} />
      </div>

      <AutoOverrideField label="UK" auto={ukAuto} autoValue={decke.uk}
        rawValue={ukOver} onChangeRaw={setUkOver}
        onToggle={() => onUpdate({ ukOverride: ukAuto ? decke.uk : '' })}
        onCommit={() => {
          if (ukAuto) return
          const v = parseFloat(ukOver)
          if (!Number.isNaN(v)) onUpdate({ ukOverride: v })
        }} />

      <AutoOverrideField label="OK" auto={okAuto} autoValue={decke.ok}
        rawValue={okOver} onChangeRaw={setOkOver}
        onToggle={() => onUpdate({ okOverride: okAuto ? decke.ok : '' })}
        onCommit={() => {
          if (okAuto) return
          const v = parseFloat(okOver)
          if (!Number.isNaN(v)) onUpdate({ okOverride: v })
        }} />
    </div>
  )
}

function DachProperties({ dach, geschosse, onUpdate, onDelete }) {
  const [dicke,   setDicke]   = useState(String(dach.dicke))
  const [neigung, setNeigung] = useState(String(dach.neigung ?? 30))
  const [eaveIdx, setEaveIdx] = useState(String(dach.eaveIdx ?? 0))
  const [ukOver,  setUkOver]  = useState(dach.ukOverride)
  useEffect(() => {
    setDicke(String(dach.dicke))
    setNeigung(String(dach.neigung ?? 30))
    setEaveIdx(String(dach.eaveIdx ?? 0))
    setUkOver(dach.ukOverride)
  }, [dach.id, dach.dicke, dach.neigung, dach.eaveIdx, dach.ukOverride])
  const dachTyp = dach.dachTyp || 'pult'

  const ukAuto = ukOver === '' || ukOver == null

  return (
    <div style={{
      display: 'flex', flexDirection: 'column', gap: 8,
      padding: 10, marginBottom: 8,
      background: 'var(--bg-section)',
      borderRadius: 'var(--r-lg)',
    }}>
      <div style={{ display: 'flex', alignItems: 'center' }}>
        <Icon name="roofing" size={13} style={{ color: 'var(--accent)', marginRight: 6 }} />
        <span style={{ ...labelXs, flex: 1, color: 'var(--accent)' }}>
          {dachTyp === 'sattel' ? 'Satteldach' : dachTyp === 'walm' ? 'Walmdach' : 'Pultdach'} · {dach.geschossName}
        </span>
        <button className="btn-icon-sm btn-icon-danger" onClick={onDelete} title="Löschen">
          <Icon name="delete" size={12} />
        </button>
      </div>

      {/* Dach-Typ */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Typ</span>
        <select value={dachTyp}
                onChange={(e) => onUpdate({ dachTyp: e.target.value })}
                style={{ flex: 1, fontSize: 11 }}>
          <option value="pult">Pult — Geneigte Fläche, Traufe = 1. Kante</option>
          <option value="sattel">Sattel — Rechteck-Outline (4 Ecken)</option>
          <option value="walm">Walm — Rechteck-Outline (4 Ecken)</option>
          <option value="mansarde">Mansarde — Rechteck-Outline (4 Ecken)</option>
        </select>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Geschoss</span>
        <select value={dach.geschoss}
                onChange={(e) => onUpdate({ geschoss: e.target.value })}
                style={{ flex: 1, fontSize: 11 }}>
          {geschosse.map(g => <option key={g.id} value={g.id}>{g.name}</option>)}
        </select>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Dicke</span>
        <input type="text" value={dicke}
               onChange={(e) => setDicke(e.target.value)}
               onBlur={() => {
                 const v = parseFloat(dicke)
                 if (v > 0 && v !== dach.dicke) onUpdate({ dicke: v })
                 else setDicke(String(dach.dicke))
               }}
               onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
               style={{ flex: 1, fontSize: 11, fontFamily: 'DM Mono, monospace' }} />
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Neigung</span>
        <input type="text" value={neigung}
               onChange={(e) => setNeigung(e.target.value)}
               onBlur={() => {
                 const v = parseFloat(neigung)
                 if (!Number.isNaN(v) && v >= 0 && v < 90) onUpdate({ neigung: v })
                 else setNeigung(String(dach.neigung ?? 30))
               }}
               onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
               style={{ flex: 1, fontSize: 11, fontFamily: 'DM Mono, monospace' }} />
        <span style={{ fontSize: 10, color: 'var(--text-muted)' }}>°</span>
      </div>

      {dachTyp === 'pult' && (
        <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
          <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
                title="Index der Traufkante (0 = Kante zwischen 1. und 2. Punkt)">
            Traufe
          </span>
          <input type="text" value={eaveIdx}
                 onChange={(e) => setEaveIdx(e.target.value)}
                 onBlur={() => {
                   const v = parseInt(eaveIdx, 10)
                   if (!Number.isNaN(v) && v >= 0) onUpdate({ eaveIdx: v })
                   else setEaveIdx(String(dach.eaveIdx ?? 0))
                 }}
                 onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
                 style={{ flex: 1, fontSize: 11, fontFamily: 'DM Mono, monospace' }} />
          <span style={{ fontSize: 9, color: 'var(--text-muted)' }}>Kante</span>
        </div>
      )}

      {/* Mansarde-spezifisch: Variante + untere Neigung + Knick-Hoehe */}
      {dachTyp === 'mansarde' && (
        <>
          <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
            <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Variante</span>
            <select value={dach.dachVariante || 'walm'}
                    onChange={(e) => onUpdate({ dachVariante: e.target.value })}
                    style={{ flex: 1, fontSize: 11 }}>
              <option value="walm">Walm — Knick auf allen 4 Seiten (Pariser Stil)</option>
              <option value="giebel">Giebel — Knick nur an langen Seiten (DACH-Standard)</option>
              <option value="walm_giebel">Walm + Giebel — Unten Walm, oben Giebel</option>
            </select>
          </div>
          <MansardeFields dach={dach} onUpdate={onUpdate} />
        </>
      )}

      <AutoOverrideField label="Basis" auto={ukAuto} autoValue={dach.uk}
        rawValue={ukOver} onChangeRaw={setUkOver}
        onToggle={() => onUpdate({ ukOverride: ukAuto ? dach.uk : '' })}
        onCommit={() => {
          if (ukAuto) return
          const v = parseFloat(ukOver)
          if (!Number.isNaN(v)) onUpdate({ ukOverride: v })
        }} />
    </div>
  )
}

function MansardeFields({ dach, onUpdate }) {
  const [nu, setNu] = useState(String(dach.neigungUnten ?? 60))
  const [kh, setKh] = useState(String(dach.knickH ?? 2.0))
  useEffect(() => {
    setNu(String(dach.neigungUnten ?? 60))
    setKh(String(dach.knickH ?? 2.0))
  }, [dach.id, dach.neigungUnten, dach.knickH])
  return (
    <>
      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
              title="Untere (steile) Neigung — bis zum Knick">
          Steil
        </span>
        <input type="text" value={nu}
               onChange={(e) => setNu(e.target.value)}
               onBlur={() => {
                 const v = parseFloat(nu)
                 if (!Number.isNaN(v) && v > 0 && v < 90) onUpdate({ neigungUnten: v })
                 else setNu(String(dach.neigungUnten ?? 60))
               }}
               onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
               style={{ flex: 1, fontSize: 11, fontFamily: 'DM Mono, monospace' }} />
        <span style={{ fontSize: 10, color: 'var(--text-muted)' }}>°</span>
      </div>
      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
              title="Höhe über Traufe wo der Knick sitzt">
          Knick H
        </span>
        <input type="text" value={kh}
               onChange={(e) => setKh(e.target.value)}
               onBlur={() => {
                 const v = parseFloat(kh)
                 if (!Number.isNaN(v) && v > 0) onUpdate({ knickH: v })
                 else setKh(String(dach.knickH ?? 2.0))
               }}
               onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
               style={{ flex: 1, fontSize: 11, fontFamily: 'DM Mono, monospace' }} />
        <span style={{ fontSize: 10, color: 'var(--text-muted)' }}>m</span>
      </div>
    </>
  )
}

const SIMS_OPTIONS = [
  { code: 'ohne',     label: 'ohne' },
  { code: 'schmal',   label: 'schmal' },
  { code: 'standard', label: 'standard' },
  { code: 'breit',    label: 'breit' },
]

const RAHMEN_POS_OPTIONS = [
  { code: 'aussen', label: 'aussen', hint: 'Rahmen bündig mit Aussenfläche' },
  { code: 'mid',    label: 'mittig', hint: 'Rahmen mittig im Wandquerschnitt' },
  { code: 'innen',  label: 'innen',  hint: 'Rahmen bündig mit Innenfläche' },
]

const OEFF_REFERENZ_OPTIONS = [
  { code: 'links',  label: 'Links',  hint: 'Klick-Punkt am linken Rand — Öffnung extendiert nach rechts (+tan der Wand-Achse)' },
  { code: 'mid',    label: 'Mittig', hint: 'Klick-Punkt mittig in der Öffnung (Standard)' },
  { code: 'rechts', label: 'Rechts', hint: 'Klick-Punkt am rechten Rand — Öffnung extendiert nach links (-tan)' },
]

function SollRow({ label, value, unit, soll, sollKey, onUpdateSoll, readOnly }) {
  // soll[sollKey] = [lo, hi, on]
  const lo = soll?.[sollKey]?.[0] ?? 0
  const hi = soll?.[sollKey]?.[1] ?? 0
  const on = soll?.[sollKey]?.[2] ?? true
  const inRange = value >= lo && value <= hi
  const valueColor = !on ? 'var(--text-muted)'
                    : inRange ? 'var(--accent)'
                    : '#c87050'
  const [loStr, setLoStr] = useState(String(lo))
  const [hiStr, setHiStr] = useState(String(hi))
  useEffect(() => {
    setLoStr(String(lo))
    setHiStr(String(hi))
  }, [lo, hi])

  const commit = (k, val, setBack, def) => {
    const v = parseFloat(val)
    if (!Number.isNaN(v) && v > 0) {
      const next = [...soll[sollKey]]
      next[k] = v
      onUpdateSoll({ ...soll, [sollKey]: next })
    } else setBack(String(def))
  }

  const toggle = () => {
    const next = [...soll[sollKey]]
    next[2] = !next[2]
    onUpdateSoll({ ...soll, [sollKey]: next })
  }

  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 10,
                  fontFamily: 'DM Mono, monospace' }}>
      <input type="checkbox" checked={on} onChange={toggle}
             title="Validierung ein/aus"
             style={{ margin: 0, cursor: 'pointer' }} />
      <span style={{ color: 'var(--text-muted)', width: 30 }}>{label}</span>
      <span style={{ color: valueColor, width: 56 }}>
        {fmtNum(value)} {unit}
      </span>
      {readOnly ? (
        <span style={{ color: 'var(--text-muted)', marginLeft: 'auto', fontSize: 9 }}>
          {fmtNum(lo)}–{fmtNum(hi)}
        </span>
      ) : (
        <>
          <span style={{ color: 'var(--text-muted)', marginLeft: 'auto', fontSize: 9 }}>Soll</span>
          <input type="text" value={loStr}
                 onChange={(e) => setLoStr(e.target.value)}
                 onBlur={() => commit(0, loStr, setLoStr, lo)}
                 onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
                 disabled={!on}
                 style={{
                   width: 38, fontSize: 9, fontFamily: 'DM Mono, monospace',
                   padding: '1px 3px', background: 'transparent',
                   border: '1px solid var(--border-light)',
                   color: on ? 'var(--text-primary)' : 'var(--text-muted)',
                 }} />
          <span style={{ color: 'var(--text-muted)', fontSize: 9 }}>–</span>
          <input type="text" value={hiStr}
                 onChange={(e) => setHiStr(e.target.value)}
                 onBlur={() => commit(1, hiStr, setHiStr, hi)}
                 onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
                 disabled={!on}
                 style={{
                   width: 38, fontSize: 9, fontFamily: 'DM Mono, monospace',
                   padding: '1px 3px', background: 'transparent',
                   border: '1px solid var(--border-light)',
                   color: on ? 'var(--text-primary)' : 'var(--text-muted)',
                 }} />
        </>
      )}
    </div>
  )
}

const DEFAULT_TREPPE_SOLL = {
  s:  [0.15, 0.20, true],
  a:  [0.21, 0.35, true],
  sa: [0.60, 0.65, true],
}

function TreppeProperties({ treppe, geschosse, onUpdate, onDelete }) {
  const [breite, setBreite] = useState(String(treppe.breite ?? 1.0))
  const [nStufen, setNStufen] = useState(String(treppe.nStufen ?? 15))
  const [laufD, setLaufD] = useState(String(treppe.laufD ?? 0.18))
  const [hStr, setHStr] = useState('')
  useEffect(() => {
    setBreite(String(treppe.breite ?? 1.0))
    setNStufen(String(treppe.nStufen ?? 15))
    setLaufD(String(treppe.laufD ?? 0.18))
  }, [treppe.id, treppe.breite, treppe.nStufen, treppe.laufD])

  const H = (treppe.ok ?? 0) - (treppe.uk ?? 0)
  const N = treppe.nStufen ?? 15
  const L = treppe.laufLen ?? 0
  const S = N > 0 ? H / N : 0
  const A = N > 0 ? L / N : 0
  const sa = 2 * S + A
  const soll = treppe.soll || DEFAULT_TREPPE_SOLL
  const hasHOver = treppe.hOver != null && treppe.hOver !== ''
  useEffect(() => {
    setHStr(hasHOver ? String(treppe.hOver) : fmtNum(H))
  }, [treppe.id, treppe.hOver, H, hasHOver])

  const allOK = (
    (!soll.s[2]  || (S  >= soll.s[0]  && S  <= soll.s[1])) &&
    (!soll.a[2]  || (A  >= soll.a[0]  && A  <= soll.a[1])) &&
    (!soll.sa[2] || (sa >= soll.sa[0] && sa <= soll.sa[1]))
  )

  const onUpdateSoll = (newSoll) => {
    onUpdate({ soll: newSoll })
  }
  const onCommitH = () => {
    const v = parseFloat(hStr)
    if (!Number.isNaN(v) && v > 0 && Math.abs(v - H) > 1e-5) {
      // User hat H ueberschrieben → Ziel auf "eigene"
      onUpdate({ hOver: v, geschossEnd: '' })
    }
  }
  const onClearHOver = () => {
    onUpdate({ hOver: '' })
  }

  const ref = treppe.treppeReferenz ?? 'mid'
  const REF_OPTIONS = [
    { code: 'links',  label: 'Links' },
    { code: 'mid',    label: 'Mittig' },
    { code: 'rechts', label: 'Rechts' },
  ]
  const modus = treppe.treppeModus ?? 'flach'
  const MODUS_OPTIONS = [
    { code: 'massiv',      label: 'massiv',      hint: 'Block bis zum Boden — wie eine Mauer unter der Treppe' },
    { code: 'flach',       label: 'flach',       hint: 'Schräge Plattenunterseite parallel zum Treppenlauf (realistisch)' },
    { code: 'plattenrand', label: 'gestuft',     hint: 'Plattenunterseite folgt den Stufen, vertikal versetzt' },
  ]

  return (
    <div style={{
      display: 'flex', flexDirection: 'column', gap: 8,
      padding: 10, marginBottom: 8,
      background: 'var(--bg-section)',
      borderRadius: 'var(--r-lg)',
    }}>
      <div style={{ display: 'flex', alignItems: 'center' }}>
        <Icon name="stairs" size={13} style={{ color: 'var(--accent)', marginRight: 6 }} />
        <span style={{ ...labelXs, flex: 1, color: 'var(--accent)' }}>
          Treppe · {treppe.geschossName} → {treppe.geschossEndName || '(auto)'}
        </span>
        <button className="btn-icon-sm btn-icon-danger" onClick={onDelete} title="Löschen">
          <Icon name="delete" size={12} />
        </button>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Start</span>
        <select value={treppe.geschoss}
                onChange={(e) => onUpdate({ geschoss: e.target.value })}
                style={{ flex: 1, fontSize: 11 }}>
          {geschosse.map(g => <option key={g.id} value={g.id}>{g.name}</option>)}
        </select>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Ziel</span>
        <select
          value={hasHOver ? '__custom__' : (treppe.geschossEnd || '')}
          onChange={(e) => {
            const v = e.target.value
            if (v === '__custom__') {
              // Eigene Hoehe — falls noch nicht gesetzt, mit aktuellem H starten
              onUpdate({ hOver: H, geschossEnd: '' })
            } else {
              onUpdate({ geschossEnd: v, hOver: '' })
            }
          }}
          style={{ flex: 1, fontSize: 11 }}>
          <option value="">(auto: Start + Höhe)</option>
          {geschosse.filter(g => g.id !== treppe.geschoss)
            .map(g => <option key={g.id} value={g.id}>{g.name}</option>)}
          <option value="__custom__">eigene Höhe</option>
        </select>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Breite</span>
        <input type="text" value={breite}
               onChange={(e) => setBreite(e.target.value)}
               onBlur={() => {
                 const v = parseFloat(breite)
                 if (!Number.isNaN(v) && v >= 0.3) onUpdate({ breite: v })
                 else setBreite(String(treppe.breite))
               }}
               onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
               style={{ flex: 1, fontSize: 11, fontFamily: 'DM Mono, monospace' }} />
        <span style={{ fontSize: 10, color: 'var(--text-muted)' }}>m</span>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Stufen</span>
        <input type="text" value={nStufen}
               onChange={(e) => setNStufen(e.target.value)}
               onBlur={() => {
                 const v = parseInt(nStufen, 10)
                 if (Number.isFinite(v) && v >= 2 && v <= 40) onUpdate({ nStufen: v })
                 else setNStufen(String(treppe.nStufen))
               }}
               onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
               style={{ flex: 1, fontSize: 11, fontFamily: 'DM Mono, monospace' }} />
        <span style={{ fontSize: 10, color: 'var(--text-muted)' }}>×</span>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Lage</span>
        <div style={{ flex: 1, display: 'flex', gap: 3 }}>
          {REF_OPTIONS.map(o => (
            <BarToggle key={o.code}
              label={o.label}
              active={ref === o.code}
              onClick={() => onUpdate({ treppeReferenz: o.code })} />
          ))}
        </div>
      </div>

      <div style={{ height: 1, background: 'var(--border-light)', margin: '2px 0' }} />

      {/* Unterseite-Modus */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
              title="Form der Treppen-Unterseite">
          Unten
        </span>
        <div style={{ flex: 1, display: 'flex', gap: 3 }}>
          {MODUS_OPTIONS.map(o => (
            <BarToggle key={o.code}
              label={o.label}
              active={modus === o.code}
              onClick={() => onUpdate({ treppeModus: o.code })}
              title={o.hint} />
          ))}
        </div>
      </div>

      {/* Lauf-Plattendicke (nur fuer flach + plattenrand relevant) */}
      {modus !== 'massiv' && (
        <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
          <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
                title="Dicke der Lauf-Platte (Materialdicke unter den Stufen)">
            Platte
          </span>
          <input type="text" value={laufD}
                 onChange={(e) => setLaufD(e.target.value)}
                 onBlur={() => {
                   const v = parseFloat(laufD)
                   if (!Number.isNaN(v) && v > 0) onUpdate({ laufD: v })
                   else setLaufD(String(treppe.laufD))
                 }}
                 onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
                 style={{ flex: 1, fontSize: 11, fontFamily: 'DM Mono, monospace' }} />
          <span style={{ fontSize: 10, color: 'var(--text-muted)' }}>m</span>
        </div>
      )}

      {/* Schrittmass-Tabelle: H (editierbar), S, A, 2S+A mit on/off + range */}
      <div style={{
        display: 'flex', flexDirection: 'column', gap: 3,
        padding: '6px 8px', borderRadius: 4,
        background: allOK ? 'rgba(95,168,150,0.10)' : 'rgba(200,112,80,0.08)',
        border: '1px solid ' + (allOK ? 'rgba(95,168,150,0.4)' : 'rgba(200,112,80,0.35)'),
        fontSize: 10, fontFamily: 'DM Mono, monospace',
      }}>
        {/* H — editierbar; aendert Hoehe und kippt Ziel auf "eigene" */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
          <span style={{ width: 16 }} />
          <span style={{ color: 'var(--text-muted)', width: 30 }}>H</span>
          <input type="text" value={hStr}
                 onChange={(e) => setHStr(e.target.value)}
                 onBlur={onCommitH}
                 onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
                 style={{
                   width: 56, fontSize: 10, fontFamily: 'DM Mono, monospace',
                   padding: '1px 3px', background: 'transparent',
                   border: '1px solid ' + (hasHOver ? 'var(--accent)' : 'var(--border-light)'),
                   color: 'var(--text-primary)',
                 }} />
          <span style={{ color: 'var(--text-muted)' }}>m</span>
          {hasHOver && (
            <button onClick={onClearHOver}
              title="Zurück zu Geschoss-Höhe"
              style={{
                marginLeft: 'auto', fontSize: 9,
                background: 'transparent', border: 'none',
                color: 'var(--text-muted)', cursor: 'pointer',
                textDecoration: 'underline',
              }}>auto</button>
          )}
        </div>
        <SollRow label="S"     value={S}   unit="m" soll={soll} sollKey="s"
                  onUpdateSoll={onUpdateSoll} />
        <SollRow label="A"     value={A}   unit="m" soll={soll} sollKey="a"
                  onUpdateSoll={onUpdateSoll} />
        <SollRow label="2S+A"  value={sa}  unit="m" soll={soll} sollKey="sa"
                  onUpdateSoll={onUpdateSoll} />
      </div>
    </div>
  )
}

function OeffnungProperties({ oeff, onUpdate, onDelete, oeffStyles = [] }) {
  const isFenster = oeff.kind === 'fenster'
  const label = isFenster ? 'Fenster' : 'Tür'
  const icon = isFenster ? 'window' : 'sensor_door'
  const [breite, setBreite] = useState(String(oeff.breite ?? (isFenster ? 1.2 : 0.9)))
  const [hoehe, setHoehe] = useState(String(oeff.hoehe ?? (isFenster ? 1.4 : 2.1)))
  const [brueest, setBrueest] = useState(String(oeff.brueest ?? 0.9))
  const [rahmenB, setRahmenB] = useState(String(oeff.rahmenB ?? 0.06))
  const [rahmenTiefe, setRahmenTiefe] = useState(String(oeff.rahmenTiefe ?? 0.08))
  useEffect(() => {
    setBreite(String(oeff.breite ?? (isFenster ? 1.2 : 0.9)))
    setHoehe(String(oeff.hoehe ?? (isFenster ? 1.4 : 2.1)))
    setBrueest(String(oeff.brueest ?? 0.9))
    setRahmenB(String(oeff.rahmenB ?? 0.06))
    setRahmenTiefe(String(oeff.rahmenTiefe ?? 0.08))
  }, [oeff.id, oeff.breite, oeff.hoehe, oeff.brueest, oeff.rahmenB,
      oeff.rahmenTiefe, isFenster])

  const commit = (key, val, setBack, def) => {
    const v = parseFloat(val)
    if (!Number.isNaN(v) && v > 0) onUpdate({ [key]: v })
    else setBack(String(def))
  }

  const fluegel = oeff.fluegel ?? 1
  const simsAus = oeff.simsAus ?? 'ohne'

  return (
    <div style={{
      display: 'flex', flexDirection: 'column', gap: 8,
      padding: 10, marginBottom: 8,
      background: 'var(--bg-section)',
      borderRadius: 'var(--r-lg)',
    }}>
      <div style={{ display: 'flex', alignItems: 'center' }}>
        <Icon name={icon} size={13} style={{ color: 'var(--accent)', marginRight: 6 }} />
        <span style={{ ...labelXs, flex: 1, color: 'var(--accent)' }}>
          {label} · {oeff.geschossName}
        </span>
        <button className="btn-icon-sm btn-icon-danger" onClick={onDelete} title="Löschen">
          <Icon name="delete" size={12} />
        </button>
      </div>

      {/* Stil-Picker — Liste passender Styles (gefiltert nach typ) */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
              title="Stil — gespeicherte Properties-Sets fuer Fenster/Tueren">
          Stil
        </span>
        <div style={{ flex: 1, display: 'flex' }}>
          <BarCombo
            value={oeff.styleId || ''}
            onChange={(v) => {
              if (v === '__save__') {
                const sugg = (oeffStyles.find(s => s.id === oeff.styleId) || {}).name || ''
                const n = (window.prompt('Name fuer neuen Stil:', sugg || (isFenster ? 'Mein Fenster' : 'Meine Tuer')) || '').trim()
                if (!n) return
                saveOeffStyle(n, {
                  typ: isFenster ? 'fenster' : 'tuer',
                  breite: oeff.breite, hoehe: oeff.hoehe, brueest: oeff.brueest,
                  rahmenB: oeff.rahmenB, rahmenTiefe: oeff.rahmenTiefe,
                  rahmenOffset: oeff.rahmenOffset,
                  fluegel: oeff.fluegel, simsAus: oeff.simsAus,
                  glas: oeff.glas, darstellung: oeff.darstellung,
                  tuerRahmen: oeff.tuerRahmen,
                  tuerTyp: oeff.tuerTyp, hingeSide: oeff.hingeSide,
                  openAngle: oeff.openAngle,
                })
                return
              }
              if (v === '__delete__') {
                if (oeff.styleId && window.confirm('Aktiven Stil loeschen?'))
                  deleteOeffStyle(oeff.styleId)
                return
              }
              onUpdate({ styleId: v })
            }}
            title="Stil anwenden — alle Properties werden gesetzt">
            <option value="">— Eigene Werte —</option>
            {oeffStyles
              .filter(s => s.typ === (isFenster ? 'fenster' : 'tuer'))
              .map(s => <option key={s.id} value={s.id}>{s.name}</option>)}
            <option disabled>──────────</option>
            <option value="__save__">+ Aktuelle als Stil speichern…</option>
            {oeff.styleId && <option value="__delete__">🗑 Aktiven Stil loeschen</option>}
          </BarCombo>
        </div>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
              title="SIA-400 Detaillierungsgrad. Einfach=1:100, Standard=1:50, Detail=1:20">
          Darstell.
        </span>
        <div style={{ flex: 1, display: 'flex' }}>
          <BarCombo
            value={oeff.darstellung || 'auto'}
            onChange={(v) => onUpdate({ darstellung: v })}
            title="Detaillierungsgrad — Auto folgt der Modelldarstellung in der Topbar">
            <option value="auto">Nach Modelldarstellung</option>
            <option value="einfach">Einfach (1:100)</option>
            <option value="standard">Standard (1:50)</option>
            <option value="detail">Detail (1:20)</option>
          </BarCombo>
        </div>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
              title="Orientierung — welche Seite der Wand ist aussen. Beim Setzen aus der Click-Richtung erkannt, hier umkehren falls falsch.">
          Orient.
        </span>
        <div style={{ flex: 1, display: 'flex' }}>
          <BarToggle label="Umkehren"
            onClick={() => onUpdate({ aussenseite:
              (oeff.aussenseite || 'rechts') === 'rechts' ? 'links' : 'rechts' })}
            title="Aussenseite auf die andere Wandseite umkehren" />
        </div>
      </div>

      {!isFenster && (
        <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
          <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
                title="Tueren-Typ. Wandoeffnung = nur Cutout ohne Schwung-Blatt">
            Typ
          </span>
          <div style={{ flex: 1, display: 'flex', gap: 3 }}>
            <BarToggle label="Normal"
              active={(oeff.tuerTyp || 'normal') === 'normal'}
              onClick={() => onUpdate({ tuerTyp: 'normal' })}
              title="Tuere mit Tuerblatt + Schwung-Bogen" />
            <BarToggle label="Wandöffnung"
              active={(oeff.tuerTyp || 'normal') === 'wandoeffnung'}
              onClick={() => onUpdate({ tuerTyp: 'wandoeffnung' })}
              title="Nur Wand-Cutout ohne Schwung" />
          </div>
        </div>
      )}

      {!isFenster && (oeff.tuerTyp || 'normal') === 'normal' && (
        <>
          <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
            <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
                  title="Bandseite — welche Tuerflueg-Seite. Im Plan = Scharnier-Position">
              Band
            </span>
            <div style={{ flex: 1, display: 'flex', gap: 3 }}>
              <BarToggle label="Links"
                active={(oeff.hingeSide || 'links') === 'links'}
                onClick={() => onUpdate({ hingeSide: 'links' })} />
              <BarToggle label="Rechts"
                active={(oeff.hingeSide || 'links') === 'rechts'}
                onClick={() => onUpdate({ hingeSide: 'rechts' })} />
            </div>
          </div>

          <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
            <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
                  title="Oeffnungswinkel im Plan (Grad, 0-180)">
              Öffn.
            </span>
            <input type="text"
                   value={String(oeff.openAngle ?? 90)}
                   onChange={(e) => {
                     const v = parseFloat(e.target.value.replace(',', '.'))
                     if (!Number.isNaN(v) && v >= 0 && v <= 180) onUpdate({ openAngle: v })
                   }}
                   onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
                   style={{ flex: 1, fontSize: 11, fontFamily: 'DM Mono, monospace' }} />
            <span style={{ fontSize: 10, color: 'var(--text-muted)' }}>°</span>
            <BarToggle label="Umkehren"
              onClick={() => onUpdate({ swingInvert: !oeff.swingInvert })}
              title="Schwung-Richtung umkehren (nach aussen statt innen)" />
          </div>
        </>
      )}

      {!isFenster && (oeff.tuerTyp || 'normal') === 'normal' && (
        <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
          <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
                title="Tueren-Rahmen-Typ. Zarge sitzt in der Oeffnung, Blockrahmen sitzt aussen herum">
            Rahmen
          </span>
          <div style={{ flex: 1, display: 'flex', gap: 3 }}>
            <BarToggle label="Zarge"
              active={(oeff.tuerRahmen || 'zarge') === 'zarge'}
              onClick={() => onUpdate({ tuerRahmen: 'zarge' })} />
            <BarToggle label="Block"
              active={(oeff.tuerRahmen || 'zarge') === 'block'}
              onClick={() => onUpdate({ tuerRahmen: 'block' })} />
          </div>
        </div>
      )}

      {!isFenster && (oeff.tuerTyp || 'normal') === 'normal' && (
        <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
          <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
                title="Sturzlinien-Anzeige bei 1:100 (gestrichelt). Aussen = Linie an Wand-Aussenseite, Innen = Wand-Innenseite, Beide = beide Linien">
            Sturz
          </span>
          <select
            value={oeff.sturz || 'beide'}
            onChange={(e) => onUpdate({ sturz: e.target.value })}
            style={{ flex: 1, fontSize: 11 }}>
            <option value="keine">Keine</option>
            <option value="innen">Innen</option>
            <option value="aussen">Aussen</option>
            <option value="beide">Beide</option>
          </select>
        </div>
      )}

      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Breite</span>
        <input type="text" value={breite}
               onChange={(e) => setBreite(e.target.value)}
               onBlur={() => commit('breite', breite, setBreite, oeff.breite)}
               onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
               style={{ flex: 1, fontSize: 11, fontFamily: 'DM Mono, monospace' }} />
        <span style={{ fontSize: 10, color: 'var(--text-muted)' }}>m</span>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Höhe</span>
        <input type="text" value={hoehe}
               onChange={(e) => setHoehe(e.target.value)}
               onBlur={() => commit('hoehe', hoehe, setHoehe, oeff.hoehe)}
               onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
               style={{ flex: 1, fontSize: 11, fontFamily: 'DM Mono, monospace' }} />
        <span style={{ fontSize: 10, color: 'var(--text-muted)' }}>m</span>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
              title={isFenster
                ? 'Brüstungshöhe über UK der Wand'
                : 'Türschwelle / Höhe über UK der Wand'}>
          {isFenster ? 'Brüst.' : 'Schw.'}
        </span>
        <input type="text" value={brueest}
               onChange={(e) => setBrueest(e.target.value)}
               onBlur={() => {
                 const v = parseFloat(brueest)
                 if (!Number.isNaN(v) && v >= 0) onUpdate({ brueest: v })
                 else setBrueest(String(oeff.brueest))
               }}
               onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
               style={{ flex: 1, fontSize: 11, fontFamily: 'DM Mono, monospace' }} />
        <span style={{ fontSize: 10, color: 'var(--text-muted)' }}>m</span>
      </div>

      {/* Referenz-Lage: wo sitzt der Klick-Punkt in der Oeffnung */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
              title="Lage des Klick-Punkts in der Öffnung">
          Ref
        </span>
        <div style={{ flex: 1, display: 'flex', gap: 3 }}>
          {OEFF_REFERENZ_OPTIONS.map(o => (
            <BarToggle key={o.code}
              label={o.label}
              active={(oeff.oeffReferenz || 'mid') === o.code}
              onClick={() => onUpdate({ oeffReferenz: o.code })}
              title={o.hint} />
          ))}
        </div>
      </div>

      <div style={{ height: 1, background: 'var(--border-light)', margin: '2px 0' }} />

      {/* Rahmen-Profil (Breite × Tiefe) — beide Felder gleich breit */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
              title="Rahmen-Profil: Breite (in Wandflaeche) × Tiefe (entlang Wandnormale)">
          Rahmen
        </span>
        <input type="text" value={rahmenB}
               onChange={(e) => setRahmenB(e.target.value)}
               onBlur={() => commit('rahmenB', rahmenB, setRahmenB, oeff.rahmenB)}
               onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
               title="Profil-Breite (sichtbar in der Fassade)"
               style={{ flex: '1 1 0', minWidth: 0, width: 0, fontSize: 11, fontFamily: 'DM Mono, monospace' }} />
        <span style={{ fontSize: 10, color: 'var(--text-muted)' }}>×</span>
        <input type="text" value={rahmenTiefe}
               onChange={(e) => setRahmenTiefe(e.target.value)}
               onBlur={() => commit('rahmenTiefe', rahmenTiefe, setRahmenTiefe, oeff.rahmenTiefe)}
               onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
               title="Rahmen-Tiefe (Lage in der Wand)"
               style={{ flex: '1 1 0', minWidth: 0, width: 0, fontSize: 11, fontFamily: 'DM Mono, monospace' }} />
        <span style={{ fontSize: 10, color: 'var(--text-muted)' }}>m</span>
      </div>

      {/* Rahmen-Lage: Abstand der Rahmen-Innenkante von der Wand-Innenseite */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
              title="Abstand der Rahmen-Innenkante von der Wand-Innenseite (Aussenseite-Flag oben bestimmt welche Seite innen ist)">
          Lage
        </span>
        <input type="text"
               value={String(oeff.rahmenOffset ?? 0.05)}
               onChange={(e) => {
                 const v = parseFloat(e.target.value.replace(',', '.'))
                 if (!Number.isNaN(v) && v >= 0) onUpdate({ rahmenOffset: v })
               }}
               onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
               style={{ flex: 1, fontSize: 11, fontFamily: 'DM Mono, monospace' }} />
        <span style={{ fontSize: 10, color: 'var(--text-muted)' }}>m v. innen</span>
      </div>

      {/* Fluegel-Anzahl — nur fuer Fenster (Tueren haben ein einzelnes Tuerblatt) */}
      {isFenster && (
        <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
          <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
                title="Anzahl Flügel (vertikale Unterteilung)">
            Flügel
          </span>
          <div style={{ flex: 1, display: 'flex', gap: 3 }}>
            {[1, 2, 3, 4].map(n => (
              <BarToggle key={n}
                label={String(n)}
                active={fluegel === n}
                onClick={() => onUpdate({ fluegel: n })} />
            ))}
          </div>
        </div>
      )}

      {/* Sims — nur aussen. Innen gibt's bewusst nicht. Dient zugleich
          als visueller Indikator fuer die Aussenseite-Einstellung. */}
      {isFenster && (
        <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
          <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
                title="Aussensims — Platte unter Öffnung, ragt aussen heraus">
            Sims
          </span>
          <div style={{ flex: 1, display: 'flex' }}>
            <BarCombo
              value={simsAus}
              onChange={(v) => onUpdate({ simsAus: v })}
              title="Sims-Stil">
              {SIMS_OPTIONS.map(o =>
                <option key={o.code} value={o.code}>{o.label}</option>)}
            </BarCombo>
          </div>
        </div>
      )}

      {/* Glas-Toggle: bei Tueren ersetzt Glas das Tuerblatt (verglaste Tuer) */}
      <div style={{ display: 'flex', gap: 4 }}>
        <BarToggle
          label={(isFenster ? 'Glas' : 'Verglast') + (oeff.glas ? ' ✓' : '')}
          active={!!oeff.glas}
          onClick={() => onUpdate({ glas: !oeff.glas })}
          title={isFenster ? 'Glasscheibe sichtbar' : 'Verglaste Tür (statt Türblatt)'} />
      </div>
    </div>
  )
}


// Wiederverwendete UI fuer UK/OK-Felder mit Auto/Custom-Toggle
function AutoOverrideField({ label, auto, autoValue, rawValue, onChangeRaw, onToggle, onCommit }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
      <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>{label}</span>
      <BarToggle label={auto ? 'Auto' : 'Custom'} active={auto}
        onClick={onToggle}
        title={auto ? 'Folgt Geschoss' : 'Eigener Wert'} />
      <input type="text"
             value={auto ? fmtNum(autoValue) : rawValue}
             disabled={auto}
             onChange={(e) => onChangeRaw(e.target.value)}
             onBlur={onCommit}
             onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
             style={{ flex: 1, fontSize: 11, fontFamily: 'DM Mono, monospace',
                      opacity: auto ? 0.6 : 1 }} />
    </div>
  )
}
