import React, { useState, useEffect, useCallback, useMemo } from 'react';
import {
  Alert,
  Box,
  Button,
  ButtonGroup,
  Checkbox,
  Chip,
  CircularProgress,
  Dialog,
  DialogActions,
  DialogContent,
  DialogContentText,
  DialogTitle,
  Divider,
  FormControl,
  FormControlLabel,
  FormGroup,
  IconButton,
  InputLabel,
  List,
  ListItem,
  ListItemText,
  MenuItem,
  Paper,
  Popover,
  Radio,
  RadioGroup,
  Select,
  Skeleton,
  Stack,
  Switch,
  Tab,
  Table,
  TableBody,
  TableCell,
  TableContainer,
  TableHead,
  TableRow,
  Tabs,
  TextField,
  Tooltip,
  Typography,
  useMediaQuery,
  useTheme,
} from '@mui/material';
import {
  Add,
  Cancel as CancelIcon,
  CalendarViewMonth as CalendarIcon,
  CheckCircle as CheckIcon,
  ChevronLeft,
  ChevronRight,
  ContentCopy as CopyIcon,
  DeleteForever as DeleteForeverIcon,
  Edit as EditIcon,
  FileDownload as FileDownloadIcon,
  FileUpload as FileUploadIcon,
  HelpOutline as UnknownIcon,
  IosShare,
  PictureAsPdf as PdfIcon,
  Settings as SettingsIcon,
  Star as StarIcon,
  Today as TodayIcon,
  ViewList as ListViewIcon,
  ViewDay as ViewDayIcon,
  ViewWeek as ViewWeekIcon,
} from '@mui/icons-material';
import { useNavigate, useSearchParams } from 'react-router-dom';
import DashboardLayout from '../components/dashboard/DashboardLayout';
import ServiceModePage from '../components/shared/ServiceModePage';
import ChatAwareFab from '../components/shared/ChatAwareFab';
import { toGermanDateTime, fromGermanDate, fromGermanDateTime } from '../utils/dateInput';

import { usePermissionContext } from '../contexts/PermissionContext';
import { useNotification } from '../contexts/NotificationContext';
import { trainingApi } from '../services/training';
import { eventsApi } from '../services/events';
import { configApi, type PdfSettings } from '../services/config';
import type {
  UebungListItem,
  UebungTyp,
  TeilnahmeStatus,
} from '../types/training.types';
import type {
  VeranstaltungListItem,
  VeranstaltungKategorie,
  GroupInfo,
  CreateVeranstaltungInput,
  WiederholungConfig,
} from '../types/events.types';
import {
  format as fnsFormat,
  startOfWeek,
  endOfWeek,
  startOfMonth,
  endOfMonth,
  addDays,
  subDays,
  addWeeks,
  subWeeks,
  eachDayOfInterval,
  parseISO,
} from 'date-fns';
import { de } from 'date-fns/locale';

// ──────────────────────────────────────────────────────────────────────────────
// Constants
// ──────────────────────────────────────────────────────────────────────────────

const WEEKDAY_LABELS = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'];
const MONTH_LABELS = [
  'Januar', 'Februar', 'März', 'April', 'Mai', 'Juni',
  'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember',
];

const TYP_DOT_COLOR: Record<UebungTyp, string> = {
  'Übungsabend':        '#1976d2',
  'Lehrgang':           '#7b1fa2',
  'Sonderdienst':       '#e65100',
  'Versammlung':        '#616161',
  'Gemeinschaftsübung': '#00796b',
  'Sonstiges':          '#9e9e9e',
};

const EMPTY_VERANSTALTUNG_FORM: CreateVeranstaltungInput = {
  titel: '',
  beschreibung: null,
  ort: null,
  ort_url: null,
  kategorie_id: null,
  datum_von: new Date().toISOString(),
  datum_bis: new Date().toISOString(),
  ganztaegig: false,
  zielgruppen: [],
  alle_gruppen: true,
  max_teilnehmer: null,
  anmeldung_erforderlich: false,
  anmeldung_bis: null,
};




// ──────────────────────────────────────────────────────────────────────────────
// Helpers
// ──────────────────────────────────────────────────────────────────────────────

function startOfDay(d: Date): Date {
  const c = new Date(d);
  c.setHours(0, 0, 0, 0);
  return c;
}

function sameDay(a: Date, b: Date): boolean {
  return (
    a.getFullYear() === b.getFullYear() &&
    a.getMonth() === b.getMonth() &&
    a.getDate() === b.getDate()
  );
}

function buildMonthGrid(year: number, month: number): Date[] {
  const firstDay = new Date(year, month, 1);
  const dayOfWeek = (firstDay.getDay() + 6) % 7;
  const start = new Date(firstDay);
  start.setDate(start.getDate() - dayOfWeek);
  const cells: Date[] = [];
  for (let i = 0; i < 42; i++) {
    const d = new Date(start);
    d.setDate(start.getDate() + i);
    cells.push(d);
  }
  return cells;
}

function formatTime(isoString: string): string {
  const d = new Date(isoString);
  return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
}

function formatDateLong(d: Date): string {
  const days = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'];
  return `${days[d.getDay()]}, ${d.getDate()}. ${MONTH_LABELS[d.getMonth()]} ${d.getFullYear()}`;
}

/** ISO string → YYYY-MM-DDTHH:MM (for type="datetime-local") */
function toDatetimeLocalValue(iso: string | null | undefined): string {
  if (!iso) return '';
  const d = new Date(iso);
  if (isNaN(d.getTime())) return '';
  const pad = (n: number) => String(n).padStart(2, '0');
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
}

/** ISO string → YYYY-MM-DD (for type="date") */
function toDateInputValue(iso: string | null | undefined): string {
  if (!iso) return '';
  const d = new Date(iso);
  if (isNaN(d.getTime())) return '';
  const pad = (n: number) => String(n).padStart(2, '0');
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
}

// ──────────────────────────────────────────────────────────────────────────────
// Types for unified calendar
// ──────────────────────────────────────────────────────────────────────────────

interface TrainingDayEvent {
  kind: 'training';
  id: string;
  color: string;
  titel: string;
  abgesagt: boolean;
  pflicht: boolean;
  datum_von: string;
}

interface VeranstaltungDayEvent {
  kind: 'event';
  id: string;
  color: string;
  titel: string;
  abgesagt: boolean;
  datum_von: string;
}

type CalDayEvent = TrainingDayEvent | VeranstaltungDayEvent;

// ──────────────────────────────────────────────────────────────────────────────
// RSVP dot
// ──────────────────────────────────────────────────────────────────────────────

function RsvpDot({ status }: { status: TeilnahmeStatus | undefined }) {
  if (!status || status === 'unbekannt')
    return <UnknownIcon sx={{ fontSize: 14, color: 'text.disabled' }} />;
  if (status === 'zugesagt' || status === 'erschienen')
    return <CheckIcon sx={{ fontSize: 14, color: 'success.main' }} />;
  return <CancelIcon sx={{ fontSize: 14, color: 'error.main' }} />;
}

// ──────────────────────────────────────────────────────────────────────────────
// Month Calendar (training + events)
// ──────────────────────────────────────────────────────────────────────────────

interface MonthCalendarProps {
  year: number;
  month: number;
  trainingEvents: UebungListItem[];
  veranstaltungen: VeranstaltungListItem[];
  selectedKategorie: string | 'all';
  onDayClick: (day: Date, anchor: Element) => void;
}

function MonthCalendar({
  year, month, trainingEvents, veranstaltungen, selectedKategorie, onDayClick,
}: MonthCalendarProps) {
  const theme = useTheme();
  const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
  const todayDate = startOfDay(new Date());
  const cells = useMemo(() => buildMonthGrid(year, month), [year, month]);

  const eventsByDay = useMemo(() => {
    const map = new Map<string, CalDayEvent[]>();

    for (const t of trainingEvents) {
      const key = startOfDay(new Date(t.datum_von)).toISOString().slice(0, 10);
      const arr = map.get(key) ?? [];
      arr.push({
        kind: 'training',
        id: t.id,
        color: TYP_DOT_COLOR[t.typ],
        titel: t.titel,
        abgesagt: t.abgesagt,
        pflicht: t.pflichtveranstaltung,
        datum_von: t.datum_von,
      });
      map.set(key, arr);
    }

    for (const ev of veranstaltungen) {
      if (selectedKategorie !== 'all' && ev.kategorie_id !== selectedKategorie) continue;
      const start = startOfDay(new Date(ev.datum_von));
      const end = startOfDay(new Date(ev.datum_bis));
      const cur = new Date(start);
      while (cur <= end) {
        const key = cur.toISOString().slice(0, 10);
        const arr = map.get(key) ?? [];
        arr.push({
          kind: 'event',
          id: ev.id,
          color: ev.kategorie_farbe ?? '#1976d2',
          titel: ev.titel,
          abgesagt: ev.abgesagt,
          datum_von: ev.datum_von,
        });
        map.set(key, arr);
        cur.setDate(cur.getDate() + 1);
      }
    }

    return map;
  }, [trainingEvents, veranstaltungen, selectedKategorie]);

  return (
    <Box>
      <Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', mb: 0.5 }}>
        {WEEKDAY_LABELS.map((wd) => (
          <Typography
            key={wd}
            variant="caption"
            sx={{
              textAlign: 'center',
              fontWeight: 600,
              color: wd === 'Sa' || wd === 'So' ? 'error.main' : 'text.secondary',
              py: 0.5,
            }}
          >
            {wd}
          </Typography>
        ))}
      </Box>

      <Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: '2px' }}>
        {cells.map((cell, idx) => {
          const isCurrentMonth = cell.getMonth() === month;
          const isTodayCell = sameDay(cell, todayDate);
          const key = cell.toISOString().slice(0, 10);
          const dayEvents = eventsByDay.get(key) ?? [];
          const hasEvents = dayEvents.length > 0;
          const maxDots = isMobile ? 3 : 5;

          return (
            <Box
              key={idx}
              onClick={(e) => hasEvents && onDayClick(cell, e.currentTarget)}
              sx={{
                minHeight: isMobile ? 44 : 72,
                borderRadius: 1,
                p: '4px',
                cursor: hasEvents ? 'pointer' : 'default',
                bgcolor: isTodayCell
                  ? 'primary.main'
                  : isCurrentMonth ? 'background.paper' : 'action.disabledBackground',
                border: '1px solid',
                borderColor: isTodayCell ? 'primary.dark' : 'divider',
                transition: 'background 0.1s',
                '&:hover': hasEvents
                  ? { bgcolor: isTodayCell ? 'primary.dark' : 'action.hover' }
                  : {},
                display: 'flex',
                flexDirection: 'column',
                alignItems: 'center',
                overflow: 'hidden',
              }}
            >
              <Typography
                variant="caption"
                sx={{
                  fontWeight: isTodayCell ? 700 : 400,
                  color: isTodayCell
                    ? 'primary.contrastText'
                    : isCurrentMonth ? 'text.primary' : 'text.disabled',
                  lineHeight: 1.4,
                  fontSize: isMobile ? '0.7rem' : '0.75rem',
                }}
              >
                {cell.getDate()}
              </Typography>

              {hasEvents && (
                <Box
                  sx={{
                    display: 'flex',
                    flexWrap: 'wrap',
                    gap: '2px',
                    justifyContent: 'center',
                    mt: 0.25,
                  }}
                >
                  {dayEvents.slice(0, maxDots).map((ev, i) => (
                    <Box
                      key={i}
                      sx={{
                        width: isMobile ? 5 : 7,
                        height: isMobile ? 5 : 7,
                        borderRadius: '50%',
                        bgcolor: ev.abgesagt ? 'text.disabled' : ev.color,
                        border:
                          ev.kind === 'training' && ev.pflicht
                            ? '1.5px solid'
                            : 'none',
                        borderColor: 'warning.main',
                        flexShrink: 0,
                        opacity: ev.abgesagt ? 0.5 : 1,
                      }}
                    />
                  ))}
                  {dayEvents.length > maxDots && (
                    <Typography
                      sx={{
                        fontSize: '0.55rem',
                        color: isTodayCell ? 'primary.contrastText' : 'text.secondary',
                        lineHeight: 1,
                      }}
                    >
                      +{dayEvents.length - maxDots}
                    </Typography>
                  )}
                </Box>
              )}

              {!isMobile && hasEvents && (
                <Box sx={{ width: '100%', mt: 0.25 }}>
                  {dayEvents.slice(0, 2).map((ev, i) => (
                    <Typography
                      key={i}
                      variant="caption"
                      noWrap
                      sx={{
                        display: 'block',
                        fontSize: '0.6rem',
                        lineHeight: 1.3,
                        color: ev.abgesagt ? 'text.disabled' : ev.color,
                        textDecoration: ev.abgesagt ? 'line-through' : 'none',
                        px: 0.25,
                      }}
                    >
                      {ev.kind === 'training' && ev.pflicht && '* '}
                      {ev.titel}
                    </Typography>
                  ))}
                </Box>
              )}
            </Box>
          );
        })}
      </Box>
    </Box>
  );
}

