<template>
  <div class="view-container">
    <div class="view-header">
      <h2>Kalender</h2>
      <div class="header-actions">
        <SelectButton v-model="viewMode" :options="viewModeOptions" optionLabel="label" optionValue="value" size="small" />
        <Button icon="pi pi-plus" label="Neuer Kalender" size="small" outlined @click="showNewCalendar = true" />
        <Button icon="pi pi-plus" label="Neuer Termin" size="small" @click="openNewEvent()" />
      </div>
    </div>

    <div class="calendar-layout">
      <aside class="calendar-sidebar">
        <h4>Kalender</h4>
        <div v-for="cal in calendars" :key="cal.id" class="calendar-item">
          <input type="checkbox" v-model="visibleCalendars[cal.id]" @change="refreshEvents" />
          <div class="calendar-color" :style="{ background: cal.color }"></div>
          <span class="cal-name">{{ cal.name }}</span>
          <span v-if="cal.permission !== 'owner'" class="shared-label">(geteilt)</span>
          <Button icon="pi pi-ellipsis-v" text size="small" @click.stop="openCalendarMenu(cal)" />
        </div>
      </aside>

      <div class="calendar-main">
        <FullCalendar v-show="viewMode === 'calendar'" ref="fcRef" :options="calendarOptions">
          <template #eventContent="arg">
            <div class="fc-event-content-inner" :title="eventTooltip(arg.event)">
              <span v-if="arg.event.extendedProps.all_day" class="fc-icon">📅</span>
              <span v-else-if="arg.timeText" class="fc-time">{{ formatEventTime(arg.event) }}</span>
              <span v-if="arg.event.extendedProps.recurrence_rule" class="fc-icon" title="Wiederholung">🔁</span>
              <span class="fc-title">{{ arg.event.title }}</span>
            </div>
          </template>
        </FullCalendar>

        <div v-if="viewMode === 'list'" class="list-view">
          <div class="list-filters">
            <span class="p-input-icon-left" style="flex: 1;">
              <i class="pi pi-search"></i>
              <InputText v-model="listSearch" placeholder="Suchen (Titel, Ort, Beschreibung)..." fluid />
            </span>
            <InputText v-model="listFrom" type="date" title="Von" />
            <InputText v-model="listTo" type="date" title="Bis" />
            <Select v-model="listCalFilter" :options="listCalOptions"
                    optionLabel="label" optionValue="value" placeholder="Alle Kalender"
                    showClear style="min-width: 180px" />
          </div>
          <div class="list-meta">{{ filteredListEvents.length }} Termin(e)</div>
          <table class="list-table">
            <thead>
              <tr>
                <th @click="toggleListSort('dtstart')" class="sortable">
                  Datum <i v-if="listSort === 'dtstart'" :class="listSortDir === 'asc' ? 'pi pi-arrow-up' : 'pi pi-arrow-down'"></i>
                </th>
                <th @click="toggleListSort('summary')" class="sortable">
                  Titel <i v-if="listSort === 'summary'" :class="listSortDir === 'asc' ? 'pi pi-arrow-up' : 'pi pi-arrow-down'"></i>
                </th>
                <th>Kalender</th>
                <th>Ort</th>
                <th></th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="ev in filteredListEvents" :key="ev.id" class="list-row" @click="openEditEvent(ev)">
                <td class="col-date">
                  <div>{{ formatListDate(ev) }}</div>
                  <div v-if="!ev.all_day" class="meta-time">{{ formatListTime(ev) }}</div>
                </td>
                <td class="col-title">
                  <span class="cal-dot" :style="{ background: ev._cal?.color }"></span>
                  {{ ev.summary || '(ohne Titel)' }}
                  <i v-if="ev.recurrence_rule" class="pi pi-replay" title="Wiederholung" style="margin-left: 0.25rem; font-size: 0.75rem;"></i>
                </td>
                <td>{{ ev._cal?.name }}</td>
                <td>{{ ev.location || '' }}</td>
                <td class="col-actions" @click.stop>
                  <Button icon="pi pi-trash" text severity="danger" size="small"
                          :disabled="ev._cal?.permission === 'read'"
                          @click="confirmDeleteListEvent(ev)" title="Loeschen" />
                </td>
              </tr>
              <tr v-if="!filteredListEvents.length">
                <td colspan="5" class="empty-row">Keine Termine gefunden.</td>
              </tr>
            </tbody>
          </table>
        </div>
      </div>
    </div>

    <!-- New Calendar Dialog -->
    <Dialog v-model:visible="showNewCalendar" header="Neuer Kalender" modal :style="{ width: '400px' }">
      <div class="field">
        <label>Name</label>
        <InputText v-model="newCalName" fluid autofocus />
      </div>
      <div class="field">
        <label>Farbe</label>
        <InputText v-model="newCalColor" type="color" style="width: 60px; height: 36px" />
      </div>
      <template #footer>
        <Button label="Abbrechen" text @click="showNewCalendar = false" />
        <Button label="Erstellen" @click="createCalendar" />
      </template>
    </Dialog>

    <!-- Event Dialog -->
    <Dialog v-model:visible="showEventDialog" :header="editingEvent ? 'Termin bearbeiten' : 'Neuer Termin'"
            modal :style="{ width: '560px' }">
      <div class="field">
        <label>Titel</label>
        <InputText v-model="eventForm.summary" fluid autofocus />
      </div>
      <div class="field">
        <label>Kalender</label>
        <Select v-model="eventForm.calendar_id" :options="ownCalendars" optionLabel="name" optionValue="id" fluid />
      </div>
      <div class="field-row">
        <div class="field">
          <label>Start</label>
          <InputText v-model="eventForm.dtstart" :type="eventForm.all_day ? 'date' : 'datetime-local'" fluid />
        </div>
        <div class="field">
          <label>Ende</label>
          <InputText v-model="eventForm.dtend" :type="eventForm.all_day ? 'date' : 'datetime-local'" fluid />
        </div>
      </div>
      <div class="field">
        <label><input type="checkbox" v-model="eventForm.all_day" /> Ganztaegig</label>
      </div>
      <div class="field">
        <label><input type="checkbox" v-model="eventForm.is_private" /> 🔒 Privat (Teilnehmer sehen nur den Zeitblock)</label>
      </div>
      <div class="field">
        <label>Ort</label>
        <InputText v-model="eventForm.location" fluid />
      </div>
      <div class="field">
        <label>Beschreibung</label>
        <Textarea v-model="eventForm.description" rows="3" fluid />
      </div>

      <!-- Recurrence editor -->
      <div class="field">
        <label>Wiederholung</label>
        <Select v-model="recurFreq" :options="recurFreqOptions" optionLabel="label" optionValue="value" fluid />
      </div>
      <div v-if="recurFreq !== 'none'" class="recur-details">
        <div class="field-row">
          <div class="field">
            <label>Alle</label>
            <InputText v-model.number="recurInterval" type="number" min="1" style="width: 80px" />
            <span class="recur-unit">{{ recurUnitLabel }}</span>
          </div>
          <div class="field">
            <label>Ende</label>
            <Select v-model="recurEndMode" :options="recurEndOptions" optionLabel="label" optionValue="value" />
          </div>
        </div>
        <div v-if="recurEndMode === 'count'" class="field">
          <label>Nach X Wiederholungen</label>
          <InputText v-model.number="recurCount" type="number" min="1" style="width: 100px" />
        </div>
        <div v-if="recurEndMode === 'until'" class="field">
          <label>Bis Datum</label>
          <InputText v-model="recurUntil" type="date" style="width: 180px" />
        </div>
        <div v-if="recurFreq === 'weekly'" class="field">
          <label>An Wochentagen</label>
          <div class="weekday-row">
            <label v-for="d in weekdayBtns" :key="d.value" class="weekday-btn" :class="{ active: recurWeekdays.includes(d.value) }">
              <input type="checkbox" :value="d.value" v-model="recurWeekdays" /> {{ d.label }}
            </label>
          </div>
        </div>
        <div v-if="recurFreq === 'monthly'" class="field">
          <label><input type="checkbox" v-model="recurByDayOfWeek" /> Am X. Wochentag des Monats (z.B. "jeden 2. Mittwoch")</label>
        </div>
        <div v-if="recurFreq === 'monthly' && recurByDayOfWeek" class="field-row">
          <div class="field">
            <label>Position</label>
            <Select v-model="recurBySetPos" :options="setPosOptions" optionLabel="label" optionValue="value" />
          </div>
          <div class="field">
            <label>Wochentag</label>
            <Select v-model="recurByDay" :options="weekdayBtns" optionLabel="label" optionValue="value" />
          </div>
        </div>
      </div>

      <template #footer>
        <Button v-if="editingEvent" label="Loeschen" severity="danger" text @click="confirmDeleteEvent = true" />
        <Button label="Abbrechen" text @click="showEventDialog = false" />
        <Button :label="editingEvent ? 'Speichern' : 'Erstellen'" @click="saveEvent" />
      </template>
    </Dialog>

    <!-- Calendar Menu -->
    <Dialog v-model:visible="showCalMenu" header="Kalender-Optionen" modal :style="{ width: '480px' }">
      <div v-if="selectedCal" class="cal-menu-content">
        <p><strong>{{ selectedCal.name }}</strong></p>

        <div class="field">
          <label>
            {{ selectedCal.permission === 'owner' ? 'Farbe' : 'Persoenliche Anzeigefarbe' }}
          </label>
          <div style="display: flex; gap: 0.5rem; align-items: center;">
            <InputText :modelValue="selectedCal.color" @change="onColorChange($event)"
                       type="color" style="width: 60px; height: 36px" />
            <span v-if="selectedCal.permission !== 'owner'" class="color-hint">
              Nur fuer deine Ansicht - {{ selectedCal.owner_name }} behaelt seine Farbe
            </span>
          </div>
        </div>

        <div v-if="selectedCal.permission === 'owner'" class="field">
          <label>Mit Benutzer teilen</label>
          <div class="share-row">
            <div style="position: relative; flex: 1;">
              <InputText v-model="shareUsername" placeholder="Benutzername suchen..."
                         fluid @input="onShareSearch" />
              <div v-if="shareSearchResults.length" class="user-search-popup">
                <div v-for="u in shareSearchResults" :key="u.id" class="user-result"
                     @click="shareUsername = u.username; shareSearchResults = []">
                  <i class="pi pi-user"></i> {{ u.username }}
                </div>
              </div>
            </div>
            <Select v-model="sharePermission" :options="permOptions" optionLabel="label" optionValue="value" />
            <Button label="Teilen" size="small" @click="shareCalendar" />
          </div>
          <div v-if="calendarShares.length" class="existing-shares">
            <template v-for="s in calendarShares" :key="s.id">
              <div v-if="editingShareId !== s.id" class="share-perm-item">
                <i class="pi pi-user"></i>
                <span>{{ s.username }}</span>
                <span class="perm-label">{{ s.permission === 'readwrite' ? 'Lesen+Schreiben' : 'Lesen' }}</span>
                <Button icon="pi pi-pencil" text size="small" title="Bearbeiten" @click="startEditShare(s)" />
                <Button icon="pi pi-trash" text size="small" severity="danger" title="Entfernen" @click="removeShare(s.id)" />
              </div>
              <div v-else class="share-perm-item editing">
                <i class="pi pi-user"></i>
                <span>{{ s.username }}</span>
                <Select v-model="editSharePermission" :options="permOptions" optionLabel="label" optionValue="value" />
                <Button icon="pi pi-check" text size="small" severity="success" title="Speichern" @click="saveEditShare(s)" />
                <Button icon="pi pi-times" text size="small" title="Abbrechen" @click="editingShareId = null" />
              </div>
            </template>
          </div>
        </div>

        <div v-if="selectedCal.permission === 'owner'" class="field ical-block">
          <label>iCal-Abo-Link</label>
          <div v-if="!selectedCal.ical_token">
            <InputText v-model="icalPassword" placeholder="Passwort (optional)" type="password" style="width: 220px" />
            <Button label="Link erstellen" icon="pi pi-link" outlined size="small" @click="generateIcalLink" style="margin-left: 0.5rem" />
          </div>
          <div v-else class="ical-url">
            <code>{{ fullIcalUrl }}</code>
            <Button icon="pi pi-copy" text size="small" @click="copyIcal" title="Kopieren" />
            <div style="margin-top: 0.5rem; display: flex; gap: 0.5rem; align-items: center;">
              <span v-if="selectedCal.ical_has_password" class="hint-badge">
                <i class="pi pi-lock"></i> Passwortgeschuetzt
              </span>
              <InputText v-model="icalPassword" placeholder="Passwort aendern" type="password" style="width: 200px" />
              <Button label="Passwort setzen" size="small" @click="setIcalPassword" />
              <Button v-if="selectedCal.ical_has_password" label="Passwort entfernen" size="small" text @click="clearIcalPassword" />
              <Button label="Link zurueckziehen" severity="danger" size="small" text @click="revokeIcal" />
            </div>
          </div>
        </div>

        <div class="field caldav-block">
          <label><i class="pi pi-info-circle"></i> CalDAV-Zugang (native Sync)</label>
          <p class="caldav-hint">
            Im Handy/Laptop-Kalender als CalDAV-Account hinzufuegen. Benutzername
            und Passwort sind deine normalen Mini-Cloud-Zugangsdaten.
          </p>
          <div class="url-row">
            <strong>Server (Auto-Discovery)</strong>
            <code>{{ origin }}</code>
            <Button icon="pi pi-copy" text size="small" title="Kopieren" @click="copyText(origin)" />
          </div>
          <div class="url-row">
            <strong>Dieser Kalender (direkt)</strong>
            <code>{{ origin }}/dav/{{ username }}/cal-{{ selectedCal.id }}/</code>
            <Button icon="pi pi-copy" text size="small" title="Kopieren"
                    @click="copyText(`${origin}/dav/${username}/cal-${selectedCal.id}/`)" />
          </div>
          <div class="caldav-clients">
            <div><strong>Thunderbird / DAVx5 / Apple</strong>: Server-URL reicht</div>
            <div><strong>Outlook (CalDAV-Synchronizer)</strong>: Direkt-URL des Kalenders</div>
          </div>
        </div>

        <Button v-if="selectedCal.permission === 'owner'" label="Kalender loeschen"
                severity="danger" text size="small" @click="confirmDeleteCal = true" />
      </div>
    </Dialog>

    <Dialog v-model:visible="confirmDeleteEvent" header="Termin loeschen" modal :style="{ width: '400px' }">
      <p>Moechtest du <strong>{{ editingEvent?.summary }}</strong> wirklich loeschen?</p>
      <template #footer>
        <Button label="Abbrechen" text @click="confirmDeleteEvent = false" />
        <Button label="Loeschen" severity="danger" @click="deleteEvent()" />
      </template>
    </Dialog>

    <!-- Scope picker for recurring events -->
    <Dialog v-model:visible="showScopeDialog" header="Serientermin bearbeiten" modal :style="{ width: '440px' }">
      <p>Dieser Termin gehoert zu einer Serie.<br>Was moechtest du bearbeiten?</p>
      <template #footer>
        <Button label="Abbrechen" text @click="showScopeDialog = false" />
        <Button label="Nur diesen Termin" outlined @click="openEditScope('occurrence')" />
        <Button label="Ganze Serie" @click="openEditScope('series')" />
      </template>
    </Dialog>

    <Dialog v-model:visible="confirmDeleteCal" header="Kalender loeschen" modal :style="{ width: '400px' }">
      <p>Moechtest du den Kalender <strong>{{ selectedCal?.name }}</strong> mit allen Terminen loeschen?</p>
      <template #footer>
        <Button label="Abbrechen" text @click="confirmDeleteCal = false" />
        <Button label="Loeschen" severity="danger" @click="deleteCalendar()" />
      </template>
    </Dialog>
  </div>
