<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"
    fixed-header
    disable-pagination
    hide-default-footer
    use-deep-search
  >
    <template #additionalActions="{ attrs, on }">
      <div class="d-flex flex-grow-1 justify-end">
        <v-autocomplete
          :items="selectable"
          item-text="name"
          clearable
          return-object
          filled
          dense
          hide-details
          :placeholder="$t('alsijil.coursebook.filter.filter_for_obj')"
          :loading="selectLoading"
          :value="currentObj"
          @input="changeSelection"
          @click:clear="changeSelection"
          class="max-width"
        />
        <div class="ml-6">
          <v-switch
            :loading="selectLoading"
            :label="$t('alsijil.coursebook.filter.own')"
            :input-value="filterType === 'my'"
            @change="
              changeSelection({
                filterType: $event ? 'my' : 'all',
                type: objType,
                id: objId,
              })
            "
            dense
            inset
            hide-details
          />
          <v-switch
            :loading="selectLoading"
            :label="$t('alsijil.coursebook.filter.missing')"
            v-model="incomplete"
            dense
            inset
            hide-details
          />
        </div>
      </div>
    </template>
    <template #default="{ items }">
      <coursebook-loader />
      <coursebook-day
        v-for="{ date, docs, first, last } in groupDocsByDay(items)"
        v-intersect="{
            handler: intersectHandler(date, first, last),
            options: {
              rootMargin: '-165px 0px 0px 0px',
              threshold: [0, 1],
            },
          }"
        :date="date"
        :docs="docs"
        :lastQuery="lastQuery"
        :focus-on-mount="initDate && (initDate.toMillis() === date.toMillis())"
        @init="transition"
        ref="days"
        />
      <coursebook-loader />

      <date-select-footer
        :value="$route.hash.substring(1)"
        @input="gotoDate"
        @prev="gotoPrev"
        @next="gotoNext"
        />
    </template>
    <template #loading>
      <coursebook-loader
        :number-of-days="10"
        :number-of-docs="5"
      />
    </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 CoursebookDay from "./CoursebookDay.vue";
import { DateTime, Interval } from "luxon";
import {
  coursesOfPerson,
  documentationsForCoursebook,
  groupsByPerson,
} from "./coursebook.graphql";
import CoursebookLoader from "./CoursebookLoader.vue";
import CoursebookEmptyMessage from "./CoursebookEmptyMessage.vue";

