<script>
import { defineComponent } from "vue";
import {
  courseBundles,
  createLessonBundles,
  deleteLessonBundles,
  gqlGroups,
  lessonBundles,
  moveLessonBundles,
  overlayBundles,
  updateLessons,
} from "./timetableManagement.graphql";
import { gqlTeachers } from "../helper.graphql";
import { timeGrids } from "../validity_range/validityRange.graphql";
import { slots } from "../breaks_and_slots/slot.graphql";
import { rooms } from "aleksis.core/components/room/room.graphql";
import MobileFullscreenDialog from "aleksis.core/components/generic/dialogs/MobileFullscreenDialog.vue";
import DeleteDialog from "aleksis.core/components/generic/dialogs/DeleteDialog.vue";
import DialogObjectForm from "aleksis.core/components/generic/dialogs/DialogObjectForm.vue";
import SecondaryActionButton from "aleksis.core/components/generic/buttons/SecondaryActionButton.vue";
import SubjectField from "aleksis.apps.cursus/components/SubjectField.vue";
import BundleCard from "./BundleCard.vue";

import { RRule } from "rrule";
import TeacherTimeTable from "../timetables/TeacherTimeTable.vue";
import RoomTimeTable from "../timetables/RoomTimeTable.vue";
import LessonRatioChip from "./LessonRatioChip.vue";
import TimeGridField from "../validity_range/TimeGridField.vue";
import BlockingCard from "./BlockingCard.vue";
import PeriodCard from "./PeriodCard.vue";
import TimetableOverlayCard from "./TimetableOverlayCard.vue";

import bundleAccessorsMixin from "../../mixins/bundleAccessorsMixin.js";

