const state = {
  bootstrap: null,
  rackTemplateId: null,
  projectName: "Neues Rack-Projekt",
  rackColor: "#50545c",
  components: [],
  placedItems: [],
  nextPlacementId: 1,
  dragPlacementId: null,
  pointerDrag: null,
  selectedComponentId: null,
  libraryDragComponentId: null,
  rackDropPreviewY: null,
};

const ui = {};
const RACK_UNIT_PX = 38;

document.addEventListener("DOMContentLoaded", async () => {
  cacheDom();
  bindEvents();
  await loadBootstrap();
});

function cacheDom() {
  ui.templateSelect = document.getElementById("rack-template-select");
  ui.projectName = document.getElementById("project-name");
  ui.rackColor = document.getElementById("rack-color");
  ui.selectedComponentInfo = document.getElementById("selected-component-info");
  ui.componentFilter = document.getElementById("component-filter");
  ui.pluginInput = document.getElementById("plugin-input");
  ui.componentLibrary = document.getElementById("component-library");
  ui.rackGrid = document.getElementById("rack-grid");
  ui.projectStats = document.getElementById("project-stats");
  ui.bomOutput = document.getElementById("bom-output");
  ui.cableFrom = document.getElementById("cable-from");
  ui.cableTo = document.getElementById("cable-to");
  ui.cableSlack = document.getElementById("cable-slack");
  ui.cableOutput = document.getElementById("cable-output");
  ui.validationOutput = document.getElementById("validation-output");
  ui.exportBom = document.getElementById("export-bom");
}

function bindEvents() {
  ui.templateSelect.addEventListener("change", () => {
    state.rackTemplateId = ui.templateSelect.value;
    state.placedItems = [];
    renderAll();
  });

  ui.projectName.addEventListener("input", () => {
    state.projectName = ui.projectName.value.trim() || "Neues Rack-Projekt";
    renderStats();
  });
  ui.rackColor.addEventListener("input", () => {
    state.rackColor = ui.rackColor.value;
    renderRack();
  });

  ui.componentFilter.addEventListener("input", renderLibrary);
  ui.cableSlack.addEventListener("input", renderCableEstimate);
  ui.cableFrom.addEventListener("change", renderCableEstimate);
  ui.cableTo.addEventListener("change", renderCableEstimate);
  ui.pluginInput.addEventListener("change", importPluginPack);
  ui.exportBom.addEventListener("click", copyBomCsv);
  document.addEventListener("pointermove", handlePointerMove);
  document.addEventListener("pointerup", handlePointerUp);
  document.addEventListener("dragend", handleLibraryDragEnd);
}

async function loadBootstrap() {
  const response = await fetch(window.APP_CONFIG.apiBootstrapUrl, {
    headers: {
      Accept: "application/json",
    },
  });

  if (!response.ok) {
    throw new Error(`Bootstrap request failed: ${response.status}`);
  }

  state.bootstrap = await response.json();
  state.components = [...state.bootstrap.components];
  state.rackTemplateId = state.bootstrap.rackTemplates[0]?.id ?? null;

  renderTemplateOptions();
  renderAll();
}

function renderAll() {
  renderLibrary();
  renderSelectionInfo();
  renderRack();
  renderStats();
  renderBom();
  renderCableSelectors();
  renderCableEstimate();
  renderValidation();
}

function renderTemplateOptions() {
  const options = state.bootstrap.rackTemplates
    .map((template) => {
      const selected = template.id === state.rackTemplateId ? " selected" : "";
      return `<option value="${escapeHtml(template.id)}"${selected}>${escapeHtml(template.name)}</option>`;
    })
    .join("");

  ui.templateSelect.innerHTML = options;
}

function renderSummary() {
  renderStats();
}