// ──────────────────────────────────────────────────────────────────────────────
// Day Popover (unified)
// ──────────────────────────────────────────────────────────────────────────────

interface DayPopoverProps {
  anchorEl: Element | null;
  day: Date | null;
  trainingForDay: UebungListItem[];
  eventsForDay: VeranstaltungListItem[];
  canWriteEvents: boolean;
  onClose: () => void;
  onTrainingClick: (id: string) => void;
  onEventEdit: (ev: VeranstaltungListItem) => void;
  onEventDelete: (id: string) => void;
}

function DayPopover({
  anchorEl, day, trainingForDay, eventsForDay,
  canWriteEvents, onClose, onTrainingClick, onEventEdit, onEventDelete,
}: DayPopoverProps) {
  if (!day) return null;
  const hasContent = trainingForDay.length > 0 || eventsForDay.length > 0;

  return (
    <Popover
      open={Boolean(anchorEl)}
      anchorEl={anchorEl}
      onClose={onClose}
      anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
      transformOrigin={{ vertical: 'top', horizontal: 'center' }}
      PaperProps={{ sx: { p: 1, maxWidth: 320, width: '90vw' } }}
    >
      <Typography variant="subtitle2" sx={{ mb: 1, px: 0.5, fontWeight: 600 }}>
        {formatDateLong(day)}
      </Typography>

      {!hasContent && (
        <Typography variant="body2" color="text.secondary" sx={{ px: 0.5 }}>
          Keine Einträge
        </Typography>
      )}

      {trainingForDay.length > 0 && (
        <>
          <Typography variant="overline" sx={{ px: 0.5, color: 'text.secondary', lineHeight: 1.5 }}>
            Dienste
          </Typography>
          <List dense disablePadding>
            {trainingForDay.map((t) => (
              <ListItem
                key={t.id}
                onClick={() => { onTrainingClick(t.id); onClose(); }}
                sx={{
                  cursor: 'pointer', borderRadius: 1, px: 0.75,
                  '&:hover': { bgcolor: 'action.hover' },
                  opacity: t.abgesagt ? 0.6 : 1,
                }}
              >
                <Box
                  sx={{
                    width: 8, height: 8, borderRadius: '50%',
                    bgcolor: TYP_DOT_COLOR[t.typ], mr: 1, flexShrink: 0,
                  }}
                />
                <ListItemText
                  primary={
                    <Typography
                      variant="body2"
                      sx={{
                        fontWeight: t.pflichtveranstaltung ? 700 : 400,
                        textDecoration: t.abgesagt ? 'line-through' : 'none',
                        display: 'flex', alignItems: 'center', gap: 0.5,
                      }}
                    >
                      {t.pflichtveranstaltung && (
                        <StarIcon sx={{ fontSize: 12, color: 'warning.main' }} />
                      )}
                      {t.titel}
                    </Typography>
                  }
                  secondary={`${formatTime(t.datum_von)} – ${formatTime(t.datum_bis)} Uhr`}
                />
                <RsvpDot status={t.eigener_status} />
              </ListItem>
            ))}
          </List>
        </>
      )}

      {trainingForDay.length > 0 && eventsForDay.length > 0 && (
        <Divider sx={{ my: 0.5 }} />
      )}

      {eventsForDay.length > 0 && (
        <>
          <Typography variant="overline" sx={{ px: 0.5, color: 'text.secondary', lineHeight: 1.5 }}>
            Veranstaltungen
          </Typography>
          <List dense disablePadding>
            {eventsForDay.map((ev) => (
              <ListItem
                key={ev.id}
                sx={{
                  borderRadius: 1, px: 0.75, opacity: ev.abgesagt ? 0.6 : 1,
                  alignItems: 'flex-start',
                }}
              >
                <Box
                  sx={{
                    width: 8, height: 8, borderRadius: '50%',
                    bgcolor: ev.kategorie_farbe ?? 'primary.main',
                    mr: 1, mt: 0.6, flexShrink: 0,
                  }}
                />
                <ListItemText
                  primary={
                    <Typography
                      variant="body2"
                      sx={{
                        fontWeight: 500,
                        textDecoration: ev.abgesagt ? 'line-through' : 'none',
                      }}
                    >
                      {ev.titel}
                      {ev.abgesagt && (
                        <Chip
                          label="Abgesagt"
                          size="small"
                          color="error"
                          variant="outlined"
                          sx={{ ml: 0.5, fontSize: '0.6rem', height: 16 }}
                        />
                      )}
                    </Typography>
                  }
                  secondary={
                    ev.ganztaegig
                      ? 'Ganztägig'
                      : `${formatTime(ev.datum_von)} – ${formatTime(ev.datum_bis)} Uhr`
                  }
                />
                {canWriteEvents && !ev.abgesagt && (
                  <IconButton
                    size="small"
                    onClick={() => { onEventEdit(ev); onClose(); }}
                    sx={{ mt: 0.25 }}
                  >
                    <EditIcon fontSize="small" />
                  </IconButton>
                )}
                {canWriteEvents && (
                  <IconButton
                    size="small"
                    color="error"
                    onClick={() => { onEventDelete(ev.id); onClose(); }}
                  >
                    <DeleteForeverIcon fontSize="small" />
                  </IconButton>
                )}
              </ListItem>
            ))}
          </List>
        </>
      )}
    </Popover>
  );
}

// ──────────────────────────────────────────────────────────────────────────────
// PDF Export helper
// ──────────────────────────────────────────────────────────────────────────────

/**
 * Render text with basic markdown (**bold**) and line breaks into a jsPDF doc.
 * Returns the final Y position after rendering.
 */
function renderMarkdownText(
  doc: import('jspdf').jsPDF,
  text: string,
  x: number,
  y: number,
  options?: { fontSize?: number; maxWidth?: number },
): number {
  const fontSize = options?.fontSize ?? 9;
  const lineHeight = fontSize * 0.5; // ~mm per line
  doc.setFontSize(fontSize);
  doc.setTextColor(0, 0, 0);

  const lines = text.split('\n');
  let curY = y;

  for (const line of lines) {
    // Split by ** to alternate normal/bold
    const segments = line.split('**');
    let curX = x;
    for (let i = 0; i < segments.length; i++) {
      const seg = segments[i];
      if (!seg) continue;
      const isBold = i % 2 === 1;
      doc.setFont('helvetica', isBold ? 'bold' : 'normal');
      doc.text(seg, curX, curY);
      curX += doc.getTextWidth(seg);
    }
    curY += lineHeight;
  }

  // Reset font
  doc.setFont('helvetica', 'normal');
  return curY;
}

let _pdfSettingsCache: PdfSettings | null = null;
let _pdfSettingsCacheTime = 0;

async function fetchPdfSettings(): Promise<PdfSettings> {
  // Cache for 30 seconds to avoid fetching on every export click
  if (_pdfSettingsCache && Date.now() - _pdfSettingsCacheTime < 30_000) {
    return _pdfSettingsCache;
  }
  try {
    _pdfSettingsCache = await configApi.getPdfSettings();
    _pdfSettingsCacheTime = Date.now();
    return _pdfSettingsCache;
  } catch {
    return { pdf_header: '', pdf_footer: '', pdf_logo: '', pdf_org_name: '' };
  }
}

async function generatePdf(
  year: number,
  month: number,
  trainingEvents: UebungListItem[],
  veranstaltungen: VeranstaltungListItem[],
) {
  // Dynamically import jsPDF to avoid bundle bloat if not needed
  const { jsPDF } = await import('jspdf');
  const autoTable = (await import('jspdf-autotable')).default;

  const doc = new jsPDF({ orientation: 'landscape', unit: 'mm', format: 'a4' });
  const monthLabel = MONTH_LABELS[month];

  const pdfSettings = await fetchPdfSettings();

  // Header bar
  doc.setFillColor(183, 28, 28); // fire-red
  doc.rect(0, 0, 297, 18, 'F');
  doc.setTextColor(255, 255, 255);
  doc.setFontSize(14);
  doc.setFont('helvetica', 'bold');
  doc.text(`Kalender — ${monthLabel} ${year}`, 10, 12);

  // Right side: logo and/or org name
  const logoSize = 14;
  const logoX = 297 - 4 - logoSize; // 4mm right margin
  if (pdfSettings.pdf_logo) {
    try {
      const fmt = pdfSettings.pdf_logo.match(/^data:image\/(\w+);/)?.[1]?.toUpperCase() ?? 'PNG';
      doc.addImage(pdfSettings.pdf_logo, fmt === 'JPG' ? 'JPEG' : fmt, logoX, 2, logoSize, logoSize);
    } catch { /* ignore invalid image */ }
  }
  if (pdfSettings.pdf_org_name) {
    doc.setFontSize(9);
    doc.setFont('helvetica', 'bold');
    doc.setTextColor(255, 255, 255);
    const nameW = doc.getTextWidth(pdfSettings.pdf_org_name);
    const nameX = (pdfSettings.pdf_logo ? logoX - 3 : 297 - 4) - nameW;
    doc.text(pdfSettings.pdf_org_name, nameX, 12);
  } else if (!pdfSettings.pdf_logo) {
    doc.setFontSize(9);
    doc.setFont('helvetica', 'normal');
    doc.text('Feuerwehr Rems', 250, 12);
  }

  // Custom header text
  let tableStartY = 22;
  if (pdfSettings.pdf_header) {
    tableStartY = renderMarkdownText(doc, pdfSettings.pdf_header, 10, 24, { fontSize: 9 }) + 2;
  }

  // Build combined list (same logic as CombinedListView)
  type ListEntry =
    | { kind: 'training'; item: UebungListItem }
    | { kind: 'event'; item: VeranstaltungListItem };

  const combined: ListEntry[] = [
    ...trainingEvents.map((t): ListEntry => ({ kind: 'training', item: t })),
    ...veranstaltungen.map((e): ListEntry => ({ kind: 'event', item: e })),
  ].sort((a, b) => a.item.datum_von.localeCompare(b.item.datum_von));

  const formatDateCell = (iso: string) => {
    const d = new Date(iso);
    return `${String(d.getDate()).padStart(2, '0')}.${String(d.getMonth() + 1).padStart(2, '0')}.${d.getFullYear()}`;
  };
  const formatTimeCell = (iso: string) => {
    const d = new Date(iso);
    return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
  };

  const rows = combined.map((entry) => {
    const item = entry.item;
    return [
      formatDateCell(item.datum_von),
      formatTimeCell(item.datum_von),
      item.titel,
      entry.kind === 'training'
        ? (item as UebungListItem).typ
        : ((item as VeranstaltungListItem).kategorie_name ?? 'Veranstaltung'),
      (item as any).ort ?? '',
    ];
  });

  autoTable(doc, {
    head: [['Datum', 'Uhrzeit', 'Titel', 'Kategorie/Typ', 'Ort']],
    body: rows,
    startY: tableStartY,
    headStyles: { fillColor: [183, 28, 28], textColor: 255, fontStyle: 'bold' },
    alternateRowStyles: { fillColor: [250, 235, 235] },
    margin: { left: 10, right: 10 },
    styles: { fontSize: 9, cellPadding: 2 },
    columnStyles: {
      0: { cellWidth: 25 },
      1: { cellWidth: 18 },
      2: { cellWidth: 90 },
      3: { cellWidth: 40 },
      4: { cellWidth: 60 },
    },
    didDrawPage: pdfSettings.pdf_footer
      ? () => {
          renderMarkdownText(doc, pdfSettings.pdf_footer, 10, doc.internal.pageSize.height - 12, { fontSize: 8 });
        }
      : undefined,
  });

  const filename = `kalender_${year}_${String(month + 1).padStart(2, '0')}.pdf`;
  doc.save(filename);
}

