<template>
    <span>
        <slot name="render" v-if="resolvedOpeningTimes" :openingTimes="resolvedOpeningTimes" :view="view">
            <button v-if="resolvedOpeningTimes" @click="$emit('click', $event)" type="button" class="tb-hours-now text-grey-darkest">
                <slot :name="'label_' + view" :openingTimes="resolvedOpeningTimes" :nextOpenDate="nextOpenDate">
                    <template v-if="languageLabels[view]">
                        {{ languageLabels[view] }}
                    </template>
                    <template v-else>
                        either language label <code>{{ view }}</code> or one of the following slots must be implemented:
                        <ul>
                            <li><code>render</code></li>
                            <li><code>label_{{ view }}</code></li>
                        </ul>
                    </template>
                </slot>
                <slot name="additional_label" />
            </button>
        </slot>
        <slot name="additional_information" :openingTimes="openingTimes" :upcomingOpeningTimes="upcomingOpeningTimes" :nextOpenDate="nextOpenDate" />
    </span>
</template>
<script>
import { ToubizLegacyParser, ToubizV1Parser } from '@nimius/toubiz-opening-times-calculation';
import formatDate from 'date-fns/format';

const VIEW = Object.freeze({
    NONE: 'none',
    OPEN: 'open',
    OPEN_CLOSING_SOON_REOPENS_TODAY: 'open_closing_soon_reopens_today',
    OPEN_CLOSING_SOON: 'open_closing_soon',
    OPEN_REOPENS_TODAY: 'open_reopens_today',
    CLOSED_OPENS_SOON: 'closed_opens_soon',
    CLOSED_OPENS_TODAY: 'closed_opens_today',
    CLOSED_OPENS_TOMORROW: 'closed_opens_tomorrow',
    CLOSED_OPENS_DAY_AFTER_TOMORROW: 'closed_opens_day_after_tomorrow',
    CLOSED_OPENS_WITHIN_NEXT_WEEK: 'closed_opens_within_next_week',
    CLOSED_OPENS_ON_DATE: 'closed_opens_on_date',
    CLOSED: 'closed',
});


const parsers = {
    legacy: ToubizLegacyParser,
    toubizV1: ToubizV1Parser,
};

/**
 * Vue component that renders a opening times preview line.
 * The following views exist:
 * - `open_closing_soon_reopens_today`: Currently open, closes within the next hour but reopens later today.
 * - `open_closing_soon`: Currently open, closes soon, does not open later today.
 * - `open_reopens_today`: Currently open, has second opening times later today.
 * - `open`: Currently open, not closing soon.
 * - `closed_opens_soon`: Currently closed but opens within the next hour.
 * - `closed_opens_today`: Currently closed but opens later today.
 * - `closed_opens_tomorrow`: Currently closed but opens tomorrow.
 * - `closed_opens_day_after_tomorrow`: Currently closed but opens the day after tomorrow.
 * - `closed_opens_within_next_week`: Currently closed but opens within the next week.
 * - `closed_opens_on_date`: Currently closed but reopens on a given date.
 * - `closed`: Currently closed.
 *
 *
 * There are multiple ways of specifying how data is being rendered:
 *
 * ## Language label via prop
 * The prop `language-labels` can contain an object of labels that are being displayed for the views.
 *
 * ## Language label via slot
 * A slot named `label_{viewName}` can be specified in order to have more control over how the label is being rendered.
 * All slots are passed a `openingTimes` prop in order to make informed decissions.
 * Note, that this is only meant to be used to render a language label: All other markup is already provided.
 * If you want more control you may want to use another method listed below.
 * Specifying this slot will disable language labels via prop.
 *
 * ## Full custom rendering
 * If you don't want to be bothered by the notion of different views then you can use the `render` slot:
 * It will just give you the `openingTimes` prop and you do the rest.
 *
 * ## Additional data
 * You can specify the `additional_information` to render more data related to the opening times.
 *
 * @example
 *
 * <tb-opening-times-line-preview
 *      :opening-times-definition="{article.openingTimes}"
 *      opening-times-format="{article.openingTimesFormat}"
 *      :language-labels="{ closed: 'ITS CLOSED YO' }"
 * >
 *
 *  <template #label_open_closingSoonReopensToday="{ openingTimes }">
 *      Closed, opens at {{ openingTimes.nextTimeRange.beginning }}
 *  </template>
 *
 * </tb-opening-times-line-preview>
 */