function renderLibrary() {
  const rack = getCurrentRackTemplate();
  if (!rack) {
    return;
  }

  const needle = ui.componentFilter.value.trim().toLowerCase();
  const filtered = state.components.filter((component) => {
    const compatible = component.rackStandard === rack.rackStandard;
    const haystack = `${component.name} ${component.category} ${component.partNumber}`.toLowerCase();
    return compatible && (!needle || haystack.includes(needle));
  });

  if (filtered.length === 0) {
    ui.componentLibrary.innerHTML = `<div class="empty-state">Keine passenden Komponenten gefunden.</div>`;
    return;
  }

  ui.componentLibrary.innerHTML = filtered
    .map(
      (component) => `
        <article class="component-card component-card--${escapeHtml(component.category)}" draggable="true" data-component-drag-id="${escapeHtml(component.id)}">
          <div class="component-card__preview">
            ${renderComponentFace(component, "library")}
          </div>
          <div class="component-card__header">
            <div>
              <strong>${escapeHtml(component.name)}</strong>
              <div>${escapeHtml(component.partNumber)}</div>
            </div>
            <span class="chip">${component.heightU}U</span>
          </div>
          <div class="component-card__meta">
            <span>${escapeHtml(component.category)}</span>
            <span>${component.depthMm} mm tief</span>
            <span>${formatCurrency(component.priceNet, component.currency)}</span>
          </div>
          <button type="button" data-action="select-component" data-component-id="${escapeHtml(component.id)}">
            ${state.selectedComponentId === component.id ? "Bereit zum Ziehen" : "Ins Rack ziehen"}
          </button>
        </article>
      `
    )
    .join("");

  ui.componentLibrary.querySelectorAll("[data-action='select-component']").forEach((button) => {
    button.addEventListener("click", () => {
      state.selectedComponentId = button.dataset.componentId;
      renderAll();
    });
  });

  ui.componentLibrary.querySelectorAll("[data-component-drag-id]").forEach((card) => {
    card.addEventListener("dragstart", (event) => beginLibraryDrag(event, card.dataset.componentDragId));
  });
}

function renderSelectionInfo() {
  const component = getComponent(state.libraryDragComponentId || state.selectedComponentId);
  if (!component) {
    ui.selectedComponentInfo.textContent = "Kein Element ausgewaehlt";
    ui.selectedComponentInfo.classList.remove("is-active");
    return;
  }

  ui.selectedComponentInfo.textContent = `${component.name} · ${component.heightU}U · aus der Bibliothek ins Rack ziehen`;
  ui.selectedComponentInfo.classList.add("is-active");
}

function renderRack() {
  const rack = getCurrentRackTemplate();
  if (!rack) {
    return;
  }

  ui.rackGrid.style.setProperty("--rack-unit-height", `${RACK_UNIT_PX}px`);
  ui.rackGrid.style.setProperty("--rack-total-u", String(rack.totalU));
  applyRackColorTheme(ui.rackGrid, state.rackColor);

  ui.rackGrid.innerHTML = `
    <div class="rack-grid__header-badge rack-grid__header-badge--left">${rack.rackStandard === "19_inch" ? '19" Rack' : '10" Rack'}</div>
    <div class="rack-grid__header-badge rack-grid__header-badge--right">${rack.totalU} HE</div>
    <div class="rack-shell">
      <div class="rack-shell__frame rack-shell__frame--top"></div>
      <div class="rack-shell__frame rack-shell__frame--bottom"></div>
      <div class="rack-shell__screw rack-shell__screw--tl"></div>
      <div class="rack-shell__screw rack-shell__screw--tr"></div>
      <div class="rack-shell__screw rack-shell__screw--bl"></div>
      <div class="rack-shell__screw rack-shell__screw--br"></div>
      <div class="rack-core">
        <div class="rack-rail rack-rail--left">${renderRailScale(rack, "left")}</div>
        <div class="rack-bay" id="rack-bay">
          <div class="rack-bay__ceiling"></div>
          <div class="rack-bay__wall rack-bay__wall--left"></div>
          <div class="rack-bay__wall rack-bay__wall--right"></div>
          <div class="rack-bay__back"></div>
          <div class="rack-bay__floor"></div>
          <div class="rack-insertion-layer" id="rack-insertion-layer"></div>
          <div class="rack-items-layer" id="rack-items-layer"></div>
        </div>
        <div class="rack-rail rack-rail--right">${renderRailScale(rack, "right")}</div>
      </div>
    </div>
  `;

  const layer = document.getElementById("rack-items-layer");
  layer.style.height = "100%";
  const bay = document.getElementById("rack-bay");
  bay.addEventListener("dragover", handleRackDragOver);
  bay.addEventListener("drop", handleRackDrop);
  bay.addEventListener("dragleave", handleRackDragLeave);

  const insertionLayer = document.getElementById("rack-insertion-layer");
  insertionLayer.innerHTML = renderInsertionZones(rack);
  ui.rackGrid.querySelectorAll("[data-action='insert-component']").forEach((element) => {
    element.addEventListener("click", () => insertSelectedComponentAt(Number(element.dataset.insertY)));
  });

  state.placedItems.forEach((item) => {
    const component = getComponent(item.componentId);
    if (!component) {
      return;
    }

    const rect = getPlacementRect(item, component);
    const element = document.createElement("article");
    element.className = `rack-item rack-item--${component.category.replace(/_/g, "-")}`;
    element.dataset.standard = component.rackStandard;
    element.style.top = `${rect.top}px`;
    element.style.left = `${rect.left}px`;
    element.style.width = `${rect.width}px`;
    element.style.height = `${rect.height}px`;
    element.innerHTML = `
      <div class="rack-item__face rack-item__face--${escapeHtml(component.category)}">
        ${renderComponentFace(component, "rack")}
      </div>
      <div class="rack-item__overlay">
        <div class="rack-item__header">
          <div>
            <strong>${escapeHtml(component.name)}</strong>
            <div class="rack-item__meta">${component.heightU}U · ${component.depthMm} mm · ${formatCurrency(component.priceNet, component.currency)}</div>
          </div>
          <span class="chip">${formatRackPosition(item.y, component)}</span>
        </div>
        <div class="rack-item__drag-hint">Ziehen zum verschieben</div>
        <button type="button" class="rack-item__remove" data-action="remove" aria-label="Komponente entfernen">×</button>
      </div>
    `;

    element.addEventListener("pointerdown", (event) => beginPointerDrag(event, item, component));
    element.querySelector("[data-action='remove']").addEventListener("click", () => {
      state.placedItems = state.placedItems.filter((entry) => entry.placementId !== item.placementId);
      renderAll();
    });

    layer.appendChild(element);
  });
}

