/**
 * DevExtreme (esm/ui/scheduler/appointments/settingsGenerator.js)
 * Version: 21.2.0 (build 21200-0321)
 * Build date: Mon Jul 19 2021
 *
 * Copyright (c) 2012 - 2021 Developer Express Inc. ALL RIGHTS RESERVED
 * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
 */
import _extends from "@babel/runtime/helpers/esm/extends";
import dateUtils from "../../../core/utils/date";
import {
    isEmptyObject
} from "../../../core/utils/type";
import {
    extend
} from "../../../core/utils/extend";
import {
    getRecurrenceProcessor
} from "../recurrence";
import timeZoneUtils from "../utils.timeZone.js";
import {
    createResourcesTree,
    getGroupCount
} from "../resources/utils";
import {
    createAppointmentAdapter
} from "../appointmentAdapter";
import {
    CellPositionCalculator
} from "./cellPositionCalculator";
import {
    ExpressionUtils
} from "../expressionUtils";
import {
    isDateAndTimeView
} from "../workspaces/utils/base";
var toMs = dateUtils.dateToMilliseconds;
export class DateGeneratorBaseStrategy {
    constructor(options) {
        this.options = options
    }
    get key() {
        return this.options.key
    }
    get rawAppointment() {
        return this.options.rawAppointment
    }
    get timeZoneCalculator() {
        return this.options.timeZoneCalculator
    }
    get resourceManager() {
        return this.options.resourceManager
    }
    get viewDataProvider() {
        return this.options.viewDataProvider
    }
    get appointmentTakesAllDay() {
        return this.options.appointmentTakesAllDay
    }
    get supportAllDayRow() {
        return this.options.supportAllDayRow
    }
    get isAllDayRowAppointment() {
        return this.options.isAllDayRowAppointment
    }
    get timeZone() {
        return this.options.timeZone
    }
    get dateRange() {
        return this.options.dateRange
    }
    get firstDayOfWeek() {
        return this.options.firstDayOfWeek
    }
    get viewStartDayHour() {
        return this.options.viewStartDayHour
    }
    get viewEndDayHour() {
        return this.options.viewEndDayHour
    }
    get endViewDate() {
        return this.options.endViewDate
    }
    get viewType() {
        return this.options.viewType
    }
    get isGroupedByDate() {
        return this.options.isGroupedByDate
    }
    get isVerticalOrientation() {
        return this.options.isVerticalOrientation
    }
    getIntervalDuration() {
        return this.appointmentTakesAllDay ? this.options.allDayIntervalDuration : this.options.intervalDuration
    }
    generate(appointmentAdapter) {
        var itemResources = this.resourceManager.getResourcesFromItem(this.rawAppointment);
        var itemGroupIndices = this._getGroupIndices(itemResources, this.resourceManager);
        var appointmentList = this._createAppointments(appointmentAdapter, itemGroupIndices);
        appointmentList = this._getProcessedByAppointmentTimeZone(appointmentList, appointmentAdapter);
        if (this._canProcessNotNativeTimezoneDates(appointmentAdapter)) {
            appointmentList = this._getProcessedNotNativeTimezoneDates(appointmentList, appointmentAdapter)
        }
        var dateSettings = this._createGridAppointmentList(appointmentList, appointmentAdapter);
        dateSettings = this._cropAppointmentsByStartDayHour(dateSettings, this.rawAppointment);
        dateSettings = this._fillNormalizedEndDate(dateSettings, this.rawAppointment);
        if (this._needSeparateLongParts()) {
            dateSettings = this._separateLongParts(dateSettings, appointmentAdapter)
        }
        return {
            dateSettings: dateSettings,
            itemGroupIndices: itemGroupIndices
        }
    }
    _getProcessedByAppointmentTimeZone(appointmentList, appointment) {
        var hasAppointmentTimeZone = !isEmptyObject(appointment.startDateTimeZone) || !isEmptyObject(appointment.endDateTimeZone);
        if (appointmentList.length > 1 && hasAppointmentTimeZone) {
            var appointmentOffsets = {
                startDate: this.timeZoneCalculator.getOffsets(appointment.startDate, appointment.startDateTimeZone),
                endDate: this.timeZoneCalculator.getOffsets(appointment.endDate, appointment.endDateTimeZone)
            };
            appointmentList.forEach(a => {
                var sourceOffsets_startDate = this.timeZoneCalculator.getOffsets(a.startDate, appointment.startDateTimeZone),
                    sourceOffsets_endDate = this.timeZoneCalculator.getOffsets(a.endDate, appointment.endDateTimeZone);
                var startDateOffsetDiff = appointmentOffsets.startDate.appointment - sourceOffsets_startDate.appointment;
                var endDateOffsetDiff = appointmentOffsets.endDate.appointment - sourceOffsets_endDate.appointment;
                if (0 !== startDateOffsetDiff) {
                    a.startDate = new Date(a.startDate.getTime() + startDateOffsetDiff * toMs("hour"))
                }
                if (0 !== endDateOffsetDiff) {
                    a.endDate = new Date(a.endDate.getTime() + endDateOffsetDiff * toMs("hour"))
                }
            })
        }
        return appointmentList
    }
    _createAppointments(appointment, groupIndices) {
        var appointments = this._createRecurrenceAppointments(appointment, groupIndices);
        if (!appointment.isRecurrent && 0 === appointments.length) {
            appointments.push({
                startDate: appointment.startDate,
                endDate: appointment.endDate
            })
        }
        appointments = appointments.map(item => {
            var _item$endDate;
            var resultEndTime = null === (_item$endDate = item.endDate) || void 0 === _item$endDate ? void 0 : _item$endDate.getTime();
            if (item.startDate.getTime() === resultEndTime) {
                item.endDate.setTime(resultEndTime + toMs("minute"))
            }
            return _extends({}, item, {
                exceptionDate: new Date(item.startDate)
            })
        });
        return appointments
    }
    _canProcessNotNativeTimezoneDates(appointment) {
        var isTimeZoneSet = !isEmptyObject(this.timeZone);
        if (!isTimeZoneSet) {
            return false
        }
        if (!appointment.isRecurrent) {
            return false
        }
        return !timeZoneUtils.isEqualLocalTimeZone(this.timeZone, appointment.startDate)
    }
    _getProcessedNotNativeDateIfCrossDST(date, offset) {
        if (offset < 0) {
            var newDate = new Date(date);
            var newDateMinusOneHour = new Date(newDate);
            newDateMinusOneHour.setHours(newDateMinusOneHour.getHours() - 1);
            var newDateOffset = this.timeZoneCalculator.getOffsets(newDate).common;
            var newDateMinusOneHourOffset = this.timeZoneCalculator.getOffsets(newDateMinusOneHour).common;
            if (newDateOffset !== newDateMinusOneHourOffset) {
                return 0
            }
        }
        return offset
    }
    _getProcessedNotNativeTimezoneDates(appointmentList, appointment) {
        var startDateRange = appointment.startDate;
        var endDateRange = appointmentList[appointmentList.length - 1].endDate;
        var startDateRangeOffset = this.timeZoneCalculator.getOffsets(startDateRange).common;
        var endDateRangeOffset = this.timeZoneCalculator.getOffsets(endDateRange).common;
        var isChangeOffsetInRange = startDateRangeOffset !== endDateRangeOffset;
        if (isChangeOffsetInRange) {
            return appointmentList.map(item => {
                var diffStartDateOffset = this.timeZoneCalculator.getOffsets(appointment.startDate).common - this.timeZoneCalculator.getOffsets(item.startDate).common;
                var diffEndDateOffset = this.timeZoneCalculator.getOffsets(appointment.endDate).common - this.timeZoneCalculator.getOffsets(item.endDate).common;
                diffStartDateOffset = this._getProcessedNotNativeDateIfCrossDST(item.startDate, diffStartDateOffset);
                diffEndDateOffset = this._getProcessedNotNativeDateIfCrossDST(item.endDate, diffEndDateOffset);
                var newStartDate = new Date(item.startDate.getTime() + diffStartDateOffset * toMs("hour"));
                var newEndDate = new Date(item.endDate.getTime() + diffEndDateOffset * toMs("hour"));
                var testNewStartDate = this.timeZoneCalculator.createDate(newStartDate, {
                    path: "toGrid"
                });
                var testNewEndDate = this.timeZoneCalculator.createDate(newEndDate, {
                    path: "toGrid"
                });
                if (appointment.duration > testNewEndDate.getTime() - testNewStartDate.getTime()) {
                    newEndDate = new Date(newStartDate.getTime() + appointment.duration)
                }
                return _extends({}, item, {
                    startDate: newStartDate,
                    endDate: newEndDate,
                    exceptionDate: new Date(newStartDate)
                })
            })
        }
        return appointmentList
    }
    _needSeparateLongParts() {
        return this.isVerticalOrientation ? this.isGroupedByDate : this.isGroupedByDate && this.appointmentTakesAllDay
    }
    normalizeEndDateByViewEnd(rawAppointment, endDate) {
        var result = new Date(endDate.getTime());
        var isAllDay = isDateAndTimeView(this.viewType) && this.appointmentTakesAllDay;
        if (!isAllDay) {
            var roundedEndViewDate = dateUtils.roundToHour(this.endViewDate);
            if (result > roundedEndViewDate) {
                result = roundedEndViewDate
            }
        }
        var endDayHour = this.viewEndDayHour;
        var allDay = ExpressionUtils.getField(this.key, "allDay", rawAppointment);
        var currentViewEndTime = new Date(new Date(endDate.getTime()).setHours(endDayHour, 0, 0, 0));
        if (result.getTime() > currentViewEndTime.getTime() || allDay && result.getHours() < endDayHour) {
            result = currentViewEndTime
        }
        return result
    }
    _fillNormalizedEndDate(dateSettings, rawAppointment) {
        return dateSettings.map(item => {
            var {
                endDate: endDate
            } = item;
            var normalizedEndDate = this.normalizeEndDateByViewEnd(rawAppointment, endDate);
            return _extends({}, item, {
                normalizedEndDate: normalizedEndDate
            })
        })
    }
    _separateLongParts(gridAppointmentList, appointmentAdapter) {
        var result = [];
        gridAppointmentList.forEach(gridAppointment => {
            var maxDate = new Date(this.dateRange[1]);
            var {
                normalizedEndDate: endDateOfPart
            } = gridAppointment;
            var longStartDateParts = dateUtils.getDatesOfInterval(gridAppointment.startDate, endDateOfPart, {
                milliseconds: this.getIntervalDuration(this.appointmentTakesAllDay)
            });
            var list = longStartDateParts.filter(startDatePart => new Date(startDatePart) < maxDate).map(date => {
                var endDate = new Date(new Date(date).setMilliseconds(appointmentAdapter.duration));
                var normalizedEndDate = this.normalizeEndDateByViewEnd(this.rawAppointment, endDate);
                return {
                    startDate: date,
                    endDate: endDate,
                    normalizedEndDate: normalizedEndDate,
                    source: gridAppointment.source
                }
            });
            result = result.concat(list)
        });
        return result
    }
    _createGridAppointmentList(appointmentList, appointment) {
        return appointmentList.map(source => {
            var offsetDifference = appointment.startDate.getTimezoneOffset() - source.startDate.getTimezoneOffset();
            if (0 !== offsetDifference && this._canProcessNotNativeTimezoneDates(appointment)) {
                source.startDate = new Date(source.startDate.getTime() + offsetDifference * toMs("minute"));
                source.endDate = new Date(source.endDate.getTime() + offsetDifference * toMs("minute"));
                source.exceptionDate = new Date(source.startDate)
            }
            var startDate = this.timeZoneCalculator.createDate(source.startDate, {
                path: "toGrid"
            });
            var endDate = this.timeZoneCalculator.createDate(source.endDate, {
                path: "toGrid"
            });
            return {
                startDate: startDate,
                endDate: endDate,
                source: source
            }
        })
    }
    _createExtremeRecurrenceDates() {
        var startViewDate = this.appointmentTakesAllDay ? dateUtils.trimTime(this.dateRange[0]) : this.dateRange[0];
        var endViewDateByEndDayHour = this.dateRange[1];
        if (this.timeZone) {
            startViewDate = this.timeZoneCalculator.createDate(startViewDate, {
                path: "fromGrid"
            });
            endViewDateByEndDayHour = this.timeZoneCalculator.createDate(endViewDateByEndDayHour, {
                path: "fromGrid"
            });
            var daylightOffset = timeZoneUtils.getDaylightOffsetInMs(startViewDate, endViewDateByEndDayHour);
            if (daylightOffset) {
                endViewDateByEndDayHour = new Date(endViewDateByEndDayHour.getTime() + daylightOffset)
            }
        }
        return [startViewDate, endViewDateByEndDayHour]
    }
    _createRecurrenceOptions(appointment, groupIndex) {
        var [minRecurrenceDate, maxRecurrenceDate] = this._createExtremeRecurrenceDates(groupIndex);
        return {
            rule: appointment.recurrenceRule,
            exception: appointment.recurrenceException,
            min: minRecurrenceDate,
            max: maxRecurrenceDate,
            firstDayOfWeek: this.firstDayOfWeek,
            start: appointment.startDate,
            end: appointment.endDate,
            getPostProcessedException: date => {
                if (isEmptyObject(this.timeZone) || timeZoneUtils.isEqualLocalTimeZone(this.timeZone, date)) {
                    return date
                }
                var appointmentOffset = this.timeZoneCalculator.getOffsets(appointment.startDate).common;
                var exceptionAppointmentOffset = this.timeZoneCalculator.getOffsets(date).common;
                var diff = appointmentOffset - exceptionAppointmentOffset;
                diff = this._getProcessedNotNativeDateIfCrossDST(date, diff);
                return new Date(date.getTime() - diff * dateUtils.dateToMilliseconds("hour"))
            }
        }
    }
    _createRecurrenceAppointments(appointment, groupIndices) {
        var {
            duration: duration
        } = appointment;
        var option = this._createRecurrenceOptions(appointment);
        var generatedStartDates = getRecurrenceProcessor().generateDates(option);
        return generatedStartDates.map(date => {
            var utcDate = timeZoneUtils.createUTCDateWithLocalOffset(date);
            utcDate.setTime(utcDate.getTime() + duration);
            var endDate = timeZoneUtils.createDateFromUTCWithLocalOffset(utcDate);
            return {
                startDate: new Date(date),
                endDate: endDate
            }
        })
    }
    _cropAppointmentsByStartDayHour(appointments, rawAppointment) {
        return appointments.filter(appointment => {
            var firstViewDate = this._getAppointmentFirstViewDate(appointment);
            if (!firstViewDate) {
                return false
            }
            var startDayHour = this._getViewStartDayHour(firstViewDate);
            var startDate = new Date(appointment.startDate);
            appointment.startDate = this._getAppointmentResultDate({
                appointment: appointment,
                rawAppointment: rawAppointment,
                startDate: startDate,
                startDayHour: startDayHour,
                firstViewDate: firstViewDate
            });
            return !this.isAllDayRowAppointment ? appointment.endDate > appointment.startDate : true
        })
    }
    _getViewStartDayHour() {
        return this.viewStartDayHour
    }
    _getAppointmentResultDate(options) {
        var {
            appointment: appointment,
            startDayHour: startDayHour,
            firstViewDate: firstViewDate
        } = options;
        var {
            startDate: startDate
        } = options;
        var resultDate = new Date(appointment.startDate);
        if (this.appointmentTakesAllDay) {
            resultDate = dateUtils.normalizeDate(startDate, firstViewDate)
        } else {
            if (startDate < firstViewDate) {
                startDate = firstViewDate
            }
            resultDate = dateUtils.normalizeDate(appointment.startDate, startDate)
        }
        return dateUtils.roundDateByStartDayHour(resultDate, startDayHour)
    }
    _getAppointmentFirstViewDate(appointment) {
        var groupIndex = appointment.source.groupIndex || 0;
        var {
            startDate: startDate,
            endDate: endDate
        } = appointment;
        return this.viewDataProvider.findGroupCellStartDate(groupIndex, startDate, endDate, this.isAllDayRowAppointment)
    }
    _getGroupIndices(appointmentResources, resourceManager) {
        var result = [];
        if (appointmentResources && resourceManager.loadedResources.length) {
            var tree = createResourcesTree(resourceManager.loadedResources);
            result = resourceManager.getResourceTreeLeaves(tree, appointmentResources)
        }
        return result
    }
}
export class DateGeneratorVirtualStrategy extends DateGeneratorBaseStrategy {
    get groupCount() {
        return getGroupCount(this.resourceManager.loadedResources)
    }
    _createRecurrenceAppointments(appointment, groupIndices) {
        var {
            duration: duration
        } = appointment;
        var result = [];
        var validGroupIndices = this.groupCount ? groupIndices : [0];
        validGroupIndices.forEach(groupIndex => {
            var option = this._createRecurrenceOptions(appointment, groupIndex);
            var generatedStartDates = getRecurrenceProcessor().generateDates(option);
            var recurrentInfo = generatedStartDates.map(date => {
                var startDate = new Date(date);
                var utcDate = timeZoneUtils.createUTCDateWithLocalOffset(date);
                utcDate.setTime(utcDate.getTime() + duration);
                var endDate = timeZoneUtils.createDateFromUTCWithLocalOffset(utcDate);
                return {
                    startDate: startDate,
                    endDate: endDate,
                    groupIndex: groupIndex
                }
            });
            result.push(...recurrentInfo)
        });
        return result
    }
    _getViewStartDayHour(firstViewDate) {
        return firstViewDate.getHours()
    }
    _updateGroupIndices(appointments, groupIndices) {
        var result = [];
        groupIndices.forEach(groupIndex => {
            var groupStartDate = this.viewDataProvider.getGroupStartDate(groupIndex);
            if (groupStartDate) {
                appointments.forEach(appointment => {
                    var appointmentCopy = extend({}, appointment);
                    appointmentCopy.groupIndex = groupIndex;
                    result.push(appointmentCopy)
                })
            }
        });
        return result
    }
    _getGroupIndices(resources, resourceManager) {
        var _groupIndices;
        var groupIndices = super._getGroupIndices(resources, resourceManager);
        var viewDataGroupIndices = this.viewDataProvider.getGroupIndices();
        if (!(null !== (_groupIndices = groupIndices) && void 0 !== _groupIndices && _groupIndices.length)) {
            groupIndices = [0]
        }
        return groupIndices.filter(groupIndex => -1 !== viewDataGroupIndices.indexOf(groupIndex))
    }
    _createAppointments(appointment, groupIndices) {
        var appointments = super._createAppointments(appointment, groupIndices);
        return !appointment.isRecurrent ? this._updateGroupIndices(appointments, groupIndices) : appointments
    }
}
export class AppointmentSettingsGenerator {
    constructor(options) {
        this.options = options;
        this.appointmentAdapter = createAppointmentAdapter(this.options.key, this.rawAppointment)
    }
    get rawAppointment() {
        return this.options.rawAppointment
    }
    get resourceManager() {
        return this.options.resourceManager
    }
    get isAllDayRowAppointment() {
        return this.options.appointmentTakesAllDay && this.options.supportAllDayRow
    }
    get dateSettingsStrategy() {
        var options = _extends({}, this.options, {
            isAllDayRowAppointment: this.isAllDayRowAppointment
        });
        return this.options.isVirtualScrolling ? new DateGeneratorVirtualStrategy(options) : new DateGeneratorBaseStrategy(options)
    }
    create() {
        var {
            dateSettings: dateSettings,
            itemGroupIndices: itemGroupIndices
        } = this._generateDateSettings();
        var cellPositions = this._calculateCellPositions(dateSettings, itemGroupIndices);
        var result = this._prepareAppointmentInfos(dateSettings, cellPositions);
        return result
    }
    _generateDateSettings() {
        return this.dateSettingsStrategy.generate(this.appointmentAdapter)
    }
    _calculateCellPositions(dateSettings, itemGroupIndices) {
        var cellPositionCalculator = new CellPositionCalculator(_extends({}, this.options, {
            dateSettings: dateSettings
        }));
        return cellPositionCalculator.calculateCellPositions(itemGroupIndices, this.isAllDayRowAppointment, this.appointmentAdapter.isRecurrent)
    }
    _prepareAppointmentInfos(dateSettings, cellPositions) {
        var infos = [];
        cellPositions.forEach(_ref => {
            var {
                coordinates: coordinates,
                dateSettingIndex: dateSettingIndex
            } = _ref;
            var dateSetting = dateSettings[dateSettingIndex];
            infos.push(_extends({}, coordinates, {
                info: {
                    appointment: dateSetting,
                    sourceAppointment: dateSetting.source
                }
            }))
        });
        return infos
    }
}