</template>

<script setup>
import { ref, computed, onMounted, onUnmounted, reactive, watch } from 'vue'
import { useToast } from 'primevue/usetoast'
import { useAuthStore } from '../stores/auth'
import apiClient from '../api/client'
import Button from 'primevue/button'
import Dialog from 'primevue/dialog'
import InputText from 'primevue/inputtext'
import Textarea from 'primevue/textarea'
import Select from 'primevue/select'
import SelectButton from 'primevue/selectbutton'
import FullCalendar from '@fullcalendar/vue3'
import dayGridPlugin from '@fullcalendar/daygrid'
import timeGridPlugin from '@fullcalendar/timegrid'
import interactionPlugin from '@fullcalendar/interaction'
import rrulePlugin from '@fullcalendar/rrule'
import deLocale from '@fullcalendar/core/locales/de'

const toast = useToast()
const auth = useAuthStore()
const origin = computed(() => window.location.origin)
const username = computed(() => auth.user?.username || '')
const calendars = ref([])
const visibleCalendars = reactive({})
const fcRef = ref(null)

const showNewCalendar = ref(false)
const newCalName = ref('')
const newCalColor = ref('#3788d8')

const viewMode = ref('calendar')
const viewModeOptions = [
  { label: 'Kalender', value: 'calendar' },
  { label: 'Liste', value: 'list' },
]
const listEvents = ref([])
const listSearch = ref('')
const listFrom = ref('')
const listTo = ref('')
const listCalFilter = ref(null)
const listSort = ref('dtstart')
const listSortDir = ref('asc')