function renderRailScale(rack, side) {
  const rows = [];
  for (let u = rack.totalU; u >= 1; u -= 1) {
    rows.push(`
      <div class="rack-rail__unit">
        <span class="rack-rail__label rack-rail__label--${side}">${u}</span>
        <span class="rack-rail__tick rack-rail__tick--${side}"></span>
        <span class="rack-rail__holes rack-rail__holes--${side}">
          <span></span><span></span><span></span>
        </span>
      </div>
    `);
  }
  return rows.join("");
}

function insertSelectedComponentAt(y) {
  const componentId = state.libraryDragComponentId || state.selectedComponentId;
  if (!componentId) {
    return;
  }

  const component = getComponent(componentId);
  const rack = getCurrentRackTemplate();
  if (!component || !rack) {
    return;
  }

  const nextRect = {
    left: 0,
    top: snapToUnit(y),
    width: getComponentWidthPercent(component),
    height: getComponentHeightPx(component),
  };
  const hasOverlap = state.placedItems.some((item) => {
    const other = getComponent(item.componentId);
    if (!other) {
      return false;
    }
    return rectanglesOverlap(
      nextRect,
      {
        left: item.x,
        top: item.y,
        width: getComponentWidthPercent(other),
        height: getComponentHeightPx(other),
      }
    );
  });

  if (hasOverlap) {
    alert("Diese Einbauposition ist bereits belegt.");
    return;
  }

  state.placedItems.push({
    placementId: `p${state.nextPlacementId++}`,
    componentId: component.id,
    y: snapToUnit(y),
    x: 0,
  });
  state.libraryDragComponentId = null;
  state.rackDropPreviewY = null;
  state.selectedComponentId = null;
  renderAll();
}

function renderStats() {
  const rack = getCurrentRackTemplate();
  if (!rack) {
    return;
  }

  const usedU = state.placedItems.reduce((sum, item) => {
    const component = getComponent(item.componentId);
    return sum + (component?.heightU ?? 0);
  }, 0);

  const usedWeight = state.placedItems.reduce((sum, item) => {
    const component = getComponent(item.componentId);
    return sum + (component?.weightKg ?? 0);
  }, 0);

  const usedPower = state.placedItems.reduce((sum, item) => {
    const component = getComponent(item.componentId);
    return sum + (component?.powerW ?? 0);
  }, 0);

  ui.projectStats.innerHTML = `
    <div class="stat-card">
      <span>Projekt</span>
      <strong>${escapeHtml(state.projectName)}</strong>
    </div>
    <div class="stat-card">
      <span>Rack</span>
      <strong>${rack.rackStandard === "19_inch" ? '19"' : '10"'} · ${rack.totalU}U</strong>
    </div>
    <div class="stat-card">
      <span>Tiefe</span>
      <strong>${rack.usableDepthMm} mm</strong>
    </div>
    <div class="stat-card">
      <span>Belegte U</span>
      <strong>${usedU} / ${rack.totalU}</strong>
    </div>
    <div class="stat-card">
      <span>Freie U</span>
      <strong>${rack.totalU - usedU}</strong>
    </div>
    <div class="stat-card">
      <span>Gewicht</span>
      <strong>${usedWeight.toFixed(1)} / ${rack.maxWeightKg} kg</strong>
    </div>
    <div class="stat-card">
      <span>Leistung</span>
      <strong>${usedPower} W</strong>
    </div>
  `;
}

