// Selektions-Attribut-Kontrakt: leitet aus der aktuellen Auswahl
// (selectedWallIds/selectedDrawingId) + dem Projekt ein normalisiertes,
// darstellungsfertiges Attribut-Objekt für das ERSTE selektierte Element ab.
// Die kommenden Paletten (Attributes, Object-Info) lesen ausschließlich diesen
// Kontrakt — sie kennen weder das Modell noch die Auflösungs-Logik.
//
// „Effektiv aufgelöst" = dieselbe Reihenfolge wie generatePlan: explizite
// Übersteuerung am Element → LineStyle → Kategorie-Default. So zeigt die Palette
// den TATSÄCHLICH gezeichneten Wert, nicht nur das (oft leere) Override-Feld.
//
// Bezeichner englisch, Kommentare deutsch (CONVENTIONS.md).

import {
  ceilingThickness,
  flattenCategories,
  getCeilingType,
  getWallType,
  wallTypeLabel,
  wallTypeThickness,
} from "../model/types";
import type {
  AttributeSource,
  Ceiling,
  Column,
  ColumnProfile,
  Drawing2D,
  Drawing2DGeom,
  ExtrudedSolid,
  LayerCategory,
  Opening,
  Project,
  Room,
  SiaCategory,
  SliceTermination,
  Stair,
  StairShape,
  VerticalAnchor,
  Wall,
  WallReferenceLine,
} from "../model/types";
import { columnVerticalExtent } from "../model/wall";
import { columnFootprint } from "../geometry/column";
import { evaluateRoom, polygonArea } from "../geometry/roomArea";
import {
  ceilingVerticalExtent,
  nextFloorAbove,
  stairVerticalExtent,
  wallVerticalExtent,
} from "../model/wall";
import { ceilingArea, outlineBBox } from "../geometry/ceiling";
import { openingGapQuad, openingVerticalExtent } from "../geometry/opening";
import { stairGeometry, stairBBox } from "../geometry/stair";

/** Eintrag für die WallType-Auswahl (Preset-Dropdown) im Object-Info-Panel. */
export interface WallTypeChoice {
  id: string;
  name: string;
  /** Kurz-Label für das Dropdown (aus wallTypeLabel: Kürzel+Dicke oder Name). */
  label: string;
  /** Gesamtdicke (Meter) — informativ im Dropdown-Label. */
  thickness: number;
  /** Anzahl Schichten (1 = einschichtig). */
  layerCount: number;
}

/** Eintrag für die Geschoss-Auswahl (Referenzgeschoss / OK-Bindung). */
export interface FloorChoice {
  id: string;
  name: string;
}

/**
 * Wand-spezifische Attribute für das Object-Info-Panel (nur bei Auswahl einer
 * einzelnen Wand gesetzt). Effektiv aufgelöste, anzeigefertige Werte plus die
 * Listen, die die Dropdowns brauchen.
 */
export interface WallInfo {
  referenceLine: WallReferenceLine;
  /** Terminierungs-Regel am Deckenanschluss (Default "both" = heutiges Verhalten). */
  sliceTermination: SliceTermination;
  wallTypeId: string;
  /** Aktuelle Gesamtdicke (Meter). */
  thickness: number;
  /** Ob der aktuelle Wandtyp einschichtig ist (1 Schicht). */
  singleLayer: boolean;
  /** Verfügbare Wandtyp-Presets (für den Mehrschicht-Aufbau). */
  wallTypes: WallTypeChoice[];
  /** Geschoss, dem die Wand zugeordnet ist (Referenzgeschoss). */
  floorId: string;
  floorName: string;
  /** Alle Geschosse (für UK/OK-„an Geschoss gebunden"-Auswahl). */
  floors: FloorChoice[];
  /** Geschoss direkt darüber (für „Verknüpfung zum oberen Geschoss"). */
  floorAbove?: FloorChoice;
  /** UK-Bindung (undefined = Geschoss-Default). */
  bottom?: VerticalAnchor;
  /** OK-Bindung (undefined = UK + height). */
  top?: VerticalAnchor;
  /** Aufgelöste absolute UK/OK (Meter) + abgeleitete Höhe. */
  zBottom: number;
  zTop: number;
  /** Achslänge der Wand (Meter). */
  length: number;
  /** Bruttovolumen = Länge × Dicke × Höhe (m³, ohne Öffnungsabzug). */
  grossVolume: number;
  /** Summe der Öffnungsvolumina (Fenster/Türen) dieser Wand (m³). */
  openingVolume: number;
  /** Nettovolumen = Brutto − Öffnungen (m³, ≥ 0). */
  netVolume: number;
}

