<template>
    <span>
        <slot
            name="render"
            v-if="resolvedOpeningTimes"
            :openingTimes="resolvedOpeningTimes"
            :view="view"
        >
            <button
                v-if="resolvedOpeningTimes && view !== 'none'"
                @click="toggleAdditional"
                class="tb-hours-now text-grey-darkest"
                :class="{ 'cursor-default': !toggleEnabled }"
            >

                <strong v-if="view === 'always_open'" class="tb-hours-now__state tb-hours-now__state--open">
                    {{ translate('heading.alwaysOpen') }}
                </strong>

                <span v-else-if="view === 'open'" class="tb-hours-now__info-line">
                    <strong class="tb-hours-now__state tb-hours-now__state--open">
                        {{ translate('heading.open') }}
                    </strong>
                    {{ translate('heading.open.until', [ resolvedOpeningTimes.timeRange.end ]) }}
                </span>

                <template v-else-if="view === 'open_closing_soon_reopens_today'">
                    <span class="tb-hours-now__info-line">
                        <strong class="tb-hours-now__state tb-hours-now__state--changes-soon">
                            {{ translate('heading.closingSoon') }}
                        </strong>
                        <span class="tb-hours-now__info-line">
                            {{ translate('subtext.closesAt', [ resolvedOpeningTimes.timeRange.end ]) }}
                        </span>
                        <span class="tb-hours-now__info-line">
                            {{ translate('subtext.opensAgainAt', [
                                resolvedOpeningTimes.timeRange.beginning,
                                resolvedOpeningTimes.timeRange.end
                            ]) }}
                        </span>
                    </span>
                </template>

                <template v-else-if="view === 'open_closing_soon'">
                    <span class="tb-hours-now__info-line">
                        <strong class="tb-hours-now__state tb-hours-now__state--changes-soon">
                            {{ translate('heading.closingSoon') }}
                        </strong>
                    </span>
                    <span class="tb-hours-now__info-line">
                        {{ translate('subtext.closesAt', [ resolvedOpeningTimes.timeRange.end ]) }}
                    </span>
                </template>

                <template v-else-if="view === 'open_reopens_today'">
                    <span class="tb-hours-now__info-line">
                        <strong class="tb-hours-now__state tb-hours-now__state--open">
                            {{ translate('heading.open') }}
                        </strong>
                        {{ translate('heading.open.until', [ resolvedOpeningTimes.timeRange.end ]) }}
                    </span>
                    <span class="tb-hours-now__info-line">
                        {{ translate('subtext.opensAgainAt', [
                            resolvedOpeningTimes.timeRange.beginning,
                            resolvedOpeningTimes.timeRange.end
                        ]) }}
                    </span>
                </template>

                <template v-else-if="view === 'closed_opens_soon'">
                    <span class="tb-hours-now__info-line">
                        <strong class="tb-hours-now__state tb-hours-now__state--changes-soon">
                            {{ translate('heading.openSoon') }}
                        </strong>
                    </span>
                    <span class="tb-hours-now__info-line">
                        {{ translate('subtext.openTime', [
                            resolvedOpeningTimes.nextTimeRange.beginning,
                            resolvedOpeningTimes.nextTimeRange.end
                        ]) }}
                    </span>
                </template>

                <template v-else-if="view === 'closed_opens_today'">
                    <span class="tb-hours-now__info-line">
                        <strong class="tb-hours-now__state tb-hours-now__state--closed">
                            {{ translate('heading.closed') }}
                        </strong>
                    </span>
                    <span class="tb-hours-now__info-line">
                        {{ translate('subtext.openTime', [
                            resolvedOpeningTimes.nextTimeRange.beginning,
                            resolvedOpeningTimes.nextTimeRange.end
                        ]) }}
                    </span>
                </template>

                <template v-else-if="view === 'closed_opens_tomorrow'">
                    <span class="tb-hours-now__info-line">
                        <strong class="tb-hours-now__state tb-hours-now__state--closed">
                            {{ translate('heading.closedToday') }}
                        </strong>
                    </span>
                    <span class="tb-hours-now__info-line">
                        {{ translate('subtext.openTomorrow', [
                            resolvedOpeningTimes.nextTimeRange.beginning,
                            resolvedOpeningTimes.nextTimeRange.end
                        ]) }}
                    </span>
                </template>

                <template v-else-if="view === 'closed_opens_day_after_tomorrow'">
                    <span class="tb-hours-now__info-line">
                        <strong class="tb-hours-now__state tb-hours-now__state--closed">
                            {{ translate('heading.closedToday') }}
                        </strong>
                    </span>
                    <span class="tb-hours-now__info-line">
                        {{ translate('subtext.openDayAfterTomorrow', [
                            resolvedOpeningTimes.nextTimeRange.beginning,
                            resolvedOpeningTimes.nextTimeRange.end
                        ]) }}
                    </span>
                </template>

                <template v-else-if="view === 'closed_opens_within_next_week'">
                    <span class="tb-hours-now__info-line">
                        <strong class="tb-hours-now__state tb-hours-now__state--closed">
                            {{ translate('heading.closedToday') }}
                        </strong>
                    </span>
                    <span class="tb-hours-now__info-line">
                        <template v-if="isNextOpenOnSpecificWeekDay">
                            {{ translate(`subtext.openOnWeekday.${weekdayName(resolvedOpeningTimes.nextOpenWeekday)}`, [
                                resolvedOpeningTimes.nextTimeRange.beginning,
                                resolvedOpeningTimes.nextTimeRange.end
                            ]) }}
                        </template>
                        <template v-else>
                            {{ translate('subtext.opensWithinNextWeek', [
                                resolvedOpeningTimes.nextTimeRange.beginning,
                                resolvedOpeningTimes.nextTimeRange.end
                            ]) }}
                        </template>
                    </span>
                </template>

                <template v-else-if="view === 'closed_opens_on_date'">
                    <span class="tb-hours-now__info-line">
                        <strong class="tb-hours-now__state tb-hours-now__state--closed">
                            {{ translate('heading.closedToday') }}
                        </strong>
                    </span>
                    <span class="tb-hours-now__info-line">
                        {{ translate('subtext.opensOn', [
                            nextOpenDate,
                            resolvedOpeningTimes.nextTimeRange.beginning,
                            resolvedOpeningTimes.nextTimeRange.end,
                        ]) }}
                    </span>
                </template>

                <template v-else-if="view === 'closed'">
                    <span class="tb-hours-now__info-line">
                        <strong class="tb-hours-now__state tb-hours-now__state--closed">
                            {{ translate('heading.closedToday') }}
                        </strong>
                    </span>
                </template>

                <slot v-if="toggleEnabled" name="additional_label" :additionalActive="additionalActive" />
            </button>
        </slot>
        <slot
            v-if="toggleEnabled"
            name="additional_information"
            :openingTimes="openingTimes"
            :upcomingOpeningTimes="upcomingOpeningTimes"
            :nextOpenDate="nextOpenDate"
            :additionalActive="additionalActive"
        />
    </span>