function renderBom() {
  const aggregated = new Map();

  state.placedItems.forEach((item) => {
    const component = getComponent(item.componentId);
    if (!component) {
      return;
    }

    const key = component.partNumber;
    if (!aggregated.has(key)) {
      aggregated.set(key, {
        quantity: 0,
        name: component.name,
        manufacturer: component.manufacturer,
        partNumber: component.partNumber,
        currency: component.currency,
        priceNet: component.priceNet,
      });
    }

    aggregated.get(key).quantity += 1;
  });

  const lines = Array.from(aggregated.values());
  if (lines.length === 0) {
    ui.bomOutput.innerHTML = `<div class="empty-state">Noch keine Komponenten platziert.</div>`;
    return;
  }

  ui.bomOutput.innerHTML = lines
    .map((line) => {
      const total = line.quantity * line.priceNet;
      return `
        <div class="bom-line">
          <div>
            <strong>${escapeHtml(line.name)}</strong>
            <div>${escapeHtml(line.partNumber)} · ${escapeHtml(line.manufacturer)}</div>
          </div>
          <div>${line.quantity} × ${formatCurrency(total, line.currency)}</div>
        </div>
      `;
    })
    .join("");
}

function renderCableSelectors() {
  const options = ['<option value="">Bitte waehlen</option>']
    .concat(
      state.placedItems.map((item) => {
        const component = getComponent(item.componentId);
        if (!component) {
          return "";
        }
        return `<option value="${escapeHtml(item.placementId)}">${escapeHtml(component.name)} @ ${formatRackPosition(item.y, component)}</option>`;
      })
    )
    .join("");

  ui.cableFrom.innerHTML = options;
  ui.cableTo.innerHTML = options;
}

function renderCableEstimate() {
  const fromId = ui.cableFrom.value;
  const toId = ui.cableTo.value;
  const slackPercent = Number(ui.cableSlack.value || 0);

  if (!fromId || !toId || fromId === toId) {
    ui.cableOutput.textContent = "Fuer eine Schaetzung zwei verschiedene Komponenten auswaehlen.";
    return;
  }

  const from = state.placedItems.find((item) => item.placementId === fromId);
  const to = state.placedItems.find((item) => item.placementId === toId);
  const rack = getCurrentRackTemplate();
  const fromComponent = from ? getComponent(from.componentId) : null;
  const toComponent = to ? getComponent(to.componentId) : null;

  if (!from || !to || !rack || !fromComponent || !toComponent) {
    ui.cableOutput.textContent = "Kabelschaetzung momentan nicht verfuegbar.";
    return;
  }

  const verticalMm = Math.abs(getPlacementCenterY(from, fromComponent) - getPlacementCenterY(to, toComponent)) * (44.45 / getUnitHeightPx());
  const depthAllowance = Math.min(rack.usableDepthMm * 0.35, 280);
  const sideAllowance = estimateSideAllowance(fromComponent, toComponent);
  const rawLength = verticalMm + depthAllowance + sideAllowance;
  const withSlack = rawLength * (1 + slackPercent / 100);
  const recommended = recommendCableLength(withSlack);

  ui.cableOutput.innerHTML = `
    Geschaetzt: <strong>${Math.round(rawLength)} mm</strong><br>
    Mit Reserve: <strong>${Math.round(withSlack)} mm</strong><br>
    Bestelllaenge: <strong>${recommended.toFixed(2)} m</strong>
  `;
}

function renderValidation() {
  const rack = getCurrentRackTemplate();
  if (!rack) {
    return;
  }

  const notes = [];
  const usedU = state.placedItems.reduce((sum, item) => {
    const component = getComponent(item.componentId);
    return sum + (component?.heightU ?? 0);
  }, 0);
  const usedWeight = state.placedItems.reduce((sum, item) => {
    const component = getComponent(item.componentId);
    return sum + (component?.weightKg ?? 0);
  }, 0);

  if (usedU <= rack.totalU) {
    notes.push({ className: "ok", text: `U-Belegung ist gueltig. ${rack.totalU - usedU}U frei.` });
  } else {
    notes.push({ className: "warn", text: "Rack ist ueberbelegt." });
  }

  if (usedWeight <= rack.maxWeightKg) {
    notes.push({ className: "ok", text: `Gewicht innerhalb der Rack-Grenze (${usedWeight.toFixed(1)} kg).` });
  } else {
    notes.push({ className: "warn", text: `Gewicht ueberschreitet die Rack-Grenze von ${rack.maxWeightKg} kg.` });
  }

  const tooDeep = state.placedItems
    .map((item) => ({ item, component: getComponent(item.componentId) }))
    .filter(({ component }) => component && component.depthMm > rack.usableDepthMm);

  const overlaps = getPlacementOverlaps();

  if (tooDeep.length === 0) {
    notes.push({ className: "ok", text: "Keine Tiefenkonflikte erkannt." });
  } else {
    tooDeep.forEach(({ component }) => {
      notes.push({ className: "warn", text: `${component.name} ist tiefer als das Rack erlaubt.` });
    });
  }

  if (overlaps.length === 0) {
    notes.push({ className: "ok", text: "Keine visuellen Ueberschneidungen im Rack erkannt." });
  } else {
    overlaps.forEach((entry) => {
      notes.push({ className: "warn", text: `${entry.a} ueberlappt mit ${entry.b}.` });
    });
  }

  ui.validationOutput.innerHTML = notes.map((note) => `<li class="${note.className}">${escapeHtml(note.text)}</li>`).join("");
}

