/**
 * DevExtreme (esm/__internal/scheduler/appointments/m_settings_generator.js)
 * Version: 23.2.6 (build 24140-0112)
 * Build date: Sun May 19 2024
 *
 * Copyright (c) 2012 - 2024 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 {
    extend
} from "../../../core/utils/extend";
import {
    isEmptyObject
} from "../../../core/utils/type";
import timeZoneUtils from "../../../ui/scheduler/utils.timeZone";
import {
    dateUtilsTs
} from "../../core/utils/date";
import {
    getGroupCount,
    isDateAndTimeView
} from "../../scheduler/r1/utils/index";
import {
    createAppointmentAdapter
} from "../m_appointment_adapter";
import {
    ExpressionUtils
} from "../m_expression_utils";
import {
    getRecurrenceProcessor
} from "../m_recurrence";
import {
    createResourcesTree,
    getDataAccessors,
    getResourceTreeLeaves
} from "../resources/m_utils";
import {
    CellPositionCalculator
} from "./m_cell_position_calculator";
import {
    createFormattedDateText
} from "./m_text_utils";
var toMs = dateUtils.dateToMilliseconds;
var APPOINTMENT_DATE_TEXT_FORMAT = "TIME";
export class DateGeneratorBaseStrategy {
    constructor(options) {
        this.options = options
    }
    get rawAppointment() {
        return this.options.rawAppointment
    }
    get timeZoneCalculator() {
        return this.options.timeZoneCalculator
    }
    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.isVerticalGroupOrientation
    }
    get dataAccessors() {
        return this.options.dataAccessors
    }
    get loadedResources() {
        return this.options.loadedResources
    }
    get isDateAppointment() {
        return !isDateAndTimeView(this.viewType) && this.appointmentTakesAllDay
    }
    getIntervalDuration() {
        return this.appointmentTakesAllDay ? this.options.allDayIntervalDuration : this.options.intervalDuration
    }
    generate(appointmentAdapter) {
        var {
            isRecurrent: isRecurrent
        } = appointmentAdapter;
        var itemGroupIndices = this._getGroupIndices(this.rawAppointment);
        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);
        var firstViewDates = this._getAppointmentsFirstViewDate(dateSettings);
        dateSettings = this._fillNormalizedStartDate(dateSettings, firstViewDates);
        dateSettings = this._cropAppointmentsByStartDayHour(dateSettings, firstViewDates);
        dateSettings = this._fillNormalizedEndDate(dateSettings, this.rawAppointment);
        if (this._needSeparateLongParts()) {
            dateSettings = this._separateLongParts(dateSettings, appointmentAdapter)
        }
        dateSettings = this.shiftSourceAppointmentDates(dateSettings);
        return {
            dateSettings: dateSettings,
            itemGroupIndices: itemGroupIndices,
            isRecurrent: isRecurrent
        }
    }
    shiftSourceAppointmentDates(dateSettings) {
        var {
            viewOffset: viewOffset
        } = this.options;
        return dateSettings.map(item => _extends(_extends({}, item), {
            source: _extends(_extends({}, item.source), {
                startDate: dateUtilsTs.addOffsets(item.source.startDate, [viewOffset]),
                endDate: dateUtilsTs.addOffsets(item.source.endDate, [viewOffset])
            })
        }))
    }
    _getProcessedByAppointmentTimeZone(appointmentList, appointment) {
        var hasAppointmentTimeZone = !isEmptyObject(appointment.startDateTimeZone) || !isEmptyObject(appointment.endDateTimeZone);
        if (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 (sourceOffsets_startDate.appointment !== sourceOffsets_startDate.common) {
                    a.startDate = new Date(a.startDate.getTime() + startDateOffsetDiff * toMs("hour"))
                }
                if (sourceOffsets_endDate.appointment !== sourceOffsets_endDate.common) {
                    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 _a;
            var resultEndTime = null === (_a = item.endDate) || void 0 === _a ? void 0 : _a.getTime();
            if (item.startDate.getTime() === resultEndTime) {
                item.endDate.setTime(resultEndTime + toMs("minute"))
            }
            return _extends(_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
    }
    _getCommonOffset(date) {
        return this.timeZoneCalculator.getOffsets(date).common
    }
    _getProcessedNotNativeTimezoneDates(appointmentList, appointment) {
        return appointmentList.map(item => {
            var diffStartDateOffset = this._getCommonOffset(appointment.startDate) - this._getCommonOffset(item.startDate);
            var diffEndDateOffset = this._getCommonOffset(appointment.endDate) - this._getCommonOffset(item.endDate);
            if (0 === diffStartDateOffset && 0 === diffEndDateOffset) {
                return item
            }
            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(_extends({}, item), {
                startDate: newStartDate,
                endDate: newEndDate,
                exceptionDate: new Date(newStartDate)
            })
        })
    }
    _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.dataAccessors, "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 => _extends(_extends({}, item), {
            normalizedEndDate: this.normalizeEndDateByViewEnd(rawAppointment, item.endDate)
        }))
    }
    _separateLongParts(gridAppointmentList, appointmentAdapter) {
        var result = [];
        gridAppointmentList.forEach(gridAppointment => {
            var maxDate = new Date(this.dateRange[1]);
            var {
                startDate: startDate,
                normalizedEndDate: endDateOfPart
            } = gridAppointment;
            var longStartDateParts = dateUtils.getDatesOfInterval(startDate, endDateOfPart, {
                milliseconds: this.getIntervalDuration()
            });
            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, appointmentAdapter) {
        return appointmentList.map(source => {
            var offsetDifference = appointmentAdapter.startDate.getTimezoneOffset() - source.startDate.getTimezoneOffset();
            if (0 !== offsetDifference && this._canProcessNotNativeTimezoneDates(appointmentAdapter)) {
                source.startDate = dateUtilsTs.addOffsets(source.startDate, [offsetDifference * toMs("minute")]);
                source.endDate = dateUtilsTs.addOffsets(source.endDate, [offsetDifference * toMs("minute")]);
                source.exceptionDate = new Date(source.startDate)
            }
            var duration = source.endDate.getTime() - source.startDate.getTime();
            var startDate = this.timeZoneCalculator.createDate(source.startDate, {
                path: "toGrid"
            });
            var endDate = dateUtilsTs.addOffsets(startDate, [duration]);
            return {
                startDate: startDate,
                endDate: endDate,
                allDay: appointmentAdapter.allDay || false,
                source: source
            }
        })
    }
    _createExtremeRecurrenceDates(groupIndex) {
        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 {
            viewOffset: viewOffset
        } = this.options;
        var originalAppointmentStartDate = dateUtilsTs.addOffsets(appointment.startDate, [viewOffset]);
        var originalAppointmentEndDate = dateUtilsTs.addOffsets(appointment.endDate, [viewOffset]);
        var [minRecurrenceDate, maxRecurrenceDate] = this._createExtremeRecurrenceDates(groupIndex);
        var shiftedMinRecurrenceDate = dateUtilsTs.addOffsets(minRecurrenceDate, [viewOffset]);
        var shiftedMaxRecurrenceDate = dateUtilsTs.addOffsets(maxRecurrenceDate, [viewOffset]);
        return {
            rule: appointment.recurrenceRule,
            exception: appointment.recurrenceException,
            min: shiftedMinRecurrenceDate,
            max: shiftedMaxRecurrenceDate,
            firstDayOfWeek: this.firstDayOfWeek,
            start: originalAppointmentStartDate,
            end: originalAppointmentEndDate,
            appointmentTimezoneOffset: this.timeZoneCalculator.getOriginStartDateOffsetInMs(originalAppointmentStartDate, appointment.rawAppointment.startDateTimeZone, true),
            getPostProcessedException: date => {
                if (isEmptyObject(this.timeZone) || timeZoneUtils.isEqualLocalTimeZone(this.timeZone, date)) {
                    return date
                }
                var appointmentOffset = this.timeZoneCalculator.getOffsets(originalAppointmentStartDate).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 {
            viewOffset: viewOffset
        } = this.options;
        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
            }
        }).map(_ref => {
            var {
                startDate: startDate,
                endDate: endDate
            } = _ref;
            return {
                startDate: dateUtilsTs.addOffsets(startDate, [-viewOffset]),
                endDate: dateUtilsTs.addOffsets(endDate, [-viewOffset])
            }
        })
    }
    _getAppointmentsFirstViewDate(appointments) {
        var {
            viewOffset: viewOffset
        } = this.options;
        return appointments.map(appointment => {
            var tableFirstDate = this._getAppointmentFirstViewDate(_extends(_extends({}, appointment), {
                startDate: dateUtilsTs.addOffsets(appointment.startDate, [viewOffset]),
                endDate: dateUtilsTs.addOffsets(appointment.endDate, [viewOffset])
            }));
            if (!tableFirstDate) {
                return appointment.startDate
            }
            var firstDate = dateUtilsTs.addOffsets(tableFirstDate, [-viewOffset]);
            return firstDate > appointment.startDate ? firstDate : appointment.startDate
        })
    }
    _fillNormalizedStartDate(appointments, firstViewDates, rawAppointment) {
        return appointments.map((item, idx) => _extends(_extends({}, item), {
            startDate: this._getAppointmentResultDate({
                appointment: item,
                rawAppointment: rawAppointment,
                startDate: new Date(item.startDate),
                startDayHour: this.viewStartDayHour,
                firstViewDate: firstViewDates[idx]
            })
        }))
    }
    _cropAppointmentsByStartDayHour(appointments, firstViewDates) {
        return appointments.filter((appointment, idx) => {
            if (!firstViewDates[idx]) {
                return false
            }
            if (this.appointmentTakesAllDay) {
                return true
            }
            return appointment.endDate > appointment.startDate
        })
    }
    _getAppointmentResultDate(options) {
        var {
            appointment: appointment,
            startDayHour: startDayHour,
            firstViewDate: firstViewDate
        } = options;
        var {
            startDate: startDate
        } = options;
        var resultDate;
        if (this.appointmentTakesAllDay) {
            resultDate = dateUtils.normalizeDate(startDate, firstViewDate)
        } else {
            if (startDate < firstViewDate) {
                startDate = firstViewDate
            }
            resultDate = dateUtils.normalizeDate(appointment.startDate, startDate)
        }
        return !this.isDateAppointment ? dateUtils.roundDateByStartDayHour(resultDate, startDayHour) : resultDate
    }
    _getAppointmentFirstViewDate(appointment) {
        var groupIndex = appointment.source.groupIndex || 0;
        var {
            startDate: startDate,
            endDate: endDate
        } = appointment;
        if (this.isAllDayRowAppointment || appointment.allDay) {
            return this.viewDataProvider.findAllDayGroupCellStartDate(groupIndex)
        }
        return this.viewDataProvider.findGroupCellStartDate(groupIndex, startDate, endDate, this.isDateAppointment)
    }
    _getGroupIndices(rawAppointment) {
        var result = [];
        if (rawAppointment && this.loadedResources.length) {
            var tree = createResourcesTree(this.loadedResources);
            result = getResourceTreeLeaves((field, action) => getDataAccessors(this.options.dataAccessors.resources, field, action), tree, rawAppointment)
        }
        return result
    }
}
export class DateGeneratorVirtualStrategy extends DateGeneratorBaseStrategy {
    get groupCount() {
        return getGroupCount(this.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
    }
    _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) {
        var groupIndices = super._getGroupIndices(resources);
        var viewDataGroupIndices = this.viewDataProvider.getGroupIndices();
        if (!(null === groupIndices || void 0 === groupIndices ? void 0 : 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.rawAppointment, this.dataAccessors, this.timeZoneCalculator)
    }
    get rawAppointment() {
        return this.options.rawAppointment
    }
    get dataAccessors() {
        return this.options.dataAccessors
    }
    get timeZoneCalculator() {
        return this.options.timeZoneCalculator
    }
    get isAllDayRowAppointment() {
        return this.options.appointmentTakesAllDay && this.options.supportAllDayRow
    }
    get groups() {
        return this.options.groups
    }
    get dateSettingsStrategy() {
        var options = _extends(_extends({}, this.options), {
            isAllDayRowAppointment: this.isAllDayRowAppointment
        });
        return this.options.isVirtualScrolling ? new DateGeneratorVirtualStrategy(options) : new DateGeneratorBaseStrategy(options)
    }
    create() {
        var {
            dateSettings: dateSettings,
            itemGroupIndices: itemGroupIndices,
            isRecurrent: isRecurrent
        } = this._generateDateSettings();
        var cellPositions = this._calculateCellPositions(dateSettings, itemGroupIndices);
        var result = this._prepareAppointmentInfos(dateSettings, cellPositions, isRecurrent);
        return result
    }
    _generateDateSettings() {
        return this.dateSettingsStrategy.generate(this.appointmentAdapter)
    }
    _calculateCellPositions(dateSettings, itemGroupIndices) {
        var cellPositionCalculator = new CellPositionCalculator(_extends(_extends({}, this.options), {
            dateSettings: dateSettings
        }));
        return cellPositionCalculator.calculateCellPositions(itemGroupIndices, this.isAllDayRowAppointment, this.appointmentAdapter.isRecurrent)
    }
    _prepareAppointmentInfos(dateSettings, cellPositions, isRecurrent) {
        var infos = [];
        cellPositions.forEach(_ref2 => {
            var {
                coordinates: coordinates,
                dateSettingIndex: dateSettingIndex
            } = _ref2;
            var dateSetting = dateSettings[dateSettingIndex];
            var dateText = this._getAppointmentDateText(dateSetting);
            var info = {
                appointment: dateSetting,
                sourceAppointment: dateSetting.source,
                dateText: dateText,
                isRecurrent: isRecurrent
            };
            infos.push(_extends(_extends({}, coordinates), {
                info: info
            }))
        });
        return infos
    }
    _getAppointmentDateText(sourceAppointment) {
        var {
            startDate: startDate,
            endDate: endDate,
            allDay: allDay
        } = sourceAppointment;
        return createFormattedDateText({
            startDate: startDate,
            endDate: endDate,
            allDay: allDay,
            format: APPOINTMENT_DATE_TEXT_FORMAT
        })
    }
}