/**
 * Decken-spezifische Attribute für das Object-Info-Panel (nur bei Auswahl genau
 * einer Decke gesetzt). Anzeigefertige Werte + Listen für die Dropdowns.
 */
export interface CeilingInfo {
  /** Effektiv aktiver Aufbau-Typ: `ceilingTypeId`, sonst LEGACY-`wallTypeId`. */
  typeId: string;
  /** Aktuelle Gesamtdicke (Meter). */
  thickness: number;
  /** Ob der aktuelle Aufbau-Typ einschichtig ist (1 Schicht). */
  singleLayer: boolean;
  /** Grundfläche der Decke (m²). */
  area: number;
  /** Volumen = Fläche × Dicke (m³). */
  volume: number;
  /** Verfügbare Deckentyp-Presets (dediziert, `project.ceilingTypes`). */
  ceilingTypes: WallTypeChoice[];
  /** Geschoss, dem die Decke zugeordnet ist. */
  floorId: string;
  floorName: string;
  /** Alle Geschosse (für die OK-„an Geschoss gebunden"-Auswahl). */
  floors: FloorChoice[];
  /** OK-Bindung (undefined = Geschoss-Oberkante). */
  top?: VerticalAnchor;
  /** UK-Bindung (undefined = OK − Dicke). */
  bottom?: VerticalAnchor;
  /** Aufgelöste absolute UK/OK (Meter). */
  zBottom: number;
  zTop: number;
}

/**
 * Öffnungs-spezifische Attribute für das Object-Info-Panel (nur bei Auswahl
 * genau einer Öffnung gesetzt). Anzeigefertige Werte + Wirts-Wand-Bezug.
 */
export interface OpeningInfo {
  kind: "window" | "door";
  /** Wirts-Wand (ID + Name/Geschoss zur Anzeige). */
  hostWallId: string;
  hostWallName: string;
  /** Abstand vom Wand-Startpunkt (Meter). */
  position: number;
  width: number;
  height: number;
  /** Brüstungshöhe (Meter); 0 bei Türen. */
  sillHeight: number;
  /** Nur Tür: Anschlag/Aufschlag/Winkel/Richtung. */
  hinge?: "start" | "end";
  swing?: "left" | "right";
  swingAngle?: number;
  openingDir?: "in" | "out";
  /** Nur Fenster: Anzahl der Flügel (1–4). Fehlt → 1 (Default). */
  wingCount?: number;
  /** Nur Tür: Tür-Typ (normal / Wandöffnung). Fehlt → "normal". */
  doorType?: "normal" | "wandoeffnung";
  /** Nur Tür: Sturzlinien (keine/innen/aussen/beide). Fehlt → "beide". */
  lintelLines?: "keine" | "innen" | "aussen" | "beide";
  /** Zugewiesener Tür-/Fenstertyp (Bibliothek, `Opening.typeId`) oder undefined. */
  typeId?: string;
  /** Aufgelöste absolute UK/OK (Meter). */
  zBottom: number;
  zTop: number;
}

/**
 * Treppen-spezifische Attribute für das Object-Info-Panel (nur bei Auswahl genau
 * einer Treppe gesetzt). Anzeigefertige Werte + Listen für die Dropdowns.
 */
export interface StairInfo {
  shape: StairShape;
  /** Laufbreite (Meter). */
  width: number;
  /** Stufenanzahl (Setzstufen). */
  stepCount: number;
  /** Gesamt-Steighöhe (Meter, OKFF → OKFF). */
  totalRise: number;
  /** Abgeleitete Steigungshöhe (Meter) = totalRise / stepCount. */
  riserHeight: number;
  /** Abgeleitete Auftrittstiefe (Meter). */
  treadDepth: number;
  /** Laufrichtung aufwärts (Auf-/Abpfeil-Richtung). */
  up: boolean;
  /** Referenzpunkt der Laufbreite (links/mitte/rechts). Fehlt → "mitte". */
  referenz?: "links" | "mitte" | "rechts";
  /** Zugewiesener Treppentyp (Bibliothek, `Stair.typeId`) oder undefined. */
  typeId?: string;
  /** Geschoss, dem die Treppe zugeordnet ist. */
  floorId: string;
  floorName: string;
  /** Aufgelöste absolute UK/OK (Meter). */
  zBottom: number;
  zTop: number;
}