function importPluginPack(event) {
  const file = event.target.files?.[0];
  if (!file) {
    return;
  }

  const reader = new FileReader();
  reader.onload = () => {
    try {
      const payload = JSON.parse(String(reader.result || "{}"));
      validatePluginPack(payload);
      const imported = payload.components.map((component, index) => ({
        ...component,
        id: component.id || `plugin-${Date.now()}-${index}`,
      }));
      state.components = deduplicateComponents([...state.components, ...imported]);
      renderAll();
      ui.pluginInput.value = "";
      alert(`Plugin-Pack "${payload.name}" importiert.`);
    } catch (error) {
      alert(`Plugin-Import fehlgeschlagen: ${error.message}`);
    }
  };
  reader.readAsText(file);
}

function validatePluginPack(payload) {
  if (!payload || typeof payload !== "object") {
    throw new Error("Ungueltiges JSON.");
  }
  ["manifestVersion", "name", "version", "components"].forEach((key) => {
    if (!(key in payload)) {
      throw new Error(`Pflichtfeld fehlt: ${key}`);
    }
  });
  if (!Array.isArray(payload.components) || payload.components.length === 0) {
    throw new Error("Plugin enthaelt keine Komponenten.");
  }
  payload.components.forEach((component) => {
    ["name", "partNumber", "rackStandard", "heightU", "depthMm", "priceNet", "currency"].forEach((key) => {
      if (!(key in component)) {
        throw new Error(`Komponente unvollstaendig: ${component.name || "unbekannt"} (${key} fehlt)`);
      }
    });
  });
}

async function copyBomCsv() {
  const rows = [["quantity", "manufacturer", "partNumber", "name", "unitPrice", "currency", "totalPrice"]];
  const aggregated = new Map();

  state.placedItems.forEach((item) => {
    const component = getComponent(item.componentId);
    if (!component) {
      return;
    }
    const key = component.partNumber;
    if (!aggregated.has(key)) {
      aggregated.set(key, { quantity: 0, component });
    }
    aggregated.get(key).quantity += 1;
  });

  aggregated.forEach(({ quantity, component }) => {
    rows.push([
      String(quantity),
      component.manufacturer,
      component.partNumber,
      component.name,
      String(component.priceNet),
      component.currency,
      String(quantity * component.priceNet),
    ]);
  });

  const csv = rows.map((row) => row.map(csvEscape).join(",")).join("\n");

  try {
    await navigator.clipboard.writeText(csv);
    ui.exportBom.textContent = "CSV kopiert";
    window.setTimeout(() => {
      ui.exportBom.textContent = "CSV kopieren";
    }, 1600);
  } catch (error) {
    console.error(error);
    alert("CSV konnte nicht in die Zwischenablage kopiert werden.");
  }
}

function getCurrentRackTemplate() {
  return state.bootstrap?.rackTemplates.find((template) => template.id === state.rackTemplateId) ?? null;
}

function getComponent(componentId) {
  return state.components.find((component) => component.id === componentId) ?? null;
}

function deduplicateComponents(components) {
  const byId = new Map();
  components.forEach((component) => {
    byId.set(component.id, component);
  });
  return Array.from(byId.values());
}

function estimateSideAllowance(fromComponent, toComponent) {
  const fromSide = fromComponent.ports?.[0]?.side ?? "front";
  const toSide = toComponent.ports?.[0]?.side ?? "front";
  return fromSide === toSide ? 160 : 420;
}

function recommendCableLength(lengthMm) {
  const options = [0.25, 0.5, 1, 1.5, 2, 3, 5, 7.5, 10];
  const meters = lengthMm / 1000;
  return options.find((option) => option >= meters) ?? Math.ceil(meters);
}