export default {
  name: "Coursebook",
  components: {
    CoursebookEmptyMessage,
    CoursebookLoader,
    CRUDIterator,
    DateSelectFooter,
    CoursebookDay,
  },
  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,
      lastQuery: null,
      dateStart: "",
      dateEnd: "",
      // Placeholder values while query isn't completed yet
      groups: [],
      courses: [],
      incomplete: false,
      ready: false,
      initDate: false,
    };
  },
  apollo: {
    groups: {
      query: groupsByPerson,
    },
    courses: {
      query: coursesOfPerson,
    },
  },
  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,
      };
    },
    selectable() {
      return [
        { header: this.$t("alsijil.coursebook.filter.groups") },
        ...this.groups.map((group) => ({ type: "group", ...group })),
        { header: this.$t("alsijil.coursebook.filter.courses") },
        ...this.courses.map((course) => ({ type: "course", ...course })),
      ];
    },
    currentObj() {
      return this.selectable.find(
        (o) => o.type === this.objType && o.id === this.objId,
      );
    },
    selectLoading() {
      return (
        this.$apollo.queries.groups.loading ||
        this.$apollo.queries.courses.loading
      );
    },
  },
  methods: {
    resetDate() {
      // Assure current date
      console.log('Resetting date range', this.$route.hash);
      if (!this.$route.hash) {
        console.log('Set default date');
        this.setDate(DateTime.now().toISODate());
      } 

      const date = DateTime.fromISO(this.$route.hash.substring(1));
      this.initDate = date;
      this.dateStart = date.minus({ days: 3 }).toISODate();
      this.dateEnd = date.plus({ days: 4 }).toISODate();
    },
    changeSelection(selection) {
      this.$router.push({
        name: "alsijil.coursebook",
        params: {
          filterType: selection.filterType
            ? selection.filterType
            : this.filterType,
          objType: selection.type,
          objId: selection.id,
        },
        hash: this.$route.hash,
      });
      this.resetDate();
      // might skip query until both set = atomic
    },
    transition() {
      this.initDate = false
      this.ready = true
    },
    groupDocsByDay(docs) {
      // => {dt: {date: dt, docs: 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: []};
        byDay[day].docs.push(doc);
        return byDay;
      }, {});
      // => [{date: dt, docs: doc ..., idx: idx, lastIdx: last-idx} ...]
      // sorting is necessary since backend can send docs unordered
      return Object.keys(docsByDay)
        .sort()
        .map((key, idx, {length}) => {
          const day = docsByDay[key];
          day.first = idx === 0;
          const lastIdx = length - 1;
          day.last = idx === lastIdx;
          return day;
        });
    },
    // docsByDay: {dt: [dt doc ...] ...}
    fetchMore(from, to, then) {
      console.log('fetching', from, to);
      this.lastQuery.fetchMore({
        variables: {
          dateStart: from,
          dateEnd: to,
        },
        // Transform the previous result with new data
        updateQuery: (previousResult, { fetchMoreResult }) => {
          console.log('previousResult', previousResult);
          console.log('fetchMoreResult', fetchMoreResult);
          then();
          return { items: previousResult.items.concat(fetchMoreResult.items) };
        }
      });
    },
    setDate(date) {
      if (!(this.$route.hash.substring(1) === date)) {
        this.$router.replace({ hash: date })
      }
    },
    fixScrollPos(height, top) {
      console.log('fix @', top, document.documentElement.scrollTop, height, document.documentElement.scrollHeight);
      this.$nextTick(() => {
        console.log('fix @', top, document.documentElement.scrollTop, height, document.documentElement.scrollHeight);
        if (height < document.documentElement.scrollHeight) {
          console.log('fixingTop');
          document.documentElement.scrollTop = document.documentElement.scrollHeight - height + top;
          this.ready = true;
        } else {
          // Update top, could have changed in the meantime.
          this.fixScrollPos(height, document.documentElement.scrollTop);
        }
      });
    },
    intersectHandler(date, first, last) {
      let once = true;
      return (entries) => {
        const entry = entries[0];
        if (entry.isIntersecting) {

          // TODO: Make 165 a var?
          if ((entry.boundingClientRect.top <= 165)
              || first
              || last) {
            console.log('@', date.toISODate());
            this.setDate(date.toISODate());
          }

          if (once && this.ready && first) {
            console.log('load up', date.toISODate());
            this.ready = false;
            this.fetchMore(date.minus({ days: 5 }).toISODate(),
                           date.minus({ days: 1 }).toISODate(),
                          () => {
                            this.fixScrollPos(document.documentElement.scrollHeight,
                                              document.documentElement.scrollTop);
                          });
            once = false;
          } else if (once && this.ready && last) {
            console.log('load down', date.toISODate());
            this.ready = false;
            this.fetchMore(date.plus({ days: 1 }).toISODate(),
                           date.plus({ days: 5 }).toISODate(),
                           () => { this.ready = true });
            once = false;
          }
        }
      };
    },
    // TODO: only load the else if out of range / not just not present
    gotoDate(date) {
      const present = this.$refs.days
            .find((day) => day.date.toISODate() === date);

      if (present) {
        // React immediatly -> smoother navigation
        // Also intersect handler does not always react to scrollIntoView
        this.setDate(date);
        present.focus("smooth");
      } else {
        this.setDate(date);
        this.resetDate();
      }
    },
    // TODO: Disable navigation while loading!
    gotoPrev() {
      const current = this.$route.hash.substring(1);
      const pref = this.$refs.days
            .map((day) => day.date.toISODate())
            .sort()
            .reverse()
            .find((date) => date < current);
      this.gotoDate(pref);
    },
    gotoNext() {
      const current = this.$route.hash.substring(1);
      const next = this.$refs.days
            .map((day) => day.date.toISODate())
            .sort()
            .find((date) => date > current);
      this.gotoDate(next);
    },
  },
  created() {
    this.resetDate();
  },
};
</script>

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