<template>
  <div class="calendar-page">
    <header class="calendar-header">
      <div>
        <span class="calendar-eyebrow">{{ $t('calendar.eyebrow') }}</span>
        <h2>{{ $t('calendar.title') }}</h2>
        <p>{{ $t('calendar.description') }}</p>
      </div>
      <div class="calendar-actions">
        <button type="button" class="calendar-nav-button" @click="goToPreviousMonth">‹</button>
        <button type="button" class="calendar-today-button" @click="goToToday">{{ $t('calendar.today') }}</button>
        <button type="button" class="calendar-nav-button" @click="goToNextMonth">›</button>
      </div>
    </header>

    <section class="calendar-toolbar">
      <strong>{{ monthLabel }}</strong>
      <div class="calendar-legend">
        <button
          v-for="type in eventTypes"
          :key="type.key"
          type="button"
          class="legend-item"
          :class="[`legend-${type.key}`, { inactive: !activeTypes[type.key] }]"
          @click="toggleType(type.key)"
        >
          {{ type.label }} <span>{{ eventCounts[type.key] }}</span>
        </button>
      </div>
    </section>

    <details v-if="currentClub" class="calendar-options" :open="optionsOpen">
      <summary class="calendar-options-summary" @click.prevent="optionsOpen = !optionsOpen">
        <span>{{ $t('calendar.options.title') }}</span>
        <small>{{ $t('calendar.options.subtitle') }}</small>
      </summary>
      <div class="calendar-options-body">
        <section class="training-cancellation-panel">
          <div>
            <h3>{{ $t('calendar.cancellation.title') }}</h3>
            <p>{{ $t('calendar.cancellation.description') }}</p>
          </div>
          <form class="training-cancellation-form" @submit.prevent="saveTrainingCancellation">
            <label>
              <span>{{ $t('calendar.cancellation.date') }}</span>
              <input v-model="cancellationForm.startDate" type="date" required />
            </label>
            <label>
              <span>{{ $t('calendar.cancellation.untilOptional') }}</span>
              <input v-model="cancellationForm.endDate" type="date" />
            </label>
            <label>
              <span>{{ $t('calendar.cancellation.trainingGroups') }}</span>
              <select v-model="cancellationForm.trainingGroupIds" multiple>
                <option
                  v-for="group in trainingGroupsWithTimes"
                  :key="`cancel-group-${group.id}`"
                  :value="String(group.id)"
                >
                  {{ group.name }}
                </option>
              </select>
            </label>
            <input v-model="cancellationForm.reason" type="text" :placeholder="$t('calendar.cancellation.reasonPlaceholder')" />
            <button type="submit" :disabled="cancellationSaving">
              {{ cancellationSaving ? $t('calendar.cancellation.saving') : $t('calendar.cancellation.submit') }}
            </button>
          </form>
          <div v-if="visibleTrainingCancellations.length" class="training-cancellation-list">
            <button
              v-for="cancellation in visibleTrainingCancellations"
              :key="`cancel-${cancellation.cancellationId}`"
              type="button"
              class="training-cancellation-item"
              @click="openTrainingCancellationDeleteDialog(cancellation)"
              :title="$t('common.delete')"
            >
              <strong>{{ formatShortDate(cancellation.date) }}</strong>
              <span>{{ cancellation.title }}</span>
              <small>{{ $t('common.delete') }}</small>
            </button>
          </div>
        </section>

        <section class="custom-event-panel">
          <div>
            <h3>{{ $t('calendar.customEvent.title') }}</h3>
            <p>{{ $t('calendar.customEvent.description') }}</p>
          </div>
          <form class="custom-event-form" @submit.prevent="saveCustomEvent">
            <input v-model="customEventForm.title" type="text" :placeholder="$t('calendar.customEvent.titlePlaceholder')" required />
            <input v-model="customEventForm.startDate" type="date" required />
            <input v-model="customEventForm.endDate" type="date" />
            <input v-model="customEventForm.category" type="text" :placeholder="$t('calendar.customEvent.categoryPlaceholder')" />
            <button type="submit" :disabled="customEventSaving">
              {{ customEventSaving ? $t('calendar.customEvent.saving') : $t('calendar.customEvent.submit') }}
            </button>
          </form>
          <div v-if="visibleCustomEvents.length" class="custom-event-list">
            <button
              v-for="event in visibleCustomEvents"
              :key="`ce-${event.customEventId}`"
              type="button"
              class="custom-event-item"
              @click="deleteCustomEvent(event)"
              :title="$t('common.delete')"
            >
              <strong>{{ formatEventDate(event) }}</strong>
              <span>{{ event.title }}</span>
              <small>{{ $t('common.delete') }}</small>
            </button>
          </div>
        </section>
      </div>
    </details>

    <section v-if="sourceWarnings.length" class="calendar-state calendar-state-warning">
      {{ sourceWarnings.join(' · ') }}
    </section>
    <section v-if="loading" class="calendar-state">{{ $t('calendar.loading') }}</section>
    <section v-else-if="error" class="calendar-state calendar-state-error">{{ error }}</section>

    <section v-if="!currentClub" class="calendar-state">{{ $t('calendar.noClub') }}</section>

    <section v-else class="calendar-grid" :aria-label="$t('calendar.title')">
      <div v-for="day in weekdays" :key="day" class="calendar-weekday">{{ day }}</div>
      <article
        v-for="day in calendarDays"
        :key="day.key"
        class="calendar-day"
        :class="{ 'outside-month': !day.isCurrentMonth, today: day.isToday }"
      >
        <div class="calendar-day-number">{{ day.dayNumber }}</div>
        <div class="calendar-events">
          <button
            v-for="event in day.events"
            :key="event.id"
            type="button"
            class="calendar-event"
            :class="`event-${event.type}`"
            @click="openEvent(event)"
          >
            <span class="event-time" v-if="event.time">{{ event.time }}</span>
            <span class="event-title">{{ event.title }}</span>
            <span class="event-subtitle" v-if="event.subtitle">{{ event.subtitle }}</span>
          </button>
        </div>
      </article>
    </section>

    <section class="calendar-agenda">
      <h3>{{ $t('calendar.agendaTitle') }}</h3>
      <p v-if="visibleEvents.length === 0" class="agenda-empty">{{ $t('calendar.agendaEmpty') }}</p>
      <button
        v-for="event in visibleEvents"
        :key="`agenda-${event.id}`"
        type="button"
        class="agenda-item"
        :class="`agenda-${event.type}`"
        @click="openEvent(event)"
      >
        <span class="agenda-date">{{ formatEventDate(event) }}</span>
        <span class="agenda-main">
          <strong>{{ event.title }}</strong>
          <small>{{ event.subtitle }}</small>
        </span>
        <span class="agenda-time">{{ event.time }}</span>
      </button>
    </section>

    <BaseDialog
      v-model="quickCancellationDialog.isOpen"
      :title="$t('calendar.quickCancellation.title')"
      size="small"
      :close-on-overlay="false"
      @close="closeQuickCancellationDialog"
    >
      <div class="quick-cancellation-dialog">
        <p>
          {{ $t('calendar.quickCancellation.message', { title: quickCancellationDialog.eventTitle }) }}
        </p>
        <label v-if="quickCancellationDialog.isRange" class="quick-cancellation-checkbox">
          <input v-model="quickCancellationDialog.useRange" type="checkbox" />
          <span>
            {{ $t('calendar.quickCancellation.useWholeRange', {
              start: quickCancellationDialog.startDate,
              end: quickCancellationDialog.endDate
            }) }}
          </span>
        </label>
        <label class="quick-cancellation-groups">
          <span>{{ $t('calendar.quickCancellation.trainingGroups') }}</span>
          <select v-model="quickCancellationDialog.trainingGroupIds" multiple>
            <option
              v-for="group in trainingGroupsWithTimes"
              :key="`quick-cancel-group-${group.id}`"
              :value="String(group.id)"
            >
              {{ group.name }}
            </option>
          </select>
        </label>
        <p v-if="!trainingGroupsWithTimes.length" class="quick-cancellation-hint">
          {{ $t('calendar.quickCancellation.noGroups') }}
        </p>
      </div>
      <template #footer>
        <button type="button" class="dialog-secondary-button" @click="closeQuickCancellationDialog">
          {{ $t('common.cancel') }}
        </button>
        <button
          type="button"
          class="dialog-primary-button"
          :disabled="quickCancellationDialog.trainingGroupIds.length === 0"
          @click="confirmQuickCancellation"
        >
          {{ $t('calendar.quickCancellation.confirm') }}
        </button>
      </template>
    </BaseDialog>

    <BaseDialog
      v-model="cancellationDeleteDialog.isOpen"
      :title="$t('calendar.cancelCancellation.title')"
      size="small"
      :close-on-overlay="false"
      @close="closeTrainingCancellationDeleteDialog"
    >
      <div class="quick-cancellation-dialog">
        <p>
          {{ $t('calendar.cancelCancellation.message', {
            title: cancellationDeleteDialog.title,
            date: cancellationDeleteDialog.dateLabel
          }) }}
        </p>
        <p v-if="cancellationDeleteDialog.subtitle" class="quick-cancellation-hint">
          {{ cancellationDeleteDialog.subtitle }}
        </p>
      </div>
      <template #footer>
        <button type="button" class="dialog-secondary-button" @click="closeTrainingCancellationDeleteDialog">
          {{ $t('common.cancel') }}
        </button>
        <button type="button" class="dialog-danger-button" @click="confirmTrainingCancellationDelete">
          {{ $t('calendar.cancelCancellation.confirm') }}
        </button>
      </template>
    </BaseDialog>
  </div>
