import { createModule } from "@evyweb/ioctopus";
import { p as prisma, a9 as readonlyPrisma, w as withReporting, l as logger, B as BookingStatus } from "./i18n-49-r55rr.js";
import { B as BookingRepository } from "./booking-9BXi8T67.js";
import "./index-rbwdGzy0.js";
import { g as getBusyCalendarTimes, i as intervalLimitKeyToUnit } from "./isBookingLimits-Dt41oWO0.js";
import { s as subtract } from "./date-ranges-79rc7oZX.js";
import { s as stringToDayjs } from "./index-sMkzixNw.js";
import { c as getPiiFreeBooking } from "./piiFreeData-j9VT_8Gj.js";
import { performance } from "perf_hooks";
import dayjs from "dayjs";
const BOOKING_DI_TOKENS = {
  REGULAR_BOOKING_SERVICE: Symbol("RegularBookingService"),
  REGULAR_BOOKING_SERVICE_MODULE: Symbol("RegularBookingServiceModule"),
  RECURRING_BOOKING_SERVICE: Symbol("RecurringBookingService"),
  RECURRING_BOOKING_SERVICE_MODULE: Symbol("RecurringBookingServiceModule"),
  INSTANT_BOOKING_CREATE_SERVICE: Symbol("InstantBookingCreateService"),
  INSTANT_BOOKING_CREATE_SERVICE_MODULE: Symbol("InstantBookingCreateServiceModule")
};
const DI_TOKENS = {
  PRISMA_CLIENT: Symbol("PrismaClient"),
  READ_ONLY_PRISMA_CLIENT: Symbol("ReadOnlyPrismaClient"),
  PRISMA_MODULE: Symbol("PrismaModule"),
  REDIS_CLIENT: Symbol("RedisClient"),
  OOO_REPOSITORY: Symbol("OOORepository"),
  OOO_REPOSITORY_MODULE: Symbol("OOORepositoryModule"),
  SCHEDULE_REPOSITORY: Symbol("ScheduleRepository"),
  SCHEDULE_REPOSITORY_MODULE: Symbol("ScheduleRepositoryModule"),
  SELECTED_SLOT_REPOSITORY: Symbol("SelectedSlotRepository"),
  SELECTED_SLOT_REPOSITORY_MODULE: Symbol("SelectedSlotRepositoryModule"),
  TEAM_REPOSITORY: Symbol("TeamRepository"),
  TEAM_REPOSITORY_MODULE: Symbol("TeamRepositoryModule"),
  USER_REPOSITORY: Symbol("UserRepository"),
  USER_REPOSITORY_MODULE: Symbol("UserRepositoryModule"),
  BOOKING_REPOSITORY: Symbol("BookingRepository"),
  BOOKING_REPOSITORY_MODULE: Symbol("BookingRepositoryModule"),
  EVENT_TYPE_REPOSITORY: Symbol("EventTypeRepository"),
  EVENT_TYPE_REPOSITORY_MODULE: Symbol("EventTypeRepositoryModule"),
  ROUTING_FORM_RESPONSE_REPOSITORY: Symbol("RoutingFormResponseRepository"),
  ROUTING_FORM_RESPONSE_REPOSITORY_MODULE: Symbol("RoutingFormResponseRepositoryModule"),
  AVAILABLE_SLOTS_SERVICE: Symbol("AvailableSlotsService"),
  AVAILABLE_SLOTS_SERVICE_MODULE: Symbol("AvailableSlotsModule"),
  INSIGHTS_ROUTING_SERVICE: Symbol("InsightsRoutingService"),
  INSIGHTS_ROUTING_SERVICE_MODULE: Symbol("InsightsRoutingServiceModule"),
  INSIGHTS_BOOKING_SERVICE: Symbol("InsightsBookingService"),
  INSIGHTS_BOOKING_SERVICE_MODULE: Symbol("InsightsBookingServiceModule"),
  FEATURES_REPOSITORY: Symbol("FeaturesRepository"),
  FEATURES_REPOSITORY_MODULE: Symbol("FeaturesRepositoryModule"),
  CACHE_SERVICE: Symbol("CacheService"),
  CACHE_SERVICE_MODULE: Symbol("CacheServiceModule"),
  CHECK_BOOKING_LIMITS_SERVICE: Symbol("CheckBookingLimitsService"),
  CHECK_BOOKING_LIMITS_SERVICE_MODULE: Symbol("CheckBookingLimitsServiceModule"),
  CHECK_BOOKING_AND_DURATION_LIMITS_SERVICE: Symbol("CheckBookingAndDurationLimitsService"),
  CHECK_BOOKING_AND_DURATION_LIMITS_SERVICE_MODULE: Symbol("CheckBookingAndDurationLimitsServiceModule"),
  GET_USER_AVAILABILITY_SERVICE: Symbol("GetUserAvailabilityService"),
  GET_USER_AVAILABILITY_SERVICE_MODULE: Symbol("GetUserAvailabilityModule"),
  BUSY_TIMES_SERVICE: Symbol("BusyTimesService"),
  BUSY_TIMES_SERVICE_MODULE: Symbol("BusyTimesServiceModule"),
  QUALIFIED_HOSTS_SERVICE: Symbol("QualifiedHostsService"),
  QUALIFIED_HOSTS_SERVICE_MODULE: Symbol("QualifiedHostsServiceModule"),
  FILTER_HOSTS_SERVICE: Symbol("FilterHostsService"),
  FILTER_HOSTS_SERVICE_MODULE: Symbol("FilterHostsServiceModule"),
  NO_SLOTS_NOTIFICATION_SERVICE: Symbol("NoSlotsNotificationService"),
  NO_SLOTS_NOTIFICATION_SERVICE_MODULE: Symbol("NoSlotsNotificationServiceModule"),
  MEMBERSHIP_REPOSITORY: Symbol("MembershipRepository"),
  MEMBERSHIP_REPOSITORY_MODULE: Symbol("MembershipRepositoryModule"),
  LUCKY_USER_SERVICE: Symbol("LuckyUserService"),
  LUCKY_USER_SERVICE_MODULE: Symbol("LuckyUserServiceModule"),
  HOST_REPOSITORY: Symbol("HostRepository"),
  HOST_REPOSITORY_MODULE: Symbol("HostRepositoryModule"),
  ATTRIBUTE_REPOSITORY: Symbol("AttributeRepository"),
  ATTRIBUTE_REPOSITORY_MODULE: Symbol("AttributeRepositoryModule"),
  // Booking service tokens
  ...BOOKING_DI_TOKENS
};
const prismaModule = createModule();
const token$1 = DI_TOKENS.PRISMA_CLIENT;
const readOnlyToken = DI_TOKENS.READ_ONLY_PRISMA_CLIENT;
prismaModule.bind(token$1).toFactory(() => prisma, "singleton");
prismaModule.bind(readOnlyToken).toFactory(() => readonlyPrisma, "singleton");
const bookingRepositoryModule = createModule();
const token = DI_TOKENS.BOOKING_REPOSITORY;
bookingRepositoryModule.bind(token).toClass(BookingRepository, [DI_TOKENS.PRISMA_CLIENT]);
const getDefinedBufferTimes = () => {
  return [5, 10, 15, 20, 30, 45, 60, 90, 120];
};
class BusyTimesService {
  constructor(dependencies) {
    this.dependencies = dependencies;
    this.getBusyTimes = withReporting(this._getBusyTimes.bind(this), "getBusyTimes");
  }
  async _getBusyTimes(params) {
    const {
      credentials,
      userId,
      userEmail,
      username,
      eventTypeId,
      startTime,
      endTime,
      beforeEventBuffer,
      afterEventBuffer,
      selectedCalendars,
      seatedEvent,
      rescheduleUid,
      duration,
      bypassBusyCalendarTimes = false,
      silentlyHandleCalendarFailures = false,
      shouldServeCache
    } = params;
    logger.silly(
      `Checking Busy time from Cal Bookings in range ${startTime} to ${endTime} for input ${JSON.stringify({
        userId,
        eventTypeId,
        status: BookingStatus.ACCEPTED
      })}`
    );
    performance.mark("prismaBookingGetStart");
    const startTimeDate = rescheduleUid && duration ? dayjs(startTime).subtract(duration, "minute").toDate() : new Date(startTime);
    const endTimeDate = rescheduleUid && duration ? dayjs(endTime).add(duration, "minute").toDate() : new Date(endTime);
    const definedBufferTimes = getDefinedBufferTimes();
    const maxBuffer = definedBufferTimes[definedBufferTimes.length - 1];
    const startTimeAdjustedWithMaxBuffer = dayjs(startTimeDate).subtract(maxBuffer, "minute").toDate();
    const endTimeAdjustedWithMaxBuffer = dayjs(endTimeDate).add(maxBuffer, "minute").toDate();
    let bookings = params.currentBookings;
    if (!bookings) {
      const bookingRepo = this.dependencies.bookingRepo;
      bookings = await bookingRepo.findAllExistingBookingsForEventTypeBetween({
        userIdAndEmailMap: /* @__PURE__ */ new Map([[userId, userEmail]]),
        eventTypeId,
        startDate: startTimeAdjustedWithMaxBuffer,
        endDate: endTimeAdjustedWithMaxBuffer,
        seatedEvent
      });
    }
    const bookingSeatCountMap = {};
    const busyTimes = bookings.reduce((aggregate, booking) => {
      const { id, startTime: startTime2, endTime: endTime2, eventType, title, ...rest } = booking;
      const minutesToBlockBeforeEvent = (eventType?.beforeEventBuffer || 0) + (afterEventBuffer || 0);
      const minutesToBlockAfterEvent = (eventType?.afterEventBuffer || 0) + (beforeEventBuffer || 0);
      if (rest._count?.seatsReferences) {
        const bookedAt = `${dayjs(startTime2).utc().format()}<>${dayjs(endTime2).utc().format()}`;
        bookingSeatCountMap[bookedAt] = bookingSeatCountMap[bookedAt] || 0;
        bookingSeatCountMap[bookedAt]++;
        if (
          // there are still seats available.
          bookingSeatCountMap[bookedAt] < (eventType?.seatsPerTimeSlot || 1) && // and this is the seated event, other event types should be blocked.
          eventTypeId === eventType?.id
        ) {
          if (minutesToBlockBeforeEvent) {
            aggregate.push({
              start: dayjs(startTime2).subtract(minutesToBlockBeforeEvent, "minute").toDate(),
              end: dayjs(startTime2).toDate()
              // The event starts after the buffer
            });
          }
          if (minutesToBlockAfterEvent) {
            aggregate.push({
              start: dayjs(endTime2).toDate(),
              // The event ends before the buffer
              end: dayjs(endTime2).add(minutesToBlockAfterEvent, "minute").toDate()
            });
          }
          return aggregate;
        }
        delete bookingSeatCountMap[bookedAt];
      }
      if (rest.uid === rescheduleUid) {
        return aggregate;
      }
      aggregate.push({
        start: dayjs(startTime2).subtract(minutesToBlockBeforeEvent, "minute").toDate(),
        end: dayjs(endTime2).add(minutesToBlockAfterEvent, "minute").toDate(),
        title,
        source: `eventType-${eventType?.id}-booking-${id}`
      });
      return aggregate;
    }, []);
    logger.debug(
      `Busy Time from Cal Bookings ${JSON.stringify({
        busyTimes,
        bookings: bookings?.map((booking) => getPiiFreeBooking(booking)),
        numCredentials: credentials?.length
      })}`
    );
    performance.mark("prismaBookingGetEnd");
    performance.measure(`prisma booking get took $1'`, "prismaBookingGetStart", "prismaBookingGetEnd");
    if (credentials?.length > 0 && !bypassBusyCalendarTimes) {
      const startConnectedCalendarsGet = performance.now();
      const calendarBusyTimesQuery = await getBusyCalendarTimes(
        credentials,
        startTime,
        endTime,
        selectedCalendars,
        shouldServeCache
      );
      if (!calendarBusyTimesQuery.success) {
        if (silentlyHandleCalendarFailures) {
          logger.warn(
            `Calendar busy times fetch failed but handling silently due to silentlyHandleCalendarFailures flag for user ${username}`,
            {
              selectedCalendarIds: selectedCalendars.map((calendar) => calendar.id)
            }
          );
        } else {
          throw new Error(
            `Failed to fetch busy calendar times for selected calendars ${selectedCalendars.map(
              (calendar) => calendar.id
            )}`
          );
        }
      } else {
        const calendarBusyTimes = calendarBusyTimesQuery.data;
        const endConnectedCalendarsGet = performance.now();
        logger.debug(
          `Connected Calendars get took ${endConnectedCalendarsGet - startConnectedCalendarsGet} ms for user ${username}`,
          JSON.stringify({
            eventTypeId,
            startTimeDate,
            endTimeDate,
            calendarBusyTimes
          })
        );
        const openSeatsDateRanges = Object.keys(bookingSeatCountMap).map((key) => {
          const [start, end] = key.split("<>");
          return {
            start: dayjs(start),
            end: dayjs(end)
          };
        });
        if (rescheduleUid) {
          const originalRescheduleBooking = bookings.find((booking) => booking.uid === rescheduleUid);
          if (originalRescheduleBooking) {
            openSeatsDateRanges.push({
              start: dayjs(originalRescheduleBooking.startTime),
              end: dayjs(originalRescheduleBooking.endTime)
            });
          }
        }
        const result = subtract(
          calendarBusyTimes.map((value) => ({
            ...value,
            end: dayjs(value.end),
            start: dayjs(value.start)
          })),
          openSeatsDateRanges
        );
        busyTimes.push(
          ...result.map((busyTime) => ({
            ...busyTime,
            start: busyTime.start.subtract(afterEventBuffer || 0, "minute").toDate(),
            end: busyTime.end.add(beforeEventBuffer || 0, "minute").toDate()
          }))
        );
      }
    } else {
      logger.warn(`No credentials found for user ${userId}`, {
        selectedCalendarIds: selectedCalendars.map((calendar) => calendar.id)
      });
    }
    logger.debug(
      "getBusyTimes:",
      JSON.stringify({
        allBusyTimes: busyTimes
      })
    );
    return busyTimes;
  }
  getStartEndDateforLimitCheck(startDate, endDate, bookingLimits, durationLimits) {
    const startTimeAsDayJs = stringToDayjs(startDate);
    const endTimeAsDayJs = stringToDayjs(endDate);
    let limitDateFrom = stringToDayjs(startDate);
    let limitDateTo = stringToDayjs(endDate);
    for (const key of ["PER_MONTH", "PER_WEEK", "PER_DAY"]) {
      if (bookingLimits?.[key] || durationLimits?.[key]) {
        const unit = intervalLimitKeyToUnit(key);
        limitDateFrom = dayjs.min(limitDateFrom, startTimeAsDayJs.startOf(unit));
        limitDateTo = dayjs.max(limitDateTo, endTimeAsDayJs.endOf(unit));
      }
    }
    return { limitDateFrom, limitDateTo };
  }
  async getBusyTimesForLimitChecks(params) {
    const { userIds, eventTypeId, startDate, endDate, rescheduleUid, bookingLimits, durationLimits } = params;
    performance.mark("getBusyTimesForLimitChecksStart");
    let busyTimes = [];
    if (!bookingLimits && !durationLimits) {
      return busyTimes;
    }
    const { limitDateFrom, limitDateTo } = this.getStartEndDateforLimitCheck(
      startDate,
      endDate,
      bookingLimits,
      durationLimits
    );
    logger.silly(
      `Fetch limit checks bookings in range ${limitDateFrom} to ${limitDateTo} for input ${JSON.stringify({
        eventTypeId,
        status: BookingStatus.ACCEPTED
      })}`
    );
    const where = {
      userId: {
        in: userIds
      },
      eventTypeId,
      status: BookingStatus.ACCEPTED,
      // FIXME: bookings that overlap on one side will never be counted
      startTime: {
        gte: limitDateFrom.toDate()
      },
      endTime: {
        lte: limitDateTo.toDate()
      }
    };
    if (rescheduleUid) {
      where.NOT = {
        uid: rescheduleUid
      };
    }
    const bookings = await prisma.booking.findMany({
      where,
      select: {
        id: true,
        startTime: true,
        endTime: true,
        eventType: {
          select: {
            id: true
          }
        },
        title: true,
        userId: true
      }
    });
    busyTimes = bookings.map(({ id, startTime, endTime, eventType, title, userId }) => ({
      start: dayjs(startTime).toDate(),
      end: dayjs(endTime).toDate(),
      title,
      source: `eventType-${eventType?.id}-booking-${id}`,
      userId
    }));
    logger.silly(`Fetch limit checks bookings for eventId: ${eventTypeId} ${JSON.stringify(busyTimes)}`);
    performance.mark("getBusyTimesForLimitChecksEnd");
    performance.measure(
      `prisma booking get for limits took $1'`,
      "getBusyTimesForLimitChecksStart",
      "getBusyTimesForLimitChecksEnd"
    );
    return busyTimes;
  }
}
class LimitManager {
  constructor() {
    this.busyMap = /* @__PURE__ */ new Map();
  }
  /**
   * Creates a busy map key
   */
  static createKey(start, unit, timeZone) {
    const tzStart = timeZone ? start.tz(timeZone) : start;
    return `${unit}-${tzStart.startOf(unit).toISOString()}`;
  }
  /**
   * Checks if already marked busy by ancestors or siblings
   */
  isAlreadyBusy(start, unit, timeZone) {
    if (this.busyMap.has(LimitManager.createKey(start, "year", timeZone)))
      return true;
    if (unit === "month" && this.busyMap.has(LimitManager.createKey(start, "month", timeZone))) {
      return true;
    } else if (unit === "week" && // weeks can be part of two months
    (this.busyMap.has(LimitManager.createKey(start, "month", timeZone)) && this.busyMap.has(LimitManager.createKey(start.endOf("week"), "month", timeZone)) || this.busyMap.has(LimitManager.createKey(start, "week", timeZone)))) {
      return true;
    } else if (unit === "day" && (this.busyMap.has(LimitManager.createKey(start, "month", timeZone)) || this.busyMap.has(LimitManager.createKey(start, "week", timeZone)) || this.busyMap.has(LimitManager.createKey(start, "day", timeZone)))) {
      return true;
    } else {
      return false;
    }
  }
  /**
   * Adds a new busy time
   */
  addBusyTime(start, unit, timeZone) {
    const tzStart = timeZone ? start.tz(timeZone) : start;
    this.busyMap.set(`${unit}-${tzStart.toISOString()}`, {
      start: tzStart.toISOString(),
      end: tzStart.endOf(unit).toISOString()
    });
  }
  /**
   * Merges busy times from another LimitManager
   */
  mergeBusyTimes(otherManager) {
    otherManager.busyMap.forEach((busyTime, key) => {
      if (!this.busyMap.has(key)) {
        this.busyMap.set(key, busyTime);
      }
    });
  }
  /**
   * Returns all busy times
   */
  getBusyTimes() {
    return Array.from(this.busyMap.values());
  }
}
function extractDateParameters(booking, periodStart, periodEnd, timeZone) {
  const bookingStart = dayjs(booking.start).tz(timeZone);
  const bookingDay = bookingStart.format("YYYY-MM-DD");
  const periodStartDay = periodStart.format("YYYY-MM-DD");
  const periodEndDay = periodEnd.format("YYYY-MM-DD");
  return {
    bookingStart,
    bookingDay,
    periodStartDay,
    periodEndDay
  };
}
function isBookingWithinPeriod(booking, periodStart, periodEnd, timeZone) {
  const { bookingDay, periodStartDay, periodEndDay } = extractDateParameters(
    booking,
    periodStart,
    periodEnd,
    timeZone
  );
  return !(bookingDay < periodStartDay || bookingDay > periodEndDay);
}
export {
  BusyTimesService as B,
  DI_TOKENS as D,
  LimitManager as L,
  bookingRepositoryModule as b,
  isBookingWithinPeriod as i,
  prismaModule as p
};