export default {
    props: {
        openingTimesDefinition: {
            type: Object,
            required: true,
        },
        openingTimesFormat: {
            type: String,
            required: true,
            validator(value) {
                if (Object.keys(parsers).indexOf(value) === -1) {
                    console.warn(`openingTimesFormat must be one of ${Object.keys(parsers).join(', ')}. ${value} is not allowed.`);
                    return false;
                }
                return true;
            }
        },
        languageLabels: {
            type: Object,
            default: () => ({})
        },
        dateFormat: {
            type: String,
            default: 'YYYY-MM-DD',
        }
    },


    data: () => ({
        openingTimes: null,
        resolvedOpeningTimes: null,
        nextOpenDate: null,
    }),

    mounted() {
        this.update();
    },

    methods: {
        getNextDates(numberOfDays) {
            const dates = [];
            for (let i = 0; i < numberOfDays; i++) {
                const date = new Date();
                date.setDate(date.getDate() + i);
                dates.push(date);
            }
            return dates;
        },

        update() {
            const openingTimes = this.parse(this.openingTimesDefinition, this.openingTimesFormat);
            if (!openingTimes) {
                return;
            }

            this.openingTimes = openingTimes;
            this.resolvedOpeningTimes = openingTimes.timesAt(new Date());

            const nextOpen = new Date(this.resolvedOpeningTimes.nextOpenDate);
            this.resolvedOpeningTimes.nextOpenWeekday = nextOpen.getDay();
            this.nextOpenDate = formatDate(nextOpen, this.dateFormat);
        },

        parse() {
            if (!this.openingTimesDefinition) {
                return null;
            }

            const Constructor = parsers[this.openingTimesFormat];
            const collection = (new Constructor()).parse(this.openingTimesDefinition);
            if (!collection || collection.times.length === 0) {
                return null;
            }
            return collection;
        },

    },

    computed: {
        upcomingOpeningTimes() {
            const upcoming = [];
            for (const date of this.getNextDates(7)) {
                upcoming.push({
                    weekday: date.getDay(),
                    times: this.openingTimes ? this.openingTimes.openingTimesForDate(date) : null,
                })
            }
            return upcoming;
        },

        view() {
            const openingTimes = this.resolvedOpeningTimes;
            if (!openingTimes) {
                return VIEW.NONE;
            }

            if (openingTimes.isOpen) {
                if (openingTimes.minutesUntilClosing <= 60) {
                    return openingTimes.daysUntilNextOpen === 0
                        ? VIEW.OPEN_CLOSING_SOON_REOPENS_TODAY
                        : VIEW.OPEN_CLOSING_SOON;
                }

                if (openingTimes.daysUntilNextOpen === 0) {
                    return VIEW.OPEN_REOPENS_TODAY;
                }

                return VIEW.OPEN;
            }

            if (openingTimes.openingTimes === null) {
                return VIEW.CLOSED;
            }

            if (openingTimes.minutesUntilNextOpen <= 60) {
                return VIEW.CLOSED_OPENS_SOON;
            }

            switch (openingTimes.daysUntilNextOpen) {
                case 0: return VIEW.CLOSED_OPENS_TODAY;
                case 1: return VIEW.CLOSED_OPENS_TOMORROW;
                case 2: return VIEW.CLOSED_OPENS_DAY_AFTER_TOMORROW;

                case 3:
                case 4:
                case 5:
                case 6:
                case 7:
                    return VIEW.CLOSED_OPENS_WITHIN_NEXT_WEEK
            }

            if (openingTimes.daysUntilNextOpen) {
                return VIEW.CLOSED_OPENS_ON_DATE;
            }

            return VIEW.CLOSED;
        }
    },
}
</script>