const listCalOptions = computed(() => calendars.value.map(c => ({ label: c.name, value: c.id })))

const filteredListEvents = computed(() => {
  const q = listSearch.value.trim().toLowerCase()
  const fromDt = listFrom.value ? new Date(listFrom.value + 'T00:00:00') : null
  const toDt = listTo.value ? new Date(listTo.value + 'T23:59:59') : null
  let arr = listEvents.value.filter(e => {
    if (listCalFilter.value && e.calendar_id !== listCalFilter.value) return false
    if (fromDt && new Date(e.dtstart) < fromDt) return false
    if (toDt && new Date(e.dtstart) > toDt) return false
    if (q) {
      const hay = `${e.summary || ''} ${e.location || ''} ${e.description || ''}`.toLowerCase()
      if (!hay.includes(q)) return false
    }
    return true
  })
  const dir = listSortDir.value === 'asc' ? 1 : -1
  const key = listSort.value
  arr = arr.slice().sort((a, b) => {
    const av = key === 'dtstart' ? new Date(a.dtstart).getTime() : (a.summary || '').toLowerCase()
    const bv = key === 'dtstart' ? new Date(b.dtstart).getTime() : (b.summary || '').toLowerCase()
    return av < bv ? -dir : av > bv ? dir : 0
  })
  return arr
})