// ──────────────────────────────────────────────────────────────────────────────
// CSV Import Dialog
// ──────────────────────────────────────────────────────────────────────────────

const CSV_EXAMPLE = [
  'Titel;Datum Von;Datum Bis;Ganztaegig;Ort;Kategorie;Beschreibung',
  'Übung Atemschutz;15.03.2026 19:00;15.03.2026 21:00;Nein;Feuerwehrhaus;Übung;Atemschutzübung für alle',
  'Tag der offenen Tür;20.04.2026;20.04.2026;Ja;Feuerwehrhaus;Veranstaltung;',
].join('\n');

interface CsvRow {
  titel: string;
  datum_von: string;
  datum_bis: string;
  ganztaegig: boolean;
  ort: string | null;
  beschreibung: string | null;
  valid: boolean;
  error?: string;
}

function parseCsvRow(line: string, lineNo: number): CsvRow {
  const parts = line.split(';');
  if (parts.length < 4) {
    return { titel: '', datum_von: '', datum_bis: '', ganztaegig: false, ort: null, beschreibung: null, valid: false, error: `Zeile ${lineNo}: Zu wenige Spalten` };
  }
  const [titel, rawVon, rawBis, rawGanztaegig, ort, ...rest] = parts;
  // Support both 7-column (with Kategorie) and 6-column (without) CSVs:
  // 7 cols: Titel;Von;Bis;Ganztaegig;Ort;Kategorie;Beschreibung
  // 6 cols: Titel;Von;Bis;Ganztaegig;Ort;Beschreibung
  const beschreibung = rest.length >= 2 ? rest[1] : rest[0];
  const ganztaegig = rawGanztaegig?.trim().toLowerCase() === 'ja';

  const convertDate = (raw: string): string => {
    const trimmed = raw.trim();
    // DD.MM.YYYY HH:MM
    const dtIso = fromGermanDateTime(trimmed);
    if (dtIso) return new Date(dtIso).toISOString();
    // DD.MM.YYYY
    const dIso = fromGermanDate(trimmed);
    if (dIso) return new Date(dIso + 'T00:00:00').toISOString();
    return '';
  };

  const datum_von = convertDate(rawVon ?? '');
  const datum_bis = convertDate(rawBis ?? '');

  if (!titel?.trim()) {
    return { titel: '', datum_von, datum_bis, ganztaegig, ort: null, beschreibung: null, valid: false, error: `Zeile ${lineNo}: Titel fehlt` };
  }
  if (!datum_von) {
    return { titel: titel.trim(), datum_von, datum_bis, ganztaegig, ort: null, beschreibung: null, valid: false, error: `Zeile ${lineNo}: Datum Von ungültig` };
  }

  return {
    titel: titel.trim(),
    datum_von,
    datum_bis: datum_bis || datum_von,
    ganztaegig,
    ort: ort?.trim() || null,
    beschreibung: beschreibung?.trim() || null,
    valid: true,
  };
}

interface CsvImportDialogProps {
  open: boolean;
  onClose: () => void;
  onImported: () => void;
}

function CsvImportDialog({ open, onClose, onImported }: CsvImportDialogProps) {
  const [rows, setRows] = useState<CsvRow[]>([]);
  const [importing, setImporting] = useState(false);
  const [result, setResult] = useState<{ created: number; errors: string[] } | null>(null);
  const notification = useNotification();
  const fileRef = React.useRef<HTMLInputElement>(null);

  const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (!file) return;
    const reader = new FileReader();
    reader.onload = (ev) => {
      const text = ev.target?.result as string;
      const lines = text.split(/\r?\n/).filter((l) => l.trim());
      // Skip header line
      const dataLines = lines[0]?.toLowerCase().includes('titel') ? lines.slice(1) : lines;
      const parsed = dataLines.map((line, i) => parseCsvRow(line, i + 2));
      setRows(parsed);
      setResult(null);
    };
    reader.readAsText(file, 'UTF-8');
  };

  const downloadExample = () => {
    const blob = new Blob(['\uFEFF' + CSV_EXAMPLE], { type: 'text/csv;charset=utf-8;' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'kalender_import_beispiel.csv';
    a.click();
    URL.revokeObjectURL(url);
  };

  const validRows = rows.filter((r) => r.valid);

  const handleImport = async () => {
    if (validRows.length === 0) return;
    setImporting(true);
    try {
      const events = validRows.map((r) => ({
        titel: r.titel,
        datum_von: r.datum_von,
        datum_bis: r.datum_bis,
        ganztaegig: r.ganztaegig,
        ort: r.ort,
        beschreibung: r.beschreibung,
        zielgruppen: [],
        alle_gruppen: true,
        anmeldung_erforderlich: false,
      }));
      const res = await eventsApi.importEvents(events);
      setResult(res);
      if (res.created > 0) {
        notification.showSuccess(`${res.created} Veranstaltung${res.created !== 1 ? 'en' : ''} importiert`);
        onImported();
      }
    } catch {
      notification.showError('Import fehlgeschlagen');
    } finally {
      setImporting(false);
    }
  };

  const handleClose = () => {
    setRows([]);
    setResult(null);
    if (fileRef.current) fileRef.current.value = '';
    onClose();
  };

  return (
    <Dialog open={open} onClose={handleClose} maxWidth="md" fullWidth>
      <DialogTitle>Kalender importieren (CSV)</DialogTitle>
      <DialogContent>
        <Stack spacing={2} sx={{ mt: 1 }}>
          <Button
            variant="outlined"
            size="small"
            startIcon={<FileDownloadIcon />}
            onClick={downloadExample}
            sx={{ alignSelf: 'flex-start' }}
          >
            Beispiel-CSV herunterladen
          </Button>

          <Box>
            <input
              ref={fileRef}
              type="file"
              accept=".csv,text/csv"
              onChange={handleFileChange}
              style={{ display: 'none' }}
              id="csv-import-input"
            />
            <label htmlFor="csv-import-input">
              <Button variant="contained" component="span" startIcon={<FileUploadIcon />}>
                CSV-Datei auswählen
              </Button>
            </label>
          </Box>

          {rows.length > 0 && (
            <>
              <Typography variant="body2">
                {validRows.length} gültige / {rows.length - validRows.length} fehlerhafte Zeilen
              </Typography>
              {rows.some((r) => !r.valid) && (
                <Alert severity="warning" sx={{ whiteSpace: 'pre-line' }}>
                  {rows.filter((r) => !r.valid).map((r) => r.error).join('\n')}
                </Alert>
              )}
              <TableContainer component={Paper} variant="outlined" sx={{ maxHeight: 300 }}>
                <Table size="small" stickyHeader>
                  <TableHead>
                    <TableRow>
                      <TableCell>Status</TableCell>
                      <TableCell>Titel</TableCell>
                      <TableCell>Von</TableCell>
                      <TableCell>Bis</TableCell>
                      <TableCell>Ganztägig</TableCell>
                      <TableCell>Ort</TableCell>
                    </TableRow>
                  </TableHead>
                  <TableBody>
                    {rows.map((row, i) => (
                      <TableRow key={i} sx={{ bgcolor: row.valid ? undefined : 'error.light' }}>
                        <TableCell>
                          <Chip
                            label={row.valid ? 'OK' : 'Fehler'}
                            color={row.valid ? 'success' : 'error'}
                            size="small"
                          />
                        </TableCell>
                        <TableCell>{row.titel}</TableCell>
                        <TableCell>{row.datum_von ? new Date(row.datum_von).toLocaleDateString('de-DE') : '—'}</TableCell>
                        <TableCell>{row.datum_bis ? new Date(row.datum_bis).toLocaleDateString('de-DE') : '—'}</TableCell>
                        <TableCell>{row.ganztaegig ? 'Ja' : 'Nein'}</TableCell>
                        <TableCell>{row.ort ?? '—'}</TableCell>
                      </TableRow>
                    ))}
                  </TableBody>
                </Table>
              </TableContainer>
            </>
          )}

          {result && (
            <Alert severity={result.errors.length === 0 ? 'success' : 'warning'}>
              {result.created} Veranstaltung{result.created !== 1 ? 'en' : ''} importiert.
              {result.errors.length > 0 && ` ${result.errors.length} Fehler.`}
            </Alert>
          )}
        </Stack>
      </DialogContent>
      <DialogActions>
        <Button onClick={handleClose}>Schließen</Button>
        <Button
          variant="contained"
          onClick={handleImport}
          disabled={validRows.length === 0 || importing}
          startIcon={importing ? <CircularProgress size={16} /> : <FileUploadIcon />}
        >
          {validRows.length > 0 ? `${validRows.length} importieren` : 'Importieren'}
        </Button>
      </DialogActions>
    </Dialog>
  );
}

// ──────────────────────────────────────────────────────────────────────────────
// Combined List View (training + events sorted by date)
// ──────────────────────────────────────────────────────────────────────────────

interface CombinedListViewProps {
  trainingEvents: UebungListItem[];
  veranstaltungen: VeranstaltungListItem[];
  selectedKategorie: string | 'all';
  canWriteEvents: boolean;
  onTrainingClick: (id: string) => void;
  onEventEdit: (ev: VeranstaltungListItem) => void;
  onEventCancel: (id: string) => void;
  onEventDelete: (id: string) => void;
}

function CombinedListView({
  trainingEvents, veranstaltungen, selectedKategorie,
  canWriteEvents, onTrainingClick, onEventEdit, onEventCancel, onEventDelete,
}: CombinedListViewProps) {
  type ListEntry =
    | { kind: 'training'; item: UebungListItem }
    | { kind: 'event'; item: VeranstaltungListItem };

  const combined = useMemo(() => {
    const entries: ListEntry[] = [
      ...trainingEvents.map((t): ListEntry => ({ kind: 'training', item: t })),
      ...veranstaltungen
        .filter((e) => selectedKategorie === 'all' || e.kategorie_id === selectedKategorie)
        .map((e): ListEntry => ({ kind: 'event', item: e })),
    ];
    return entries.sort((a, b) => {
      const da = a.kind === 'training' ? a.item.datum_von : a.item.datum_von;
      const db = b.kind === 'training' ? b.item.datum_von : b.item.datum_von;
      return da.localeCompare(db);
    });
  }, [trainingEvents, veranstaltungen, selectedKategorie]);

  if (combined.length === 0) {
    return (
      <Typography
        variant="body2"
        color="text.secondary"
        sx={{ textAlign: 'center', py: 4 }}
      >
        Keine Einträge in diesem Monat.
      </Typography>
    );
  }

  return (
    <List disablePadding>
      {combined.map((entry, idx) => {
        const isTraining = entry.kind === 'training';
        const item = entry.item;
        const color = isTraining
          ? TYP_DOT_COLOR[(item as UebungListItem).typ]
          : ((item as VeranstaltungListItem).kategorie_farbe ?? '#1976d2');

        return (
          <Box key={`${entry.kind}-${item.id}`}>
            {idx > 0 && <Divider />}
            <ListItem
              sx={{
                px: 1,
                py: 1,
                borderRadius: 1,
                opacity: item.abgesagt ? 0.55 : 1,
                borderLeft: `4px solid ${color}`,
                '&:hover': { bgcolor: 'action.hover' },
                cursor: isTraining ? 'pointer' : 'default',
              }}
              onClick={isTraining ? () => onTrainingClick(item.id) : undefined}
            >
              {/* Date badge */}
              <Box sx={{ minWidth: 52, textAlign: 'center', mr: 2, flexShrink: 0 }}>
                <Typography variant="caption" sx={{ display: 'block', color: 'text.disabled', fontSize: '0.65rem' }}>
                  {new Date(item.datum_von).getDate()}.
                  {new Date(item.datum_von).getMonth() + 1}.
                </Typography>
                <Typography variant="caption" sx={{ display: 'block', color: 'text.secondary', fontSize: '0.7rem' }}>
                  {formatTime(item.datum_von)}
                </Typography>
              </Box>

              <ListItemText
                primary={
                  <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, flexWrap: 'wrap' }}>
                    {isTraining && (item as UebungListItem).pflichtveranstaltung && (
                      <StarIcon sx={{ fontSize: 14, color: 'warning.main' }} />
                    )}
                    <Typography
                      variant="body2"
                      sx={{
                        fontWeight: isTraining && (item as UebungListItem).pflichtveranstaltung ? 700 : 400,
                        textDecoration: item.abgesagt ? 'line-through' : 'none',
                      }}
                    >
                      {item.titel}
                    </Typography>
                    {item.abgesagt && (
                      <Chip label="Abgesagt" size="small" color="error" variant="outlined" sx={{ fontSize: '0.6rem', height: 16 }} />
                    )}
                  </Box>
                }
                secondary={
                  <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, mt: 0.25, flexWrap: 'wrap' }}>
                    <Chip
                      label={
                        isTraining
                          ? (item as UebungListItem).typ
                          : ((item as VeranstaltungListItem).kategorie_name ?? 'Veranstaltung')
                      }
                      size="small"
                      sx={{
                        fontSize: '0.6rem',
                        height: 16,
                        bgcolor: color,
                        color: 'white',
                      }}
                    />
                    {(item as { ort?: string | null }).ort && (
                      <Typography variant="caption" color="text.disabled" noWrap>
                        {(item as { ort?: string | null }).ort}
                      </Typography>
                    )}
                    {isTraining && (
                      <Box sx={{ ml: 'auto' }}>
                        <RsvpDot status={(item as UebungListItem).eigener_status} />
                      </Box>
                    )}
                  </Box>
                }
                sx={{ my: 0 }}
              />

              {!isTraining && canWriteEvents && !item.abgesagt && (
                <Box sx={{ display: 'flex', gap: 0.5, ml: 1 }}>
                  <IconButton size="small" onClick={() => onEventEdit(item as VeranstaltungListItem)}>
                    <EditIcon fontSize="small" />
                  </IconButton>
                  <IconButton size="small" color="error" onClick={() => onEventCancel(item.id)}>
                    <CancelIcon fontSize="small" />
                  </IconButton>
                </Box>
              )}
              {!isTraining && canWriteEvents && (
                <Box sx={{ display: 'flex', gap: 0.5, ml: item.abgesagt ? 1 : 0 }}>
                  <IconButton
                    size="small"
                    color="error"
                    onClick={() => onEventDelete(item.id)}
                    title="Endgültig löschen"
                  >
                    <DeleteForeverIcon fontSize="small" />
                  </IconButton>
                </Box>
              )}
            </ListItem>
          </Box>
        );
      })}
    </List>
  );
}

