import { sortBy } from "lodash";
import { g as getCalendar } from "./getCalendar-ZZLZQRN8.js";
import { f as formatCalEvent, b as getUid, e as getRichDescription, M as MeetLocationType } from "./CalEventParser-ZzmnrFso.js";
import { g as getApps } from "./utils-xu1OORoz.js";
import "./index-rbwdGzy0.js";
import { C as CalendarAppDelegationCredentialError } from "./CalendarAppError-I94WN34B.js";
import { l as logger, i as isDelegationCredential, b as safeStringify, j as symmetricDecrypt, a6 as ORGANIZER_EMAIL_EXEMPT_DOMAINS } from "./user--mpkKOwW.js";
import { b as getPiiFreeSelectedCalendar, g as getPiiFreeCredential, a as getPiiFreeCalendarEvent } from "./piiFreeData-dxrPDUBm.js";
import "./perfObserver-0o0QCLMi.js";
import { performance } from "perf_hooks";
import dayjs from "dayjs";
const log$1 = logger.getSubLogger({ prefix: ["getCalendarsEvents"] });
const CALENDSO_ENCRYPTION_KEY = process.env.CALENDSO_ENCRYPTION_KEY || "";
const getCalendarsEventsWithTimezones = async (withCredentials, dateFrom, dateTo, selectedCalendars) => {
  const calendarCredentials = withCredentials.filter((credential) => credential.type === "google_calendar").filter((credential) => !credential.invalid);
  const calendarAndCredentialPairs = await Promise.all(
    calendarCredentials.map(async (credential) => {
      const calendar = await getCalendar(credential);
      return [calendar, credential];
    })
  );
  const calendars = calendarAndCredentialPairs.map(([calendar]) => calendar);
  const calendarToCredentialMap = new Map(calendarAndCredentialPairs);
  const results = calendars.map(async (c, i) => {
    if (!c)
      return [];
    const { type } = calendarCredentials[i];
    const credential = calendarToCredentialMap.get(c);
    const passedSelectedCalendars = credential ? filterSelectedCalendarsForCredential(selectedCalendars, credential) : selectedCalendars.filter((sc) => sc.integration === type).sort((a, b) => a.externalId < b.externalId ? -1 : a.externalId > b.externalId ? 1 : 0);
    const isADelegationCredential = credential && isDelegationCredential({ credentialId: credential.id });
    const allowFallbackToPrimary = isADelegationCredential;
    if (!passedSelectedCalendars.length) {
      if (!isADelegationCredential) {
        log$1.error(
          `No selected calendars for non DWD credential: Skipping getAvailability call for credential ${credential?.id}`
        );
        return [];
      }
      log$1.info("Allowing getAvailability even without any selected calendars for Delegation Credential");
    }
    const eventBusyDates = await c.getAvailabilityWithTimeZones?.(
      dateFrom,
      dateTo,
      passedSelectedCalendars,
      allowFallbackToPrimary
    ) || [];
    return eventBusyDates;
  });
  const awaitedResults = await Promise.all(results);
  return awaitedResults;
};
const getCalendarsEvents = async (withCredentials, dateFrom, dateTo, selectedCalendars, shouldServeCache) => {
  const calendarCredentials = withCredentials.filter((credential) => credential.type.endsWith("_calendar")).filter((credential) => !credential.invalid);
  const calendarAndCredentialPairs = await Promise.all(
    calendarCredentials.map(async (credential) => {
      const calendar = await getCalendar(credential);
      return [calendar, credential];
    })
  );
  const calendars = calendarAndCredentialPairs.map(([calendar]) => calendar);
  const calendarToCredentialMap = new Map(calendarAndCredentialPairs);
  performance.mark("getBusyCalendarTimesStart");
  const results = calendars.map(async (calendarService, i) => {
    if (!calendarService)
      return [];
    const { type, appId } = calendarCredentials[i];
    const credential = calendarToCredentialMap.get(calendarService);
    const passedSelectedCalendars = credential ? filterSelectedCalendarsForCredential(selectedCalendars, credential) : selectedCalendars.filter((sc) => sc.integration === type).sort((a, b) => a.externalId < b.externalId ? -1 : a.externalId > b.externalId ? 1 : 0);
    const isADelegationCredential = credential && isDelegationCredential({ credentialId: credential.id });
    const allowFallbackToPrimary = isADelegationCredential;
    if (!passedSelectedCalendars.length) {
      if (!isADelegationCredential) {
        log$1.error(
          `No selected calendars for non DWD credential: Skipping getAvailability call for credential ${credential?.id}`
        );
        return [];
      }
      log$1.info("Allowing getAvailability even without any selected calendars for Delegation Credential");
    }
    const selectedCalendarIds = passedSelectedCalendars.map((sc) => sc.externalId);
    performance.mark("eventBusyDatesStart");
    log$1.debug(
      `Getting availability for`,
      safeStringify({
        calendarService: calendarService.constructor.name,
        selectedCalendars: passedSelectedCalendars.map(getPiiFreeSelectedCalendar)
      })
    );
    const eventBusyDates = await calendarService.getAvailability(
      dateFrom,
      dateTo,
      passedSelectedCalendars,
      shouldServeCache,
      allowFallbackToPrimary
    );
    performance.mark("eventBusyDatesEnd");
    performance.measure(
      `[getAvailability for ${selectedCalendarIds.join(", ")}][$1]'`,
      "eventBusyDatesStart",
      "eventBusyDatesEnd"
    );
    return eventBusyDates.map((a) => ({
      ...a,
      source: `${appId}`
    }));
  });
  const awaitedResults = await Promise.all(results);
  performance.mark("getBusyCalendarTimesEnd");
  performance.measure(
    `getBusyCalendarTimes took $1 for creds ${calendarCredentials.map((cred) => cred.id)}`,
    "getBusyCalendarTimesStart",
    "getBusyCalendarTimesEnd"
  );
  log$1.debug(
    "Result",
    safeStringify({
      calendarCredentials: calendarCredentials.map(getPiiFreeCredential),
      selectedCalendars: selectedCalendars.map(getPiiFreeSelectedCalendar),
      calendarEvents: awaitedResults
    })
  );
  return awaitedResults;
};
const getCalendarsEvents$1 = getCalendarsEvents;
function getServerUrlFromCalendarExternalId(externalId) {
  try {
    const url = new URL(externalId);
    return `${url.protocol}//${url.host}`;
  } catch (error) {
    return null;
  }
}
function getServerUrlFromCredential(credential) {
  try {
    if (credential.type !== "caldav_calendar") {
      return null;
    }
    const decryptedData = JSON.parse(symmetricDecrypt(credential.key, CALENDSO_ENCRYPTION_KEY));
    if (!decryptedData.url) {
      return null;
    }
    const url = new URL(decryptedData.url);
    return `${url.protocol}//${url.host}`;
  } catch (error) {
    return null;
  }
}
function filterSelectedCalendarsForCredential(selectedCalendars, credential) {
  const { type } = credential;
  if (type !== "caldav_calendar") {
    return selectedCalendars.filter((sc) => sc.integration === type);
  }
  const credentialServerUrl = getServerUrlFromCredential(credential);
  if (!credentialServerUrl) {
    log$1.warn("Could not extract server URL from CalDAV credential", {
      credentialId: credential.id
    });
    return [];
  }
  return selectedCalendars.filter((sc) => {
    if (sc.integration !== type) {
      return false;
    }
    const calendarServerUrl = getServerUrlFromCalendarExternalId(sc.externalId);
    if (!calendarServerUrl) {
      log$1.warn("Could not extract server URL from calendar externalId", {
        externalId: sc.externalId,
        integration: sc.integration
      });
      return false;
    }
    const matches = credentialServerUrl === calendarServerUrl;
    if (!matches) {
      log$1.debug("CalDAV calendar server URL does not match credential server URL", {
        credentialId: credential.id,
        credentialServerUrl,
        calendarServerUrl,
        calendarExternalId: sc.externalId
      });
    }
    return matches;
  });
}
const log = logger.getSubLogger({ prefix: ["CalendarManager"] });
const getCalendarCredentials = (credentials) => {
  const calendarCredentials = getApps(credentials, true).filter((app) => app.type.endsWith("_calendar")).flatMap((app) => {
    const credentials2 = app.credentials.flatMap((credential) => {
      const calendar = getCalendar(credential);
      return app.variant === "calendar" ? [{ integration: app, credential, calendar }] : [];
    });
    return credentials2.length ? credentials2 : [];
  });
  return calendarCredentials;
};
const getConnectedCalendars = async (calendarCredentials, selectedCalendars, destinationCalendarExternalId) => {
  let destinationCalendar;
  const connectedCalendars = await Promise.all(
    calendarCredentials.map(async (item) => {
      try {
        const { integration, credential } = item;
        const safeToSendIntegration = cleanIntegrationKeys(integration);
        const calendar = await item.calendar;
        const credentialId = credential.id;
        const delegationCredentialId = credential.delegatedToId ?? null;
        if (!calendar) {
          return {
            integration: safeToSendIntegration,
            credentialId,
            delegationCredentialId
          };
        }
        const cals = await calendar.listCalendars();
        const calendars = sortBy(
          cals.map((cal) => {
            if (cal.externalId === destinationCalendarExternalId)
              destinationCalendar = cal;
            return {
              ...cal,
              readOnly: cal.readOnly || false,
              primary: cal.primary || null,
              isSelected: selectedCalendars.some((selected) => selected.externalId === cal.externalId),
              credentialId,
              delegationCredentialId
            };
          }),
          ["primary"]
        );
        const primary = calendars.find((item2) => item2.primary) ?? calendars.find((cal) => cal !== void 0);
        if (!primary) {
          return {
            integration: safeToSendIntegration,
            credentialId,
            error: {
              message: "No primary calendar found"
            }
          };
        }
        if (destinationCalendar && !Object.isFrozen(destinationCalendar)) {
          destinationCalendar.primaryEmail = primary.email;
          destinationCalendar.integrationTitle = integration.title;
          destinationCalendar = Object.freeze(destinationCalendar);
        }
        return {
          integration: safeToSendIntegration,
          credentialId,
          delegationCredentialId,
          primary,
          calendars
        };
      } catch (error) {
        let errorMessage = "Could not get connected calendars";
        if (error instanceof Error) {
          if (error.message === "invalid_grant") {
            errorMessage = "Access token expired or revoked";
          }
        }
        if (error instanceof CalendarAppDelegationCredentialError) {
          errorMessage = error.message;
        }
        log.error("getConnectedCalendars failed", safeStringify(error), safeStringify({ item }));
        return {
          integration: cleanIntegrationKeys(item.integration),
          credentialId: item.credential.id,
          delegationCredentialId: item.credential.delegatedToId,
          error: {
            message: errorMessage
          }
        };
      }
    })
  );
  return { connectedCalendars, destinationCalendar };
};
const cleanIntegrationKeys = (appIntegration) => {
  const { credentials, credential, ...rest } = appIntegration;
  return rest;
};
const deduplicateCredentialsBasedOnSelectedCalendars = ({
  credentials,
  selectedCalendars
}) => {
  if (credentials.length === 0) {
    return credentials;
  }
  const userEmail = credentials[0].user?.email;
  if (!userEmail) {
    return credentials;
  }
  const delegationCredentials = credentials.filter((credential) => credential.delegatedToId);
  const selectedCalendarsWithUserEmailConnectedWithRegularCredential = selectedCalendars.filter(
    (calendar) => calendar.externalId === userEmail && calendar.credentialId && calendar.credentialId > 0
  );
  if (delegationCredentials.length === 0 || selectedCalendarsWithUserEmailConnectedWithRegularCredential.length === 0) {
    return credentials;
  }
  const deduplicatedCredentials = [...credentials];
  const credentialIdsToRemove = selectedCalendarsWithUserEmailConnectedWithRegularCredential.filter(
    (calendar) => delegationCredentials.some((credential) => credential.type === calendar.integration)
  ).map((calendar) => calendar.credentialId);
  return deduplicatedCredentials.filter((credential) => !credentialIdsToRemove.includes(credential.id));
};
const getBusyCalendarTimes = async (withCredentials, dateFrom, dateTo, selectedCalendars, shouldServeCache, includeTimeZone) => {
  let results = [];
  const deduplicatedCredentials = deduplicateCredentialsBasedOnSelectedCalendars({
    credentials: withCredentials,
    selectedCalendars
  });
  if (deduplicatedCredentials.length !== withCredentials.length) {
    log.info(
      "Deduplicated credentials and removed",
      withCredentials.length - deduplicatedCredentials.length,
      "duplicates. Total number of credentials now is",
      deduplicatedCredentials.length
    );
  }
  const startDate = dayjs(dateFrom).subtract(11, "hours").format();
  const endDate = dayjs(dateTo).add(14, "hours").format();
  try {
    if (includeTimeZone) {
      results = await getCalendarsEventsWithTimezones(
        deduplicatedCredentials,
        startDate,
        endDate,
        selectedCalendars
      );
    } else {
      results = await getCalendarsEvents$1(
        deduplicatedCredentials,
        startDate,
        endDate,
        selectedCalendars,
        shouldServeCache
      );
    }
  } catch (e) {
    log.warn(`Error getting calendar availability`, {
      selectedCalendarIds: selectedCalendars.map((calendar) => calendar.externalId),
      error: safeStringify(e)
    });
    return { success: false, data: [{ start: startDate, end: endDate, source: "error-placeholder" }] };
  }
  return { success: true, data: results.reduce((acc, availability) => acc.concat(availability), []) };
};
const createEvent = async (credential, originalEvent, externalId) => {
  const formattedEvent = formatCalEvent(originalEvent);
  const uid = getUid(formattedEvent);
  const calendar = await getCalendar(credential);
  let success = true;
  let calError = void 0;
  log.debug(
    "Creating calendar event",
    safeStringify({
      calEvent: getPiiFreeCalendarEvent(formattedEvent)
    })
  );
  if (formattedEvent.hideCalendarNotes) {
    formattedEvent.additionalNotes = "Notes have been hidden by the organizer";
  }
  const calEvent = processEvent(formattedEvent);
  const externalCalendarIdWhenDelegationCredentialIsChosen = credential.delegatedToId ? externalId : void 0;
  const creationResult = calendar ? await calendar.createEvent(calEvent, credential.id, externalCalendarIdWhenDelegationCredentialIsChosen).catch(async (error) => {
    success = false;
    if (error?.code === 404) {
      return void 0;
    }
    if (error?.calError) {
      calError = error.calError;
    }
    log.error(
      "createEvent failed",
      safeStringify(error),
      safeStringify({ calEvent: getPiiFreeCalendarEvent(calEvent) })
    );
    return void 0;
  }) : void 0;
  if (!creationResult) {
    logger.error(
      "createEvent failed",
      safeStringify({
        success,
        uid,
        creationResult,
        originalEvent: getPiiFreeCalendarEvent(calEvent),
        calError
      })
    );
  }
  log.debug(
    "Created calendar event",
    safeStringify({
      calEvent: getPiiFreeCalendarEvent(calEvent),
      creationResult
    })
  );
  return {
    appName: credential.appId || "",
    type: credential.type,
    success,
    uid,
    iCalUID: creationResult?.iCalUID || void 0,
    createdEvent: creationResult,
    originalEvent: calEvent,
    calError,
    calWarnings: creationResult?.additionalInfo?.calWarnings || [],
    externalId,
    credentialId: credential.id,
    delegatedToId: credential.delegatedToId ?? void 0
  };
};
const updateEvent = async (credential, rawCalEvent, bookingRefUid, externalCalendarId) => {
  const formattedEvent = formatCalEvent(rawCalEvent);
  const calEvent = processEvent(formattedEvent);
  const uid = getUid(calEvent);
  const calendar = await getCalendar(credential);
  let success = false;
  let calError = void 0;
  let calWarnings = [];
  log.debug(
    "Updating calendar event",
    safeStringify({
      bookingRefUid,
      calEvent: getPiiFreeCalendarEvent(calEvent)
    })
  );
  if (bookingRefUid === "") {
    log.error(
      "updateEvent failed",
      "bookingRefUid is empty",
      safeStringify({ calEvent: getPiiFreeCalendarEvent(calEvent) })
    );
  }
  const updatedResult = calendar && bookingRefUid ? await calendar.updateEvent(bookingRefUid, calEvent, externalCalendarId).then((event) => {
    success = true;
    return event;
  }).catch(async (e) => {
    log.error(
      "updateEvent failed",
      safeStringify({ e, calEvent: getPiiFreeCalendarEvent(calEvent) })
    );
    if (e?.calError) {
      calError = e.calError;
    }
    return void 0;
  }) : void 0;
  if (!updatedResult) {
    logger.error(
      "updateEvent failed",
      safeStringify({
        success,
        bookingRefUid,
        credential: getPiiFreeCredential(credential),
        originalEvent: getPiiFreeCalendarEvent(calEvent),
        calError
      })
    );
  }
  if (Array.isArray(updatedResult)) {
    calWarnings = updatedResult.flatMap((res) => res.additionalInfo?.calWarnings ?? []);
  } else {
    calWarnings = updatedResult?.additionalInfo?.calWarnings || [];
  }
  return {
    appName: credential.appId || "",
    type: credential.type,
    success,
    uid,
    updatedEvent: updatedResult,
    originalEvent: calEvent,
    calError,
    calWarnings
  };
};
const deleteEvent = async ({
  credential,
  bookingRefUid,
  event,
  externalCalendarId
}) => {
  const calendar = await getCalendar(credential);
  log.debug(
    "Deleting calendar event",
    safeStringify({
      bookingRefUid,
      event: getPiiFreeCalendarEvent(event)
    })
  );
  if (calendar) {
    return calendar.deleteEvent(bookingRefUid, event, externalCalendarId);
  } else {
    log.error(
      "Could not do deleteEvent - No calendar adapter found",
      safeStringify({
        credential: getPiiFreeCredential(credential),
        event
      })
    );
  }
  return Promise.resolve({});
};
const processEvent = (calEvent) => {
  const calendarEvent = {
    ...calEvent,
    calendarDescription: getRichDescription(calEvent)
  };
  const isMeetLocationType = calEvent.location === MeetLocationType;
  const isOrganizerExempt = ORGANIZER_EMAIL_EXEMPT_DOMAINS?.split(",").filter((domain) => domain.trim() !== "").some((domain) => calEvent.organizer.email.toLowerCase().endsWith(domain.toLowerCase()));
  if (calEvent.hideOrganizerEmail && !isOrganizerExempt && !isMeetLocationType) {
    calendarEvent.attendees = [];
  }
  return calendarEvent;
};
export {
  getCalendarCredentials as a,
  getConnectedCalendars as b,
  createEvent as c,
  deleteEvent as d,
  getBusyCalendarTimes as g,
  updateEvent as u
};
