"use strict";
const Sentry = require("@sentry/nextjs");
const z = require("zod");
const _metadata = require("./_metadata-G5VTI_Dn.cjs");
require("./index-Br7AzmuA.cjs");
const user = require("./user-xjV81VA_.cjs");
const dateRanges = require("./date-ranges-Uz4z3i_9.cjs");
const index$1 = require("./index-UtrZFBcS.cjs");
const errorCodes = require("./errorCodes-ifTrQJQk.cjs");
const httpError = require("./http-error-mkY8Ke7V.cjs");
const isBookingLimits = require("./isBookingLimits-4w4srzLF.cjs");
const utils = require("./utils-rQyGQqTb.cjs");
const ioctopus = require("@evyweb/ioctopus");
const checkBookingLimits = require("./checkBookingLimits-EzeZghN2.cjs");
const index = require("./i18n-HHdcQbuJ.cjs");
const dayjs = require("dayjs");
const booking = require("./booking-COXEjLZr.cjs");
const perf_hooks = require("perf_hooks");
const credential = require("./credential-xoMnaX4v.cjs");
const eventTypeRepository = require("./eventTypeRepository-cMU_T_VH.cjs");
function _interopNamespaceDefault(e) {
  const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
  if (e) {
    for (const k in e) {
      if (k !== "default") {
        const d = Object.getOwnPropertyDescriptor(e, k);
        Object.defineProperty(n, k, d.get ? d : {
          enumerable: true,
          get: () => e[k]
        });
      }
    }
  }
  n.default = e;
  return Object.freeze(n);
}
const Sentry__namespace = /* @__PURE__ */ _interopNamespaceDefault(Sentry);
const checkBookingAndDurationLimitsModule = ioctopus.createModule();
const token$1 = utils.DI_TOKENS.CHECK_BOOKING_AND_DURATION_LIMITS_SERVICE;
checkBookingAndDurationLimitsModule.bind(token$1).toClass(checkBookingLimits.CheckBookingAndDurationLimitsService, {
  checkBookingLimitsService: utils.DI_TOKENS.CHECK_BOOKING_LIMITS_SERVICE
});
const token = utils.DI_TOKENS.CHECK_BOOKING_LIMITS_SERVICE;
const checkBookingLimitsModule = ioctopus.createModule();
checkBookingLimitsModule.bind(token).toClass(checkBookingLimits.CheckBookingLimitsService, { bookingRepo: utils.DI_TOKENS.BOOKING_REPOSITORY });
const container$1 = ioctopus.createContainer();
container$1.load(utils.DI_TOKENS.PRISMA_MODULE, utils.prismaModule);
container$1.load(utils.DI_TOKENS.BOOKING_REPOSITORY_MODULE, utils.bookingRepositoryModule);
container$1.load(utils.DI_TOKENS.CHECK_BOOKING_LIMITS_SERVICE_MODULE, checkBookingLimitsModule);
container$1.load(
  utils.DI_TOKENS.CHECK_BOOKING_AND_DURATION_LIMITS_SERVICE_MODULE,
  checkBookingAndDurationLimitsModule
);
function getCheckBookingLimitsService() {
  return container$1.get(utils.DI_TOKENS.CHECK_BOOKING_LIMITS_SERVICE);
}
function getCheckBookingAndDurationLimitsService() {
  return container$1.get(
    utils.DI_TOKENS.CHECK_BOOKING_AND_DURATION_LIMITS_SERVICE
  );
}
const busyTimesModule = ioctopus.createModule();
busyTimesModule.bind(utils.DI_TOKENS.BUSY_TIMES_SERVICE).toClass(utils.BusyTimesService, {
  bookingRepo: utils.DI_TOKENS.BOOKING_REPOSITORY
});
const container = ioctopus.createContainer();
container.load(utils.DI_TOKENS.PRISMA_MODULE, utils.prismaModule);
container.load(utils.DI_TOKENS.BOOKING_REPOSITORY_MODULE, utils.bookingRepositoryModule);
container.load(utils.DI_TOKENS.BUSY_TIMES_SERVICE_MODULE, busyTimesModule);
function getBusyTimesService() {
  return container.get(utils.DI_TOKENS.BUSY_TIMES_SERVICE);
}
function _getPeriodStartDatesBetween(dateFrom, dateTo, period, timeZone) {
  const dates = [];
  let startDate = timeZone ? dayjs(dateFrom).tz(timeZone).startOf(period) : dayjs(dateFrom).startOf(period);
  const endDate = timeZone ? dayjs(dateTo).tz(timeZone).endOf(period) : dayjs(dateTo).endOf(period);
  while (startDate.isBefore(endDate)) {
    dates.push(startDate);
    startDate = startDate.add(1, period);
  }
  return dates;
}
const getPeriodStartDatesBetween = index.withReporting(_getPeriodStartDatesBetween, "getPeriodStartDatesBetween");
const _getBusyTimesFromLimits = async (bookingLimits, durationLimits, dateFrom, dateTo, duration, eventType, bookings, timeZone, rescheduleUid) => {
  perf_hooks.performance.mark("limitsStart");
  const limitManager = new utils.LimitManager();
  if (bookingLimits) {
    perf_hooks.performance.mark("bookingLimitsStart");
    await getBusyTimesFromBookingLimits({
      bookings,
      bookingLimits,
      dateFrom,
      dateTo,
      eventTypeId: eventType.id,
      limitManager,
      rescheduleUid,
      timeZone
    });
    perf_hooks.performance.mark("bookingLimitsEnd");
    perf_hooks.performance.measure(`checking booking limits took $1'`, "bookingLimitsStart", "bookingLimitsEnd");
  }
  if (durationLimits) {
    perf_hooks.performance.mark("durationLimitsStart");
    await getBusyTimesFromDurationLimits(
      bookings,
      durationLimits,
      dateFrom,
      dateTo,
      duration,
      eventType,
      limitManager,
      timeZone,
      rescheduleUid
    );
    perf_hooks.performance.mark("durationLimitsEnd");
    perf_hooks.performance.measure(`checking duration limits took $1'`, "durationLimitsStart", "durationLimitsEnd");
  }
  perf_hooks.performance.mark("limitsEnd");
  perf_hooks.performance.measure(`checking all limits took $1'`, "limitsStart", "limitsEnd");
  return limitManager.getBusyTimes();
};
const _getBusyTimesFromBookingLimits = async (params) => {
  const {
    bookings,
    bookingLimits,
    dateFrom,
    dateTo,
    limitManager,
    eventTypeId,
    teamId,
    user: user2,
    rescheduleUid,
    includeManagedEvents = false,
    timeZone
  } = params;
  for (const key of isBookingLimits.descendingLimitKeys) {
    const limit = bookingLimits?.[key];
    if (!limit)
      continue;
    const unit = isBookingLimits.intervalLimitKeyToUnit(key);
    const periodStartDates = getPeriodStartDatesBetween(dateFrom, dateTo, unit);
    for (const periodStart of periodStartDates) {
      if (limitManager.isAlreadyBusy(periodStart, unit))
        continue;
      if (unit === "year") {
        try {
          const checkBookingLimitsService = getCheckBookingLimitsService();
          await checkBookingLimitsService.checkBookingLimit({
            eventStartDate: periodStart.toDate(),
            limitingNumber: limit,
            eventId: eventTypeId,
            key,
            teamId,
            user: user2,
            rescheduleUid,
            includeManagedEvents,
            timeZone
          });
        } catch (_) {
          limitManager.addBusyTime(periodStart, unit);
          if (periodStartDates.every((start) => limitManager.isAlreadyBusy(start, unit))) {
            return;
          }
        }
        continue;
      }
      const periodEnd = periodStart.endOf(unit);
      let totalBookings = 0;
      for (const booking2 of bookings) {
        if (!utils.isBookingWithinPeriod(booking2, periodStart, periodEnd, timeZone || "UTC")) {
          continue;
        }
        totalBookings++;
        if (totalBookings >= limit) {
          limitManager.addBusyTime(periodStart, unit);
          break;
        }
      }
    }
  }
};
const _getBusyTimesFromDurationLimits = async (bookings, durationLimits, dateFrom, dateTo, duration, eventType, limitManager, timeZone, rescheduleUid) => {
  for (const key of isBookingLimits.descendingLimitKeys) {
    const limit = durationLimits?.[key];
    if (!limit)
      continue;
    const unit = isBookingLimits.intervalLimitKeyToUnit(key);
    const periodStartDates = getPeriodStartDatesBetween(dateFrom, dateTo, unit);
    for (const periodStart of periodStartDates) {
      if (limitManager.isAlreadyBusy(periodStart, unit))
        continue;
      const selectedDuration = (duration || eventType.length) ?? 0;
      if (selectedDuration > limit) {
        limitManager.addBusyTime(periodStart, unit);
        continue;
      }
      if (unit === "year") {
        const bookingRepo = new booking.BookingRepository(index.prisma);
        const totalYearlyDuration = await bookingRepo.getTotalBookingDuration({
          eventId: eventType.id,
          startDate: periodStart.toDate(),
          endDate: periodStart.endOf(unit).toDate(),
          rescheduleUid
        });
        if (totalYearlyDuration + selectedDuration > limit) {
          limitManager.addBusyTime(periodStart, unit);
          if (periodStartDates.every((start) => limitManager.isAlreadyBusy(start, unit))) {
            return;
          }
        }
        continue;
      }
      const periodEnd = periodStart.endOf(unit);
      let totalDuration = selectedDuration;
      for (const booking2 of bookings) {
        if (!utils.isBookingWithinPeriod(booking2, periodStart, periodEnd, timeZone || "UTC")) {
          continue;
        }
        totalDuration += dayjs(booking2.end).diff(dayjs(booking2.start), "minute");
        if (totalDuration > limit) {
          limitManager.addBusyTime(periodStart, unit);
          break;
        }
      }
    }
  }
};
const getBusyTimesFromDurationLimits = index.withReporting(
  _getBusyTimesFromDurationLimits,
  "getBusyTimesFromDurationLimits"
);
const _getBusyTimesFromTeamLimits = async (user2, bookingLimits, dateFrom, dateTo, teamId, includeManagedEvents, timeZone, rescheduleUid) => {
  const busyTimesService = getBusyTimesService();
  const { limitDateFrom, limitDateTo } = busyTimesService.getStartEndDateforLimitCheck(
    dateFrom.toISOString(),
    dateTo.toISOString(),
    bookingLimits
  );
  const bookingRepo = new booking.BookingRepository(index.prisma);
  const bookings = await bookingRepo.getAllAcceptedTeamBookingsOfUser({
    user: user2,
    teamId,
    startDate: limitDateFrom.toDate(),
    endDate: limitDateTo.toDate(),
    excludedUid: rescheduleUid,
    includeManagedEvents
  });
  const busyTimes = bookings.map(({ id, startTime, endTime, eventTypeId, title, userId }) => ({
    start: dayjs(startTime).toDate(),
    end: dayjs(endTime).toDate(),
    title,
    source: `eventType-${eventTypeId}-booking-${id}`,
    userId
  }));
  const limitManager = new utils.LimitManager();
  await getBusyTimesFromBookingLimits({
    bookings: busyTimes,
    bookingLimits,
    dateFrom,
    dateTo,
    limitManager,
    rescheduleUid,
    teamId,
    user: user2,
    includeManagedEvents,
    timeZone
  });
  return limitManager.getBusyTimes();
};
const getBusyTimesFromLimits = index.withReporting(_getBusyTimesFromLimits, "getBusyTimesFromLimits");
const getBusyTimesFromBookingLimits = index.withReporting(
  _getBusyTimesFromBookingLimits,
  "getBusyTimesFromBookingLimits"
);
const getBusyTimesFromTeamLimits = index.withReporting(
  _getBusyTimesFromTeamLimits,
  "getBusyTimesFromTeamLimits"
);
async function findUsersForAvailabilityCheck({ where }) {
  const user$1 = await index.prisma.user.findFirst({
    where,
    select: {
      ...index.availabilityUserSelect,
      selectedCalendars: true,
      credentials: {
        select: index.credentialForCalendarServiceSelect
      }
    }
  });
  if (!user$1) {
    return null;
  }
  return await credential.enrichUserWithDelegationCredentialsIncludeServiceAccountKey({
    user: user.withSelectedCalendars(user$1)
  });
}
const log = index.logger.getSubLogger({ prefix: ["getUserAvailability"] });
const availabilitySchema = z.z.object({
  dateFrom: index$1.stringToDayjsZod,
  dateTo: index$1.stringToDayjsZod,
  eventTypeId: z.z.number().optional(),
  username: z.z.string().optional(),
  userId: z.z.number().optional(),
  afterEventBuffer: z.z.number().optional(),
  beforeEventBuffer: z.z.number().optional(),
  duration: z.z.number().optional(),
  withSource: z.z.boolean().optional(),
  returnDateOverrides: z.z.boolean(),
  bypassBusyCalendarTimes: z.z.boolean().optional(),
  silentlyHandleCalendarFailures: z.z.boolean().optional(),
  shouldServeCache: z.z.boolean().optional()
}).refine((data) => !!data.username || !!data.userId, "Either username or userId should be filled in.");
class UserAvailabilityService {
  constructor(dependencies) {
    this.dependencies = dependencies;
    this.getEventType = index.withReporting(this._getEventType.bind(this), "getEventType");
    this.getUser = index.withReporting(this._getUser.bind(this), "getUser");
    this.getCurrentSeats = index.withReporting(this._getCurrentSeats.bind(this), "getCurrentSeats");
    this.getUserAvailability = index.withReporting(this._getUserAvailability.bind(this), "getUserAvailability");
    this.getPeriodStartDatesBetween = index.withReporting(
      (dateFrom, dateTo, period, timeZone) => getPeriodStartDatesBetween(dateFrom, dateTo, period, timeZone),
      "getPeriodStartDatesBetween"
    );
    this.getUsersAvailability = index.withReporting(this._getUsersAvailability.bind(this), "getUsersAvailability");
  }
  // Fetch timezones from outlook or google using delegated credentials (formely known as domain wide delegatiion)
  async getTimezoneFromDelegatedCalendars(user2) {
    if (!user2.credentials || user2.credentials.length === 0) {
      return null;
    }
    const delegatedCredentials = user2.credentials.filter(
      (credential2) => credential2.type.endsWith("_calendar") && Boolean(credential2.delegatedToId)
    );
    if (!delegatedCredentials || delegatedCredentials.length === 0) {
      return null;
    }
    const cacheKey = `user-timezone:${user2.id}`;
    try {
      const cachedTimezone = await this.dependencies.redisClient.get(cacheKey);
      if (cachedTimezone) {
        log.debug(`Got timezone ${cachedTimezone} from Redis cache for user ${user2.id}`);
        return cachedTimezone;
      }
    } catch (error) {
      log.warn(`Failed to get timezone from Redis cache for user ${user2.id}:`, error);
    }
    if (delegatedCredentials.length === 0) {
      return null;
    }
    for (const credential2 of delegatedCredentials) {
      try {
        const calendar = await _metadata.getCalendar(credential2);
        if (calendar && "getMainTimeZone" in calendar && typeof calendar.getMainTimeZone === "function") {
          const timezone = await calendar.getMainTimeZone();
          if (timezone && timezone !== "UTC") {
            log.debug(`Got timezone ${timezone} from calendar service ${credential2.type}`);
            try {
              await this.dependencies.redisClient.set(cacheKey, timezone, { ttl: 3600 * 6 * 1e3 });
              log.debug(`Cached timezone ${timezone} in Redis for user ${user2.id}`);
            } catch (error) {
              log.warn(`Failed to set timezone in Redis cache for user ${user2.id}:`, error);
            }
            return timezone;
          }
        }
      } catch (error) {
        log.warn(`Failed to get timezone from calendar service ${credential2.type}:`, error);
      }
    }
    return null;
  }
  async _getEventType(id) {
    const eventType = await this.dependencies.eventTypeRepo.findByIdForUserAvailability({ id });
    if (!eventType) {
      return eventType;
    }
    return {
      ...eventType,
      metadata: index.EventTypeMetaDataSchema.parse(eventType.metadata)
    };
  }
  async _getUser(where) {
    return findUsersForAvailabilityCheck({ where });
  }
  async _getCurrentSeats(eventType, dateFrom, dateTo) {
    const { schedulingType, hosts, id } = eventType;
    const hostEmails = hosts?.map((host) => host.user.email);
    const isTeamEvent = schedulingType === index.SchedulingType.MANAGED || schedulingType === index.SchedulingType.ROUND_ROBIN || schedulingType === index.SchedulingType.COLLECTIVE;
    const bookings = await this.dependencies.bookingRepo.findAcceptedBookingByEventTypeId({
      eventTypeId: id,
      dateFrom: dateFrom.format(),
      dateTo: dateTo.format()
    });
    return bookings.map((booking2) => {
      const attendees = isTeamEvent ? booking2.attendees.filter((attendee) => !hostEmails?.includes(attendee.email)) : booking2.attendees;
      return {
        uid: booking2.uid,
        startTime: booking2.startTime,
        _count: {
          attendees: attendees.length
        }
      };
    });
  }
  /** This should be called getUsersWorkingHoursAndBusySlots (...and remaining seats, and final timezone) */
  async _getUserAvailability(query, initialData) {
    const {
      username,
      userId,
      dateFrom,
      dateTo,
      eventTypeId,
      afterEventBuffer,
      beforeEventBuffer,
      duration,
      returnDateOverrides,
      bypassBusyCalendarTimes = false,
      silentlyHandleCalendarFailures = false,
      shouldServeCache
    } = availabilitySchema.parse(query);
    log.debug(
      `EventType: ${eventTypeId} | User: ${username} (ID: ${userId}) - Called with: ${index.safeStringify({
        query
      })}`
    );
    if (!dateFrom.isValid() || !dateTo.isValid()) {
      throw new httpError.HttpError({ statusCode: 400, message: "Invalid time range given." });
    }
    const where = {};
    if (username)
      where.username = username;
    if (userId)
      where.id = userId;
    const user$1 = initialData?.user || await this.getUser(where);
    if (!user$1) {
      throw new httpError.HttpError({ statusCode: 404, message: "No user found in getUserAvailability" });
    }
    let eventType = initialData?.eventType || null;
    if (!eventType && eventTypeId)
      eventType = await this.getEventType(eventTypeId);
    let currentSeats = initialData?.currentSeats || null;
    if (!currentSeats && eventType?.seatsPerTimeSlot) {
      currentSeats = await this.getCurrentSeats(eventType, dateFrom, dateTo);
    }
    const userSchedule = user$1.schedules.filter(
      (schedule2) => !user$1?.defaultScheduleId || schedule2.id === user$1?.defaultScheduleId
    )[0];
    const hostSchedule = eventType?.hosts?.find((host) => host.user.id === user$1.id)?.schedule;
    const fallbackTimezoneIfScheduleIsMissing = eventType?.timeZone || user$1.timeZone;
    const fallbackSchedule = {
      availability: [
        {
          startTime: /* @__PURE__ */ new Date("1970-01-01T09:00:00Z"),
          endTime: /* @__PURE__ */ new Date("1970-01-01T17:00:00Z"),
          days: [1, 2, 3, 4, 5],
          // Monday to Friday
          date: null
        }
      ],
      id: 0,
      timeZone: fallbackTimezoneIfScheduleIsMissing
    };
    const potentialSchedule = eventType?.schedule ? eventType.schedule : hostSchedule ? hostSchedule : userSchedule;
    const schedule = potentialSchedule ?? fallbackSchedule;
    const bookingLimits = eventType?.bookingLimits && typeof eventType.bookingLimits === "object" && Object.keys(eventType.bookingLimits).length > 0 ? isBookingLimits.parseBookingLimit(eventType.bookingLimits) : null;
    const durationLimits = eventType?.durationLimits && typeof eventType.durationLimits === "object" && Object.keys(eventType.durationLimits).length > 0 ? isBookingLimits.parseDurationLimit(eventType.durationLimits) : null;
    const getBusyTimesStart = dateFrom.toISOString();
    const getBusyTimesEnd = dateTo.toISOString();
    const selectedCalendars = eventType?.useEventLevelSelectedCalendars ? eventTypeRepository.EventTypeRepository.getSelectedCalendarsFromUser({ user: user$1, eventTypeId: eventType.id }) : user$1.userLevelSelectedCalendars;
    const isTimezoneSet = Boolean(potentialSchedule && potentialSchedule.timeZone !== null);
    const calendarTimezone = !isTimezoneSet ? await this.getTimezoneFromDelegatedCalendars(user$1) : null;
    const finalTimezone = !isTimezoneSet && calendarTimezone ? calendarTimezone : schedule?.timeZone || fallbackTimezoneIfScheduleIsMissing;
    let busyTimesFromLimits = [];
    if (initialData?.busyTimesFromLimits && initialData?.eventTypeForLimits) {
      busyTimesFromLimits = initialData.busyTimesFromLimits.get(user$1.id) || [];
    } else if (eventType && (bookingLimits || durationLimits)) {
      busyTimesFromLimits = await getBusyTimesFromLimits(
        bookingLimits,
        durationLimits,
        dateFrom.tz(finalTimezone),
        dateTo.tz(finalTimezone),
        duration,
        eventType,
        initialData?.busyTimesFromLimitsBookings ?? [],
        finalTimezone,
        initialData?.rescheduleUid ?? void 0
      );
    }
    const teamForBookingLimits = initialData?.teamForBookingLimits ?? eventType?.team ?? (eventType?.parent?.team?.includeManagedEventsInLimits ? eventType?.parent?.team : null);
    const teamBookingLimits = isBookingLimits.parseBookingLimit(teamForBookingLimits?.bookingLimits);
    let busyTimesFromTeamLimits = [];
    if (initialData?.teamBookingLimits && teamForBookingLimits) {
      busyTimesFromTeamLimits = initialData.teamBookingLimits.get(user$1.id) || [];
    } else if (teamForBookingLimits && teamBookingLimits) {
      busyTimesFromTeamLimits = await getBusyTimesFromTeamLimits(
        user$1,
        teamBookingLimits,
        dateFrom.tz(finalTimezone),
        dateTo.tz(finalTimezone),
        teamForBookingLimits.id,
        teamForBookingLimits.includeManagedEventsInLimits,
        finalTimezone,
        initialData?.rescheduleUid ?? void 0
      );
    }
    let busyTimes = [];
    try {
      const busyTimesService = getBusyTimesService();
      busyTimes = await busyTimesService.getBusyTimes({
        credentials: user$1.credentials,
        startTime: getBusyTimesStart,
        endTime: getBusyTimesEnd,
        eventTypeId,
        userId: user$1.id,
        userEmail: user$1.email,
        username: `${user$1.username}`,
        beforeEventBuffer,
        afterEventBuffer,
        selectedCalendars,
        seatedEvent: !!eventType?.seatsPerTimeSlot,
        rescheduleUid: initialData?.rescheduleUid || null,
        duration,
        currentBookings: initialData?.currentBookings,
        bypassBusyCalendarTimes,
        silentlyHandleCalendarFailures,
        shouldServeCache
      });
    } catch (error) {
      log.error(`Error fetching busy times for user ${username}:`, error);
      return {
        busy: [],
        timeZone: finalTimezone,
        dateRanges: [],
        oooExcludedDateRanges: [],
        workingHours: [],
        dateOverrides: [],
        currentSeats: [],
        datesOutOfOffice: void 0
      };
    }
    const detailedBusyTimes = [
      ...busyTimes.map((a) => ({
        ...a,
        start: dayjs(a.start).toISOString(),
        end: dayjs(a.end).toISOString(),
        title: a.title,
        source: query.withSource ? a.source : void 0
      })),
      ...busyTimesFromLimits,
      ...busyTimesFromTeamLimits
    ];
    const isDefaultSchedule = userSchedule && userSchedule.id === schedule?.id;
    log.debug(
      `EventType: ${eventTypeId} | User: ${username} (ID: ${userId}) - usingSchedule: ${index.safeStringify({
        chosenSchedule: schedule,
        eventTypeSchedule: eventType?.schedule,
        userSchedule,
        hostSchedule
      })}`
    );
    if (!(schedule?.availability || (eventType?.availability.length ? eventType.availability : user$1.availability))) {
      throw new httpError.HttpError({ statusCode: 400, message: errorCodes.ErrorCode.AvailabilityNotFoundInSchedule });
    }
    const availability = (schedule?.availability || (eventType?.availability.length ? eventType.availability : user$1.availability)).map((a) => ({
      ...a,
      userId: user$1.id
    }));
    const workingHours = user.getWorkingHours({ timeZone: finalTimezone }, availability);
    const dateOverrides = [];
    if (returnDateOverrides) {
      const calculateDateOverridesSpan = Sentry__namespace.startInactiveSpan({ name: "calculateDateOverrides" });
      const availabilityWithDates = availability.filter((availability2) => !!availability2.date);
      for (let i = 0; i < availabilityWithDates.length; i++) {
        const override = availabilityWithDates[i];
        const startTime = dayjs.utc(override.startTime);
        const endTime = dayjs.utc(override.endTime);
        const overrideStartDate = dayjs.utc(override.date).hour(startTime.hour()).minute(startTime.minute());
        const overrideEndDate = dayjs.utc(override.date).hour(endTime.hour()).minute(endTime.minute());
        if (overrideStartDate.isBetween(dateFrom, dateTo, null, "[]") || overrideEndDate.isBetween(dateFrom, dateTo, null, "[]")) {
          dateOverrides.push({
            start: overrideStartDate.toDate(),
            end: overrideEndDate.toDate()
          });
        }
      }
      calculateDateOverridesSpan.end();
    }
    const outOfOfficeDays = initialData?.outOfOfficeDays ?? await this.dependencies.oooRepo.findUserOOODays({
      userId: user$1.id,
      dateFrom: dateFrom.toISOString(),
      dateTo: dateTo.toISOString()
    });
    const datesOutOfOffice = this.calculateOutOfOfficeRanges(outOfOfficeDays, availability);
    const { dateRanges: dateRanges$1, oooExcludedDateRanges } = dateRanges.buildDateRanges({
      dateFrom,
      dateTo,
      availability,
      timeZone: finalTimezone,
      travelSchedules: isDefaultSchedule ? user$1.travelSchedules.map((schedule2) => {
        return {
          startDate: dayjs(schedule2.startDate),
          endDate: schedule2.endDate ? dayjs(schedule2.endDate) : void 0,
          timeZone: schedule2.timeZone
        };
      }) : [],
      outOfOffice: datesOutOfOffice
    });
    const formattedBusyTimes = detailedBusyTimes.map((busy) => ({
      start: dayjs(busy.start),
      end: dayjs(busy.end)
    }));
    const dateRangesInWhichUserIsAvailable = dateRanges.subtract(dateRanges$1, formattedBusyTimes);
    const dateRangesInWhichUserIsAvailableWithoutOOO = dateRanges.subtract(oooExcludedDateRanges, formattedBusyTimes);
    const result = {
      busy: detailedBusyTimes,
      timeZone: finalTimezone,
      dateRanges: dateRangesInWhichUserIsAvailable,
      oooExcludedDateRanges: dateRangesInWhichUserIsAvailableWithoutOOO,
      workingHours,
      dateOverrides,
      currentSeats,
      datesOutOfOffice
    };
    log.debug(
      `EventType: ${eventTypeId} | User: ${username} (ID: ${userId}) - Result: ${index.safeStringify(result)}`
    );
    return result;
  }
  calculateOutOfOfficeRanges(outOfOfficeDays, availability) {
    if (!outOfOfficeDays || outOfOfficeDays.length === 0) {
      return {};
    }
    return outOfOfficeDays.reduce((acc, { start, end, toUser, user: user2, reason }) => {
      const startDateRange = dayjs(start).utc().isBefore(dayjs().startOf("day").utc()) ? dayjs().utc().startOf("day") : dayjs(start).utc().startOf("day");
      const flattenDays = Array.from(new Set(availability.flatMap((a) => "days" in a ? a.days : []))).sort(
        (a, b) => a - b
      );
      const endDateRange = dayjs(end).utc().endOf("day");
      for (let date = startDateRange; date.isBefore(endDateRange); date = date.add(1, "day")) {
        const dayNumberOnWeek = date.day();
        if (!flattenDays?.includes(dayNumberOnWeek)) {
          continue;
        }
        acc[date.format("YYYY-MM-DD")] = {
          // @TODO:  would be good having start and end availability time here, but for now should be good
          // you can obtain that from user availability defined outside of here
          fromUser: { id: user2.id, displayName: user2.name },
          // optional chaining destructuring toUser
          toUser: !!toUser ? { id: toUser.id, displayName: toUser.name, username: toUser.username } : null,
          reason: !!reason ? reason.reason : null,
          emoji: !!reason ? reason.emoji : null
        };
      }
      return acc;
    }, {});
  }
  async _getUsersAvailability({ users, query, initialData }) {
    if (users.length >= 50) {
      const userIds = users.map(({ id }) => id).join(", ");
      log.warn(
        `High-load warning: Attempting to fetch availability for ${users.length} users. User IDs: [${userIds}], EventTypeId: [${query.eventTypeId}]`
      );
    }
    return await Promise.all(
      users.map(
        (user2) => this._getUserAvailability(
          {
            ...query,
            userId: user2.id,
            username: user2.username || ""
          },
          initialData ? {
            ...initialData,
            user: user2,
            currentBookings: user2.currentBookings,
            outOfOfficeDays: user2.outOfOfficeDays
          } : void 0
        )
      )
    );
  }
}
exports.UserAvailabilityService = UserAvailabilityService;
exports.busyTimesModule = busyTimesModule;
exports.getBusyTimesService = getBusyTimesService;
exports.getCheckBookingAndDurationLimitsService = getCheckBookingAndDurationLimitsService;