/**
 * Raum-spezifische Attribute für das Object-Info-Panel (nur bei Auswahl genau
 * eines Raums gesetzt). Anzeigefertige Werte; Fläche/Umfang werden aus dem
 * Umriss abgeleitet (nie gespeichert).
 */
export interface RoomInfo {
  /** Raum-Name (editierbar). */
  name: string;
  /** SIA-416-Blatt-Kategorie (HNF/NNF/VF/FF/KGF). */
  siaCategory: SiaCategory;
  /** Netto-anrechenbare Fläche (m²). */
  area: number;
  /** Umfang des Umrisses (m). */
  perimeter: number;
  /** Geschoss, dem der Raum zugeordnet ist. */
  floorId: string;
  floorName: string;
}

/**
 * Extrusions-spezifische Attribute für das Object-Info-Panel (nur bei Auswahl
 * genau eines extrudierten Körpers, truck-Integration, gesetzt).
 */
export interface ExtrudedSolidInfo {
  /** Extrusionshöhe in Metern (editierbar). */
  height: number;
  /** Verjüngung 0 (Prisma) … 1 (Spitze), editierbar. */
  taper: number;
  /** Grundfläche des Profils (m²). */
  area: number;
  /** Geschoss, dessen `baseElevation` die UK des Körpers bestimmt. */
  floorId: string;
  floorName: string;
}

/**
 * Stützen-spezifische Attribute für das Object-Info-Panel (nur bei Auswahl genau
 * einer Stütze gesetzt). Anzeigefertige Werte für die Panel-Felder.
 */
export interface ColumnInfo {
  /** Profil (Rechteck/Kreis) — bestimmt, welche Masse editierbar sind. */
  profile: ColumnProfile;
  /** Höhe (Meter, editierbar). */
  height: number;
  /** Drehung (Grad, editierbar) — intern in Radiant gespeichert. */
  rotationDeg: number;
  /** Geschoss, dem die Stütze zugeordnet ist. */
  floorId: string;
  floorName: string;
  /** Aufgelöste absolute UK/OK (Meter). */
  zBottom: number;
  zTop: number;
}

/** Achsparallele Bounding-Box eines Elements in Modell-Metern. */
export interface SelectionBBox {
  minX: number;
  minY: number;
  maxX: number;
  maxY: number;
}

/**
 * Normalisierte Sicht auf das erste selektierte Element. Farbe/Strichstärke sind
 * EFFEKTIV aufgelöst (Override sonst Default), damit die Palette den real
 * gezeichneten Wert zeigt. `fillHatchId`/`closed` sind nur für Drawing2D sinnvoll.
 */