export default defineComponent({
  name: "TimetableManagement",
  components: {
    TimetableOverlayCard,
    PeriodCard,
    BlockingCard,
    TimeGridField,
    SubjectField,
    DialogObjectForm,
    LessonRatioChip,
    MobileFullscreenDialog,
    RoomTimeTable,
    TeacherTimeTable,
    DeleteDialog,
    BundleCard,
    SecondaryActionButton,
  },
  mixins: [bundleAccessorsMixin],
  data() {
    return {
      weekdays: [],
      periods: [],
      slotsByPeriods: [],
      internalTimeGrid: null,
      courseSearch: null,
      lessonsUsed: {},
      lessonQuotaTotal: 0,
      deleteMutation: deleteLessonBundles,
      deleteDialog: false,
      itemsToDelete: [],
      selectedObject: null,
      selectedObjectType: null,
      selectedObjectTitle: "",
      selectedObjectDialogOpen: false,
      selectedObjectDialogTab: 0,
      timeGrids: [],
      groups: [],
      selectedGroup: null,
      lessonEdit: {
        open: false,
        id: null,
        object: {},
        fields: [
          {
            text: this.$t(
              "lesrooster.timetable_management.lesson_fields.subject",
            ),
            value: "subject",
          },
          {
            text: this.$t(
              "lesrooster.timetable_management.lesson_fields.teachers",
            ),
            value: "teachers",
          },
          {
            text: this.$t(
              "lesrooster.timetable_management.lesson_fields.rooms",
            ),
            value: "rooms",
          },
        ],
        mutation: updateLessons,
      },
      draggedItem: null,
      overlayBundles: [],
    };
  },
  apollo: {
    groups: {
      query: gqlGroups,
      variables() {
        return {
          timeGrid: this.internalTimeGrid.id,
        };
      },
      skip() {
        return this.internalTimeGrid?.id == null;
      },
      result() {
        if (this.$route.params.group && this.groups) {
          this.selectedGroup = this.groups.find(
            (group) => group.id === this.$route.params.group,
          );
        }
      },
    },
    slots: {
      query: slots,
      variables() {
        return {
          filters: JSON.stringify({
            time_grid: this.internalTimeGrid.id,
          }),
        };
      },
      skip() {
        return !this.readyForQueries;
      },
      update: (data) => data.items,
      result({ data: { items } }) {
        this.weekdays = Array.from(
          new Set(
            items
              .filter((slot) => slot.model === "Slot")
              .map((slot) => slot.weekday),
          ),
        );
        this.periods = Array.from(
          new Set(
            items
              .filter((slot) => slot.model === "Slot")
              .map((slot) => slot.period),
          ),
        );
        this.slotsByPeriods = this.periods.map((period) => ({
          period: period,
          slots: items.filter(
            (slot) => slot.model === "Slot" && slot.period === period,
          ),
        }));
      },
    },
    timeGrids: {
      query: timeGrids,
      update: (data) => data.items,
      variables() {
        return {
          filters: JSON.stringify({
            validity_range: this.internalTimeGrid.validityRange.id,
          }),
        };
      },
      skip() {
        return !this.internalTimeGrid;
      },
    },
    courseBundles: {
      query: courseBundles,
      variables() {
        return {
          group: this.selectedGroup.id,
          validityRange: this.internalTimeGrid.validityRange.id,
        };
      },
      skip() {
        return !this.readyForQueries;
      },
      result({ data }) {
        this.lessonQuotaTotal =
          data && data.courseBundles
            ? data.courseBundles.reduce(
                (accumulator, course) => accumulator + course.lessonQuota,
                0,
              )
            : 0;
      },
    },
    lessonBundles: {
      query: lessonBundles,
      variables() {
        return {
          group: this.selectedGroup.id,
          timeGrid: this.internalTimeGrid.id,
        };
      },
      skip() {
        return !this.readyForQueries;
      },
      result({ data }) {
        this.lessonsUsed = {};
        data.lessonBundles.forEach((lessonBundle) => {
          let increment =
            this.periods.indexOf(lessonBundle.slotEnd.period) -
            this.periods.indexOf(lessonBundle.slotStart.period) +
            1;
          this.lessonsUsed[lessonBundle.courseBundle.id] =
            this.lessonsUsed[lessonBundle.courseBundle.id] + increment ||
            increment;
        });
      },
    },
    overlayBundles: {
      query: overlayBundles,
      variables() {
        return {
          timeGrid: this.internalTimeGrid.id,
          rooms: this.draggedRooms,
          teachers: this.draggedTeachers,
        };
      },
      skip() {
        return !this.readyForQueries || !this.draggedItem;
      },
    },
    persons: {
      query: gqlTeachers,
    },
    rooms: {
      query: rooms,
      update: (data) => data.items,
    },
  },
  computed: {
    readyForQueries() {
      // Non-typesafe check to also handle undefined
      return (
        this.internalTimeGrid != null &&
        this.selectedGroup != null &&
        this.selectedGroup.id != null
      );
    },
    griddedLessonBundles() {
      return this.lessonBundles
        ? this.lessonBundles.map((lessonBundle) => ({
            x: this.weekdays.indexOf(lessonBundle.slotStart.weekday) + 1,
            y: this.periods.indexOf(lessonBundle.slotStart.period) + 1,
            w:
              this.weekdays.indexOf(lessonBundle.slotEnd.weekday) -
              this.weekdays.indexOf(lessonBundle.slotStart.weekday) +
              1,
            h:
              this.periods.indexOf(lessonBundle.slotEnd.period) -
              this.periods.indexOf(lessonBundle.slotStart.period) +
              1,
            key: "lesson-bundle-" + lessonBundle.id,
            disabled: !lessonBundle.canEdit,
            data: lessonBundle,
          }))
        : [];
    },
    gridItems() {
      // As we may want to display more in the future
      return this.griddedLessonBundles;
    },
    gridLoading() {
      return (
        this.$apollo.queries.slots.loading ||
        this.$apollo.queries.lessonBundles.loading ||
        this.$apollo.queries.groups.loading
      );
    },
    griddedCourseBundles() {
      return this.courseBundles
        ? this.courseBundles.map((bundle) => {
            const lessonQuota =
              bundle.lessonQuota ||
              bundle.courses.reduce(
                (min, course) => Math.min(min, course.lessonQuota),
                Number.MAX_SAFE_INTEGER,
              );
            return {
              x: "0",
              y: "0",
              w: 1,
              h: 1,
              key: "course-bundle-" + bundle.id,
              data: {
                // TODO: A uniform interface between courseBundles and lessonBundles
                ...bundle,
                lessonQuota: lessonQuota,
                lessonsUsed: this.lessonsUsed[bundle.id] || 0,
                lessonRatio: (this.lessonsUsed[bundle.id] || 0) / lessonQuota,
              },
            };
          })
        : [];
    },
    disabledSlots() {
      // Disable all fields in the grid where no slot exists
      return this.periods
        .map((period, indexY) =>
          this.weekdays.map((weekday, indexX) =>
            this.slots.filter(
              (slot) =>
                slot.model === "Slot" &&
                slot.weekday === weekday &&
                slot.period === period,
            ).length === 0
              ? {
                  x: indexX + 1,
                  y: indexY + 1,
                }
              : undefined,
          ),
        )
        .flat()
        .filter((val) => val !== undefined);
    },
    totalLessonRatio() {
      return this.$t(
        "lesrooster.timetable_management.lessons_used_ratio_total",
        {
          lessonsUsed: Object.values(this.lessonsUsed).reduce(
            (a, b) => a + b,
            0,
          ),
          lessonQuota: this.lessonQuotaTotal,
        },
      );
    },
    draggedTeachers() {
      const bundle = this.draggedItem?.data;
      if (bundle) {
        return this.bundleTeachers(bundle).map((teacher) => teacher.id);
      }
      return [];
    },
    draggedRooms() {
      const bundle = this.draggedItem?.data;
      if (bundle) {
        return this.bundleRooms(bundle).map((room) => room.id);
      }
      return [];
    },
  },
  watch: {
    selectedGroup() {
      if (!this.selectedGroup) return;
      if (
        this.selectedGroup.id != this.$route.params.group ||
        this.internalTimeGrid.id != this.$route.params.timeGrid
      ) {
        // to be able to select a group, the timeGrid has to be set
        this.$router.push({
          name: "lesrooster.timetable_management",
          params: {
            group: this.selectedGroup.id,
            timeGrid: this.internalTimeGrid.id,
          },
        });
      }
      this.$setToolBarTitle(
        this.$t("lesrooster.timetable_management.for_group", {
          group: this.selectedGroup.name,
        }),
      );
      this.$apollo.queries.courseBundles.refetch();
      this.$apollo.queries.lessonBundles.refetch();
    },
    internalTimeGrid(newTimeGrid, oldTimeGrid) {
      if (!oldTimeGrid) return;

      if (this.selectedGroup?.id) {
        this.$router.push({
          name: "lesrooster.timetable_management",
          params: {
            group: this.selectedGroup.id,
            timeGrid: this.internalTimeGrid.id,
          },
        });
      }
    },
  },
  methods: {
    itemMovedToLessons(eventData) {
      let newStartSlotId = this.slots.filter(
        (slot) =>
          slot.period === this.periods[eventData.y - 1] &&
          slot.weekday === this.weekdays[eventData.x - 1],
      );
      let newEndSlotId = this.slots.filter(
        (slot) =>
          slot.period === this.periods[eventData.y + eventData.h - 2] &&
          slot.weekday === this.weekdays[eventData.x + eventData.w - 2],
      );

      let newStartSlot, newEndSlot;

      if (newStartSlotId.length === 1 && newStartSlotId.length === 1) {
        newStartSlot = newStartSlotId[0];
        newStartSlotId = newStartSlot.id;

        newEndSlot = newEndSlotId[0];
        newEndSlotId = newEndSlot.id;
      } else {
        throw new Error("Multiple slots matched");
      }

      if (eventData.originGridId === "lessonBundles") {
        let that = this;
        this.$apollo
          .mutate({
            mutation: moveLessonBundles,
            variables: {
              input: {
                id: eventData.data.id,
                slotStart: newStartSlotId,
                slotEnd: newEndSlotId,
              },
            },
            optimisticResponse: {
              updateLessonBundles: {
                lessonBundles: [
                  {
                    ...eventData.data,
                    slotStart: newStartSlot,
                    slotEnd: newEndSlot,
                    isOptimistic: true,
                  },
                ],
                __typename: "LessonBundleBatchPatchMutation",
              },
            },
            update(
              store,
              {
                data: {
                  updateLessonBundles: { lessonBundles },
                },
              },
            ) {
              let query = {
                ...that.$apollo.queries.lessonBundles.options,
                variables: JSON.parse(
                  that.$apollo.queries.lessonBundles.previousVariablesJson,
                ),
              };
              // Read the data from cache for query
              const storedData = store.readQuery(query);

              if (!storedData) {
                // There are no data in the cache yet
                return;
              }

              lessonBundles.forEach((lessonBundle) => {
                const index = storedData.lessonBundles.findIndex(
                  (lessonBundleObject) =>
                    lessonBundleObject.id === lessonBundle.id,
                );
                storedData.lessonBundles[index].slotStart =
                  lessonBundle.slotStart;
                storedData.lessonBundles[index].slotEnd = lessonBundle.slotEnd;

                // Write data back to the cache
                store.writeQuery({ ...query, data: storedData });
              });
            },
          })
          .then(() => {
            this.$toastSuccess(
              this.$t(
                "lesrooster.timetable_management.snacks.lesson_move.success",
              ),
            );
          })
          .catch(() => {
            this.$toastError(
              this.$t(
                "lesrooster.timetable_management.snacks.lesson_move.error",
              ),
            );
          });
      } else if (eventData.originGridId === "courseBundles") {
        let that = this;
        const rule = new RRule({
          freq: RRule.WEEKLY, // TODO: Make this configurable
          dtstart: new Date(this.internalTimeGrid.validityRange.dateStart), // FIXME: check if this is correct with timezones etc.
          until: new Date(this.internalTimeGrid.validityRange.dateEnd), // FIXME: check if this is correct with timezones etc.
        });
        const recurrenceString = rule.toString();
        this.$apollo
          .mutate({
            mutation: createLessonBundles,
            variables: {
              input: {
                slotStart: newStartSlotId,
                slotEnd: newEndSlotId,
                courseBundle: eventData.data.id,
                recurrence: recurrenceString,
              },
            },
            optimisticResponse: {
              createLessonBundles: {
                items: [
                  {
                    id: "temporary-lesson-bundle-id-" + crypto.randomUUID(),
                    slotStart: newStartSlot,
                    slotEnd: newEndSlot,
                    recurrence: recurrenceString,
                    courseBundle: eventData.data,
                    lessons: eventData.data.courses.map((course) => {
                      return {
                        id: "temporary-lesson-id-" + crypto.randomUUID(),
                        course: course,
                        rooms: [course.defaultRoom],
                        teachers: course.teachers,
                        subject: course.subject,
                        __typename: "LessonType",
                      };
                    }),
                    isOptimistic: true,
                    canEdit: true,
                    canDelete: true,
                    __typename: "LessonBundleType",
                  },
                ],
                __typename: "LessonBundleBatchCreateMutation",
              },
            },
            update(
              store,
              {
                data: {
                  createLessonBundles: { items },
                },
              },
            ) {
              let query = {
                ...that.$apollo.queries.lessonBundles.options,
                variables: JSON.parse(
                  that.$apollo.queries.lessonBundles.previousVariablesJson,
                ),
              };
              // Read the data from cache for query
              const storedData = store.readQuery(query);

              if (!storedData) {
                // There are no data in the cache yet
                return;
              }

              items.forEach((lessonBundle) =>
                storedData.lessonBundles.push(lessonBundle),
              );

              // Write data back to the cache
              store.writeQuery({ ...query, data: storedData });
            },
          })
          .then(() => {
            this.$toastSuccess(
              this.$t(
                "lesrooster.timetable_management.snacks.lesson_create.success",
              ),
            );
          })
          .catch(() => {
            this.$toastError(
              this.$t(
                "lesrooster.timetable_management.snacks.lesson_create.error",
              ),
            );
          });
      }
    },
    itemMovedToCourses(eventData) {
      if (eventData.originGridId === "lessonBundles") {
        // TODO: remove lessons from plan?
        // Maybe not needed, due to delete button in menu
      }
    },
    canShortenLessonBundle(lessonBundle) {
      // Only allow shortening a lessonBundle if it is longer than 1 slot
      return lessonBundle.slotEnd.id !== lessonBundle.slotStart.id;
    },
    canProlongLessonBundle(lessonBundle) {
      const nextSlot = this.slots
        .filter(
          (slot) =>
            slot.weekday === lessonBundle.slotEnd.weekday &&
            slot.period > lessonBundle.slotEnd.period,
        )
        .reduce(
          (prev, current) =>
            prev && prev.period > current.period ? current : prev || current,
          null,
        );

      return !!nextSlot;
    },
    changeLessonBundleSlots(lessonBundle, slotStart, slotEnd) {
      let that = this;
      this.$apollo
        .mutate({
          mutation: moveLessonBundles,
          variables: {
            input: {
              id: lessonBundle.id,
              slotStart: slotStart.id,
              slotEnd: slotEnd.id,
            },
          },
          optimisticResponse: {
            updateLessonBundles: {
              lessonBundles: [
                {
                  ...lessonBundle,
                  slotStart: slotStart,
                  slotEnd: slotEnd,
                  isOptimistic: true,
                },
              ],
              __typename: "LessonBundleBatchPatchMutation",
            },
          },
          update(
            store,
            {
              data: {
                updateLessonBundles: { lessonBundles },
              },
            },
          ) {
            let query = {
              ...that.$apollo.queries.lessonBundles.options,
              variables: JSON.parse(
                that.$apollo.queries.lessonBundles.previousVariablesJson,
              ),
            };
            // Read the data from cache for query
            const storedData = store.readQuery(query);

            if (!storedData) {
              // There are no data in the cache yet
              return;
            }

            lessonBundles.forEach((lessonBundle) => {
              const index = storedData.lessonBundles.findIndex(
                (lessonBundleObject) =>
                  lessonBundleObject.id === lessonBundle.id,
              );
              storedData.lessonBundles[index].slotStart =
                lessonBundle.slotStart;
              storedData.lessonBundles[index].slotEnd = lessonBundle.slotEnd;

              // Write data back to the cache
              store.writeQuery({ ...query, data: storedData });
            });
          },
        })
        .then(() => {
          this.$toastSuccess(
            this.$t(
              "lesrooster.timetable_management.snacks.lesson_change_length.success",
            ),
          );
        })
        .catch(() => {
          this.$toastError(
            this.$t(
              "lesrooster.timetable_management.snacks.lesson_change_length.error",
            ),
          );
        });
    },
    prolongLessonBundle(lessonBundle) {
      // Find next slot on the same day
      const slotEnd = this.slots
        .filter(
          (slot) =>
            slot.weekday === lessonBundle.slotEnd.weekday &&
            slot.period > lessonBundle.slotEnd.period,
        )
        .reduce((prev, current) =>
          prev.period < current.period ? prev : current,
        );

      this.changeLessonBundleSlots(
        lessonBundle,
        lessonBundle.slotStart,
        slotEnd,
      );
    },
    shortenLessonBundle(lessonBundle) {
      // Find previous slot on the same day
      const slotEnd = this.slots
        .filter(
          (slot) =>
            slot.weekday === lessonBundle.slotEnd.weekday &&
            slot.period < lessonBundle.slotEnd.period,
        )
        .reduce((prev, current) =>
          prev.period > current.period ? prev : current,
        );

      this.changeLessonBundleSlots(
        lessonBundle,
        lessonBundle.slotStart,
        slotEnd,
      );
    },
    deleteLessonBundle(lessonBundle) {
      this.itemsToDelete = [lessonBundle];
      this.deleteDialog = true;
    },
    teacherClick(teacher) {
      // A teacher was selected for miniplan
      this.selectedObjectType = "teacher";
      this.selectedObject = teacher.id;
      this.selectedObjectTitle = teacher.fullName;
      this.selectedObjectDialogOpen = true;
    },
    roomClick(room) {
      // A room was selected for miniplan
      this.selectedObjectType = "room";
      this.selectedObject = room.id;
      this.selectedObjectTitle = room.name;
      this.selectedObjectDialogOpen = true;
    },
    editLessonClick(lesson) {
      this.lessonEdit.id = lesson.id;
      this.lessonEdit.object = lesson;
      this.lessonEdit.open = true;
    },
    getTeacherList(subjectTeachers) {
      return [
        {
          header: this.$t(
            "lesrooster.timebound_course_config.subject_teachers",
          ),
        },
        ...this.persons.filter((person) =>
          subjectTeachers.find((teacher) => teacher.id === person.id),
        ),
        { divider: true },
        { header: this.$t("lesrooster.timebound_course_config.all_teachers") },
        ...this.persons.filter(
          (person) =>
            !subjectTeachers.find((teacher) => teacher.id === person.id),
        ),
      ];
    },
    handleLessonEditUpdate(store, lesson) {
      const query = {
        ...this.$apollo.queries.lessonBundles.options,
        variables: JSON.parse(
          this.$apollo.queries.lessonBundles.previousVariablesJson,
        ),
      };
      // Read the data from cache for query
      const storedData = store.readQuery(query);

      if (!storedData) {
        // There are no data in the cache yet
        return;
      }

      let lessonIndex = -1;

      const bundleIndex = storedData.lessonBundles.findIndex((lessonBundle) => {
        const index = lessonBundle.lessons.findIndex(
          (lessonObject) => lessonObject.id === lesson.id,
        );

        if (index < 0) {
          return false;
        }

        lessonIndex = index;
        return true;
      });

      if (bundleIndex === -1 || lessonIndex === -1) {
        return;
      }

      storedData.lessonBundles[bundleIndex].lessons[lessonIndex].subject =
        lesson.subject;
      storedData.lessonBundles[bundleIndex].lessons[lessonIndex].teachers =
        lesson.teachers;
      storedData.lessonBundles[bundleIndex].lessons[lessonIndex].rooms =
        lesson.rooms;

      // Write data back to the cache
      store.writeQuery({ ...query, data: storedData });
    },
    handleLessonEditSave() {
      this.$toastSuccess(
        this.$t("lesrooster.timetable_management.snacks.lesson_edit.success"),
      );
    },
    handleLessonEditError() {
      this.$toastError(
        this.$t("lesrooster.timetable_management.snacks.lesson_edit.error"),
      );
    },
    lessonEditGetPatchData(lesson) {
      return {
        id: lesson.id,
        subject: lesson.subject.id,
        teachers: lesson.teachers.map((teacher) => teacher.id),
        rooms: lesson.rooms.map((room) => room.id),
      };
    },
    courseSearchFilter(items, search) {
      if (!search || !items.length) return items;
      search = (search || "").trim().toLowerCase();
      if (!search) return items;

      return items.filter((item) => {
        return (
          item.data.name?.toLowerCase().includes(search) ||
          item.data.subject?.name?.toLowerCase().includes(search) ||
          item.data.subject?.teachers?.some(
            (teacher) =>
              teacher.fullName?.toLowerCase().includes(search) ||
              teacher.shortName?.toLowerCase().includes(search),
          ) ||
          item.data.teachers?.some(
            (teacher) =>
              teacher.fullName?.toLowerCase().includes(search) ||
              teacher.shortName?.toLowerCase().includes(search),
          ) ||
          item.data.groups?.some(
            (group) =>
              group.name?.toLowerCase().includes(search) ||
              group.shortName?.toLowerCase().includes(search),
          )
        );
      });
    },
    formatTimeGrid(item) {
      if (!item) return null;
      if (item.group === null) {
        return this.$t(
          "lesrooster.validity_range.time_grid.repr.generic",
          item.validityRange,
        );
      }
      return this.$t("lesrooster.validity_range.time_grid.repr.default", [
        item.validityRange.name,
        item.group.name,
      ]);
    },
    timeRangesByWeekdays(period) {
      return period.slots
        .map((slot) => ({ timeStart: slot.timeStart, timeEnd: slot.timeEnd }))
        .filter(
          (value, index, self) =>
            index ===
            self.findIndex(
              (timeRange) =>
                timeRange.timeStart === value.timeStart &&
                timeRange.timeEnd === value.timeEnd,
            ),
        )
        .map((timeRange) => ({
          ...timeRange,
          weekdays: period.slots
            .filter(
              (slot) =>
                slot.timeStart === timeRange.timeStart &&
                slot.timeEnd === timeRange.timeEnd,
            )
            .map((slot) => slot.weekday),
        }));
    },
    handleContainerDrag(element, type) {
      if (type === "start") {
        this.draggedItem = element;
      } else {
        this.draggedItem = null;
      }
    },
    setInitialTimeGrid(timeGrids) {
      if (!this.internalTimeGrid?.id) {
        this.internalTimeGrid = timeGrids.find(
          this.$route.params.timeGrid
            ? (timeGrid) => timeGrid.id === this.$route.params.timeGrid
            : (timeGrid) =>
                timeGrid.validityRange.isCurrent &&
                (!timeGrid.group ||
                  timeGrid.group?.id === this.$route.params.group),
        );
      }
    },
  },
});
</script>