// ──────────────────────────────────────────────────────────────────────────────
// Veranstaltung Form Dialog
// ──────────────────────────────────────────────────────────────────────────────

interface VeranstaltungFormDialogProps {
  open: boolean;
  onClose: () => void;
  onSaved: () => void;
  editingEvent: VeranstaltungListItem | null;
  kategorien: VeranstaltungKategorie[];
  groups: GroupInfo[];
}

function VeranstaltungFormDialog({
  open, onClose, onSaved, editingEvent, kategorien, groups,
}: VeranstaltungFormDialogProps) {
  const notification = useNotification();
  const [loading, setLoading] = useState(false);
  const [form, setForm] = useState<CreateVeranstaltungInput>({ ...EMPTY_VERANSTALTUNG_FORM });
  const [wiederholungAktiv, setWiederholungAktiv] = useState(false);
  const [wiederholungTyp, setWiederholungTyp] = useState<WiederholungConfig['typ']>('wöchentlich');
  const [wiederholungIntervall, setWiederholungIntervall] = useState(1);
  const [wiederholungBis, setWiederholungBis] = useState('');
  const [wiederholungWochentag, setWiederholungWochentag] = useState(0);

  useEffect(() => {
    if (!open) return;
    if (editingEvent) {
      setForm({
        titel: editingEvent.titel,
        beschreibung: editingEvent.beschreibung ?? null,
        ort: editingEvent.ort ?? null,
        ort_url: null,
        kategorie_id: editingEvent.kategorie_id ?? null,
        datum_von: editingEvent.datum_von,
        datum_bis: editingEvent.datum_bis,
        ganztaegig: editingEvent.ganztaegig,
        zielgruppen: editingEvent.zielgruppen ?? [],
        alle_gruppen: editingEvent.alle_gruppen,
        max_teilnehmer: null,
        anmeldung_erforderlich: editingEvent.anmeldung_erforderlich,
        anmeldung_bis: null,
      });
      // Populate recurrence fields if parent event has config (read-only display)
      if (editingEvent.wiederholung) {
        setWiederholungAktiv(true);
        setWiederholungTyp(editingEvent.wiederholung.typ);
        setWiederholungIntervall(editingEvent.wiederholung.intervall ?? 1);
        setWiederholungBis(editingEvent.wiederholung.bis ?? '');
        setWiederholungWochentag(editingEvent.wiederholung.wochentag ?? 0);
      } else {
        setWiederholungAktiv(false);
        setWiederholungTyp('wöchentlich');
        setWiederholungIntervall(1);
        setWiederholungBis('');
        setWiederholungWochentag(0);
      }
    } else {
      const now = new Date();
      now.setMinutes(0, 0, 0);
      const later = new Date(now);
      later.setHours(later.getHours() + 2);
      setForm({ ...EMPTY_VERANSTALTUNG_FORM, datum_von: now.toISOString(), datum_bis: later.toISOString() });
      setWiederholungAktiv(false);
      setWiederholungTyp('wöchentlich');
      setWiederholungIntervall(1);
      setWiederholungBis('');
      setWiederholungWochentag(0);
    }
  }, [open, editingEvent]);

  // Auto-correct: end date should never be before start date
  useEffect(() => {
    const von = new Date(form.datum_von);
    const bis = new Date(form.datum_bis);
    if (!isNaN(von.getTime()) && !isNaN(bis.getTime()) && bis < von) {
      // Set datum_bis to datum_von (preserve time offset for non-ganztaegig)
      if (form.ganztaegig) {
        handleChange('datum_bis', von.toISOString());
      } else {
        const adjusted = new Date(von);
        adjusted.setHours(adjusted.getHours() + 2);
        handleChange('datum_bis', adjusted.toISOString());
      }
    }
    // Also auto-correct wiederholungBis
    if (wiederholungBis) {
      const vonDateOnly = form.datum_von.slice(0, 10);
      if (wiederholungBis < vonDateOnly) {
        setWiederholungBis(vonDateOnly);
      }
    }
  }, [form.datum_von]); // eslint-disable-line react-hooks/exhaustive-deps

  const handleChange = (field: keyof CreateVeranstaltungInput, value: unknown) => {
    if (field === 'kategorie_id' && !editingEvent) {
      // Auto-fill zielgruppen / alle_gruppen from the selected category (only for new events)
      const kat = kategorien.find((k) => k.id === value);
      if (kat) {
        setForm((prev) => ({
          ...prev,
          kategorie_id: value as string | null,
          alle_gruppen: kat.alle_gruppen,
          zielgruppen: kat.alle_gruppen ? [] : (kat.zielgruppen ?? []),
        }));
        return;
      }
    }
    setForm((prev) => ({ ...prev, [field]: value }));
  };

  const handleGroupToggle = (groupId: string) => {
    setForm((prev) => {
      const updated = prev.zielgruppen.includes(groupId)
        ? prev.zielgruppen.filter((g) => g !== groupId)
        : [...prev.zielgruppen, groupId];
      return { ...prev, zielgruppen: updated };
    });
  };

  const handleSave = async () => {
    if (!form.titel.trim()) {
      notification.showError('Titel ist erforderlich');
      return;
    }

    // Date validation
    const vonDate = new Date(form.datum_von);
    const bisDate = new Date(form.datum_bis);
    if (isNaN(vonDate.getTime())) {
      notification.showError('Ungültiges Datum Von');
      return;
    }
    if (isNaN(bisDate.getTime())) {
      notification.showError('Ungültiges Datum Bis');
      return;
    }
    if (bisDate < vonDate) {
      notification.showError('Datum Bis muss nach Datum Von liegen');
      return;
    }
    if (wiederholungAktiv && wiederholungBis && isNaN(new Date(wiederholungBis).getTime())) {
      notification.showError('Ungültiges Datum für Wiederholung Bis');
      return;
    }

    setLoading(true);
    try {
      const createPayload: CreateVeranstaltungInput = {
        ...form,
        wiederholung: ((!editingEvent || (editingEvent.wiederholung && !editingEvent.wiederholung_parent_id)) && wiederholungAktiv && wiederholungBis)
          ? {
              typ: wiederholungTyp,
              bis: wiederholungBis,
              intervall: wiederholungTyp === 'wöchentlich' ? wiederholungIntervall : undefined,
              wochentag: (wiederholungTyp === 'monatlich_erster_wochentag' || wiederholungTyp === 'monatlich_letzter_wochentag')
                ? wiederholungWochentag
                : undefined,
            }
          : null,
      };
      if (editingEvent) {
        await eventsApi.updateEvent(editingEvent.id, createPayload);
        notification.showSuccess('Veranstaltung aktualisiert');
      } else {
        await eventsApi.createEvent(createPayload);
        notification.showSuccess(
          wiederholungAktiv && wiederholungBis
            ? 'Veranstaltung und Wiederholungen erstellt'
            : 'Veranstaltung erstellt'
        );
      }
      onSaved();
      onClose();
    } catch (e: unknown) {
      notification.showError(e instanceof Error ? e.message : 'Fehler beim Speichern');
    } finally {
      setLoading(false);
    }
  };

  return (
    <Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
      <DialogTitle>
        {editingEvent ? 'Veranstaltung bearbeiten' : 'Neue Veranstaltung'}
      </DialogTitle>
      <DialogContent>
        <Stack spacing={2} sx={{ mt: 1 }}>
          <TextField
            label="Titel"
            value={form.titel}
            onChange={(e) => handleChange('titel', e.target.value)}
            required
            fullWidth
          />
          <TextField
            label="Beschreibung"
            value={form.beschreibung ?? ''}
            onChange={(e) => handleChange('beschreibung', e.target.value || null)}
            multiline
            rows={2}
            fullWidth
          />
          <FormControl fullWidth>
            <InputLabel id="kategorie-select-label" shrink>Kategorie</InputLabel>
            <Select
              labelId="kategorie-select-label"
              label="Kategorie"
              value={form.kategorie_id ?? ''}
              onChange={(e) => handleChange('kategorie_id', e.target.value || null)}
              displayEmpty
              notched
            >
              <MenuItem value=""><em>Keine Kategorie</em></MenuItem>
              {kategorien.map((k) => (
                <MenuItem key={k.id} value={k.id}>
                  <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
                    <Box sx={{ width: 12, height: 12, borderRadius: '50%', bgcolor: k.farbe, flexShrink: 0 }} />
                    {k.name}
                  </Box>
                </MenuItem>
              ))}
            </Select>
          </FormControl>
          <FormControlLabel
            control={
              <Switch
                checked={form.ganztaegig}
                onChange={(e) => handleChange('ganztaegig', e.target.checked)}
              />
            }
            label="Ganztägig"
          />
          <TextField
            label="Von"
            type={form.ganztaegig ? 'date' : 'datetime-local'}
            value={
              form.ganztaegig
                ? toDateInputValue(form.datum_von)
                : toDatetimeLocalValue(form.datum_von)
            }
            onChange={(e) => {
              const raw = e.target.value;
              if (!raw) return;
              const d = form.ganztaegig
                ? new Date(raw + 'T00:00:00')
                : new Date(raw + ':00');
              if (isNaN(d.getTime())) return;
              handleChange('datum_von', d.toISOString());
            }}
            InputLabelProps={{ shrink: true }}
            fullWidth
          />
          <TextField
            label="Bis"
            type={form.ganztaegig ? 'date' : 'datetime-local'}
            value={
              form.ganztaegig
                ? toDateInputValue(form.datum_bis)
                : toDatetimeLocalValue(form.datum_bis)
            }
            onChange={(e) => {
              const raw = e.target.value;
              if (!raw) return;
              const d = form.ganztaegig
                ? new Date(raw + 'T23:59:00')
                : new Date(raw + ':00');
              if (isNaN(d.getTime())) return;
              handleChange('datum_bis', d.toISOString());
            }}
            InputLabelProps={{ shrink: true }}
            fullWidth
          />
          <TextField
            label="Ort"
            value={form.ort ?? ''}
            onChange={(e) => handleChange('ort', e.target.value || null)}
            fullWidth
          />
          <Divider />
          <FormControlLabel
            control={
              <Switch
                checked={form.alle_gruppen}
                onChange={(e) => handleChange('alle_gruppen', e.target.checked)}
              />
            }
            label="Für alle Mitglieder sichtbar"
          />
          {!form.alle_gruppen && groups.length > 0 && (
            <Box>
              <Typography variant="body2" fontWeight={600} sx={{ mb: 0.5 }}>
                Zielgruppen
              </Typography>
              <FormGroup>
                {groups.map((g) => (
                  <FormControlLabel
                    key={g.id}
                    control={
                      <Checkbox
                        checked={form.zielgruppen.includes(g.id)}
                        onChange={() => handleGroupToggle(g.id)}
                        size="small"
                      />
                    }
                    label={g.label}
                  />
                ))}
              </FormGroup>
            </Box>
          )}
          <Divider />
          <FormControlLabel
            control={
              <Switch
                checked={form.anmeldung_erforderlich}
                onChange={(e) => handleChange('anmeldung_erforderlich', e.target.checked)}
              />
            }
            label="Anmeldung erforderlich"
          />
          {form.anmeldung_erforderlich && (
            <TextField
              label="Anmeldeschluss"
              placeholder="TT.MM.JJJJ HH:MM"
              value={form.anmeldung_bis ? toGermanDateTime(form.anmeldung_bis) : ''}
              onChange={(e) => {
                const raw = e.target.value;
                if (!raw) { handleChange('anmeldung_bis', null); return; }
                const iso = fromGermanDateTime(raw);
                if (iso) handleChange('anmeldung_bis', new Date(iso).toISOString());
              }}
              InputLabelProps={{ shrink: true }}
              fullWidth
            />
          )}
          {/* Wiederholung */}
          {(!editingEvent || editingEvent.wiederholung || editingEvent.wiederholung_parent_id) && (
            <>
              <Divider />
              {editingEvent?.wiederholung_parent_id ? (
                <>
                  <Typography variant="body2" color="text.secondary" sx={{ fontStyle: 'italic' }}>
                    Dieser Termin ist Teil einer Serienveranstaltung. Änderungen betreffen nur diesen Einzeltermin.
                  </Typography>
                  <FormControlLabel
                    control={<Switch checked disabled />}
                    label="Wiederkehrende Veranstaltung"
                  />
                </>
              ) : (
                <FormControlLabel
                  control={
                    <Switch
                      checked={wiederholungAktiv}
                      onChange={(e) => {
                        setWiederholungAktiv(e.target.checked);
                        if (e.target.checked && !wiederholungBis) {
                          const bisDefault = new Date(form.datum_von);
                          bisDefault.setMonth(bisDefault.getMonth() + 3);
                          setWiederholungBis(bisDefault.toISOString().slice(0, 10));
                        }
                      }}
                    />
                  }
                  label="Wiederkehrende Veranstaltung"
                />
              )}
              {wiederholungAktiv && (
                <Stack spacing={2}>
                  <FormControl fullWidth size="small" disabled={!!editingEvent?.wiederholung_parent_id}>
                    <InputLabel id="wiederholung-typ-label">Wiederholung</InputLabel>
                    <Select
                      labelId="wiederholung-typ-label"
                      label="Wiederholung"
                      value={wiederholungTyp}
                      onChange={(e) => setWiederholungTyp(e.target.value as WiederholungConfig['typ'])}
                    >
                      <MenuItem value="wöchentlich">Wöchentlich</MenuItem>
                      <MenuItem value="zweiwöchentlich">Vierzehntägig (alle 2 Wochen)</MenuItem>
                      <MenuItem value="monatlich_datum">Monatlich (gleicher Tag)</MenuItem>
                      <MenuItem value="monatlich_erster_wochentag">Monatlich (erster Wochentag)</MenuItem>
                      <MenuItem value="monatlich_letzter_wochentag">Monatlich (letzter Wochentag)</MenuItem>
                    </Select>
                  </FormControl>

                  {wiederholungTyp === 'wöchentlich' && (
                    <TextField
                      label="Intervall (Wochen)"
                      type="number"
                      size="small"
                      value={wiederholungIntervall}
                      onChange={(e) => setWiederholungIntervall(Math.max(1, Math.min(52, parseInt(e.target.value) || 1)))}
                      inputProps={{ min: 1, max: 52 }}
                      fullWidth
                      disabled={!!editingEvent?.wiederholung_parent_id}
                    />
                  )}

                  {(wiederholungTyp === 'monatlich_erster_wochentag' || wiederholungTyp === 'monatlich_letzter_wochentag') && (
                    <FormControl fullWidth size="small" disabled={!!editingEvent?.wiederholung_parent_id}>
                      <InputLabel id="wiederholung-wochentag-label">Wochentag</InputLabel>
                      <Select
                        labelId="wiederholung-wochentag-label"
                        label="Wochentag"
                        value={wiederholungWochentag}
                        onChange={(e) => setWiederholungWochentag(Number(e.target.value))}
                      >
                        {['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'].map((d, i) => (
                          <MenuItem key={i} value={i}>{d}</MenuItem>
                        ))}
                      </Select>
                    </FormControl>
                  )}

                  <TextField
                    label="Wiederholungen bis"
                    size="small"
                    type="date"
                    value={wiederholungBis}
                    onChange={(e) => setWiederholungBis(e.target.value)}
                    InputLabelProps={{ shrink: true }}
                    fullWidth
                    disabled={!!editingEvent?.wiederholung_parent_id}
                    helperText="Letztes Datum für Wiederholungen"
                  />
                </Stack>
              )}
            </>
          )}
        </Stack>
      </DialogContent>
      <DialogActions>
        <Button onClick={onClose}>Abbrechen</Button>
        <Button variant="contained" onClick={handleSave} disabled={loading}>
          {loading ? <CircularProgress size={20} /> : editingEvent ? 'Speichern' : 'Erstellen'}
        </Button>
      </DialogActions>
    </Dialog>
  );
}

