<!--
Calendar implementation for the HTWK Leipzig timetable. Evaluation and display of the individual dates in iCal format.
Copyright (C) 2024 HTWKalender support@htwkalender.de

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
-->

<script setup lang="ts">
import FullCalendar from "@fullcalendar/vue3";
import { computed, ComputedRef, inject, Ref, ref, watch } from "vue";
import {
  CalendarOptions,
  DatesSetArg,
  EventClickArg,
} from "@fullcalendar/core";
import allLocales from "@fullcalendar/core/locales-all";
import dayGridPlugin from "@fullcalendar/daygrid";
import interactionPlugin from "@fullcalendar/interaction";
import timeGridPlugin from "@fullcalendar/timegrid";
import iCalenderPlugin from "@fullcalendar/icalendar";
import router from "@/router";
import { formatYearMonthDay } from "@/helpers/dates.ts";
import { useI18n } from "vue-i18n";
import { useQuery, useQueryClient } from "@tanstack/vue-query";
import tokenStore from "@/store/tokenStore.ts";
import { parseICalData } from "@/helpers/ical.ts";
import { fetchICalendarEvents } from "@/api/loadICal.ts";

const { t } = useI18n({ useScope: "global" });

const props = defineProps({
  token: {
    type: String,
    required: true,
  },
});

const op = ref();
const clickedEvent = ref();

const toggle = (info: EventClickArg) => {
  const start = !info.event.start ? "" : info.event.start;
  const end = !info.event.end ? "" : info.event.end;

  if (op.value.visible) {
    clickedEvent.value = null;
    op.value.hide();
    return;
  } else {
    clickedEvent.value = {
      title: info.event._def.title,
      start: start,
      end: end,
      notes: info.event._def.extendedProps.notes,
      allDay: info.event._def.allDay,
      location: info.event._def.extendedProps.location,
    };
    op.value.show(info.jsEvent);
    op.value.target = info.el;
  }
};

const selectedToken = computed(() => {
  return props.token;
});

const mobilePage = inject("mobilePage") as Ref<boolean>;
const date: Ref<Date> = ref(new Date());

const { data: calendar } = useQuery({
  queryKey: ["userCalendar", selectedToken],
  queryFn: () => fetchICalendarEvents(selectedToken.value),
  select: (data) => {
    return data;
  },
  staleTime: 12 * 60 * 60 * 1000, // 12 hours
  refetchOnWindowFocus: "always",
  refetchOnReconnect: "always",
  networkMode: "offlineFirst",
  enabled: () => tokenStore().token !== "",
});

const queryClient = useQueryClient();

const invalidateAndRefetchCalendar = () => {
  console.debug("invalidateAndRefetchCalendar", selectedToken);
  const queryKey = ["userCalendar", selectedToken];
  queryClient.invalidateQueries({ queryKey: queryKey }).then(() => {
    queryClient.refetchQueries({ queryKey: queryKey });
  });
};

defineExpose({
  invalidateAndRefetchCalendar,
});

const events = computed(() => {
  return parseICalData(calendar.value);
});

const fullCalendar = ref<InstanceType<typeof FullCalendar>>();

const calendarOptions: ComputedRef<CalendarOptions> = computed(() => ({
  locales: allLocales,
  locale: t("languageCode"),
  plugins: [dayGridPlugin, interactionPlugin, timeGridPlugin, iCalenderPlugin],
  // local debugging of mobilePage variable in object creation on ternary expression
  initialView: mobilePage.value ? "Day" : "week",
  initialDate: date.value,
  dayHeaderFormat: { weekday: "short", omitCommas: true },
  slotDuration: "00:15:00",
  eventTimeFormat: {
    hour: "2-digit",
    minute: "2-digit",
    hour12: false,
  },
  eventClick(info) {
    toggle(info);
  },
  height: "auto",
  views: {
    week: {
      type: "timeGrid",
      slotLabelFormat: {
        hour: "numeric",
        minute: "2-digit",
        omitZeroMinute: false,
        meridiem: false,
        hour12: false,
      },
      dateAlignment: "week",
      titleFormat: { month: "short", day: "numeric" },
      slotMinTime: "06:00:00",
      slotMaxTime: "22:00:00",
      duration: { days: 7 },
      firstDay: 1,
      allDaySlot: false,
      hiddenDays: [0],
    },
    Day: {
      type: "timeGrid",
      slotLabelFormat: {
        hour: "numeric",
        minute: "2-digit",
        omitZeroMinute: false,
        meridiem: false,
        hour12: false,
      },
      titleFormat: { month: "short", day: "numeric" },
      slotMinTime: "06:00:00",
      slotMaxTime: "22:00:00",
      duration: { days: 1 },
      allDaySlot: false,
      hiddenDays: [0],
    },
  },
  headerToolbar: {
    end: "prev,next today",
    center: "title",
    start: "week,Day",
  },

  datesSet: function (dateInfo: DatesSetArg) {
    const view = dateInfo.view;
    const offset = new Date().getTimezoneOffset();
    const endDate = new Date(view.activeEnd.getTime() - offset * 60 * 1000);
    router.replace({
      query: {
        ...router.currentRoute.value.query,
        date: formatYearMonthDay(endDate),
      },
    });
  },
  events: events.value,
}));

watch(mobilePage, () => {
  fullCalendar.value?.getApi().changeView(mobilePage.value ? "Day" : "week");
});
</script>

<template>
  <FullCalendar
    id="overlay-mount-point"
    ref="fullCalendar"
    :options="calendarOptions"
  >
  </FullCalendar>

  <OverlayPanel ref="op">
    <div>
      <h3>{{ clickedEvent.title }}</h3>
      <p>Location: {{ clickedEvent.location }}</p>
      <p>Start: {{ clickedEvent.start?.toLocaleString() }}</p>
      <p>End: {{ clickedEvent.end?.toLocaleString() }}</p>
      <p>Notes: {{ clickedEvent.notes }}</p>
    </div>
  </OverlayPanel>
</template>

<style scoped>
:deep(.fc-toolbar.fc-header-toolbar) {
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.5rem;
}
</style>
