import { i as intersect, a as isBookingWithinPeriod, b as buildDateRanges, L as LimitManager } from "./utils-1vlKp3ZI.js";
import { B } from "./utils-1vlKp3ZI.js";
import { v4 } from "uuid";
import { S as SchedulingType, p as prisma, w as withReporting, j as getTranslation, z as WEBAPP_URL, an as IS_PRODUCTION, l as logger, a as safeStringify, aA as RESERVED_SUBDOMAINS, aB as orgDomainConfig } from "./orgDomains-eTg-cjth.js";
import { R as RedisService, i as isEventTypeLoggingEnabled, s as shouldIgnoreContactOwner, f as findQualifiedHostsWithDelegationCredentials, c as checkForConflicts, a as calculatePeriodLimits, b as isTimeViolatingFutureLimit, d as isTimeOutOfBounds, B as BookingDateInPastError } from "./isOutOfBounds-bQNZF0GU.js";
import { a as getTimeZone, g as getUTCOffsetByTimezone } from "./index-ruwhZ-bu.js";
import { g as getDefaultEvent } from "./defaultEvents-fyusRmHI.js";
import { d as descendingLimitKeys, i as intervalLimitKeyToUnit, p as parseBookingLimit, a as parseDurationLimit } from "./isBookingLimits-4PD5exXf.js";
import { F as FeaturesRepository } from "./features.repository-X0S5Zedc.js";
import { w as withSelectedCalendars } from "./user-9vti0VBh.js";
import dayjs from "dayjs";
import { i as indexBrowser } from "./index-browser-zve3RCF0.js";
import { TRPCError } from "@trpc/server";
import { v as sendOrganizationAdminNoSlotsNotification } from "./email-manager-mHSBIi6g.js";
function createIntervalNodes(items, getStart, getEnd) {
  return items.map((item, index) => ({
    item,
    index,
    start: getStart(item),
    end: getEnd(item),
    maxEnd: getEnd(item)
  }));
}
class IntervalTree {
  constructor(nodes) {
    this.root = this.buildTree([...nodes]);
  }
  buildTree(nodes) {
    if (nodes.length === 0)
      return void 0;
    const mid = Math.floor(nodes.length / 2);
    const node = nodes[mid];
    const leftNodes = nodes.slice(0, mid);
    const rightNodes = nodes.slice(mid + 1);
    node.left = this.buildTree(leftNodes);
    node.right = this.buildTree(rightNodes);
    node.maxEnd = Math.max(node.end, node.left?.maxEnd ?? 0, node.right?.maxEnd ?? 0);
    return node;
  }
  getRoot() {
    return this.root;
  }
}
class ContainmentSearchAlgorithm {
  constructor(tree) {
    this.tree = tree;
  }
  findContainingIntervals(targetStart, targetEnd, targetIndex) {
    const result = [];
    this.searchContaining(this.tree.getRoot(), targetStart, targetEnd, targetIndex, result);
    return result;
  }
  searchContaining(node, targetStart, targetEnd, targetIndex, result) {
    if (!node)
      return;
    if (node.end < node.start) {
      this.searchContaining(node.left, targetStart, targetEnd, targetIndex, result);
      this.searchContaining(node.right, targetStart, targetEnd, targetIndex, result);
      return;
    }
    if (node.start <= targetStart && node.end >= targetEnd && node.index !== targetIndex) {
      result.push(node);
    }
    if (node.left && node.left.maxEnd >= targetStart) {
      this.searchContaining(node.left, targetStart, targetEnd, targetIndex, result);
    }
    if (node.right && node.start <= targetEnd) {
      this.searchContaining(node.right, targetStart, targetEnd, targetIndex, result);
    }
  }
}
function filterRedundantDateRanges(dateRanges) {
  if (dateRanges.length <= 1)
    return dateRanges;
  const sortedRanges = [...dateRanges].sort((a, b) => a.start.valueOf() - b.start.valueOf());
  const intervalNodes = createIntervalNodes(
    sortedRanges,
    (range) => range.start.valueOf(),
    (range) => range.end.valueOf()
  );
  const intervalTree = new IntervalTree(intervalNodes);
  const searchAlgorithm = new ContainmentSearchAlgorithm(intervalTree);
  return sortedRanges.filter((range, index) => {
    if (range.end.valueOf() < range.start.valueOf()) {
      return true;
    }
    const containingIntervals = searchAlgorithm.findContainingIntervals(
      range.start.valueOf(),
      range.end.valueOf(),
      index
    );
    for (const containingNode of containingIntervals) {
      const otherRange = containingNode.item;
      const otherIndex = containingNode.index;
      if (otherRange.start.valueOf() === range.start.valueOf() && otherRange.end.valueOf() === range.end.valueOf()) {
        return otherIndex > index;
      }
      return false;
    }
    return true;
  });
}
function mergeOverlappingDateRanges(dateRanges) {
  dateRanges.sort((a, b) => a.start.valueOf() - b.start.valueOf());
  const mergedDateRanges = [];
  let currentRange = dateRanges[0];
  if (!currentRange) {
    return [];
  }
  for (let i = 1; i < dateRanges.length; i++) {
    const nextRange = dateRanges[i];
    if (isCurrentRangeOverlappingNext(currentRange, nextRange)) {
      currentRange = {
        start: currentRange.start,
        end: currentRange.end.valueOf() > nextRange.end.valueOf() ? currentRange.end : nextRange.end
      };
    } else {
      mergedDateRanges.push(currentRange);
      currentRange = nextRange;
    }
  }
  mergedDateRanges.push(currentRange);
  return mergedDateRanges;
}
function isCurrentRangeOverlappingNext(currentRange, nextRange) {
  return currentRange.start.valueOf() <= nextRange.start.valueOf() && currentRange.end.valueOf() > nextRange.start.valueOf();
}
function uniqueAndSortedDateRanges(ranges) {
  const seen = /* @__PURE__ */ new Set();
  return ranges.sort((a, b) => {
    const startDiff = a.start.valueOf() - b.start.valueOf();
    return startDiff !== 0 ? startDiff : a.end.valueOf() - b.end.valueOf();
  }).filter((range) => {
    const key = `${range.start.valueOf()}-${range.end.valueOf()}`;
    if (seen.has(key))
      return false;
    seen.add(key);
    return true;
  });
}
const getAggregatedAvailability = (userAvailability, schedulingType) => {
  const isTeamEvent = schedulingType === SchedulingType.COLLECTIVE || schedulingType === SchedulingType.ROUND_ROBIN || userAvailability.length > 1;
  const fixedHosts = userAvailability.filter(
    ({ user }) => !schedulingType || schedulingType === SchedulingType.COLLECTIVE || user?.isFixed
  );
  const fixedDateRanges = mergeOverlappingDateRanges(
    intersect(fixedHosts.map((s) => !isTeamEvent ? s.dateRanges : s.oooExcludedDateRanges))
  );
  const dateRangesToIntersect = !!fixedDateRanges.length ? [fixedDateRanges] : [];
  const roundRobinHosts = userAvailability.filter(({ user }) => user?.isFixed !== true);
  if (roundRobinHosts.length) {
    dateRangesToIntersect.push(
      roundRobinHosts.flatMap((s) => !isTeamEvent ? s.dateRanges : s.oooExcludedDateRanges)
    );
  }
  const availability = intersect(dateRangesToIntersect);
  const uniqueRanges = uniqueAndSortedDateRanges(availability);
  return filterRedundantDateRanges(uniqueRanges);
};
async function isRestrictionScheduleEnabled(teamId) {
  if (!teamId) {
    return false;
  }
  const featureRepo = new FeaturesRepository(prisma);
  return await featureRepo.checkIfTeamHasFeature(teamId, "restriction-schedule");
}
const minimumOfOne = (input) => input < 1 ? 1 : input;
function buildSlotsWithDateRanges({
  dateRanges,
  frequency,
  eventLength,
  timeZone,
  minimumBookingNotice,
  offsetStart,
  datesOutOfOffice
}) {
  frequency = minimumOfOne(frequency);
  eventLength = minimumOfOne(eventLength);
  offsetStart = offsetStart ? minimumOfOne(offsetStart) : 0;
  const orderedDateRanges = dateRanges.sort((a, b) => a.start.valueOf() - b.start.valueOf());
  const slots = /* @__PURE__ */ new Map();
  let interval = Number(process.env.NEXT_PUBLIC_AVAILABILITY_SCHEDULE_INTERVAL) || 1;
  const intervalsWithDefinedStartTimes = [60, 30, 20, 15, 10, 5];
  for (let i = 0; i < intervalsWithDefinedStartTimes.length; i++) {
    if (frequency % intervalsWithDefinedStartTimes[i] === 0) {
      interval = intervalsWithDefinedStartTimes[i];
      break;
    }
  }
  const startTimeWithMinNotice = dayjs.utc().add(minimumBookingNotice, "minute");
  const slotBoundaries = /* @__PURE__ */ new Map();
  orderedDateRanges.forEach((range) => {
    const dateYYYYMMDD = range.start.format("YYYY-MM-DD");
    let slotStartTime = range.start.utc().isAfter(startTimeWithMinNotice) ? range.start : startTimeWithMinNotice;
    slotStartTime = slotStartTime.minute() % interval !== 0 ? slotStartTime.startOf("hour").add(Math.ceil(slotStartTime.minute() / interval) * interval, "minute") : slotStartTime;
    slotStartTime = slotStartTime.add(offsetStart ?? 0, "minutes").tz(timeZone);
    const slotBoundariesValueArray = Array.from(slotBoundaries.keys());
    if (slotBoundariesValueArray.length > 0) {
      slotBoundariesValueArray.sort((a, b) => a - b);
      let prevBoundary = null;
      for (let i = slotBoundariesValueArray.length - 1; i >= 0; i--) {
        if (slotBoundariesValueArray[i] < slotStartTime.valueOf()) {
          prevBoundary = slotBoundariesValueArray[i];
          break;
        }
      }
      if (prevBoundary) {
        const prevBoundaryEnd = dayjs(prevBoundary).add(frequency + (offsetStart ?? 0), "minutes");
        if (prevBoundaryEnd.isAfter(slotStartTime)) {
          const dayjsPrevBoundary = dayjs(prevBoundary);
          if (!dayjsPrevBoundary.isBefore(range.start)) {
            slotStartTime = dayjsPrevBoundary;
          } else {
            slotStartTime = prevBoundaryEnd;
          }
          slotStartTime = slotStartTime.tz(timeZone);
        }
      }
    }
    while (!slotStartTime.add(eventLength, "minutes").subtract(1, "second").utc().isAfter(range.end)) {
      const slotKey = slotStartTime.toISOString();
      if (slots.has(slotKey)) {
        slotStartTime = slotStartTime.add(frequency + (offsetStart ?? 0), "minutes");
        continue;
      }
      slotBoundaries.set(slotStartTime.valueOf(), true);
      const dateOutOfOfficeExists = datesOutOfOffice?.[dateYYYYMMDD];
      let slotData = {
        time: slotStartTime
      };
      if (dateOutOfOfficeExists) {
        const { toUser, fromUser, reason, emoji } = dateOutOfOfficeExists;
        slotData = {
          time: slotStartTime,
          away: true,
          ...fromUser && { fromUser },
          ...toUser && { toUser },
          ...reason && { reason },
          ...emoji && { emoji }
        };
      }
      slots.set(slotKey, slotData);
      slotStartTime = slotStartTime.add(frequency + (offsetStart ?? 0), "minutes");
    }
  });
  return Array.from(slots.values());
}
const getSlots = ({
  inviteeDate,
  frequency,
  minimumBookingNotice,
  dateRanges,
  eventLength,
  offsetStart = 0,
  datesOutOfOffice
}) => {
  return buildSlotsWithDateRanges({
    dateRanges,
    frequency,
    eventLength,
    timeZone: getTimeZone(inviteeDate),
    minimumBookingNotice,
    offsetStart,
    datesOutOfOffice
  });
};
const getSlots$1 = withReporting(getSlots, "getSlots");
const REDIS_KEY_VERSION = "V1";
const NO_SLOTS_NOTIFICATION_FREQUENCY = IS_PRODUCTION ? 7 * 24 * 3600 : 60;
const NO_SLOTS_COUNT_FOR_NOTIFICATION = 2;
const constructRedisKey = (eventDetails, orgSlug, teamId) => {
  return `${REDIS_KEY_VERSION}.${eventDetails.username}:${eventDetails.eventSlug}${orgSlug ? `@${orgSlug}:team_${teamId}` : ""}`;
};
const constructDataHash = (eventDetails) => {
  const obj = {
    st: eventDetails.startTime.format("YYYY-MM-DD"),
    vTz: eventDetails?.visitorTimezone,
    vUuid: eventDetails?.visitorUid
  };
  return JSON.stringify(obj);
};
const handleNotificationWhenNoSlots = async ({
  eventDetails,
  orgDetails,
  teamId
}) => {
  if (!orgDetails.currentOrgDomain || !teamId)
    return;
  const UPSTASH_ENV_FOUND = process.env.UPSTASH_REDIS_REST_TOKEN && process.env.UPSTASH_REDIS_REST_URL;
  if (!UPSTASH_ENV_FOUND)
    return;
  const orgSettings = await prisma.team.findFirst({
    where: {
      slug: orgDetails.currentOrgDomain,
      isOrganization: true
    },
    select: {
      organizationSettings: {
        select: {
          adminGetsNoSlotsNotification: true
        }
      }
    }
  });
  if (!orgSettings?.organizationSettings?.adminGetsNoSlotsNotification)
    return;
  const redis = new RedisService();
  const usersUniqueKey = constructRedisKey(eventDetails, orgDetails.currentOrgDomain, teamId);
  const usersExistingNoSlots = await redis.lrange(usersUniqueKey, 0, NO_SLOTS_COUNT_FOR_NOTIFICATION - 1) ?? [];
  await redis.lpush(usersUniqueKey, constructDataHash(eventDetails));
  if (!usersExistingNoSlots.length) {
    await redis.expire(usersUniqueKey, NO_SLOTS_NOTIFICATION_FREQUENCY);
  }
  if (usersExistingNoSlots.length + 1 === NO_SLOTS_COUNT_FOR_NOTIFICATION) {
    const foundAdmins = await prisma.membership.findMany({
      where: {
        team: {
          id: teamId,
          parentId: {
            not: null
          }
        },
        role: {
          in: ["ADMIN", "OWNER"]
        }
      },
      select: {
        user: {
          select: {
            email: true,
            locale: true
          }
        }
      }
    });
    const teamSlug = await prisma.team.findUnique({
      where: {
        id: teamId
      },
      select: {
        slug: true
      }
    });
    const emailsToSend = [];
    for (const admin of foundAdmins) {
      const translation = await getTranslation(admin.user.locale ?? "en", "common");
      const payload = {
        language: translation,
        to: {
          email: admin.user.email
        },
        user: eventDetails.username,
        slug: eventDetails.eventSlug,
        startTime: eventDetails.startTime.format("YYYY-MM-DD"),
        endTime: eventDetails.endTime.format("YYYY-MM-DD"),
        // For now navigate here - when impersonation via parameter has been pushed we will impersonate and then navigate to availability
        editLink: `${WEBAPP_URL}/availability?type=team`,
        teamSlug: teamSlug?.slug ?? ""
      };
      emailsToSend.push(sendOrganizationAdminNoSlotsNotification(payload));
    }
    await Promise.all(emailsToSend);
  }
};
const log = logger.getSubLogger({ prefix: ["[slots/util]"] });
const DEFAULT_SLOTS_CACHE_TTL = 2e3;
function withSlotsCache(redisClient, func) {
  return async (args) => {
    const cacheKey = `${JSON.stringify(args.input)}`;
    let success = false;
    let cachedResult = null;
    const startTime = process.hrtime();
    try {
      cachedResult = await redisClient.get(cacheKey);
      success = true;
    } catch (err) {
      if (err instanceof Error && err.name === "TimeoutError") {
        const endTime = process.hrtime(startTime);
        log.error(`Redis request timed out after ${endTime[0]}${endTime[1] / 1e6}ms`);
      } else {
        throw err;
      }
    }
    if (!success) {
      return await func(args);
    }
    if (cachedResult) {
      log.info("[CACHE HIT] Available slots", { cacheKey });
      return cachedResult;
    }
    const result = await func(args);
    const ttl = parseInt(process.env.SLOTS_CACHE_TTL ?? "", 10) || DEFAULT_SLOTS_CACHE_TTL;
    redisClient.set(cacheKey, result, { ttl });
    log.info("[CACHE MISS] Available slots", { cacheKey, ttl });
    return result;
  };
}
class AvailableSlotsService {
  constructor(dependencies) {
    this.dependencies = dependencies;
    this.getDynamicEventType = withReporting(this._getDynamicEventType.bind(this), "getDynamicEventType");
    this.getEventType = withReporting(this._getEventType.bind(this), "getEventType");
    this.getAllDatesWithBookabilityStatus = withReporting(
      this._getAllDatesWithBookabilityStatus.bind(this),
      "getAllDatesWithBookabilityStatus"
    );
    this.getBusyTimesFromLimitsForUsers = withReporting(
      this._getBusyTimesFromLimitsForUsers.bind(this),
      "getBusyTimesFromLimitsForUsers"
    );
    this.getBusyTimesFromTeamLimitsForUsers = withReporting(
      this._getBusyTimesFromTeamLimitsForUsers.bind(this),
      "getBusyTimesFromTeamLimitsForUsers"
    );
    this.getOOODates = withReporting(this._getOOODates.bind(this), "getOOODates");
    this.getUsersWithCredentials = withReporting(
      this._getUsersWithCredentials.bind(this),
      "getUsersWithCredentials"
    );
    this.getRegularOrDynamicEventType = withReporting(
      this._getRegularOrDynamicEventType.bind(this),
      "getRegularOrDynamicEventType"
    );
    this.getAvailableSlots = withReporting(
      withSlotsCache(this.dependencies.redisClient, this._getAvailableSlots.bind(this)),
      "getAvailableSlots"
    );
  }
  async _getReservedSlotsAndCleanupExpired({
    bookerClientUid,
    usersWithCredentials,
    eventTypeId
  }) {
    const currentTimeInUtc = dayjs.utc().format();
    const slotsRepo = this.dependencies.selectedSlotRepo;
    const unexpiredSelectedSlots = await slotsRepo.findManyUnexpiredSlots({
      userIds: usersWithCredentials.map((user) => user.id),
      currentTimeInUtc
    }) || [];
    const slotsSelectedByOtherUsers = unexpiredSelectedSlots.filter((slot) => slot.uid !== bookerClientUid);
    await _cleanupExpiredSlots({ eventTypeId });
    const reservedSlots = slotsSelectedByOtherUsers;
    return reservedSlots;
    async function _cleanupExpiredSlots({ eventTypeId: eventTypeId2 }) {
      await slotsRepo.deleteManyExpiredSlots({ eventTypeId: eventTypeId2, currentTimeInUtc });
    }
  }
  async _getDynamicEventType(input, organizationDetails) {
    const { currentOrgDomain, isValidOrgDomain } = organizationDetails;
    if (!input.eventTypeSlug) {
      throw new Error("Event type slug is required in dynamic booking.");
    }
    const dynamicEventType = getDefaultEvent(input.eventTypeSlug);
    const userRepo = this.dependencies.userRepo;
    const usersForDynamicEventType = await userRepo.findManyUsersForDynamicEventType({
      currentOrgDomain: isValidOrgDomain ? currentOrgDomain : null,
      usernameList: Array.isArray(input.usernameList) ? input.usernameList : input.usernameList ? [input.usernameList] : []
    });
    const usersWithOldSelectedCalendars = usersForDynamicEventType.map((user) => withSelectedCalendars(user));
    const isDynamicAllowed = !usersWithOldSelectedCalendars.some((user) => !user.allowDynamicBooking);
    if (!isDynamicAllowed) {
      throw new TRPCError({
        message: "Some of the users in this group do not allow dynamic booking",
        code: "UNAUTHORIZED"
      });
    }
    return Object.assign({}, dynamicEventType, {
      users: usersWithOldSelectedCalendars
    });
  }
  applyOccupiedSeatsToCurrentSeats(currentSeats, occupiedSeats) {
    const occupiedSeatsMap = /* @__PURE__ */ new Map();
    occupiedSeats.forEach((item) => {
      const dateKey = item.slotUtcStartDate.toISOString();
      occupiedSeatsMap.set(dateKey, (occupiedSeatsMap.get(dateKey) || 0) + 1);
    });
    occupiedSeatsMap.forEach((count, date) => {
      currentSeats.push({
        uid: v4(),
        startTime: dayjs(date).toDate(),
        _count: { attendees: count }
      });
    });
    return currentSeats;
  }
  async _getEventType(input, organizationDetails) {
    const { eventTypeSlug, usernameList, isTeamEvent } = input;
    log.info(
      "getEventType",
      safeStringify({ usernameList, eventTypeSlug, isTeamEvent, organizationDetails })
    );
    const eventTypeId = input.eventTypeId || // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    await this.getEventTypeId({
      slug: usernameList?.[0],
      eventTypeSlug,
      isTeamEvent,
      organizationDetails
    });
    if (!eventTypeId) {
      return null;
    }
    const eventTypeRepo = this.dependencies.eventTypeRepo;
    return await eventTypeRepo.findForSlots({ id: eventTypeId });
  }
  doesRangeStartFromToday(periodType) {
    return periodType === indexBrowser.PeriodType.ROLLING_WINDOW || periodType === indexBrowser.PeriodType.ROLLING;
  }
  _getAllDatesWithBookabilityStatus(availableDates) {
    const availableDatesSet = new Set(availableDates);
    const firstDate = dayjs(availableDates[0]);
    const lastDate = dayjs(availableDates[availableDates.length - 1]);
    const allDates = {};
    let currentDate = firstDate;
    while (currentDate <= lastDate) {
      allDates[currentDate.format("YYYY-MM-DD")] = {
        isBookable: availableDatesSet.has(currentDate.format("YYYY-MM-DD"))
      };
      currentDate = currentDate.add(1, "day");
    }
    return allDates;
  }
  async getUserIdFromUsername(username, organizationDetails) {
    const { currentOrgDomain, isValidOrgDomain } = organizationDetails;
    log.info("getUserIdFromUsername", safeStringify({ organizationDetails, username }));
    const userRepo = this.dependencies.userRepo;
    const [user] = await userRepo.findUsersByUsername({
      usernameList: [username],
      orgSlug: isValidOrgDomain ? currentOrgDomain : null
    });
    return user?.id;
  }
  async getEventTypeId({
    slug,
    eventTypeSlug,
    isTeamEvent,
    organizationDetails
  }) {
    if (!eventTypeSlug || !slug)
      return null;
    let teamId;
    let userId;
    if (isTeamEvent) {
      teamId = await this.getTeamIdFromSlug(
        slug,
        organizationDetails ?? { currentOrgDomain: null, isValidOrgDomain: false }
      );
    } else {
      userId = await this.getUserIdFromUsername(
        slug,
        organizationDetails ?? { currentOrgDomain: null, isValidOrgDomain: false }
      );
    }
    const eventTypeRepo = this.dependencies.eventTypeRepo;
    const eventType = await eventTypeRepo.findFirstEventTypeId({ slug: eventTypeSlug, teamId, userId });
    if (!eventType) {
      throw new TRPCError({ code: "NOT_FOUND" });
    }
    return eventType?.id;
  }
  async getTeamIdFromSlug(slug, organizationDetails) {
    const { currentOrgDomain, isValidOrgDomain } = organizationDetails;
    const teamRepo = this.dependencies.teamRepo;
    const team = await teamRepo.findFirstBySlugAndParentSlug({
      slug,
      parentSlug: isValidOrgDomain && currentOrgDomain ? currentOrgDomain : null,
      select: { id: true }
    });
    return team?.id;
  }
  async _getBusyTimesFromLimitsForUsers(users, bookingLimits, durationLimits, dateFrom, dateTo, duration, eventType, timeZone, rescheduleUid) {
    const userBusyTimesMap = /* @__PURE__ */ new Map();
    if (!bookingLimits && !durationLimits) {
      return userBusyTimesMap;
    }
    const { limitDateFrom, limitDateTo } = this.dependencies.busyTimesService.getStartEndDateforLimitCheck(
      dateFrom.toISOString(),
      dateTo.toISOString(),
      bookingLimits || durationLimits
    );
    const busyTimesFromLimitsBookings = await this.dependencies.busyTimesService.getBusyTimesForLimitChecks({
      userIds: users.map((user) => user.id),
      eventTypeId: eventType.id,
      startDate: limitDateFrom.format(),
      endDate: limitDateTo.format(),
      rescheduleUid,
      bookingLimits,
      durationLimits
    });
    const globalLimitManager = new LimitManager();
    if (bookingLimits) {
      for (const key of descendingLimitKeys) {
        const limit = bookingLimits?.[key];
        if (!limit)
          continue;
        const unit = intervalLimitKeyToUnit(key);
        const periodStartDates = this.dependencies.userAvailabilityService.getPeriodStartDatesBetween(
          dateFrom,
          dateTo,
          unit,
          timeZone
        );
        for (const periodStart of periodStartDates) {
          if (globalLimitManager.isAlreadyBusy(periodStart, unit, timeZone))
            continue;
          const periodEnd = periodStart.endOf(unit);
          let totalBookings = 0;
          for (const booking of busyTimesFromLimitsBookings) {
            if (!isBookingWithinPeriod(booking, periodStart, periodEnd, timeZone)) {
              continue;
            }
            totalBookings++;
            if (totalBookings >= limit) {
              globalLimitManager.addBusyTime(periodStart, unit, timeZone);
              break;
            }
          }
        }
      }
    }
    for (const user of users) {
      const userBookings = busyTimesFromLimitsBookings.filter((booking) => booking.userId === user.id);
      const limitManager = new LimitManager();
      limitManager.mergeBusyTimes(globalLimitManager);
      if (bookingLimits) {
        for (const key of descendingLimitKeys) {
          const limit = bookingLimits?.[key];
          if (!limit)
            continue;
          const unit = intervalLimitKeyToUnit(key);
          const periodStartDates = this.dependencies.userAvailabilityService.getPeriodStartDatesBetween(
            dateFrom,
            dateTo,
            unit,
            timeZone
          );
          for (const periodStart of periodStartDates) {
            if (limitManager.isAlreadyBusy(periodStart, unit, timeZone))
              continue;
            if (unit === "year") {
              try {
                await this.dependencies.checkBookingLimitsService.checkBookingLimit({
                  eventStartDate: periodStart.toDate(),
                  limitingNumber: limit,
                  eventId: eventType.id,
                  key,
                  user,
                  rescheduleUid,
                  timeZone
                });
              } catch (_) {
                limitManager.addBusyTime(periodStart, unit, timeZone);
                if (periodStartDates.every((start) => limitManager.isAlreadyBusy(start, unit, timeZone))) {
                  break;
                }
              }
              continue;
            }
            const periodEnd = periodStart.endOf(unit);
            let totalBookings = 0;
            for (const booking of userBookings) {
              if (!isBookingWithinPeriod(booking, periodStart, periodEnd, timeZone)) {
                continue;
              }
              totalBookings++;
              if (totalBookings >= limit) {
                limitManager.addBusyTime(periodStart, unit, timeZone);
                break;
              }
            }
          }
        }
      }
      if (durationLimits) {
        for (const key of descendingLimitKeys) {
          const limit = durationLimits?.[key];
          if (!limit)
            continue;
          const unit = intervalLimitKeyToUnit(key);
          const periodStartDates = this.dependencies.userAvailabilityService.getPeriodStartDatesBetween(
            dateFrom,
            dateTo,
            unit,
            timeZone
          );
          for (const periodStart of periodStartDates) {
            if (limitManager.isAlreadyBusy(periodStart, unit, timeZone))
              continue;
            const selectedDuration = (duration || eventType.length) ?? 0;
            if (selectedDuration > limit) {
              limitManager.addBusyTime(periodStart, unit, timeZone);
              continue;
            }
            if (unit === "year") {
              const totalYearlyDuration = await this.dependencies.bookingRepo.getTotalBookingDuration({
                eventId: eventType.id,
                startDate: periodStart.toDate(),
                endDate: periodStart.endOf(unit).toDate(),
                rescheduleUid
              });
              if (totalYearlyDuration + selectedDuration > limit) {
                limitManager.addBusyTime(periodStart, unit, timeZone);
                if (periodStartDates.every((start) => limitManager.isAlreadyBusy(start, unit, timeZone))) {
                  break;
                }
              }
              continue;
            }
            const periodEnd = periodStart.endOf(unit);
            let totalDuration = selectedDuration;
            for (const booking of userBookings) {
              if (!isBookingWithinPeriod(booking, periodStart, periodEnd, timeZone)) {
                continue;
              }
              totalDuration += dayjs(booking.end).diff(dayjs(booking.start), "minute");
              if (totalDuration > limit) {
                limitManager.addBusyTime(periodStart, unit, timeZone);
                break;
              }
            }
          }
        }
      }
      userBusyTimesMap.set(user.id, limitManager.getBusyTimes());
    }
    return userBusyTimesMap;
  }
  async _getBusyTimesFromTeamLimitsForUsers(users, bookingLimits, dateFrom, dateTo, teamId, includeManagedEvents, timeZone, rescheduleUid) {
    const { limitDateFrom, limitDateTo } = this.dependencies.busyTimesService.getStartEndDateforLimitCheck(
      dateFrom.toISOString(),
      dateTo.toISOString(),
      bookingLimits
    );
    const bookingRepo = this.dependencies.bookingRepo;
    const bookings = await bookingRepo.getAllAcceptedTeamBookingsOfUsers({
      users,
      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 globalLimitManager = new LimitManager();
    for (const key of descendingLimitKeys) {
      const limit = bookingLimits?.[key];
      if (!limit)
        continue;
      const unit = intervalLimitKeyToUnit(key);
      const periodStartDates = this.dependencies.userAvailabilityService.getPeriodStartDatesBetween(
        dateFrom,
        dateTo,
        unit,
        timeZone
      );
      for (const periodStart of periodStartDates) {
        if (globalLimitManager.isAlreadyBusy(periodStart, unit, timeZone))
          continue;
        const periodEnd = periodStart.endOf(unit);
        let totalBookings = 0;
        for (const booking of busyTimes) {
          if (!isBookingWithinPeriod(booking, periodStart, periodEnd, timeZone)) {
            continue;
          }
          totalBookings++;
          if (totalBookings >= limit) {
            globalLimitManager.addBusyTime(periodStart, unit, timeZone);
            break;
          }
        }
      }
    }
    const userBusyTimesMap = /* @__PURE__ */ new Map();
    for (const user of users) {
      const userBusyTimes = busyTimes.filter((busyTime) => busyTime.userId === user.id);
      const limitManager = new LimitManager();
      limitManager.mergeBusyTimes(globalLimitManager);
      for (const key of descendingLimitKeys) {
        const limit = bookingLimits?.[key];
        if (!limit)
          continue;
        const unit = intervalLimitKeyToUnit(key);
        const periodStartDates = this.dependencies.userAvailabilityService.getPeriodStartDatesBetween(
          dateFrom,
          dateTo,
          unit,
          timeZone
        );
        for (const periodStart of periodStartDates) {
          if (limitManager.isAlreadyBusy(periodStart, unit, timeZone))
            continue;
          if (unit === "year") {
            try {
              await this.dependencies.checkBookingLimitsService.checkBookingLimit({
                eventStartDate: periodStart.toDate(),
                limitingNumber: limit,
                key,
                teamId,
                user,
                rescheduleUid,
                includeManagedEvents,
                timeZone
              });
            } catch (_) {
              limitManager.addBusyTime(periodStart, unit, timeZone);
              if (periodStartDates.every((start) => limitManager.isAlreadyBusy(start, unit, timeZone))) {
                return;
              }
            }
            continue;
          }
          const periodEnd = periodStart.endOf(unit);
          let totalBookings = 0;
          for (const booking of userBusyTimes) {
            if (!isBookingWithinPeriod(booking, periodStart, periodEnd, timeZone)) {
              continue;
            }
            totalBookings++;
            if (totalBookings >= limit) {
              limitManager.addBusyTime(periodStart, unit, timeZone);
              break;
            }
          }
        }
      }
      userBusyTimesMap.set(user.id, limitManager.getBusyTimes());
    }
    return userBusyTimesMap;
  }
  async _getOOODates(startTimeDate, endTimeDate, allUserIds) {
    return this.dependencies.oooRepo.findManyOOO({ startTimeDate, endTimeDate, allUserIds });
  }
  _getUsersWithCredentials({
    hosts
  }) {
    return hosts.map(({ isFixed, user }) => ({ isFixed, ...user }));
  }
  getStartTime(startTimeInput, timeZone, minimumBookingNotice) {
    const startTimeMin = dayjs.utc().add(minimumBookingNotice || 1, "minutes");
    const startTime = timeZone === "Etc/GMT" ? dayjs.utc(startTimeInput) : dayjs(startTimeInput).tz(timeZone);
    return startTimeMin.isAfter(startTime) ? startTimeMin.tz(timeZone) : startTime;
  }
  async calculateHostsAndAvailabilities({
    input,
    eventType,
    hosts,
    loggerWithEventDetails,
    startTime,
    endTime,
    bypassBusyCalendarTimes,
    shouldServeCache
  }) {
    const usersWithCredentials = this.getUsersWithCredentials({
      hosts
    });
    loggerWithEventDetails.debug("Using users", {
      usersWithCredentials: usersWithCredentials.map((user) => user.email)
    });
    const durationToUse = input.duration || 0;
    let currentSeats;
    const startTimeDate = input.rescheduleUid && durationToUse ? startTime.subtract(durationToUse, "minute").toDate() : startTime.toDate();
    const endTimeDate = input.rescheduleUid && durationToUse ? endTime.add(durationToUse, "minute").toDate() : endTime.toDate();
    const userIdAndEmailMap = new Map(usersWithCredentials.map((user) => [user.id, user.email]));
    const allUserIds = Array.from(userIdAndEmailMap.keys());
    const bookingRepo = this.dependencies.bookingRepo;
    const [currentBookingsAllUsers, outOfOfficeDaysAllUsers] = await Promise.all([
      bookingRepo.findAllExistingBookingsForEventTypeBetween({
        startDate: startTimeDate,
        endDate: endTimeDate,
        eventTypeId: eventType.id,
        seatedEvent: Boolean(eventType.seatsPerTimeSlot),
        userIdAndEmailMap
      }),
      this.getOOODates(startTimeDate, endTimeDate, allUserIds)
    ]);
    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;
    let busyTimesFromLimitsBookingsAllUsers = [];
    if (eventType && (bookingLimits || durationLimits)) {
      busyTimesFromLimitsBookingsAllUsers = await this.dependencies.busyTimesService.getBusyTimesForLimitChecks({
        userIds: allUserIds,
        eventTypeId: eventType.id,
        startDate: startTime.format(),
        endDate: endTime.format(),
        rescheduleUid: input.rescheduleUid,
        bookingLimits,
        durationLimits
      });
    }
    let busyTimesFromLimitsMap = void 0;
    if (eventType && (bookingLimits || durationLimits)) {
      const usersForLimits = usersWithCredentials.map((user) => ({ id: user.id, email: user.email }));
      busyTimesFromLimitsMap = await this.getBusyTimesFromLimitsForUsers(
        usersForLimits,
        bookingLimits,
        durationLimits,
        startTime,
        endTime,
        typeof input.duration === "number" ? input.duration : void 0,
        eventType,
        usersWithCredentials[0]?.timeZone || "UTC",
        input.rescheduleUid || void 0
      );
    }
    const teamForBookingLimits = eventType?.team ?? (eventType?.parent?.team?.includeManagedEventsInLimits ? eventType?.parent?.team : null);
    const teamBookingLimits = parseBookingLimit(teamForBookingLimits?.bookingLimits);
    let teamBookingLimitsMap = void 0;
    if (teamForBookingLimits && teamBookingLimits) {
      const usersForTeamLimits = usersWithCredentials.map((user) => ({ id: user.id, email: user.email }));
      teamBookingLimitsMap = await this.getBusyTimesFromTeamLimitsForUsers(
        usersForTeamLimits,
        teamBookingLimits,
        startTime,
        endTime,
        teamForBookingLimits.id,
        teamForBookingLimits.includeManagedEventsInLimits,
        usersWithCredentials[0]?.timeZone || "UTC",
        input.rescheduleUid || void 0
      );
    }
    function _enrichUsersWithData() {
      return usersWithCredentials.map((currentUser) => {
        return {
          ...currentUser,
          currentBookings: currentBookingsAllUsers.filter(
            (b) => b.userId === currentUser.id || b.attendees?.some((a) => a.email === currentUser.email)
          ).map((bookings) => {
            const { attendees: _attendees, ...bookingWithoutAttendees } = bookings;
            return bookingWithoutAttendees;
          }),
          outOfOfficeDays: outOfOfficeDaysAllUsers.filter((o) => o.user.id === currentUser.id)
        };
      });
    }
    const enrichUsersWithData = withReporting(_enrichUsersWithData.bind(this), "enrichUsersWithData");
    const users = enrichUsersWithData();
    const premappedUsersAvailability = await this.dependencies.userAvailabilityService.getUsersAvailability({
      users,
      query: {
        dateFrom: startTime.format(),
        dateTo: endTime.format(),
        eventTypeId: eventType.id,
        afterEventBuffer: eventType.afterEventBuffer,
        beforeEventBuffer: eventType.beforeEventBuffer,
        duration: input.duration || 0,
        returnDateOverrides: false,
        bypassBusyCalendarTimes,
        shouldServeCache
      },
      initialData: {
        eventType,
        currentSeats,
        rescheduleUid: input.rescheduleUid,
        busyTimesFromLimitsBookings: busyTimesFromLimitsBookingsAllUsers,
        busyTimesFromLimits: busyTimesFromLimitsMap,
        eventTypeForLimits: eventType && (bookingLimits || durationLimits) ? eventType : null,
        teamBookingLimits: teamBookingLimitsMap,
        teamForBookingLimits
      }
    });
    const allUsersAvailability = premappedUsersAvailability.map(
      ({ busy, dateRanges, oooExcludedDateRanges, currentSeats: _currentSeats, timeZone, datesOutOfOffice }, index) => {
        const currentUser = users[index];
        if (!currentSeats && _currentSeats)
          currentSeats = _currentSeats;
        return {
          timeZone,
          dateRanges,
          oooExcludedDateRanges,
          busy,
          user: currentUser,
          datesOutOfOffice
        };
      }
    );
    return {
      allUsersAvailability,
      usersWithCredentials,
      currentSeats
    };
  }
  async _getRegularOrDynamicEventType(input, organizationDetails) {
    const isDynamicBooking = input.usernameList && input.usernameList.length > 1;
    return isDynamicBooking ? await this.getDynamicEventType(input, organizationDetails) : await this.getEventType(input, organizationDetails);
  }
  async _getAvailableSlots({ input, ctx }) {
    const {
      _enableTroubleshooter: enableTroubleshooter = false,
      _bypassCalendarBusyTimes: bypassBusyCalendarTimes = false,
      _shouldServeCache,
      routingFormResponseId,
      queuedFormResponseId
    } = input;
    const orgDetails = input?.orgSlug ? {
      currentOrgDomain: input.orgSlug,
      isValidOrgDomain: !!input.orgSlug && !RESERVED_SUBDOMAINS.includes(input.orgSlug)
    } : orgDomainConfig(ctx?.req);
    if (process.env.INTEGRATION_TEST_MODE === "true") {
      logger.settings.minLevel = 2;
    }
    const eventType = await this.getRegularOrDynamicEventType(input, orgDetails);
    if (!eventType) {
      throw new TRPCError({ code: "NOT_FOUND" });
    }
    const shouldServeCache = await this.dependencies.cacheService.getShouldServeCache(
      _shouldServeCache,
      eventType.team?.id
    );
    if (isEventTypeLoggingEnabled({ eventTypeId: eventType.id })) {
      logger.settings.minLevel = 2;
    }
    const isRollingWindowPeriodType = eventType.periodType === indexBrowser.PeriodType.ROLLING_WINDOW;
    const startTimeAsIsoString = input.startTime;
    const isStartTimeInPast = dayjs(startTimeAsIsoString).isBefore(dayjs().subtract(1, "day").startOf("day"));
    const startTimeAdjustedForRollingWindowComputation = isStartTimeInPast || !isRollingWindowPeriodType ? startTimeAsIsoString : dayjs(startTimeAsIsoString).subtract(1, "month").toISOString();
    const loggerWithEventDetails = logger.getSubLogger({
      type: "json",
      prefix: ["getAvailableSlots", `${eventType.id}:${input.usernameList}/${input.eventTypeSlug}`]
    });
    const startTime = this.getStartTime(
      startTimeAdjustedForRollingWindowComputation,
      input.timeZone,
      eventType.minimumBookingNotice
    );
    const endTime = input.timeZone === "Etc/GMT" ? dayjs.utc(input.endTime) : dayjs(input.endTime).utc().tz(input.timeZone);
    const routedTeamMemberIds = input.routedTeamMemberIds ?? [];
    const contactOwnerEmailFromInput = input.teamMemberEmail ?? null;
    const skipContactOwner = shouldIgnoreContactOwner({
      skipContactOwner: input.skipContactOwner ?? null,
      rescheduleUid: input.rescheduleUid ?? null,
      routedTeamMemberIds: input.routedTeamMemberIds ?? null
    });
    const contactOwnerEmail = skipContactOwner ? null : contactOwnerEmailFromInput;
    let routingFormResponse = null;
    if (routingFormResponseId) {
      const formResponseRepo = this.dependencies.routingFormResponseRepo;
      routingFormResponse = await formResponseRepo.findFormResponseIncludeForm({
        routingFormResponseId
      });
    } else if (queuedFormResponseId) {
      const formResponseRepo = this.dependencies.routingFormResponseRepo;
      routingFormResponse = await formResponseRepo.findQueuedFormResponseIncludeForm({
        queuedFormResponseId
      });
    }
    const { qualifiedRRHosts, allFallbackRRHosts, fixedHosts } = await findQualifiedHostsWithDelegationCredentials({
      eventType,
      rescheduleUid: input.rescheduleUid ?? null,
      routedTeamMemberIds,
      contactOwnerEmail,
      routingFormResponse
    });
    const allHosts = [...qualifiedRRHosts, ...fixedHosts];
    const twoWeeksFromNow = dayjs().add(2, "week");
    const hasFallbackRRHosts = allFallbackRRHosts && allFallbackRRHosts.length > qualifiedRRHosts.length;
    let { allUsersAvailability, usersWithCredentials, currentSeats } = await this.calculateHostsAndAvailabilities({
      input,
      eventType,
      hosts: allHosts,
      loggerWithEventDetails,
      // adjust start time so we can check for available slots in the first two weeks
      startTime: hasFallbackRRHosts && startTime.isBefore(twoWeeksFromNow) ? this.getStartTime(dayjs().format(), input.timeZone, eventType.minimumBookingNotice) : startTime,
      // adjust end time so we can check for available slots in the first two weeks
      endTime: hasFallbackRRHosts && endTime.isBefore(twoWeeksFromNow) ? this.getStartTime(twoWeeksFromNow.format(), input.timeZone, eventType.minimumBookingNotice) : endTime,
      bypassBusyCalendarTimes,
      shouldServeCache
    });
    let aggregatedAvailability = getAggregatedAvailability(allUsersAvailability, eventType.schedulingType);
    if (hasFallbackRRHosts) {
      let diff = 0;
      if (startTime.isBefore(twoWeeksFromNow)) {
        diff = aggregatedAvailability.length > 0 ? aggregatedAvailability[0].start.diff(twoWeeksFromNow, "day") : 1;
      } else {
        if (!aggregatedAvailability.length) {
          const firstTwoWeeksAvailabilities = await this.calculateHostsAndAvailabilities({
            input,
            eventType,
            hosts: [...qualifiedRRHosts, ...fixedHosts],
            loggerWithEventDetails,
            startTime: dayjs(),
            endTime: twoWeeksFromNow,
            bypassBusyCalendarTimes,
            shouldServeCache
          });
          if (!getAggregatedAvailability(
            firstTwoWeeksAvailabilities.allUsersAvailability,
            eventType.schedulingType
          ).length) {
            diff = 1;
          }
        }
      }
      if (input.email) {
        loggerWithEventDetails.info({
          email: input.email,
          contactOwnerEmail,
          qualifiedRRHosts: qualifiedRRHosts.map((host) => host.user.id),
          fallbackRRHosts: allFallbackRRHosts.map((host) => host.user.id),
          fallBackActive: diff > 0
        });
      }
      if (diff > 0) {
        ({ allUsersAvailability, usersWithCredentials, currentSeats } = await this.calculateHostsAndAvailabilities({
          input,
          eventType,
          hosts: [...allFallbackRRHosts, ...fixedHosts],
          loggerWithEventDetails,
          startTime,
          endTime,
          bypassBusyCalendarTimes,
          shouldServeCache
        }));
        aggregatedAvailability = getAggregatedAvailability(allUsersAvailability, eventType.schedulingType);
      }
    }
    const isTeamEvent = eventType.schedulingType === SchedulingType.COLLECTIVE || eventType.schedulingType === SchedulingType.ROUND_ROBIN || allUsersAvailability.length > 1;
    const timeSlots = getSlots$1({
      inviteeDate: startTime,
      eventLength: input.duration || eventType.length,
      offsetStart: eventType.offsetStart,
      dateRanges: aggregatedAvailability,
      minimumBookingNotice: eventType.minimumBookingNotice,
      frequency: eventType.slotInterval || input.duration || eventType.length,
      datesOutOfOffice: !isTeamEvent ? allUsersAvailability[0]?.datesOutOfOffice : void 0
    });
    let availableTimeSlots = [];
    const bookerClientUid = ctx?.req?.cookies?.uid;
    const isRestrictionScheduleFeatureEnabled = await isRestrictionScheduleEnabled(eventType.team?.id);
    if (eventType.restrictionScheduleId && isRestrictionScheduleFeatureEnabled) {
      const restrictionSchedule = await this.dependencies.scheduleRepo.findScheduleByIdForBuildDateRanges({
        scheduleId: eventType.restrictionScheduleId
      });
      if (restrictionSchedule) {
        if (!eventType.useBookerTimezone && !restrictionSchedule.timeZone) {
          throw new Error("No timezone is set for the restricted schedule");
        }
        const restrictionTimezone = eventType.useBookerTimezone ? input.timeZone : restrictionSchedule.timeZone;
        const eventLength = input.duration || eventType.length;
        const restrictionAvailability = restrictionSchedule.availability.map((rule) => ({
          days: rule.days,
          startTime: rule.startTime,
          endTime: rule.endTime,
          date: rule.date
        }));
        const isDefaultSchedule = restrictionSchedule.user.defaultScheduleId === restrictionSchedule.id;
        const travelSchedules = isDefaultSchedule && !eventType.useBookerTimezone ? restrictionSchedule.user.travelSchedules.map((schedule) => ({
          startDate: dayjs(schedule.startDate),
          endDate: schedule.endDate ? dayjs(schedule.endDate) : void 0,
          timeZone: schedule.timeZone
        })) : [];
        const { dateRanges: restrictionRanges } = buildDateRanges({
          availability: restrictionAvailability,
          timeZone: restrictionTimezone || "UTC",
          dateFrom: startTime,
          dateTo: endTime,
          travelSchedules
        });
        availableTimeSlots = timeSlots.filter((slot) => {
          const slotStart = slot.time;
          const slotEnd = slot.time.add(eventLength, "minute");
          return restrictionRanges.some(
            (range) => (slotStart.isAfter(range.start) || slotStart.isSame(range.start)) && (slotEnd.isBefore(range.end) || slotEnd.isSame(range.end))
          );
        });
      } else {
        availableTimeSlots = timeSlots;
      }
    } else {
      availableTimeSlots = timeSlots;
    }
    const reservedSlots = await this._getReservedSlotsAndCleanupExpired({
      bookerClientUid,
      eventTypeId: eventType.id,
      usersWithCredentials
    });
    const availabilityCheckProps = {
      eventLength: input.duration || eventType.length,
      currentSeats
    };
    if (reservedSlots?.length > 0) {
      let occupiedSeats = reservedSlots.filter(
        (item) => item.isSeat && item.eventTypeId === eventType.id
      );
      if (occupiedSeats?.length) {
        const addedToCurrentSeats = [];
        if (typeof availabilityCheckProps.currentSeats !== "undefined") {
          availabilityCheckProps.currentSeats = availabilityCheckProps.currentSeats.map((item) => {
            const attendees = occupiedSeats.filter(
              (seat) => seat.slotUtcStartDate.toISOString() === item.startTime.toISOString()
            )?.length || 0;
            if (attendees)
              addedToCurrentSeats.push(item.startTime.toISOString());
            return {
              ...item,
              _count: {
                attendees: item._count.attendees + attendees
              }
            };
          });
          occupiedSeats = occupiedSeats.filter(
            (item) => !addedToCurrentSeats.includes(item.slotUtcStartDate.toISOString())
          );
        }
        availabilityCheckProps.currentSeats = this.applyOccupiedSeatsToCurrentSeats(
          availabilityCheckProps.currentSeats || [],
          occupiedSeats
        );
        currentSeats = availabilityCheckProps.currentSeats;
      }
      const busySlotsFromReservedSlots = reservedSlots.reduce((r, c) => {
        if (!c.isSeat) {
          r.push({ start: c.slotUtcStartDate, end: c.slotUtcEndDate });
        }
        return r;
      }, []);
      availableTimeSlots = availableTimeSlots.map((slot) => {
        if (!checkForConflicts({
          time: slot.time,
          busy: busySlotsFromReservedSlots,
          ...availabilityCheckProps
        })) {
          return slot;
        }
        return void 0;
      }).filter(
        (item) => {
          return !!item;
        }
      );
    }
    const formatter = new Intl.DateTimeFormat("fr-CA", {
      year: "numeric",
      month: "2-digit",
      day: "2-digit",
      timeZone: input.timeZone
    });
    function _mapSlotsToDate() {
      const currentSeatsMap = /* @__PURE__ */ new Map();
      if (currentSeats && currentSeats.length > 0) {
        currentSeats.forEach((booking) => {
          const timeKey = booking.startTime.toISOString();
          currentSeatsMap.set(timeKey, {
            attendees: booking._count.attendees,
            uid: booking.uid
          });
        });
      }
      return availableTimeSlots.reduce(
        (r, { time, ...passThroughProps }) => {
          const dateString = formatter.format(time.toDate());
          const timeISO = time.toISOString();
          r[dateString] = r[dateString] || [];
          if (eventType?.onlyShowFirstAvailableSlot && r[dateString].length > 0) {
            return r;
          }
          const existingBooking = currentSeatsMap.get(timeISO);
          r[dateString].push({
            ...passThroughProps,
            time: timeISO,
            ...existingBooking && {
              attendees: existingBooking.attendees,
              bookingUid: existingBooking.uid
            }
          });
          return r;
        },
        /* @__PURE__ */ Object.create(null)
      );
    }
    const mapSlotsToDate = withReporting(_mapSlotsToDate.bind(this), "mapSlotsToDate");
    const slotsMappedToDate = mapSlotsToDate();
    const availableDates = Object.keys(slotsMappedToDate);
    const allDatesWithBookabilityStatus = this.getAllDatesWithBookabilityStatus(availableDates);
    const eventTimeZone = eventType.timeZone || eventType?.schedule?.timeZone || allUsersAvailability?.[0]?.timeZone;
    const eventUtcOffset = getUTCOffsetByTimezone(eventTimeZone) ?? 0;
    const bookerUtcOffset = input.timeZone ? getUTCOffsetByTimezone(input.timeZone) ?? 0 : 0;
    const periodLimits = calculatePeriodLimits({
      periodType: eventType.periodType,
      periodDays: eventType.periodDays,
      periodCountCalendarDays: eventType.periodCountCalendarDays,
      periodStartDate: eventType.periodStartDate,
      periodEndDate: eventType.periodEndDate,
      allDatesWithBookabilityStatusInBookerTz: allDatesWithBookabilityStatus,
      eventUtcOffset,
      bookerUtcOffset
    });
    const _mapWithinBoundsSlotsToDate = () => {
      let foundAFutureLimitViolation = false;
      if (!eventType)
        throw new TRPCError({ code: "NOT_FOUND" });
      const withinBoundsSlotsMappedToDate2 = {};
      const doesStartFromToday = this.doesRangeStartFromToday(eventType.periodType);
      for (const [date, slots] of Object.entries(slotsMappedToDate)) {
        if (foundAFutureLimitViolation && doesStartFromToday) {
          break;
        }
        const filteredSlots = slots.filter((slot) => {
          const isFutureLimitViolationForTheSlot = isTimeViolatingFutureLimit({
            time: slot.time,
            periodLimits
          });
          let isOutOfBounds = false;
          try {
            isOutOfBounds = isTimeOutOfBounds({
              time: slot.time,
              minimumBookingNotice: eventType.minimumBookingNotice
            });
          } catch (error) {
            if (error instanceof BookingDateInPastError) {
              throw new TRPCError({
                code: "BAD_REQUEST",
                message: error.message
              });
            }
            throw error;
          }
          if (isFutureLimitViolationForTheSlot) {
            foundAFutureLimitViolation = true;
          }
          return !isFutureLimitViolationForTheSlot && // TODO: Perf Optimization: Slots calculation logic already seems to consider the minimum booking notice and past booking time and thus there shouldn't be need to filter out slots here.
          !isOutOfBounds;
        });
        if (filteredSlots.length) {
          withinBoundsSlotsMappedToDate2[date] = filteredSlots;
        }
      }
      return withinBoundsSlotsMappedToDate2;
    };
    const mapWithinBoundsSlotsToDate = withReporting(
      _mapWithinBoundsSlotsToDate.bind(this),
      "mapWithinBoundsSlotsToDate"
    );
    const withinBoundsSlotsMappedToDate = mapWithinBoundsSlotsToDate();
    if (!Object.keys(withinBoundsSlotsMappedToDate).length && input.usernameList?.length === 1) {
      try {
        await handleNotificationWhenNoSlots({
          eventDetails: {
            username: input.usernameList?.[0],
            startTime,
            endTime,
            eventSlug: eventType.slug
          },
          orgDetails,
          teamId: eventType.team?.id
        });
      } catch (e) {
        loggerWithEventDetails.error(
          `Something has gone wrong. Upstash could be down and we have caught the error to not block availability:
 ${e}`
        );
      }
    }
    const troubleshooterData = enableTroubleshooter ? {
      troubleshooter: {
        routedTeamMemberIds,
        // One that Salesforce asked for
        askedContactOwner: contactOwnerEmailFromInput,
        // One that we used as per Routing skipContactOwner flag
        consideredContactOwner: contactOwnerEmail,
        // All hosts that have been checked for availability. If no routedTeamMemberIds are provided, this will be same as hosts.
        routedHosts: usersWithCredentials.map((user) => {
          return {
            userId: user.id
          };
        }),
        hostsAfterSegmentMatching: allHosts.map((host) => ({
          userId: host.user.id
        }))
      }
    } : null;
    return {
      slots: withinBoundsSlotsMappedToDate,
      ...troubleshooterData
    };
  }
}
export {
  AvailableSlotsService,
  B as BusyTimesService
};
