Source: js/chart-scaffold.js

// chart-scaffold.js — Gemeinsame Scaffold-Funktionen für Charts
import { ensureTooltip } from 'chart-dom-utils';

/**
 * Erstellt die gemeinsame Chart-Card-Struktur
 * @param {HTMLElement} mount - Mount-Element
 * @param {Object} options - Konfiguration
 * @param {string} options.title - Chart-Titel
 * @param {string} options.legendHTML - HTML für Legende
 * @param {string} [options.cacheKey='__chartScaffold'] - Cache-Property-Name
 * @returns {Object} Scaffold-Objekte
 */
export function ensureChartScaffold(mount, { title, legendHTML, cacheKey = '__chartScaffold' }) {
  if (mount[cacheKey]) return mount[cacheKey];

  mount.classList.add('card', 'mb-0');
  mount.style.position = 'relative';

  // Header mit Titel und Vollbild-Button
  const header = document.createElement('div');
  header.className = 'card-header py-2 bg-body-secondary d-flex align-items-center';

  const hTitle = document.createElement('div');
  hTitle.className = 'fw-semibold';
  hTitle.textContent = title;

  const fsBtn = document.createElement('button');
  fsBtn.type = 'button';
  fsBtn.className = 'btn btn-outline-secondary btn-sm ms-auto';
  fsBtn.title = 'Vollbild';
  fsBtn.innerHTML = '<i class="bi-arrows-fullscreen" aria-hidden="true"></i>';
  header.append(hTitle, fsBtn);

  // Legende
  const legend = document.createElement('div');
  legend.className =
    'legend small text-body-secondary d-flex align-items-center gap-3 justify-content-center pt-2';
  legend.innerHTML = legendHTML;

  // Lücken-Legende hinzufügen falls nicht vorhanden
  if (!legend.querySelector('.legend-gaps')) {
    const legendGaps = document.createElement('span');
    legendGaps.className = 'legend-gaps d-inline-flex align-items-center gap-1 d-none';
    legendGaps.innerHTML = `
      <svg class="layer-gaps" width="12" height="8" viewBox="0 0 12 8" aria-hidden="true">
        <rect x="0" y="0" width="12" height="8"></rect>
      </svg> Lücken
    `;
    legendGaps.style.display = 'none';
    legend.appendChild(legendGaps);
  }

  // Body und Tooltip
  const body = document.createElement('div');
  body.className = 'chart-body p-0';
  const tooltip = ensureTooltip(mount);
  mount.replaceChildren(header, legend, body, tooltip);

  // Fullscreen-Logik
  let fsExitBtn = null;
  let originalSvg = null;
  let fullscreenSvg = null;

  const enterFs = () => mount.requestFullscreen?.() || mount.webkitRequestFullscreen?.();
  const exitFs = () => document.exitFullscreen?.() || document.webkitExitFullscreen?.();

  fsBtn.addEventListener('click', () => {
    const inFs = document.fullscreenElement === mount || document.webkitFullscreenElement === mount;
    inFs ? exitFs() : enterFs();
  });

  const onFsChange = () => {
    const inFs = document.fullscreenElement === mount || document.webkitFullscreenElement === mount;
    if (inFs) {
      // Fullscreen aktiviert
      originalSvg = body.querySelector('svg');
      if (originalSvg) {
        fullscreenSvg = originalSvg.cloneNode(true);
        fullscreenSvg.setAttribute('width', '100%');
        fullscreenSvg.setAttribute('height', '100%');
        fullscreenSvg.style.width = '100%';

        // Höhe = 100vh minus Höhe von Header + Legende
        const headerHeight = header.offsetHeight || 0;
        const legendHeight = legend.offsetHeight || 0;
        const totalOffset = headerHeight + legendHeight;
        fullscreenSvg.style.height = `calc(100vh - ${totalOffset}px)`;
        fullscreenSvg.style.display = 'block';
        body.replaceChildren(fullscreenSvg);
      }
      mount.classList.add('fs-active');
      body.style.minHeight = '100vh';

      if (!fsExitBtn) {
        fsExitBtn = document.createElement('button');
        fsExitBtn.type = 'button';
        fsExitBtn.className = 'btn btn-sm fs-exit-btn';
        fsExitBtn.innerHTML = '<i class="bi-arrows-angle-contract" aria-hidden="true"></i>';
        fsExitBtn.title = 'Vollbild schließen';
        fsExitBtn.addEventListener('click', exitFs);
      }
      mount.appendChild(fsExitBtn);
    } else {
      // Fullscreen deaktiviert
      mount.classList.remove('fs-active');
      body.style.minHeight = '';
      if (fsExitBtn && fsExitBtn.parentNode === mount) mount.removeChild(fsExitBtn);

      if (originalSvg) {
        body.replaceChildren(originalSvg);
        originalSvg = null;
      }
      fullscreenSvg = null;
    }
  };

  document.addEventListener('fullscreenchange', onFsChange);
  document.addEventListener('webkitfullscreenchange', onFsChange);

  // Cleanup function für Event-Listener
  mount.__cleanupFullscreenListeners = () => {
    document.removeEventListener('fullscreenchange', onFsChange);
    document.removeEventListener('webkitfullscreenchange', onFsChange);
  };

  const legendGaps = legend.querySelector('.legend-gaps');
  const scaffold = { header, hTitle, legend, legendGaps, body, tooltip, fsBtn };
  mount[cacheKey] = scaffold;
  return scaffold;
}