function beginPointerDrag(event, item, component) {
  if (event.target.closest("button")) {
    return;
  }

  const rect = event.currentTarget.getBoundingClientRect();
  state.pointerDrag = {
    placementId: item.placementId,
    offsetY: event.clientY - rect.top,
    heightPx: getComponentHeightPx(component),
  };
  event.currentTarget.setPointerCapture?.(event.pointerId);
  event.currentTarget.classList.add("is-dragging");
}

function handlePointerMove(event) {
  if (!state.pointerDrag) {
    return;
  }

  const layer = document.getElementById("rack-items-layer");
  const rack = getCurrentRackTemplate();
  if (!layer || !rack) {
    return;
  }

  const item = state.placedItems.find((entry) => entry.placementId === state.pointerDrag.placementId);
  if (!item) {
    return;
  }

  const layerRect = layer.getBoundingClientRect();
  const yPx = clamp(event.clientY - layerRect.top - state.pointerDrag.offsetY, 0, getRackHeightPx(rack) - state.pointerDrag.heightPx);

  item.x = 0;
  item.y = snapToUnit(yPx);
  renderAll();
}

function handlePointerUp() {
  if (!state.pointerDrag) {
    return;
  }

  state.pointerDrag = null;
  ui.rackGrid.querySelectorAll(".rack-item.is-dragging").forEach((element) => {
    element.classList.remove("is-dragging");
  });
}

function formatCurrency(value, currency) {
  return new Intl.NumberFormat("de-DE", {
    style: "currency",
    currency: currency || "EUR",
    maximumFractionDigits: 0,
  }).format(value);
}

function renderComponentFace(component, mode) {
  const ports = renderPortStrip(component);
  const leds = '<div class="device-leds"><span></span><span></span><span></span></div>';
  const label = `<div class="device-silkscreen">${escapeHtml(component.manufacturer || component.category)}</div>`;
  const modeClass = mode === "rack" ? "device-face--rack" : "device-face--library";
  const header = `<div class="device-brand">${escapeHtml(component.name)}</div>`;

  switch (component.category) {
    case "switch":
      return `
        <div class="device-face device-face--switch ${modeClass}">
          <div class="device-mount-hole device-mount-hole--left"></div>
          <div class="device-mount-hole device-mount-hole--right"></div>
          ${header}
          ${label}
          ${leds}
          ${ports}
          <div class="device-uplink"></div>
        </div>
      `;
    case "patch_panel":
      return `
        <div class="device-face device-face--patch-panel ${modeClass}">
          <div class="device-mount-hole device-mount-hole--left"></div>
          <div class="device-mount-hole device-mount-hole--right"></div>
          ${header}
          ${label}
          ${renderPatchPanel(component)}
        </div>
      `;
    case "pdu":
      return `
        <div class="device-face device-face--pdu ${modeClass}">
          <div class="device-mount-hole device-mount-hole--left"></div>
          <div class="device-mount-hole device-mount-hole--right"></div>
          ${header}
          ${label}
          <div class="device-sockets">${renderSocketStrip(component)}</div>
          ${leds}
        </div>
      `;
    case "ups":
      return `
        <div class="device-face device-face--ups ${modeClass}">
          <div class="device-mount-hole device-mount-hole--left"></div>
          <div class="device-mount-hole device-mount-hole--right"></div>
          ${header}
          <div class="device-display"></div>
          <div class="device-vents"></div>
          <div class="device-handles"><span></span><span></span></div>
          ${label}
        </div>
      `;
    case "shelf":
      return `
        <div class="device-face device-face--shelf ${modeClass}">
          <div class="device-mount-hole device-mount-hole--left"></div>
          <div class="device-mount-hole device-mount-hole--right"></div>
          ${header}
          <div class="device-shelf-top"></div>
          ${label}
        </div>
      `;
    case "blank_panel":
      return `
        <div class="device-face device-face--blank ${modeClass}">
          <div class="device-mount-hole device-mount-hole--left"></div>
          <div class="device-mount-hole device-mount-hole--right"></div>
          ${header}
          ${label}
        </div>
      `;
    default:
      return `
        <div class="device-face device-face--generic ${modeClass}">
          <div class="device-mount-hole device-mount-hole--left"></div>
          <div class="device-mount-hole device-mount-hole--right"></div>
          ${header}
          ${label}
        </div>
      `;
  }
}

function renderPortStrip(component) {
  const totalPorts = component.name.includes("24") ? 24 : component.name.includes("12") ? 12 : 8;
  const ports = Array.from({ length: totalPorts }, (_, index) => `<span title="Port ${index + 1}"></span>`).join("");
  return `<div class="device-ports device-ports--${Math.min(totalPorts, 24)}">${ports}</div>`;
}