<template>
  <div>
    <v-row>
      <v-col cols="12" lg="8" xl="9">
        <div class="d-flex justify-space-between flex-wrap align-center">
          <secondary-action-button
            i18n-key="lesrooster.timetable_management.back"
            :to="{ name: 'cursus.school_structure' }"
          />

          <v-spacer />

          <time-grid-field
            outlined
            filled
            label="Select Validity Range"
            hide-details
            v-model="internalTimeGrid"
            @items="setInitialTimeGrid"
          />

          <v-autocomplete
            outlined
            filled
            hide-details
            label="Select Group"
            :items="groups"
            item-text="name"
            item-value="id"
            return-object
            v-model="selectedGroup"
            :loading="$apollo.queries.groups.loading"
            :disabled="!internalTimeGrid?.id"
            class="mr-4"
          />
        </div>
      </v-col>

      <v-col
        cols="12"
        lg="4"
        xl="3"
        class="d-flex justify-space-between flex-wrap align-center"
      >
        <secondary-action-button
          i18n-key="lesrooster.actions.copy_last_configuration"
          block
          disabled
        />
      </v-col>

      <v-col cols="12" lg="8" xl="9" class="align-self-start" id="grid">
        <div id="weekdays">
          <v-card
            v-for="weekday in weekdays"
            :key="weekday"
            class="d-flex justify-center align-center"
          >
            <v-card-title class="text-body-1">{{
              $t("weekdays." + weekday)
            }}</v-card-title>
          </v-card>
        </div>
        <div id="periods">
          <period-card
            v-for="(period, index) in periods"
            :key="'period-' + period"
            :period="period"
            :weekdays="weekdays"
            :time-ranges="
              timeRangesByWeekdays(
                slotsByPeriods.find(
                  (periodWithSlots) => periodWithSlots.period === period,
                ),
              )
            "
          />
        </div>
        <drag-grid
          :cols="weekdays.length"
          :rows="periods.length"
          :value="gridItems"
          :loading="gridLoading"
          context="timetable"
          :disabled-fields="disabledSlots"
          @itemChanged="itemMovedToLessons"
          grid-id="lessonBundles"
          id="timetable"
          multiple-items-y
          @containerDragStart="handleContainerDrag($event, 'start')"
          @containerDragEnd="handleContainerDrag($event, 'end')"
        >
          <template #item="item">
            <v-menu
              open-on-hover
              offset-y
              :open-on-click="false"
              :rounded="item.data.lessons.length === 1 ? 'pill' : 'xl'"
              bottom
              min-width="max-content"
              nudge-right="40%"
            >
              <template #activator="{ attrs, on }">
                <bundle-card
                  :bundle="item.data"
                  rounded="lg"
                  class="d-flex"
                  v-bind="attrs"
                  v-on="on"
                  :highlighted-rooms="draggedRooms"
                  :highlighted-teachers="draggedTeachers"
                  @click:teacher="teacherClick"
                  @click:room="roomClick"
                />
              </template>

              <v-card
                style="width: max-content"
                class="d-flex flex-column align-center"
              >
                <div>
                  <v-btn
                    icon
                    :disabled="!item.data.canDelete"
                    @click="deleteLessonBundle(item.data)"
                  >
                    <v-icon>$deleteContent</v-icon>
                  </v-btn>
                  <v-btn
                    icon
                    :disabled="!canShortenLessonBundle(item.data)"
                    @click="shortenLessonBundle(item.data)"
                  >
                    <v-icon>mdi-minus</v-icon>
                  </v-btn>
                  <v-btn
                    icon
                    :disabled="!canProlongLessonBundle(item.data)"
                    @click="prolongLessonBundle(item.data)"
                  >
                    <v-icon>mdi-plus</v-icon>
                  </v-btn>
                  <v-btn
                    v-if="item.data.lessons.length === 1"
                    icon
                    @click="editLessonClick(item.data.lessons[0])"
                  >
                    <v-icon>$edit</v-icon>
                  </v-btn>
                </div>
                <template v-if="item.data.lessons.length > 1">
                  <v-list-item
                    v-for="lesson in item.data.lessons"
                    :key="lesson.id"
                    dense
                    @click="editLessonClick(lesson)"
                  >
                    <v-list-item-icon>
                      <v-icon>$edit</v-icon>
                    </v-list-item-icon>
                    <v-list-item-title>
                      {{ lesson.subject.name }}
                    </v-list-item-title>
                  </v-list-item>
                </template>
              </v-card>
            </v-menu>
          </template>
          <template #loader>
            <v-skeleton-loader type="sentences" />
          </template>
          <template #highlight>
            <v-skeleton-loader
              type="image"
              boilerplate
              height="100%"
              id="highlight"
            />
          </template>
          <template #disabledField="{ isDraggedOver }">
            <v-fade-transition>
              <blocking-card v-show="isDraggedOver" />
            </v-fade-transition>
          </template>
          <template v-for="overlayBundle in overlayBundles">
            <timetable-overlay-card
              v-if="draggedItem"
              v-show="draggedItem?.data.id !== overlayBundle.id"
              :dragged-item="draggedItem?.data"
              :periods="periods"
              :weekdays="weekdays"
              :bundle="overlayBundle"
              :key="'overlay-' + overlayBundle.id"
            />
          </template>
        </drag-grid>
      </v-col>

      <v-col cols="12" lg="4" xl="3">
        <v-card>
          <v-card-text>
            <v-text-field
              search
              filled
              rounded
              v-model="courseSearch"
              clearable
              :label="$t('lesrooster.actions.search_courses')"
              :hint="totalLessonRatio"
              persistent-hint
            />
            <v-data-iterator
              :items="griddedCourseBundles"
              item-key="key"
              :items-per-page="-1"
              single-expand
              :search="courseSearch"
              sort-by="data.lessonRatio"
              :custom-filter="courseSearchFilter"
            >
              <template #default="{ items }">
                <drag-grid
                  :cols="3"
                  :rows="4"
                  :value="items"
                  :loading="$apollo.queries.courseBundles.loading"
                  no-highlight
                  context="timetable"
                  @itemChanged="itemMovedToCourses"
                  grid-id="courseBundles"
                  @containerDragStart="handleContainerDrag($event, 'start')"
                  @containerDragEnd="handleContainerDrag($event, 'end')"
                >
                  <template #item="item">
                    <bundle-card
                      :bundle="item.data"
                      rounded="lg"
                      :highlighted-rooms="draggedRooms"
                      :highlighted-teachers="draggedTeachers"
                      @click:teacher="teacherClick"
                      @click:room="roomClick"
                    >
                      <lesson-ratio-chip :course="item.data" />
                    </bundle-card>
                  </template>
                  <template #loader>
                    <v-skeleton-loader type="image" />
                  </template>
                </drag-grid>
              </template>
            </v-data-iterator>
          </v-card-text>
        </v-card>
      </v-col>
    </v-row>

    <mobile-fullscreen-dialog
      v-model="selectedObjectDialogOpen"
      max-width="75vw"
    >
      <v-card>
        <v-card-title class="justify-space-between">
          <span>
            {{
              $t("lesrooster.timetable_management.timetable_for", {
                name: selectedObjectTitle,
              })
            }}
          </span>

          <v-spacer />

          <v-tabs
            v-model="selectedObjectDialogTab"
            color="secondary"
            right
            v-if="timeGrids && timeGrids.length > 1"
            class="width-max-content"
          >
            <v-tab
              v-for="timeGrid in timeGrids"
              :key="'tabSelector-' + timeGrid.id"
            >
              {{ formatTimeGrid(timeGrid) }}
            </v-tab>
          </v-tabs>
        </v-card-title>
        <v-card-text>
          <v-tabs-items v-model="selectedObjectDialogTab">
            <v-tab-item
              v-for="timeGrid in timeGrids"
              :key="'tabItem-' + timeGrid.id"
            >
              <teacher-time-table
                v-if="internalTimeGrid && selectedObjectType === 'teacher'"
                :id="selectedObject"
                :time-grid="timeGrid"
                class="fill-height"
              />
              <room-time-table
                v-if="internalTimeGrid && selectedObjectType === 'room'"
                :id="selectedObject"
                :time-grid="timeGrid"
                class="fill-height"
              />
            </v-tab-item>
          </v-tabs-items>
        </v-card-text>
      </v-card>
    </mobile-fullscreen-dialog>

    <delete-dialog
      :gql-delete-mutation="deleteMutation"
      :affected-query="$apollo.queries.lessonBundles"
      gql-data-key="lessonBundles"
      v-model="deleteDialog"
      :items="itemsToDelete"
    >
      <template #body>
        <ul class="text-body-1">
          <li v-for="item in itemsToDelete" :key="'delete-' + item.id">
            <span
              v-for="lesson in item.lessons"
              :key="'delete-' + lesson.subject.id"
            >
              {{ lesson.subject.name }}
            </span>
          </li>
        </ul>
      </template>
    </delete-dialog>

    <dialog-object-form
      :is-create="false"
      :default-item="lessonEdit.object"
      :edit-item="lessonEdit.object"
      :fields="lessonEdit.fields"
      v-model="lessonEdit.open"
      item-title-attribute="course.name"
      :gql-patch-mutation="lessonEdit.mutation"
      :get-patch-data="lessonEditGetPatchData"
      @cancel="lessonEdit.open = false"
      @save="handleLessonEditSave"
      @error="handleLessonEditError"
      @update="handleLessonEditUpdate"
      force-model-item-update
    >
      <!-- eslint-disable-next-line vue/valid-v-slot -->
      <template #subject.field="{ attrs, on }">
        <subject-field v-bind="attrs" v-on="on" />
      </template>

      <!-- eslint-disable-next-line vue/valid-v-slot -->
      <template #teachers.field="{ attrs, on, item }">
        <v-autocomplete
          multiple
          return-object
          :items="getTeacherList(item.subject?.teachers || [])"
          item-text="fullName"
          item-value="id"
          v-bind="attrs"
          v-on="on"
          :loading="$apollo.queries.persons.loading"
        >
          <template #item="data">
            <v-list-item-action>
              <v-checkbox v-model="data.attrs.inputValue" />
            </v-list-item-action>
            <v-list-item-content>
              <v-list-item-title>{{ data.item.fullName }}</v-list-item-title>
              <v-list-item-subtitle v-if="data.item.shortName">{{
                data.item.shortName
              }}</v-list-item-subtitle>
            </v-list-item-content>
          </template>
        </v-autocomplete>
      </template>

      <!-- eslint-disable-next-line vue/valid-v-slot -->
      <template #rooms.field="{ attrs, on }">
        <v-autocomplete
          multiple
          return-object
          :items="rooms"
          item-text="name"
          item-value="id"
          :loading="$apollo.queries.rooms.loading"
          v-bind="attrs"
          v-on="on"
        />
      </template>
    </dialog-object-form>
  </div>
</template>

<style>
#highlight > .v-skeleton-loader__image {
  height: 100%;
}
</style>

<style scoped lang="scss">
.big {
  width: 36px;
}

.spacer {
  width: 36px;
}

.width-max-content {
  width: max-content;
}

#grid {
  display: grid;
  grid-template: ". weekdays" auto "periods timetable" auto / min-content auto;
  gap: 0.5rem;
}

#weekdays {
  grid-area: weekdays;
  display: flex;
  flex-direction: row;
  width: 100%;
  justify-content: space-between;
  gap: 0.5rem;

  & > * {
    width: 100%;
  }
}

#periods {
  grid-area: periods;
  display: flex;
  flex-direction: column;
  height: 100%;
  justify-content: space-between;
  gap: 0.5rem;

  & > * {
    height: 100%;
  }
}

#timetable {
  grid-area: timetable;
  gap: 0.5rem;
}
</style>