  const toLocalISO = (d) => {
    const year = d.getFullYear();
    const month = String(d.getMonth() + 1).padStart(2, '0');
    const day = String(d.getDate()).padStart(2, '0');
    return `${year}-${month}-${day}`;
  };

  const holidayCache = new Map();

  function calculateEasterSunday(year) {
    const a = year % 19;
    const b = Math.floor(year / 100);
    const c = year % 100;
    const d = Math.floor(b / 4);
    const e = b % 4;
    const f = Math.floor((b + 8) / 25);
    const g = Math.floor((b - f + 1) / 3);
    const h = (19 * a + b - d - g + 15) % 30;
    const i = Math.floor(c / 4);
    const k = c % 4;
    const l = (32 + 2 * e + 2 * i - h - k) % 7;
    const m = Math.floor((a + 11 * h + 22 * l) / 451);
    const month = Math.floor((h + l - 7 * m + 114) / 31);
    const day = ((h + l - 7 * m + 114) % 31) + 1;
    return new Date(year, month - 1, day);
  }

  function getGermanHolidays(year) {
    if (holidayCache.has(year)) return holidayCache.get(year);

    const holidays = new Map();
    const add = (month, day, name) => {
      holidays.set(toLocalISO(new Date(year, month - 1, day)), name);
    };

    add(1, 1, 'Neujahr');
    add(5, 1, 'Tag der Arbeit');
    add(10, 3, 'Tag der Deutschen Einheit');
    add(12, 25, '1. Weihnachtstag');
    add(12, 26, '2. Weihnachtstag');

    const easterSunday = calculateEasterSunday(year);
    const addOffset = (offset, name) => {
      const d = new Date(easterSunday);
      d.setDate(easterSunday.getDate() + offset);
      holidays.set(toLocalISO(d), name);
    };

    addOffset(-2, 'Karfreitag');
    addOffset(0, 'Ostersonntag');
    addOffset(1, 'Ostermontag');
    addOffset(39, 'Christi Himmelfahrt');
    addOffset(49, 'Pfingstsonntag');
    addOffset(50, 'Pfingstmontag');

    holidayCache.set(year, holidays);
    return holidays;
  }

  function getHolidayName(date) {
    const holidays = getGermanHolidays(date.getFullYear());
    return holidays.get(toLocalISO(date)) || '';
  }

  function isDesktopView() {
    return window.matchMedia && window.matchMedia('(min-width: 992px)').matches;
  }

  function getCalendarRange() {
    const weeks = window._calendarIsAdmin ? 12 : 4;
    const start = new Date();
    start.setHours(0, 0, 0, 0);

    if (isDesktopView()) {
      // shift back to Monday (0 = Sunday)
      const diffToMonday = (start.getDay() + 6) % 7;
      start.setDate(start.getDate() - diffToMonday);
    }

    const end = new Date(start);
    end.setDate(end.getDate() + (weeks * 7) - 1);
    return { start, end };
  }

  function renderWeekdayHeader(startDate) {
    const header = document.querySelector('.calendar-weekday-header');
    if (!header) return;
    header.innerHTML = '';
    const formatDayShort = new Intl.DateTimeFormat('de-DE', { weekday: 'short' });
    for (let i = 0; i < 7; i++) {
      const d = new Date(startDate);
      d.setDate(startDate.getDate() + i);
      const div = document.createElement('div');
      div.textContent = formatDayShort.format(d);
      header.appendChild(div);
    }
  }

  function buildCalendarStructure(force = false) {
    const daysContainer = document.getElementById('calendar-days');
    if (!daysContainer || (daysContainer.childElementCount > 0 && !force)) return;

    const isAdmin = typeof admin_enabled !== 'undefined' && admin_enabled;
    const calendarColSpan = 3;
    window._calendarColSpan = calendarColSpan;
    window._calendarIsAdmin = isAdmin;
    daysContainer.innerHTML = '';

    const formatDayName = new Intl.DateTimeFormat('de-DE', { weekday: 'long' });
    const formatDate = new Intl.DateTimeFormat('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
    const { start, end } = getCalendarRange();
    const todayIso = toLocalISO(new Date());
    renderWeekdayHeader(start);
    const dayCount = Math.round((end - start) / (1000 * 60 * 60 * 24)) + 1;

    for (let i = 0; i < dayCount; i++) {
      const date = new Date(start);
      date.setDate(start.getDate() + i);
      const isoDate = toLocalISO(date);
      const isSunday = date.getDay() === 0;
      const holidayName = getHolidayName(date);
      const holidayBadge = holidayName ? `<span class="calendar-holiday-badge">${holidayName}</span>` : '';

      const dayBlock = document.createElement('div');
      dayBlock.className = 'calendar-day empty';
      dayBlock.dataset.date = isoDate;
      if (isoDate === toLocalISO(new Date())) {
        dayBlock.classList.add('calendar-day-today');
      }
      if (isSunday) {
        dayBlock.classList.add('calendar-day-sunday');
      }
      if (holidayName) {
        dayBlock.classList.add('calendar-day-holiday');
      }
      if (isDesktopView() && isoDate < todayIso) {
        dayBlock.classList.add('calendar-day-hidden');
      }

      dayBlock.innerHTML = `
        <div class="calendar-day-header">
          <div class="calendar-day-name">${formatDayName.format(date)}</div>
          <div class="calendar-day-date">${formatDate.format(date)}${holidayBadge}</div>
        </div>
        <div class="table-responsive calendar-table-wrapper">
          <table class="table table-sm mb-0">
            <thead>
              <tr>
                <th scope="col">Zeit</th>
                <th scope="col">Titel</th>
                <th scope="col">Ort</th>
              </tr>
            </thead>
            <tbody class="calendar-entries-list">
              <tr class="calendar-empty-row">
                <td colspan="${calendarColSpan}" class="text-center text-muted">Keine Einträge</td>
              </tr>
            </tbody>
          </table>
        </div>
      `;

      daysContainer.appendChild(dayBlock);
    }
  }

  function resetDayBlock(block) {
    const tbody = block.querySelector('.calendar-entries-list');
    if (!tbody) return;
    block.classList.add('empty');
    tbody.innerHTML = `
      <tr class="calendar-empty-row">
        <td colspan="${window._calendarColSpan || 3}" class="text-center text-muted">Keine Einträge</td>
      </tr>
    `;
  }

  function clearCalendarEntries() {
    document.querySelectorAll('.calendar-day').forEach(resetDayBlock);
  }

  function escapeHtml(str) {
    return (str || '').replace(/[&<>"']/g, (c) => ({
      '&': '&amp;',
      '<': '&lt;',
      '>': '&gt;',
      '"': '&quot;',
      "'": '&#39;'
    }[c]));
  }

  function createEmptyRow() {
    const row = document.createElement('tr');
    row.className = 'calendar-empty-row';
    row.innerHTML = `<td colspan="${window._calendarColSpan || 3}" class="text-center text-muted">Keine Einträge</td>`;
    return row;
  }

  function formatDisplayTime(timeStr) {
    if (!timeStr) return '';
    const match = String(timeStr).match(/^(\d{1,2}):(\d{2})(?::\d{2})?$/);
    if (match) {
      const h = match[1].padStart(2, '0');
      const m = match[2];
      return `${h}:${m}`;
    }
    return timeStr;
  }

  // Location filter helpers
  let locationOptions = new Set();

  function resetLocationFilterOptions() {
    locationOptions = new Set();
    const select = document.getElementById('calendar-location-filter');
    if (!select) return;
    select.innerHTML = '<option value="">Ort (alle)</option>';
  }

  function addLocationOption(location) {
    if (!location) return;
    const normalized = location.trim();
    if (!normalized || locationOptions.has(normalized)) return;
    locationOptions.add(normalized);
    const select = document.getElementById('calendar-location-filter');
    if (!select) return;
    const opt = document.createElement('option');
    opt.value = normalized;
    opt.textContent = normalized;
    select.appendChild(opt);
  }

  function applyLocationFilter() {
    const select = document.getElementById('calendar-location-filter');
    const filterValue = select ? select.value : '';

    document.querySelectorAll('.calendar-day').forEach(day => {
      const tbody = day.querySelector('.calendar-entries-list');
      if (!tbody) return;

      // Remove any stale empty rows before recalculating
      tbody.querySelectorAll('.calendar-empty-row').forEach(r => r.remove());

      let visibleCount = 0;
      const entryRows = tbody.querySelectorAll('tr.calendar-entry-row');
      entryRows.forEach(row => {
        const rowLoc = (row.dataset.location || '').trim();
        const match = !filterValue || rowLoc === filterValue;
        row.style.display = match ? '' : 'none';

        const detailsRow = row.nextElementSibling && row.nextElementSibling.classList.contains('calendar-details-row')
          ? row.nextElementSibling
          : null;
        if (detailsRow) {
          const showDetails = match && detailsRow.dataset.expanded === 'true';
          detailsRow.style.display = showDetails ? '' : 'none';
        }

        if (match) visibleCount += 1;
      });

      if (visibleCount === 0) {
        day.classList.add('empty');
        tbody.appendChild(createEmptyRow());
      } else {
        day.classList.remove('empty');
      }
    });
  }

  function removeCalendarEntryFromUI(id) {
    if (!id) return;
    const selector = `.calendar-entry-row[data-id="${id}"], .calendar-details-row[data-parent-id="${id}"]`;
    const rows = document.querySelectorAll(selector);
    rows.forEach(row => {
      const tbody = row.parentElement;
      const dayBlock = row.closest('.calendar-day');
      row.remove();
      const remainingEntries = tbody ? tbody.querySelectorAll('tr.calendar-entry-row').length : 0;
      if (remainingEntries === 0 && dayBlock) {
        resetDayBlock(dayBlock);
      }
    });
  }

  // Helper to insert entries sorted by time inside an existing day block
  function addCalendarEntry(entry) {
    if (!entry || !entry.date) return;
    if (entry.id) {
      const existing = document.querySelector(`.calendar-entry-row[data-id="${entry.id}"]`);
      if (existing) return;
    }
    const block = document.querySelector(`.calendar-day[data-date="${entry.date}"]`);
    if (!block) return;

    const tbody = block.querySelector('.calendar-entries-list');
    if (!tbody) return;

    const emptyRow = tbody.querySelector('.calendar-empty-row');
    if (emptyRow) emptyRow.remove();
    block.classList.remove('empty');

    const row = document.createElement('tr');
    const timeValue = formatDisplayTime(entry.time || '');
    row.dataset.time = timeValue;
    row.dataset.id = entry.id || '';
    row.dataset.date = entry.date;
    row.dataset.title = entry.title || '';
    row.dataset.location = entry.location || '';
    row.dataset.details = entry.details || '';
    row.className = 'calendar-entry-row';
    row.innerHTML = `
      <td>${timeValue || '-'}</td>
      <td>${entry.title || ''} ${entry.details ? '<span class="details-indicator" title="Details vorhanden">ⓘ</span>' : ''}</td>
      <td>${entry.location || ''}</td>
    `;

    const rows = Array.from(tbody.querySelectorAll('tr.calendar-entry-row'));
    const insertIndex = rows.findIndex(r => {
      const existingTime = r.dataset.time || '';
      return timeValue && (!existingTime || timeValue < existingTime);
    });

    if (insertIndex === -1) {
      tbody.appendChild(row);
    } else {
      tbody.insertBefore(row, rows[insertIndex]);
    }
    const detailsRow = document.createElement('tr');
    detailsRow.className = 'calendar-details-row';
    detailsRow.dataset.parentId = entry.id || '';
    detailsRow.dataset.expanded = 'false';
    const actionsHtml = window._calendarIsAdmin ? `
      <div class="calendar-inline-actions">
        <button type="button" class="btn btn-sm btn-outline-secondary calendar-edit" title="Bearbeiten">✎</button>
        <button type="button" class="btn btn-sm btn-outline-danger calendar-delete" title="Löschen">🗑</button>
      </div>
    ` : '';
    const detailsText = entry.details ? escapeHtml(entry.details) : '';
    detailsRow.innerHTML = `<td colspan="${window._calendarColSpan || 3}"><div class="calendar-details-text">${detailsText}</div>${actionsHtml}</td>`;
    detailsRow.style.display = 'none';
    row.insertAdjacentElement('afterend', detailsRow);
    applyLocationFilter();
  }

  async function loadCalendarEntries() {
    const fetchToken = (window._calendarFetchToken = (window._calendarFetchToken || 0) + 1);
    const { start, end } = getCalendarRange();
    const todayIso = toLocalISO(start);
    const endIso = toLocalISO(end);

    clearCalendarEntries();
    resetLocationFilterOptions();

    try {
      const response = await fetch(`/api/calendar?start=${todayIso}&end=${endIso}`);
      if (!response.ok) throw new Error('Fehler beim Laden');
      const entries = await response.json();
      if (fetchToken !== window._calendarFetchToken) return;
      entries.forEach(entry => {
        addLocationOption(entry.location);
        addCalendarEntry(entry);
      });
      applyLocationFilter();
    } catch (err) {
      console.error('Kalender laden fehlgeschlagen', err);
    }
  }

  async function saveCalendarEntry(entry) {
    const isUpdate = Boolean(entry.id);
    const url = isUpdate ? `/api/calendar/${entry.id}` : '/api/calendar';
    const method = isUpdate ? 'PUT' : 'POST';
    const response = await fetch(url, {
      method,
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(entry)
    });
    if (!response.ok) {
      const msg = await response.text();
      throw new Error(msg || 'Speichern fehlgeschlagen');
    }
    return response.json();
  }

  async function createRecurringEntries(entry, repeatCount) {
    const count = Math.max(1, Math.min(52, repeatCount || 1));
    const baseDate = entry.date;
    if (!baseDate) return [];
    const base = new Date(baseDate);
    const savedEntries = [];
    for (let i = 0; i < count; i++) {
      const next = new Date(base);
      next.setDate(base.getDate() + i * 7);
      const entryForWeek = { ...entry, date: toLocalISO(next) };
      const saved = await saveCalendarEntry(entryForWeek);
      savedEntries.push(saved);
    }
    return savedEntries;
  }

  async function deleteCalendarEntry(id) {
    const response = await fetch(`/api/calendar/${id}`, { method: 'DELETE' });
    if (!response.ok) {
      const msg = await response.text();
      throw new Error(msg || 'Löschen fehlgeschlagen');
    }
    return true;
  }

  // Expose for future dynamic usage
  window.addCalendarEntry = addCalendarEntry;
  window.loadCalendarEntries = loadCalendarEntries;
  window.deleteCalendarEntry = deleteCalendarEntry;
  window.removeCalendarEntryFromUI = removeCalendarEntryFromUI;
  window.applyLocationFilter = applyLocationFilter;
  window.exportCalendar = async function exportCalendar() {
    try {
      const res = await fetch('/api/calendar/export');
      if (!res.ok) throw new Error('Export fehlgeschlagen');
      const blob = await res.blob();
      const url = window.URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = 'kalender.xlsx';
      document.body.appendChild(a);
      a.click();
      a.remove();
      window.URL.revokeObjectURL(url);
    } catch (err) {
      console.error(err);
      alert('Export fehlgeschlagen.');
    }
  };

  window.importCalendar = async function importCalendar(file) {
    const formData = new FormData();
    formData.append('file', file);
    const res = await fetch('/api/calendar/import', {
      method: 'POST',
      body: formData
    });
    if (!res.ok) {
      const msg = await res.text();
      throw new Error(msg || 'Import fehlgeschlagen');
    }
    return res.json();
  };

  function initCalendar() {
    const section = document.getElementById('calendar-section');
    if (!section || section.dataset.bound) return;
    section.dataset.bound = '1';
    const addBtn = document.getElementById('add-calendar-entry-btn');
    const modalEl = document.getElementById('calendarModal');
    const saveBtn = document.getElementById('saveCalendarEntryBtn');
    const form = document.getElementById('calendarForm');
    const daysContainer = document.getElementById('calendar-days');
    const locationFilter = document.getElementById('calendar-location-filter');
    const repeatCountInput = document.getElementById('calendarRepeatCount');
    const recurrenceRow = document.getElementById('calendarRecurrenceRow');
    const calendarModal = modalEl && window.bootstrap ? new bootstrap.Modal(modalEl) : null;
    if (calendarModal) {
      window.calendarModal = calendarModal;
      if (!modalEl.dataset.focusGuard) {
        modalEl.addEventListener('hide.bs.modal', () => {
          const active = document.activeElement;
          if (active && modalEl.contains(active)) {
            active.blur();
            requestAnimationFrame(() => {
              if (document.body) document.body.focus({ preventScroll: true });
            });
          }
        });
        modalEl.dataset.focusGuard = '1';
      }
    }
    let calendarHasLoaded = false;

    buildCalendarStructure();

    let lastViewportIsDesktop = isDesktopView();
    let resizeDebounce;
    const handleViewportChange = () => {
      const nowDesktop = isDesktopView();
      if (nowDesktop === lastViewportIsDesktop) return;
      lastViewportIsDesktop = nowDesktop;
      buildCalendarStructure(true);
      if (calendarHasLoaded) {
        loadCalendarEntries();
      }
    };
    window.addEventListener('resize', () => {
      if (resizeDebounce) clearTimeout(resizeDebounce);
      resizeDebounce = setTimeout(handleViewportChange, 150);
    });

    const setRecurrenceMode = (isEditing) => {
      if (!recurrenceRow) return;
      recurrenceRow.style.display = isEditing ? 'none' : '';
      if (!isEditing && repeatCountInput) {
        repeatCountInput.value = '1';
      }
    };

    if (addBtn && calendarModal) {
      addBtn.addEventListener('click', () => {
        if (form) form.reset();

        const today = new Date();
        const dateInput = document.getElementById('calendarDate');
        if (dateInput) dateInput.value = toLocalISO(today);

        const idInput = document.getElementById('calendarEntryId');
        if (idInput) idInput.value = '';

        setRecurrenceMode(false);
        calendarModal.show();
      });
    }

    if (saveBtn && calendarModal) {
      saveBtn.addEventListener('click', async () => {
        if (!form || !form.reportValidity()) return;

        const entry = {
          id: document.getElementById('calendarEntryId')?.value || '',
          date: document.getElementById('calendarDate')?.value,
          time: document.getElementById('calendarTime')?.value,
          title: document.getElementById('calendarTitle')?.value,
          location: document.getElementById('calendarLocation')?.value,
          details: document.getElementById('calendarDetails')?.value
        };
        const isEditing = Boolean(entry.id);
        const repeatCount = (!isEditing && window._calendarIsAdmin && repeatCountInput)
          ? Math.max(1, Math.min(52, parseInt(repeatCountInput.value, 10) || 1))
          : 1;

        try {
          if (isEditing) {
            removeCalendarEntryFromUI(entry.id);
            const saved = await saveCalendarEntry(entry);
            addLocationOption(saved.location);
            addCalendarEntry(saved);
          } else {
            const savedEntries = await createRecurringEntries(entry, repeatCount);
            savedEntries.forEach(saved => {
              addLocationOption(saved.location);
              addCalendarEntry(saved);
            });
          }
          calendarModal.hide();
        } catch (err) {
          console.error(err);
          alert('Speichern fehlgeschlagen.');
        }
      });
    }

    if (window._calendarIsAdmin) {
      const exportBtn = document.getElementById('export-calendar-btn');
      const importBtn = document.getElementById('import-calendar-btn');
      const importInput = document.getElementById('import-calendar-input');

      if (exportBtn) {
        exportBtn.addEventListener('click', (e) => {
          e.preventDefault();
          window.exportCalendar();
        });
      }

      if (importBtn && importInput) {
        importBtn.addEventListener('click', (e) => {
          e.preventDefault();
          importInput.value = '';
          importInput.click();
        });

        importInput.addEventListener('change', async () => {
          if (!importInput.files || importInput.files.length === 0) return;
          const file = importInput.files[0];
          try {
            await importCalendar(file);
            loadCalendarEntries();
          } catch (err) {
            console.error(err);
            alert('Import fehlgeschlagen.');
          }
        });
      }
    }

    if (daysContainer && typeof admin_enabled !== 'undefined' && admin_enabled) {
      daysContainer.addEventListener('click', async (e) => {
        const editBtn = e.target.closest('.calendar-edit');
        const deleteBtn = e.target.closest('.calendar-delete');
        if (!editBtn && !deleteBtn) return;

        const row = e.target.closest('tr');
        if (!row) return;

        let entryRow = row;
        if (row.classList.contains('calendar-details-row') && row.previousElementSibling && row.previousElementSibling.classList.contains('calendar-entry-row')) {
          entryRow = row.previousElementSibling;
        }

        const entry = {
          id: entryRow.dataset.id,
          date: entryRow.dataset.date,
          time: entryRow.dataset.time,
          title: entryRow.dataset.title,
          location: entryRow.dataset.location,
          details: entryRow.dataset.details
        };

        if (editBtn) {
          if (form) form.reset();
          document.getElementById('calendarEntryId').value = entry.id || '';
          document.getElementById('calendarDate').value = entry.date || '';
          document.getElementById('calendarTime').value = entry.time || '';
          document.getElementById('calendarTitle').value = entry.title || '';
          document.getElementById('calendarLocation').value = entry.location || '';
          document.getElementById('calendarDetails').value = entry.details || '';
          setRecurrenceMode(true);
          if (calendarModal) calendarModal.show();
          return;
        }

        if (deleteBtn) {
          if (!entry.id) return;
          const confirmDelete = window.confirm('Diesen Eintrag löschen?');
          if (!confirmDelete) return;
          try {
            await deleteCalendarEntry(entry.id);
            removeCalendarEntryFromUI(entry.id);
            applyLocationFilter();
          } catch (err) {
            console.error(err);
            alert('Löschen fehlgeschlagen.');
          }
        }
      });
    }

    const tabButtons = document.querySelectorAll('.tab-button');
    tabButtons.forEach(btn => {
      if (btn.getAttribute('data-tab') === 'calendar') {
        btn.addEventListener('click', () => {
          if (!calendarHasLoaded) {
            calendarHasLoaded = true;
            loadCalendarEntries();
          }
        });
      }
    });

    // Immediately load calendar entries for users landing directly on the calendar tab (non-admins may not switch tabs)
    if (!calendarHasLoaded) {
      calendarHasLoaded = true;
      loadCalendarEntries();
    }

    if (locationFilter) {
      locationFilter.addEventListener('change', () => applyLocationFilter());
    }

    // Show/hide details inline on row click (any user)
    if (daysContainer) {
      daysContainer.addEventListener('click', (e) => {
        if (e.target.closest('.calendar-edit') || e.target.closest('.calendar-delete')) return;
        const row = e.target.closest('tr.calendar-entry-row');
        if (!row) return;
        const detailsRow = row.nextElementSibling && row.nextElementSibling.classList.contains('calendar-details-row')
          ? row.nextElementSibling
          : null;
        if (!detailsRow) return;

        const isExpanded = detailsRow.dataset.expanded === 'true';
        const shouldShow = !isExpanded;
        detailsRow.dataset.expanded = shouldShow ? 'true' : 'false';

        // Respect current filter when toggling visibility
        const filterValue = (document.getElementById('calendar-location-filter')?.value || '').trim();
        const rowLoc = (row.dataset.location || '').trim();
        const match = !filterValue || rowLoc === filterValue;
        detailsRow.style.display = (shouldShow && match) ? '' : 'none';
      });
    }
}

window.initCalendar = initCalendar;
