<!--
Date range picker, taken from the following non-maintained project https://github.com/praveenpuglia/vuetify-daterange-picker
-->
<template>
  <div class="v-date-range">
    <v-menu
      v-model="menu"
      :close-on-content-click="false"
      offset-y
      v-bind="menuProps"
    >
      <template v-slot:activator="{ on }">
        <v-text-field
          v-on="on"
          class="v-date-range__input-field"
          :value="inputValue"
          readonly
          clearable
          hide-details
          :disabled="disabled"
          @click:clear="reset"
          :label="placeholder"
          append-icon="mdi-calendar"
          v-bind="inputProps"
        ></v-text-field>
      </template>
      <v-card class="v-date-range__menu-content">
        <v-card-text>
          <div
            :data-days="highlightDates.length"
            :class="{
              'v-date-range__pickers': true,
              'v-date-range--highlighted': highlightDates.length
            }"
          >
            <v-card-title v-if="$slots.title">
              <slot name="title" v-if="$slots.title"></slot>
            </v-card-title>
            <v-card-text>
              <div class="v-date-range__content">
                <v-list v-if="!noPresets" class="mr-4">
                  <v-subheader>{{ presetLabel }}</v-subheader>
                  <v-list-item
                    v-for="(preset, index) in presets"
                    v-model="isPresetActive[index]"
                    :key="index"
                    @click="selectPreset(index)"
                  >
                    <v-list-item-content>
                      {{ preset.label }}
                    </v-list-item-content>
                  </v-list-item>
                </v-list>
                <v-date-picker
                  class="mr-4 v-date-range__picker--start v-date-range__picker"
                  v-model="pickerStart"
                  :locale="locale"
                  :first-day-of-week="firstDayOfWeek"
                  :min="min"
                  :max="pickerEnd || max"
                  :no-title="noTitle"
                  :next-icon="nextIcon"
                  :prev-icon="prevIcon"
                  :events="highlightDates"
                  :event-color="highlightClasses"
                ></v-date-picker>
                <v-date-picker
                  class="v-date-range__picker--end v-date-range__picker"
                  v-model="pickerEnd"
                  :locale="locale"
                  :first-day-of-week="firstDayOfWeek"
                  :min="pickerStart || min"
                  :max="max"
                  :no-title="noTitle"
                  :next-icon="nextIcon"
                  :prev-icon="prevIcon"
                  :events="highlightDates"
                  :event-color="highlightClasses"
                ></v-date-picker>
              </div>
            </v-card-text>
          </div>
        </v-card-text>
        <v-card-actions>
          <v-spacer></v-spacer>
          <v-btn text @click="reset">{{ mergedActionLabels.reset }}</v-btn>
          <v-btn text @click="menu = false">{{
              mergedActionLabels.cancel
            }}</v-btn>
          <v-btn
            @click="applyRange"
            color="primary"
            :disabled="!bothSelected"
          >{{ mergedActionLabels.apply }}</v-btn
          >
        </v-card-actions>
      </v-card>
    </v-menu>
  </div>
</template>
<script lang="ts">
import { format, parse, differenceInCalendarDays, addDays } from 'date-fns';
import { formatIsoDate } from '@/services/utils/date'
import Vue from "vue";

interface ActionLabels { apply: string, cancel: string, reset: string }

const ISO_FORMAT: string = 'yyyy-MM-dd';
const DEFAULT_DATE: string = format(new Date(), ISO_FORMAT);
const DEFAULT_ACTION_LABELS: ActionLabels = {
  apply: 'Apply',
  cancel: 'Cancel',
  reset: 'Reset'
};