export interface Selection {
  kind:
    | "wall"
    | "ceiling"
    | "opening"
    | "stair"
    | "room"
    | "drawing2d"
    | "door"
    | "extrudedSolid"
    | "column";
  id: string;
  /** Grafik-Kategorie (Ebene) des Elements. */
  categoryCode: string;
  /** Effektiv aufgelöste Strich-/Umrandungsfarbe (hex). */
  color: string;
  /** Effektiv aufgelöste Strichstärke in Millimetern. */
  weightMm: number;
  /**
   * Schraffur-ID der Füllung (nur Drawing2D, geschlossene Form); `null` = keine.
   * Bei Wänden/Türen `undefined` (Konzept nicht anwendbar).
   */
  fillHatchId?: string | null;
  /**
   * Vollton-Füllfarbe der Füllung (nur Drawing2D, geschlossene Form); `null` =
   * keine (transparent). Bei Wänden/Türen `undefined` (nicht anwendbar).
   */
  fillColor?: string | null;
  /**
   * Vordergrund-Override (Muster-/Schraffurfarbe) des Elements; `undefined` =
   * „Nach System" (erbt vom Bauteil/System). Gesetzt bei Wand, Decke und
   * geschlossener Drawing2D.
   */
  foreground?: string;
  /**
   * Hintergrund-Override (Füllfarbe/Poché) des Elements; `undefined` = „Nach
   * System". Nachfolger von `fillColor`; hat im Plan Vorrang.
   */
  background?: string;
  /**
   * Quelle des Vordergrunds, wenn `foreground` NICHT gesetzt ist ("layer" =
   * Nach Ebene, "object"/`undefined` = Nach Bauteil, Default). Gesetzt bei
   * Wand, Decke und geschlossener Drawing2D (wie `foreground`/`pocheEditable`).
   */
  foregroundSource?: AttributeSource;
  /** Quelle des Hintergrunds, analog zu {@link Selection.foregroundSource}. */
  backgroundSource?: AttributeSource;
  /**
   * RAW Strichstärke-Override (mm) DIESES Elements; `undefined` = kein Override
   * (Herkunft dann `strokeWeightSource`). Gesetzt bei Wand, Decke, Drawing2D.
   * `weightMm` bleibt der EFFEKTIVE (aufgelöste) Wert für die Anzeige.
   */
  strokeWeightOverride?: number;
  /** Quelle der Strichstärke, wenn kein `strokeWeightOverride` gesetzt ist. */
  strokeWeightSource?: AttributeSource;
  /** Quelle der Schraffur, wenn kein `fillHatchId` gesetzt ist. */
  hatchSource?: AttributeSource;
  /** Ob die Form geschlossen ist (rect/circle/closed polyline) — nur Drawing2D. */
  closed?: boolean;
  /** Achsparallele Bounding-Box in Modell-Metern. */
  bbox: SelectionBBox;
  /**
   * Wand-Attribute (nur bei `kind === "wall"`). Trägt Referenzlinie, Aufbau-Typ
   * (Wandtyp/Dicke), Referenzgeschoss + UK/OK für den Wand-Abschnitt im Panel.
   */
  wall?: WallInfo;
  /** Decken-Attribute (nur bei `kind === "ceiling"`). */
  ceiling?: CeilingInfo;
  /** Öffnungs-Attribute (nur bei `kind === "opening"`). */
  opening?: OpeningInfo;
  /** Treppen-Attribute (nur bei `kind === "stair"`). */
  stair?: StairInfo;
  /** Raum-Attribute (nur bei `kind === "room"`). */
  room?: RoomInfo;
  /** Extrusions-Attribute (nur bei `kind === "extrudedSolid"`). */
  extrudedSolid?: ExtrudedSolidInfo;
  /** Stützen-Attribute (nur bei `kind === "column"`). */
  column?: ColumnInfo;
}

const WALL_FALLBACK_MM = 0.18;
const DRAW_FALLBACK_COLOR = "#d0d0d0";
const WALL_FALLBACK_COLOR = "#2b3039";

/** Kategorie (per Code) im Baum suchen — flach, da Codes baumweit eindeutig sind. */
function findCategory(
  layers: LayerCategory[],
  code: string,
): LayerCategory | undefined {
  return flattenCategories(layers).find((c) => c.code === code);
}

/** Ob eine 2D-Form geschlossen ist (Füllung/Schraffur sinnvoll). */
function isClosedGeom(g: Drawing2DGeom): boolean {
  return (
    g.shape === "rect" ||
    g.shape === "circle" ||
    (g.shape === "polyline" && g.closed)
  );
}

/** Bounding-Box einer 2D-Form. */
function geomBBox(g: Drawing2DGeom): SelectionBBox {
  let minX = Infinity,
    minY = Infinity,
    maxX = -Infinity,
    maxY = -Infinity;
  const acc = (x: number, y: number) => {
    minX = Math.min(minX, x);
    minY = Math.min(minY, y);
    maxX = Math.max(maxX, x);
    maxY = Math.max(maxY, y);
  };
  switch (g.shape) {
    case "line":
      acc(g.a.x, g.a.y);
      acc(g.b.x, g.b.y);
      break;
    case "polyline":
      for (const p of g.pts) acc(p.x, p.y);
      break;
    case "rect":
      acc(g.min.x, g.min.y);
      acc(g.max.x, g.max.y);
      break;
    case "circle":
    case "arc":
      acc(g.center.x - g.r, g.center.y - g.r);
      acc(g.center.x + g.r, g.center.y + g.r);
      break;
    case "text":
      acc(g.at.x, g.at.y);
      break;
  }
  if (!isFinite(minX)) return { minX: 0, minY: 0, maxX: 0, maxY: 0 };
  return { minX, minY, maxX, maxY };
}

