"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const rrule = require("rrule");
const uuid = require("uuid");
const CalEventParser = require("./CalEventParser-mh8lC_8R.cjs");
const _metadata = require("./_metadata-cJJEBrFE.cjs");
const features_repository = require("./features.repository-UzW6TkXH.cjs");
const credential = require("./credential-yvBSPLqv.cjs");
const index = require("./i18n-Bs8xJzoY.cjs");
const user = require("./user-7XDeDEMd.cjs");
const selectedCalendar = require("./selectedCalendar-fjy3A1fw.cjs");
const calendar = require("@googleapis/calendar");
const googleapisCommon = require("googleapis-common");
const CalendarAppError = require("./CalendarAppError-RC_O0RpM.cjs");
const markTokenAsExpired = require("./markTokenAsExpired-JTzOdd4i.cjs");
const oAuthManagerHelper = require("./oAuthManagerHelper-ipQc7KO6.cjs");
const universalSchema = require("./universalSchema-bsuJA9VF.cjs");
const z = require("zod");
const getParsedAppKeysFromSlug = require("./getParsedAppKeysFromSlug-6Fd-SXzu.cjs");
const uniqueBy = (array, keys) => {
  return array.filter(
    (item, index2, self) => index2 === self.findIndex((t) => keys.every((key) => t[key] === item[key]))
  );
};
const log$4 = index.logger.getSubLogger({ prefix: ["CalendarCacheRepository"] });
const MS_PER_DAY$1 = 24 * 60 * 60 * 1e3;
const ONE_MONTH_IN_MS$1 = 30 * MS_PER_DAY$1;
const CACHING_TIME = ONE_MONTH_IN_MS$1;
function parseKeyForCache(args) {
  const uniqueItems = uniqueBy(args.items, ["id"]);
  const key = JSON.stringify({
    timeMin: args.timeMin,
    timeMax: args.timeMax,
    items: uniqueItems
  });
  return key;
}
function assertCalendarHasDbCredential(calendar2) {
  if (!calendar2?.getCredentialId) {
    return;
  }
  const credentialId = calendar2.getCredentialId();
  if (credentialId < 0) {
    throw new Error(`Received invalid credentialId ${credentialId}`);
  }
}
class CalendarCacheRepository {
  constructor(calendar2 = null) {
    this.calendar = calendar2;
  }
  async watchCalendar(args) {
    assertCalendarHasDbCredential(this.calendar);
    const { calendarId, eventTypeIds } = args;
    if (typeof this.calendar?.watchCalendar !== "function") {
      log$4.info(
        '[handleWatchCalendar] Skipping watching calendar due to calendar not having "watchCalendar" method'
      );
      return;
    }
    await this.calendar?.watchCalendar({ calendarId, eventTypeIds });
  }
  async unwatchCalendar(args) {
    assertCalendarHasDbCredential(this.calendar);
    const { calendarId, eventTypeIds } = args;
    if (typeof this.calendar?.unwatchCalendar !== "function") {
      log$4.info(
        '[unwatchCalendar] Skipping unwatching calendar due to calendar not having "unwatchCalendar" method'
      );
      return;
    }
    const response = await this.calendar?.unwatchCalendar({ calendarId, eventTypeIds });
    return response;
  }
  async getCachedAvailability({
    credentialId,
    userId,
    args
  }) {
    log$4.debug("Getting cached availability", index.safeStringify({ credentialId, userId, args }));
    const key = parseKeyForCache(args);
    let cached;
    let usedInMemoryDelegationCredential = false;
    if (user.isInMemoryDelegationCredential({ credentialId })) {
      usedInMemoryDelegationCredential = true;
      if (!userId) {
        log$4.warn("userId is not available when querying cache for in-memory delegation credential");
        return null;
      }
      cached = await index.prisma.calendarCache.findFirst({
        // We have index on userId and key, so this should be fast
        // TODO: Should we consider index on all three - userId, key and expiresAt?
        where: {
          userId,
          key,
          expiresAt: { gte: new Date(Date.now()) }
        },
        orderBy: {
          // In case of multiple entries for same key and userId, we prefer the one with highest expiry, which will be the most updated one
          // TODO: For better tracking we could also want to use updatedAt directly which doesn't exist yet in CalendarCache table
          expiresAt: "desc"
        }
      });
    } else {
      cached = await index.prisma.calendarCache.findUnique({
        where: {
          credentialId_key: {
            credentialId,
            key
          },
          expiresAt: { gte: new Date(Date.now()) }
        }
      });
    }
    log$4.info(
      "Got cached availability",
      index.safeStringify({ key, cached, credentialId, usedInMemoryDelegationCredential })
    );
    return cached;
  }
  async upsertCachedAvailability({
    credentialId,
    userId,
    args,
    value
  }) {
    assertCalendarHasDbCredential(this.calendar);
    const key = parseKeyForCache(args);
    await index.prisma.calendarCache.upsert({
      where: {
        credentialId_key: {
          credentialId,
          key
        }
      },
      update: {
        // Ensure that on update userId is also set(It handles the case where userId is not set for legacy records)
        userId,
        value,
        expiresAt: new Date(Date.now() + CACHING_TIME)
      },
      create: {
        value,
        credentialId,
        userId,
        key,
        expiresAt: new Date(Date.now() + CACHING_TIME)
      }
    });
  }
  async getCacheStatusByCredentialIds(credentialIds) {
    const cacheStatuses = await index.prisma.calendarCache.groupBy({
      by: ["credentialId"],
      where: {
        credentialId: { in: credentialIds }
      },
      _max: {
        updatedAt: true
      }
    });
    return cacheStatuses.map((cache) => ({
      credentialId: cache.credentialId,
      updatedAt: cache._max.updatedAt
    }));
  }
}
const log$3 = index.logger.getSubLogger({ prefix: ["CalendarCacheRepositoryMock"] });
class CalendarCacheRepositoryMock {
  async watchCalendar() {
    log$3.info(`Skipping watchCalendar due to calendar-cache being disabled`);
  }
  async upsertCachedAvailability() {
    log$3.info(`Skipping upsertCachedAvailability due to calendar-cache being disabled`);
  }
  async getCachedAvailability() {
    log$3.info(`Skipping getCachedAvailability due to calendar-cache being disabled`);
    return null;
  }
  async unwatchCalendar() {
    log$3.info(`Skipping unwatchCalendar due to calendar-cache being disabled`);
  }
  async deleteManyByCredential() {
    log$3.info(`Skipping deleteManyByCredential due to calendar-cache being disabled`);
  }
  async getCacheStatusByCredentialIds() {
    log$3.info(`Skipping getCacheStatusByCredentialIds due to calendar-cache being disabled`);
    return [];
  }
}
const log$2 = index.logger.getSubLogger({ prefix: ["CalendarCache"] });
class CalendarCache {
  static async initFromCredentialId(credentialId) {
    log$2.debug("initFromCredentialId", index.safeStringify({ credentialId }));
    const credentialForCalendarCache = await credential.getCredentialForCalendarCache({ credentialId });
    const calendarForCalendarCache = await _metadata.getCalendar(credentialForCalendarCache);
    return await CalendarCache.init(calendarForCalendarCache);
  }
  static async init(calendar2) {
    const featureRepo = new features_repository.FeaturesRepository(index.prisma);
    const isCalendarCacheEnabledGlobally = await featureRepo.checkIfFeatureIsEnabledGlobally(
      "calendar-cache"
    );
    if (isCalendarCacheEnabledGlobally)
      return new CalendarCacheRepository(calendar2);
    return new CalendarCacheRepositoryMock();
  }
}
const getTimeMin = (timeMin) => {
  const date = timeMin ? new Date(timeMin) : /* @__PURE__ */ new Date();
  const result = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1));
  result.setUTCHours(0, 0, 0, 0);
  return result.toISOString();
};
function getTimeMax(timeMax) {
  const now = /* @__PURE__ */ new Date();
  const currentMonth = now.getUTCMonth();
  const currentYear = now.getUTCFullYear();
  if (!timeMax) {
    const result2 = new Date(Date.UTC(currentYear, currentMonth + 2, 1));
    result2.setUTCHours(0, 0, 0, 0);
    return result2.toISOString();
  }
  const date = new Date(timeMax);
  const dateMonth = date.getUTCMonth();
  const dateYear = date.getUTCFullYear();
  let baseYear = currentYear;
  let baseMonth = currentMonth;
  const isWithinCurrentOrNextTwoMonths = dateYear === currentYear && dateMonth <= currentMonth + 2 || dateYear === currentYear + 1 && (currentMonth === 10 && dateMonth === 0 || currentMonth === 11 && dateMonth <= 1);
  if (!isWithinCurrentOrNextTwoMonths) {
    baseYear = dateYear;
    baseMonth = dateMonth;
  }
  const result = new Date(Date.UTC(baseYear, baseMonth + 2, 1));
  result.setUTCHours(0, 0, 0, 0);
  return result.toISOString();
}
class AxiosLikeResponseToFetchResponse extends Response {
  constructor(axiomResponse) {
    super(JSON.stringify(axiomResponse.data), {
      status: axiomResponse.status,
      statusText: axiomResponse.statusText
    });
  }
  async json() {
    return super.json();
  }
}
const googleAppKeysSchema = z.z.object({
  client_id: z.z.string(),
  client_secret: z.z.string(),
  redirect_uris: z.z.array(z.z.string())
});
const getGoogleAppKeys = async () => {
  return getParsedAppKeysFromSlug.getParsedAppKeysFromSlug("google-calendar", googleAppKeysSchema);
};
const log$1 = index.logger.getSubLogger({ prefix: ["app-store/googlecalendar/lib/CalendarAuth"] });
class MyGoogleOAuth2Client extends googleapisCommon.OAuth2Client {
  constructor(client_id, client_secret, redirect_uri) {
    super({
      clientId: client_id,
      clientSecret: client_secret,
      redirectUri: redirect_uri,
      // default: 5 * 60 * 1000, 5 minutes
      // tho, fn will never run in excess of 60 seconds
      eagerRefreshThresholdMillis: 6e4
    });
  }
  isTokenExpiring() {
    return super.isTokenExpiring();
  }
  async refreshToken(token) {
    return super.refreshToken(token);
  }
}
class CalendarAuth {
  constructor(credential2) {
    this.jwtAuthClient = null;
    this.oAuthClient = null;
    this.refreshJwtToken = async ({ delegatedTo }) => {
      log$1.debug("Attempting to authorize using JWT auth");
      const { user: user2 } = this.credential;
      const emailToImpersonate = user2?.email ?? null;
      const authClient = await this.getJwtClientSingleton({ delegatedTo, emailToImpersonate });
      if (!authClient) {
        log$1.error("JWT auth: No auth client found");
        return null;
      }
      try {
        log$1.debug("Authorizing using JWT auth");
        return await authClient.authorize();
      } catch (error) {
        log$1.error("DelegatedTo: Error authorizing using JWT auth", JSON.stringify(error));
        if (error.response?.data?.error === "unauthorized_client") {
          throw new CalendarAppError.CalendarAppDelegationCredentialClientIdNotAuthorizedError(
            "Make sure that the Client ID for the delegation credential is added to the Google Workspace Admin Console"
          );
        }
        if (error.response?.data?.error === "invalid_grant") {
          throw new CalendarAppError.CalendarAppDelegationCredentialInvalidGrantError(
            `User ${emailToImpersonate} might not exist in Google Workspace`
          );
        }
        throw new CalendarAppError.CalendarAppDelegationCredentialError("Error authorizing delegation credential");
      }
    };
    this.credential = credential2;
    this.authMechanism = this.initAuthMechanism(credential2);
  }
  getAuthStrategy() {
    return this.credential.delegatedToId ? "jwt" : "oauth";
  }
  async getOAuthClientSingleton() {
    if (this.oAuthClient) {
      log$1.debug("Reusing existing oAuthClient");
      return this.oAuthClient;
    }
    log$1.debug("Creating new oAuthClient");
    const { client_id, client_secret, redirect_uris } = await getGoogleAppKeys();
    const googleCredentials = universalSchema.OAuth2UniversalSchema.parse(this.credential.key);
    this.oAuthClient = new MyGoogleOAuth2Client(client_id, client_secret, redirect_uris[0]);
    this.oAuthClient.setCredentials(googleCredentials);
    return this.oAuthClient;
  }
  async getJwtClientSingleton({
    emailToImpersonate,
    delegatedTo
  }) {
    if (!emailToImpersonate) {
      log$1.error("DelegatedTo: No email to impersonate found for delegation credential");
      return null;
    }
    const oauthClientIdAliasRegex = /\+[a-zA-Z0-9]{25}/;
    if (!this.jwtAuthClient) {
      log$1.debug("Creating new delegation credential authClient");
      const authClient = new googleapisCommon.JWT({
        email: delegatedTo.serviceAccountKey.client_email,
        key: delegatedTo.serviceAccountKey.private_key,
        scopes: ["https://www.googleapis.com/auth/calendar"],
        subject: emailToImpersonate.replace(oauthClientIdAliasRegex, "")
      });
      this.jwtAuthClient = authClient;
    } else {
      log$1.debug("Reusing existing delegation credential authClient");
    }
    return this.jwtAuthClient;
  }
  async refreshOAuthToken({ refreshToken }) {
    const oAuthClient = await this.getOAuthClientSingleton();
    return oAuthClient.refreshToken(refreshToken);
  }
  initAuthMechanism(credential2) {
    const authStrategy = this.getAuthStrategy();
    const authManager = new markTokenAsExpired.OAuthManager({
      // Keep it false for oauth because Google's OAuth2Client library that we use supports token expiry check, itself when we use the client to make any request
      // We keep it true for jwt because JWT Client doesn't support token expiry check and we do it ourselves
      autoCheckTokenExpiryOnRequest: authStrategy !== "oauth",
      ...authStrategy === "oauth" ? {
        // Use Google's OAuth2Client library itself to check if the token is expiring
        // For JWT, OAuthManager will do it itself
        isTokenExpiring: async () => {
          const oAuthClient = await this.getOAuthClientSingleton();
          return oAuthClient.isTokenExpiring();
        }
      } : {},
      credentialSyncVariables: {
        APP_CREDENTIAL_SHARING_ENABLED: index.APP_CREDENTIAL_SHARING_ENABLED,
        CREDENTIAL_SYNC_ENDPOINT: index.CREDENTIAL_SYNC_ENDPOINT,
        CREDENTIAL_SYNC_SECRET: index.CREDENTIAL_SYNC_SECRET,
        CREDENTIAL_SYNC_SECRET_HEADER_NAME: index.CREDENTIAL_SYNC_SECRET_HEADER_NAME
      },
      resourceOwner: {
        type: "user",
        id: credential2.userId
      },
      appSlug: _metadata.metadata$2.slug,
      getCurrentTokenObject: async () => {
        return oAuthManagerHelper.oAuthManagerHelper.getCurrentTokenObject(this.credential);
      },
      fetchNewTokenObject: async ({ refreshToken }) => {
        let result;
        if (authStrategy === "jwt" && this.credential.delegatedTo) {
          log$1.debug("Fetching new token object for JWT auth");
          result = {
            // In case of JWT Token flow, there is no refresh token, so we need to refresh the token using Service Account
            tokenObject: await this.refreshJwtToken({ delegatedTo: this.credential.delegatedTo }),
            status: 200,
            statusText: "OK"
          };
        }
        if (!result || !result.tokenObject) {
          log$1.debug("Fetching new token object for my Google Auth");
          const tokenFetchedResult = await this.refreshOAuthToken({ refreshToken });
          result = {
            tokenObject: tokenFetchedResult.res?.data ?? null,
            status: tokenFetchedResult.res?.status,
            statusText: tokenFetchedResult.res?.statusText
          };
        }
        return new Response(JSON.stringify(result.tokenObject), {
          status: result.status,
          statusText: result.statusText
        });
      },
      isTokenObjectUnusable: async function(response) {
        if (!response.ok || response.status < 200 && response.status >= 300) {
          const responseBody = await response.json();
          if (responseBody.error === "invalid_grant") {
            return {
              reason: "invalid_grant"
            };
          }
        }
        return null;
      },
      isAccessTokenUnusable: async () => {
        return null;
      },
      invalidateTokenObject: () => markTokenAsExpired.invalidateCredential(this.credential.id),
      expireAccessToken: async () => {
        await oAuthManagerHelper.oAuthManagerHelper.markTokenAsExpired(this.credential);
      },
      updateTokenObject: async (token) => {
        await oAuthManagerHelper.oAuthManagerHelper.updateTokenObjectInDb({
          tokenObject: token,
          authStrategy: this.getAuthStrategy(),
          credentialId: this.credential.id,
          userId: this.credential.userId ?? null,
          delegatedToId: this.credential.delegatedToId ?? null,
          credentialType: this.credential.type,
          appId: _metadata.metadata$2.slug
        });
        if (this.oAuthClient) {
          this.oAuthClient.setCredentials(token);
        }
        this.credential.key = token;
      }
    });
    this.authManager = authManager;
    return {
      getOAuthClientWithRefreshedToken: async () => {
        const { token } = await authManager.getTokenObjectOrFetch();
        if (!token) {
          throw new Error("Invalid grant for Google Calendar app");
        }
        const oAuthClient = await this.getOAuthClientSingleton();
        return oAuthClient;
      },
      getJwtClientWithRefreshedToken: async ({ delegatedTo }) => {
        log$1.debug("Getting JWT client with refreshed token");
        await authManager.getTokenObjectOrFetch();
        return this.getJwtClientSingleton({
          emailToImpersonate: this.credential.user?.email ?? null,
          delegatedTo
        });
      }
    };
  }
  /**
   * Returns a Google Calendar client that is authenticated with the user's credentials.
   * If the user is delegated, it will use the delegation credential.
   * If the user is not delegated, it will use the user's OAuth credentials.
   */
  async getClient() {
    log$1.debug("Getting authed calendar client");
    let googleAuthClient;
    const authStrategy = this.getAuthStrategy();
    if (authStrategy === "jwt" && this.credential.delegatedTo) {
      googleAuthClient = await this.authMechanism.getJwtClientWithRefreshedToken({
        delegatedTo: this.credential.delegatedTo
      });
    }
    if (!googleAuthClient) {
      googleAuthClient = await this.authMechanism.getOAuthClientWithRefreshedToken();
    }
    if (!googleAuthClient) {
      throw new Error("Failed to initialize Google Auth client");
    }
    return new calendar.calendar_v3.Calendar({
      auth: googleAuthClient
    });
  }
}
const log = index.logger.getSubLogger({ prefix: ["app-store/googlecalendar/lib/CalendarService"] });
const MS_PER_DAY = 24 * 60 * 60 * 1e3;
const ONE_MONTH_IN_MS = 30 * MS_PER_DAY;
const GOOGLE_WEBHOOK_URL_BASE = process.env.GOOGLE_WEBHOOK_URL || process.env.NEXT_PUBLIC_WEBAPP_URL;
const GOOGLE_WEBHOOK_URL = `${GOOGLE_WEBHOOK_URL_BASE}/api/integrations/googlecalendar/webhook`;
const isGaxiosResponse = (error) => typeof error === "object" && !!error && error.hasOwnProperty("config");
class GoogleCalendarService {
  constructor(credential2) {
    this.integrationName = "";
    this.getAttendees = ({
      event,
      hostExternalCalendarId
    }) => {
      const selectedHostDestinationCalendar = event.destinationCalendar?.find(
        (cal) => cal.credentialId === this.credential.id
      );
      const isOrganizerExempt = index.ORGANIZER_EMAIL_EXEMPT_DOMAINS?.split(",").filter((domain) => domain.trim() !== "").some((domain) => event.organizer.email.toLowerCase().endsWith(domain.toLowerCase()));
      const eventAttendees = event.attendees.map(({ id: _id, ...rest }) => ({
        ...rest,
        responseStatus: "accepted"
      }));
      const attendees = [
        {
          ...event.organizer,
          id: String(event.organizer.id),
          responseStatus: "accepted",
          organizer: true,
          // Tried changing the display name to the user but GCal will not let you do that. It will only display the name of the external calendar. Leaving this in just incase it works in the future.
          displayName: event.organizer.name,
          // We use || instead of ?? here to handle empty strings
          email: hostExternalCalendarId || selectedHostDestinationCalendar?.externalId || event.organizer.email
        },
        ...event.hideOrganizerEmail && !isOrganizerExempt ? [] : eventAttendees
      ];
      if (event.team?.members) {
        const teamAttendeesWithoutCurrentUser = event.team.members.filter((member) => member.email !== this.credential.user?.email).map((m) => {
          const teamMemberDestinationCalendar = event.destinationCalendar?.find(
            (calendar2) => calendar2.integration === "google_calendar" && calendar2.userId === m.id
          );
          return {
            email: teamMemberDestinationCalendar?.externalId ?? m.email,
            displayName: m.name,
            responseStatus: "accepted"
          };
        });
        attendees.push(...teamAttendeesWithoutCurrentUser);
      }
      return attendees;
    };
    this.integrationName = "google_calendar";
    this.credential = credential2;
    this.auth = new CalendarAuth(credential2);
    this.log = log.getSubLogger({ prefix: [`[[lib] ${this.integrationName}`] });
  }
  getCredentialId() {
    return this.credential.id;
  }
  async authedCalendar() {
    this.log.debug("Getting authed calendar");
    return this.auth.getClient();
  }
  async stopWatchingCalendarsInGoogle(channels) {
    const calendar2 = await this.authedCalendar();
    index.logger.debug(`Unsubscribing from calendars ${channels.map((c) => c.googleChannelId).join(", ")}`);
    const uniqueChannels = uniqueBy(channels, ["googleChannelId", "googleChannelResourceId"]);
    await Promise.allSettled(
      uniqueChannels.map(
        ({ googleChannelResourceId, googleChannelId }) => calendar2.channels.stop({
          requestBody: {
            resourceId: googleChannelResourceId,
            id: googleChannelId
          }
        }).catch((err) => {
          console.warn(JSON.stringify(err));
        })
      )
    );
  }
  async startWatchingCalendarsInGoogle({ calendarId }) {
    const calendar2 = await this.authedCalendar();
    index.logger.debug(`Subscribing to calendar ${calendarId}`, index.safeStringify({ GOOGLE_WEBHOOK_URL }));
    const res = await calendar2.events.watch({
      // Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the "primary" keyword.
      calendarId,
      requestBody: {
        // A UUID or similar unique string that identifies this channel.
        id: uuid.v4(),
        type: "web_hook",
        address: GOOGLE_WEBHOOK_URL,
        token: process.env.GOOGLE_WEBHOOK_TOKEN,
        params: {
          // The time-to-live in seconds for the notification channel. Default is 604800 seconds.
          ttl: `${Math.round(ONE_MONTH_IN_MS / 1e3)}`
        }
      }
    });
    return res.data;
  }
  async createEvent(calEvent, credentialId, externalCalendarId) {
    this.log.debug("Creating event");
    const payload = {
      summary: calEvent.title,
      description: calEvent.calendarDescription,
      start: {
        dateTime: calEvent.startTime,
        timeZone: calEvent.organizer.timeZone
      },
      end: {
        dateTime: calEvent.endTime,
        timeZone: calEvent.organizer.timeZone
      },
      attendees: this.getAttendees({ event: calEvent, hostExternalCalendarId: externalCalendarId }),
      reminders: {
        useDefault: true
      },
      guestsCanSeeOtherGuests: !!calEvent.seatsPerTimeSlot ? calEvent.seatsShowAttendees : true,
      iCalUID: calEvent.iCalUID
    };
    if (calEvent.hideCalendarEventDetails) {
      payload.visibility = "private";
    }
    if (calEvent.location) {
      payload["location"] = CalEventParser.getLocation(calEvent);
    }
    if (calEvent.recurringEvent) {
      const rule = new rrule.RRule({
        freq: calEvent.recurringEvent.freq,
        interval: calEvent.recurringEvent.interval,
        count: calEvent.recurringEvent.count
      });
      payload["recurrence"] = [rule.toString()];
    }
    if (calEvent.conferenceData && calEvent.location === CalEventParser.MeetLocationType$1) {
      payload["conferenceData"] = calEvent.conferenceData;
    }
    const calendar2 = await this.authedCalendar();
    const selectedCalendar2 = externalCalendarId ?? (calEvent.destinationCalendar?.find((cal) => cal.credentialId === credentialId)?.externalId || "primary");
    try {
      let event;
      let recurringEventId = null;
      if (calEvent.existingRecurringEvent) {
        recurringEventId = calEvent.existingRecurringEvent.recurringEventId;
        const recurringEventInstances = await calendar2.events.instances({
          calendarId: selectedCalendar2,
          eventId: calEvent.existingRecurringEvent.recurringEventId
        });
        if (recurringEventInstances.data.items) {
          const calComEventStartTimeMs = new Date(calEvent.startTime).getTime();
          for (let i = 0; i < recurringEventInstances.data.items.length; i++) {
            const instance = recurringEventInstances.data.items[i];
            const instanceStartTimeMs = new Date(instance.start?.dateTime || "").getTime();
            if (instanceStartTimeMs === calComEventStartTimeMs) {
              event = instance;
              break;
            }
          }
          if (!event) {
            event = recurringEventInstances.data.items[0];
            this.log.error(
              "Unable to find matching event amongst recurring event instances",
              index.safeStringify({ selectedCalendar: selectedCalendar2, credentialId })
            );
          }
          await calendar2.events.patch({
            calendarId: selectedCalendar2,
            eventId: event.id || "",
            requestBody: {
              location: CalEventParser.getLocation(calEvent),
              description: calEvent.calendarDescription
            }
          });
        }
      } else {
        const eventResponse = await calendar2.events.insert({
          calendarId: selectedCalendar2,
          requestBody: payload,
          conferenceDataVersion: 1,
          sendUpdates: "none"
        });
        event = eventResponse.data;
        if (event.recurrence) {
          if (event.recurrence.length > 0) {
            recurringEventId = event.id;
            event = await this.getFirstEventInRecurrence(recurringEventId, selectedCalendar2, calendar2);
          }
        }
      }
      if (event && event.id && event.hangoutLink) {
        await calendar2.events.patch({
          // Update the same event but this time we know the hangout link
          calendarId: selectedCalendar2,
          eventId: event.id || "",
          requestBody: {
            description: CalEventParser.getRichDescription({
              ...calEvent,
              additionalInformation: { hangoutLink: event.hangoutLink }
            })
          }
        });
      }
      return {
        uid: "",
        ...event,
        id: event?.id || "",
        thirdPartyRecurringEventId: recurringEventId,
        additionalInfo: {
          hangoutLink: event?.hangoutLink || ""
        },
        type: "google_calendar",
        password: "",
        url: "",
        iCalUID: event?.iCalUID
      };
    } catch (error) {
      if (isGaxiosResponse(error)) {
        delete error.config.body;
      }
      this.log.error(
        "There was an error creating event in google calendar: ",
        index.safeStringify({ error, selectedCalendar: selectedCalendar2, credentialId })
      );
      throw error;
    }
  }
  async getFirstEventInRecurrence(recurringEventId, selectedCalendar2, calendar2) {
    const recurringEventInstances = await calendar2.events.instances({
      calendarId: selectedCalendar2,
      eventId: recurringEventId || ""
    });
    if (recurringEventInstances.data.items) {
      return recurringEventInstances.data.items[0];
    } else {
      return {};
    }
  }
  async updateEvent(uid, event, externalCalendarId) {
    const payload = {
      summary: event.title,
      description: event.calendarDescription,
      start: {
        dateTime: event.startTime,
        timeZone: event.organizer.timeZone
      },
      end: {
        dateTime: event.endTime,
        timeZone: event.organizer.timeZone
      },
      attendees: this.getAttendees({ event, hostExternalCalendarId: externalCalendarId }),
      reminders: {
        useDefault: true
      },
      guestsCanSeeOtherGuests: !!event.seatsPerTimeSlot ? event.seatsShowAttendees : true
    };
    if (event.location) {
      payload["location"] = CalEventParser.getLocation(event);
    }
    if (event.conferenceData && event.location === CalEventParser.MeetLocationType$1) {
      payload["conferenceData"] = event.conferenceData;
    }
    const calendar2 = await this.authedCalendar();
    const selectedCalendar2 = (externalCalendarId ? event.destinationCalendar?.find((cal) => cal.externalId === externalCalendarId)?.externalId : void 0) || "primary";
    try {
      const evt = await calendar2.events.update({
        calendarId: selectedCalendar2,
        eventId: uid,
        sendNotifications: true,
        sendUpdates: "none",
        requestBody: payload,
        conferenceDataVersion: 1
      });
      this.log.debug("Updated Google Calendar Event", {
        startTime: evt?.data.start,
        endTime: evt?.data.end
      });
      if (evt && evt.data.id && evt.data.hangoutLink && event.location === CalEventParser.MeetLocationType$1) {
        calendar2.events.patch({
          // Update the same event but this time we know the hangout link
          calendarId: selectedCalendar2,
          eventId: evt.data.id || "",
          requestBody: {
            description: CalEventParser.getRichDescription({
              ...event,
              additionalInformation: { hangoutLink: evt.data.hangoutLink }
            })
          }
        });
        return {
          uid: "",
          ...evt.data,
          id: evt.data.id || "",
          additionalInfo: {
            hangoutLink: evt.data.hangoutLink || ""
          },
          type: "google_calendar",
          password: "",
          url: "",
          iCalUID: evt.data.iCalUID
        };
      }
      return evt?.data;
    } catch (error) {
      this.log.error(
        "There was an error updating event in google calendar: ",
        index.safeStringify({ error, event, uid })
      );
      throw error;
    }
  }
  async deleteEvent(uid, event, externalCalendarId) {
    const calendar2 = await this.authedCalendar();
    const selectedCalendar2 = externalCalendarId || "primary";
    try {
      const event2 = await calendar2.events.delete({
        calendarId: selectedCalendar2,
        eventId: uid,
        sendNotifications: false,
        sendUpdates: "none"
      });
      return event2?.data;
    } catch (error) {
      this.log.error(
        "There was an error deleting event from google calendar: ",
        index.safeStringify({ error, event, externalCalendarId })
      );
      const err = error;
      if (err.code === 410)
        return;
      console.error("There was an error contacting google calendar service: ", err);
      if (err.code === 404)
        return;
      throw err;
    }
  }
  async fetchAvailability(requestBody) {
    log.debug("fetchAvailability", index.safeStringify({ requestBody }));
    const calendar2 = await this.authedCalendar();
    const apiResponse = await this.auth.authManager.request(
      async () => new AxiosLikeResponseToFetchResponse(await calendar2.freebusy.query({ requestBody }))
    );
    return apiResponse.json;
  }
  async getFreeBusyResult(args, shouldServeCache) {
    if (shouldServeCache === false)
      return await this.fetchAvailability(args);
    const calendarCache = await CalendarCache.init(null);
    const cached = await calendarCache.getCachedAvailability({
      credentialId: this.credential.id,
      userId: this.credential.userId,
      args: {
        // Expand the start date to the start of the month to increase cache hits
        timeMin: getTimeMin(args.timeMin),
        // Expand the end date to the end of the month to increase cache hits
        timeMax: getTimeMax(args.timeMax),
        items: args.items
      }
    });
    if (cached) {
      log.debug("[Cache Hit] Returning cached freebusy result", index.safeStringify({ cached, args }));
      return cached.value;
    }
    log.debug("[Cache Miss] Fetching freebusy result", index.safeStringify({ args }));
    return await this.fetchAvailability(args);
  }
  getValidCalendars(cals) {
    return cals.filter((cal) => !!cal.id);
  }
  filterPrimaryCalendar(cals) {
    const validCals = this.getValidCalendars(cals);
    const primaryCal = validCals.find((cal) => !!cal.primary);
    if (primaryCal) {
      return primaryCal;
    }
    return validCals[0];
  }
  async getCacheOrFetchAvailability(args, shouldServeCache) {
    const freeBusyResult = await this.getFreeBusyResult(args, shouldServeCache);
    if (!freeBusyResult.calendars)
      return null;
    const result = Object.entries(freeBusyResult.calendars).reduce((c, [id, i]) => {
      i.busy?.forEach((busyTime) => {
        c.push({
          id,
          start: busyTime.start || "",
          end: busyTime.end || ""
        });
      });
      return c;
    }, []);
    return result;
  }
  async getAvailabilityWithTimeZones(dateFrom, dateTo, selectedCalendars, fallbackToPrimary) {
    const calendar2 = await this.authedCalendar();
    const selectedCalendarIds = selectedCalendars.filter((e) => e.integration === this.integrationName).map((e) => e.externalId);
    if (selectedCalendarIds.length === 0 && selectedCalendars.length > 0) {
      return [];
    }
    const getCalIdsWithTimeZone = async () => {
      const cals = await this.getAllCalendars(calendar2, ["id", "timeZone"]);
      if (!cals.length)
        return [];
      if (selectedCalendarIds.length !== 0) {
        return selectedCalendarIds.map((selectedCalendarId) => {
          const calWithTz = cals.find((cal) => cal.id === selectedCalendarId);
          return {
            id: selectedCalendarId,
            timeZone: calWithTz?.timeZone || ""
          };
        });
      }
      if (!fallbackToPrimary)
        return [];
      const primaryCalendar = this.filterPrimaryCalendar(cals);
      if (!primaryCalendar)
        return [];
      return [
        {
          id: primaryCalendar.id,
          timeZone: primaryCalendar.timeZone || ""
        }
      ];
    };
    try {
      const calIdsWithTimeZone = await getCalIdsWithTimeZone();
      const calIds = calIdsWithTimeZone.map((calIdWithTimeZone) => ({ id: calIdWithTimeZone.id }));
      const freeBusyData = await this.getCacheOrFetchAvailability({
        timeMin: dateFrom,
        timeMax: dateTo,
        items: calIds
      });
      if (!freeBusyData)
        throw new Error("No response from google calendar");
      const timeZoneMap = new Map(calIdsWithTimeZone.map((cal) => [cal.id, cal.timeZone]));
      const freeBusyDataWithTimeZone = freeBusyData.map((freeBusy) => {
        return {
          start: freeBusy.start,
          end: freeBusy.end,
          timeZone: timeZoneMap.get(freeBusy.id) || ""
        };
      });
      return freeBusyDataWithTimeZone;
    } catch (error) {
      this.log.error(
        "There was an error getting availability from google calendar: ",
        index.safeStringify({ error, selectedCalendars })
      );
      throw error;
    }
  }
  /**
   * Converts FreeBusy response data to EventBusyDate array
   */
  convertFreeBusyToEventBusyDates(freeBusyResult) {
    if (!freeBusyResult.calendars)
      return [];
    return Object.values(freeBusyResult.calendars).flatMap(
      (calendar2) => calendar2.busy?.map((busyTime) => ({
        start: busyTime.start || "",
        end: busyTime.end || ""
      })) || []
    );
  }
  /**
   * Attempts to get availability from cache
   */
  async tryGetAvailabilityFromCache(timeMin, timeMax, calendarIds) {
    try {
      const calendarCache = await CalendarCache.init(null);
      const cached = await calendarCache.getCachedAvailability({
        credentialId: this.credential.id,
        userId: this.credential.userId,
        args: {
          // Expand the start date to the start of the month to increase cache hits
          timeMin: getTimeMin(timeMin),
          // Expand the end date to the end of the month to increase cache hits
          timeMax: getTimeMax(timeMax),
          items: calendarIds.map((id) => ({ id }))
        }
      });
      if (cached) {
        this.log.debug(
          "[Cache Hit] Returning cached availability result",
          index.safeStringify({ timeMin, timeMax, calendarIds })
        );
        const freeBusyResult = cached.value;
        return this.convertFreeBusyToEventBusyDates(freeBusyResult);
      }
      return null;
    } catch (error) {
      this.log.debug("Cache check failed, proceeding with API call", index.safeStringify(error));
      return null;
    }
  }
  /**
   * Gets calendar IDs for the request, either from selected calendars or fallback logic
   */
  async getCalendarIds(selectedCalendarIds, fallbackToPrimary) {
    if (selectedCalendarIds.length !== 0)
      return selectedCalendarIds;
    const calendar2 = await this.authedCalendar();
    const cals = await this.getAllCalendars(calendar2, ["id", "primary"]);
    if (!cals.length)
      return [];
    if (!fallbackToPrimary) {
      return this.getValidCalendars(cals).map((cal) => cal.id);
    }
    const primaryCalendar = this.filterPrimaryCalendar(cals);
    return primaryCalendar ? [primaryCalendar.id] : [];
  }
  /**
   * Fetches availability data using the cache-or-fetch pattern
   */
  async fetchAvailabilityData(calendarIds, dateFrom, dateTo, shouldServeCache) {
    const fromDate = new Date(dateFrom);
    const toDate = new Date(dateTo);
    const oneDayMs = 1e3 * 60 * 60 * 24;
    const diff = Math.floor((toDate.getTime() - fromDate.getTime()) / oneDayMs);
    if (diff <= 90) {
      const freeBusyData = await this.getCacheOrFetchAvailability(
        {
          timeMin: dateFrom,
          timeMax: dateTo,
          items: calendarIds.map((id) => ({ id }))
        },
        shouldServeCache
      );
      if (!freeBusyData)
        throw new Error("No response from google calendar");
      return freeBusyData.map((freeBusy) => ({ start: freeBusy.start, end: freeBusy.end }));
    }
    const busyData = [];
    const loopsNumber = Math.ceil(diff / 90);
    let currentStartTime = fromDate.getTime();
    const originalEndTime = toDate.getTime();
    const ninetyDaysMs = 90 * 24 * 60 * 60 * 1e3;
    const oneMinuteMs = 60 * 1e3;
    for (let i = 0; i < loopsNumber; i++) {
      let currentEndTime = currentStartTime + ninetyDaysMs;
      if (currentEndTime > originalEndTime) {
        currentEndTime = originalEndTime;
      }
      const chunkData = await this.getCacheOrFetchAvailability(
        {
          timeMin: new Date(currentStartTime).toISOString(),
          timeMax: new Date(currentEndTime).toISOString(),
          items: calendarIds.map((id) => ({ id }))
        },
        shouldServeCache
      );
      if (chunkData) {
        busyData.push(...chunkData.map((freeBusy) => ({ start: freeBusy.start, end: freeBusy.end })));
      }
      currentStartTime = currentEndTime + oneMinuteMs;
    }
    return busyData;
  }
  async getAvailability(dateFrom, dateTo, selectedCalendars, shouldServeCache, fallbackToPrimary) {
    this.log.debug("Getting availability", index.safeStringify({ dateFrom, dateTo, selectedCalendars }));
    const selectedCalendarIds = selectedCalendars.filter((e) => e.integration === this.integrationName).map((e) => e.externalId);
    if (selectedCalendarIds.length === 0 && selectedCalendars.length > 0) {
      return [];
    }
    if (selectedCalendarIds.length > 0 && shouldServeCache !== false) {
      const cachedResult = await this.tryGetAvailabilityFromCache(dateFrom, dateTo, selectedCalendarIds);
      if (cachedResult) {
        return cachedResult;
      }
    }
    this.log.debug(
      "[Cache Miss] Proceeding with Google API calls",
      index.safeStringify({ selectedCalendarIds, fallbackToPrimary })
    );
    try {
      const calendarIds = await this.getCalendarIds(selectedCalendarIds, fallbackToPrimary);
      return await this.fetchAvailabilityData(calendarIds, dateFrom, dateTo, shouldServeCache);
    } catch (error) {
      this.log.error(
        "There was an error getting availability from google calendar: ",
        index.safeStringify({ error, selectedCalendars })
      );
      throw error;
    }
  }
  async listCalendars() {
    this.log.debug("Listing calendars");
    const calendar2 = await this.authedCalendar();
    try {
      const { json: cals } = await this.auth.authManager.request(
        async () => new AxiosLikeResponseToFetchResponse({
          status: 200,
          statusText: "OK",
          data: {
            items: await this.getAllCalendars(calendar2)
          }
        })
      );
      if (!cals.items)
        return [];
      return cals.items.map(
        (cal) => ({
          externalId: cal.id ?? "No id",
          integration: this.integrationName,
          name: cal.summary ?? "No name",
          primary: cal.primary ?? false,
          readOnly: !(cal.accessRole === "writer" || cal.accessRole === "owner") && true,
          email: cal.id ?? ""
        })
      );
    } catch (error) {
      this.log.error("There was an error getting calendars: ", index.safeStringify(error));
      throw error;
    }
  }
  // It would error if the delegation credential is not set up correctly
  async testDelegationCredentialSetup() {
    log.debug("Testing delegation credential setup");
    const calendar2 = await this.authedCalendar();
    const cals = await calendar2.calendarList.list({ fields: "items(id)" });
    return !!cals.data.items;
  }
  /**
   * calendarId is the externalId for the SelectedCalendar
   * It doesn't check if the subscription has expired or not.
   * It just creates a new subscription.
   */
  async watchCalendar({
    calendarId,
    eventTypeIds
  }) {
    log.debug("watchCalendar", index.safeStringify({ calendarId, eventTypeIds }));
    if (!process.env.GOOGLE_WEBHOOK_TOKEN) {
      log.warn("GOOGLE_WEBHOOK_TOKEN is not set, skipping watching calendar");
      return;
    }
    const allCalendarsWithSubscription = await selectedCalendar.SelectedCalendarRepository.findMany({
      where: {
        credentialId: this.credential.id,
        externalId: calendarId,
        integration: this.integrationName,
        googleChannelId: {
          not: null
        }
      }
    });
    const otherCalendarsWithSameSubscription = allCalendarsWithSubscription.filter(
      (sc) => !eventTypeIds?.includes(sc.eventTypeId)
    );
    let googleChannelProps = otherCalendarsWithSameSubscription.length ? {
      kind: otherCalendarsWithSameSubscription[0].googleChannelKind,
      id: otherCalendarsWithSameSubscription[0].googleChannelId,
      resourceId: otherCalendarsWithSameSubscription[0].googleChannelResourceId,
      resourceUri: otherCalendarsWithSameSubscription[0].googleChannelResourceUri,
      expiration: otherCalendarsWithSameSubscription[0].googleChannelExpiration
    } : {};
    if (!otherCalendarsWithSameSubscription.length) {
      try {
        googleChannelProps = await this.startWatchingCalendarsInGoogle({ calendarId });
      } catch (error) {
        this.log.error(`Failed to watch calendar ${calendarId}`, index.safeStringify(error));
        throw error;
      }
    } else {
      index.logger.info(
        `Calendar ${calendarId} is already being watched for event types ${otherCalendarsWithSameSubscription.map(
          (sc) => sc.eventTypeId
        )}. So, not watching again and instead reusing the existing channel`
      );
    }
    await this.upsertSelectedCalendarsForEventTypeIds(
      {
        externalId: calendarId,
        googleChannelId: googleChannelProps.id,
        googleChannelKind: googleChannelProps.kind,
        googleChannelResourceId: googleChannelProps.resourceId,
        googleChannelResourceUri: googleChannelProps.resourceUri,
        googleChannelExpiration: googleChannelProps.expiration
      },
      eventTypeIds
    );
    return googleChannelProps;
  }
  /**
   * GoogleChannel subscription is only stopped when all selectedCalendars are un-watched.
   */
  async unwatchCalendar({
    calendarId,
    eventTypeIds
  }) {
    const credentialId = this.credential.id;
    const eventTypeIdsToBeUnwatched = eventTypeIds;
    const calendarsWithSameCredentialId = await selectedCalendar.SelectedCalendarRepository.findMany({
      where: {
        credentialId
      }
    });
    const calendarWithSameExternalId = calendarsWithSameCredentialId.filter(
      (sc) => sc.externalId === calendarId && sc.integration === this.integrationName
    );
    const calendarsWithSameExternalIdThatAreBeingWatched = calendarWithSameExternalId.filter(
      (sc) => !!sc.googleChannelId
    );
    const calendarsWithSameExternalIdToBeStillWatched = calendarsWithSameExternalIdThatAreBeingWatched.filter(
      (sc) => !eventTypeIdsToBeUnwatched.includes(sc.eventTypeId)
    );
    if (calendarsWithSameExternalIdToBeStillWatched.length) {
      index.logger.info(
        `There are other ${calendarsWithSameExternalIdToBeStillWatched.length} calendars with the same externalId_credentialId. Not unwatching. Just removing the channelId from this selected calendar`
      );
      await this.upsertSelectedCalendarsForEventTypeIds(
        {
          externalId: calendarId,
          googleChannelId: null,
          googleChannelKind: null,
          googleChannelResourceId: null,
          googleChannelResourceUri: null,
          googleChannelExpiration: null
        },
        eventTypeIdsToBeUnwatched
      );
      return;
    }
    const allChannelsForThisCalendarBeingUnwatched = calendarsWithSameExternalIdThatAreBeingWatched.map(
      (sc) => ({
        googleChannelResourceId: sc.googleChannelResourceId,
        googleChannelId: sc.googleChannelId
      })
    );
    await index.prisma.calendarCache.deleteMany({ where: { credentialId } });
    await this.stopWatchingCalendarsInGoogle(allChannelsForThisCalendarBeingUnwatched);
    await this.upsertSelectedCalendarsForEventTypeIds(
      {
        externalId: calendarId,
        googleChannelId: null,
        googleChannelKind: null,
        googleChannelResourceId: null,
        googleChannelResourceUri: null,
        googleChannelExpiration: null
      },
      eventTypeIdsToBeUnwatched
    );
    const remainingCalendars = calendarsWithSameCredentialId.filter(
      (sc) => sc.externalId !== calendarId && sc.integration === this.integrationName
    ) || [];
    if (remainingCalendars.length > 0) {
      await this.fetchAvailabilityAndSetCache(remainingCalendars);
    }
  }
  async setAvailabilityInCache(args, data) {
    log.debug("setAvailabilityInCache", index.safeStringify({ args, data }));
    const calendarCache = await CalendarCache.init(null);
    await calendarCache.upsertCachedAvailability({
      credentialId: this.credential.id,
      userId: this.credential.userId,
      args,
      value: JSON.parse(JSON.stringify(data))
    });
  }
  async fetchAvailabilityAndSetCache(selectedCalendars) {
    this.log.debug("fetchAvailabilityAndSetCache", index.safeStringify({ selectedCalendars }));
    const selectedCalendarsPerEventType = /* @__PURE__ */ new Map();
    selectedCalendars.reduce((acc, selectedCalendar2) => {
      const eventTypeId = selectedCalendar2.eventTypeId ?? null;
      const mapValue = selectedCalendarsPerEventType.get(eventTypeId);
      if (mapValue) {
        mapValue.push(selectedCalendar2);
      } else {
        acc.set(eventTypeId, [selectedCalendar2]);
      }
      return acc;
    }, selectedCalendarsPerEventType);
    for (const [_eventTypeId, selectedCalendars2] of Array.from(selectedCalendarsPerEventType.entries())) {
      const parsedArgs = {
        /** Expand the start date to the start of the month to increase cache hits */
        timeMin: getTimeMin(),
        /** Expand the end date to the end of the month to increase cache hits */
        timeMax: getTimeMax(),
        // Dont use eventTypeId in key because it can be used by any eventType
        // The only reason we are building it per eventType is because there can be different groups of calendars to lookup the availability for
        items: selectedCalendars2.map((sc) => ({ id: sc.externalId }))
      };
      const data = await this.fetchAvailability(parsedArgs);
      await this.setAvailabilityInCache(parsedArgs, data);
    }
    await selectedCalendar.SelectedCalendarRepository.updateManyByCredentialId(this.credential.id, {});
  }
  async createSelectedCalendar(data) {
    return await selectedCalendar.SelectedCalendarRepository.create({
      ...data,
      integration: this.integrationName,
      credentialId: this.credential.id
    });
  }
  async upsertSelectedCalendar(data) {
    if (!this.credential.userId) {
      index.logger.error("upsertSelectedCalendar failed. userId is missing.");
      return;
    }
    return await selectedCalendar.SelectedCalendarRepository.upsert({
      ...data,
      eventTypeId: data.eventTypeId ?? null,
      integration: this.integrationName,
      credentialId: this.credential.id,
      userId: this.credential.userId
    });
  }
  async upsertSelectedCalendarsForEventTypeIds(data, eventTypeIds) {
    log.debug(
      "upsertSelectedCalendarsForEventTypeIds",
      index.safeStringify({ data, eventTypeIds, credential: this.credential })
    );
    if (!this.credential.userId) {
      index.logger.error("upsertSelectedCalendarsForEventTypeIds failed. userId is missing.");
      return;
    }
    await selectedCalendar.SelectedCalendarRepository.upsertManyForEventTypeIds({
      data: {
        ...data,
        integration: this.integrationName,
        credentialId: this.credential.id,
        delegationCredentialId: this.credential.delegatedToId ?? null,
        userId: this.credential.userId
      },
      eventTypeIds
    });
  }
  async getAllCalendars(calendar2, fields = ["id", "summary", "primary", "accessRole"]) {
    let allCalendars = [];
    let pageToken;
    try {
      do {
        const response = await calendar2.calendarList.list({
          fields: `items(${fields.join(",")}),nextPageToken`,
          pageToken,
          maxResults: 250
          // 250 is max
        });
        allCalendars = [...allCalendars, ...response.data.items ?? []];
        pageToken = response.data.nextPageToken;
      } while (pageToken);
      return allCalendars;
    } catch (error) {
      index.logger.error("Error fetching all Google Calendars", { error });
      throw error;
    }
  }
  async getPrimaryCalendar(_calendar) {
    try {
      const calendar2 = _calendar ?? await this.authedCalendar();
      const response = await calendar2.calendars.get({
        calendarId: "primary"
      });
      return response.data;
    } catch (error) {
      index.logger.error("Error getting primary calendar", { error });
      throw error;
    }
  }
  async getMainTimeZone() {
    try {
      const primaryCalendar = await this.getPrimaryCalendar();
      if (!primaryCalendar?.timeZone) {
        this.log.warn("No timezone found in primary calendar, defaulting to UTC");
        return "UTC";
      }
      return primaryCalendar.timeZone;
    } catch (error) {
      this.log.error("Error getting main timezone from Google Calendar", { error });
      throw error;
    }
  }
}
exports.default = GoogleCalendarService;