function toggleListSort(col) {
  if (listSort.value === col) listSortDir.value = listSortDir.value === 'asc' ? 'desc' : 'asc'
  else { listSort.value = col; listSortDir.value = 'asc' }
}

function formatListDate(ev) {
  const d = new Date(ev.dtstart)
  return d.toLocaleDateString('de-DE', { weekday: 'short', day: '2-digit', month: '2-digit', year: 'numeric' })
}
function formatListTime(ev) {
  const fmt = d => new Date(d).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })
  return ev.dtend ? `${fmt(ev.dtstart)}–${fmt(ev.dtend)}` : fmt(ev.dtstart)
}

async function loadListEvents() {
  // Lade alle Termine ueber die letzten 90 Tage und naechsten 365 Tage hinweg.
  const start = new Date(); start.setDate(start.getDate() - 90)
  const end = new Date(); end.setDate(end.getDate() + 365)
  const all = []
  for (const cal of calendars.value) {
    if (visibleCalendars[cal.id] === false) continue
    try {
      const res = await apiClient.get(`/calendars/${cal.id}/events`, {
        params: { start: start.toISOString(), end: end.toISOString() },
      })
      for (const e of res.data) all.push({ ...e, _cal: cal })
    } catch { /* ignore */ }
  }
  listEvents.value = all
}

async function confirmDeleteListEvent(ev) {
  if (!confirm(`Termin "${ev.summary || '(ohne Titel)'}" wirklich loeschen?`)) return
  try {
    await apiClient.delete(`/events/${ev.id}`)
    toast.add({ severity: 'success', summary: 'Geloescht', life: 2000 })
    await loadListEvents()
    refreshEvents()
  } catch (err) {
    toast.add({ severity: 'error', summary: 'Fehler', detail: err.response?.data?.error, life: 5000 })
  }
}

const showEventDialog = ref(false)
const editingEvent = ref(null)
const eventForm = ref({
  summary: '', description: '', location: '',
  calendar_id: null, dtstart: '', dtend: '', all_day: false, is_private: false,
})

// Recurrence editor state
const recurFreq = ref('none')
const recurInterval = ref(1)
const recurEndMode = ref('forever')
const recurCount = ref(10)
const recurUntil = ref('')
const recurWeekdays = ref([])
const recurByDayOfWeek = ref(false)
const recurBySetPos = ref(1)
const recurByDay = ref('MO')

const recurFreqOptions = [
  { label: 'Keine Wiederholung', value: 'none' },
  { label: 'Taeglich', value: 'daily' },
  { label: 'Woechentlich', value: 'weekly' },
  { label: 'Monatlich', value: 'monthly' },
  { label: 'Jaehrlich', value: 'yearly' },
]
const recurEndOptions = [
  { label: 'Kein Ende', value: 'forever' },
  { label: 'Bis Datum', value: 'until' },
  { label: 'Nach Anzahl', value: 'count' },
]
const weekdayBtns = [
  { label: 'Mo', value: 'MO' }, { label: 'Di', value: 'TU' },
  { label: 'Mi', value: 'WE' }, { label: 'Do', value: 'TH' },
  { label: 'Fr', value: 'FR' }, { label: 'Sa', value: 'SA' }, { label: 'So', value: 'SU' },
]
const setPosOptions = [
  { label: '1.', value: 1 }, { label: '2.', value: 2 }, { label: '3.', value: 3 },
  { label: '4.', value: 4 }, { label: 'letzter', value: -1 },
]
const recurUnitLabel = computed(() => ({
  daily: 'Tag(e)', weekly: 'Woche(n)', monthly: 'Monat(e)', yearly: 'Jahr(e)',
}[recurFreq.value] || ''))

const showCalMenu = ref(false)
const selectedCal = ref(null)
const shareUsername = ref('')
const sharePermission = ref('read')
const shareSearchResults = ref([])
const calendarShares = ref([])
const editingShareId = ref(null)
const editSharePermission = ref('read')
let shareSearchTimer = null
const icalPassword = ref('')
const confirmDeleteEvent = ref(false)
const confirmDeleteCal = ref(false)
const showScopeDialog = ref(false)
const pendingScopeEvent = ref(null)
const pendingScopeOccurrence = ref(null)
// 'series' (edit master), 'occurrence' (split off single date), or null (non-recurring)
const currentEditScope = ref(null)
const permOptions = [{ label: 'Lesen', value: 'read' }, { label: 'Lesen+Schreiben', value: 'readwrite' }]

const ownCalendars = computed(() => calendars.value.filter(c => c.permission === 'owner'))
const fullIcalUrl = computed(() =>
  selectedCal.value?.ical_token ? `${window.location.origin}/ical/${selectedCal.value.ical_token}` : ''
)

// FullCalendar configuration
const calendarOptions = computed(() => ({
  plugins: [dayGridPlugin, timeGridPlugin, interactionPlugin, rrulePlugin],
  initialView: 'dayGridMonth',
  headerToolbar: {
    left: 'prev,next today',
    center: 'title',
    right: 'dayGridMonth,timeGridWeek,timeGridDay',
  },
  navLinks: true,
  navLinkDayClick: (date) => fcRef.value?.getApi().changeView('timeGridDay', date),
  locale: deLocale,
  firstDay: 1,
  nowIndicator: true,
  editable: true,
  droppable: true,
  eventDurationEditable: true,
  selectable: true,
  weekNumbers: true,
  height: 'auto',
  allDaySlot: true,
  events: fetchEvents,
  eventClick: onEventClick,
  select: onDateSelect,
  eventDrop: onEventDrop,
  eventResize: onEventDrop,
  displayEventEnd: true,
  eventTimeFormat: { hour: '2-digit', minute: '2-digit', hour12: false },
  // Force bar/block display even for timed events so the month view
  // doesn't switch between dot+time and full bar depending on how
  // the event was created.
  eventDisplay: 'block',
}))