/** Selektion für eine Wand ableiten (Farbe übersteuerbar, Strichstärke = Kategorie). */
function wallSelection(project: Project, wall: Wall): Selection {
  const cat = findCategory(project.layers, wall.categoryCode);
  const color = wall.color ?? cat?.color ?? WALL_FALLBACK_COLOR;
  // Effektive Strichstärke: expliziter Override zuerst, sonst die Kategorie
  // (die Quelle "layer"/"object" macht heute keinen Unterschied, da Component
  // keine eigene Strichstärke trägt — siehe resolveStrokeWeight).
  const weightMm = wall.strokeWeight ?? cat?.lw ?? WALL_FALLBACK_MM;
  const minX = Math.min(wall.start.x, wall.end.x);
  const minY = Math.min(wall.start.y, wall.end.y);
  const maxX = Math.max(wall.start.x, wall.end.x);
  const maxY = Math.max(wall.start.y, wall.end.y);
  // Wand-Attribute auflösen (Referenzlinie, Aufbau, Referenzgeschoss, UK/OK).
  const wt = getWallType(project, wall);
  const thickness = wallTypeThickness(wt);
  const floor = project.drawingLevels.find((z) => z.id === wall.floorId);
  const { zBottom, zTop } = wallVerticalExtent(project, wall);
  const above = nextFloorAbove(project, wall);
  // Volumen aus Achslänge × Gesamtdicke × Höhe, abzüglich der Öffnungen (jede
  // Öffnung durchbricht die volle Wanddicke: Breite × Höhe × Dicke).
  const length = Math.hypot(wall.end.x - wall.start.x, wall.end.y - wall.start.y);
  const wallHeight = Math.max(0, zTop - zBottom);
  const grossVolume = length * thickness * wallHeight;
  const openingVolume = (project.openings ?? [])
    .filter((o) => o.hostWallId === wall.id)
    .reduce((sum, o) => sum + o.width * o.height * thickness, 0);
  const netVolume = Math.max(0, grossVolume - openingVolume);
  const wallInfo: WallInfo = {
    referenceLine: wall.referenceLine ?? "center",
    sliceTermination: wall.sliceTermination ?? "both",
    wallTypeId: wall.wallTypeId,
    thickness,
    singleLayer: wt.layers.length <= 1,
    wallTypes: project.wallTypes.map((t) => ({
      id: t.id,
      name: t.name,
      label: wallTypeLabel(t, project.components),
      thickness: wallTypeThickness(t),
      layerCount: t.layers.length,
    })),
    floorId: wall.floorId,
    floorName: floor?.name ?? wall.floorId,
    floors: project.drawingLevels
      .filter((z) => z.kind === "floor")
      .map((z) => ({ id: z.id, name: z.name })),
    floorAbove: above,
    bottom: wall.bottom,
    top: wall.top,
    zBottom,
    zTop,
    length,
    grossVolume,
    openingVolume,
    netVolume,
  };
  // bbox aus der Wandachse (start/end); die Schichtdicke bleibt unberücksichtigt,
  // damit die Box der Achs-Geometrie folgt (genügt für die Paletten).
  return {
    kind: "wall",
    id: wall.id,
    categoryCode: wall.categoryCode,
    color,
    weightMm,
    fillHatchId: wall.hatchId,
    fillColor: undefined,
    foreground: wall.foreground,
    background: wall.background,
    foregroundSource: wall.foregroundSource,
    backgroundSource: wall.backgroundSource,
    strokeWeightOverride: wall.strokeWeight,
    strokeWeightSource: wall.strokeWeightSource,
    hatchSource: wall.hatchSource,
    closed: undefined,
    bbox: { minX, minY, maxX, maxY },
    wall: wallInfo,
  };
}

