"use strict";
const lodash = require("lodash");
const getCalendar = require("./getCalendar-WIf1F4JR.cjs");
const CalEventParser = require("./CalEventParser-60bHdlQO.cjs");
const utils = require("./utils-U8_Jj24h.cjs");
require("./index-Br7AzmuA.cjs");
const CalendarAppError = require("./CalendarAppError-RC_O0RpM.cjs");
const index = require("./user-v6mfWQ2d.cjs");
const piiFreeData = require("./piiFreeData-FAapPrV2.cjs");
require("./perfObserver-K8wfcLIh.cjs");
const perf_hooks = require("perf_hooks");
const dayjs = require("dayjs");
const log$1 = index.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.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 && index.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.getCalendar(credential);
      return [calendar, credential];
    })
  );
  const calendars = calendarAndCredentialPairs.map(([calendar]) => calendar);
  const calendarToCredentialMap = new Map(calendarAndCredentialPairs);
  perf_hooks.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 && index.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);
    perf_hooks.performance.mark("eventBusyDatesStart");
    log$1.debug(
      `Getting availability for`,
      index.safeStringify({
        calendarService: calendarService.constructor.name,
        selectedCalendars: passedSelectedCalendars.map(piiFreeData.getPiiFreeSelectedCalendar)
      })
    );
    const eventBusyDates = await calendarService.getAvailability(
      dateFrom,
      dateTo,
      passedSelectedCalendars,
      shouldServeCache,
      allowFallbackToPrimary
    );
    perf_hooks.performance.mark("eventBusyDatesEnd");
    perf_hooks.performance.measure(
      `[getAvailability for ${selectedCalendarIds.join(", ")}][$1]'`,
      "eventBusyDatesStart",
      "eventBusyDatesEnd"
    );
    return eventBusyDates.map((a) => ({
      ...a,
      source: `${appId}`
    }));
  });
  const awaitedResults = await Promise.all(results);
  perf_hooks.performance.mark("getBusyCalendarTimesEnd");
  perf_hooks.performance.measure(
    `getBusyCalendarTimes took $1 for creds ${calendarCredentials.map((cred) => cred.id)}`,
    "getBusyCalendarTimesStart",
    "getBusyCalendarTimesEnd"
  );
  log$1.debug(
    "Result",
    index.safeStringify({
      calendarCredentials: calendarCredentials.map(piiFreeData.getPiiFreeCredential),
      selectedCalendars: selectedCalendars.map(piiFreeData.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(index.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 = index.logger.getSubLogger({ prefix: ["CalendarManager"] });
const getCalendarCredentials = (credentials) => {
  const calendarCredentials = utils.getApps(credentials, true).filter((app) => app.type.endsWith("_calendar")).flatMap((app) => {
    const credentials2 = app.credentials.flatMap((credential) => {
      const calendar = getCalendar.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 = lodash.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 CalendarAppError.CalendarAppDelegationCredentialError) {
          errorMessage = error.message;
        }
        log.error("getConnectedCalendars failed", index.safeStringify(error), index.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: index.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 = CalEventParser.formatCalEvent(originalEvent);
  const uid = CalEventParser.getUid(formattedEvent);
  const calendar = await getCalendar.getCalendar(credential);
  let success = true;
  let calError = void 0;
  log.debug(
    "Creating calendar event",
    index.safeStringify({
      calEvent: piiFreeData.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",
      index.safeStringify(error),
      index.safeStringify({ calEvent: piiFreeData.getPiiFreeCalendarEvent(calEvent) })
    );
    return void 0;
  }) : void 0;
  if (!creationResult) {
    index.logger.error(
      "createEvent failed",
      index.safeStringify({
        success,
        uid,
        creationResult,
        originalEvent: piiFreeData.getPiiFreeCalendarEvent(calEvent),
        calError
      })
    );
  }
  log.debug(
    "Created calendar event",
    index.safeStringify({
      calEvent: piiFreeData.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 = CalEventParser.formatCalEvent(rawCalEvent);
  const calEvent = processEvent(formattedEvent);
  const uid = CalEventParser.getUid(calEvent);
  const calendar = await getCalendar.getCalendar(credential);
  let success = false;
  let calError = void 0;
  let calWarnings = [];
  log.debug(
    "Updating calendar event",
    index.safeStringify({
      bookingRefUid,
      calEvent: piiFreeData.getPiiFreeCalendarEvent(calEvent)
    })
  );
  if (bookingRefUid === "") {
    log.error(
      "updateEvent failed",
      "bookingRefUid is empty",
      index.safeStringify({ calEvent: piiFreeData.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",
      index.safeStringify({ e, calEvent: piiFreeData.getPiiFreeCalendarEvent(calEvent) })
    );
    if (e?.calError) {
      calError = e.calError;
    }
    return void 0;
  }) : void 0;
  if (!updatedResult) {
    index.logger.error(
      "updateEvent failed",
      index.safeStringify({
        success,
        bookingRefUid,
        credential: piiFreeData.getPiiFreeCredential(credential),
        originalEvent: piiFreeData.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.getCalendar(credential);
  log.debug(
    "Deleting calendar event",
    index.safeStringify({
      bookingRefUid,
      event: piiFreeData.getPiiFreeCalendarEvent(event)
    })
  );
  if (calendar) {
    return calendar.deleteEvent(bookingRefUid, event, externalCalendarId);
  } else {
    log.error(
      "Could not do deleteEvent - No calendar adapter found",
      index.safeStringify({
        credential: piiFreeData.getPiiFreeCredential(credential),
        event
      })
    );
  }
  return Promise.resolve({});
};
const processEvent = (calEvent) => {
  const calendarEvent = {
    ...calEvent,
    calendarDescription: CalEventParser.getRichDescription(calEvent)
  };
  const isMeetLocationType = calEvent.location === CalEventParser.MeetLocationType;
  const isOrganizerExempt = index.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;
};
exports.createEvent = createEvent;
exports.deleteEvent = deleteEvent;
exports.getBusyCalendarTimes = getBusyCalendarTimes;
exports.getCalendarCredentials = getCalendarCredentials;
exports.getConnectedCalendars = getConnectedCalendars;
exports.updateEvent = updateEvent;