</template>

<script>
import { mapGetters } from 'vuex';
import apiClient from '../apiClient';
import BaseDialog from '../components/BaseDialog.vue';

const WEEKDAY_KEYS = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];

export default {
  name: 'CalendarView',
  components: {
    BaseDialog,
  },
  data() {
    const today = new Date();
    return {
      cursor: new Date(today.getFullYear(), today.getMonth(), 1),
      events: [],
      loading: false,
      error: '',
      sourceErrors: [],
      activeTypes: {
        training: true,
        tournament: true,
        officialTournament: true,
        match: true,
        holiday: true,
        schoolHoliday: true,
        trainingCancellation: true,
        customEvent: true
      },
      cancellationSaving: false,
      cancellationForm: {
        startDate: '',
        endDate: '',
        reason: '',
        trainingGroupIds: []
      },
      customEventSaving: false,
      customEventForm: {
        title: '',
        startDate: '',
        endDate: '',
        category: ''
      },
      optionsOpen: false,
      quickCancellationDialog: {
        isOpen: false,
        eventTitle: '',
        startDate: '',
        endDate: '',
        isRange: false,
        useRange: false,
        reason: '',
        trainingGroupIds: [],
      },
      cancellationDeleteDialog: {
        isOpen: false,
        cancellation: null,
        title: '',
        subtitle: '',
        dateLabel: '',
      },
      plannedTrainingByDateKey: {},
      plannedTrainingGroupIdsByDateKey: {},
      trainingGroups: [],
    };
  },
  computed: {
    ...mapGetters(['currentClub', 'currentClubName']),
    weekdays() {
      return WEEKDAY_KEYS.map(key => this.$t(`calendar.weekdays.${key}`));
    },
    eventTypes() {
      return [
        { key: 'training', label: this.$t('calendar.legend.training') },
        { key: 'tournament', label: this.$t('calendar.legend.tournament') },
        { key: 'officialTournament', label: this.$t('calendar.legend.officialTournament') },
        { key: 'match', label: this.$t('calendar.legend.match') },
        { key: 'holiday', label: this.$t('calendar.legend.holiday') },
        { key: 'schoolHoliday', label: this.$t('calendar.legend.schoolHoliday') },
        { key: 'trainingCancellation', label: this.$t('calendar.legend.trainingCancellation') },
        { key: 'customEvent', label: this.$t('calendar.legend.customEvent') }
      ];
    },
    monthLabel() {
      return this.cursor.toLocaleDateString(this.$i18n?.locale || 'de-DE', { month: 'long', year: 'numeric' });
    },
    displayedYear() {
      return this.cursor.getFullYear();
    },
    visibleEventPool() {
      return this.events.filter(event => this.activeTypes[event.type] !== false);
    },
    visibleEvents() {
      const month = this.cursor.getMonth();
      const year = this.cursor.getFullYear();
      const monthStart = new Date(year, month, 1);
      const monthEnd = new Date(year, month + 1, 0);
      return this.visibleEventPool
        .filter(event => this.isEventInRange(event, monthStart, monthEnd))
        .filter(event => this.shouldShowEventOnDate(event, this.toDateKey(event.date)))
        .sort((a, b) => a.startsAt - b.startsAt);
    },
    eventCounts() {
      return this.events.reduce((counts, event) => {
        counts[event.type] = (counts[event.type] || 0) + 1;
        return counts;
      }, { training: 0, tournament: 0, officialTournament: 0, match: 0, holiday: 0, schoolHoliday: 0, trainingCancellation: 0, customEvent: 0 });
    },
    visibleTrainingCancellations() {
      const month = this.cursor.getMonth();
      const year = this.cursor.getFullYear();
      return this.events
        .filter(event => event.type === 'trainingCancellation')
        .filter(event => event.date.getFullYear() === year && event.date.getMonth() === month)
        .sort((a, b) => a.startsAt - b.startsAt);
    },
    visibleCustomEvents() {
      const month = this.cursor.getMonth();
      const year = this.cursor.getFullYear();
      return this.events
        .filter(event => event.type === 'customEvent')
        .filter(event => event.date.getFullYear() === year && event.date.getMonth() === month)
        .sort((a, b) => a.startsAt - b.startsAt);
    },
    sourceWarnings() {
      return this.sourceErrors.map(source => this.$t('calendar.sourceWarning', { source }));
    },
    trainingGroupsWithTimes() {
      return this.trainingGroups.filter(group => Array.isArray(group.trainingTimes) && group.trainingTimes.length > 0);
    },
    calendarDays() {
      const year = this.cursor.getFullYear();
      const month = this.cursor.getMonth();
      const firstOfMonth = new Date(year, month, 1);
      const firstWeekday = (firstOfMonth.getDay() + 6) % 7;
      const gridStart = new Date(year, month, 1 - firstWeekday);
      const todayKey = this.toDateKey(new Date());

      return Array.from({ length: 42 }, (_, index) => {
        const date = new Date(gridStart);
        date.setDate(gridStart.getDate() + index);
        const key = this.toDateKey(date);
        return {
          key,
          date,
          dayNumber: date.getDate(),
          isCurrentMonth: date.getMonth() === month,
          isToday: key === todayKey,
          events: this.visibleEventPool
            .filter(event => this.isEventOnDate(event, date))
            .filter(event => this.shouldShowEventOnDate(event, key))
            .sort((a, b) => a.startsAt - b.startsAt)
        };
      });
    }
  },
  watch: {
    currentClub: {
      immediate: true,
      handler() {
        this.loadCalendarEvents();
      }
    },
    displayedYear() {
      this.loadCalendarEvents();
    }
  },
  methods: {
    isOurTeam(teamName) {
      if (!teamName || !this.currentClubName) return false;
      return String(teamName).startsWith(this.currentClubName);
    },
    async loadCalendarEvents() {
      if (!this.currentClub) {
        this.events = [];
        return;
      }

      this.loading = true;
      this.error = '';
      this.sourceErrors = [];

      const sources = await Promise.allSettled([
        this.loadSource(this.$t('calendar.sources.trainingDays'), () => this.loadTrainingEvents()),
        this.loadSource(this.$t('calendar.sources.trainingTimes'), () => this.loadRecurringTrainingEvents()),
        this.loadSource(this.$t('calendar.sources.trainingCancellations'), () => this.loadTrainingCancellationEvents()),
        this.loadSource(this.$t('calendar.sources.customEvents'), () => this.loadCustomEvents()),
        this.loadSource(this.$t('calendar.sources.tournaments'), () => this.loadTournamentEvents()),
        this.loadSource(this.$t('calendar.sources.officialTournaments'), () => this.loadOfficialTournamentEvents()),
        this.loadSource(this.$t('calendar.sources.matches'), () => this.loadMatchEvents()),
        this.loadSource(this.$t('calendar.sources.holidays'), () => this.loadHolidayEvents())
      ]);

      const loadedEvents = sources
        .filter(result => result.status === 'fulfilled')
        .flatMap(result => result.value.events)
        .filter(event => event.date && !Number.isNaN(event.date.getTime()));
      const calendarEvents = this.enrichTrainingCancellationSubtitles(loadedEvents);

      this.plannedTrainingByDateKey = calendarEvents
        .filter(event => event.type === 'training')
        .reduce((map, event) => {
          map[this.toDateKey(event.date)] = true;
          return map;
        }, {});
      this.plannedTrainingGroupIdsByDateKey = calendarEvents
        .filter(event => event.type === 'training' && event.trainingGroupId)
        .reduce((map, event) => {
          const dateKey = this.toDateKey(event.date);
          if (!map[dateKey]) map[dateKey] = {};
          map[dateKey][String(event.trainingGroupId)] = true;
          return map;
        }, {});

      const cancellationEvents = calendarEvents.filter(event => event.type === 'trainingCancellation');
      const afterCancellations = calendarEvents.filter(event => (
        !event.isRecurringTraining || !this.isRecurringTrainingCancelled(event, cancellationEvents)
      ));
      this.events = this.mergeRecurringTrainingSlots(afterCancellations);
      this.sourceErrors = sources
        .filter(result => result.status === 'rejected')
        .map(result => result.reason?.source)
        .filter(Boolean);

      if (sources.every(result => result.status === 'rejected')) {
        this.error = this.$t('calendar.loadError');
      }

      this.loading = false;
    },
    normalizeTrainingTimeKey(time) {
      return String(time || '')
        .trim()
        .replace(/[\u2013\u2012\u2212]/g, '-')
        .replace(/\s*-\s*/g, '-')
        .replace(/\s+/g, '');
    },
    /**
     * Alle Trainings-Einträge (Tagebuch + regelmäßige Zeiten) mit gleichem Kalendertag und gleichem
     * Zeitfenster-Text zu einem Eintrag zusammenführen (z. B. Dublette „Training“ + Gruppen).
     */
    mergeRecurringTrainingSlots(events) {
      const genericTitle = (t) => !t || /^training$/i.test(String(t).trim());
      const slotMap = new Map();
      const passthrough = [];
      for (const e of events) {
        if (e.type !== 'training' || !e.date) {
          passthrough.push(e);
          continue;
        }
        const timeKey = this.normalizeTrainingTimeKey(e.time);
        if (!timeKey) {
          passthrough.push(e);
          continue;
        }
        const dk = this.toDateKey(e.date);
        const slotKey = `${dk}|${timeKey}`;
        if (!slotMap.has(slotKey)) {
          slotMap.set(slotKey, []);
        }
        slotMap.get(slotKey).push(e);
      }
      const mergedSlots = [];
      for (const [slotKey, list] of slotMap) {
        if (list.length === 1) {
          mergedSlots.push(list[0]);
          continue;
        }
        const sorted = list.slice().sort((a, b) => a.startsAt - b.startsAt);
        const base = sorted[0];
        const rawTitles = [...new Set(sorted.map((x) => String(x.title || '').trim()).filter(Boolean))];
        const specific = rawTitles.filter((t) => !genericTitle(t));
        const titleJoined = specific.length ? specific.join(' · ') : (rawTitles.join(' · ') || base.title);
        const safeIdKey = slotKey.replace(/\|/g, '-');
        const hasRecurring = sorted.some((x) => x.isRecurringTraining);
        const recurringSubtitle = this.$t('calendar.recurringTrainingTime');
        const otherSubtitles = [...new Set(
          sorted.map((x) => String(x.subtitle || '').trim()).filter((s) => s && s !== recurringSubtitle)
        )];
        let subtitleJoined = '';
        if (hasRecurring) {
          subtitleJoined = otherSubtitles.length
            ? `${recurringSubtitle} · ${otherSubtitles.join(' · ')}`
            : recurringSubtitle;
        } else {
          subtitleJoined = otherSubtitles.join(' · ') || String(base.subtitle || '').trim() || '';
        }
        mergedSlots.push({
          ...base,
          id: `training-merged-${safeIdKey}`,
          title: titleJoined,
          subtitle: subtitleJoined,
          startsAt: Math.min(...sorted.map((x) => x.startsAt)),
          isRecurringTraining: hasRecurring
        });
      }
      return [...passthrough, ...mergedSlots];
    },
    async loadSource(source, loader) {
      try {
        const events = await loader();
        return { source, events };
      } catch (error) {
        error.source = error.source || source;
        throw error;
      }
    },
    enrichTrainingCancellationSubtitles(events) {
      return events.map(event => (
        event.type === 'trainingCancellation'
          ? { ...event, subtitle: this.formatCancellationSubtitle(event.trainingGroupIds) }
          : event
      ));
    },
    async loadTrainingEvents() {
      const response = await apiClient.get(`/diary/${this.currentClub}`);
      this.ensureSuccess(response, this.$t('calendar.sources.trainingDays'));
      return (response.data || []).map(entry => {
        const date = this.parseDate(entry.date);
        return {
          id: `training-${entry.id}`,
          type: 'training',
          date,
          startsAt: this.combineDateTime(date, entry.trainingStart),
          time: this.formatTimeRange(entry.trainingStart, entry.trainingEnd),
          title: this.$t('calendar.eventTitles.training'),
          subtitle: entry.diaryTags?.map(tag => tag.name).join(', ') || '',
          route: '/diary'
        };
      });
    },
    async loadRecurringTrainingEvents() {
      const response = await apiClient.get(`/training-times/${this.currentClub}`);
      this.ensureSuccess(response, this.$t('calendar.sources.trainingTimes'));
      const groups = Array.isArray(response.data) ? response.data : [];
      this.trainingGroups = groups.filter(group => Array.isArray(group.trainingTimes) && group.trainingTimes.length > 0);
      return groups.flatMap(group => this.mapGroupTrainingTimesToEvents(group));
    },
    mapGroupTrainingTimesToEvents(group) {
      const trainingTimes = Array.isArray(group?.trainingTimes) ? group.trainingTimes : [];
      return trainingTimes.flatMap(time => this.createRecurringTrainingEvents(group, time));
    },
    createRecurringTrainingEvents(group, time) {
      const weekday = Number(time?.weekday);
      if (!Number.isInteger(weekday) || weekday < 0 || weekday > 6 || !time?.startTime) {
        return [];
      }

      const events = [];
      const date = new Date(this.displayedYear, 0, 1);
      const daysUntilWeekday = (weekday - date.getDay() + 7) % 7;
      date.setDate(date.getDate() + daysUntilWeekday);

      while (date.getFullYear() === this.displayedYear) {
        const eventDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
        const dateKey = this.toDateKey(eventDate);
        events.push({
          id: `training-time-${group.id}-${time.id}-${dateKey}`,
          type: 'training',
          date: eventDate,
          startsAt: this.combineDateTime(eventDate, time.startTime),
          time: this.formatTimeRange(time.startTime, time.endTime),
          title: group?.name || this.$t('calendar.eventTitles.training'),
          subtitle: this.$t('calendar.recurringTrainingTime'),
          route: '/diary',
          isRecurringTraining: true,
          trainingGroupId: group?.id
        });
        date.setDate(date.getDate() + 7);
      }

      return events;
    },
    async loadTrainingCancellationEvents() {
      const response = await apiClient.get(`/training-cancellations/${this.currentClub}`, {
        params: { year: this.displayedYear }
      });
      this.ensureSuccess(response, this.$t('calendar.sources.trainingCancellations'));
      return (response.data || []).map(cancellation => {
        const date = this.parseDate(cancellation.startDate || cancellation.date);
        const endDate = this.parseDate(cancellation.endDate || cancellation.startDate || cancellation.date);
        const trainingGroupIds = this.normalizeTrainingGroupIds(cancellation.trainingGroupIds);
        return {
          id: `training-cancellation-${cancellation.id}`,
          cancellationId: cancellation.id,
          type: 'trainingCancellation',
          date,
          endDate,
          startsAt: this.combineDateTime(date),
          time: '',
          title: cancellation.reason || this.$t('calendar.cancellation.fallbackTitle'),
          subtitle: this.formatCancellationSubtitle(trainingGroupIds),
          trainingGroupIds
        };
      });
    },
    async saveTrainingCancellation() {
      if (!this.currentClub || !this.cancellationForm.startDate) return;
      this.cancellationSaving = true;
      try {
        const response = await apiClient.post(`/training-cancellations/${this.currentClub}`, {
          startDate: this.cancellationForm.startDate,
          endDate: this.cancellationForm.endDate || this.cancellationForm.startDate,
          reason: this.cancellationForm.reason,
          trainingGroupIds: this.normalizeTrainingGroupIds(this.cancellationForm.trainingGroupIds)
        });
        this.ensureSuccess(response, this.$t('calendar.sources.trainingCancellations'));
        this.cancellationForm = { startDate: '', endDate: '', reason: '', trainingGroupIds: [] };
        await this.loadCalendarEvents();
      } finally {
        this.cancellationSaving = false;
      }
    },
    getDateKeysForRange(startDate, endDate) {
      const keys = [];
      const cursor = this.startOfDay(startDate);
      const rangeEnd = this.startOfDay(endDate);
      while (cursor <= rangeEnd) {
        keys.push(this.toDateKey(cursor));
        cursor.setDate(cursor.getDate() + 1);
      }
      return keys;
    },
    async deleteTrainingCancellation(cancellation) {
      if (!this.currentClub || !cancellation?.cancellationId) return;
      const response = await apiClient.delete(`/training-cancellations/${this.currentClub}/${cancellation.cancellationId}`);
      this.ensureSuccess(response, this.$t('calendar.sources.trainingCancellations'));
      await this.loadCalendarEvents();
    },
    openTrainingCancellationDeleteDialog(cancellation) {
      if (!cancellation?.cancellationId) return;
      this.cancellationDeleteDialog = {
        isOpen: true,
        cancellation,
        title: cancellation.title || this.$t('calendar.cancellation.fallbackTitle'),
        subtitle: cancellation.subtitle || '',
        dateLabel: this.formatEventDate(cancellation),
      };
    },
    closeTrainingCancellationDeleteDialog() {
      this.cancellationDeleteDialog = {
        isOpen: false,
        cancellation: null,
        title: '',
        subtitle: '',
        dateLabel: '',
      };
    },
    async confirmTrainingCancellationDelete() {
      const cancellation = this.cancellationDeleteDialog.cancellation;
      if (!cancellation) return;
      await this.deleteTrainingCancellation(cancellation);
      this.closeTrainingCancellationDeleteDialog();
    },
    async loadCustomEvents() {
      const response = await apiClient.get(`/calendar-events/${this.currentClub}`, {
        params: { year: this.displayedYear },
      });
      this.ensureSuccess(response, this.$t('calendar.sources.customEvents'));
      return (response.data || []).map(event => {
        const date = this.parseDate(event.startDate);
        const endDate = this.parseDate(event.endDate || event.startDate);
        const subtitleParts = [
          event.category || this.$t('calendar.customEvent.subtitleFallback'),
          event.status || '',
          event.registrationDeadline ? `Frist ${this.formatDate(event.registrationDeadline)}` : '',
        ].filter(Boolean);
        return {
          id: `custom-event-${event.id}`,
          customEventId: event.id,
          type: 'customEvent',
          date,
          endDate,
          startsAt: this.combineDateTime(date),
          time: '',
          title: event.title,
          subtitle: subtitleParts.join(' · '),
        };
      });
    },
    async saveCustomEvent() {
      if (!this.currentClub || !this.customEventForm.title || !this.customEventForm.startDate) return;
      this.customEventSaving = true;
      try {
        const response = await apiClient.post(`/calendar-events/${this.currentClub}`, {
          title: this.customEventForm.title,
          startDate: this.customEventForm.startDate,
          endDate: this.customEventForm.endDate || this.customEventForm.startDate,
          category: this.customEventForm.category || null,
          eventType: 'club_event',
          status: 'planning',
        });
        this.ensureSuccess(response, this.$t('calendar.sources.customEvents'));
        this.customEventForm = { title: '', startDate: '', endDate: '', category: '' };
        await this.loadCalendarEvents();
      } finally {
        this.customEventSaving = false;
      }
    },
    async deleteCustomEvent(event) {
      if (!this.currentClub || !event?.customEventId) return;
      const response = await apiClient.delete(`/calendar-events/${this.currentClub}/${event.customEventId}`);
      this.ensureSuccess(response, this.$t('calendar.sources.customEvents'));
      await this.loadCalendarEvents();
    },
    async loadTournamentEvents() {
      const response = await apiClient.get(`/tournament/${this.currentClub}`);
      this.ensureSuccess(response, this.$t('calendar.sources.tournaments'));
      return (response.data || []).map(tournament => {
        const date = this.parseDate(tournament.date);
        return {
          id: `tournament-${tournament.id}`,
          type: 'tournament',
          date,
          startsAt: this.combineDateTime(date),
          time: '',
          title: tournament.name || tournament.tournamentName || this.$t('calendar.eventTitles.tournament'),
          subtitle: tournament.allowsExternal ? this.$t('calendar.tournament.open') : this.$t('calendar.tournament.club'),
          route: '/tournaments'
        };
      });
    },
    async loadMatchEvents() {
      const response = await apiClient.get(`/matches/leagues/${this.currentClub}/matches`);
      this.ensureSuccess(response, this.$t('calendar.sources.matches'));
      return (response.data || [])
        .filter(match => this.isOurTeam(match.homeTeam?.name) || this.isOurTeam(match.guestTeam?.name))
        .map(match => {
        const date = this.parseDate(match.date);
        const home = match.homeTeam?.name || this.$t('calendar.match.home');
        const guest = match.guestTeam?.name || this.$t('calendar.match.guest');
        return {
          id: `match-${match.id}`,
          type: 'match',
          date,
          startsAt: this.combineDateTime(date, match.time),
          time: this.formatTime(match.time),
          title: `${home} - ${guest}`,
          subtitle: match.leagueDetails?.name || match.league?.name || this.$t('calendar.eventTitles.match'),
          route: '/schedule'
        };
      });
    },
    async loadOfficialTournamentEvents() {
      const response = await apiClient.get(`/official-tournaments/${this.currentClub}/participations/summary`);
      this.ensureSuccess(response, this.$t('calendar.sources.officialTournaments'));
      return (response.data || [])
        .filter(tournament => Array.isArray(tournament.entries) && tournament.entries.length > 0)
        .map(tournament => {
          const date = this.parseDmyDate(tournament.startDate);
          const endDate = this.parseDmyDate(tournament.endDate || tournament.startDate);
          if (!date) return null;
          const entries = tournament.entries || [];
          const participantCount = new Set(entries.map(entry => entry.memberId).filter(Boolean)).size;
          return {
            id: `official-tournament-${tournament.tournamentId}`,
            type: 'officialTournament',
            date,
            endDate: endDate || date,
            startsAt: this.combineDateTime(date),
            time: '',
            title: tournament.tournamentName || tournament.title || this.$t('calendar.eventTitles.officialTournament'),
            subtitle: participantCount > 0
              ? this.$t('calendar.participants', { count: participantCount })
              : this.$t('calendar.starts', { count: entries.length }),
            route: '/tournament-participations'
          };
        })
        .filter(Boolean);
    },
    async loadHolidayEvents() {
      const response = await apiClient.get(`/calendar/club/${this.currentClub}/holidays`, {
        params: { year: this.displayedYear }
      });
      this.ensureSuccess(response, this.$t('calendar.sources.holidays'));
      const holidays = response.data?.holidays || [];
      const schoolHolidays = response.data?.schoolHolidays || [];
      return [
        ...holidays.map(entry => this.mapCalendarDayEvent(entry, 'holiday', this.$t('calendar.holidayTypes.holiday'))),
        ...schoolHolidays.map(entry => this.mapCalendarDayEvent(entry, 'schoolHoliday', this.$t('calendar.holidayTypes.schoolHoliday')))
      ].filter(Boolean);
    },
    mapCalendarDayEvent(entry, type, fallbackTitle) {
      const date = this.parseDate(entry.startDate);
      const endDate = this.parseDate(entry.endDate || entry.startDate);
      if (!date || !endDate) return null;
      return {
        id: `${type}-${entry.id || entry.startDate}`,
        type,
        date,
        endDate,
        startsAt: this.combineDateTime(date),
        time: '',
        title: entry.name || fallbackTitle,
        subtitle: fallbackTitle
      };
    },
    ensureSuccess(response, source) {
      if (!response || response.status >= 400) {
        const error = new Error(`HTTP ${response?.status || 0}`);
        error.source = source;
        throw error;
      }
    },
    toggleType(type) {
      this.activeTypes = {
        ...this.activeTypes,
        [type]: !this.activeTypes[type]
      };
    },
    parseDate(value) {
      if (!value) return null;
      if (value instanceof Date) return new Date(value.getFullYear(), value.getMonth(), value.getDate());
      const text = String(value).slice(0, 10);
      const [year, month, day] = text.split('-').map(Number);
      if (!year || !month || !day) return new Date(value);
      return new Date(year, month - 1, day);
    },
    parseDmyDate(value) {
      const match = String(value || '').match(/(\d{1,2})\.(\d{1,2})\.(\d{4})/);
      if (!match) return null;
      const date = new Date(Number(match[3]), Number(match[2]) - 1, Number(match[1]));
      return Number.isNaN(date.getTime()) ? null : date;
    },
    combineDateTime(date, time) {
      if (!date) return Number.POSITIVE_INFINITY;
      const result = new Date(date);
      const [hours, minutes] = String(time || '').split(':').map(Number);
      result.setHours(Number.isFinite(hours) ? hours : 0, Number.isFinite(minutes) ? minutes : 0, 0, 0);
      return result.getTime();
    },
    isEventOnDate(event, date) {
      const eventStart = this.startOfDay(event.date);
      const eventEnd = this.startOfDay(event.endDate || event.date);
      const candidate = this.startOfDay(date);
      return candidate >= eventStart && candidate <= eventEnd;
    },
    isEventInRange(event, rangeStart, rangeEnd) {
      const eventStart = this.startOfDay(event.date);
      const eventEnd = this.startOfDay(event.endDate || event.date);
      return eventStart <= this.startOfDay(rangeEnd) && eventEnd >= this.startOfDay(rangeStart);
    },
    shouldShowEventOnDate(event, dateKey) {
      if (event.type !== 'trainingCancellation') return true;
      const trainingGroupIds = this.normalizeTrainingGroupIds(event.trainingGroupIds);
      if (!trainingGroupIds.length) return Boolean(this.plannedTrainingByDateKey[dateKey]);
      const plannedGroupIds = this.plannedTrainingGroupIdsByDateKey[dateKey] || {};
      return trainingGroupIds.some(groupId => Boolean(plannedGroupIds[String(groupId)]));
    },
    isRecurringTrainingCancelled(event, cancellationEvents) {
      return cancellationEvents.some(cancellation => (
        this.isEventOnDate(cancellation, event.date)
        && this.matchesCancellationGroup(cancellation, event.trainingGroupId)
      ));
    },
    matchesCancellationGroup(cancellation, trainingGroupId) {
      const trainingGroupIds = this.normalizeTrainingGroupIds(cancellation.trainingGroupIds);
      return trainingGroupIds.length === 0 || trainingGroupIds.includes(Number.parseInt(trainingGroupId, 10));
    },
    normalizeTrainingGroupIds(trainingGroupIds) {
      const values = this.parseTrainingGroupIdsValue(trainingGroupIds);
      return [...new Set(
        values
          .map(groupId => Number.parseInt(groupId, 10))
          .filter(groupId => Number.isInteger(groupId) && groupId > 0)
      )];
    },
    parseTrainingGroupIdsValue(trainingGroupIds) {
      if (Array.isArray(trainingGroupIds)) return trainingGroupIds;
      if (trainingGroupIds === null || trainingGroupIds === undefined || trainingGroupIds === '') return [];
      if (typeof trainingGroupIds === 'string') {
        try {
          const parsed = JSON.parse(trainingGroupIds);
          if (Array.isArray(parsed)) return parsed;
          if (parsed !== null && parsed !== undefined) return [parsed];
        } catch (error) {
          return trainingGroupIds.split(',');
        }
      }
      return [];
    },
    formatCancellationSubtitle(trainingGroupIds) {
      const normalizedIds = this.normalizeTrainingGroupIds(trainingGroupIds);
      if (!normalizedIds.length) return this.$t('calendar.cancellation.subtitle');
      const names = normalizedIds
        .map(groupId => this.trainingGroupsWithTimes.find(group => Number(group.id) === groupId)?.name)
        .filter(Boolean);
      return names.length
        ? `${this.$t('calendar.cancellation.subtitle')} · ${names.join(', ')}`
        : this.$t('calendar.cancellation.subtitle');
    },
    startOfDay(date) {
      const result = new Date(date);
      result.setHours(0, 0, 0, 0);
      return result;
    },
    toDateKey(date) {
      const year = date.getFullYear();
      const month = String(date.getMonth() + 1).padStart(2, '0');
      const day = String(date.getDate()).padStart(2, '0');
      return `${year}-${month}-${day}`;
    },
    formatTime(value) {
      if (!value) return '';
      return String(value).slice(0, 5);
    },
    formatTimeRange(start, end) {
      const startTime = this.formatTime(start);
      const endTime = this.formatTime(end);
      if (startTime && endTime) return `${startTime}-${endTime}`;
      return startTime || endTime || '';
    },
    formatShortDate(date) {
      return date.toLocaleDateString(this.$i18n?.locale || 'de-DE', { weekday: 'short', day: '2-digit', month: '2-digit' });
    },
    formatEventDate(event) {
      if (!event.endDate || this.toDateKey(event.date) === this.toDateKey(event.endDate)) {
        return this.formatShortDate(event.date);
      }
      return `${this.formatShortDate(event.date)} - ${this.formatShortDate(event.endDate)}`;
    },
    goToPreviousMonth() {
      this.cursor = new Date(this.cursor.getFullYear(), this.cursor.getMonth() - 1, 1);
    },
    goToNextMonth() {
      this.cursor = new Date(this.cursor.getFullYear(), this.cursor.getMonth() + 1, 1);
    },
    goToToday() {
      const today = new Date();
      this.cursor = new Date(today.getFullYear(), today.getMonth(), 1);
    },
    openEvent(event) {
      if (event?.type === 'holiday' || event?.type === 'schoolHoliday') {
        this.offerTrainingCancellationForHoliday(event);
        return;
      }
      if (event?.type === 'trainingCancellation') {
        this.openTrainingCancellationDeleteDialog(event);
        return;
      }
      if (event.route) {
        this.$router.push(event.route);
      }
    },
    offerTrainingCancellationForHoliday(event) {
      if (!this.currentClub) return;
      const startDateKey = this.toDateKey(event.date);
      const endDateKey = this.toDateKey(event.endDate || event.date);
      const isRange = startDateKey !== endDateKey;
      const reason = `${event.title}${event.subtitle ? ` (${event.subtitle})` : ''}`.trim();

      this.quickCancellationDialog = {
        isOpen: true,
        eventTitle: event.title,
        startDate: startDateKey,
        endDate: endDateKey,
        isRange,
        useRange: isRange,
        reason,
        trainingGroupIds: this.trainingGroupsWithTimes.map(group => String(group.id)),
      };
    },
    closeQuickCancellationDialog() {
      this.quickCancellationDialog.isOpen = false;
    },
    async confirmQuickCancellation() {
      const dialog = this.quickCancellationDialog;
      if (!this.currentClub || !dialog.startDate || dialog.trainingGroupIds.length === 0) return;
      const endDate = dialog.isRange && dialog.useRange ? dialog.endDate : dialog.startDate;
      await this.createTrainingCancellation(
        dialog.startDate,
        endDate,
        dialog.reason,
        dialog.trainingGroupIds
      );
      this.closeQuickCancellationDialog();
      await this.loadCalendarEvents();
    },
    async createTrainingCancellation(startDate, endDate, reason, trainingGroupIds = []) {
      const response = await apiClient.post(`/training-cancellations/${this.currentClub}`, {
        startDate,
        endDate,
        reason,
        trainingGroupIds: this.normalizeTrainingGroupIds(trainingGroupIds)
      });
      this.ensureSuccess(response, this.$t('calendar.sources.trainingCancellations'));
    }
  }
};
</script>