/** Selektion für eine Decke ableiten (Farbe übersteuerbar, Strichstärke = Kategorie). */
function ceilingSelection(project: Project, ceiling: Ceiling): Selection {
  const cat = findCategory(project.layers, ceiling.categoryCode);
  const color = ceiling.color ?? cat?.color ?? WALL_FALLBACK_COLOR;
  const weightMm = ceiling.strokeWeight ?? cat?.lw ?? WALL_FALLBACK_MM;
  const box = outlineBBox(ceiling.outline);
  const wt = getCeilingType(project, ceiling);
  const thickness = ceilingThickness(project, ceiling);
  const floor = project.drawingLevels.find((z) => z.id === ceiling.floorId);
  const { zBottom, zTop } = ceilingVerticalExtent(project, ceiling);
  const ceilingInfo: CeilingInfo = {
    typeId: ceiling.ceilingTypeId ?? ceiling.wallTypeId,
    thickness,
    singleLayer: wt.layers.length <= 1,
    area: ceilingArea(ceiling.outline),
    volume: ceilingArea(ceiling.outline) * thickness,
    ceilingTypes: (project.ceilingTypes ?? []).map((t) => ({
      id: t.id,
      name: t.name,
      label: wallTypeLabel(t, project.components),
      thickness: wallTypeThickness(t),
      layerCount: t.layers.length,
    })),
    floorId: ceiling.floorId,
    floorName: floor?.name ?? ceiling.floorId,
    floors: project.drawingLevels
      .filter((z) => z.kind === "floor")
      .map((z) => ({ id: z.id, name: z.name })),
    top: ceiling.top,
    bottom: ceiling.bottom,
    zBottom,
    zTop,
  };
  return {
    kind: "ceiling",
    id: ceiling.id,
    categoryCode: ceiling.categoryCode,
    color,
    weightMm,
    fillHatchId: ceiling.hatchId,
    fillColor: undefined,
    foreground: ceiling.foreground,
    background: ceiling.background,
    foregroundSource: ceiling.foregroundSource,
    backgroundSource: ceiling.backgroundSource,
    strokeWeightOverride: ceiling.strokeWeight,
    strokeWeightSource: ceiling.strokeWeightSource,
    hatchSource: ceiling.hatchSource,
    closed: true,
    bbox: { minX: box.minX, minY: box.minY, maxX: box.maxX, maxY: box.maxY },
    ceiling: ceilingInfo,
  };
}

/** Selektion für eine Öffnung ableiten (Farbe übersteuerbar, Strichstärke = Kategorie). */
function openingSelection(project: Project, o: Opening): Selection {
  const cat = findCategory(project.layers, o.categoryCode);
  const color = o.color ?? cat?.color ?? WALL_FALLBACK_COLOR;
  const weightMm = cat?.lw ?? WALL_FALLBACK_MM;
  const wall = project.walls.find((w) => w.id === o.hostWallId);
  const floor = wall
    ? project.drawingLevels.find((z) => z.id === wall.floorId)
    : undefined;
  // Bounding-Box aus dem Lücken-Quad (volle Wanddicke); Fallback: leere Box.
  const quad = wall ? openingGapQuad(project, wall, o) : null;
  let minX = Infinity,
    minY = Infinity,
    maxX = -Infinity,
    maxY = -Infinity;
  for (const p of quad ?? []) {
    minX = Math.min(minX, p.x);
    minY = Math.min(minY, p.y);
    maxX = Math.max(maxX, p.x);
    maxY = Math.max(maxY, p.y);
  }
  if (!isFinite(minX)) {
    minX = minY = maxX = maxY = 0;
  }
  const ext = wall
    ? openingVerticalExtent(project, wall, o)
    : { zBottom: 0, zTop: o.height };
  const openingInfo: OpeningInfo = {
    kind: o.kind,
    hostWallId: o.hostWallId,
    hostWallName: floor ? `${floor.name}` : o.hostWallId,
    position: o.position,
    width: o.width,
    height: o.height,
    sillHeight: o.sillHeight,
    hinge: o.hinge,
    swing: o.swing,
    swingAngle: o.swingAngle,
    openingDir: o.openingDir,
    wingCount: o.wingCount,
    doorType: o.doorType,
    lintelLines: o.lintelLines,
    typeId: o.typeId,
    zBottom: ext.zBottom,
    zTop: ext.zTop,
  };
  return {
    kind: "opening",
    id: o.id,
    categoryCode: o.categoryCode,
    color,
    weightMm,
    fillHatchId: undefined,
    fillColor: undefined,
    closed: undefined,
    bbox: { minX, minY, maxX, maxY },
    opening: openingInfo,
  };
}

/** Selektion für eine Treppe ableiten (Farbe übersteuerbar, Strichstärke = Kategorie). */
function stairSelection(project: Project, s: Stair): Selection {
  const cat = findCategory(project.layers, s.categoryCode);
  const color = s.color ?? cat?.color ?? WALL_FALLBACK_COLOR;
  const weightMm = cat?.lw ?? WALL_FALLBACK_MM;
  const { zBottom, zTop } = stairVerticalExtent(project, s);
  const totalRise = zTop - zBottom;
  const geo = stairGeometry(s, totalRise);
  const box = stairBBox(geo);
  const floor = project.drawingLevels.find((z) => z.id === s.floorId);
  const stairInfo: StairInfo = {
    shape: s.shape,
    width: s.width,
    stepCount: s.stepCount,
    totalRise,
    riserHeight: geo.riserHeight,
    treadDepth: geo.treadDepth,
    up: s.up !== false,
    referenz: s.referenz,
    typeId: s.typeId,
    floorId: s.floorId,
    floorName: floor?.name ?? s.floorId,
    zBottom,
    zTop,
  };
  return {
    kind: "stair",
    id: s.id,
    categoryCode: s.categoryCode,
    color,
    weightMm,
    fillHatchId: undefined,
    fillColor: undefined,
    closed: undefined,
    bbox: { minX: box.minX, minY: box.minY, maxX: box.maxX, maxY: box.maxY },
    stair: stairInfo,
  };
}