function formatEventTime(ev) {
  if (!ev.start) return ''
  const fmt = d => d.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })
  if (!ev.end) return fmt(ev.start)
  return `${fmt(ev.start)}–${fmt(ev.end)}`
}

function eventTooltip(ev) {
  const p = ev.extendedProps
  const parts = [ev.title]
  if (p.all_day) parts.push('Ganztaegig')
  else if (ev.start) {
    const fmt = d => d.toLocaleString('de-DE', { hour: '2-digit', minute: '2-digit' })
    parts.push(ev.end ? `${fmt(ev.start)}–${fmt(ev.end)}` : fmt(ev.start))
  }
  if (p.recurrence_rule) parts.push('Wiederholung')
  if (p.location) parts.push('Ort: ' + p.location)
  if (p.description) parts.push(p.description)
  return parts.join(' • ')
}

async function fetchEvents(info, successCallback, failureCallback) {
  try {
    const all = []
    for (const cal of calendars.value) {
      if (visibleCalendars[cal.id] === false) continue
      const res = await apiClient.get(`/calendars/${cal.id}/events`, {
        params: { start: info.startStr, end: info.endStr },
      })
      for (const e of res.data) {
        all.push(toFcEvent(e, cal))
      }
    }
    successCallback(all)
  } catch (err) {
    failureCallback(err)
  }
}

function toFcEvent(e, cal) {
  const fc = {
    id: String(e.id),
    title: e.summary,
    color: cal.color,
    allDay: e.all_day,
    extendedProps: { ...e, _cal: cal },
  }
  if (e.recurrence_rule) {
    fc.rrule = {
      freq: extractFreq(e.recurrence_rule),
      dtstart: e.dtstart,
      ...parseRRule(e.recurrence_rule),
    }
    if (e.exdates && e.exdates.length) {
      fc.exdate = e.exdates
    }
    if (e.dtend && e.dtstart) {
      const ms = new Date(e.dtend).getTime() - new Date(e.dtstart).getTime()
      if (ms > 0) fc.duration = msToDuration(ms)
    }
  } else {
    fc.start = e.dtstart
    fc.end = e.dtend
  }
  return fc
}

function extractFreq(rrule) {
  const m = /FREQ=([A-Z]+)/.exec(rrule)
  return m ? m[1].toLowerCase() : 'daily'
}

function parseRRule(rrule) {
  const out = {}
  const parts = rrule.replace(/^RRULE:/i, '').split(';')
  for (const p of parts) {
    const [k, v] = p.split('=')
    if (!v) continue
    const key = k.toUpperCase()
    if (key === 'INTERVAL') out.interval = parseInt(v)
    else if (key === 'COUNT') out.count = parseInt(v)
    else if (key === 'UNTIL') out.until = v
    else if (key === 'BYDAY') out.byweekday = v.split(',')
    else if (key === 'BYSETPOS') out.bysetpos = v.split(',').map(Number)
  }
  return out
}

function msToDuration(ms) {
  const hours = Math.floor(ms / 3600000)
  const minutes = Math.floor((ms % 3600000) / 60000)
  return { hours, minutes }
}

function buildRRule() {
  if (recurFreq.value === 'none') return ''
  const parts = [`FREQ=${recurFreq.value.toUpperCase()}`]
  if (recurInterval.value > 1) parts.push(`INTERVAL=${recurInterval.value}`)
  if (recurEndMode.value === 'count') parts.push(`COUNT=${recurCount.value}`)
  if (recurEndMode.value === 'until' && recurUntil.value) {
    parts.push(`UNTIL=${recurUntil.value.replace(/-/g, '')}T235959Z`)
  }
  if (recurFreq.value === 'weekly' && recurWeekdays.value.length) {
    parts.push(`BYDAY=${recurWeekdays.value.join(',')}`)
  }
  if (recurFreq.value === 'monthly' && recurByDayOfWeek.value) {
    parts.push(`BYDAY=${recurByDay.value}`)
    parts.push(`BYSETPOS=${recurBySetPos.value}`)
  }
  return parts.join(';')
}

function loadRRuleIntoForm(rrule) {
  if (!rrule) {
    recurFreq.value = 'none'
    return
  }
  const parsed = parseRRule(rrule)
  recurFreq.value = extractFreq(rrule)
  recurInterval.value = parsed.interval || 1
  if (parsed.count) { recurEndMode.value = 'count'; recurCount.value = parsed.count }
  else if (parsed.until) {
    recurEndMode.value = 'until'
    // UNTIL=20261231T235959Z -> 2026-12-31
    const d = parsed.until.slice(0, 8)
    recurUntil.value = `${d.slice(0, 4)}-${d.slice(4, 6)}-${d.slice(6, 8)}`
  } else recurEndMode.value = 'forever'
  recurWeekdays.value = parsed.byweekday || []
  if (parsed.bysetpos) {
    recurByDayOfWeek.value = true
    recurBySetPos.value = parsed.bysetpos[0]
    recurByDay.value = (parsed.byweekday || ['MO'])[0]
  } else {
    recurByDayOfWeek.value = false
  }
}

function onEventClick(info) {
  const e = info.event.extendedProps
  if (e.recurrence_rule) {
    pendingScopeEvent.value = e
    pendingScopeOccurrence.value = info.event.start
    showScopeDialog.value = true
    return
  }
  currentEditScope.value = null
  openEditEvent(e)
}

