<template>
  <c-r-u-d-iterator
    i18n-key="alsijil.coursebook"
    :gql-query="gqlQuery"
    :gql-additional-query-args="gqlQueryArgs"
    :enable-create="false"
    :enable-edit="false"
    :elevated="false"
    @lastQuery="lastQuery = $event"
    ref="iterator"
    disable-pagination
    hide-default-footer
    use-deep-search
  >
    <template #additionalActions="{ attrs, on }">
      <coursebook-filters v-model="filters" />
    </template>
    <template #default="{ items }">
      <v-list-item
        v-for="{ date, docs, first, last } in groupDocsByDay(items)"
        v-intersect="onIntersect"
        :data-date="date.toISODate()"
        :data-first="first"
        :data-last="last"
        two-line
        :key="'day-' + date"
        :id="'documentation_' + date.toISODate()"
      >
        <v-list-item-content>
          <v-subheader class="text-h6">{{
            $d(day[0], "dateWithWeekday")
          }}</v-subheader>
          <v-list max-width="100%" class="pt-0 mt-n1">
            <v-list-item
              v-for="doc in docs"
              :key="'documentation-' + (doc.oldId || doc.id)"
            >
              <documentation-modal
                :documentation="doc"
                :affected-query="lastQuery"
              />
            </v-list-item>
          </v-list>
        </v-list-item-content>
      </v-list-item>

      <date-select-footer :value="$route.hash.substring(1)" />
    </template>
    <template #loading>
      <CoursebookLoader />
    </template>

    <template #no-data>
      <CoursebookEmptyMessage icon="mdi-book-off-outline">
        {{ $t("alsijil.coursebook.no_data") }}
      </CoursebookEmptyMessage>
    </template>

    <template #no-results>
      <CoursebookEmptyMessage icon="mdi-book-alert-outline">
        {{
          $t("alsijil.coursebook.no_results", { search: $refs.iterator.search })
        }}
      </CoursebookEmptyMessage>
    </template>
  </c-r-u-d-iterator>
</template>

<script>
import CRUDIterator from "aleksis.core/components/generic/CRUDIterator.vue";
import DateSelectFooter from "aleksis.core/components/generic/DateSelectFooter.vue";
import DocumentationModal from "./documentation/DocumentationModal.vue";
import { DateTime, Interval } from "luxon";
import { documentationsForCoursebook } from "./coursebook.graphql";
import CoursebookFilters from "./CoursebookFilters.vue";
import CoursebookLoader from "./CoursebookLoader.vue";
import CoursebookEmptyMessage from "./CoursebookEmptyMessage.vue";