/** Selektion für einen Raum ableiten (Fläche/Umfang aus dem Umriss abgeleitet). */
function roomSelection(project: Project, r: Room): Selection {
  const cat = findCategory(project.layers, r.categoryCode);
  const color = r.color ?? cat?.color ?? WALL_FALLBACK_COLOR;
  const weightMm = cat?.lw ?? WALL_FALLBACK_MM;
  const res = evaluateRoom(r.boundary, r.siaCategory);
  const floor = project.drawingLevels.find((z) => z.id === r.floorId);
  let minX = Infinity,
    minY = Infinity,
    maxX = -Infinity,
    maxY = -Infinity;
  for (const p of r.boundary) {
    minX = Math.min(minX, p.x);
    minY = Math.min(minY, p.y);
    maxX = Math.max(maxX, p.x);
    maxY = Math.max(maxY, p.y);
  }
  if (!isFinite(minX)) minX = minY = maxX = maxY = 0;
  const roomInfo: RoomInfo = {
    name: r.name,
    siaCategory: r.siaCategory,
    area: res.netArea,
    perimeter: res.perimeter,
    floorId: r.floorId,
    floorName: floor?.name ?? r.floorId,
  };
  return {
    kind: "room",
    id: r.id,
    categoryCode: r.categoryCode,
    color,
    weightMm,
    fillHatchId: undefined,
    fillColor: undefined,
    closed: true,
    bbox: { minX, minY, maxX, maxY },
    room: roomInfo,
  };
}

/** Auswahl-Farbe der Extrusions-Footprints (identisch zum Grundriss-Umriss/3D-Orange). */
const EXTRUSION_COLOR = "#d98c40";

/** Selektion für einen extrudierten Körper ableiten (truck-Integration). */
function extrudedSolidSelection(project: Project, s: ExtrudedSolid): Selection {
  const floor = project.drawingLevels.find((z) => z.id === s.levelId);
  let minX = Infinity,
    minY = Infinity,
    maxX = -Infinity,
    maxY = -Infinity;
  for (const p of s.points) {
    minX = Math.min(minX, p.x);
    minY = Math.min(minY, p.y);
    maxX = Math.max(maxX, p.x);
    maxY = Math.max(maxY, p.y);
  }
  if (!isFinite(minX)) minX = minY = maxX = maxY = 0;
  const info: ExtrudedSolidInfo = {
    height: s.height,
    taper: s.taper ?? 0,
    area: polygonArea(s.points),
    floorId: s.levelId,
    floorName: floor?.name ?? s.levelId,
  };
  return {
    kind: "extrudedSolid",
    id: s.id,
    categoryCode: "",
    color: EXTRUSION_COLOR,
    weightMm: WALL_FALLBACK_MM,
    fillHatchId: null,
    fillColor: null,
    closed: true,
    bbox: { minX, minY, maxX, maxY },
    extrudedSolid: info,
  };
}

/** Umriss-/Auswahlfarbe der Stützen-Poché (neutrales Tragwerks-Grau). */
const COLUMN_COLOR = "#3a3f47";

/** Selektion für eine Stütze (Column) ableiten. */
function columnSelection(project: Project, col: Column): Selection {
  const floor = project.drawingLevels.find((z) => z.id === col.floorId);
  const category = findCategory(project.layers, col.categoryCode);
  const pts = columnFootprint(col);
  let minX = Infinity,
    minY = Infinity,
    maxX = -Infinity,
    maxY = -Infinity;
  for (const p of pts) {
    minX = Math.min(minX, p.x);
    minY = Math.min(minY, p.y);
    maxX = Math.max(maxX, p.x);
    maxY = Math.max(maxY, p.y);
  }
  if (!isFinite(minX)) minX = minY = maxX = maxY = 0;
  const { zBottom, zTop } = columnVerticalExtent(project, col);
  const info: ColumnInfo = {
    profile: col.profile,
    height: col.height,
    rotationDeg: ((col.rotation ?? 0) * 180) / Math.PI,
    floorId: col.floorId,
    floorName: floor?.name ?? col.floorId,
    zBottom,
    zTop,
  };
  return {
    kind: "column",
    id: col.id,
    categoryCode: col.categoryCode,
    color: col.color ?? category?.color ?? COLUMN_COLOR,
    weightMm: WALL_FALLBACK_MM,
    bbox: { minX, minY, maxX, maxY },
    column: info,
  };
}