function openEditScope(scope) {
  showScopeDialog.value = false
  const e = pendingScopeEvent.value
  const occ = pendingScopeOccurrence.value
  if (scope === 'series') {
    currentEditScope.value = 'series'
    openEditEvent(e)
  } else {
    // "Nur diesen Termin" - form starts from the clicked occurrence's
    // start/end, non-recurring. Save will add an EXDATE on the master
    // plus create a standalone event.
    currentEditScope.value = 'occurrence'
    editingEvent.value = e
    const durationMs = e.dtend ? new Date(e.dtend) - new Date(e.dtstart) : 3600000
    const occEnd = new Date(occ.getTime() + durationMs)
    eventForm.value = {
      summary: e.summary,
      description: e.description || '',
      location: e.location || '',
      calendar_id: e.calendar_id,
      dtstart: toLocalISO(occ, e.all_day),
      dtend: toLocalISO(occEnd, e.all_day),
      all_day: e.all_day,
    }
    loadRRuleIntoForm('')
    // Freeze recurrence editor - this is a single instance
    recurFreq.value = 'none'
    showEventDialog.value = true
  }
}

function onDateSelect(info) {
  openNewEvent(info.start, info.end, info.allDay)
}

async function onEventDrop(info) {
  const e = info.event.extendedProps
  try {
    await apiClient.put(`/events/${e.id}`, {
      dtstart: toServerISO(info.event.start, e.all_day),
      dtend: info.event.end ? toServerISO(info.event.end, e.all_day) : null,
    })
    toast.add({ severity: 'success', summary: 'Verschoben', life: 2000 })
  } catch (err) {
    info.revert()
    toast.add({ severity: 'error', summary: 'Fehler', detail: err.response?.data?.error, life: 4000 })
  }
}

function refreshEvents() {
  fcRef.value?.getApi().refetchEvents()
  if (viewMode.value === 'list') loadListEvents()
}

async function loadCalendars() {
  const res = await apiClient.get('/calendars')
  calendars.value = res.data
  for (const c of calendars.value) {
    if (!(c.id in visibleCalendars)) visibleCalendars[c.id] = true
  }
  if (!calendars.value.length) {
    await apiClient.post('/calendars', { name: 'Mein Kalender', color: '#3788d8' })
    await loadCalendars()
  }
}

async function createCalendar() {
  if (!newCalName.value.trim()) return
  await apiClient.post('/calendars', { name: newCalName.value.trim(), color: newCalColor.value })
  showNewCalendar.value = false
  newCalName.value = ''
  await loadCalendars()
  refreshEvents()
}

function openNewEvent(start, end, allDay = false) {
  editingEvent.value = null
  const now = start || new Date()
  const later = end || new Date(now.getTime() + 3600000)
  eventForm.value = {
    summary: '',
    description: '',
    location: '',
    calendar_id: ownCalendars.value[0]?.id,
    dtstart: toLocalISO(now, allDay),
    dtend: toLocalISO(later, allDay),
    all_day: allDay,
    is_private: false,
  }
  loadRRuleIntoForm('')
  showEventDialog.value = true
}

function openEditEvent(evt) {
  editingEvent.value = evt
  eventForm.value = {
    summary: evt.summary,
    description: evt.description || '',
    location: evt.location || '',
    calendar_id: evt.calendar_id,
    dtstart: toLocalISO(new Date(evt.dtstart), evt.all_day),
    dtend: evt.dtend ? toLocalISO(new Date(evt.dtend), evt.all_day) : '',
    all_day: evt.all_day,
    is_private: !!evt.is_private,
  }
  loadRRuleIntoForm(evt.recurrence_rule)
  showEventDialog.value = true
}

async function saveEvent() {
  if (!eventForm.value.summary.trim()) return
  const payload = {
    summary: eventForm.value.summary.trim(),
    description: eventForm.value.description,
    location: eventForm.value.location,
    dtstart: fromLocalISO(eventForm.value.dtstart, eventForm.value.all_day),
    dtend: eventForm.value.dtend ? fromLocalISO(eventForm.value.dtend, eventForm.value.all_day) : null,
    all_day: eventForm.value.all_day,
    is_private: eventForm.value.is_private,
    recurrence_rule: buildRRule(),
    calendar_id: eventForm.value.calendar_id,
  }
  try {
    if (currentEditScope.value === 'occurrence' && editingEvent.value) {
      // Split single occurrence off the series: add EXDATE + create replacement
      await apiClient.post(`/events/${editingEvent.value.id}/exception`, {
        occurrence_date: toLocalISO(pendingScopeOccurrence.value, editingEvent.value.all_day),
        replacement: {
          summary: payload.summary,
          description: payload.description,
          location: payload.location,
          dtstart: payload.dtstart,
          dtend: payload.dtend,
          all_day: payload.all_day,
        },
      })
    } else if (editingEvent.value) {
      await apiClient.put(`/events/${editingEvent.value.id}`, payload)
    } else {
      await apiClient.post(`/calendars/${payload.calendar_id}/events`, payload)
    }
    showEventDialog.value = false
    currentEditScope.value = null
    pendingScopeEvent.value = null
    pendingScopeOccurrence.value = null
    refreshEvents()
  } catch (err) {
    toast.add({ severity: 'error', summary: 'Fehler', detail: err.response?.data?.error, life: 5000 })
  }
}

async function deleteEvent() {
  if (!editingEvent.value) return
  try {
    await apiClient.delete(`/events/${editingEvent.value.id}`)
    confirmDeleteEvent.value = false
    showEventDialog.value = false
    refreshEvents()
  } catch (err) {
    toast.add({ severity: 'error', summary: 'Fehler', detail: err.response?.data?.error, life: 5000 })
  }
}

function openCalendarMenu(cal) {
  selectedCal.value = cal
  icalPassword.value = ''
  shareUsername.value = ''
  shareSearchResults.value = []
  showCalMenu.value = true
  loadShares()
}

function onShareSearch() {
  clearTimeout(shareSearchTimer)
  const q = shareUsername.value.trim()
  if (q.length < 2) { shareSearchResults.value = []; return }
  shareSearchTimer = setTimeout(async () => {
    try {
      const res = await apiClient.get('/users/search', { params: { q } })
      shareSearchResults.value = res.data
    } catch { shareSearchResults.value = [] }
  }, 250)
}