export default Vue.extend({
  props: {
    // Take start and end as the input. Passable via v-model.
    value: {
      type: Object as () => DateRange,
      default: () => {
        return { start: '', end: '' };
      }
    },
    disabled: {
      type: Boolean as () => boolean,
      default: false
    },
    presets: {
      type: Array as () => Array<DateRangePreset>,
      default: () => {
        return [];
      }
    },
    noPresets: {
      type: Boolean as () => boolean,
      default: false
    },
    label: {
      type: String as () => string | null,
      default: null
    },
    // Denotes the Placeholder string for start date.
    startLabel: {
      type: String as () => string,
      default: 'Start Date'
    },
    // Denotes the Placeholder string for start date.
    endLabel: {
      type: String as () => string,
      default: 'End Date'
    },
    // The string that gets placed between `startLabel` and `endLabel`
    separatorLabel: {
      type: String as () => string,
      default: 'To'
    },
    presetLabel: {
      type: String as () => string,
      default: 'Presets'
    },
    actionLabels: {
      type: Object as () => ActionLabels,
      default: () => {
        return DEFAULT_ACTION_LABELS;
      }
    },
    /**
     * Following values are all passable to vuetify's own datepicker
     * component.
     */
    // Min selectable date.
    min: {
      type: String as () => string,
      default: undefined
    },
    // Max selectable date
    max: {
      type: String as () => string,
      default: undefined
    },
    // Locale
    locale: {
      type: String as () => string,
      default: 'en-us'
    },
    firstDayOfWeek: {
      type: [String, Number] as [() => string, () => number],
      default: 0
    },
    noTitle: {
      type: Boolean as () => boolean,
      default: false
    },
    displayFormat: {
      type: String as () => string
    },
    highlightColor: {
      type: String as () => string,
      default: 'blue lighten-5'
    },
    showReset: {
      type: Boolean as () => boolean,
      default: true
    },
    /**
     * Icons
     */
    nextIcon: {
      type: String,
      default: '$vuetify.icons.next'
    },
    prevIcon: {
      type: String,
      default: '$vuetify.icons.prev'
    },
    inputProps: {
      type: Object,
      default: () => {
        return {};
      }
    },
    menuProps: {
      type: Object,
      default: () => {
        return {};
      }
    }
  },
  data() {
    return {
      menu: false,
      pickerStart: this.value.start as string,
      pickerEnd: this.value.end as string,
      highlightDates: [] as Array<string>,
      highlightClasses: {}
    };
  },
  computed: {
    inputValue(): string {
      if (this.isValueEmpty) {
        return '';
      }
      const start = this.displayFormat
        ? formatIsoDate(this.value.start, this.displayFormat)
        : this.value.start;
      const end = this.displayFormat
        ? formatIsoDate(this.value.end, this.displayFormat)
        : this.value.end;
      return `${start}  ${this.separatorLabel}  ${end}`;
    },
    placeholder(): string {
      return this.label !== null ? this.label : `${this.startLabel} ${this.separatorLabel} ${this.endLabel}`;
    },
    /**
     * If the value prop doesn't have any start value,
     * its most likely that an empty object was passed.
     */
    isValueEmpty(): boolean {
      return !this.value.start;
    },
    /**
     * If the user has selected both the dates or not
     */
    bothSelected(): boolean {
      return !!this.pickerStart && !!this.pickerEnd;
    },
    isPresetActive(): Array<boolean> {
      return this.presets.map(
        preset =>
          preset.range.start === this.pickerStart &&
          preset.range.end === this.pickerEnd
      );
    },
    mergedActionLabels(): ActionLabels {
      return { ...DEFAULT_ACTION_LABELS, ...this.actionLabels };
    }
  },
  methods: {
    /**
     * Emit the input event with the updated range data.
     * This makes v-model work.
     */
    applyRange() {
      this.menu = false;
      this.emitRange();
    },
    /**
     * Called every time the menu is closed.
     * It also emits an event to tell the parent
     * that the menu has closed.
     *
     * Upon closing the datepicker values are set
     * to the current selected value.
     */
    closeMenu() {
      // Reset the changed values for datepicker models.
      this.pickerStart = this.value.start;
      this.pickerEnd = this.value.end;
      this.highlight();
      this.$emit('menu-closed');
    },
    highlight() {
      if (!this.bothSelected) {
        return;
      }
      const dates = [];
      const classes = {} as Array<string>;

      const start = parse(this.pickerStart, ISO_FORMAT, new Date());
      const end = parse(this.pickerEnd, ISO_FORMAT, new Date());

      const diff = Math.abs(differenceInCalendarDays(start, end));

      // Loop though all the days in range.
      for (let i = 0; i <= diff; i++) {
        const date: string = format(addDays(start, i), ISO_FORMAT);
        dates.push(date);
        const classesArr = [];
        classesArr.push(`v-date-range__in-range`);
        classesArr.push(this.highlightColor);

        i === 0 && classesArr.push(`v-date-range__range-start`);
        i === diff && classesArr.push(`v-date-range__range-end`);

        classes[date as any] = classesArr.join(' ');
      }
      this.highlightDates = dates;
      this.highlightClasses = classes;
    },
    selectPreset(presetIndex: number) {
      this.pickerStart = this.presets[presetIndex].range.start;
      this.pickerEnd = this.presets[presetIndex].range.end;
    },
    reset() {
      // Reset Picker Values
      this.pickerStart = '';
      this.pickerEnd = '';
      this.highlightDates = [];
      this.highlightClasses = {};
      this.emitRange();
    },
    emitRange() {
      this.$emit('input', {
        start: this.pickerStart,
        end: this.pickerEnd
      });
    }
  },
  watch: {
    // Watching to see if the menu is closed.
    menu(isOpen: boolean) {
      if (!isOpen) {
        this.closeMenu();
      } else {
        this.highlight();
      }
    },
    pickerStart: 'highlight',
    pickerEnd: 'highlight'
  }
})
</script>

<style>

  .v-date-range__input-field ::placeholder {
    color: #666666;
    opacity: 1; /* Firefox */
  }

  .v-menu__content {
    top: 144px !important;
  }

  .v-date-range__content > .v-date-picker-table .v-btn {
    border-radius: 0;
  }

  .v-date-range__pickers .v-date-picker-table table {
    width: auto;
    margin: auto;
    border-collapse: collapse;
  }

  .v-date-range__pickers .v-date-picker-table .v-btn {
    position: initial;
  }

  .v-date-range__pickers .v-date-range__content {
    display: flex;
  }

  .v-date-range__pickers .v-date-picker-table__events {
    height: 100%;
    width: 100%;
    top: 0;
    z-index: -1;
  }

  .v-date-range__pickers .v-date-picker-table__events .v-date-range__in-range {
    position: absolute;
    z-index: 0;
    width: 100%;
    height: 100%;
    left: 0;
    top: 0;
    bottom: 0;
    right: 0;
    border-radius: 0;
  }

  .v-date-range__pickers .v-date-picker-table__events .v-date-range__in-range.v-date-range__range-start {
    border-top-left-radius: 50%;
    border-bottom-left-radius: 50%;
  }

  .v-date-range__pickers .v-date-picker-table__events .v-date-range__in-range.v-date-range__range-end {
    border-top-right-radius: 50%;
    border-bottom-right-radius: 50%;
    width: calc(100% - 5px);
  }

</style>