/** Selektion für ein 2D-Zeichenelement ableiten (gleiche Auflösung wie generatePlan). */
function drawingSelection(project: Project, d: Drawing2D): Selection {
  const ls = d.lineStyleId
    ? project.lineStyles.find((l) => l.id === d.lineStyleId)
    : undefined;
  const cat = findCategory(project.layers, d.categoryCode);
  const color = d.color ?? ls?.color ?? cat?.color ?? DRAW_FALLBACK_COLOR;
  // Effektive Strichstärke, dieselbe Kette wie generatePlan/addDrawing2D
  // (resolveStrokeWeight): Override zuerst, "layer" erzwingt die Kategorie
  // (umgeht das LineStyle-Gewicht), sonst die heutige LineStyle-/Kategorie-Kette.
  const weightMm =
    d.weightMm ??
    (d.strokeWeightSource === "layer" ? cat?.lw : undefined) ??
    ls?.weight ??
    cat?.lw ??
    WALL_FALLBACK_MM;
  const closed = isClosedGeom(d.geom);
  return {
    kind: "drawing2d",
    id: d.id,
    categoryCode: d.categoryCode,
    color,
    weightMm,
    fillHatchId: closed ? d.hatchId ?? null : null,
    fillColor: closed ? d.fillColor ?? null : null,
    foreground: closed ? d.foreground : undefined,
    background: closed ? d.background : undefined,
    foregroundSource: closed ? d.foregroundSource : undefined,
    backgroundSource: closed ? d.backgroundSource : undefined,
    strokeWeightOverride: d.weightMm,
    strokeWeightSource: d.strokeWeightSource,
    hatchSource: closed ? d.hatchSource : undefined,
    closed,
    bbox: geomBBox(d.geom),
  };
}

/**
 * Leitet den Selektions-Kontrakt für das ERSTE selektierte Element ab.
 * Drawing-Auswahl und Wand-Auswahl schließen sich aus; bei Drawing-Auswahl
 * hat diese Vorrang. Liefert `null`, wenn nichts (oder kein auffindbares
 * Element) selektiert ist.
 */
export function deriveSelection(
  project: Project,
  selectedWallIds: string[],
  selectedDrawingId: string | null,
  selectedCeilingId: string | null = null,
  selectedOpeningId: string | null = null,
  selectedStairId: string | null = null,
  selectedRoomId: string | null = null,
  selectedExtrudedSolidId: string | null = null,
  selectedColumnId: string | null = null,
): Selection | null {
  if (selectedDrawingId) {
    const d = project.drawings2d.find((x) => x.id === selectedDrawingId);
    return d ? drawingSelection(project, d) : null;
  }
  if (selectedExtrudedSolidId) {
    const s = (project.extrudedSolids ?? []).find((x) => x.id === selectedExtrudedSolidId);
    return s ? extrudedSolidSelection(project, s) : null;
  }
  if (selectedColumnId) {
    const c = (project.columns ?? []).find((x) => x.id === selectedColumnId);
    return c ? columnSelection(project, c) : null;
  }
  if (selectedOpeningId) {
    const o = (project.openings ?? []).find((x) => x.id === selectedOpeningId);
    if (o) return openingSelection(project, o);
  }
  const wallId = selectedWallIds[0];
  if (wallId) {
    const w = project.walls.find((x) => x.id === wallId);
    return w ? wallSelection(project, w) : null;
  }
  if (selectedCeilingId) {
    const c = (project.ceilings ?? []).find((x) => x.id === selectedCeilingId);
    return c ? ceilingSelection(project, c) : null;
  }
  if (selectedStairId) {
    const s = (project.stairs ?? []).find((x) => x.id === selectedStairId);
    return s ? stairSelection(project, s) : null;
  }
  if (selectedRoomId) {
    const r = (project.rooms ?? []).find((x) => x.id === selectedRoomId);
    return r ? roomSelection(project, r) : null;
  }
  return null;
}