async function shareCalendar() {
  if (!shareUsername.value.trim() || !selectedCal.value) return
  try {
    await apiClient.post(`/calendars/${selectedCal.value.id}/share`, {
      username: shareUsername.value.trim(), permission: sharePermission.value,
    })
    toast.add({ severity: 'success', summary: 'Kalender geteilt', life: 3000 })
    shareUsername.value = ''
    shareSearchResults.value = []
    await loadShares()
  } catch (err) {
    toast.add({ severity: 'error', summary: 'Fehler', detail: err.response?.data?.error || err.message, life: 5000 })
  }
}

async function loadShares() {
  if (!selectedCal.value || selectedCal.value.permission !== 'owner') {
    calendarShares.value = []
    return
  }
  try {
    const res = await apiClient.get(`/calendars/${selectedCal.value.id}/shares`)
    calendarShares.value = res.data
  } catch { calendarShares.value = [] }
}

function startEditShare(s) {
  editingShareId.value = s.id
  editSharePermission.value = s.permission
}

async function saveEditShare(s) {
  if (!selectedCal.value) return
  try {
    await apiClient.post(`/calendars/${selectedCal.value.id}/share`, {
      username: s.username,
      permission: editSharePermission.value,
    })
    editingShareId.value = null
    await loadShares()
    toast.add({ severity: 'success', summary: 'Berechtigung aktualisiert', life: 2500 })
  } catch (err) {
    toast.add({ severity: 'error', summary: 'Fehler', detail: err.response?.data?.error || err.message, life: 5000 })
  }
}

async function removeShare(shareId) {
  if (!selectedCal.value) return
  try {
    await apiClient.delete(`/calendars/${selectedCal.value.id}/shares/${shareId}`)
    await loadShares()
    toast.add({ severity: 'success', summary: 'Freigabe entfernt', life: 2500 })
  } catch (err) {
    toast.add({ severity: 'error', summary: 'Fehler', detail: err.response?.data?.error, life: 5000 })
  }
}

async function generateIcalLink() {
  if (!selectedCal.value) return
  const body = {}
  if (icalPassword.value) body.password = icalPassword.value
  const res = await apiClient.post(`/calendars/${selectedCal.value.id}/ical-link`, body)
  selectedCal.value.ical_token = res.data.token
  selectedCal.value.ical_has_password = res.data.has_password
  icalPassword.value = ''
  toast.add({ severity: 'success', summary: 'Link erstellt', life: 2500 })
}

async function setIcalPassword() {
  if (!selectedCal.value || !icalPassword.value) return
  const res = await apiClient.post(`/calendars/${selectedCal.value.id}/ical-link`, { password: icalPassword.value })
  selectedCal.value.ical_has_password = res.data.has_password
  icalPassword.value = ''
  toast.add({ severity: 'success', summary: 'Passwort gesetzt', life: 2500 })
}

async function clearIcalPassword() {
  if (!selectedCal.value) return
  const res = await apiClient.post(`/calendars/${selectedCal.value.id}/ical-link`, { clear_password: true })
  selectedCal.value.ical_has_password = res.data.has_password
  toast.add({ severity: 'success', summary: 'Passwort entfernt', life: 2500 })
}

async function revokeIcal() {
  if (!selectedCal.value) return
  if (!confirm('Link zurueckziehen? Abonnenten koennen danach nicht mehr zugreifen.')) return
  await apiClient.delete(`/calendars/${selectedCal.value.id}/ical-link`)
  selectedCal.value.ical_token = null
  selectedCal.value.ical_has_password = false
  toast.add({ severity: 'success', summary: 'Link zurueckgezogen', life: 2500 })
}

function copyIcal() {
  navigator.clipboard.writeText(fullIcalUrl.value)
  toast.add({ severity: 'info', summary: 'Link kopiert', life: 2000 })
}

function copyText(text) {
  navigator.clipboard.writeText(text)
  toast.add({ severity: 'info', summary: 'Kopiert', life: 1500 })
}

async function onColorChange(ev) {
  if (!selectedCal.value) return
  const color = ev.target.value
  try {
    const res = await apiClient.put(`/calendars/${selectedCal.value.id}/my-color`, { color })
    selectedCal.value.color = res.data.color
    await loadCalendars()
    refreshEvents()
    toast.add({ severity: 'success', summary: 'Farbe aktualisiert', life: 2000 })
  } catch (err) {
    toast.add({ severity: 'error', summary: 'Fehler', detail: err.response?.data?.error, life: 4000 })
  }
}

async function deleteCalendar() {
  if (!selectedCal.value) return
  await apiClient.delete(`/calendars/${selectedCal.value.id}`)
  showCalMenu.value = false
  confirmDeleteCal.value = false
  await loadCalendars()
  refreshEvents()
}

function toLocalISO(date, allDay = false) {
  const pad = n => String(n).padStart(2, '0')
  const d = `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`
  if (allDay) return d
  return `${d}T${pad(date.getHours())}:${pad(date.getMinutes())}`
}

function fromLocalISO(s, allDay = false) {
  // datetime-local strings are in local tz - just pass through
  if (allDay) return s + 'T00:00:00'
  return s
}

function toServerISO(date, allDay = false) {
  return toLocalISO(date, allDay)
}

watch(() => eventForm.value.all_day, (allDay) => {
  // Toggle between date / datetime-local - strip or add time part
  const fix = (v) => {
    if (!v) return v
    if (allDay) return v.length > 10 ? v.slice(0, 10) : v
    return v.length === 10 ? `${v}T09:00` : v
  }
  eventForm.value.dtstart = fix(eventForm.value.dtstart)
  eventForm.value.dtend = fix(eventForm.value.dtend)
})

// Live updates via SSE: refresh calendar list + events when anything
// relevant changes (own or shared).
let eventSource = null
let reloadTimer = null

function scheduleReload() {
  if (reloadTimer) return
  reloadTimer = setTimeout(async () => {
    reloadTimer = null
    await loadCalendars()
    refreshEvents()
    if (viewMode.value === 'list') await loadListEvents()
  }, 300)
}

watch(viewMode, async (mode) => {
  if (mode === 'list') await loadListEvents()
})