<style scoped>
.calendar-page {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.calendar-header,
.calendar-toolbar,
.training-cancellation-panel,
.custom-event-panel,
.calendar-agenda {
  border: 1px solid #dfe7e2;
  border-radius: 10px;
  background: #fff;
  box-shadow: 0 6px 18px rgba(15, 23, 42, 0.05);
}

.calendar-header {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  align-items: flex-start;
  padding: 1.2rem;
}

.calendar-eyebrow {
  display: block;
  margin-bottom: 0.25rem;
  color: #2f7a5f;
  font-size: 0.78rem;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.calendar-header h2 {
  margin: 0;
  color: #14251f;
}

.calendar-header p {
  margin: 0.35rem 0 0;
  color: #607169;
}

.calendar-actions,
.calendar-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
}

.calendar-nav-button,
.calendar-today-button {
  border: 1px solid #cfdad4;
  border-radius: 8px;
  background: #f8fbf9;
  color: #173d31;
  font-weight: 700;
  cursor: pointer;
}

.calendar-nav-button {
  width: 2.5rem;
  height: 2.5rem;
  font-size: 1.35rem;
}

.calendar-today-button {
  min-height: 2.5rem;
  padding: 0 0.85rem;
}

.calendar-toolbar {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  align-items: center;
  padding: 0.65rem 1rem;
}

.legend-item {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  border: 1px solid #d6e2dc;
  border-radius: 999px;
  background: #f8fbf9;
  font-size: 0.8rem;
  font-weight: 700;
  color: #40524b;
  cursor: pointer;
  padding: 0.35rem 0.65rem;
}

.calendar-options {
  border: 1px solid #dfe7e2;
  border-radius: 10px;
  background: #fff;
  box-shadow: 0 6px 18px rgba(15, 23, 42, 0.05);
  overflow: hidden;
}

.calendar-options-summary {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  cursor: pointer;
  padding: 0.75rem 1rem;
  background: #f8fbf9;
  border-bottom: 1px solid #edf1ee;
  user-select: none;
}

.calendar-options-summary span {
  font-weight: 900;
  color: #14251f;
}

.calendar-options-summary small {
  color: #607169;
  font-weight: 700;
}

.calendar-options-body {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
  padding: 1rem;
}

.legend-item.inactive {
  opacity: 0.45;
}

.legend-item::before {
  content: '';
  width: 0.7rem;
  height: 0.7rem;
  border-radius: 999px;
}

.legend-training::before { background: #2f7a5f; }
.legend-tournament::before { background: #b7791f; }
.legend-officialTournament::before { background: #9333ea; }
.legend-match::before { background: #2563eb; }
.legend-holiday::before { background: #dc2626; }
.legend-schoolHoliday::before { background: #7c3aed; }
.legend-trainingCancellation::before { background: #64748b; }
.legend-customEvent::before { background: #0ea5e9; }

.custom-event-panel {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
  padding: 0;
}

.custom-event-panel h3 {
  margin: 0;
  color: #14251f;
}

.custom-event-panel p {
  margin: 0.25rem 0 0;
  color: #607169;
  font-size: 0.9rem;
}

.custom-event-form {
  display: grid;
  grid-template-columns: minmax(14rem, 1fr) 10rem 10rem 12rem auto;
  gap: 0.5rem;
}

.custom-event-form input,
.custom-event-form button {
  border: 1px solid #cfdad4;
  border-radius: 8px;
  min-height: 2.4rem;
  padding: 0 0.75rem;
}

.custom-event-form button {
  background: #0ea5e9;
  color: #fff;
  cursor: pointer;
  font-weight: 800;
}

.custom-event-list {
  display: flex;
  grid-column: 1 / -1;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.custom-event-item {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  border: 1px solid #bae6fd;
  border-radius: 999px;
  background: #f0f9ff;
  color: #0c4a6e;
  cursor: pointer;
  padding: 0.35rem 0.65rem;
}

.custom-event-item small {
  color: #991b1b;
  font-weight: 800;
}

.training-cancellation-panel {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
  padding: 0;
}

.training-cancellation-panel h3 {
  margin: 0;
  color: #14251f;
}

.training-cancellation-panel p {
  margin: 0.25rem 0 0;
  color: #607169;
  font-size: 0.9rem;
}

.training-cancellation-form {
  display: grid;
  grid-template-columns: 10rem 10rem 12rem minmax(12rem, 1fr) auto;
  gap: 0.5rem;
}

.training-cancellation-form label {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

.training-cancellation-form label span {
  color: #607169;
  font-size: 0.75rem;
  font-weight: 800;
}

.training-cancellation-form input,
.training-cancellation-form select,
.training-cancellation-form button {
  border: 1px solid #cfdad4;
  border-radius: 8px;
  min-height: 2.4rem;
  padding: 0 0.75rem;
}

.training-cancellation-form select {
  min-height: 4.8rem;
  padding: 0.35rem 0.5rem;
}

.training-cancellation-form button {
  background: #2f7a5f;
  color: #fff;
  cursor: pointer;
  font-weight: 800;
}

.training-cancellation-list {
  display: flex;
  grid-column: 1 / -1;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.training-cancellation-item {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  border: 1px solid #cbd5e1;
  border-radius: 999px;
  background: #f8fafc;
  color: #334155;
  cursor: pointer;
  padding: 0.35rem 0.65rem;
}

.training-cancellation-item small {
  color: #991b1b;
  font-weight: 800;
}

.calendar-state {
  padding: 0.9rem 1rem;
  border-radius: 8px;
  background: #eef6f2;
  color: #255545;
  font-weight: 700;
}

.calendar-state-error {
  background: #fee2e2;
  color: #991b1b;
}

.calendar-state-warning {
  background: #fff7e6;
  color: #8a4b11;
}

.calendar-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  overflow: hidden;
  border: 1px solid #dfe7e2;
  border-radius: 10px;
  background: #dfe7e2;
  gap: 1px;
}

.calendar-weekday,
.calendar-day {
  background: #fff;
}

.calendar-weekday {
  padding: 0.7rem;
  color: #607169;
  font-size: 0.78rem;
  font-weight: 800;
  text-transform: uppercase;
}

.calendar-day {
  min-height: 9.5rem;
  padding: 0.55rem;
}

.calendar-day.outside-month {
  background: #f6f8f7;
  color: #9aa8a1;
}

.calendar-day.today {
  box-shadow: inset 0 0 0 2px #2f7a5f;
}

.calendar-day-number {
  margin-bottom: 0.45rem;
  font-weight: 800;
}

.calendar-events {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.calendar-event,
.agenda-item {
  border: 0;
  text-align: left;
  cursor: pointer;
}

.calendar-event {
  display: flex;
  flex-direction: column;
  gap: 0.05rem;
  padding: 0.4rem 0.45rem;
  border-radius: 7px;
  color: #0f172a;
}

.event-training { background: #e8f4ef; border-left: 4px solid #2f7a5f; }
.event-tournament { background: #fff7e6; border-left: 4px solid #b7791f; }
.event-officialTournament { background: #f3e8ff; border-left: 4px solid #9333ea; }
.event-match { background: #eaf1ff; border-left: 4px solid #2563eb; }
.event-holiday { background: #fee2e2; border-left: 4px solid #dc2626; }
.event-schoolHoliday { background: #f3e8ff; border-left: 4px solid #7c3aed; }
.event-trainingCancellation { background: #f1f5f9; border-left: 4px solid #64748b; }
.event-customEvent { background: #f0f9ff; border-left: 4px solid #0ea5e9; }

.event-time {
  font-size: 0.72rem;
  font-weight: 800;
  color: #4b5d55;
}

.event-title {
  overflow: hidden;
  font-size: 0.82rem;
  font-weight: 800;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.event-subtitle {
  overflow: hidden;
  color: #5b6b64;
  font-size: 0.74rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.calendar-agenda {
  padding: 1rem;
}

.calendar-agenda h3 {
  margin: 0 0 0.75rem;
}

.agenda-empty {
  margin: 0;
  color: #607169;
}

.agenda-item {
  display: grid;
  grid-template-columns: 12rem 1fr auto;
  gap: 0.85rem;
  align-items: center;
  width: 100%;
  padding: 0.7rem 0;
  border-top: 1px solid #edf1ee;
  background: transparent;
}

.agenda-date {
  color: #607169;
  font-weight: 800;
}

.agenda-main {
  display: flex;
  min-width: 0;
  flex-direction: column;
}

.agenda-main strong,
.agenda-main small {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.agenda-main small {
  color: #607169;
}

.quick-cancellation-dialog {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}

.quick-cancellation-dialog p {
  margin: 0;
  color: #40524b;
}

.quick-cancellation-checkbox,
.quick-cancellation-groups {
  display: flex;
  gap: 0.5rem;
}

.quick-cancellation-checkbox {
  align-items: flex-start;
}

.quick-cancellation-groups {
  flex-direction: column;
}

.quick-cancellation-groups span {
  color: #40524b;
  font-size: 0.85rem;
  font-weight: 800;
}

.quick-cancellation-groups select {
  min-height: 8rem;
  border: 1px solid #cfdad4;
  border-radius: 8px;
  padding: 0.45rem;
}

.quick-cancellation-hint {
  color: #8a4b11;
  font-weight: 700;
}

.dialog-primary-button,
.dialog-danger-button,
.dialog-secondary-button {
  min-height: 2.35rem;
  border-radius: 8px;
  cursor: pointer;
  font-weight: 800;
  padding: 0 0.85rem;
}

.dialog-primary-button {
  border: 1px solid #2f7a5f;
  background: #2f7a5f;
  color: #fff;
}

.dialog-danger-button {
  border: 1px solid #b91c1c;
  background: #dc2626;
  color: #fff;
}

.dialog-primary-button:disabled {
  cursor: not-allowed;
  opacity: 0.55;
}

.dialog-secondary-button {
  border: 1px solid #cfdad4;
  background: #f8fbf9;
  color: #173d31;
}

.agenda-time {
  color: #40524b;
  font-weight: 800;
}

@media (max-width: 900px) {
  .calendar-header,
  .calendar-toolbar,
  .training-cancellation-panel,
  .custom-event-panel {
    align-items: stretch;
    grid-template-columns: 1fr;
    flex-direction: column;
  }

  .calendar-options-body {
    grid-template-columns: 1fr;
  }

  .training-cancellation-form {
    grid-template-columns: 1fr;
  }

  .custom-event-form {
    grid-template-columns: 1fr;
  }

  .calendar-grid {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    border: 0;
    background: transparent;
  }

  .calendar-weekday,
  .calendar-day.outside-month {
    display: none;
  }

  .calendar-day {
    min-height: auto;
    border: 1px solid #dfe7e2;
    border-radius: 9px;
  }

  .calendar-day:not(:has(.calendar-event)) {
    display: none;
  }

  .agenda-item {
    grid-template-columns: 1fr;
    gap: 0.25rem;
  }
}
</style>
