import OneUptimeDate from "../Date";
import EventInterval from "../Events/EventInterval";
import Recurring from "../Events/Recurring";
import Typeof from "../Typeof";
import RestrictionTimes, { RestrictionType, } from "./RestrictionTimes";
export default class LayerUtil {
    getEvents(data, options) {
        let events = [];
        if (!this.isDataValid(data)) {
            return [];
        }
        data = this.sanitizeData(data);
        let start = data.calendarStartDate;
        const end = data.calendarEndDate;
        // start time of the layer is after the start time of the calendar, so we need to update the start time of the calendar
        if (OneUptimeDate.isAfter(data.startDateTimeOfLayer, start)) {
            start = data.startDateTimeOfLayer;
        }
        // split events by rotation.
        const rotation = data.rotation;
        let hasReachedTheEndOfTheCalendar = false;
        let handOffTime = data.handOffTime;
        if (!handOffTime) {
            return [];
        }
        // Looop vars
        let currentUserIndex = 0;
        let currentEventStartTime = start;
        // bring handoff time to the same day as the currentStartTime.
        // before we do this, we need to update the user index.
        currentUserIndex = this.getCurrentUserIndexBasedOnHandoffTime({
            rotation,
            handOffTime,
            currentUserIndex,
            startDateTimeOfLayer: data.startDateTimeOfLayer,
            users: data.users,
            currentEventStartTime,
            restrictionTimes: data.restrictionTimes,
        });
        // update handoff time to the same day as current start time
        handOffTime = this.moveHandsOffTimeAfterCurrentEventStartTime({
            handOffTime,
            currentEventStartTime,
            rotation: data.rotation,
        });
        let currentEventEndTime = OneUptimeDate.getCurrentDate(); // temporary set to current time to avoid typescript error
        // check if calendar end is before the handoff time. if it is, then we need to return the event with the current user index as no handoff is needed.
        if (OneUptimeDate.isBefore(end, handOffTime)) {
            const trimmedStartAndEndTimes = this.trimStartAndEndTimesBasedOnRestrictionTimes({
                eventStartTime: currentEventStartTime,
                eventEndTime: end,
                restrictionTimes: data.restrictionTimes,
            });
            events = [
                ...events,
                ...this.getCalendarEventsFromStartAndEndDates(trimmedStartAndEndTimes, data.users, currentUserIndex),
            ];
            return events;
        }
        // break clause. This loop executes 50 times at max.
        const maxLoopCount = 100;
        let loopCount = 0;
        while (!hasReachedTheEndOfTheCalendar) {
            loopCount++;
            if (loopCount > maxLoopCount) {
                break;
            }
            currentEventEndTime = handOffTime;
            // if current event start time and end time is the same then increase current event start time by 1 second.
            if (OneUptimeDate.isSame(currentEventStartTime, currentEventEndTime)) {
                currentEventStartTime = OneUptimeDate.addRemoveSeconds(currentEventEndTime, 1);
                handOffTime = this.moveHandsOffTimeAfterCurrentEventStartTime({
                    handOffTime,
                    currentEventStartTime,
                    rotation: data.rotation,
                });
                continue;
            }
            // check calendar end time. if the end time of the event is after the end time of the calendar, we need to update the end time of the event
            if (OneUptimeDate.isAfter(currentEventEndTime, end)) {
                currentEventEndTime = end;
                hasReachedTheEndOfTheCalendar = true;
            }
            // check restriction times. if the end time of the event is after the end time of the restriction times, we need to update the end time of the event.
            const trimmedStartAndEndTimes = this.trimStartAndEndTimesBasedOnRestrictionTimes({
                eventStartTime: currentEventStartTime,
                eventEndTime: currentEventEndTime,
                restrictionTimes: data.restrictionTimes,
            });
            events = [
                ...events,
                ...this.getCalendarEventsFromStartAndEndDates(trimmedStartAndEndTimes, data.users, currentUserIndex),
            ];
            if ((options === null || options === void 0 ? void 0 : options.getNumberOfEvents) !== undefined) {
                if (events.length >= options.getNumberOfEvents) {
                    return events;
                }
            }
            // update the current event start time
            currentEventStartTime = OneUptimeDate.addRemoveSeconds(currentEventEndTime, 1);
            // update the handoff time
            handOffTime = this.moveHandsOffTimeAfterCurrentEventStartTime({
                handOffTime,
                currentEventStartTime,
                rotation: data.rotation,
            });
            /*
             * Only advance the rotation if at least one event was actually generated
             * for this rotation period. Otherwise the user "lost" their turn to a
             * fully restricted window (e.g. a weekend with Mon-Fri restrictions),
             * which would skip rotations and break ordering across the gap. See
             * issue #2413.
             */
            if (trimmedStartAndEndTimes.length > 0) {
                currentUserIndex = this.incrementUserIndex(currentUserIndex, data.users.length);
            }
        }
        // increment ids of all the events and return them, to make sure they are unique
        let id = 1;
        for (const event of events) {
            event.id = id;
            id++;
        }
        if ((options === null || options === void 0 ? void 0 : options.getNumberOfEvents) !== undefined) {
            if (events.length > options.getNumberOfEvents) {
                events = events.slice(0, options.getNumberOfEvents);
            }
        }
        return events;
    }
    sanitizeData(data) {
        if (!(data.restrictionTimes instanceof RestrictionTimes)) {
            data.restrictionTimes = RestrictionTimes.fromJSON(data.restrictionTimes);
        }
        if (!(data.rotation instanceof Recurring)) {
            data.rotation = Recurring.fromJSON(data.rotation);
        }
        if (typeof data.startDateTimeOfLayer === Typeof.String) {
            data.startDateTimeOfLayer = OneUptimeDate.fromString(data.startDateTimeOfLayer);
        }
        if (typeof data.calendarStartDate === Typeof.String) {
            data.calendarStartDate = OneUptimeDate.fromString(data.calendarStartDate);
        }
        if (typeof data.calendarEndDate === Typeof.String) {
            data.calendarEndDate = OneUptimeDate.fromString(data.calendarEndDate);
        }
        if (typeof data.handOffTime === Typeof.String) {
            data.handOffTime = OneUptimeDate.fromString(data.handOffTime);
        }
        return data;
    }
    isDataValid(data) {
        // if calendar end time is before the start time then return an empty array.
        if (OneUptimeDate.isBefore(data.calendarEndDate, data.calendarStartDate)) {
            return false;
        }
        // end time of the layer is before the end time of the calendar, so, we dont have any events and we can return empty array
        if (OneUptimeDate.isAfter(data.startDateTimeOfLayer, data.calendarEndDate)) {
            return false;
        }
        // if users are empty, we dont have any events and we can return empty array
        if (data.users.length === 0) {
            return false;
        }
        return true;
    }
    moveHandsOffTimeAfterCurrentEventStartTime(data) {
        // if handoff time is ahead of the current event start time, then we dont need to move and we can return it as is.
        if (OneUptimeDate.isAfter(data.handOffTime, data.currentEventStartTime)) {
            return data.handOffTime;
        }
        let handOffTime = data.handOffTime;
        let intervalBetweenStartTimeAndHandoffTime = 0;
        const rotationInterval = data.rotation.intervalCount.toNumber();
        if (data.rotation.intervalType === EventInterval.Day) {
            intervalBetweenStartTimeAndHandoffTime =
                OneUptimeDate.getDaysBetweenTwoDatesInclusive(handOffTime, data.currentEventStartTime);
            if (intervalBetweenStartTimeAndHandoffTime < rotationInterval) {
                intervalBetweenStartTimeAndHandoffTime = rotationInterval;
            }
            else if (intervalBetweenStartTimeAndHandoffTime % rotationInterval !==
                0) {
                intervalBetweenStartTimeAndHandoffTime += rotationInterval;
            }
            // add intervalBetweenStartTimeAndHandoffTime to handoff time
            handOffTime = OneUptimeDate.addRemoveDays(handOffTime, intervalBetweenStartTimeAndHandoffTime);
            if (OneUptimeDate.isOnOrBefore(handOffTime, data.currentEventStartTime)) {
                handOffTime = OneUptimeDate.addRemoveDays(handOffTime, 1);
            }
            return handOffTime;
        }
        if (data.rotation.intervalType === EventInterval.Hour) {
            intervalBetweenStartTimeAndHandoffTime =
                OneUptimeDate.getHoursBetweenTwoDatesInclusive(handOffTime, data.currentEventStartTime);
            if (intervalBetweenStartTimeAndHandoffTime < rotationInterval) {
                intervalBetweenStartTimeAndHandoffTime = rotationInterval;
            }
            else if (intervalBetweenStartTimeAndHandoffTime % rotationInterval !==
                0) {
                intervalBetweenStartTimeAndHandoffTime += rotationInterval;
            }
            // add intervalBetweenStartTimeAndHandoffTime to handoff time
            handOffTime = OneUptimeDate.addRemoveHours(handOffTime, intervalBetweenStartTimeAndHandoffTime);
            if (OneUptimeDate.isOnOrBefore(handOffTime, data.currentEventStartTime)) {
                handOffTime = OneUptimeDate.addRemoveHours(handOffTime, 1);
            }
            return handOffTime;
        }
        if (data.rotation.intervalType === EventInterval.Week) {
            intervalBetweenStartTimeAndHandoffTime =
                OneUptimeDate.getWeeksBetweenTwoDatesInclusive(handOffTime, data.currentEventStartTime);
            if (intervalBetweenStartTimeAndHandoffTime < rotationInterval) {
                intervalBetweenStartTimeAndHandoffTime = rotationInterval;
            }
            else if (intervalBetweenStartTimeAndHandoffTime % rotationInterval !==
                0) {
                intervalBetweenStartTimeAndHandoffTime += rotationInterval;
            }
            // add intervalBetweenStartTimeAndHandoffTime to handoff time
            handOffTime = OneUptimeDate.addRemoveWeeks(handOffTime, intervalBetweenStartTimeAndHandoffTime);
            if (OneUptimeDate.isOnOrBefore(handOffTime, data.currentEventStartTime)) {
                handOffTime = OneUptimeDate.addRemoveWeeks(handOffTime, 1);
            }
            return handOffTime;
        }
        if (data.rotation.intervalType === EventInterval.Month) {
            intervalBetweenStartTimeAndHandoffTime =
                OneUptimeDate.getMonthsBetweenTwoDatesInclusive(handOffTime, data.currentEventStartTime);
            if (intervalBetweenStartTimeAndHandoffTime < rotationInterval) {
                intervalBetweenStartTimeAndHandoffTime = rotationInterval;
            }
            else if (intervalBetweenStartTimeAndHandoffTime % rotationInterval !==
                0) {
                intervalBetweenStartTimeAndHandoffTime += rotationInterval;
            }
            // add intervalBetweenStartTimeAndHandoffTime to handoff time
            handOffTime = OneUptimeDate.addRemoveMonths(handOffTime, intervalBetweenStartTimeAndHandoffTime);
            if (OneUptimeDate.isOnOrBefore(handOffTime, data.currentEventStartTime)) {
                handOffTime = OneUptimeDate.addRemoveMonths(handOffTime, 1);
            }
            return handOffTime;
        }
        if (data.rotation.intervalType === EventInterval.Year) {
            intervalBetweenStartTimeAndHandoffTime =
                OneUptimeDate.getYearsBetweenTwoDatesInclusive(handOffTime, data.currentEventStartTime);
            if (intervalBetweenStartTimeAndHandoffTime < rotationInterval) {
                intervalBetweenStartTimeAndHandoffTime = rotationInterval;
            }
            else if (intervalBetweenStartTimeAndHandoffTime % rotationInterval !==
                0) {
                intervalBetweenStartTimeAndHandoffTime += rotationInterval;
            }
            // add intervalBetweenStartTimeAndHandoffTime to handoff time
            handOffTime = OneUptimeDate.addRemoveYears(handOffTime, intervalBetweenStartTimeAndHandoffTime);
            if (OneUptimeDate.isOnOrBefore(handOffTime, data.currentEventStartTime)) {
                handOffTime = OneUptimeDate.addRemoveYears(handOffTime, 1);
            }
            return handOffTime;
        }
        return handOffTime;
    }
    getCurrentUserIndexBasedOnHandoffTime(data) {
        let currentUserIndex = data.currentUserIndex;
        // if current event start time is before layer start, idx unchanged.
        if (OneUptimeDate.isBefore(data.currentEventStartTime, data.startDateTimeOfLayer)) {
            return currentUserIndex;
        }
        // if handoff is after current start, no rotation has occurred yet — idx unchanged.
        if (OneUptimeDate.isAfter(data.handOffTime, data.currentEventStartTime)) {
            return currentUserIndex;
        }
        /*
         * Simulate rotation periods from layer start up to currentEventStartTime,
         * only incrementing the user index when a period would have produced an
         * event under the restriction. This mirrors the main loop in getEvents
         * (see issue #2413) and keeps the rotation continuous across calendar
         * windows that begin in the future (e.g. previewing next week).
         */
        let simulatedTime = data.startDateTimeOfLayer;
        let simulatedHandOff = this.moveHandsOffTimeAfterCurrentEventStartTime({
            handOffTime: data.handOffTime,
            currentEventStartTime: simulatedTime,
            rotation: data.rotation,
        });
        /*
         * Generous safety bound: 10000 covers ~27 years of daily rotation or
         * ~14 months of hourly rotation. The loop normally exits via the
         * isBefore check; this cap only fires for pathologically long-running
         * schedules to keep the function bounded.
         */
        const maxIterations = 10000;
        let iterations = 0;
        while (OneUptimeDate.isBefore(simulatedTime, data.currentEventStartTime) &&
            iterations < maxIterations) {
            iterations++;
            const eventEnd = simulatedHandOff;
            // Stop once the rotation period would extend past the target.
            if (OneUptimeDate.isAfter(eventEnd, data.currentEventStartTime)) {
                break;
            }
            const trimmed = this.trimStartAndEndTimesBasedOnRestrictionTimes({
                eventStartTime: simulatedTime,
                eventEndTime: eventEnd,
                restrictionTimes: data.restrictionTimes,
            });
            if (trimmed.length > 0) {
                currentUserIndex = this.incrementUserIndex(currentUserIndex, data.users.length);
            }
            simulatedTime = OneUptimeDate.addRemoveSeconds(eventEnd, 1);
            simulatedHandOff = this.moveHandsOffTimeAfterCurrentEventStartTime({
                handOffTime: simulatedHandOff,
                currentEventStartTime: simulatedTime,
                rotation: data.rotation,
            });
        }
        return currentUserIndex;
    }
    trimStartAndEndTimesBasedOnRestrictionTimes(data) {
        const restrictionTimes = data.restrictionTimes;
        if (restrictionTimes.restictionType === RestrictionType.None) {
            return [
                {
                    startTime: data.eventStartTime,
                    endTime: data.eventEndTime,
                },
            ];
        }
        if (restrictionTimes.restictionType === RestrictionType.Daily &&
            restrictionTimes.dayRestrictionTimes) {
            // before this we need to make sure restrciton times are moved to the day of the event.
            restrictionTimes.dayRestrictionTimes.startTime =
                OneUptimeDate.keepTimeButMoveDay(restrictionTimes.dayRestrictionTimes.startTime, data.eventStartTime);
            restrictionTimes.dayRestrictionTimes.endTime =
                OneUptimeDate.keepTimeButMoveDay(restrictionTimes.dayRestrictionTimes.endTime, data.eventStartTime);
            return this.getEventsByDailyRestriction({
                eventStartTime: data.eventStartTime,
                eventEndTime: data.eventEndTime,
                restrictionStartAndEndTime: restrictionTimes.dayRestrictionTimes,
                props: {
                    intervalType: EventInterval.Day,
                },
            });
        }
        if (restrictionTimes.restictionType === RestrictionType.Weekly) {
            return this.getEventsByWeeklyRestriction(data);
        }
        return [];
    }
    getEventsByWeeklyRestriction(data) {
        const weeklyRestrictionTimes = data.restrictionTimes.weeklyRestrictionTimes;
        // if there are no weekly restriction times, we dont have any restrictions and we can return the event start and end times
        let trimmedStartAndEndTimes = [];
        if (!weeklyRestrictionTimes || weeklyRestrictionTimes.length === 0) {
            return [
                {
                    startTime: data.eventStartTime,
                    endTime: data.eventEndTime,
                },
            ];
        }
        const restrictionStartAndEndTimes = this.getWeeklyRestrictionTimesForWeek(data);
        for (const restrictionStartAndEndTime of restrictionStartAndEndTimes) {
            const trimmedStartAndEndTimesForRestriction = this.getEventsByDailyRestriction({
                eventStartTime: data.eventStartTime,
                eventEndTime: data.eventEndTime,
                restrictionStartAndEndTime: restrictionStartAndEndTime,
                props: {
                    intervalType: EventInterval.Week,
                },
            });
            trimmedStartAndEndTimes = [
                ...trimmedStartAndEndTimes,
                ...trimmedStartAndEndTimesForRestriction,
            ];
        }
        return trimmedStartAndEndTimes;
    }
    getWeeklyRestrictionTimesForWeek(data) {
        const weeklyRestrictionTimes = data.restrictionTimes.weeklyRestrictionTimes;
        const eventStartTime = data.eventStartTime;
        const startAndEndTimesOfWeeklyRestrictions = [];
        for (const weeklyRestriction of weeklyRestrictionTimes) {
            // move all of these to the week of the event start time
            let startTime = weeklyRestriction.startTime;
            let endTime = weeklyRestriction.endTime;
            // move start and end times to the week of the event start time
            startTime = OneUptimeDate.moveDateToTheDayOfWeek(startTime, eventStartTime, OneUptimeDate.getDayOfWeek(startTime));
            endTime = OneUptimeDate.moveDateToTheDayOfWeek(endTime, eventStartTime, OneUptimeDate.getDayOfWeek(endTime));
            // now we have true start and end times of the weekly restriction
            // if start time is after end time, we need to add one week to the end time
            if (OneUptimeDate.isAfter(startTime, endTime)) {
                /*
                 * in this case the restriction is towards the ends of the week and not in the middle so we need to add two objects to the array.
                 * One for start of the week
                 * and the other for end of the week .
                 */
                const startOfWeek = data.eventStartTime;
                // add 7 days to the end time to get the end of the week
                const endOfTheWeek = OneUptimeDate.addRemoveDays(startOfWeek, 7);
                startAndEndTimesOfWeeklyRestrictions.push({
                    startTime: startOfWeek,
                    endTime: endTime,
                });
                startAndEndTimesOfWeeklyRestrictions.push({
                    startTime: startTime,
                    endTime: endOfTheWeek,
                });
            }
            startAndEndTimesOfWeeklyRestrictions.push({
                startTime,
                endTime,
            });
        }
        return startAndEndTimesOfWeeklyRestrictions;
    }
    getEventsByDailyRestriction(data) {
        const dayRestrictionTimes = data.restrictionStartAndEndTime;
        // if there are no day restriction times, we dont have any restrictions and we can return the event start and end times
        if (!dayRestrictionTimes) {
            return [
                {
                    startTime: data.eventStartTime,
                    endTime: data.eventEndTime,
                },
            ];
        }
        //
        let restrictionStartTime = dayRestrictionTimes.startTime;
        let restrictionEndTime = dayRestrictionTimes.endTime;
        /*
         * Special Case: Overnight (wrap-around) window where end time is logically on the next day.
         * Example: 23:00 -> 11:00 (next day). Existing algorithm assumed end >= start within same day
         * and returned no events. We explicitly expand such windows into two segments per day:
         * 1) start -> endOfDay(start)
         * 2) startOfNextDay -> end (moved to next day)
         */
        if (OneUptimeDate.isBefore(restrictionEndTime, restrictionStartTime)) {
            const results = [];
            // We'll iterate day-by-day within the event range (max 31 iterations safeguard)
            let currentDayStart = OneUptimeDate.getStartOfDay(data.eventStartTime);
            const absoluteEventEnd = data.eventEndTime;
            let safetyCounter = 0;
            const maxDays = 62; // generous safeguard
            while (OneUptimeDate.isOnOrBefore(currentDayStart, absoluteEventEnd) &&
                safetyCounter < maxDays) {
                safetyCounter++;
                const segmentNightStart = OneUptimeDate.keepTimeButMoveDay(restrictionStartTime, currentDayStart);
                const segmentNightEnd = OneUptimeDate.getEndOfDay(segmentNightStart);
                const nextDayStart = OneUptimeDate.addRemoveDays(currentDayStart, 1);
                const segmentMorningStart = OneUptimeDate.getStartOfDay(nextDayStart);
                const segmentMorningEnd = OneUptimeDate.keepTimeButMoveDay(restrictionEndTime, nextDayStart);
                // helper to add intersection if it overlaps the event window
                const addIntersection = (segStart, segEnd) => {
                    // normalize zero / invalid lengths
                    if (OneUptimeDate.isOnOrBefore(segEnd, segStart)) {
                        return; // no length
                    }
                    // intersect with [eventStart, eventEnd]
                    const start = OneUptimeDate.getGreaterDate(segStart, data.eventStartTime);
                    const end = OneUptimeDate.getLesserDate(segEnd, data.eventEndTime);
                    if (OneUptimeDate.isAfter(end, start)) {
                        results.push({ startTime: start, endTime: end });
                    }
                };
                addIntersection(segmentNightStart, segmentNightEnd);
                addIntersection(segmentMorningStart, segmentMorningEnd);
                // advance a day
                currentDayStart = nextDayStart;
            }
            return results;
        }
        let currentStartTime = data.eventStartTime;
        const currentEndTime = data.eventEndTime;
        const trimmedStartAndEndTimes = [];
        let reachedTheEndOfTheCurrentEvent = false;
        // create a break clause. This loop executes 100 times at max.
        const maxLoopCount = 50;
        let loopCount = 0;
        while (!reachedTheEndOfTheCurrentEvent) {
            loopCount++;
            if (loopCount > maxLoopCount) {
                break;
            }
            // if current end time is equalto or before than the current start time, we need to return the current event and exit the loop
            if (OneUptimeDate.isOnOrBefore(currentEndTime, currentStartTime)) {
                reachedTheEndOfTheCurrentEvent = true;
            }
            // if the event is ourside the restriction times, we need to return the trimmed array
            if (OneUptimeDate.isOnOrAfter(restrictionStartTime, currentEndTime)) {
                return trimmedStartAndEndTimes;
            }
            // if current event start time is after the restriction end time then we need to return empty array as there is no event.
            if (OneUptimeDate.isOnOrAfter(currentStartTime, restrictionEndTime)) {
                return trimmedStartAndEndTimes;
            }
            // if the restriction end time is before the restriction start time, we need to add one day to the restriction end time
            if (OneUptimeDate.isAfter(restrictionStartTime, restrictionEndTime)) {
                restrictionEndTime = OneUptimeDate.addRemoveDays(restrictionEndTime, data.props.intervalType === EventInterval.Day ? 1 : 7);
            }
            // 1 - if the current event falls within the restriction times, we need to return the current event.
            if (OneUptimeDate.isOnOrAfter(currentStartTime, restrictionStartTime) &&
                OneUptimeDate.isOnOrAfter(restrictionEndTime, currentEndTime)) {
                trimmedStartAndEndTimes.push({
                    startTime: currentStartTime,
                    endTime: currentEndTime,
                });
                reachedTheEndOfTheCurrentEvent = true;
            }
            /*
             * 2 - Start Restriction: If the current event starts after the restriction start time and ends after the restriction end time, we need to return the current event with the start time of the current event and end time of the restriction
             * Use strict isAfter on the end so this branch does not double-fire with case 1 when currentEnd === restrictionEnd.
             */
            if (OneUptimeDate.isOnOrAfter(currentStartTime, restrictionStartTime) &&
                OneUptimeDate.isAfter(currentEndTime, restrictionEndTime)) {
                trimmedStartAndEndTimes.push({
                    startTime: currentStartTime,
                    endTime: restrictionEndTime,
                });
                reachedTheEndOfTheCurrentEvent = true;
            }
            // 3 - End Restriction - If the current event starts before the restriction start time and ends before the restriction end time, we need to return the current event with the start time of the restriction and end time of the current event.
            if (OneUptimeDate.isBefore(currentStartTime, restrictionStartTime) &&
                OneUptimeDate.isBefore(currentEndTime, restrictionEndTime) &&
                OneUptimeDate.isAfter(currentEndTime, restrictionStartTime)) {
                trimmedStartAndEndTimes.push({
                    startTime: restrictionStartTime,
                    endTime: currentEndTime,
                });
                reachedTheEndOfTheCurrentEvent = true;
            }
            // 4 - If the current event starts before the restriction start time and ends after the restriction end time, we need to return the current event with the start time of the restriction and end time of the restriction.
            if (OneUptimeDate.isBefore(currentStartTime, restrictionStartTime) &&
                OneUptimeDate.isOnOrAfter(currentEndTime, restrictionEndTime)) {
                trimmedStartAndEndTimes.push({
                    startTime: restrictionStartTime,
                    endTime: restrictionEndTime,
                });
                currentStartTime = OneUptimeDate.addRemoveSeconds(restrictionEndTime, 1);
                // add day to restriction start and end times.
                restrictionStartTime = OneUptimeDate.addRemoveDays(restrictionStartTime, data.props.intervalType === EventInterval.Day ? 1 : 7);
                restrictionEndTime = OneUptimeDate.addRemoveDays(restrictionEndTime, data.props.intervalType === EventInterval.Day ? 1 : 7);
            }
        }
        return trimmedStartAndEndTimes;
    }
    // helper functions.
    incrementUserIndex(currentIndex, userArrayLength, incrementBy) {
        // update the current user index
        if (incrementBy === undefined) {
            incrementBy = 1;
        }
        currentIndex = currentIndex + incrementBy;
        // if the current user index is greater than the length of the users array, we need to reset the current user index to 0
        if (currentIndex >= userArrayLength) {
            // then modulo the current user index by the length of the users array
            currentIndex = currentIndex % userArrayLength; // so this rotates the users.
        }
        return currentIndex;
    }
    getCalendarEventsFromStartAndEndDates(trimmedStartAndEndTimes, users, currentUserIndex) {
        var _a, _b;
        const events = [];
        const userId = ((_b = (_a = users[currentUserIndex]) === null || _a === void 0 ? void 0 : _a.id) === null || _b === void 0 ? void 0 : _b.toString()) || "";
        for (const trimmedStartAndEndTime of trimmedStartAndEndTimes) {
            const event = {
                id: 0,
                title: userId, // This will be changed to username in the UI or will bve kept the same if used on the server.
                allDay: false,
                start: trimmedStartAndEndTime.startTime,
                end: trimmedStartAndEndTime.endTime,
            };
            events.push(event);
        }
        return events;
    }
    getMultiLayerEvents(data, options) {
        const events = [];
        let layerPriority = 1;
        for (const layer of data.layers) {
            const layerEvents = this.getEvents({
                users: layer.users,
                startDateTimeOfLayer: layer.startDateTimeOfLayer,
                restrictionTimes: layer.restrictionTimes,
                handOffTime: layer.handOffTime,
                rotation: layer.rotation,
                calendarStartDate: data.calendarStartDate,
                calendarEndDate: data.calendarEndDate,
            }, options);
            // add priority to each event
            for (const layerEvent of layerEvents) {
                const priorityEvent = Object.assign(Object.assign({}, layerEvent), { priority: layerPriority });
                events.push(priorityEvent);
            }
            // increment layer priority
            layerPriority++;
        }
        // now remove the overlapping events
        const nonOverlappingEvents = this.removeOverlappingEvents(events);
        if ((options === null || options === void 0 ? void 0 : options.getNumberOfEvents) !== undefined) {
            if (nonOverlappingEvents.length > options.getNumberOfEvents) {
                return nonOverlappingEvents.slice(0, options.getNumberOfEvents);
            }
        }
        return nonOverlappingEvents;
    }
    removeOverlappingEvents(events) {
        /*
         * now remove overlapping events by priority and trim them by priority. Lower priority number will be kept and higher priority number will be trimmed.
         * so if there are two events with the same start and end time, we will keep the one with the lower priority number and remove the one with the higher priority number.
         * if there are overlapping events, we will trim the one with the higher priority number.
         */
        // sort the events by priority
        // now remove the overlapping events
        // remove events where start time and end time are the same
        events = events.filter((event) => {
            return !OneUptimeDate.isSame(event.start, event.end);
        });
        // remove events where start time is after end time
        events = events.filter((event) => {
            return !OneUptimeDate.isBefore(event.end, event.start);
        });
        const finalEvents = [];
        // sort events by start time
        events.sort((a, b) => {
            if (OneUptimeDate.isBefore(a.start, b.start)) {
                return -1;
            }
            if (OneUptimeDate.isAfter(a.start, b.start)) {
                return 1;
            }
            return 0;
        });
        for (const event of events) {
            // trim the trimmed events by the current event based on priority
            // if this event starts and end at the same time, we need to remove it
            if (OneUptimeDate.isSame(event.start, event.end)) {
                continue;
            }
            // if the end time of the event is before the start time, we need to remove it
            if (OneUptimeDate.isBefore(event.end, event.start)) {
                continue;
            }
            for (let i = 0; i < finalEvents.length; i++) {
                const finalEvent = finalEvents[i];
                if (!finalEvent) {
                    continue;
                }
                // check if this final event overlaps with the current event
                if (OneUptimeDate.isOverlapping(finalEvent.start, finalEvent.end, event.start, event.end)) {
                    // if the current event has a higher priority than the final event, we need to trim the final event
                    if (event.priority < finalEvent.priority) {
                        /*
                         * trim the final event based on the current event
                         * end time of the final event will be the start time of the current event - 1 second
                         */
                        const tempFinalEventEnd = finalEvent.end;
                        finalEvent.end = OneUptimeDate.addRemoveSeconds(event.start, -1);
                        /*
                         * check if the final event end time is before the start time of the current event
                         * if it is, we need to remove the final event from the final events array
                         */
                        if (OneUptimeDate.isBefore(finalEvent.end, finalEvent.start)) {
                            finalEvents.splice(i, 1);
                            i--; // Adjust index after removal
                            continue;
                        }
                        // if end and start time of the final event is same, we need to remove it
                        if (OneUptimeDate.isSame(finalEvent.start, finalEvent.end)) {
                            finalEvents.splice(i, 1);
                            i--; // Adjust index after removal
                            continue;
                        }
                        // final event was originally ending after the current event, so we need to add the trimmed event to the final events array
                        if (OneUptimeDate.isAfter(tempFinalEventEnd, event.end)) {
                            // add the trimmed event to the final events array
                            const trimmedEvent = Object.assign(Object.assign({}, finalEvent), { priority: finalEvent.priority, start: OneUptimeDate.addRemoveSeconds(event.end, 1), end: tempFinalEventEnd });
                            // check if the event end time is before the start time of the trimmed event
                            if (OneUptimeDate.isAfter(trimmedEvent.end, trimmedEvent.start)) {
                                finalEvents.push(trimmedEvent);
                            }
                        }
                    }
                    else {
                        /*
                         * trim the current event based on the final event
                         * start time of the current event will be the end time of the final event + 1 second
                         */
                        event.start = OneUptimeDate.addRemoveSeconds(finalEvent.end, 1);
                    }
                }
            }
            // check if the event end time is before the start time of the current event
            if (OneUptimeDate.isAfter(event.end, event.start)) {
                finalEvents.push(event);
            }
            // sort by start times
            finalEvents.sort((a, b) => {
                if (OneUptimeDate.isBefore(a.start, b.start)) {
                    return -1;
                }
                if (OneUptimeDate.isAfter(a.start, b.start)) {
                    return 1;
                }
                return 0;
            });
            // if an event starts and end at the same time, we need to remove it
            for (let index = 0; index < finalEvents.length; index++) {
                const finalEvent = finalEvents[index];
                if (!finalEvent) {
                    continue;
                }
                if (OneUptimeDate.isSame(finalEvent.start, finalEvent.end)) {
                    finalEvents.splice(index, 1);
                    index--; // Adjust index after removal
                    continue;
                }
                // if any event ends before it starts, we need to remove it
                if (OneUptimeDate.isBefore(finalEvent.end, finalEvent.start)) {
                    finalEvents.splice(index, 1);
                    index--; // Adjust index after removal
                }
            }
        }
        // convert PriorityCalendarEvents to CalendarEvents
        const calendarEvents = [];
        let id = 1;
        for (const event of finalEvents) {
            const calendarEvent = Object.assign(Object.assign({}, event), { id: id });
            calendarEvents.push(calendarEvent);
            id++;
        }
        return calendarEvents;
    }
}
//# sourceMappingURL=Layer.js.map