import { M as MSTeamsLocationType, g as getRichDescriptionHTML, b as getLocation, h as handleErrorsJson, a as handleErrorsRaw } from "./CalEventParser-gVLQw-Bk.js";
import "./index-rbwdGzy0.js";
import { C as CalendarAppDelegationCredentialInvalidGrantError, a as CalendarAppDelegationCredentialConfigurationError } from "./CalendarAppError-vhtDqQzt.js";
import { l as logger } from "./i18n-49-r55rr.js";
import { g as getTokenObjectFromCredential, O as OAuthManager } from "./markTokenAsExpired-H8gTrqCa.js";
import { o as oAuthManagerHelper } from "./oAuthManagerHelper-6d5bvuCs.js";
import { m as metadata } from "./_metadata-z9hxrtRQ.js";
import { g as getOfficeAppKeys } from "./getOfficeAppKeys-V90saMJX.js";
import dayjs from "dayjs";
class Office365CalendarService {
  constructor(credential) {
    this.url = "";
    this.integrationName = "";
    this.apiGraphUrl = "https://graph.microsoft.com/v1.0";
    this.translateEvent = (event, rescheduledEvent) => {
      const isOnlineMeeting = event.location === MSTeamsLocationType;
      const isRescheduledOnlineMeeting = rescheduledEvent ? rescheduledEvent.isOnlineMeeting : false;
      const existingBody = rescheduledEvent?.body?.contentType === "html" ? rescheduledEvent.body.content : void 0;
      let content = "";
      if (isOnlineMeeting) {
        if (isRescheduledOnlineMeeting && existingBody) {
          content = `
        ${getRichDescriptionHTML(event)}<hr>
        ${existingBody}`.trim();
        } else {
          content = getRichDescriptionHTML(event);
        }
      } else {
        content = event.calendarDescription;
      }
      const office365Event = {
        subject: event.title,
        body: {
          contentType: isOnlineMeeting ? "html" : "text",
          content
        },
        start: {
          dateTime: dayjs(event.startTime).tz(event.organizer.timeZone).format("YYYY-MM-DDTHH:mm:ss"),
          timeZone: event.organizer.timeZone
        },
        end: {
          dateTime: dayjs(event.endTime).tz(event.organizer.timeZone).format("YYYY-MM-DDTHH:mm:ss"),
          timeZone: event.organizer.timeZone
        },
        hideAttendees: !event.seatsPerTimeSlot ? false : !event.seatsShowAttendees,
        organizer: {
          emailAddress: {
            address: event.destinationCalendar ? event.destinationCalendar.find((cal) => cal.userId === event.organizer.id)?.externalId ?? event.organizer.email : event.organizer.email,
            name: event.organizer.name
          }
        },
        attendees: [
          ...event.attendees.map((attendee) => ({
            emailAddress: {
              address: attendee.email,
              name: attendee.name
            },
            type: "required"
          })),
          ...event.team?.members ? event.team?.members.filter((member) => member.email !== this.credential.user?.email).map((member) => {
            const destinationCalendar = event.destinationCalendar && event.destinationCalendar.find((cal) => cal.userId === member.id);
            return {
              emailAddress: {
                address: destinationCalendar?.externalId ?? member.email,
                name: member.name
              },
              type: "required"
            };
          }) : []
        ],
        location: event.location ? { displayName: getLocation(event) } : void 0
      };
      if (event.hideCalendarEventDetails) {
        office365Event.sensitivity = "private";
      }
      if (isOnlineMeeting) {
        office365Event.isOnlineMeeting = true;
        office365Event.allowNewTimeProposals = true;
        office365Event.onlineMeetingProvider = "teamsForBusiness";
        office365Event.location = rescheduledEvent && !isRescheduledOnlineMeeting ? { displayName: "Microsoft Teams Meeting" } : void 0;
      }
      return office365Event;
    };
    this.fetcher = async (endpoint, init) => {
      return this.auth.requestRaw({
        url: `${this.apiGraphUrl}${endpoint}`,
        options: {
          method: "get",
          ...init
        }
      });
    };
    this.fetchResponsesWithNextLink = async (settledResponses) => {
      const alreadySuccess = [];
      const newLinkRequest = [];
      settledResponses?.forEach((response) => {
        if (response.status === 200 && response.body["@odata.nextLink"] === void 0) {
          alreadySuccess.push(response);
        } else {
          const nextLinkUrl = response.body["@odata.nextLink"] ? String(response.body["@odata.nextLink"]).replace(this.apiGraphUrl, "") : "";
          if (nextLinkUrl) {
            newLinkRequest.push({
              id: Number(response.id),
              method: "GET",
              url: nextLinkUrl
            });
          }
          delete response.body["@odata.nextLink"];
          alreadySuccess.push(response);
        }
      });
      if (newLinkRequest.length === 0) {
        return alreadySuccess;
      }
      const newResponse = await this.apiGraphBatchCall(newLinkRequest);
      let newResponseBody = await handleErrorsJson(newResponse);
      if (typeof newResponseBody === "string") {
        newResponseBody = this.handleTextJsonResponseWithHtmlInBody(newResponseBody);
      }
      const newSettledResponses = await this.fetchResponsesWithNextLink(newResponseBody.responses);
      return [...alreadySuccess, ...newSettledResponses];
    };
    this.fetchRequestWithRetryAfter = async (originalRequests, settledPromises, maxRetries, retryCount = 0) => {
      const getRandomness = () => Number(Math.random().toFixed(3));
      let retryAfterTimeout = 0;
      if (retryCount >= maxRetries) {
        return { responses: settledPromises };
      }
      const alreadySuccessRequest = [];
      const failedRequest = [];
      settledPromises.forEach((item) => {
        if (item.status === 200) {
          alreadySuccessRequest.push(item);
        } else if (item.status === 429) {
          const newTimeout = Number(item.headers["Retry-After"]) * 1e3 || 0;
          retryAfterTimeout = newTimeout > retryAfterTimeout ? newTimeout : retryAfterTimeout;
          failedRequest.push(originalRequests[Number(item.id)]);
        }
      });
      if (failedRequest.length === 0) {
        return { responses: alreadySuccessRequest };
      }
      await new Promise((r) => setTimeout(r, retryAfterTimeout + getRandomness()));
      const newResponses = await this.apiGraphBatchCall(failedRequest);
      let newResponseBody = await handleErrorsJson(newResponses);
      if (typeof newResponseBody === "string") {
        newResponseBody = this.handleTextJsonResponseWithHtmlInBody(newResponseBody);
      }
      const retryAfter = !!newResponseBody?.responses && this.findRetryAfterResponse(newResponseBody.responses);
      if (retryAfter && newResponseBody.responses) {
        newResponseBody = await this.fetchRequestWithRetryAfter(
          failedRequest,
          newResponseBody.responses,
          maxRetries,
          retryCount + 1
        );
      }
      return { responses: [...alreadySuccessRequest, ...newResponseBody?.responses || []] };
    };
    this.apiGraphBatchCall = async (requests) => {
      const response = await this.fetcher(`/$batch`, {
        method: "POST",
        body: JSON.stringify({ requests })
      });
      return response;
    };
    this.handleTextJsonResponseWithHtmlInBody = (response) => {
      try {
        const parsedJson = JSON.parse(response);
        return parsedJson;
      } catch (error) {
        const openTag = '"body":<';
        const closeTag = "</html>";
        const htmlBeginning = response.indexOf(openTag) + openTag.length - 1;
        const htmlEnding = response.indexOf(closeTag) + closeTag.length + 2;
        const resultString = `${response.repeat(1).substring(0, htmlBeginning)} ""${response.repeat(1).substring(htmlEnding, response.length)}`;
        return JSON.parse(resultString);
      }
    };
    this.findRetryAfterResponse = (response) => {
      const foundRetry = response.find((request) => request.status === 429);
      return !!foundRetry;
    };
    this.processBusyTimes = (responses) => {
      return responses.reduce(
        (acc, subResponse) => {
          if (!subResponse.body?.value)
            return acc;
          return acc.concat(
            subResponse.body.value.reduce((acc2, evt) => {
              if (evt.showAs === "free" || evt.showAs === "workingElsewhere")
                return acc2;
              return acc2.concat({
                start: `${evt.start.dateTime}Z`,
                end: `${evt.end.dateTime}Z`
              });
            }, [])
          );
        },
        []
      );
    };
    this.integrationName = "office365_calendar";
    const tokenResponse = getTokenObjectFromCredential(credential);
    this.auth = new OAuthManager({
      credentialSyncVariables: oAuthManagerHelper.credentialSyncVariables,
      resourceOwner: {
        type: "user",
        id: credential.userId
      },
      appSlug: metadata.slug,
      currentTokenObject: tokenResponse,
      fetchNewTokenObject: async ({ refreshToken }) => {
        const isDelegated = Boolean(credential?.delegatedTo);
        if (!isDelegated && !refreshToken) {
          return null;
        }
        const { client_id, client_secret } = await this.getAuthCredentials(isDelegated);
        const url = this.getAuthUrl(isDelegated, credential?.delegatedTo?.serviceAccountKey?.tenant_id);
        const bodyParams = {
          scope: isDelegated ? "https://graph.microsoft.com/.default" : "User.Read Calendars.Read Calendars.ReadWrite",
          client_id,
          client_secret,
          grant_type: isDelegated ? "client_credentials" : "refresh_token",
          ...isDelegated ? {} : { refresh_token: refreshToken ?? "" }
        };
        return fetch(url, {
          method: "POST",
          headers: { "Content-Type": "application/x-www-form-urlencoded" },
          body: new URLSearchParams(bodyParams)
        });
      },
      isTokenObjectUnusable: async function() {
        return null;
      },
      isAccessTokenUnusable: async function() {
        return null;
      },
      invalidateTokenObject: () => oAuthManagerHelper.invalidateCredential(credential.id),
      expireAccessToken: () => oAuthManagerHelper.markTokenAsExpired(credential),
      updateTokenObject: (tokenObject) => {
        if (!Boolean(credential.delegatedTo)) {
          return oAuthManagerHelper.updateTokenObject({ tokenObject, credentialId: credential.id });
        }
        return Promise.resolve();
      }
    });
    this.credential = credential;
    this.log = logger.getSubLogger({ prefix: [`[[lib] ${this.integrationName}`] });
  }
  getAuthUrl(delegatedTo, tenantId) {
    if (delegatedTo) {
      if (!tenantId) {
        throw new CalendarAppDelegationCredentialInvalidGrantError(
          "Invalid DelegationCredential Settings: tenantId is missing"
        );
      }
      return `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
    }
    return "https://login.microsoftonline.com/common/oauth2/v2.0/token";
  }
  async getAuthCredentials(isDelegated) {
    if (isDelegated) {
      const client_id = this.credential?.delegatedTo?.serviceAccountKey?.client_id;
      const client_secret = this.credential?.delegatedTo?.serviceAccountKey?.private_key;
      if (!client_id || !client_secret) {
        throw new CalendarAppDelegationCredentialConfigurationError(
          "Delegation credential without clientId or Secret"
        );
      }
      return { client_id, client_secret };
    }
    return getOfficeAppKeys();
  }
  async getAzureUserId(credential) {
    if (this.azureUserId)
      return this.azureUserId;
    const isDelegated = Boolean(credential?.delegatedTo);
    if (!isDelegated)
      return;
    const url = this.getAuthUrl(isDelegated, credential?.delegatedTo?.serviceAccountKey?.tenant_id);
    const delegationCredentialClientId = credential.delegatedTo?.serviceAccountKey?.client_id;
    const delegationCredentialClientSecret = credential.delegatedTo?.serviceAccountKey?.private_key;
    if (!delegationCredentialClientId || !delegationCredentialClientSecret) {
      throw new CalendarAppDelegationCredentialConfigurationError(
        "Delegation credential without clientId or Secret"
      );
    }
    const loginResponse = await fetch(url, {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: new URLSearchParams({
        scope: "https://graph.microsoft.com/.default",
        client_id: delegationCredentialClientId,
        grant_type: "client_credentials",
        client_secret: delegationCredentialClientSecret
      })
    });
    if (!this.azureUserId && credential?.delegatedTo) {
      const clonedResponse = loginResponse.clone();
      const parsedLoginResponse = await clonedResponse.json();
      const token = parsedLoginResponse?.access_token;
      const oauthClientIdAliasRegex = /\+[a-zA-Z0-9]{25}/;
      const email = this.credential?.user?.email.replace(oauthClientIdAliasRegex, "");
      const encodedFilter = encodeURIComponent(`mail eq '${email}'`);
      const queryParams = `$filter=${encodedFilter}`;
      const response = await fetch(`https://graph.microsoft.com/v1.0/users?${queryParams}`, {
        method: "GET",
        headers: {
          "Content-Type": "application/x-www-form-urlencoded",
          Authorization: `Bearer ${token}`
        }
      });
      const parsedBody = await response.json();
      if (!parsedBody?.value?.[0]?.id) {
        throw new CalendarAppDelegationCredentialInvalidGrantError(
          "User might not exist in Microsoft Azure Active Directory"
        );
      }
      this.azureUserId = parsedBody.value[0].id;
    }
    return this.azureUserId;
  }
  // It would error if the delegation credential is not set up correctly
  async testDelegationCredentialSetup() {
    const delegationCredentialClientId = this.credential.delegatedTo?.serviceAccountKey?.client_id;
    const delegationCredentialClientSecret = this.credential.delegatedTo?.serviceAccountKey?.private_key;
    const url = this.getAuthUrl(
      Boolean(this.credential?.delegatedTo),
      this.credential?.delegatedTo?.serviceAccountKey?.tenant_id
    );
    if (!delegationCredentialClientId || !delegationCredentialClientSecret) {
      return false;
    }
    const loginResponse = await fetch(url, {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: new URLSearchParams({
        scope: "https://graph.microsoft.com/.default",
        client_id: delegationCredentialClientId,
        grant_type: "client_credentials",
        client_secret: delegationCredentialClientSecret
      })
    });
    const parsedLoginResponse = await loginResponse.json();
    return Boolean(parsedLoginResponse?.access_token);
  }
  async getUserEndpoint() {
    const azureUserId = await this.getAzureUserId(this.credential);
    return azureUserId ? `/users/${this.azureUserId}` : "/me";
  }
  async createEvent(event, credentialId) {
    const mainHostDestinationCalendar = event.destinationCalendar ? event.destinationCalendar.find((cal) => cal.credentialId === credentialId) ?? event.destinationCalendar[0] : void 0;
    try {
      const eventsUrl = mainHostDestinationCalendar?.externalId ? `${await this.getUserEndpoint()}/calendars/${mainHostDestinationCalendar?.externalId}/events` : `${await this.getUserEndpoint()}/calendar/events`;
      const response = await this.fetcher(eventsUrl, {
        method: "POST",
        body: JSON.stringify(this.translateEvent(event))
      });
      const responseJson = await handleErrorsJson(response);
      if (responseJson?.onlineMeeting?.joinUrl) {
        responseJson.url = responseJson?.onlineMeeting?.joinUrl;
      }
      return { ...responseJson, iCalUID: responseJson.iCalUId };
    } catch (error) {
      this.log.error(error);
      throw error;
    }
  }
  async updateEvent(uid, event) {
    try {
      let rescheduledEvent;
      if (event.location === MSTeamsLocationType) {
        const response2 = await this.fetcher(`${await this.getUserEndpoint()}/calendar/events/${uid}`, {
          method: "GET"
        });
        rescheduledEvent = await handleErrorsJson(response2);
      }
      const response = await this.fetcher(`${await this.getUserEndpoint()}/calendar/events/${uid}`, {
        method: "PATCH",
        body: JSON.stringify(this.translateEvent(event, rescheduledEvent))
      });
      const responseJson = await handleErrorsJson(response);
      if (responseJson?.onlineMeeting?.joinUrl) {
        responseJson.url = responseJson?.onlineMeeting?.joinUrl;
      }
      return { ...responseJson, iCalUID: responseJson.iCalUId };
    } catch (error) {
      this.log.error(error);
      throw error;
    }
  }
  async deleteEvent(uid) {
    try {
      const response = await this.fetcher(`${await this.getUserEndpoint()}/calendar/events/${uid}`, {
        method: "DELETE"
      });
      handleErrorsRaw(response);
    } catch (error) {
      this.log.error(error);
      throw error;
    }
  }
  async getAvailability(dateFrom, dateTo, selectedCalendars) {
    const dateFromParsed = new Date(dateFrom);
    const dateToParsed = new Date(dateTo);
    const filter = `?startDateTime=${encodeURIComponent(
      dateFromParsed.toISOString()
    )}&endDateTime=${encodeURIComponent(dateToParsed.toISOString())}`;
    const calendarSelectParams = "$select=showAs,start,end";
    try {
      const selectedCalendarIds = selectedCalendars.reduce((calendarIds, calendar) => {
        if (calendar.integration === this.integrationName && calendar.externalId)
          calendarIds.push(calendar.externalId);
        return calendarIds;
      }, []);
      if (selectedCalendarIds.length === 0 && selectedCalendars.length > 0) {
        return Promise.resolve([]);
      }
      const ids = await (selectedCalendarIds.length === 0 ? this.listCalendars().then((cals) => cals.map((e_2) => e_2.externalId).filter(Boolean) || []) : Promise.resolve(selectedCalendarIds));
      const requestsPromises = ids.map(async (calendarId, id) => ({
        id,
        method: "GET",
        url: `${await this.getUserEndpoint()}/calendars/${calendarId}/calendarView${filter}&${calendarSelectParams}`
      }));
      const requests = await Promise.all(requestsPromises);
      const response = await this.apiGraphBatchCall(requests);
      const responseBody = await this.handleErrorJsonOffice365Calendar(response);
      let responseBatchApi = { responses: [] };
      if (typeof responseBody === "string") {
        responseBatchApi = this.handleTextJsonResponseWithHtmlInBody(responseBody);
      }
      let alreadySuccessResponse = [];
      const retryAfter = !!responseBatchApi?.responses && this.findRetryAfterResponse(responseBatchApi.responses);
      if (retryAfter && responseBatchApi.responses) {
        responseBatchApi = await this.fetchRequestWithRetryAfter(requests, responseBatchApi.responses, 2);
      }
      alreadySuccessResponse = await this.fetchResponsesWithNextLink(responseBatchApi.responses);
      return alreadySuccessResponse ? this.processBusyTimes(alreadySuccessResponse) : [];
    } catch (err) {
      return Promise.reject([]);
    }
  }
  async listCalendars() {
    const officeCalendars = [];
    let finishedParsingCalendars = false;
    const calendarFilterParam = "$select=id,name,isDefaultCalendar,canEdit";
    let requestLink = `${await this.getUserEndpoint()}/calendars?${calendarFilterParam}`;
    while (!finishedParsingCalendars) {
      const response = await this.fetcher(requestLink);
      let responseBody = await handleErrorsJson(
        response
      );
      if (typeof responseBody === "string") {
        responseBody = JSON.parse(responseBody);
      }
      officeCalendars.push(...responseBody.value);
      if (responseBody["@odata.nextLink"]) {
        requestLink = responseBody["@odata.nextLink"].replace(this.apiGraphUrl, "");
      } else {
        finishedParsingCalendars = true;
      }
    }
    const user = await this.fetcher(`${await this.getUserEndpoint()}`);
    const userResponseBody = await handleErrorsJson(user);
    const email = userResponseBody.mail ?? userResponseBody.userPrincipalName;
    return officeCalendars.map((cal) => {
      const calendar = {
        externalId: cal.id ?? "No Id",
        integration: this.integrationName,
        name: cal.name ?? "No calendar name",
        primary: cal.isDefaultCalendar ?? false,
        readOnly: !cal.canEdit && true,
        email: email ?? ""
      };
      return calendar;
    });
  }
  async handleErrorJsonOffice365Calendar(response) {
    if (response.headers.get("content-encoding") === "gzip") {
      return response.text();
    }
    if (response.status === 204) {
      return {};
    }
    if (!response.ok) {
      let errorBody;
      try {
        errorBody = await response.json();
      } catch (e) {
        errorBody = await response.text();
      }
      this.log.error(
        `handleErrorJsonOffice365Calendar: Office365 API request failed with status ${response.status}`,
        errorBody
      );
    }
    return response.json();
  }
  async getMainTimeZone() {
    try {
      const response = await this.fetcher(`${await this.getUserEndpoint()}/mailboxSettings/timeZone`);
      const timezone = await handleErrorsJson(response);
      if (!timezone || typeof timezone !== "string") {
        this.log.warn("No timezone found in outlook mailbox settings, defaulting to Europe/London", {
          timezone
        });
        return "Europe/London";
      }
      this.log.info("timezone found in outlook mailbox settings", { timezone });
      return timezone;
    } catch (error) {
      this.log.error("Error getting main timezone from Office365 Calendar", { error });
      throw error;
    }
  }
}
export {
  Office365CalendarService as default
};
