"use strict";
const lodash = require("lodash");
const uuid = require("uuid");
const _metadata = require("./_metadata-6Vf19eEr.cjs");
const VideoApiAdapter = require("./VideoApiAdapter-IeBDs5Y2.cjs");
const index = require("./i18n-AoA-9chq.cjs");
const CalEventParser = require("./CalEventParser-6orQ9JwB.cjs");
const utils = require("./utils-y5q2Ftep.cjs");
const features_repository = require("./features.repository-UzW6TkXH.cjs");
const index$1 = require("./email-manager-YlvR5YEL.cjs");
const piiFreeData = require("./piiFreeData-FAapPrV2.cjs");
const credential = require("./credential-iqJKRnXS.cjs");
const isDurationLimits = require("./isDurationLimits-fGG53FIy.cjs");
const user = require("./user-PezNMbsP.cjs");
const conferencing = require("./conferencing.cjs");
const errorCodes = require("./errorCodes-ifTrQJQk.cjs");
const hashedLinksUtils = require("./hashedLinksUtils-5IHt1AtQ.cjs");
const membership = require("./membership-Y-zGMz5F.cjs");
const buildCredentialPayloadForCalendar = require("./buildCredentialPayloadForCalendar-ShynKeZ5.cjs");
const getEventTypeAppData = (eventType, appId, forcedGet) => {
  const metadata = eventType.metadata;
  const appMetadata = metadata?.apps && metadata.apps[appId];
  if (appMetadata) {
    const allowDataGet2 = forcedGet ? true : appMetadata.enabled;
    return allowDataGet2 ? {
      ...appMetadata,
      // We should favor eventType's price and currency over appMetadata's price and currency
      price: eventType.price || appMetadata.price || null,
      currency: eventType.currency || appMetadata.currency || null,
      // trackingId is legacy way to store value for TRACKING_ID. So, we need to support both.
      TRACKING_ID: appMetadata.TRACKING_ID || appMetadata.trackingId || null
    } : null;
  }
  const legacyAppsData = {
    stripe: {
      enabled: !!eventType.price,
      // Price default is 0 in DB. So, it would always be non nullish.
      price: eventType.price,
      // Currency default is "usd" in DB.So, it would also be available always
      currency: eventType.currency,
      paymentOption: "ON_BOOKING"
    },
    giphy: {
      enabled: !!eventType.metadata?.giphyThankYouPage,
      thankYouPage: eventType.metadata?.giphyThankYouPage || ""
    }
  };
  const legacyAppData = legacyAppsData[appId];
  const allowDataGet = forcedGet ? true : legacyAppData?.enabled;
  return allowDataGet ? legacyAppData : null;
};
const tasksConfig = {
  createCRMEvent: {
    minRetryIntervalMins: index.IS_PRODUCTION ? 10 : 1,
    maxAttempts: 10
  },
  executeAIPhoneCall: {
    maxAttempts: 1
  }
};
class CRMScheduler {
  static async createEvent({ bookingUid }) {
    return index$1.tasker.create(
      "createCRMEvent",
      { bookingUid },
      { maxAttempts: tasksConfig.createCRMEvent?.maxAttempts }
    );
  }
}
const CrmServiceMap = {
  closecom: Promise.resolve().then(() => require("./CrmService-MoxTr8cu.cjs")),
  hubspot: Promise.resolve().then(() => require("./CrmService-PWXGiCWk.cjs")),
  "pipedrive-crm": Promise.resolve().then(() => require("./CrmService-F4xmhdXl.cjs")),
  salesforce: Promise.resolve().then(() => require("./CrmService-mSf74CfQ.cjs")),
  "zoho-bigin": Promise.resolve().then(() => require("./CrmService-T7Z2xA-7.cjs")),
  zohocrm: Promise.resolve().then(() => require("./CrmService-FCrsxtmY.cjs"))
};
const log$3 = index.logger.getSubLogger({ prefix: ["CrmManager"] });
const getCrm = async (credential2, appOptions) => {
  if (!credential2 || !credential2.key)
    return null;
  const { type: crmType } = credential2;
  const crmName = crmType.split("_")[0];
  const crmServiceImportFn = await CrmServiceMap[crmName];
  if (!crmServiceImportFn) {
    log$3.warn(`crm of type ${crmType} is not implemented`);
    return null;
  }
  const CrmService = crmServiceImportFn.default;
  if (!CrmService) {
    log$3.warn(`crm of type ${crmType} is not implemented`);
    return null;
  }
  return new CrmService(credential2, appOptions);
};
const getCrm$1 = getCrm;
const log$2 = index.logger.getSubLogger({ prefix: ["CrmManager"] });
class CrmManager {
  constructor(credential2, appOptions) {
    this.crmService = null;
    this.credential = credential2;
    this.appOptions = appOptions;
  }
  async getCrmService(credential2) {
    if (this.crmService)
      return this.crmService;
    const crmService = await getCrm$1(credential2, this.appOptions);
    this.crmService = crmService;
    if (!this.crmService) {
      console.log("💀 Error initializing CRM service");
      log$2.error("CRM service initialization failed");
    }
    return crmService;
  }
  async createEvent(event) {
    const crmService = await this.getCrmService(this.credential);
    if (!crmService)
      return;
    const { skipContactCreation = false, ignoreGuests = false } = crmService.getAppOptions() || {};
    const eventAttendees = ignoreGuests ? [event.attendees[0]] : event.attendees;
    let contacts = await this.getContacts({ emails: eventAttendees.map((a) => a.email) }) || [];
    if (contacts.length == eventAttendees.length) {
      return await crmService.createEvent(event, contacts);
    }
    if (skipContactCreation)
      return;
    const contactSet = new Set(contacts.map((c) => c.email));
    const contactsToCreate = eventAttendees.filter((attendee) => !contactSet.has(attendee.email));
    const createdContacts = await this.createContacts(
      contactsToCreate,
      event.organizer?.email,
      event.responses
    );
    contacts = contacts.concat(createdContacts);
    return await crmService.createEvent(event, contacts);
  }
  async updateEvent(uid, event) {
    const crmService = await this.getCrmService(this.credential);
    return await crmService?.updateEvent(uid, event);
  }
  async deleteEvent(uid, event) {
    const crmService = await this.getCrmService(this.credential);
    return await crmService?.deleteEvent(uid, event);
  }
  async getContacts(params) {
    const crmService = await this.getCrmService(this.credential);
    const contacts = await crmService?.getContacts(params);
    return contacts;
  }
  async createContacts(contactsToCreate, organizerEmail, calEventResponses) {
    const crmService = await this.getCrmService(this.credential);
    const createdContacts = await crmService?.createContacts(contactsToCreate, organizerEmail, calEventResponses) || [];
    return createdContacts;
  }
  async handleAttendeeNoShow(bookingUid, attendees) {
    const crmService = await this.getCrmService(this.credential);
    if (crmService?.handleAttendeeNoShow) {
      await crmService.handleAttendeeNoShow(bookingUid, attendees);
    }
  }
}
const log$1 = index.logger.getSubLogger({ prefix: ["EventManager"] });
const CALENDSO_ENCRYPTION_KEY = process.env.CALENDSO_ENCRYPTION_KEY || "";
const CALDAV_CALENDAR_TYPE = "caldav_calendar";
const isDedicatedIntegration = (location) => {
  return location !== CalEventParser.MeetLocationType && location.includes("integrations:");
};
const latestCredentialFirst = (a, b) => {
  return b.id - a.id;
};
const delegatedCredentialFirst = (a, b) => {
  return (b.delegatedToId ? 1 : 0) - (a.delegatedToId ? 1 : 0);
};
const getLocationRequestFromIntegration = (location) => {
  const eventLocationType = CalEventParser.getLocationFromApp(location);
  if (eventLocationType) {
    const requestId = uuid.v5(location, uuid.v5.URL);
    return {
      conferenceData: {
        createRequest: {
          requestId
        }
      },
      location
    };
  }
  return null;
};
const getCredential = ({
  id,
  allCredentials
}) => {
  return id.delegationCredentialId ? allCredentials.find((c) => c.delegatedToId === id.delegationCredentialId) : allCredentials.find((c) => c.id === id.credentialId);
};
const processLocation = (event) => {
  if (event.location?.includes("integration")) {
    const maybeLocationRequestObject = getLocationRequestFromIntegration(event.location);
    event = lodash.merge(event, maybeLocationRequestObject);
  }
  return event;
};
function getCredentialPayload(result) {
  return {
    credentialId: result?.credentialId && result.credentialId > 0 ? result.credentialId : void 0,
    delegationCredentialId: result?.delegatedToId || void 0
  };
}
class EventManager {
  /**
   * Takes an array of credentials and initializes a new instance of the EventManager.
   *
   * @param user
   */
  constructor(user2, eventTypeAppMetadata) {
    log$1.silly("Initializing EventManager", index.safeStringify({ user: piiFreeData.getPiiFreeUser(user2) }));
    const appCredentials = utils.getApps(user2.credentials, true).flatMap(
      (app) => app.credentials.map((creds) => ({ ...creds, appName: app.name }))
    );
    this.calendarCredentials = appCredentials.filter(
      // Backwards compatibility until CRM manager is implemented
      (cred) => cred.type.endsWith("_calendar") && !cred.type.includes("other_calendar")
    ).sort(latestCredentialFirst).sort(delegatedCredentialFirst);
    this.videoCredentials = appCredentials.filter((cred) => cred.type.endsWith("_video") || cred.type.endsWith("_conferencing")).sort(latestCredentialFirst);
    this.crmCredentials = appCredentials.filter(
      (cred) => cred.type.endsWith("_crm") || cred.type.endsWith("_other_calendar")
    );
    this.appOptions = eventTypeAppMetadata;
  }
  extractServerUrlFromCredential(credential2) {
    try {
      if (credential2.type !== CALDAV_CALENDAR_TYPE) {
        return null;
      }
      const decryptedData = JSON.parse(index.symmetricDecrypt(credential2.key, CALENDSO_ENCRYPTION_KEY));
      if (!decryptedData.url) {
        return null;
      }
      const url = new URL(decryptedData.url);
      return `${url.protocol}//${url.host}`;
    } catch (error) {
      log$1.warn("Failed to extract server URL from CalDAV credential", {
        credentialId: credential2.id,
        error: error instanceof Error ? error.message : "Unknown error"
      });
      return null;
    }
  }
  extractServerUrlFromDestination(destination) {
    try {
      if (destination.integration !== CALDAV_CALENDAR_TYPE || !destination.externalId) {
        return null;
      }
      const url = new URL(destination.externalId);
      return `${url.protocol}//${url.host}`;
    } catch (error) {
      log$1.warn("Failed to extract server URL from destination calendar", {
        destinationId: destination.id,
        externalId: destination.externalId,
        error: error instanceof Error ? error.message : "Unknown error"
      });
      return null;
    }
  }
  credentialMatchesDestination(credential2, destination) {
    if (credential2.type !== CALDAV_CALENDAR_TYPE || destination.integration !== CALDAV_CALENDAR_TYPE) {
      return true;
    }
    const credentialServerUrl = this.extractServerUrlFromCredential(credential2);
    const destinationServerUrl = this.extractServerUrlFromDestination(destination);
    if (!credentialServerUrl || !destinationServerUrl) {
      log$1.warn("Could not extract server URLs for CalDAV credential validation", {
        credentialId: credential2.id,
        destinationId: destination.id,
        credentialServerUrl,
        destinationServerUrl
      });
      return false;
    }
    const matches = credentialServerUrl === destinationServerUrl;
    if (!matches) {
      log$1.warn("CalDAV credential server URL does not match destination calendar server URL", {
        credentialId: credential2.id,
        destinationId: destination.id,
        credentialServerUrl,
        destinationServerUrl
      });
    }
    return matches;
  }
  updateMSTeamsVideoCallData(evt, results) {
    const office365CalendarWithTeams = results.find(
      (result) => result.type === "office365_calendar" && result.success && result.createdEvent?.url
    );
    if (office365CalendarWithTeams) {
      evt.videoCallData = {
        type: "office365_video",
        id: office365CalendarWithTeams.createdEvent?.id,
        password: "",
        url: office365CalendarWithTeams.createdEvent?.url
      };
      if (evt.location && evt.responses) {
        evt.responses["location"] = {
          ...evt.responses["location"] ?? {},
          value: {
            optionValue: "",
            value: evt.location
          }
        };
      }
    }
  }
  /**
   * Takes a CalendarEvent and creates all necessary integration entries for it.
   * When a video integration is chosen as the event's location, a video integration
   * event will be scheduled for it as well.
   *
   * @param event
   */
  async create(event) {
    const evt = processLocation(event);
    if (!evt.location) {
      const calVideo = await index.prisma.app.findUnique({
        where: {
          slug: "daily-video"
        },
        select: {
          keys: true,
          enabled: true
        }
      });
      const calVideoKeys = index.appKeysSchema$3.safeParse(calVideo?.keys);
      if (calVideo?.enabled && calVideoKeys.success)
        evt["location"] = "integrations:daily";
      log$1.warn("Falling back to cal video as no location is set");
    }
    const [mainHostDestinationCalendar] = evt.destinationCalendar ?? [];
    if (evt.location === CalEventParser.MeetLocationType && mainHostDestinationCalendar?.integration !== "google_calendar") {
      const [googleCalendarCredential] = this.calendarCredentials.filter(
        (cred) => cred.type === "google_calendar"
      );
      if (!user.isDelegationCredential({ credentialId: googleCalendarCredential?.id })) {
        log$1.warn(
          "Falling back to Cal Video integration for Regular Credential as Google Calendar is not set as destination calendar"
        );
        evt["location"] = "integrations:daily";
        evt["conferenceCredentialId"] = void 0;
      }
    }
    const isDedicated = evt.location ? isDedicatedIntegration(evt.location) : null;
    const isMSTeamsWithOutlookCalendar = evt.location === CalEventParser.MSTeamsLocationType$1 && mainHostDestinationCalendar?.integration === "office365_calendar";
    const results = [];
    if (isDedicated && !isMSTeamsWithOutlookCalendar) {
      const result = await this.createVideoEvent(evt);
      if (result?.createdEvent) {
        evt.videoCallData = result.createdEvent;
        evt.location = result.originalEvent.location;
        result.type = result.createdEvent.type;
        if (evt.location && evt.responses) {
          evt.responses["location"] = {
            ...evt.responses["location"] ?? {},
            value: {
              optionValue: "",
              value: evt.location
            }
          };
        }
      }
      results.push(result);
    }
    const clonedCalEvent = lodash.cloneDeep(event);
    results.push(...await this.createAllCalendarEvents(clonedCalEvent));
    if (evt.location === CalEventParser.MSTeamsLocationType$1) {
      this.updateMSTeamsVideoCallData(evt, results);
    }
    const isCalendarResult = (result) => {
      return result.type.includes("_calendar");
    };
    const createdCRMEvents = await this.createAllCRMEvents(evt);
    results.push(...createdCRMEvents);
    const referencesToCreate = results.map((result) => {
      let thirdPartyRecurringEventId;
      let createdEventObj = null;
      if (typeof result?.createdEvent === "string") {
        createdEventObj = index.createdEventSchema.parse(JSON.parse(result.createdEvent));
      }
      const isCalendarType = isCalendarResult(result);
      if (isCalendarType) {
        evt.iCalUID = result.iCalUID || event.iCalUID || void 0;
        thirdPartyRecurringEventId = result.createdEvent?.thirdPartyRecurringEventId;
      }
      return {
        type: result.type,
        uid: createdEventObj ? createdEventObj.id : result.createdEvent?.id?.toString() ?? "",
        thirdPartyRecurringEventId: isCalendarType ? thirdPartyRecurringEventId : void 0,
        meetingId: createdEventObj ? createdEventObj.id : result.createdEvent?.id?.toString(),
        meetingPassword: createdEventObj ? createdEventObj.password : result.createdEvent?.password,
        meetingUrl: createdEventObj ? createdEventObj.onlineMeetingUrl : result.createdEvent?.url,
        externalCalendarId: isCalendarType ? result.externalId : void 0,
        ...getCredentialPayload(result)
      };
    });
    return {
      results,
      referencesToCreate
    };
  }
  async updateLocation(event, booking) {
    const evt = processLocation(event);
    const isDedicated = evt.location ? isDedicatedIntegration(evt.location) : null;
    const results = [];
    if (isDedicated) {
      const result = await this.createVideoEvent(evt);
      if (result.createdEvent) {
        evt.videoCallData = result.createdEvent;
        evt.location = result.originalEvent.location;
        result.type = result.createdEvent.type;
        if (evt.location && evt.responses) {
          evt.responses["location"] = {
            ...evt.responses["location"] ?? {},
            value: {
              optionValue: "",
              value: evt.location
            }
          };
        }
      }
      results.push(result);
    }
    const calendarReference = booking.references.find((reference) => reference.type.includes("_calendar"));
    if (calendarReference) {
      results.push(...await this.updateAllCalendarEvents(evt, booking));
      if (evt.location === CalEventParser.MSTeamsLocationType$1) {
        this.updateMSTeamsVideoCallData(evt, results);
      }
    }
    const referencesToCreate = results.map((result) => {
      const updatedEvent = Array.isArray(result.updatedEvent) ? result.updatedEvent[0] : result.updatedEvent;
      const createdEvent = result.createdEvent;
      let event2 = updatedEvent;
      if (!event2) {
        log$1.warn(
          "updateLocation: No updatedEvent when doing updateLocation. Falling back to createdEvent but this is probably not what we want",
          index.safeStringify({ bookingId: booking.id })
        );
        event2 = createdEvent;
      }
      const uid = event2?.id?.toString() ?? "";
      const meetingId = event2?.id?.toString();
      if (!uid) {
        log$1.error(
          "updateLocation: No uid for booking reference. The corresponding record in third party if created is orphan now",
          index.safeStringify({ result })
        );
      }
      return {
        type: result.type,
        uid,
        meetingId,
        meetingPassword: event2?.password,
        meetingUrl: event2?.url,
        externalCalendarId: result.externalId,
        ...result.credentialId && result.credentialId > 0 ? { credentialId: result.credentialId } : {}
      };
    });
    return {
      results,
      referencesToCreate
    };
  }
  async deleteCalendarEventForBookingReference({
    reference,
    event,
    isBookingInRecurringSeries
  }) {
    log$1.debug(
      "deleteCalendarEventForBookingReference",
      index.safeStringify({ bookingCalendarReference: reference, event: piiFreeData.getPiiFreeCalendarEvent(event) })
    );
    const {
      // uid: bookingRefUid,
      externalCalendarId: bookingExternalCalendarId,
      credentialId,
      type: credentialType
    } = reference;
    const bookingRefUid = isBookingInRecurringSeries && reference?.thirdPartyRecurringEventId ? reference.thirdPartyRecurringEventId : reference.uid;
    const calendarCredential = await this.getCredentialAndWarnIfNotFound(
      credentialId,
      this.calendarCredentials,
      credentialType,
      reference.delegationCredentialId
    );
    if (calendarCredential) {
      await isDurationLimits.deleteEvent({
        credential: calendarCredential,
        bookingRefUid,
        event,
        externalCalendarId: bookingExternalCalendarId
      });
    }
  }
  async deleteVideoEventForBookingReference({ reference }) {
    log$1.debug("deleteVideoEventForBookingReference", index.safeStringify({ bookingVideoReference: reference }));
    const { uid: bookingRefUid, credentialId } = reference;
    const videoCredential = await this.getCredentialAndWarnIfNotFound(
      credentialId,
      this.videoCredentials,
      reference.type
    );
    if (videoCredential) {
      await conferencing.deleteMeeting(videoCredential, bookingRefUid);
    }
  }
  async getCredentialAndWarnIfNotFound(credentialId, credentials, type, delegationCredentialId) {
    if (delegationCredentialId) {
      return this.calendarCredentials.find((cred) => cred.delegatedToId === delegationCredentialId);
    }
    const credential$1 = credentials.find((cred) => cred.id === credentialId);
    if (credential$1) {
      return credential$1;
    } else {
      const credential2 = typeof credentialId === "number" && credentialId > 0 ? await credential.CredentialRepository.findCredentialForCalendarServiceById({ id: credentialId }) : (
        // Fallback for zero or nullish credentialId which could be the case of Global App e.g. dailyVideo
        this.videoCredentials.find((cred) => cred.type === type) || this.calendarCredentials.find((cred) => cred.type === type) || null
      );
      if (!credential2) {
        log$1.error(
          "getCredentialAndWarnIfNotFound: Could not find credential",
          index.safeStringify({
            credentialId,
            type,
            videoCredentials: this.videoCredentials
          })
        );
      }
      return credential2;
    }
  }
  /**
   * Takes a calendarEvent and a rescheduleUid and updates the event that has the
   * given uid using the data delivered in the given CalendarEvent.
   *
   * @param event
   */
  async reschedule(event, rescheduleUid, newBookingId, changedOrganizer, previousHostDestinationCalendar, isBookingRequestedReschedule, skipDeleteEventsAndMeetings) {
    const originalEvt = processLocation(event);
    const evt = lodash.cloneDeep(originalEvt);
    if (!rescheduleUid) {
      throw new Error("You called eventManager.update without an `rescheduleUid`. This should never happen.");
    }
    const booking = await index.prisma.booking.findUnique({
      where: {
        uid: rescheduleUid
      },
      select: {
        id: true,
        userId: true,
        attendees: true,
        location: true,
        endTime: true,
        references: {
          where: {
            deleted: null
          },
          // NOTE: id field removed from select as we don't require for deletingMany
          // but was giving error on recreate for reschedule, probably because promise.all() didn't finished
          select: {
            type: true,
            uid: true,
            meetingId: true,
            meetingPassword: true,
            meetingUrl: true,
            externalCalendarId: true,
            credentialId: true
          }
        },
        destinationCalendar: true,
        payment: true,
        eventType: {
          select: {
            seatsPerTimeSlot: true,
            seatsShowAttendees: true,
            seatsShowAvailabilityCount: true
          }
        }
      }
    });
    if (!booking) {
      throw new Error("booking not found");
    }
    const results = [];
    const updatedBookingReferences = [];
    const isLocationChanged = !!evt.location && !!booking.location && evt.location !== booking.location;
    let isDailyVideoRoomExpired = false;
    if (evt.location === "integrations:daily") {
      const originalBookingEndTime = new Date(booking.endTime);
      const roomExpiryTime = new Date(originalBookingEndTime.getTime() + 14 * 24 * 60 * 60 * 1e3);
      const now = /* @__PURE__ */ new Date();
      isDailyVideoRoomExpired = now > roomExpiryTime;
    }
    const shouldUpdateBookingReferences = !!changedOrganizer || isLocationChanged || !!isBookingRequestedReschedule || isDailyVideoRoomExpired;
    if (evt.requiresConfirmation && !skipDeleteEventsAndMeetings) {
      log$1.debug("RescheduleRequiresConfirmation: Deleting Event and Meeting for previous booking");
      await this.deleteEventsAndMeetings({
        event: { ...event, destinationCalendar: previousHostDestinationCalendar },
        bookingReferences: booking.references
      });
    } else {
      if (changedOrganizer) {
        if (!skipDeleteEventsAndMeetings) {
          log$1.debug("RescheduleOrganizerChanged: Deleting Event and Meeting for previous booking");
          await this.deleteEventsAndMeetings({
            event: { ...event, destinationCalendar: previousHostDestinationCalendar },
            bookingReferences: booking.references
          });
        }
        log$1.debug("RescheduleOrganizerChanged: Creating Event and Meeting for for new booking");
        const createdEvent = await this.create(originalEvt);
        results.push(...createdEvent.results);
        updatedBookingReferences.push(...createdEvent.referencesToCreate);
      } else {
        if (isLocationChanged || isBookingRequestedReschedule || isDailyVideoRoomExpired) {
          const updatedLocation = await this.updateLocation(evt, booking);
          results.push(...updatedLocation.results);
          updatedBookingReferences.push(...updatedLocation.referencesToCreate);
        } else {
          const isDedicated = evt.location ? isDedicatedIntegration(evt.location) : null;
          if (isDedicated) {
            const result = await this.updateVideoEvent(evt, booking);
            const [updatedEvent] = Array.isArray(result.updatedEvent) ? result.updatedEvent : [result.updatedEvent];
            if (updatedEvent) {
              evt.videoCallData = updatedEvent;
              evt.location = updatedEvent.url;
            }
            results.push(result);
          }
          const bookingCalendarReference = booking.references.find(
            (reference) => reference.type.includes("_calendar")
          );
          if (bookingCalendarReference) {
            results.push(...await this.updateAllCalendarEvents(evt, booking, newBookingId));
          }
        }
        results.push(...await this.updateAllCRMEvents(evt, booking));
      }
    }
    const bookingPayment = booking?.payment;
    if (bookingPayment && newBookingId) {
      const paymentIds = bookingPayment.map((payment) => payment.id);
      await index.prisma.payment.updateMany({
        where: {
          id: {
            in: paymentIds
          }
        },
        data: {
          bookingId: newBookingId
        }
      });
    }
    return {
      results,
      referencesToCreate: shouldUpdateBookingReferences ? updatedBookingReferences : [...booking.references]
    };
  }
  async cancelEvent(event, bookingReferences, isBookingInRecurringSeries) {
    await this.deleteEventsAndMeetings({
      event,
      bookingReferences,
      isBookingInRecurringSeries
    });
  }
  async deleteEventsAndMeetings({
    event,
    bookingReferences,
    isBookingInRecurringSeries
  }) {
    const log2 = index.logger.getSubLogger({ prefix: [`[deleteEventsAndMeetings]: ${event?.uid}`] });
    const calendarReferences = [], videoReferences = [], allPromises = [];
    for (const reference of bookingReferences) {
      if (reference.type.includes("_calendar") && !reference.type.includes("other_calendar")) {
        calendarReferences.push(reference);
        allPromises.push(
          this.deleteCalendarEventForBookingReference({
            reference,
            event,
            isBookingInRecurringSeries
          })
        );
      }
      if (reference.type.includes("_video")) {
        videoReferences.push(reference);
        allPromises.push(
          this.deleteVideoEventForBookingReference({
            reference
          })
        );
      }
      if (reference.type.includes("_crm") || reference.type.includes("other_calendar")) {
        allPromises.push(this.deleteCRMEvent({ reference, event }));
      }
    }
    log2.debug("deleteEventsAndMeetings", index.safeStringify({ calendarReferences, videoReferences }));
    (await Promise.allSettled(allPromises)).some((result) => {
      if (result.status === "rejected") {
        log2.warn(
          "Error deleting calendar event or video meeting for booking",
          index.safeStringify({ error: result.reason })
        );
      }
    });
    if (!allPromises.length) {
      log2.warn("No calendar or video references found for booking - Couldn't delete events or meetings");
    }
  }
  async updateCalendarAttendees(event, booking) {
    if (booking.references.length === 0) {
      console.error("Tried to update references but there wasn't any.");
      return;
    }
    await this.updateAllCalendarEvents(event, booking);
  }
  /**
   * Creates event entries for all calendar integrations given in the credentials.
   * When noMail is true, no mails will be sent. This is used when the event is
   * a video meeting because then the mail containing the video credentials will be
   * more important than the mails created for these bare calendar events.
   *
   * When the optional uid is set, it will be used instead of the auto generated uid.
   *
   * @param event
   * @param noMail
   * @private
   */
  async createAllCalendarEvents(event) {
    let createdEvents = [];
    const fallbackToFirstCalendarInTheList = async () => {
      const [credential2] = this.calendarCredentials.filter((cred) => !cred.type.endsWith("other_calendar"));
      if (credential2) {
        if (!user.isDelegationCredential({ credentialId: credential2.id })) {
          log$1.warn("Check the User setup, it isn't normal to fallback for regular credential.");
        }
        const createdEvent = await isDurationLimits.createEvent(credential2, event);
        log$1.silly("Created Calendar event using credential", index.safeStringify({ credential: credential2, createdEvent }));
        if (createdEvent) {
          createdEvents.push(createdEvent);
        }
      }
    };
    if (event.destinationCalendar && event.destinationCalendar.length > 0) {
      let eventCreated = false;
      let gCalAdded = false;
      const destinationCalendars = event.destinationCalendar.reduce(
        (destinationCals, cal) => {
          if (cal.integration === "google_calendar") {
            if (gCalAdded) {
              return destinationCals;
            } else {
              gCalAdded = true;
              destinationCals.push(cal);
            }
          } else {
            destinationCals.push(cal);
          }
          return destinationCals;
        },
        []
      );
      for (const destination of destinationCalendars) {
        if (eventCreated)
          break;
        log$1.silly("Creating Calendar event", JSON.stringify({ destination }));
        if (destination.credentialId || destination.delegationCredentialId) {
          let credential$1 = getCredential({
            id: {
              credentialId: destination.credentialId,
              delegationCredentialId: destination.delegationCredentialId
            },
            allCredentials: this.calendarCredentials
          });
          if (!credential$1) {
            if (destination.credentialId) {
              const credentialFromDB = await credential.CredentialRepository.findCredentialForCalendarServiceById({
                id: destination.credentialId
              });
              if (credentialFromDB && credentialFromDB.appId) {
                credential$1 = {
                  id: credentialFromDB.id,
                  type: credentialFromDB.type,
                  key: credentialFromDB.key,
                  userId: credentialFromDB.userId,
                  teamId: credentialFromDB.teamId,
                  invalid: credentialFromDB.invalid,
                  appId: credentialFromDB.appId,
                  user: credentialFromDB.user,
                  delegatedToId: credentialFromDB.delegatedToId,
                  delegatedTo: credentialFromDB.delegatedTo,
                  delegationCredentialId: credentialFromDB.delegationCredentialId
                };
              }
            } else if (destination.delegationCredentialId) {
              log$1.warn(
                "DelegationCredential: DelegationCredential seems to be disabled, falling back to first non-delegationCredential"
              );
              const firstNonDelegatedCalendarCredential = this.calendarCredentials.find(
                (cred) => !cred.type.endsWith("other_calendar") && !cred.delegatedToId
              );
              credential$1 = firstNonDelegatedCalendarCredential;
            }
          }
          if (credential$1) {
            const createdEvent = await isDurationLimits.createEvent(credential$1, event, destination.externalId);
            if (createdEvent) {
              createdEvents.push(createdEvent);
              eventCreated = true;
            }
          }
        } else {
          const destinationCalendarCredentials = this.calendarCredentials.filter((c) => {
            if (c.type !== destination.integration)
              return false;
            if (c.type === CALDAV_CALENDAR_TYPE) {
              return this.credentialMatchesDestination(c, destination);
            }
            return true;
          });
          const firstCalendarCredential = destinationCalendarCredentials[0];
          if (!firstCalendarCredential) {
            if (destination.integration === CALDAV_CALENDAR_TYPE) {
              log$1.warn(
                "No CalDAV credentials found with matching server URL for destination calendar. This prevents credential leakage.",
                index.safeStringify({
                  destination: piiFreeData.getPiiFreeDestinationCalendar(destination)
                })
              );
            } else {
              log$1.warn(
                "No other credentials found of the same type as the destination calendar. Falling back to first connected calendar"
              );
            }
            await fallbackToFirstCalendarInTheList();
          } else {
            log$1.warn(
              "No credentialId found for destination calendar, falling back to first found calendar of same type as destination calendar",
              index.safeStringify({
                destination: piiFreeData.getPiiFreeDestinationCalendar(destination),
                firstConnectedCalendar: piiFreeData.getPiiFreeCredential(firstCalendarCredential)
              })
            );
            createdEvents.push(await isDurationLimits.createEvent(firstCalendarCredential, event));
            eventCreated = true;
          }
        }
      }
    } else {
      log$1.warn(
        "No destination Calendar found, falling back to first connected calendar",
        index.safeStringify({
          calendarCredentials: this.calendarCredentials
        })
      );
      await fallbackToFirstCalendarInTheList();
    }
    createdEvents = createdEvents.concat(
      await Promise.all(
        this.calendarCredentials.filter((cred) => cred.type.includes("other_calendar")).map(async (cred) => await isDurationLimits.createEvent(cred, event))
      )
    );
    return createdEvents;
  }
  /**
   * Checks which video integration is needed for the event's location and returns
   * credentials for that - if existing.
   * @param event
   * @private
   */
  getVideoCredential(event) {
    if (!event.location) {
      return void 0;
    }
    const integrationName = event.location.replace("integrations:", "");
    let videoCredential;
    if (event.conferenceCredentialId) {
      videoCredential = this.videoCredentials.find(
        (credential2) => credential2.id === event.conferenceCredentialId
      );
    } else {
      videoCredential = this.videoCredentials.find(
        (credential2) => credential2.type.includes(integrationName)
      );
      log$1.warn(
        `Could not find conferenceCredentialId for event with location: ${event.location}, trying to use last added video credential`
      );
    }
    if (!videoCredential) {
      log$1.warn(
        `Falling back to "daily" video integration for event with location: ${event.location} because credential is missing for the app`
      );
      videoCredential = { ...VideoApiAdapter.FAKE_DAILY_CREDENTIAL };
    }
    return videoCredential;
  }
  /**
   * Creates a video event entry for the selected integration location.
   *
   * When optional uid is set, it will be used instead of the auto generated uid.
   *
   * @param event
   * @private
   */
  async createVideoEvent(event) {
    const credential2 = this.getVideoCredential(event);
    if (credential2) {
      return conferencing.createMeeting(credential2, event);
    } else {
      return Promise.reject(
        `No suitable credentials given for the requested integration name:${event.location}`
      );
    }
  }
  /**
   * Updates the event entries for all calendar integrations given in the credentials.
   * When noMail is true, no mails will be sent. This is used when the event is
   * a video meeting because then the mail containing the video credentials will be
   * more important than the mails created for these bare calendar events.
   *
   * @param event
   * @param booking
   * @private
   */
  async updateAllCalendarEvents(event, booking, newBookingId) {
    let calendarReference = void 0, credential$1;
    log$1.silly("updateAllCalendarEvents", JSON.stringify({ event, booking, newBookingId }));
    try {
      let newBooking;
      if (newBookingId) {
        newBooking = await index.prisma.booking.findUnique({
          where: {
            id: newBookingId
          },
          select: {
            references: true
          }
        });
      }
      calendarReference = newBooking?.references.length ? newBooking.references.filter((reference) => reference.type.includes("_calendar")) : booking.references.filter((reference) => reference.type.includes("_calendar"));
      if (calendarReference.length === 0) {
        return [];
      }
      let result = [];
      for (const reference of calendarReference) {
        const { uid: bookingRefUid, externalCalendarId: bookingExternalCalendarId } = reference;
        let calenderExternalId = null;
        if (bookingExternalCalendarId) {
          calenderExternalId = bookingExternalCalendarId;
        }
        if (reference.credentialId) {
          credential$1 = this.calendarCredentials.filter(
            (credential2) => credential2.id === reference?.credentialId
          )[0];
          if (!credential$1) {
            const credentialFromDB = await credential.CredentialRepository.findCredentialForCalendarServiceById({
              id: reference.credentialId
            });
            if (credentialFromDB && credentialFromDB.appId) {
              credential$1 = {
                id: credentialFromDB.id,
                type: credentialFromDB.type,
                key: credentialFromDB.key,
                userId: credentialFromDB.userId,
                teamId: credentialFromDB.teamId,
                invalid: credentialFromDB.invalid,
                appId: credentialFromDB.appId,
                user: credentialFromDB.user,
                delegatedToId: credentialFromDB.delegatedToId,
                delegatedTo: credentialFromDB.delegatedTo,
                delegationCredentialId: credentialFromDB.delegationCredentialId
              };
            }
          }
          result.push(isDurationLimits.updateEvent(credential$1, event, bookingRefUid, calenderExternalId));
        } else {
          const credentials = this.calendarCredentials.filter(
            (credential2) => credential2.type === reference?.type
          );
          for (const credential2 of credentials) {
            log$1.silly("updateAllCalendarEvents-credential", JSON.stringify({ credentials }));
            result.push(isDurationLimits.updateEvent(credential2, event, bookingRefUid, calenderExternalId));
          }
        }
      }
      if (newBookingId) {
        const oldCalendarEvent = booking.references.find((reference) => reference.type.includes("_calendar"));
        if (oldCalendarEvent?.credentialId) {
          const calendarCredential = await credential.CredentialRepository.findCredentialForCalendarServiceById({
            id: oldCalendarEvent.credentialId
          });
          const calendar = await _metadata.getCalendar(calendarCredential);
          await calendar?.deleteEvent(oldCalendarEvent.uid, event, oldCalendarEvent.externalCalendarId);
        }
      }
      result = result.concat(
        this.calendarCredentials.filter((cred) => cred.type.includes("other_calendar")).map(async (cred) => {
          const calendarReference2 = booking.references.find((ref) => ref.type === cred.type);
          if (!calendarReference2) {
            return {
              appName: cred.appId || "",
              type: cred.type,
              success: false,
              uid: "",
              originalEvent: event,
              credentialId: cred.id
            };
          }
          const { externalCalendarId: bookingExternalCalendarId, meetingId: bookingRefUid } = calendarReference2;
          return await isDurationLimits.updateEvent(cred, event, bookingRefUid ?? null, bookingExternalCalendarId ?? null);
        })
      );
      return Promise.all(result);
    } catch (error) {
      let message = `Tried to 'updateAllCalendarEvents' but there was no '{thing}' for '${credential$1?.type}', userId: '${credential$1?.userId}', bookingId: '${booking?.id}'`;
      if (error instanceof Error) {
        message = message.replace("{thing}", error.message);
      }
      return Promise.resolve(
        calendarReference?.map((reference) => {
          return {
            appName: "none",
            type: reference?.type || "calendar",
            success: false,
            uid: "",
            originalEvent: event,
            credentialId: 0
          };
        }) ?? []
      );
    }
  }
  /**
   * Updates a single video event.
   *
   * @param event
   * @param booking
   * @private
   */
  async updateVideoEvent(event, booking) {
    const credential2 = this.getVideoCredential(event);
    if (credential2) {
      const bookingRef = booking ? booking.references.filter((ref) => ref.type === credential2.type)[0] : null;
      return conferencing.updateMeeting(credential2, event, bookingRef);
    } else {
      return Promise.reject(
        `No suitable credentials given for the requested integration name:${event.location}`
      );
    }
  }
  async createAllCRMEvents(event) {
    const createdEvents = [];
    const featureRepo = new features_repository.FeaturesRepository(index.prisma);
    const isTaskerEnabledForSalesforceCrm = event.team?.id ? await featureRepo.checkIfTeamHasFeature(event.team.id, "salesforce-crm-tasker") : false;
    const uid = CalEventParser.getUid(event);
    for (const credential2 of this.crmCredentials) {
      if (isTaskerEnabledForSalesforceCrm) {
        if (!event.uid) {
          console.error(
            `Missing bookingId when scheduling CRM event creation on event type ${event?.eventTypeId}`
          );
          continue;
        }
        await CRMScheduler.createEvent({ bookingUid: event.uid });
        continue;
      }
      const currentAppOption = this.getAppOptionsFromEventMetadata(credential2);
      const crm = new CrmManager(credential2, currentAppOption);
      let success = true;
      const createdEvent = await crm.createEvent(event).catch((error) => {
        success = false;
        const errorMsg = error instanceof Error ? error.message : JSON.stringify(error);
        log$1.warn(`Error creating crm event for ${credential2.type} for booking ${event?.uid}`, errorMsg);
      });
      if (createdEvent) {
        createdEvents.push({
          type: credential2.type,
          appName: credential2.appId || "",
          uid,
          success,
          createdEvent: {
            id: createdEvent?.id || "",
            type: credential2.type,
            credentialId: credential2.id
          },
          id: createdEvent?.id || "",
          originalEvent: event,
          credentialId: credential2.id
        });
      }
    }
    return createdEvents;
  }
  async updateAllCRMEvents(event, booking) {
    const updatedEvents = [];
    for (const reference of booking.references) {
      const credential2 = this.crmCredentials.find((cred) => cred.id === reference.credentialId);
      let success = true;
      if (credential2) {
        const crm = new CrmManager(credential2);
        const updatedEvent = await crm.updateEvent(reference.uid, event).catch((error) => {
          success = false;
          log$1.warn(`Error updating crm event for ${credential2.type} for booking ${event?.uid}`, error);
        });
        updatedEvents.push({
          type: credential2.type,
          appName: credential2.appId || "",
          success,
          uid: updatedEvent?.id || "",
          originalEvent: event
        });
      }
    }
    return updatedEvents;
  }
  async deleteCRMEvent({ reference, event }) {
    const credential2 = this.crmCredentials.find((cred) => cred.id === reference.credentialId);
    if (credential2) {
      const currentAppOption = this.getAppOptionsFromEventMetadata(credential2);
      const crm = new CrmManager(credential2, currentAppOption);
      await crm.deleteEvent(reference.uid, event);
    }
  }
  getAppOptionsFromEventMetadata(credential2) {
    if (!this.appOptions || !credential2.appId)
      return {};
    if (credential2.appId in this.appOptions)
      return this.appOptions[credential2.appId];
  }
}
const placeholderCreatedEvent = { results: [], referencesToCreate: [] };
function isEventTypeColor(obj) {
  return index.eventTypeColor.safeParse(obj).success;
}
function parseEventTypeColor(obj) {
  let eventTypeColor = null;
  if (isEventTypeColor(obj))
    eventTypeColor = obj;
  return eventTypeColor;
}
const hashedLinkSelect = {
  id: true,
  link: true,
  eventTypeId: true,
  expiresAt: true,
  maxUsageCount: true,
  usageCount: true,
  eventType: {
    select: {
      userId: true,
      teamId: true,
      users: {
        select: {
          username: true,
          profiles: {
            select: {
              id: true,
              organizationId: true,
              username: true
            }
          }
        }
      },
      team: {
        select: {
          id: true,
          slug: true,
          hideBranding: true,
          parent: {
            select: {
              hideBranding: true
            }
          },
          members: {
            select: {
              user: {
                select: {
                  timeZone: true
                }
              }
            },
            take: 1
          }
        }
      },
      hosts: {
        select: {
          user: {
            select: {
              timeZone: true
            }
          }
        }
      },
      profile: {
        select: {
          user: {
            select: {
              timeZone: true
            }
          }
        }
      },
      owner: {
        select: {
          timeZone: true
        }
      }
    }
  }
};
class HashedLinkRepository {
  constructor(prismaClient) {
    this.prismaClient = prismaClient;
  }
  static create() {
    return new HashedLinkRepository(index.prisma);
  }
  async deleteLinks(eventTypeId, linksToDelete) {
    if (linksToDelete.length === 0)
      return;
    return await this.prismaClient.hashedLink.deleteMany({
      where: {
        eventTypeId,
        link: { in: linksToDelete }
      }
    });
  }
  async createLink(eventTypeId, linkData) {
    const data = {
      eventTypeId,
      link: linkData.link,
      expiresAt: linkData.expiresAt
    };
    if (linkData.maxUsageCount && Number.isFinite(linkData.maxUsageCount)) {
      data.maxUsageCount = linkData.maxUsageCount;
    }
    return await this.prismaClient.hashedLink.create({ data });
  }
  async updateLink(eventTypeId, linkData) {
    const updateData = {
      expiresAt: linkData.expiresAt
    };
    if (typeof linkData.maxUsageCount === "number" && linkData.maxUsageCount !== null) {
      updateData.maxUsageCount = linkData.maxUsageCount;
    }
    return await this.prismaClient.hashedLink.updateMany({
      where: {
        eventTypeId,
        link: linkData.link
      },
      data: updateData
    });
  }
  async findLinksByEventTypeId(eventTypeId) {
    return await this.prismaClient.hashedLink.findMany({
      where: {
        eventTypeId
      },
      select: {
        link: true,
        expiresAt: true,
        maxUsageCount: true,
        usageCount: true
      }
    });
  }
  async findLinkWithEventTypeDetails(linkId) {
    return await this.prismaClient.hashedLink.findUnique({
      where: {
        link: linkId
      },
      select: {
        id: true,
        link: true,
        expiresAt: true,
        maxUsageCount: true,
        usageCount: true,
        eventTypeId: true,
        eventType: {
          select: {
            teamId: true,
            userId: true
          }
        }
      }
    });
  }
  async findLinkWithDetails(linkId) {
    return await this.prismaClient.hashedLink.findUnique({
      where: {
        link: linkId
      },
      select: hashedLinkSelect
    });
  }
  async findLinksWithEventTypeDetails(linkIds) {
    return await this.prismaClient.hashedLink.findMany({
      where: {
        link: {
          in: linkIds
        }
      },
      select: {
        id: true,
        link: true,
        expiresAt: true,
        maxUsageCount: true,
        usageCount: true,
        eventTypeId: true,
        eventType: {
          select: {
            teamId: true,
            userId: true
          }
        }
      }
    });
  }
  async findLinkWithValidationData(linkId) {
    return await this.prismaClient.hashedLink.findUnique({
      where: {
        link: linkId
      },
      select: {
        id: true,
        expiresAt: true,
        maxUsageCount: true,
        usageCount: true,
        eventType: {
          select: {
            userId: true,
            teamId: true,
            hosts: {
              select: {
                user: {
                  select: {
                    timeZone: true
                  }
                }
              }
            },
            profile: {
              select: {
                user: {
                  select: {
                    timeZone: true
                  }
                }
              }
            },
            owner: {
              select: {
                timeZone: true
              }
            }
          }
        }
      }
    });
  }
  async incrementUsage(linkId, maxUsageCount) {
    return await this.prismaClient.hashedLink.update({
      where: {
        id: linkId,
        usageCount: { lt: maxUsageCount }
      },
      data: {
        usageCount: { increment: 1 }
      }
    });
  }
}
class MembershipService {
  constructor(membershipRepository = new membership.MembershipRepository()) {
    this.membershipRepository = membershipRepository;
  }
  /**
   * Checks the membership status of a user within a specific team.
   */
  async checkMembership(teamId, userId) {
    const membership$1 = await membership.MembershipRepository.findUniqueByUserIdAndTeamId({ teamId, userId });
    if (!membership$1 || !membership$1.accepted) {
      return {
        isMember: false,
        isAdmin: false,
        isOwner: false,
        role: void 0
      };
    }
    const { role } = membership$1;
    const isOwner = role === index.MembershipRole.OWNER;
    const isAdmin = isOwner || role === index.MembershipRole.ADMIN;
    return {
      isMember: true,
      isAdmin,
      isOwner,
      role
    };
  }
}
class HashedLinkService {
  constructor(hashedLinkRepository = HashedLinkRepository.create(), membershipService = new MembershipService()) {
    this.hashedLinkRepository = hashedLinkRepository;
    this.membershipService = membershipService;
  }
  /**
   * Normalizes link input to a consistent format
   * @param input - String link or object with link and options
   * @returns Normalized link object
   */
  normalizeLinkInput(input) {
    return typeof input === "string" ? { link: input, expiresAt: null } : {
      link: input.link,
      expiresAt: input.expiresAt ?? null,
      maxUsageCount: input.maxUsageCount
    };
  }
  /**
   * Handles multiple private links operations - orchestrates create, update, and delete
   * @param eventTypeId - The ID of the event type
   * @param multiplePrivateLinks - Array of links to create/update (can be strings or objects with expiration)
   * @param connectedMultiplePrivateLinks - Array of existing link IDs for this event type
   */
  async handleMultiplePrivateLinks({
    eventTypeId,
    multiplePrivateLinks,
    connectedMultiplePrivateLinks
  }) {
    if (!eventTypeId || eventTypeId <= 0) {
      throw new Error("Invalid event type ID");
    }
    if (!multiplePrivateLinks || multiplePrivateLinks.length === 0) {
      await this.hashedLinkRepository.deleteLinks(eventTypeId, connectedMultiplePrivateLinks);
      return;
    }
    const normalizedLinks = multiplePrivateLinks.map((input) => this.normalizeLinkInput(input));
    const currentLinks = normalizedLinks.map((l) => l.link);
    const currentLinksSet = new Set(currentLinks);
    const linksToDelete = connectedMultiplePrivateLinks.filter((link) => !currentLinksSet.has(link));
    await this.hashedLinkRepository.deleteLinks(eventTypeId, linksToDelete);
    const existingLinksSet = new Set(connectedMultiplePrivateLinks);
    for (const linkData of normalizedLinks) {
      const exists = existingLinksSet.has(linkData.link);
      if (!exists) {
        await this.hashedLinkRepository.createLink(eventTypeId, linkData);
      } else {
        await this.hashedLinkRepository.updateLink(eventTypeId, linkData);
      }
    }
  }
  /**
   * Validates a link without incrementing usage count
   * Handles both time-based and usage-based expiration with timezone awareness
   * @param linkId - The hashed link ID to validate
   * @returns The validated link data
   * @throws Error with ErrorCode.PrivateLinkExpired if link is expired or invalid
   */
  async validate(linkId) {
    if (!linkId || typeof linkId !== "string") {
      throw new Error("Invalid link ID");
    }
    const hashedLink = await this.hashedLinkRepository.findLinkWithValidationData(linkId);
    if (!hashedLink) {
      throw new Error(errorCodes.ErrorCode.PrivateLinkExpired);
    }
    hashedLinksUtils.validateHashedLinkData(hashedLink);
    return hashedLink;
  }
  /**
   * Validates a link and increments usage count if valid
   * Handles both time-based and usage-based expiration with timezone awareness
   * @param linkId - The hashed link ID to validate
   * @returns The validated link data
   * @throws Error with ErrorCode.PrivateLinkExpired if link is expired or invalid
   */
  async validateAndIncrementUsage(linkId) {
    const hashedLink = await this.validate(linkId);
    if (hashedLink.expiresAt)
      return hashedLink;
    if (hashedLink.maxUsageCount && hashedLink.maxUsageCount > 0) {
      try {
        await this.hashedLinkRepository.incrementUsage(hashedLink.id, hashedLink.maxUsageCount);
      } catch (updateError) {
        throw new Error(errorCodes.ErrorCode.PrivateLinkExpired);
      }
    }
    return hashedLink;
  }
  async findLinkWithDetails(linkId) {
    const hashedLink = await this.hashedLinkRepository.findLinkWithDetails(linkId);
    if (!hashedLink) {
      return null;
    }
    return hashedLink;
  }
  async createLinkForEventType(eventTypeId, input) {
    const normalized = this.normalizeLinkInput(input);
    return this.hashedLinkRepository.createLink(eventTypeId, normalized);
  }
  async updateLinkForEventType(eventTypeId, input) {
    const normalized = this.normalizeLinkInput(input);
    return this.hashedLinkRepository.updateLink(eventTypeId, normalized);
  }
  async deleteLinkForEventType(eventTypeId, linkId) {
    return this.hashedLinkRepository.deleteLinks(eventTypeId, [linkId]);
  }
  async listLinksByEventType(eventTypeId) {
    return this.hashedLinkRepository.findLinksByEventTypeId(eventTypeId);
  }
  /**
   * Checks if a user has permission to access a link
   * @param link - Link object with event type information
   * @param userId - The user ID to check permissions for
   * @returns true if user has permission, false otherwise
   */
  async checkUserPermissionForLink(link, userId) {
    if (!link?.eventType || !userId || userId <= 0) {
      return false;
    }
    if (link.eventType.userId) {
      return link.eventType.userId === userId;
    }
    if (!link.eventType.teamId) {
      return false;
    }
    const membership2 = await this.membershipService.checkMembership(link.eventType.teamId, userId);
    return membership2.isMember;
  }
}
function allowDisablingHostConfirmationEmails(workflows) {
  return !!workflows.find(
    (workflow) => workflow.trigger === index.WorkflowTriggerEvents.NEW_EVENT && !!workflow.steps.find((step) => step.action === index.WorkflowActions.EMAIL_HOST)
  );
}
function allowDisablingAttendeeConfirmationEmails(workflows) {
  return !!workflows.find(
    (workflow) => workflow.trigger === index.WorkflowTriggerEvents.NEW_EVENT && !!workflow.steps.find(
      (step) => step.action === index.WorkflowActions.EMAIL_ATTENDEE || step.action === index.WorkflowActions.SMS_ATTENDEE
    )
  );
}
const log = index.logger.getSubLogger({ prefix: ["DestinationCalendarRepository"] });
class DestinationCalendarRepository {
  static async create(data) {
    return await index.prisma.destinationCalendar.create({
      data
    });
  }
  static async createIfNotExistsForUser(data) {
    const conflictingCalendar = await DestinationCalendarRepository.findConflictingForUser(data);
    if (conflictingCalendar) {
      return conflictingCalendar;
    }
    return await index.prisma.destinationCalendar.create({
      data
    });
  }
  static async getByUserId(userId) {
    return await index.prisma.destinationCalendar.findFirst({
      where: {
        userId
      }
    });
  }
  static async getByEventTypeId(eventTypeId) {
    return await index.prisma.destinationCalendar.findFirst({
      where: {
        eventTypeId
      }
    });
  }
  static async find({ where }) {
    return await index.prisma.destinationCalendar.findFirst({
      where
    });
  }
  static async findConflictingForUser(data) {
    return await DestinationCalendarRepository.find({
      where: {
        userId: data.userId,
        integration: data.integration,
        externalId: data.externalId,
        eventTypeId: null
      }
    });
  }
  static async upsert({
    where,
    update,
    create
  }) {
    log.debug("upsert", { where, update, create });
    const credentialPayloadForUpdate = buildCredentialPayloadForCalendar.buildCredentialPayloadForPrisma({
      credentialId: update.credentialId,
      delegationCredentialId: update.delegationCredentialId
    });
    const credentialPayloadForCreate = buildCredentialPayloadForCalendar.buildCredentialPayloadForPrisma({
      credentialId: create.credentialId,
      delegationCredentialId: create.delegationCredentialId
    });
    return await index.prisma.destinationCalendar.upsert({
      where,
      update: {
        ...update,
        ...credentialPayloadForUpdate
      },
      create: {
        ...create,
        ...credentialPayloadForCreate
      }
    });
  }
}
exports.CrmManager = CrmManager;
exports.DestinationCalendarRepository = DestinationCalendarRepository;
exports.EventManager = EventManager;
exports.HashedLinkRepository = HashedLinkRepository;
exports.HashedLinkService = HashedLinkService;
exports.allowDisablingAttendeeConfirmationEmails = allowDisablingAttendeeConfirmationEmails;
exports.allowDisablingHostConfirmationEmails = allowDisablingHostConfirmationEmails;
exports.getEventTypeAppData = getEventTypeAppData;
exports.parseEventTypeColor = parseEventTypeColor;
exports.placeholderCreatedEvent = placeholderCreatedEvent;