function renderPatchPanel(component) {
  const totalPorts = component.name.includes("24") ? 24 : 12;
  const ports = Array.from({ length: totalPorts }, (_, index) => `<span title="Patch ${index + 1}"></span>`).join("");
  return `<div class="device-keystones">${ports}</div>`;
}

function renderSocketStrip(component) {
  const totalSockets = component.name.includes("8") ? 8 : 6;
  return Array.from({ length: totalSockets }, () => "<span></span>").join("");
}

function renderInsertionZones(rack) {
  if (!state.libraryDragComponentId) {
    return "";
  }

  const component = getComponent(state.libraryDragComponentId);
  if (!component) {
    return "";
  }

  const unitHeight = getUnitHeightPx();
  const zones = [];
  for (let y = 0; y <= getRackHeightPx(rack) - getComponentHeightPx(component); y += unitHeight) {
    const occupied = state.placedItems.some((item) => {
      const other = getComponent(item.componentId);
      if (!other) {
        return false;
      }
      return rectanglesOverlap(
        {
          left: 0,
          top: y,
          width: 100,
          height: getComponentHeightPx(component),
        },
        {
          left: 0,
          top: item.y,
          width: 100,
          height: getComponentHeightPx(other),
        }
      );
    });
    const isPreview = state.rackDropPreviewY !== null && Math.abs(state.rackDropPreviewY - y) < 1;

    zones.push(`
      <button
        type="button"
        class="rack-insert-zone${occupied ? " is-disabled" : ""}${isPreview ? " is-preview" : ""}"
        data-action="insert-component"
        data-insert-y="${y}"
        style="top:${y}px;height:${getComponentHeightPx(component)}px;left:0;width:100%;"
        ${occupied ? "disabled" : ""}
      >
        <span class="rack-insert-zone__ear rack-insert-zone__ear--left"></span>
        <span class="rack-insert-zone__body">
          <span class="rack-insert-zone__icon">☞</span>
          <span>Element hier einstecken</span>
        </span>
        <span class="rack-insert-zone__ear rack-insert-zone__ear--right"></span>
      </button>
    `);
  }

  return zones.join("");
}

function getRackHeightPx(rack) {
  const layer = document.getElementById("rack-items-layer");
  if (layer) {
    return layer.clientHeight || rack.totalU * RACK_UNIT_PX;
  }
  return rack.totalU * RACK_UNIT_PX;
}

function getComponentHeightPx(component) {
  return component.heightU * getUnitHeightPx();
}

function getComponentWidthPercent(component) {
  return 100;
}

function getDefaultComponentX(component) {
  return 0;
}

function getPlacementRect(item, component) {
  const rackInnerWidthPx = getRackInnerWidthPx();
  const width = (getComponentWidthPercent(component) / 100) * rackInnerWidthPx;
  const left = (item.x / 100) * rackInnerWidthPx;
  return {
    left,
    top: item.y,
    width,
    height: getComponentHeightPx(component),
  };
}

function getPlacementCenterY(item, component) {
  return item.y + getComponentHeightPx(component) / 2;
}

function formatRackPosition(y, component) {
  const rack = getCurrentRackTemplate();
  if (!rack) {
    return "1U";
  }
  const rowIndex = Math.round(y / getUnitHeightPx());
  const componentHeightU = component?.heightU ?? 1;
  const u = rack.totalU - rowIndex - componentHeightU + 1;
  return `${Math.max(1, u)}U`;
}

function getPlacementOverlaps() {
  const overlaps = [];
  for (let index = 0; index < state.placedItems.length; index += 1) {
    for (let compareIndex = index + 1; compareIndex < state.placedItems.length; compareIndex += 1) {
      const aItem = state.placedItems[index];
      const bItem = state.placedItems[compareIndex];
      const aComponent = getComponent(aItem.componentId);
      const bComponent = getComponent(bItem.componentId);
      if (!aComponent || !bComponent) {
        continue;
      }
      if (rectanglesOverlap(getPlacementRect(aItem, aComponent), getPlacementRect(bItem, bComponent))) {
        overlaps.push({ a: aComponent.name, b: bComponent.name });
      }
    }
  }
  return overlaps;
}

function rectanglesOverlap(a, b) {
  return a.left < b.left + b.width && a.left + a.width > b.left && a.top < b.top + b.height && a.top + a.height > b.top;
}

function getRackInnerWidthPx() {
  const layer = document.getElementById("rack-items-layer");
  if (layer) {
    return layer.clientWidth || 620;
  }
  return 620;
}

