import * as Sentry from "@sentry/nextjs";
import { z } from "zod";
import { g as getCalendar } from "./_metadata-HCC3lZef.js";
import "./index-rbwdGzy0.js";
import { w as withSelectedCalendars, f as getWorkingHours } from "./user-sJCjuzB2.js";
import { b as buildDateRanges, s as subtract } from "./date-ranges-79rc7oZX.js";
import { b as stringToDayjsZod } from "./index-sMkzixNw.js";
import { E as ErrorCode } from "./errorCodes-E8kmN1h5.js";
import { H as HttpError } from "./http-error-RJXCPZV2.js";
import { d as descendingLimitKeys, i as intervalLimitKeyToUnit, b as parseBookingLimit, p as parseDurationLimit } from "./isBookingLimits-_5f4G3q2.js";
import { D as DI_TOKENS, p as prismaModule, b as bookingRepositoryModule, B as BusyTimesService, i as isBookingWithinPeriod, L as LimitManager } from "./utils-Ty7pfMGk.js";
import { createModule, createContainer } from "@evyweb/ioctopus";
import { a as CheckBookingAndDurationLimitsService, C as CheckBookingLimitsService } from "./checkBookingLimits-qNBVoJry.js";
import { w as withReporting, p as prisma, b as availabilityUserSelect, c as credentialForCalendarServiceSelect, l as logger, E as EventTypeMetaDataSchema, S as SchedulingType, a as safeStringify } from "./i18n-tiaWqB1b.js";
import dayjs from "dayjs";
import { B as BookingRepository } from "./booking-flvbirx3.js";
import { performance } from "perf_hooks";
import { e as enrichUserWithDelegationCredentialsIncludeServiceAccountKey } from "./credential-7RBuTGyw.js";
import { E as EventTypeRepository } from "./eventTypeRepository-H_vAA4Gi.js";
const checkBookingAndDurationLimitsModule = createModule();
const token$1 = DI_TOKENS.CHECK_BOOKING_AND_DURATION_LIMITS_SERVICE;
checkBookingAndDurationLimitsModule.bind(token$1).toClass(CheckBookingAndDurationLimitsService, {
  checkBookingLimitsService: DI_TOKENS.CHECK_BOOKING_LIMITS_SERVICE
});
const token = DI_TOKENS.CHECK_BOOKING_LIMITS_SERVICE;
const checkBookingLimitsModule = createModule();
checkBookingLimitsModule.bind(token).toClass(CheckBookingLimitsService, { bookingRepo: DI_TOKENS.BOOKING_REPOSITORY });
const container$1 = createContainer();
container$1.load(DI_TOKENS.PRISMA_MODULE, prismaModule);
container$1.load(DI_TOKENS.BOOKING_REPOSITORY_MODULE, bookingRepositoryModule);
container$1.load(DI_TOKENS.CHECK_BOOKING_LIMITS_SERVICE_MODULE, checkBookingLimitsModule);
container$1.load(
  DI_TOKENS.CHECK_BOOKING_AND_DURATION_LIMITS_SERVICE_MODULE,
  checkBookingAndDurationLimitsModule
);
function getCheckBookingLimitsService() {
  return container$1.get(DI_TOKENS.CHECK_BOOKING_LIMITS_SERVICE);
}
function getCheckBookingAndDurationLimitsService() {
  return container$1.get(
    DI_TOKENS.CHECK_BOOKING_AND_DURATION_LIMITS_SERVICE
  );
}
const busyTimesModule = createModule();
busyTimesModule.bind(DI_TOKENS.BUSY_TIMES_SERVICE).toClass(BusyTimesService, {
  bookingRepo: DI_TOKENS.BOOKING_REPOSITORY
});
const container = createContainer();
container.load(DI_TOKENS.PRISMA_MODULE, prismaModule);
container.load(DI_TOKENS.BOOKING_REPOSITORY_MODULE, bookingRepositoryModule);
container.load(DI_TOKENS.BUSY_TIMES_SERVICE_MODULE, busyTimesModule);
function getBusyTimesService() {
  return container.get(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 = withReporting(_getPeriodStartDatesBetween, "getPeriodStartDatesBetween");
const _getBusyTimesFromLimits = async (bookingLimits, durationLimits, dateFrom, dateTo, duration, eventType, bookings, timeZone, rescheduleUid) => {
  performance.mark("limitsStart");
  const limitManager = new LimitManager();
  if (bookingLimits) {
    performance.mark("bookingLimitsStart");
    await getBusyTimesFromBookingLimits({
      bookings,
      bookingLimits,
      dateFrom,
      dateTo,
      eventTypeId: eventType.id,
      limitManager,
      rescheduleUid,
      timeZone
    });
    performance.mark("bookingLimitsEnd");
    performance.measure(`checking booking limits took $1'`, "bookingLimitsStart", "bookingLimitsEnd");
  }
  if (durationLimits) {
    performance.mark("durationLimitsStart");
    await getBusyTimesFromDurationLimits(
      bookings,
      durationLimits,
      dateFrom,
      dateTo,
      duration,
      eventType,
      limitManager,
      timeZone,
      rescheduleUid
    );
    performance.mark("durationLimitsEnd");
    performance.measure(`checking duration limits took $1'`, "durationLimitsStart", "durationLimitsEnd");
  }
  performance.mark("limitsEnd");
  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,
    rescheduleUid,
    includeManagedEvents = false,
    timeZone
  } = params;
  for (const key of descendingLimitKeys) {
    const limit = bookingLimits?.[key];
    if (!limit)
      continue;
    const unit = 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,
            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 booking of bookings) {
        if (!isBookingWithinPeriod(booking, 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 descendingLimitKeys) {
    const limit = durationLimits?.[key];
    if (!limit)
      continue;
    const unit = 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 BookingRepository(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 booking of bookings) {
        if (!isBookingWithinPeriod(booking, periodStart, periodEnd, timeZone || "UTC")) {
          continue;
        }
        totalDuration += dayjs(booking.end).diff(dayjs(booking.start), "minute");
        if (totalDuration > limit) {
          limitManager.addBusyTime(periodStart, unit);
          break;
        }
      }
    }
  }
};
const getBusyTimesFromDurationLimits = withReporting(
  _getBusyTimesFromDurationLimits,
  "getBusyTimesFromDurationLimits"
);
const _getBusyTimesFromTeamLimits = async (user, bookingLimits, dateFrom, dateTo, teamId, includeManagedEvents, timeZone, rescheduleUid) => {
  const busyTimesService = getBusyTimesService();
  const { limitDateFrom, limitDateTo } = busyTimesService.getStartEndDateforLimitCheck(
    dateFrom.toISOString(),
    dateTo.toISOString(),
    bookingLimits
  );
  const bookingRepo = new BookingRepository(prisma);
  const bookings = await bookingRepo.getAllAcceptedTeamBookingsOfUser({
    user,
    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 LimitManager();
  await getBusyTimesFromBookingLimits({
    bookings: busyTimes,
    bookingLimits,
    dateFrom,
    dateTo,
    limitManager,
    rescheduleUid,
    teamId,
    user,
    includeManagedEvents,
    timeZone
  });
  return limitManager.getBusyTimes();
};
const getBusyTimesFromLimits = withReporting(_getBusyTimesFromLimits, "getBusyTimesFromLimits");
const getBusyTimesFromBookingLimits = withReporting(
  _getBusyTimesFromBookingLimits,
  "getBusyTimesFromBookingLimits"
);
const getBusyTimesFromTeamLimits = withReporting(
  _getBusyTimesFromTeamLimits,
  "getBusyTimesFromTeamLimits"
);
async function findUsersForAvailabilityCheck({ where }) {
  const user = await prisma.user.findFirst({
    where,
    select: {
      ...availabilityUserSelect,
      selectedCalendars: true,
      credentials: {
        select: credentialForCalendarServiceSelect
      }
    }
  });
  if (!user) {
    return null;
  }
  return await enrichUserWithDelegationCredentialsIncludeServiceAccountKey({
    user: withSelectedCalendars(user)
  });
}
const log = logger.getSubLogger({ prefix: ["getUserAvailability"] });
const availabilitySchema = z.object({
  dateFrom: stringToDayjsZod,
  dateTo: stringToDayjsZod,
  eventTypeId: z.number().optional(),
  username: z.string().optional(),
  userId: z.number().optional(),
  afterEventBuffer: z.number().optional(),
  beforeEventBuffer: z.number().optional(),
  duration: z.number().optional(),
  withSource: z.boolean().optional(),
  returnDateOverrides: z.boolean(),
  bypassBusyCalendarTimes: z.boolean().optional(),
  silentlyHandleCalendarFailures: z.boolean().optional(),
  shouldServeCache: 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 = withReporting(this._getEventType.bind(this), "getEventType");
    this.getUser = withReporting(this._getUser.bind(this), "getUser");
    this.getCurrentSeats = withReporting(this._getCurrentSeats.bind(this), "getCurrentSeats");
    this.getUserAvailability = withReporting(this._getUserAvailability.bind(this), "getUserAvailability");
    this.getPeriodStartDatesBetween = withReporting(
      (dateFrom, dateTo, period, timeZone) => getPeriodStartDatesBetween(dateFrom, dateTo, period, timeZone),
      "getPeriodStartDatesBetween"
    );
    this.getUsersAvailability = withReporting(this._getUsersAvailability.bind(this), "getUsersAvailability");
  }
  // Fetch timezones from outlook or google using delegated credentials (formely known as domain wide delegatiion)
  async getTimezoneFromDelegatedCalendars(user) {
    if (!user.credentials || user.credentials.length === 0) {
      return null;
    }
    const delegatedCredentials = user.credentials.filter(
      (credential) => credential.type.endsWith("_calendar") && Boolean(credential.delegatedToId)
    );
    if (!delegatedCredentials || delegatedCredentials.length === 0) {
      return null;
    }
    const cacheKey = `user-timezone:${user.id}`;
    try {
      const cachedTimezone = await this.dependencies.redisClient.get(cacheKey);
      if (cachedTimezone) {
        log.debug(`Got timezone ${cachedTimezone} from Redis cache for user ${user.id}`);
        return cachedTimezone;
      }
    } catch (error) {
      log.warn(`Failed to get timezone from Redis cache for user ${user.id}:`, error);
    }
    if (delegatedCredentials.length === 0) {
      return null;
    }
    for (const credential of delegatedCredentials) {
      try {
        const calendar = await getCalendar(credential);
        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 ${credential.type}`);
            try {
              await this.dependencies.redisClient.set(cacheKey, timezone, { ttl: 3600 * 6 * 1e3 });
              log.debug(`Cached timezone ${timezone} in Redis for user ${user.id}`);
            } catch (error) {
              log.warn(`Failed to set timezone in Redis cache for user ${user.id}:`, error);
            }
            return timezone;
          }
        }
      } catch (error) {
        log.warn(`Failed to get timezone from calendar service ${credential.type}:`, error);
      }
    }
    return null;
  }
  async _getEventType(id) {
    const eventType = await this.dependencies.eventTypeRepo.findByIdForUserAvailability({ id });
    if (!eventType) {
      return eventType;
    }
    return {
      ...eventType,
      metadata: 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 === SchedulingType.MANAGED || schedulingType === SchedulingType.ROUND_ROBIN || schedulingType === SchedulingType.COLLECTIVE;
    const bookings = await this.dependencies.bookingRepo.findAcceptedBookingByEventTypeId({
      eventTypeId: id,
      dateFrom: dateFrom.format(),
      dateTo: dateTo.format()
    });
    return bookings.map((booking) => {
      const attendees = isTeamEvent ? booking.attendees.filter((attendee) => !hostEmails?.includes(attendee.email)) : booking.attendees;
      return {
        uid: booking.uid,
        startTime: booking.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: ${safeStringify({
        query
      })}`
    );
    if (!dateFrom.isValid() || !dateTo.isValid()) {
      throw new HttpError({ statusCode: 400, message: "Invalid time range given." });
    }
    const where = {};
    if (username)
      where.username = username;
    if (userId)
      where.id = userId;
    const user = initialData?.user || await this.getUser(where);
    if (!user) {
      throw new 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.schedules.filter(
      (schedule2) => !user?.defaultScheduleId || schedule2.id === user?.defaultScheduleId
    )[0];
    const hostSchedule = eventType?.hosts?.find((host) => host.user.id === user.id)?.schedule;
    const fallbackTimezoneIfScheduleIsMissing = eventType?.timeZone || user.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 ? parseBookingLimit(eventType.bookingLimits) : null;
    const durationLimits = eventType?.durationLimits && typeof eventType.durationLimits === "object" && Object.keys(eventType.durationLimits).length > 0 ? parseDurationLimit(eventType.durationLimits) : null;
    const getBusyTimesStart = dateFrom.toISOString();
    const getBusyTimesEnd = dateTo.toISOString();
    const selectedCalendars = eventType?.useEventLevelSelectedCalendars ? EventTypeRepository.getSelectedCalendarsFromUser({ user, eventTypeId: eventType.id }) : user.userLevelSelectedCalendars;
    const isTimezoneSet = Boolean(potentialSchedule && potentialSchedule.timeZone !== null);
    const calendarTimezone = !isTimezoneSet ? await this.getTimezoneFromDelegatedCalendars(user) : null;
    const finalTimezone = !isTimezoneSet && calendarTimezone ? calendarTimezone : schedule?.timeZone || fallbackTimezoneIfScheduleIsMissing;
    let busyTimesFromLimits = [];
    if (initialData?.busyTimesFromLimits && initialData?.eventTypeForLimits) {
      busyTimesFromLimits = initialData.busyTimesFromLimits.get(user.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 = parseBookingLimit(teamForBookingLimits?.bookingLimits);
    let busyTimesFromTeamLimits = [];
    if (initialData?.teamBookingLimits && teamForBookingLimits) {
      busyTimesFromTeamLimits = initialData.teamBookingLimits.get(user.id) || [];
    } else if (teamForBookingLimits && teamBookingLimits) {
      busyTimesFromTeamLimits = await getBusyTimesFromTeamLimits(
        user,
        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.credentials,
        startTime: getBusyTimesStart,
        endTime: getBusyTimesEnd,
        eventTypeId,
        userId: user.id,
        userEmail: user.email,
        username: `${user.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: ${safeStringify({
        chosenSchedule: schedule,
        eventTypeSchedule: eventType?.schedule,
        userSchedule,
        hostSchedule
      })}`
    );
    if (!(schedule?.availability || (eventType?.availability.length ? eventType.availability : user.availability))) {
      throw new HttpError({ statusCode: 400, message: ErrorCode.AvailabilityNotFoundInSchedule });
    }
    const availability = (schedule?.availability || (eventType?.availability.length ? eventType.availability : user.availability)).map((a) => ({
      ...a,
      userId: user.id
    }));
    const workingHours = getWorkingHours({ timeZone: finalTimezone }, availability);
    const dateOverrides = [];
    if (returnDateOverrides) {
      const calculateDateOverridesSpan = Sentry.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.id,
      dateFrom: dateFrom.toISOString(),
      dateTo: dateTo.toISOString()
    });
    const datesOutOfOffice = this.calculateOutOfOfficeRanges(outOfOfficeDays, availability);
    const { dateRanges, oooExcludedDateRanges } = buildDateRanges({
      dateFrom,
      dateTo,
      availability,
      timeZone: finalTimezone,
      travelSchedules: isDefaultSchedule ? user.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 = subtract(dateRanges, formattedBusyTimes);
    const dateRangesInWhichUserIsAvailableWithoutOOO = 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: ${safeStringify(result)}`
    );
    return result;
  }
  calculateOutOfOfficeRanges(outOfOfficeDays, availability) {
    if (!outOfOfficeDays || outOfOfficeDays.length === 0) {
      return {};
    }
    return outOfOfficeDays.reduce((acc, { start, end, toUser, user, 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: user.id, displayName: user.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(
        (user) => this._getUserAvailability(
          {
            ...query,
            userId: user.id,
            username: user.username || ""
          },
          initialData ? {
            ...initialData,
            user,
            currentBookings: user.currentBookings,
            outOfOfficeDays: user.outOfOfficeDays
          } : void 0
        )
      )
    );
  }
}
export {
  UserAvailabilityService as U,
  getCheckBookingAndDurationLimitsService as a,
  busyTimesModule as b,
  getBusyTimesService as g
};