onMounted(async () => {
  await loadCalendars()
  refreshEvents()
  if (auth.accessToken) {
    try {
      eventSource = new EventSource(`/api/sync/events?token=${encodeURIComponent(auth.accessToken)}`)
      eventSource.addEventListener('calendar', scheduleReload)
      eventSource.addEventListener('message', scheduleReload)
      eventSource.onerror = () => { /* auto-reconnects */ }
    } catch { /* ignore */ }
  }
})

onUnmounted(() => {
  if (reloadTimer) { clearTimeout(reloadTimer); reloadTimer = null }
  if (eventSource) { eventSource.close(); eventSource = null }
})
</script>

<style scoped>
.view-container { padding: 1.5rem; }
.view-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem; }
.view-header h2 { margin: 0; }
.header-actions { display: flex; gap: 0.5rem; }
.calendar-layout { display: flex; gap: 1rem; align-items: flex-start; }
.calendar-sidebar { width: 240px; flex-shrink: 0; }
.calendar-sidebar h4 { margin: 0 0 0.5rem; font-size: 0.85rem; text-transform: uppercase; color: var(--p-text-muted-color); }
.calendar-item { display: flex; align-items: center; gap: 0.5rem; padding: 0.375rem 0; font-size: 0.875rem; }
.calendar-color { width: 12px; height: 12px; border-radius: 3px; flex-shrink: 0; }
.cal-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.shared-label { color: var(--p-text-muted-color); font-size: 0.7rem; }
.calendar-main { flex: 1; min-width: 0; }
.field { margin-bottom: 0.75rem; }
.field label { display: block; margin-bottom: 0.25rem; font-weight: 500; font-size: 0.875rem; }
.field-row { display: flex; gap: 0.75rem; }
.field-row .field { flex: 1; }
.recur-details { padding: 0.75rem; background: var(--p-surface-50); border-radius: 6px; margin-top: 0.5rem; }
.recur-unit { display: inline-block; margin-left: 0.5rem; font-size: 0.85rem; color: var(--p-text-muted-color); }
.weekday-row { display: flex; gap: 0.25rem; flex-wrap: wrap; }
.weekday-btn { display: flex; align-items: center; gap: 0.25rem; padding: 0.25rem 0.5rem; border-radius: 4px;
               background: var(--p-surface-100); font-size: 0.85rem; cursor: pointer; }
.weekday-btn.active { background: var(--p-primary-100); }
.share-row { display: flex; gap: 0.5rem; align-items: center; }
.user-search-popup { position: absolute; top: 100%; left: 0; right: 0; z-index: 10;
                     background: white; border: 1px solid var(--p-surface-200); border-radius: 4px;
                     max-height: 160px; overflow-y: auto; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
.user-result { padding: 0.5rem 0.75rem; cursor: pointer; font-size: 0.875rem; display: flex; gap: 0.5rem; align-items: center; }
.user-result:hover { background: var(--p-primary-50); }
.existing-shares { margin-top: 0.5rem; }
.share-perm-item { display: flex; align-items: center; gap: 0.5rem; padding: 0.375rem 0; font-size: 0.875rem; flex-wrap: wrap; }
.share-perm-item.editing { background: var(--p-surface-50); padding: 0.5rem; border-radius: 4px; }
.perm-label { color: var(--p-text-muted-color); font-size: 0.75rem; }
.ical-block { border-top: 1px solid var(--p-surface-200); padding-top: 1rem; margin-top: 1rem; }
.ical-url { font-size: 0.8rem; display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
.ical-url code { background: var(--p-surface-100); padding: 0.25rem 0.5rem; border-radius: 4px; word-break: break-all; }
.caldav-block { border-top: 1px solid var(--p-surface-200); padding-top: 1rem; margin-top: 1rem; }
.caldav-hint { font-size: 0.8rem; color: var(--p-text-muted-color); margin: 0 0 0.75rem; }
.url-row { display: flex; gap: 0.5rem; align-items: center; margin-bottom: 0.5rem; flex-wrap: wrap; }
.url-row strong { min-width: 160px; font-size: 0.8rem; }
.url-row code { background: var(--p-surface-100); padding: 0.25rem 0.5rem; border-radius: 4px;
                font-size: 0.8rem; word-break: break-all; flex: 1; }
.caldav-clients { font-size: 0.75rem; color: var(--p-text-muted-color); margin-top: 0.5rem; }
.caldav-clients div { margin-bottom: 0.15rem; }
.color-hint { font-size: 0.75rem; color: var(--p-text-muted-color); font-style: italic; }
.hint-badge { font-size: 0.75rem; color: var(--p-primary-700); display: inline-flex; gap: 0.25rem; align-items: center; }
.fc-event-content-inner { display: flex; align-items: center; gap: 0.2rem; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; padding: 0 2px; }
.fc-event-content-inner .fc-icon { flex-shrink: 0; font-size: 0.85em; }
.fc-event-content-inner .fc-time { flex-shrink: 0; font-weight: 600; font-size: 0.8em; opacity: 0.9; }
.fc-event-content-inner .fc-title { overflow: hidden; text-overflow: ellipsis; }

.list-view { display: flex; flex-direction: column; gap: 0.75rem; }
.list-filters { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
.list-meta { font-size: 0.8rem; color: var(--p-text-muted-color); }
.list-table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }
.list-table th { text-align: left; padding: 0.5rem; border-bottom: 2px solid var(--p-surface-200); font-weight: 600; user-select: none; }
.list-table th.sortable { cursor: pointer; }
.list-table th.sortable:hover { background: var(--p-surface-50); }
.list-table td { padding: 0.5rem; border-bottom: 1px solid var(--p-surface-100); vertical-align: top; }
.list-row { cursor: pointer; }
.list-row:hover { background: var(--p-surface-50); }
.col-date { white-space: nowrap; min-width: 140px; }
.col-title { font-weight: 500; }
.col-actions { width: 60px; text-align: right; }
.cal-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 0.4rem; vertical-align: middle; }
.meta-time { font-size: 0.75rem; color: var(--p-text-muted-color); }
.empty-row { text-align: center; padding: 2rem !important; color: var(--p-text-muted-color); }
</style>