export default {
  name: "Coursebook",
  components: {
    CoursebookEmptyMessage,
    CoursebookFilters,
    CoursebookLoader,
    CRUDIterator,
    DateSelectFooter,
    DocumentationModal,
  },
  props: {
    filterType: {
      type: String,
      required: true,
    },
    objId: {
      type: [Number, String],
      required: false,
      default: null,
    },
    objType: {
      type: String,
      required: false,
      default: null,
    },
  },
  data() {
    return {
      gqlQuery: documentationsForCoursebook,
      currentDate: "",
      visible: [],
      knownDates: {},
      lastQuery: null,
      dateStart: "",
      dateEnd: "",
      // Placeholder values while query isn't completed yet
      groups: [],
      courses: [],
      incomplete: false,
    };
  },
  computed: {
    // Assertion: Should only fire on page load or selection change.
    //            Resets date range.
    gqlQueryArgs() {
      console.log('computing gqlQueryArgs');
      return {
        own: this.filterType === "all" ? false : true,
        objId: this.objId ? Number(this.objId) : undefined,
        objType: this.objType?.toUpperCase(),
        dateStart: this.dateStart,
        dateEnd: this.dateEnd,
        incomplete: !!this.incomplete,
      };
    },
    filters: {
      get() {
        return {
          objType: this.objType,
          objId: this.objId,
          filterType: this.filterType,
          incomplete: this.incomplete,
        };
      },
      set(selectedFilters) {
        if (Object.hasOwn(selectedFilters, "incomplete")) {
          this.incomplete = selectedFilters.incomplete;
        } else if (
          Object.hasOwn(selectedFilters, "filterType") ||
          Object.hasOwn(selectedFilters, "objId") ||
          Object.hasOwn(selectedFilters, "objType")
        ) {
          this.$router.push({
            name: "alsijil.coursebook",
            params: {
              filterType: selectedFilters.filterType
                ? selectedFilters.filterType
                : this.filterType,
              objType: selectedFilters.objType,
              objId: selectedFilters.objId,
            },
            hash: this.$route.hash,
          });
          // computed should not have side effects
          // but this was actually done before filters was refactored into
          // its own component
          this.resetDate();
        }
      },
    },
  },
  methods: {
    // TODO: Scroll to actual first date!
    resetDate() {
      // Assure current date
      console.log('Resetting date range', this.$route.hash);
      if (!this.$route.hash) {
        console.log('Set default date');
        this.$router.replace({ hash: DateTime.now().toISODate() })
      }
      // Resetting known dates to dateRange around current date
      this.knownDates = {};
      this.currentDate = this.$route.hash.substring(1);
      const dateRange = this.dateRange(DateTime.fromISO(this.$route.hash.substring(1)));
      dateRange.forEach((ts) => this.knownDates[ts] = true);
      const lastIdx = dateRange.length - 1;
      // Returning a dateRange each around first & last date for the initial query
      this.dateStart = this.dateRange(dateRange[0])[0].toISODate();
      this.dateEnd = this.dateRange(dateRange[lastIdx])[lastIdx].toISODate();
    },
    groupDocsByDay(docs) {
      // => {dt: [dt doc ...] ...}
      const docsByDay = docs.reduce((byDay, doc) => {
        // This works with dummy. Does actual doc have dateStart instead?
        const day = DateTime.fromISO(doc.datetimeStart).startOf("day");
        byDay[day] ??= {date: day, docs: [], first: false, last: false};
        byDay[day]['docs'].push(doc);
        return byDay;
      }, {});
      // => [[dt doc ...] ...]
      return Object.keys(docsByDay)
        .sort()
        .map((key, i, {length}) => {
          const day = docsByDay[key];
          if (i === 0) {
            day['first'] = true;
          } else if (i === length - 1) {
            day['last'] = true;
          }
          return day;
        });
      // sorting is necessary since backend can send docs unordered
    },
    /**
     * @param {"prev"|"next"} direction
     */
    handleDateMove(direction) {
      const dateStartParsed = DateTime.fromISO(this.dateStart);
      const dateEndParsed = DateTime.fromISO(this.dateEnd);
      const dateParsed = DateTime.fromISO(this.date);

      const newDate =
        direction === "prev"
          ? dateParsed.minus({ days: 1 })
          : dateParsed.plus({ days: 1 });

      /*
       TODO:
         Everything below this line is also needed for when a date is selected via the calendar.
         → probably move this into a different function and create a second event listener for the input event.
       */

      // Load 3 days into the future/past
      if (dateStartParsed >= newDate) {
        this.dateStart = newDate.minus({ days: 3 }).toISODate();
      }
      if (dateEndParsed <= newDate) {
        this.dateEnd = newDate.plus({ days: 3 }).toISODate();
      }

      this.$router.push({
        name: "alsijil.coursebook",
        params: {
          filterType: this.filterType,
          objType: this.objType,
          objId: this.objId,
        },
      });

      // Define the function to find the nearest ID
      const ids = Array.from(
        document.querySelectorAll("[id^='documentation_']"),
      ).map((el) => el.id);

      // TODO: This should only be done after loading the new data
      const nearestId = this.findNearestId(newDate, direction, ids);
      this.$vuetify.goTo("#" + nearestId);
    },
    findNearestId(targetDate, direction, ids) {
      const sortedIds = ids
        .map((id) => DateTime.fromISO(id.split("_")[1]))
        .sort((a, b) => a - b);

      if (direction === "prev") {
        sortedIds.reverse();
      }

      const nearestId =
        sortedIds.find((id) =>
          direction === "next" ? id >= targetDate : id <= targetDate,
        ) || sortedIds[sortedIds.length - 1];

      return "documentation_" + nearestId.toISODate();
    },
    dateRange(date) {
      return Interval
        .fromDateTimes(date.minus({ days: 3 }), date.plus({ days: 4 }))
        .splitBy({ days: 1 })
        .map((ts) => ts.start);
    },
    // docsByDay: {dt: [dt doc ...] ...}
    assureDate(date) {
      if (!this.knownDates[date]) {
        console.log(this.lastQuery);
        console.log('unknown date', date.toISODate());
        console.log(this.knownDates);
        // find missing & fetch missing range
        const missing = this.dateRange(date)
              .filter((ts) => !this.knownDates[ts]);
        // ask for first to last
        this.lastQuery.fetchMore({
          variables: {
            dateStart: missing[0].toISODate(),
            dateEnd: missing[missing.length - 1].toISODate(),
          },
          // Transform the previous result with new data
          updateQuery: (previousResult, { fetchMoreResult }) => {
            console.log('previousResult', previousResult);
            console.log('fetchMoreResult', fetchMoreResult);
            return {
              items: [...previousResult.items,
                      ...fetchMoreResult.items.filter((doc) => {
                        return previousResult.items.find((prev) => prev.id === doc.id)
                      }),
                     ],
            };
          }
        })
        // integrate into docsByDay
      }
    },
    gotoDate(date, scroll) {
      // show
      this.$router.replace({ hash: date })
      console.log('hash', this.$route.hash);
      // assure
      this.assureDate(DateTime.fromISO(date));
      // scroll
    },
    onIntersect(entries, observer) {
      const entry = entries[0];
      if (entry.isIntersecting) {
        // coming
        console.log('intersect', this.visible);

        // track visible
        if (this.visible[0] > entry.target.dataset.date || this.visible.length === 0) {
          // coming is new first (top) date
          this.visible.unshift(entry.target.dataset.date);
          console.log('current', this.visible[0]);
        } else if (this.visible[this.visible.length -1] < entry.target.dataset.date) {
          // coming is new last (bottom) date
          this.visible.push(entry.target.dataset.date);
        } 

        // load more
        if (entry.target.dataset.first) {
          console.log('load up');
        } else if (entry.target.dataset.last) {
          console.log('load down');
        }
      } else if (this.visible[0] === entry.target.dataset.date) {
        // first (top) visible date is going
        this.visible.shift()
        console.log('current', this.visible[0]);
      } else if (this.visible[this.visible.length - 1] === entry.target.dataset.date) {
        // last (bottom) visible date is going
        this.visible.pop()
      }
    },
  },
  created() {
    this.resetDate();
  },
};
</script>

<style>
.max-width {
  max-width: 25rem;
}
</style>