// ──────────────────────────────────────────────────────────────────────────────
// Settings Tab (Kategorien CRUD)
// ──────────────────────────────────────────────────────────────────────────────

interface SettingsTabProps {
  kategorien: VeranstaltungKategorie[];
  onKategorienChange: (k: VeranstaltungKategorie[]) => void;
}

function SettingsTab({ kategorien, onKategorienChange }: SettingsTabProps) {
  const notification = useNotification();
  const [editingKat, setEditingKat] = useState<VeranstaltungKategorie | null>(null);
  const [newKatOpen, setNewKatOpen] = useState(false);
  const [newKatForm, setNewKatForm] = useState({ name: '', farbe: '#1976d2', beschreibung: '' });
  const [saving, setSaving] = useState(false);

  const reload = async () => {
    const kat = await eventsApi.getKategorien();
    onKategorienChange(kat);
  };

  const handleCreate = async () => {
    if (!newKatForm.name.trim()) return;
    setSaving(true);
    try {
      await eventsApi.createKategorie({ name: newKatForm.name.trim(), farbe: newKatForm.farbe, beschreibung: newKatForm.beschreibung || undefined });
      notification.showSuccess('Kategorie erstellt');
      setNewKatOpen(false);
      setNewKatForm({ name: '', farbe: '#1976d2', beschreibung: '' });
      await reload();
    } catch { notification.showError('Fehler beim Erstellen'); }
    finally { setSaving(false); }
  };

  const handleUpdate = async () => {
    if (!editingKat) return;
    setSaving(true);
    try {
      await eventsApi.updateKategorie(editingKat.id, { name: editingKat.name, farbe: editingKat.farbe, beschreibung: editingKat.beschreibung ?? undefined });
      notification.showSuccess('Kategorie gespeichert');
      setEditingKat(null);
      await reload();
    } catch { notification.showError('Fehler beim Speichern'); }
    finally { setSaving(false); }
  };

  const handleDelete = async (id: string) => {
    try {
      await eventsApi.deleteKategorie(id);
      notification.showSuccess('Kategorie gelöscht');
      await reload();
    } catch { notification.showError('Fehler beim Löschen'); }
  };

  return (
    <Box>
      <Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
        <Typography variant="h6" sx={{ flexGrow: 1 }}>Veranstaltungskategorien</Typography>
        <Button startIcon={<Add />} variant="contained" size="small" onClick={() => setNewKatOpen(true)}>
          Neue Kategorie
        </Button>
      </Box>

      <TableContainer component={Paper} variant="outlined">
        <Table size="small">
          <TableHead>
            <TableRow>
              <TableCell>Farbe</TableCell>
              <TableCell>Name</TableCell>
              <TableCell>Beschreibung</TableCell>
              <TableCell align="right">Aktionen</TableCell>
            </TableRow>
          </TableHead>
          <TableBody>
            {kategorien.map((k) => (
              <TableRow key={k.id}>
                <TableCell>
                  <Box sx={{ width: 24, height: 24, borderRadius: '50%', bgcolor: k.farbe, border: '1px solid', borderColor: 'divider' }} />
                </TableCell>
                <TableCell>{k.name}</TableCell>
                <TableCell>{k.beschreibung ?? '—'}</TableCell>
                <TableCell align="right">
                  <IconButton size="small" onClick={() => setEditingKat({ ...k })}>
                    <EditIcon fontSize="small" />
                  </IconButton>
                  <IconButton size="small" color="error" onClick={() => handleDelete(k.id)}>
                    <DeleteForeverIcon fontSize="small" />
                  </IconButton>
                </TableCell>
              </TableRow>
            ))}
            {kategorien.length === 0 && (
              <TableRow>
                <TableCell colSpan={4} sx={{ textAlign: 'center', py: 3, color: 'text.secondary' }}>
                  Noch keine Kategorien vorhanden
                </TableCell>
              </TableRow>
            )}
          </TableBody>
        </Table>
      </TableContainer>

      {/* Edit dialog */}
      <Dialog open={Boolean(editingKat)} onClose={() => setEditingKat(null)} maxWidth="xs" fullWidth>
        <DialogTitle>Kategorie bearbeiten</DialogTitle>
        <DialogContent>
          <Stack spacing={2} sx={{ mt: 1 }}>
            <TextField label="Name" value={editingKat?.name ?? ''} onChange={(e) => setEditingKat((k) => k ? { ...k, name: e.target.value } : k)} fullWidth required />
            <Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
              <Typography variant="body2">Farbe</Typography>
              <input type="color" value={editingKat?.farbe ?? '#1976d2'} onChange={(e) => setEditingKat((k) => k ? { ...k, farbe: e.target.value } : k)} style={{ width: 40, height: 36, border: 'none', cursor: 'pointer', borderRadius: 4 }} />
              <Typography variant="body2" color="text.secondary">{editingKat?.farbe}</Typography>
            </Box>
            <TextField label="Beschreibung" value={editingKat?.beschreibung ?? ''} onChange={(e) => setEditingKat((k) => k ? { ...k, beschreibung: e.target.value || null } : k)} fullWidth multiline rows={2} />
          </Stack>
        </DialogContent>
        <DialogActions>
          <Button onClick={() => setEditingKat(null)}>Abbrechen</Button>
          <Button variant="contained" onClick={handleUpdate} disabled={saving || !editingKat?.name.trim()}>
            {saving ? <CircularProgress size={20} /> : 'Speichern'}
          </Button>
        </DialogActions>
      </Dialog>

      {/* New category dialog */}
      <Dialog open={newKatOpen} onClose={() => setNewKatOpen(false)} maxWidth="xs" fullWidth>
        <DialogTitle>Neue Kategorie</DialogTitle>
        <DialogContent>
          <Stack spacing={2} sx={{ mt: 1 }}>
            <TextField label="Name" value={newKatForm.name} onChange={(e) => setNewKatForm((f) => ({ ...f, name: e.target.value }))} fullWidth required />
            <Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
              <Typography variant="body2">Farbe</Typography>
              <input type="color" value={newKatForm.farbe} onChange={(e) => setNewKatForm((f) => ({ ...f, farbe: e.target.value }))} style={{ width: 40, height: 36, border: 'none', cursor: 'pointer', borderRadius: 4 }} />
              <Typography variant="body2" color="text.secondary">{newKatForm.farbe}</Typography>
            </Box>
            <TextField label="Beschreibung" value={newKatForm.beschreibung} onChange={(e) => setNewKatForm((f) => ({ ...f, beschreibung: e.target.value }))} fullWidth multiline rows={2} />
          </Stack>
        </DialogContent>
        <DialogActions>
          <Button onClick={() => setNewKatOpen(false)}>Abbrechen</Button>
          <Button variant="contained" onClick={handleCreate} disabled={saving || !newKatForm.name.trim()}>
            {saving ? <CircularProgress size={20} /> : 'Erstellen'}
          </Button>
        </DialogActions>
      </Dialog>
    </Box>
  );
}