</template>
<script>
import {
    ToubizLegacyAttractionParser,
    ToubizLegacyParser,
    ToubizV1Parser,
} from '@nimius/toubiz-opening-times-calculation';
import formatDate from 'date-fns/format';

const VIEW = Object.freeze({
    NONE: 'none',
    OPEN: 'open',
    ALWAYS_OPEN: 'always_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,
    'legacy-attraction': ToubizLegacyAttractionParser,
    '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 {
    name: 'TbOpeningTimesLinePreview',
    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,
        toggleEnabled: false,
        additionalActive: false,
        openDate: null,
    }),

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

    methods: {

        translate(label, replacements = []) {
            let text = this.languageLabels[label] || label;
            for (const key of Object.keys(replacements)) {
                text = text.replace(new RegExp(`\\{${key}\\}`, 'g'), replacements[key]);
            }
            return text;
        },

        weekdayName(number) {
            switch (number) {
                case 1: return 'monday';
                case 2: return 'tuesday';
                case 3: return 'wednesday';
                case 4: return 'thursday';
                case 5: return 'friday';
                case 6: return 'saturday';
                case 7:
                case 0:
                    return 'sunday';
            }
        },

        getNextDates(numberOfDays) {
            const dates = [];
            for (let i = 0; i < numberOfDays; i++) {
                const date = new Date(this.openDate);
                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;
            const resolvedOpeningTimes = openingTimes.timesAt(new Date());
            this.toggleEnabled = this.$scopedSlots.additional_information
                && resolvedOpeningTimes
                && !resolvedOpeningTimes.defaultsToAlwaysOpen
                && resolvedOpeningTimes.daysUntilNextOpen <= 7;
            this.resolvedOpeningTimes = resolvedOpeningTimes;
            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;
        },

        toggleAdditional() {
            if (this.toggleEnabled) {
                this.additionalActive = !this.additionalActive;
            }
        },

        setOpenDate(date) {
            this.openDate = date;
            this.update();
        },
    },

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

        view() {
            const openingTimes = this.resolvedOpeningTimes;

            if (
                !openingTimes ||
                (!openingTimes.isOpen && !openingTimes.nextOpenDate)
            ) {
                return VIEW.NONE;
            }

            if (openingTimes.defaultsToAlwaysOpen) {
                return VIEW.ALWAYS_OPEN;
            }

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

                if (openingTimes.daysUntilNextOpen === 0
                    && openingTimes.timeRange.end.hour < openingTimes.nextTimeRange.beginning.hour
                ) {
                    return VIEW.OPEN_REOPENS_TODAY;
                }

                return VIEW.OPEN;
            }

            if (openingTimes.daysUntilNextOpen <= 1
                && openingTimes.minutesUntilNextOpen !== null
                && 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:
                    return VIEW.CLOSED_OPENS_WITHIN_NEXT_WEEK;
            }

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

            return VIEW.CLOSED;
        },

        isNextOpenOnSpecificWeekDay() {
            return this.resolvedOpeningTimes.nextOpenWeekday >= 1 && this.resolvedOpeningTimes.nextOpenWeekday <= 7;
        },
    },
};
</script>