function getUnitHeightPx() {
  const rack = getCurrentRackTemplate();
  const layer = document.getElementById("rack-items-layer");
  if (rack && layer && layer.clientHeight) {
    return layer.clientHeight / rack.totalU;
  }
  return RACK_UNIT_PX;
}

function clamp(value, min, max) {
  return Math.max(min, Math.min(max, value));
}

function snapToUnit(value) {
  const unitHeight = getUnitHeightPx();
  return Math.round(value / unitHeight) * unitHeight;
}

function beginLibraryDrag(event, componentId) {
  state.libraryDragComponentId = componentId;
  state.selectedComponentId = componentId;
  if (event.dataTransfer) {
    event.dataTransfer.effectAllowed = "copy";
    event.dataTransfer.setData("text/plain", componentId);
  }
  window.requestAnimationFrame(() => {
    renderAll();
  });
}

function handleRackDragOver(event) {
  if (!state.libraryDragComponentId) {
    return;
  }
  event.preventDefault();
  const rack = getCurrentRackTemplate();
  const component = getComponent(state.libraryDragComponentId);
  const layer = document.getElementById("rack-items-layer");
  if (!rack || !component || !layer) {
    return;
  }
  const rect = layer.getBoundingClientRect();
  const y = clamp(event.clientY - rect.top - getComponentHeightPx(component) / 2, 0, getRackHeightPx(rack) - getComponentHeightPx(component));
  const snappedY = snapToUnit(y);
  if (state.rackDropPreviewY !== snappedY) {
    state.rackDropPreviewY = snappedY;
    const insertionLayer = document.getElementById("rack-insertion-layer");
    if (insertionLayer) {
      insertionLayer.innerHTML = renderInsertionZones(rack);
      ui.rackGrid.querySelectorAll("[data-action='insert-component']").forEach((element) => {
        element.addEventListener("click", () => insertSelectedComponentAt(Number(element.dataset.insertY)));
      });
    }
  }
}

function handleRackDrop(event) {
  if (!state.libraryDragComponentId) {
    return;
  }
  event.preventDefault();
  if (state.rackDropPreviewY !== null) {
    insertSelectedComponentAt(state.rackDropPreviewY);
  }
}

function handleRackDragLeave(event) {
  if (!state.libraryDragComponentId) {
    return;
  }
  if (event.currentTarget.contains(event.relatedTarget)) {
    return;
  }
  state.rackDropPreviewY = null;
  const rack = getCurrentRackTemplate();
  const insertionLayer = document.getElementById("rack-insertion-layer");
  if (rack && insertionLayer) {
    insertionLayer.innerHTML = renderInsertionZones(rack);
    ui.rackGrid.querySelectorAll("[data-action='insert-component']").forEach((element) => {
      element.addEventListener("click", () => insertSelectedComponentAt(Number(element.dataset.insertY)));
    });
  }
}

function handleLibraryDragEnd() {
  if (!state.libraryDragComponentId && state.rackDropPreviewY === null) {
    return;
  }
  state.libraryDragComponentId = null;
  state.rackDropPreviewY = null;
  renderAll();
}

function applyRackColorTheme(element, color) {
  const rgb = hexToRgb(color);
  if (!rgb) {
    return;
  }

  element.style.setProperty("--rack-frame", color);
  element.style.setProperty("--rack-frame-dark", shadeRgb(rgb, -34));
  element.style.setProperty("--rack-frame-light", shadeRgb(rgb, 28));
  element.style.setProperty("--rack-rail", shadeRgb(rgb, 18));
}

function hexToRgb(hex) {
  const normalized = String(hex).replace("#", "");
  if (!/^[a-f0-9]{6}$/i.test(normalized)) {
    return null;
  }

  return {
    r: Number.parseInt(normalized.slice(0, 2), 16),
    g: Number.parseInt(normalized.slice(2, 4), 16),
    b: Number.parseInt(normalized.slice(4, 6), 16),
  };
}

function shadeRgb(rgb, amount) {
  const channel = (value) => Math.max(0, Math.min(255, value + amount));
  return `rgb(${channel(rgb.r)}, ${channel(rgb.g)}, ${channel(rgb.b)})`;
}

function escapeHtml(value) {
  return String(value)
    .replaceAll("&", "&amp;")
    .replaceAll("<", "&lt;")
    .replaceAll(">", "&gt;")
    .replaceAll('"', "&quot;")
    .replaceAll("'", "&#39;");
}

function csvEscape(value) {
  const stringValue = String(value ?? "");
  if (stringValue.includes(",") || stringValue.includes('"') || stringValue.includes("\n")) {
    return `"${stringValue.replaceAll('"', '""')}"`;
  }
  return stringValue;
}