// ──────────────────────────────────────────────────────────────────────────────
// Main Kalender Page
// ──────────────────────────────────────────────────────────────────────────────

export default function Kalender() {
  const navigate = useNavigate();
  const { hasPermission, isFeatureEnabled } = usePermissionContext();
  const notification = useNotification();
  const theme = useTheme();
  const isMobile = useMediaQuery(theme.breakpoints.down('sm'));

  const canWriteEvents = hasPermission('kalender:create');

  // ── Tab / search params ───────────────────────────────────────────────────
  const [searchParams, setSearchParams] = useSearchParams();
  const activeTab = Number(searchParams.get('tab') ?? 0);
  const setActiveTab = (n: number) => setSearchParams({ tab: String(n) });

  // ── Calendar state ─────────────────────────────────────────────────────────
  const today = new Date();
  const [viewMonth, setViewMonth] = useState({
    year: today.getFullYear(),
    month: today.getMonth(),
  });
  const [viewMode, setViewMode] = useState<'calendar' | 'list' | 'day' | 'week'>('calendar');
  const [currentDate, setCurrentDate] = useState(new Date());
  const [selectedKategorie, setSelectedKategorie] = useState<string | 'all'>('all');
  const [listFrom, setListFrom] = useState(() => fnsFormat(startOfMonth(today), 'yyyy-MM-dd'));
  const [listTo, setListTo] = useState(() => fnsFormat(endOfMonth(today), 'yyyy-MM-dd'));

  const [trainingEvents, setTrainingEvents] = useState<UebungListItem[]>([]);
  const [veranstaltungen, setVeranstaltungen] = useState<VeranstaltungListItem[]>([]);
  const [kategorien, setKategorien] = useState<VeranstaltungKategorie[]>([]);
  const [groups, setGroups] = useState<GroupInfo[]>([]);
  const [calLoading, setCalLoading] = useState(false);
  const [calError, setCalError] = useState<string | null>(null);

  // Popover state (day click)
  const [popoverAnchor, setPopoverAnchor] = useState<Element | null>(null);
  const [popoverDay, setPopoverDay] = useState<Date | null>(null);

  // Veranstaltung form / cancel dialogs
  const [veranstFormOpen, setVeranstFormOpen] = useState(false);
  const [veranstEditing, setVeranstEditing] = useState<VeranstaltungListItem | null>(null);
  const [cancelEventId, setCancelEventId] = useState<string | null>(null);
  const [cancelEventGrund, setCancelEventGrund] = useState('');
  const [cancelEventLoading, setCancelEventLoading] = useState(false);
  const [deleteEventId, setDeleteEventId] = useState<string | null>(null);
  const [deleteEventLoading, setDeleteEventLoading] = useState(false);
  const [deleteMode, setDeleteMode] = useState<'single' | 'future' | 'all'>('all');
  const [editScopeEvent, setEditScopeEvent] = useState<VeranstaltungListItem | null>(null);

  // iCal subscription
  const [icalEventOpen, setIcalEventOpen] = useState(false);
  const [icalEventUrl, setIcalEventUrl] = useState('');
  const [csvImportOpen, setCsvImportOpen] = useState(false);

  // ── Data loading ─────────────────────────────────────────────────────────────

  const loadCalendarData = useCallback(async () => {
    setCalLoading(true);
    setCalError(null);
    try {
      const firstDay = new Date(viewMonth.year, viewMonth.month, 1);
      const dayOfWeek = (firstDay.getDay() + 6) % 7;
      const gridStart = new Date(firstDay);
      gridStart.setDate(gridStart.getDate() - dayOfWeek);
      const gridEnd = new Date(gridStart);
      gridEnd.setDate(gridStart.getDate() + 41);

      const [trainData, eventData] = await Promise.all([
        trainingApi.getCalendarRange(gridStart, gridEnd),
        eventsApi.getCalendarRange(gridStart, gridEnd),
      ]);
      setTrainingEvents(trainData);
      setVeranstaltungen(eventData);
    } catch (e: unknown) {
      setCalError(e instanceof Error ? e.message : 'Fehler beim Laden');
    } finally {
      setCalLoading(false);
    }
  }, [viewMonth]);

  // Load kategorien + groups once
  useEffect(() => {
    Promise.all([eventsApi.getKategorien(), eventsApi.getGroups()]).then(
      ([kat, grp]) => {
        setKategorien(kat);
        setGroups(grp);
      }
    );
  }, []);

  useEffect(() => {
    loadCalendarData();
  }, [loadCalendarData]);

  // ── Calendar tab helpers ─────────────────────────────────────────────────────

  const handlePrev = () => {
    if (viewMode === 'day') {
      setCurrentDate((d) => subDays(d, 1));
    } else if (viewMode === 'week') {
      setCurrentDate((d) => subWeeks(d, 1));
    } else {
      setViewMonth((prev) => {
        const m = prev.month === 0 ? 11 : prev.month - 1;
        const y = prev.month === 0 ? prev.year - 1 : prev.year;
        return { year: y, month: m };
      });
    }
  };

  const handleNext = () => {
    if (viewMode === 'day') {
      setCurrentDate((d) => addDays(d, 1));
    } else if (viewMode === 'week') {
      setCurrentDate((d) => addWeeks(d, 1));
    } else {
      setViewMonth((prev) => {
        const m = prev.month === 11 ? 0 : prev.month + 1;
        const y = prev.month === 11 ? prev.year + 1 : prev.year;
        return { year: y, month: m };
      });
    }
  };

  const handleToday = () => {
    const now = new Date();
    setViewMonth({ year: now.getFullYear(), month: now.getMonth() });
    setCurrentDate(now);
  };

  const handleDayClick = useCallback(
    (day: Date, anchor: Element) => {
      setPopoverDay(day);
      setPopoverAnchor(anchor);
    },
    []
  );

  // Training + events for the popover day
  const trainingForDay = useMemo(() => {
    if (!popoverDay) return [];
    return trainingEvents.filter((t) =>
      sameDay(new Date(t.datum_von), popoverDay)
    );
  }, [trainingEvents, popoverDay]);

  const eventsForDay = useMemo(() => {
    if (!popoverDay) return [];
    const key = popoverDay.toISOString().slice(0, 10);
    return veranstaltungen.filter((ev) => {
      if (selectedKategorie !== 'all' && ev.kategorie_id !== selectedKategorie) return false;
      const start = startOfDay(new Date(ev.datum_von)).toISOString().slice(0, 10);
      const end = startOfDay(new Date(ev.datum_bis)).toISOString().slice(0, 10);
      return key >= start && key <= end;
    });
  }, [veranstaltungen, popoverDay, selectedKategorie]);

  // Filtered lists for list view (filtered by date range)
  const trainingForMonth = useMemo(
    () => {
      const from = parseISO(listFrom);
      const to = parseISO(listTo);
      return trainingEvents.filter((t) => {
        const d = new Date(t.datum_von);
        return d >= startOfDay(from) && d <= new Date(to.getFullYear(), to.getMonth(), to.getDate(), 23, 59, 59);
      });
    },
    [trainingEvents, listFrom, listTo]
  );

  const eventsForMonth = useMemo(
    () => {
      const from = parseISO(listFrom);
      const to = parseISO(listTo);
      return veranstaltungen.filter((ev) => {
        const d = new Date(ev.datum_von);
        return d >= startOfDay(from) && d <= new Date(to.getFullYear(), to.getMonth(), to.getDate(), 23, 59, 59);
      });
    },
    [veranstaltungen, listFrom, listTo]
  );

  // Events for the selected day (day view)
  const trainingForCurrentDay = useMemo(
    () => trainingEvents.filter((t) => sameDay(new Date(t.datum_von), currentDate)),
    [trainingEvents, currentDate]
  );

  const eventsForCurrentDay = useMemo(
    () => veranstaltungen.filter((ev) => {
      const start = startOfDay(new Date(ev.datum_von));
      const end = startOfDay(new Date(ev.datum_bis));
      const cur = startOfDay(currentDate);
      return cur >= start && cur <= end;
    }),
    [veranstaltungen, currentDate]
  );

  // Events for the selected week (week view)
  const currentWeekStartCal = useMemo(
    () => startOfWeek(currentDate, { weekStartsOn: 1 }),
    [currentDate]
  );
  const currentWeekEndCal = useMemo(
    () => endOfWeek(currentDate, { weekStartsOn: 1 }),
    [currentDate]
  );
  const weekDaysCal = useMemo(
    () => eachDayOfInterval({ start: currentWeekStartCal, end: currentWeekEndCal }),
    [currentWeekStartCal, currentWeekEndCal]
  );

  // ── Veranstaltung cancel ─────────────────────────────────────────────────────

  const handleCancelEvent = async () => {
    if (!cancelEventId || cancelEventGrund.trim().length < 5) return;
    setCancelEventLoading(true);
    try {
      await eventsApi.cancelEvent(cancelEventId, cancelEventGrund.trim());
      notification.showSuccess('Veranstaltung wurde abgesagt');
      setCancelEventId(null);
      setCancelEventGrund('');
      loadCalendarData();
    } catch (e: unknown) {
      notification.showError((e as any)?.message || 'Fehler beim Absagen');
    } finally {
      setCancelEventLoading(false);
    }
  };

  const handleDeleteEvent = async () => {
    if (!deleteEventId) return;
    setDeleteEventLoading(true);
    try {
      await eventsApi.deleteEvent(deleteEventId, deleteMode);
      notification.showSuccess('Veranstaltung wurde endgültig gelöscht');
      setDeleteEventId(null);
      loadCalendarData();
    } catch (e: unknown) {
      notification.showError((e as any)?.message || 'Fehler beim Löschen');
    } finally {
      setDeleteEventLoading(false);
    }
  };

  const handleOpenDeleteDialog = useCallback((id: string) => {
    setDeleteMode('all');
    setDeleteEventId(id);
  }, []);

  const handleEventEdit = useCallback(async (ev: VeranstaltungListItem) => {
    if (ev.wiederholung_parent_id) {
      setEditScopeEvent(ev);
      return;
    }
    setVeranstEditing(ev);
    setVeranstFormOpen(true);
  }, []);

  const handleIcalEventOpen = async () => {
    try {
      const { subscribeUrl } = await eventsApi.getCalendarToken();
      setIcalEventUrl(subscribeUrl);
      setIcalEventOpen(true);
    } catch (e: unknown) {
      const msg = e instanceof Error ? e.message : 'Fehler beim Laden des Tokens';
      notification.showError(msg);
    }
  };

  // ── Render ───────────────────────────────────────────────────────────────────

  if (!isFeatureEnabled('kalender')) {
    return <ServiceModePage message="Der Kalender befindet sich aktuell im Wartungsmodus." />;
  }

  return (
    <DashboardLayout>
      <Box sx={{ maxWidth: 1100, mx: 'auto' }}>
        {/* Page header */}
        <Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
          <CalendarIcon color="primary" />
          <Typography variant="h5" sx={{ fontWeight: 700, flexGrow: 1 }}>
            Kalender
          </Typography>
        </Box>

        {canWriteEvents ? (
          <Tabs value={activeTab} onChange={(_, v) => setActiveTab(v)} sx={{ mb: 2, borderBottom: 1, borderColor: 'divider' }}>
            <Tab label="Kalender" value={0} />
            <Tab icon={<SettingsIcon fontSize="small" />} iconPosition="start" label="Einstellungen" value={1} />
          </Tabs>
        ) : null}

        {activeTab === 0 && (
        <>
        {/* ── Calendar ───────────────────────────────────────────── */}
          <Box>
            {/* Controls row */}
            <Box
              sx={{
                display: 'flex',
                alignItems: 'center',
                flexWrap: 'wrap',
                gap: 1,
                mb: 2,
              }}
            >
              {/* View toggle */}
              <ButtonGroup size="small" variant="outlined">
                <Tooltip title="Tagesansicht">
                  <Button
                    onClick={() => setViewMode('day')}
                    variant={viewMode === 'day' ? 'contained' : 'outlined'}
                  >
                    <ViewDayIcon fontSize="small" />
                    {!isMobile && <Box sx={{ ml: 0.5 }}>Tag</Box>}
                  </Button>
                </Tooltip>
                <Tooltip title="Wochenansicht">
                  <Button
                    onClick={() => setViewMode('week')}
                    variant={viewMode === 'week' ? 'contained' : 'outlined'}
                  >
                    <ViewWeekIcon fontSize="small" />
                    {!isMobile && <Box sx={{ ml: 0.5 }}>Woche</Box>}
                  </Button>
                </Tooltip>
                <Tooltip title="Monatsansicht">
                  <Button
                    onClick={() => setViewMode('calendar')}
                    variant={viewMode === 'calendar' ? 'contained' : 'outlined'}
                  >
                    <CalendarIcon fontSize="small" />
                    {!isMobile && <Box sx={{ ml: 0.5 }}>Monat</Box>}
                  </Button>
                </Tooltip>
                <Tooltip title="Listenansicht">
                  <Button
                    onClick={() => setViewMode('list')}
                    variant={viewMode === 'list' ? 'contained' : 'outlined'}
                  >
                    <ListViewIcon fontSize="small" />
                    {!isMobile && <Box sx={{ ml: 0.5 }}>Liste</Box>}
                  </Button>
                </Tooltip>
              </ButtonGroup>

              {/* PDF Export — available in all views */}
              <Tooltip title="Als PDF exportieren">
                <IconButton
                  size="small"
                  onClick={() => generatePdf(
                    viewMonth.year,
                    viewMonth.month,
                    trainingForMonth,
                    eventsForMonth,
                  )}
                >
                  <PdfIcon fontSize="small" />
                </IconButton>
              </Tooltip>

              {/* CSV Import */}
              {canWriteEvents && (
                <Tooltip title="Kalender importieren (CSV)">
                  <IconButton
                    size="small"
                    onClick={() => setCsvImportOpen(true)}
                  >
                    <FileUploadIcon fontSize="small" />
                  </IconButton>
                </Tooltip>
              )}

              {/* iCal subscribe */}
              <Button
                startIcon={<IosShare />}
                onClick={handleIcalEventOpen}
                variant="outlined"
                size="small"
              >
                Kalender
              </Button>
            </Box>

            {/* Category filter — between controls and navigation */}
            {kategorien.length > 0 && (
              <Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap', mb: 1 }}>
                <Chip
                  label="Alle"
                  onClick={() => setSelectedKategorie('all')}
                  color={selectedKategorie === 'all' ? 'primary' : 'default'}
                  variant={selectedKategorie === 'all' ? 'filled' : 'outlined'}
                  size="small"
                />
                {kategorien.map((k) => (
                  <Chip
                    key={k.id}
                    label={k.name}
                    onClick={() =>
                      setSelectedKategorie(selectedKategorie === k.id ? 'all' : k.id)
                    }
                    size="small"
                    sx={{
                      bgcolor: selectedKategorie === k.id ? k.farbe : undefined,
                      color: selectedKategorie === k.id ? 'white' : undefined,
                    }}
                    variant={selectedKategorie === k.id ? 'filled' : 'outlined'}
                  />
                ))}
              </Box>
            )}

            {/* Navigation */}
            <Box sx={{ display: 'flex', alignItems: 'center', mb: 2, gap: 1 }}>
              <IconButton onClick={handlePrev} size="small">
                <ChevronLeft />
              </IconButton>
              <Typography
                variant="h6"
                sx={{ flexGrow: 1, textAlign: 'center', fontWeight: 600 }}
              >
                {viewMode === 'day'
                  ? formatDateLong(currentDate)
                  : viewMode === 'week'
                    ? `KW ${fnsFormat(currentWeekStartCal, 'w')} — ${fnsFormat(currentWeekStartCal, 'dd.MM.')} – ${fnsFormat(currentWeekEndCal, 'dd.MM.yyyy')}`
                    : `${MONTH_LABELS[viewMonth.month]} ${viewMonth.year}`}
              </Typography>
              <Button
                size="small"
                startIcon={<TodayIcon fontSize="small" />}
                onClick={handleToday}
                sx={{ minWidth: 'auto' }}
              >
                {!isMobile && 'Heute'}
              </Button>
              <IconButton onClick={handleNext} size="small">
                <ChevronRight />
              </IconButton>
            </Box>

            {calError && (
              <Alert severity="error" sx={{ mb: 2 }} onClose={() => setCalError(null)}>
                {calError}
              </Alert>
            )}

            {/* Calendar / List / Day / Week body */}
            {calLoading ? (
              <Skeleton
                variant="rectangular"
                height={isMobile ? 320 : 480}
                sx={{ borderRadius: 2 }}
              />
            ) : viewMode === 'day' ? (
              /* ── Day View ── */
              <Paper elevation={1} sx={{ p: 2 }}>
                {trainingForCurrentDay.length === 0 && eventsForCurrentDay.length === 0 ? (
                  <Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', py: 4 }}>
                    Keine Einträge an diesem Tag.
                  </Typography>
                ) : (
                  <List disablePadding>
                    {trainingForCurrentDay.length > 0 && (
                      <Typography variant="overline" sx={{ px: 0.5, color: 'text.secondary' }}>
                        Dienste
                      </Typography>
                    )}
                    {trainingForCurrentDay.map((t) => (
                      <ListItem
                        key={`t-${t.id}`}
                        sx={{
                          borderLeft: `4px solid ${TYP_DOT_COLOR[t.typ]}`,
                          borderRadius: 1,
                          mb: 0.5,
                          cursor: 'pointer',
                          '&:hover': { bgcolor: 'action.hover' },
                          opacity: t.abgesagt ? 0.55 : 1,
                        }}
                        onClick={() => navigate(`/training/${t.id}`)}
                      >
                        <ListItemText
                          primary={
                            <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
                              {t.pflichtveranstaltung && <StarIcon sx={{ fontSize: 14, color: 'warning.main' }} />}
                              <Typography variant="body2" sx={{ fontWeight: t.pflichtveranstaltung ? 700 : 400, textDecoration: t.abgesagt ? 'line-through' : 'none' }}>
                                {t.titel}
                              </Typography>
                            </Box>
                          }
                          secondary={`${formatTime(t.datum_von)} – ${formatTime(t.datum_bis)} Uhr · ${t.typ}`}
                        />
                        <RsvpDot status={t.eigener_status} />
                      </ListItem>
                    ))}
                    {trainingForCurrentDay.length > 0 && eventsForCurrentDay.length > 0 && <Divider sx={{ my: 1 }} />}
                    {eventsForCurrentDay.length > 0 && (
                      <Typography variant="overline" sx={{ px: 0.5, color: 'text.secondary' }}>
                        Veranstaltungen
                      </Typography>
                    )}
                    {eventsForCurrentDay
                      .filter((ev) => selectedKategorie === 'all' || ev.kategorie_id === selectedKategorie)
                      .map((ev) => (
                        <ListItem
                          key={`e-${ev.id}`}
                          sx={{
                            borderLeft: `4px solid ${ev.kategorie_farbe ?? '#1976d2'}`,
                            borderRadius: 1,
                            mb: 0.5,
                            opacity: ev.abgesagt ? 0.55 : 1,
                          }}
                        >
                          <ListItemText
                            primary={
                              <Typography variant="body2" sx={{ fontWeight: 500, textDecoration: ev.abgesagt ? 'line-through' : 'none' }}>
                                {ev.titel}
                                {ev.abgesagt && <Chip label="Abgesagt" size="small" color="error" variant="outlined" sx={{ ml: 0.5, fontSize: '0.6rem', height: 16 }} />}
                              </Typography>
                            }
                            secondary={
                              <>
                                {ev.ganztaegig ? 'Ganztägig' : `${formatTime(ev.datum_von)} – ${formatTime(ev.datum_bis)} Uhr`}
                                {ev.ort && ` · ${ev.ort}`}
                              </>
                            }
                          />
                        </ListItem>
                      ))}
                  </List>
                )}
              </Paper>
            ) : viewMode === 'week' ? (
              /* ── Week View ── */
              <Paper elevation={1} sx={{ p: 1, overflowX: 'auto' }}>
                <Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: '2px', minWidth: 700 }}>
                  {weekDaysCal.map((day) => {
                    const isToday = sameDay(day, new Date());
                    const dayTraining = trainingEvents.filter((t) => sameDay(new Date(t.datum_von), day));
                    const dayEvents = veranstaltungen.filter((ev) => {
                      if (selectedKategorie !== 'all' && ev.kategorie_id !== selectedKategorie) return false;
                      const start = startOfDay(new Date(ev.datum_von));
                      const end = startOfDay(new Date(ev.datum_bis));
                      const cur = startOfDay(day);
                      return cur >= start && cur <= end;
                    });

                    return (
                      <Box
                        key={day.toISOString()}
                        sx={{
                          minHeight: 120,
                          border: '1px solid',
                          borderColor: isToday ? 'primary.main' : 'divider',
                          borderRadius: 1,
                          p: 0.5,
                          bgcolor: isToday ? 'primary.main' : 'background.paper',
                        }}
                      >
                        <Typography
                          variant="caption"
                          sx={{
                            display: 'block',
                            textAlign: 'center',
                            fontWeight: 600,
                            color: isToday ? 'primary.contrastText' : 'text.primary',
                            mb: 0.5,
                          }}
                        >
                          {fnsFormat(day, 'EEE dd.MM.', { locale: de })}
                        </Typography>
                        {dayTraining.map((t) => (
                          <Chip
                            key={`t-${t.id}`}
                            label={t.titel}
                            size="small"
                            onClick={() => navigate(`/training/${t.id}`)}
                            sx={{
                              fontSize: '0.6rem',
                              height: 18,
                              mb: '2px',
                              width: '100%',
                              justifyContent: 'flex-start',
                              bgcolor: t.abgesagt ? 'action.disabledBackground' : TYP_DOT_COLOR[t.typ],
                              color: t.abgesagt ? 'text.disabled' : 'white',
                              textDecoration: t.abgesagt ? 'line-through' : 'none',
                              cursor: 'pointer',
                            }}
                          />
                        ))}
                        {dayEvents.map((ev) => (
                          <Chip
                            key={`e-${ev.id}`}
                            label={ev.titel}
                            size="small"
                            sx={{
                              fontSize: '0.6rem',
                              height: 18,
                              mb: '2px',
                              width: '100%',
                              justifyContent: 'flex-start',
                              bgcolor: ev.abgesagt ? 'action.disabledBackground' : (ev.kategorie_farbe ?? '#1976d2'),
                              color: ev.abgesagt ? 'text.disabled' : 'white',
                              textDecoration: ev.abgesagt ? 'line-through' : 'none',
                            }}
                          />
                        ))}
                      </Box>
                    );
                  })}
                </Box>
              </Paper>
            ) : viewMode === 'calendar' ? (
              <Paper elevation={1} sx={{ p: 1 }}>
                <MonthCalendar
                  year={viewMonth.year}
                  month={viewMonth.month}
                  trainingEvents={trainingEvents}
                  veranstaltungen={veranstaltungen}
                  selectedKategorie={selectedKategorie}
                  onDayClick={handleDayClick}
                />
              </Paper>
            ) : (
              <>
                {/* Date range inputs for list view */}
                <Box sx={{ display: 'flex', gap: 1, mb: 2, flexWrap: 'wrap', alignItems: 'center' }}>
                  <TextField
                    type="date"
                    label="Von"
                    size="small"
                    value={listFrom}
                    onChange={(e) => setListFrom(e.target.value)}
                    InputLabelProps={{ shrink: true }}
                    sx={{ width: 170 }}
                  />
                  <TextField
                    type="date"
                    label="Bis"
                    size="small"
                    value={listTo}
                    onChange={(e) => setListTo(e.target.value)}
                    InputLabelProps={{ shrink: true }}
                    sx={{ width: 170 }}
                  />
                </Box>
                <Paper elevation={1} sx={{ px: 1 }}>
                  <CombinedListView
                    trainingEvents={trainingForMonth}
                    veranstaltungen={eventsForMonth}
                    selectedKategorie={selectedKategorie}
                    canWriteEvents={canWriteEvents}
                    onTrainingClick={(id) => navigate(`/training/${id}`)}
                    onEventEdit={handleEventEdit}
                    onEventCancel={(id) => {
                      setCancelEventId(id);
                      setCancelEventGrund('');
                    }}
                    onEventDelete={handleOpenDeleteDialog}
                  />
                </Paper>
              </>
            )}

            {/* FAB: Create Veranstaltung */}
            {canWriteEvents && (
              <ChatAwareFab
                onClick={() => {
                  setVeranstEditing(null);
                  setVeranstFormOpen(true);
                }}
              >
                <Add />
              </ChatAwareFab>
            )}

            {/* Day Popover */}
            <DayPopover
              anchorEl={popoverAnchor}
              day={popoverDay}
              trainingForDay={trainingForDay}
              eventsForDay={eventsForDay}
              canWriteEvents={canWriteEvents}
              onClose={() => setPopoverAnchor(null)}
              onTrainingClick={(id) => navigate(`/training/${id}`)}
              onEventEdit={handleEventEdit}
              onEventDelete={handleOpenDeleteDialog}
            />

            {/* Veranstaltung Form Dialog */}
            <VeranstaltungFormDialog
              open={veranstFormOpen}
              onClose={() => { setVeranstFormOpen(false); setVeranstEditing(null); }}
              onSaved={loadCalendarData}
              editingEvent={veranstEditing}
              kategorien={kategorien}
              groups={groups}
            />

            {/* Veranstaltung Cancel Dialog */}
            <Dialog
              open={Boolean(cancelEventId)}
              onClose={() => setCancelEventId(null)}
              maxWidth="xs"
              fullWidth
            >
              <DialogTitle>Veranstaltung absagen</DialogTitle>
              <DialogContent>
                <DialogContentText sx={{ mb: 2 }}>
                  Bitte gib einen Grund für die Absage an (mind. 5 Zeichen).
                </DialogContentText>
                <TextField
                  fullWidth
                  multiline
                  rows={3}
                  label="Absagegrund"
                  value={cancelEventGrund}
                  onChange={(e) => setCancelEventGrund(e.target.value)}
                  autoFocus
                />
              </DialogContent>
              <DialogActions>
                <Button onClick={() => setCancelEventId(null)}>Abbrechen</Button>
                <Button
                  variant="contained"
                  color="error"
                  onClick={handleCancelEvent}
                  disabled={cancelEventGrund.trim().length < 5 || cancelEventLoading}
                >
                  {cancelEventLoading ? <CircularProgress size={20} /> : 'Absagen'}
                </Button>
              </DialogActions>
            </Dialog>

            {/* Endgültig löschen Dialog */}
            {(() => {
              const deleteEvent = veranstaltungen.find(e => e.id === deleteEventId) ?? null;
              const isRecurring = Boolean(deleteEvent?.wiederholung || deleteEvent?.wiederholung_parent_id);
              return (
                <Dialog
                  open={Boolean(deleteEventId)}
                  onClose={() => setDeleteEventId(null)}
                  maxWidth="xs"
                  fullWidth
                >
                  <DialogTitle>Veranstaltung endgültig löschen?</DialogTitle>
                  <DialogContent>
                    <DialogContentText>
                      Diese Aktion kann nicht rückgängig gemacht werden.
                    </DialogContentText>
                    {isRecurring && (
                      <RadioGroup
                        value={deleteMode}
                        onChange={(e) => setDeleteMode(e.target.value as 'single' | 'future' | 'all')}
                        sx={{ mt: 2 }}
                      >
                        <FormControlLabel value="single" control={<Radio />} label="Nur diesen Termin" />
                        <FormControlLabel value="future" control={<Radio />} label="Diesen und alle folgenden Termine" />
                        <FormControlLabel value="all" control={<Radio />} label="Alle Termine der Serie" />
                      </RadioGroup>
                    )}
                  </DialogContent>
                  <DialogActions>
                    <Button onClick={() => setDeleteEventId(null)} disabled={deleteEventLoading}>
                      Abbrechen
                    </Button>
                    <Button
                      variant="contained"
                      color="error"
                      onClick={handleDeleteEvent}
                      disabled={deleteEventLoading}
                    >
                      {deleteEventLoading ? <CircularProgress size={20} /> : 'Endgültig löschen'}
                    </Button>
                  </DialogActions>
                </Dialog>
              );
            })()}

            {/* Edit scope dialog for recurring event instances */}
            <Dialog
              open={Boolean(editScopeEvent)}
              onClose={() => setEditScopeEvent(null)}
              maxWidth="xs"
              fullWidth
            >
              <DialogTitle>Wiederkehrenden Termin bearbeiten</DialogTitle>
              <DialogContent>
                <DialogContentText>
                  Welche Termine möchtest du bearbeiten?
                </DialogContentText>
              </DialogContent>
              <DialogActions sx={{ flexDirection: 'column', alignItems: 'stretch', gap: 1, pb: 2, px: 2 }}>
                <Button
                  variant="outlined"
                  onClick={() => {
                    if (editScopeEvent) {
                      setVeranstEditing(editScopeEvent);
                      setVeranstFormOpen(true);
                    }
                    setEditScopeEvent(null);
                  }}
                >
                  Nur diesen Termin bearbeiten
                </Button>
                <Button
                  variant="contained"
                  onClick={async () => {
                    if (!editScopeEvent?.wiederholung_parent_id) return;
                    try {
                      const parent = await eventsApi.getById(editScopeEvent.wiederholung_parent_id);
                      setVeranstEditing({
                        id: parent.id,
                        titel: parent.titel,
                        beschreibung: parent.beschreibung,
                        datum_von: parent.datum_von,
                        datum_bis: parent.datum_bis,
                        ganztaegig: parent.ganztaegig,
                        ort: parent.ort,
                        kategorie_id: parent.kategorie_id,
                        kategorie_name: parent.kategorie_name,
                        kategorie_farbe: parent.kategorie_farbe,
                        kategorie_icon: parent.kategorie_icon,
                        wiederholung: parent.wiederholung,
                        wiederholung_parent_id: null,
                        alle_gruppen: parent.alle_gruppen,
                        zielgruppen: parent.zielgruppen ?? [],
                        anmeldung_erforderlich: parent.anmeldung_erforderlich,
                        abgesagt: parent.abgesagt,
                      });
                      setVeranstFormOpen(true);
                    } catch {
                      notification.showError('Fehler beim Laden der Serie');
                    }
                    setEditScopeEvent(null);
                  }}
                >
                  Alle Termine der Serie bearbeiten
                </Button>
                <Button onClick={() => setEditScopeEvent(null)}>Abbrechen</Button>
              </DialogActions>
            </Dialog>

            {/* iCal Event subscription dialog */}
            <Dialog open={icalEventOpen} onClose={() => setIcalEventOpen(false)} maxWidth="sm" fullWidth>
              <DialogTitle>Kalender abonnieren</DialogTitle>
              <DialogContent>
                <Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
                  Abonniere den Dienste- & Veranstaltungskalender in deiner Kalenderanwendung. Kopiere die URL und füge sie als neuen
                  Kalender (per URL) in Apple Kalender, Google Kalender oder Outlook ein.
                </Typography>
                <TextField
                  fullWidth
                  value={icalEventUrl}
                  InputProps={{
                    readOnly: true,
                    endAdornment: (
                      <IconButton
                        onClick={() => {
                          navigator.clipboard.writeText(icalEventUrl);
                          notification.showSuccess('URL kopiert!');
                        }}
                      >
                        <CopyIcon />
                      </IconButton>
                    ),
                  }}
                />
              </DialogContent>
              <DialogActions>
                <Button onClick={() => setIcalEventOpen(false)}>Schließen</Button>
                <Button
                  variant="outlined"
                  startIcon={<FileDownloadIcon />}
                  component="a"
                  href={icalEventUrl}
                  download="veranstaltungen.ics"
                >
                  Herunterladen
                </Button>
              </DialogActions>
            </Dialog>
          </Box>
        </>
        )}

        {activeTab === 1 && canWriteEvents && (
          <SettingsTab kategorien={kategorien} onKategorienChange={setKategorien} />
        )}

      </Box>

      {/* CSV Import Dialog */}
      <CsvImportDialog
        open={csvImportOpen}
        onClose={() => setCsvImportOpen(false)}
        onImported={() => {
          setCsvImportOpen(false);
          loadCalendarData();
        }}
      />
    </DashboardLayout>
  );
}
