import { v4 } from "uuid";
import "./index-rbwdGzy0.js";
import { z as guessEventLocationType, C as CAL_AI_AGENT_PHONE_NUMBER_FIELD } from "./CalEventParser-tm9lJVKT.js";
import { F as FeaturesRepository } from "./features.repository-0riNrEoU.js";
import { T as TimeFormat, u as tasker, C as checkRateLimitAndThrowError, s as sendOrScheduleWorkflowEmails, F as generateIcsString, G as deleteMultipleScheduledSMS, k as cancelSMS, H as getSenderId, I as sendSmsOrFallbackEmail, J as scheduleSmsOrFallbackEmail } from "./email-manager-nZMavfll.js";
import { f as WorkflowActions, Z as APP_NAME, p as prisma, l as logger, at as PhoneNumberSubscriptionStatus, W as WorkflowTriggerEvents, e as WorkflowMethods, X as WEBSITE_URL, aF as bookingMetadataSchema, ap as WorkflowTemplates, g as getTranslation, aG as preprocessNameFieldDataWithVariant, aH as TimeUnit, aI as WorkflowContactType, a as safeStringify, ao as SENDER_ID, j as client, t as teamMetadataSchema, aJ as BillingPeriod, aK as ORGANIZATION_SELF_SERVE_PRICE, aL as ORGANIZATION_SELF_SERVE_MIN_SEATS, q as WEBAPP_URL, x as IS_PRODUCTION, aM as MINIMUM_NUMBER_OF_ORG_SEATS, aN as teamMetadataStrictSchema } from "./i18n-tiaWqB1b.js";
import dayjs from "dayjs";
import { Dub } from "dub-package";
import { z } from "zod";
import { g as getStripeCustomerIdFromUserId } from "./customer-45C3w6LN.js";
import Stripe from "stripe";
const whatsappEventCancelledTemplate = (isEditingMode, locale, action, timeFormat, startTime, eventName, timeZone, attendee, name) => {
  const currentTimeFormat = timeFormat || TimeFormat.TWELVE_HOUR;
  const dateTimeFormat = `ddd, MMM D, YYYY ${currentTimeFormat}`;
  let eventDate;
  if (isEditingMode) {
    eventName = "{EVENT_NAME}";
    timeZone = "{TIMEZONE}";
    startTime = `{START_TIME_${currentTimeFormat}}`;
    eventDate = `{EVENT_DATE_${dateTimeFormat}}`;
    attendee = action === WorkflowActions.WHATSAPP_ATTENDEE ? "{ORGANIZER}" : "{ATTENDEE}";
    name = action === WorkflowActions.WHATSAPP_ATTENDEE ? "{ATTENDEE}" : "{ORGANIZER}";
  } else {
    eventDate = dayjs(startTime).tz(timeZone).locale(locale).format("YYYY MMM D");
    startTime = dayjs(startTime).tz(timeZone).locale(locale).format(currentTimeFormat);
  }
  const templateOne = `Hi${name ? ` ${name}` : ``}, your meeting (*${eventName}*) with ${attendee} on ${eventDate} at ${startTime} ${timeZone} has been canceled.`;
  if (templateOne.length <= 1024)
    return templateOne;
  return null;
};
const whatsappEventCompletedTemplate = (isEditingMode, locale, action, timeFormat, startTime, eventName, timeZone, attendee, name) => {
  const currentTimeFormat = timeFormat || TimeFormat.TWELVE_HOUR;
  const dateTimeFormat = `ddd, MMM D, YYYY ${currentTimeFormat}`;
  let eventDate;
  if (isEditingMode) {
    eventName = "{EVENT_NAME}";
    timeZone = "{TIMEZONE}";
    startTime = `{START_TIME_${currentTimeFormat}}`;
    eventDate = `{EVENT_DATE_${dateTimeFormat}}`;
    action === WorkflowActions.WHATSAPP_ATTENDEE ? "{ORGANIZER}" : "{ATTENDEE}";
    name = action === WorkflowActions.WHATSAPP_ATTENDEE ? "{ATTENDEE}" : "{ORGANIZER}";
  } else {
    eventDate = dayjs(startTime).tz(timeZone).locale(locale).format("YYYY MMM D");
    startTime = dayjs(startTime).tz(timeZone).locale(locale).format(currentTimeFormat);
  }
  const templateOne = `Hi${name ? ` ${name}` : ``}, thank you for attending the event (*${eventName}*) on ${eventDate} at ${startTime} ${timeZone}.`;
  if (templateOne.length <= 1024)
    return templateOne;
  return null;
};
const whatsappReminderTemplate = (isEditingMode, locale, action, timeFormat, startTime, eventName, timeZone, attendee, name) => {
  const currentTimeFormat = timeFormat || TimeFormat.TWELVE_HOUR;
  const dateTimeFormat = `ddd, MMM D, YYYY ${currentTimeFormat}`;
  let eventDate;
  if (isEditingMode) {
    eventName = "{EVENT_NAME}";
    timeZone = "{TIMEZONE}";
    startTime = `{START_TIME_${currentTimeFormat}}`;
    eventDate = `{EVENT_DATE_${dateTimeFormat}}`;
    attendee = action === WorkflowActions.WHATSAPP_ATTENDEE ? "{ORGANIZER}" : "{ATTENDEE}";
    name = action === WorkflowActions.WHATSAPP_ATTENDEE ? "{ATTENDEE}" : "{ORGANIZER}";
  } else {
    eventDate = dayjs(startTime).tz(timeZone).locale(locale).format("YYYY MMM D");
    startTime = dayjs(startTime).tz(timeZone).locale(locale).format(currentTimeFormat);
  }
  const templateOne = `Hi${name ? ` ${name}` : ``}, this is a reminder that your meeting (*${eventName}*) with ${attendee} is on ${eventDate} at ${startTime} ${timeZone}.`;
  if (templateOne.length <= 1024)
    return templateOne;
  return null;
};
const whatsappEventRescheduledTemplate = (isEditingMode, locale, action, timeFormat, startTime, eventName, timeZone, attendee, name) => {
  const currentTimeFormat = timeFormat || TimeFormat.TWELVE_HOUR;
  const dateTimeFormat = `ddd, MMM D, YYYY ${currentTimeFormat}`;
  let eventDate;
  if (isEditingMode) {
    eventName = "{EVENT_NAME}";
    timeZone = "{TIMEZONE}";
    startTime = `{START_TIME_${currentTimeFormat}}`;
    eventDate = `{EVENT_DATE_${dateTimeFormat}}`;
    attendee = action === WorkflowActions.WHATSAPP_ATTENDEE ? "{ORGANIZER}" : "{ATTENDEE}";
    name = action === WorkflowActions.WHATSAPP_ATTENDEE ? "{ATTENDEE}" : "{ORGANIZER}";
  } else {
    eventDate = dayjs(startTime).tz(timeZone).locale(locale).format("YYYY MMM D");
    startTime = dayjs(startTime).tz(timeZone).locale(locale).format(currentTimeFormat);
  }
  const templateOne = `Hi${name ? ` ${name}` : ``}, your meeting (*${eventName}*) with ${attendee} on ${eventDate} at ${startTime} ${timeZone} has been rescheduled.`;
  if (templateOne.length <= 1024)
    return templateOne;
  return null;
};
const emailRatingTemplate = ({
  isEditingMode,
  locale,
  action,
  t,
  timeFormat,
  startTime,
  endTime,
  eventName,
  timeZone,
  organizer,
  name,
  isBrandingDisabled,
  ratingUrl,
  noShowUrl
}) => {
  const currentTimeFormat = timeFormat || TimeFormat.TWELVE_HOUR;
  const dateTimeFormat = `ddd, MMM D, YYYY ${currentTimeFormat}`;
  let eventDate = "";
  if (isEditingMode) {
    endTime = "{EVENT_END_TIME}";
    eventName = "{EVENT_NAME}";
    timeZone = "{TIMEZONE}";
    organizer = "{ORGANIZER}";
    name = action === WorkflowActions.EMAIL_ATTENDEE ? "{ATTENDEE}" : "{ORGANIZER}";
    eventDate = `{EVENT_DATE_${dateTimeFormat}}`;
    ratingUrl = "{RATING_URL}";
    noShowUrl = "{NO_SHOW_URL}";
  } else {
    eventDate = dayjs(startTime).tz(timeZone).locale(locale).format(dateTimeFormat);
    endTime = dayjs(endTime).tz(timeZone).locale(locale).format(currentTimeFormat);
  }
  const emailSubject = `${t("experience_review_prompt")} ${eventName}`;
  const introHtml = `<p>${t("hi")}${name ? ` ${name}` : ""},<br><br>${t(
    "improve_customer_experience_message"
  )} ${t("meeting_satisfaction_question")}<br></p>`;
  const ratingHtml = `<h6><a href="${ratingUrl}=1">😠 </a> <a href="${ratingUrl}=2">🙁 </a> <a href="${ratingUrl}=3">😐 </a> <a href="${ratingUrl}=4">😄 </a> <a href="${ratingUrl}=5">😍</a></h6>`;
  const noShowHtml = `${organizer} ${t("meeting_not_joined_question")}<a href="${noShowUrl}"> ${t(
    "reschedule_cta_short"
  )}</a><br><br>`;
  const eventHtml = `<strong>${t("event_upper_case")}: </strong>${eventName}<br><br>`;
  const dateTimeHtml = `<strong>${t(
    "date_and_time"
  )}: </strong>${eventDate} - ${endTime} (${timeZone})<br><br>`;
  const attendeeHtml = `<strong>${t("attendees")}: </strong>${t("you_and_conjunction")} ${organizer}<br><br>`;
  const branding = !isBrandingDisabled && !isEditingMode ? `<div>_<br><br>${t("scheduling_by")} ${APP_NAME}</div>` : "";
  const endingHtml = `${t("email_survey_triggered_by_workflow")}${branding}`;
  const emailBody = `<body>${introHtml}${ratingHtml}<p>${noShowHtml}${eventHtml}${dateTimeHtml}${attendeeHtml}${endingHtml}</p></body>`;
  return { emailSubject, emailBody };
};
const emailReminderTemplate = ({
  isEditingMode,
  locale,
  t,
  action,
  timeFormat,
  startTime,
  endTime,
  eventName,
  timeZone,
  location,
  meetingUrl,
  otherPerson,
  name,
  isBrandingDisabled
}) => {
  const currentTimeFormat = timeFormat || TimeFormat.TWELVE_HOUR;
  const dateTimeFormat = `ddd, MMM D, YYYY ${currentTimeFormat}`;
  let eventDate = "";
  let locationString = `${guessEventLocationType(location)?.label || location} ${meetingUrl}`;
  if (isEditingMode) {
    endTime = "{EVENT_END_TIME}";
    eventName = "{EVENT_NAME}";
    timeZone = "{TIMEZONE}";
    locationString = "{LOCATION} {MEETING_URL}";
    otherPerson = action === WorkflowActions.EMAIL_ATTENDEE ? "{ORGANIZER}" : "{ATTENDEE}";
    name = action === WorkflowActions.EMAIL_ATTENDEE ? "{ATTENDEE}" : "{ORGANIZER}";
    eventDate = `{EVENT_DATE_${dateTimeFormat}}`;
  } else {
    eventDate = dayjs(startTime).tz(timeZone).locale(locale).format(dateTimeFormat);
    endTime = dayjs(endTime).tz(timeZone).locale(locale).format(currentTimeFormat);
  }
  const emailSubject = `${t("reminder")}: ${eventName} - ${eventDate}`;
  const introHtml = `<body>${t("hi")}${name ? ` ${name}` : ""},<br><br>${t?.(
    "email_reminder_upcoming_event_notice"
  )}<br><br>`;
  const eventHtml = `<div><strong class="editor-text-bold">${t(
    "event_upper_case"
  )}: </strong></div>${eventName}<br><br>`;
  const dateTimeHtml = `<div><strong class="editor-text-bold">${t(
    "date_and_time"
  )}: </strong></div>${eventDate} - ${endTime} (${timeZone})<br><br>`;
  const attendeeHtml = `<div><strong class="editor-text-bold">${t("attendees")}: </strong></div>${t(
    "you_and_conjunction"
  )} ${otherPerson}<br><br>`;
  const locationHtml = `<div><strong class="editor-text-bold">${t(
    "location"
  )}: </strong></div>${locationString}<br><br>`;
  const branding = !isBrandingDisabled && !isEditingMode ? `<br><br>_<br><br>${t("scheduling_by")} ${APP_NAME}` : "";
  const endingHtml = `${t("email_reminder_triggered_by_workflow")}${branding}</body>`;
  const emailBody = introHtml + eventHtml + dateTimeHtml + attendeeHtml + locationHtml + endingHtml;
  return { emailSubject, emailBody };
};
const smsReminderTemplate = (isEditingMode, locale, action, timeFormat, startTime, eventName, timeZone, attendee, name) => {
  const currentTimeFormat = timeFormat || TimeFormat.TWELVE_HOUR;
  let eventDate;
  if (isEditingMode) {
    eventName = "{EVENT_NAME}";
    timeZone = "{TIMEZONE}";
    startTime = `{EVENT_TIME_${currentTimeFormat}}`;
    eventDate = "{EVENT_DATE_YYYY MMM D}";
    attendee = action === WorkflowActions.SMS_ATTENDEE ? "{ORGANIZER}" : "{ATTENDEE}";
    name = action === WorkflowActions.SMS_ATTENDEE ? "{ATTENDEE}" : "{ORGANIZER}";
  } else {
    eventDate = dayjs(startTime).tz(timeZone).locale(locale).format("YYYY MMM D");
    startTime = dayjs(startTime).tz(timeZone).locale(locale).format(currentTimeFormat);
  }
  const templateOne = `Hi${name ? ` ${name}` : ``}, this is a reminder that your meeting (${eventName}) with ${attendee} is on ${eventDate} at ${startTime} ${timeZone}.`;
  if (templateOne.length <= 320)
    return templateOne;
  const templateTwo = `Hi, this is a reminder that your meeting with ${attendee} is on ${eventDate} at ${startTime} ${timeZone}`;
  if (templateTwo.length <= 1600)
    return templateTwo;
  return null;
};
function isSMSAction(action) {
  return action === WorkflowActions.SMS_ATTENDEE || action === WorkflowActions.SMS_NUMBER;
}
function isWhatsappAction(action) {
  return action === WorkflowActions.WHATSAPP_NUMBER || action === WorkflowActions.WHATSAPP_ATTENDEE;
}
function isSMSOrWhatsappAction(action) {
  return isSMSAction(action) || isWhatsappAction(action);
}
function isCalAIAction(action) {
  return action === WorkflowActions.CAL_AI_PHONE_CALL;
}
function isEmailAction(action) {
  return action === WorkflowActions.EMAIL_ADDRESS || action === WorkflowActions.EMAIL_ATTENDEE || action === WorkflowActions.EMAIL_HOST;
}
function isAttendeeAction(action) {
  return action === WorkflowActions.SMS_ATTENDEE || action === WorkflowActions.EMAIL_ATTENDEE || action === WorkflowActions.WHATSAPP_ATTENDEE;
}
function extractPhoneNumber(responses) {
  if (!responses)
    return void 0;
  const aiAgentPhoneResponse = responses[CAL_AI_AGENT_PHONE_NUMBER_FIELD];
  if (aiAgentPhoneResponse && typeof aiAgentPhoneResponse === "object" && "value" in aiAgentPhoneResponse) {
    return aiAgentPhoneResponse.value;
  }
  const attendeePhoneResponse = responses.attendeePhoneNumber;
  if (attendeePhoneResponse && typeof attendeePhoneResponse === "object" && "value" in attendeePhoneResponse) {
    return attendeePhoneResponse.value;
  }
  return void 0;
}
const createWorkflowReminderAndExtractPhone = async (args) => {
  const { evt, workflowStepId, scheduledDate, seatReferenceUid } = args;
  let attendeePhoneNumber = extractPhoneNumber(evt.responses);
  if (!attendeePhoneNumber) {
    const attendeePhone = evt.attendees?.[0]?.phoneNumber;
    if (attendeePhone) {
      attendeePhoneNumber = attendeePhone;
    } else {
      throw new Error(`No attendee phone number found for workflow step ${workflowStepId}`);
    }
  }
  const workflowReminder = await prisma.workflowReminder.create({
    data: {
      bookingUid: evt.uid,
      workflowStepId,
      method: WorkflowMethods.AI_PHONE_CALL,
      scheduledDate: scheduledDate.toDate(),
      scheduled: true,
      seatReferenceId: seatReferenceUid
    }
  });
  return { workflowReminder, attendeePhoneNumber };
};
const scheduleAIPhoneCall = async (args) => {
  const { evt, triggerEvent, timeSpan, workflowStepId, userId, teamId, seatReferenceUid, verifiedAt } = args;
  if (!verifiedAt || !workflowStepId) {
    logger.warn(`Workflow step ${workflowStepId} not yet verified or not found`);
    return;
  }
  const workflowStep = await prisma.workflowStep.findUnique({
    where: { id: workflowStepId },
    select: {
      agent: {
        select: {
          id: true,
          providerAgentId: true,
          outboundPhoneNumbers: {
            select: {
              phoneNumber: true,
              subscriptionStatus: true
            }
          }
        }
      }
    }
  });
  const activePhoneNumbers = workflowStep?.agent?.outboundPhoneNumbers?.filter(
    (phoneNumber) => phoneNumber.subscriptionStatus === PhoneNumberSubscriptionStatus.ACTIVE || phoneNumber.subscriptionStatus === null || phoneNumber.subscriptionStatus === void 0
  );
  if (!workflowStep?.agent) {
    logger.warn(`No agent configured for workflow step ${workflowStepId}`);
    return;
  }
  if (!workflowStep.agent.outboundPhoneNumbers?.length || !activePhoneNumbers?.length) {
    logger.warn(`No active outbound phone number configured for agent ${workflowStep.agent.id}`);
    return;
  }
  const featuresRepository = new FeaturesRepository(prisma);
  const calAIVoiceAgents = await featuresRepository.checkIfFeatureIsEnabledGlobally("cal-ai-voice-agents");
  if (!calAIVoiceAgents) {
    logger.warn("Cal AI voice agents are disabled - skipping AI phone call scheduling");
    return;
  }
  const { startTime, endTime } = evt;
  const uid = evt.uid;
  const currentDate = dayjs();
  const timeUnit = timeSpan.timeUnit?.toLocaleLowerCase();
  let scheduledDate = null;
  if (triggerEvent === WorkflowTriggerEvents.BEFORE_EVENT) {
    scheduledDate = timeSpan.time && timeUnit ? dayjs(startTime).subtract(timeSpan.time, timeUnit) : null;
  } else if (triggerEvent === WorkflowTriggerEvents.AFTER_EVENT) {
    scheduledDate = timeSpan.time && timeUnit ? dayjs(endTime).add(timeSpan.time, timeUnit) : null;
  }
  if (!scheduledDate) {
    scheduledDate = currentDate;
  }
  const shouldExecuteImmediately = (
    // Immediate triggers (NEW_EVENT, EVENT_CANCELLED, etc.)
    !timeSpan.time || !timeSpan.timeUnit || // Or if the scheduled time has already passed
    scheduledDate && currentDate.isAfter(scheduledDate)
  );
  if (!shouldExecuteImmediately) {
    try {
      const { workflowReminder, attendeePhoneNumber } = await createWorkflowReminderAndExtractPhone({
        evt,
        workflowStepId,
        scheduledDate,
        seatReferenceUid
      });
      await scheduleAIPhoneCallTask({
        workflowReminderId: workflowReminder.id,
        scheduledDate: scheduledDate.toDate(),
        agentId: workflowStep.agent.id,
        phoneNumber: activePhoneNumbers[0].phoneNumber,
        attendeePhoneNumber,
        bookingUid: uid,
        userId,
        teamId,
        providerAgentId: workflowStep.agent.providerAgentId,
        referenceUid: workflowReminder.uuid || v4()
      });
      logger.info(`AI phone call scheduled for workflow step ${workflowStepId} at ${scheduledDate}`);
    } catch (error) {
      logger.error(`Error scheduling AI phone call with error ${error}`);
    }
  } else {
    try {
      const { workflowReminder, attendeePhoneNumber } = await createWorkflowReminderAndExtractPhone({
        evt,
        workflowStepId,
        scheduledDate: currentDate,
        seatReferenceUid
      });
      await scheduleAIPhoneCallTask({
        workflowReminderId: workflowReminder.id,
        scheduledDate: currentDate.toDate(),
        agentId: workflowStep.agent.id,
        phoneNumber: activePhoneNumbers[0].phoneNumber,
        attendeePhoneNumber,
        bookingUid: uid,
        userId,
        teamId,
        providerAgentId: workflowStep.agent.providerAgentId,
        referenceUid: workflowReminder.uuid || v4()
      });
      logger.info(`AI phone call scheduled for immediate execution for workflow step ${workflowStepId}`);
    } catch (error) {
      logger.error(`Error scheduling immediate AI phone call with error ${error}`);
    }
  }
};
const scheduleAIPhoneCallTask = async (args) => {
  const {
    workflowReminderId,
    scheduledDate,
    agentId,
    phoneNumber,
    attendeePhoneNumber,
    bookingUid,
    userId,
    teamId,
    providerAgentId,
    referenceUid
  } = args;
  const featuresRepository = new FeaturesRepository(prisma);
  const calAIVoiceAgents = await featuresRepository.checkIfFeatureIsEnabledGlobally("cal-ai-voice-agents");
  if (!calAIVoiceAgents) {
    logger.warn("Cal AI voice agents are disabled - skipping AI phone call");
    return;
  }
  if (userId) {
    await checkRateLimitAndThrowError({
      rateLimitingType: "core",
      identifier: `ai-phone-call:${userId}`
    });
  }
  try {
    await tasker.create(
      "executeAIPhoneCall",
      {
        workflowReminderId,
        agentId,
        fromNumber: phoneNumber,
        toNumber: attendeePhoneNumber,
        bookingUid,
        userId,
        teamId,
        providerAgentId
      },
      {
        scheduledAt: scheduledDate,
        maxAttempts: 1,
        referenceUid
      }
    );
  } catch (error) {
    console.error("Error creating AI phone call task:", error);
    throw error;
  }
};
const deleteScheduledAIPhoneCall = async (reminderId, referenceId) => {
  const workflowReminder = await prisma.workflowReminder.findUnique({
    where: {
      id: reminderId
    }
  });
  if (!workflowReminder) {
    logger.error("AI Phone Call workflow reminder not found");
    return;
  }
  const { uuid } = workflowReminder;
  const taskReferenceId = referenceId || uuid;
  if (taskReferenceId) {
    try {
      const taskId = await tasker.cancelWithReference(taskReferenceId, "executeAIPhoneCall");
      if (taskId) {
        await prisma.workflowReminder.delete({
          where: {
            id: reminderId
          }
        });
        logger.info(`AI phone call reminder ${reminderId} cancelled and deleted`);
        return;
      }
    } catch (error) {
      logger.error(`Error canceling/deleting AI phone call reminder with tasker. Error: ${error}`);
    }
  }
  try {
    await prisma.workflowReminder.delete({
      where: {
        id: reminderId
      }
    });
    logger.info(`AI phone call reminder ${reminderId} deleted (no task to cancel)`);
  } catch (error) {
    logger.error(`Error deleting AI phone call reminder ${reminderId}: ${error}`);
  }
};
function getWorkflowRecipientEmail({
  action,
  organizerEmail,
  attendeeEmail,
  sendToEmail
}) {
  switch (action) {
    case "EMAIL_ADDRESS":
      return sendToEmail || null;
    case "EMAIL_HOST":
      return organizerEmail || null;
    case "EMAIL_ATTENDEE":
      return attendeeEmail || null;
    case "SMS_ATTENDEE":
      return attendeeEmail || null;
    case "WHATSAPP_ATTENDEE":
      return attendeeEmail || null;
    case "SMS_NUMBER":
    case "WHATSAPP_NUMBER":
      return null;
    default:
      return null;
  }
}
function replaceVariablePlaceholders(text) {
  return text.replace(/\{([A-Z0-9_]+)_VARIABLE}/g, (_, base) => `{${base}}`);
}
const customTemplate = (text, variables, locale, timeFormat, isBrandingDisabled) => {
  const translatedDate = new Intl.DateTimeFormat(locale, {
    weekday: "long",
    month: "long",
    day: "numeric",
    year: "numeric"
  }).format(variables.eventDate?.add(dayjs().tz(variables.timeZone).utcOffset(), "minute").toDate());
  let locationString = variables.location || "";
  text = replaceVariablePlaceholders(text);
  if (text.includes("{LOCATION}")) {
    locationString = guessEventLocationType(locationString)?.label || locationString;
  }
  const cancelLink = variables.cancelLink ?? "";
  const rescheduleLink = variables.rescheduleLink ?? "";
  const currentTimeFormat = timeFormat || TimeFormat.TWELVE_HOUR;
  const attendeeNameWords = variables.attendeeName?.trim().split(" ");
  const attendeeNameWordCount = attendeeNameWords?.length ?? 0;
  const attendeeFirstName = variables.attendeeFirstName ? variables.attendeeFirstName : attendeeNameWords?.[0] ?? "";
  const attendeeLastName = variables.attendeeLastName ? variables.attendeeLastName : attendeeNameWordCount > 1 ? attendeeNameWords[attendeeNameWordCount - 1] : "";
  let dynamicText = text.replaceAll("{EVENT_NAME}", variables.eventName || "").replaceAll("{ORGANIZER}", variables.organizerName || "").replaceAll("{ATTENDEE}", variables.attendeeName || "").replaceAll("{ORGANIZER_NAME}", variables.organizerName || "").replaceAll("{ATTENDEE_NAME}", variables.attendeeName || "").replaceAll("{ATTENDEE_FIRST_NAME}", attendeeFirstName).replaceAll("{ATTENDEE_LAST_NAME}", attendeeLastName).replaceAll("{EVENT_DATE}", translatedDate).replaceAll("{EVENT_TIME}", variables.eventDate?.format(currentTimeFormat) || "").replaceAll("{START_TIME}", variables.eventDate?.format(currentTimeFormat) || "").replaceAll("{EVENT_END_TIME}", variables.eventEndTime?.format(currentTimeFormat) || "").replaceAll("{LOCATION}", locationString).replaceAll("{ADDITIONAL_NOTES}", variables.additionalNotes || "").replaceAll("{ATTENDEE_EMAIL}", variables.attendeeEmail || "").replaceAll("{TIMEZONE}", variables.timeZone || "").replaceAll("{CANCEL_URL}", cancelLink).replaceAll("{CANCELLATION_REASON}", variables.cancelReason || "").replaceAll("{RESCHEDULE_URL}", rescheduleLink).replaceAll("{RESCHEDULE_REASON}", variables.rescheduleReason || "").replaceAll("{MEETING_URL}", variables.meetingUrl || "").replaceAll("{RATING_URL}", variables.ratingUrl || "").replaceAll("{NO_SHOW_URL}", variables.noShowUrl || "").replaceAll("{ATTENDEE_TIMEZONE}", variables.attendeeTimezone || "").replaceAll(
    "{EVENT_START_TIME_IN_ATTENDEE_TIMEZONE}",
    variables.eventTimeInAttendeeTimezone?.format(currentTimeFormat) || ""
  ).replaceAll(
    "{EVENT_END_TIME_IN_ATTENDEE_TIMEZONE}",
    variables.eventEndTimeInAttendeeTimezone?.format(currentTimeFormat) || ""
  );
  const customInputvariables = dynamicText.match(/\{(.+?)}/g)?.map((variable) => {
    return variable.replace("{", "").replace("}", "");
  });
  customInputvariables?.forEach((variable) => {
    if (variable.startsWith("EVENT_DATE_") || variable.startsWith("EVENT_TIME_") || variable.startsWith("START_TIME_")) {
      const dateFormat = variable.substring(11, text.length);
      const formattedDate = variables.eventDate?.locale(locale).format(dateFormat);
      dynamicText = dynamicText.replace(`{${variable}}`, formattedDate || "");
      return;
    }
    if (variable.startsWith("EVENT_END_TIME_")) {
      const dateFormat = variable.substring(15, text.length);
      const formattedDate = variables.eventEndTime?.locale(locale).format(dateFormat);
      dynamicText = dynamicText.replace(`{${variable}}`, formattedDate || "");
      return;
    }
    if (variables.responses) {
      Object.keys(variables.responses).forEach((customInput) => {
        const formatedToVariable = customInput.replace(/[^a-zA-Z0-9 ]/g, "").trim().replaceAll(" ", "_").toUpperCase();
        if (variable === formatedToVariable && variables.responses && variables.responses[customInput].value) {
          dynamicText = dynamicText.replace(
            `{${variable}}`,
            variables.responses[customInput].value.toString()
          );
        }
      });
    }
  });
  const branding = !isBrandingDisabled ? `<br><br>_<br><br>Scheduling by ${APP_NAME}` : "";
  const textHtml = `<body style="white-space: pre-wrap;">${dynamicText}${branding}</body>`;
  return { text: dynamicText, html: textHtml };
};
const log$4 = logger.getSubLogger({ prefix: ["[emailReminderManager]"] });
const scheduleEmailReminder = async (args) => {
  const {
    evt,
    triggerEvent,
    timeSpan,
    template,
    sender,
    workflowStepId,
    seatReferenceUid,
    sendTo,
    emailSubject = "",
    emailBody = "",
    hideBranding,
    includeCalendarEvent,
    action,
    verifiedAt
  } = args;
  if (!verifiedAt) {
    log$4.warn(`Workflow step ${workflowStepId} not yet verified`);
    return;
  }
  const { startTime, endTime } = evt;
  const uid = evt.uid;
  const timeUnit = timeSpan.timeUnit?.toLocaleLowerCase();
  let scheduledDate = null;
  if (triggerEvent === WorkflowTriggerEvents.BEFORE_EVENT) {
    scheduledDate = timeSpan.time && timeUnit ? dayjs(startTime).subtract(timeSpan.time, timeUnit) : null;
  } else if (triggerEvent === WorkflowTriggerEvents.AFTER_EVENT) {
    scheduledDate = timeSpan.time && timeUnit ? dayjs(endTime).add(timeSpan.time, timeUnit) : null;
  }
  let attendeeToBeUsedInMail = null;
  let name = "";
  let attendeeName = "";
  let timeZone = "";
  switch (action) {
    case WorkflowActions.EMAIL_ADDRESS:
      name = "";
      attendeeToBeUsedInMail = evt.attendees[0];
      attendeeName = evt.attendees[0].name;
      timeZone = evt.organizer.timeZone;
      break;
    case WorkflowActions.EMAIL_HOST:
      attendeeToBeUsedInMail = evt.attendees[0];
      name = evt.organizer.name;
      attendeeName = attendeeToBeUsedInMail.name;
      timeZone = evt.organizer.timeZone;
      break;
    case WorkflowActions.EMAIL_ATTENDEE:
      const attendeeEmailToBeUsedInMailFromEvt = evt.attendees.find(
        (attendee) => attendee.email === sendTo[0]
      );
      attendeeToBeUsedInMail = attendeeEmailToBeUsedInMailFromEvt ? attendeeEmailToBeUsedInMailFromEvt : evt.attendees[0];
      name = attendeeToBeUsedInMail.name;
      attendeeName = evt.organizer.name;
      timeZone = attendeeToBeUsedInMail.timeZone;
      break;
  }
  let emailContent = {
    emailSubject,
    emailBody: `<body style="white-space: pre-wrap;">${emailBody}</body>`
  };
  const bookerUrl = evt.bookerUrl ?? WEBSITE_URL;
  if (emailBody) {
    const isEmailAttendeeAction = action === WorkflowActions.EMAIL_ATTENDEE;
    const recipientEmail = getWorkflowRecipientEmail({
      action,
      attendeeEmail: attendeeToBeUsedInMail.email,
      organizerEmail: evt.organizer.email,
      sendToEmail: sendTo[0]
    });
    const variables = {
      eventName: evt.title || "",
      organizerName: evt.organizer.name,
      attendeeName: attendeeToBeUsedInMail.name,
      attendeeFirstName: attendeeToBeUsedInMail.firstName,
      attendeeLastName: attendeeToBeUsedInMail.lastName,
      attendeeEmail: attendeeToBeUsedInMail.email,
      eventDate: dayjs(startTime).tz(timeZone),
      eventEndTime: dayjs(endTime).tz(timeZone),
      timeZone,
      location: evt.location,
      additionalNotes: evt.additionalNotes,
      responses: evt.responses,
      meetingUrl: bookingMetadataSchema.parse(evt.metadata || {})?.videoCallUrl,
      cancelLink: `${bookerUrl}/booking/${evt.uid}?cancel=true${recipientEmail ? `&cancelledBy=${encodeURIComponent(recipientEmail)}` : ""}${isEmailAttendeeAction && seatReferenceUid ? `&seatReferenceUid=${seatReferenceUid}` : ""}`,
      cancelReason: evt.cancellationReason,
      rescheduleLink: `${bookerUrl}/reschedule/${evt.uid}${recipientEmail ? `?rescheduledBy=${encodeURIComponent(recipientEmail)}${isEmailAttendeeAction && seatReferenceUid ? `&seatReferenceUid=${encodeURIComponent(seatReferenceUid)}` : ""}` : isEmailAttendeeAction && seatReferenceUid ? `?seatReferenceUid=${encodeURIComponent(seatReferenceUid)}` : ""}`,
      rescheduleReason: evt.rescheduleReason,
      ratingUrl: `${bookerUrl}/booking/${evt.uid}?rating`,
      noShowUrl: `${bookerUrl}/booking/${evt.uid}?noShow=true`,
      attendeeTimezone: evt.attendees[0].timeZone,
      eventTimeInAttendeeTimezone: dayjs(startTime).tz(evt.attendees[0].timeZone),
      eventEndTimeInAttendeeTimezone: dayjs(endTime).tz(evt.attendees[0].timeZone)
    };
    const locale = isEmailAttendeeAction ? attendeeToBeUsedInMail.language?.locale : evt.organizer.language.locale;
    const emailSubjectTemplate = customTemplate(emailSubject, variables, locale, evt.organizer.timeFormat);
    emailContent.emailSubject = emailSubjectTemplate.text;
    emailContent.emailBody = customTemplate(
      emailBody,
      variables,
      locale,
      evt.organizer.timeFormat,
      hideBranding
    ).html;
  } else if (template === WorkflowTemplates.REMINDER) {
    emailContent = emailReminderTemplate({
      isEditingMode: false,
      locale: evt.organizer.language.locale,
      t: await getTranslation(evt.organizer.language.locale || "en", "common"),
      action,
      timeFormat: evt.organizer.timeFormat,
      startTime,
      endTime,
      eventName: evt.title,
      timeZone,
      location: evt.location || "",
      meetingUrl: bookingMetadataSchema.parse(evt.metadata || {})?.videoCallUrl || "",
      otherPerson: attendeeName,
      name
    });
  } else if (template === WorkflowTemplates.RATING) {
    emailContent = emailRatingTemplate({
      isEditingMode: true,
      locale: evt.organizer.language.locale,
      action,
      t: await getTranslation(evt.organizer.language.locale || "en", "common"),
      timeFormat: evt.organizer.timeFormat,
      startTime,
      endTime,
      eventName: evt.title,
      timeZone,
      organizer: evt.organizer.name,
      name,
      ratingUrl: `${bookerUrl}/booking/${evt.uid}?rating`,
      noShowUrl: `${bookerUrl}/booking/${evt.uid}?noShow=true`
    });
  }
  log$4.debug(`Sending Email for trigger ${triggerEvent}`, JSON.stringify(emailContent));
  async function prepareEmailData() {
    const status = triggerEvent === WorkflowTriggerEvents.EVENT_CANCELLED ? "CANCELLED" : "CONFIRMED";
    const organizerT = await getTranslation(evt.organizer.language.locale || "en", "common");
    const attendeeT = await getTranslation(evt.attendees[0].language.locale || "en", "common");
    const attendee = {
      ...evt.attendees[0],
      name: preprocessNameFieldDataWithVariant("fullName", evt.attendees[0].name),
      language: { ...evt.attendees[0].language, translate: attendeeT }
    };
    const emailEvent = {
      ...evt,
      type: evt.eventType?.slug || "",
      organizer: { ...evt.organizer, language: { ...evt.organizer.language, translate: organizerT } },
      attendees: [attendee],
      location: bookingMetadataSchema.parse(evt.metadata || {})?.videoCallUrl || evt.location
    };
    const attachments = includeCalendarEvent ? [
      {
        content: generateIcsString({
          event: emailEvent,
          status
        }) || "",
        filename: "event.ics",
        type: "text/calendar; method=REQUEST",
        disposition: "attachment",
        contentId: v4()
      }
    ] : void 0;
    return {
      subject: emailContent.emailSubject,
      html: emailContent.emailBody,
      ...!evt.hideOrganizerEmail && { replyTo: evt?.eventType?.customReplyToEmail || evt.organizer.email },
      attachments,
      sender
    };
  }
  const mailData = await prepareEmailData();
  let reminderUid = void 0;
  if (scheduledDate) {
    const reminder = await prisma.workflowReminder.create({
      data: {
        bookingUid: uid,
        workflowStepId,
        method: WorkflowMethods.EMAIL,
        scheduledDate: scheduledDate.toDate(),
        scheduled: true
      }
    });
    reminderUid = reminder.uuid;
  }
  await sendOrScheduleWorkflowEmails({
    ...mailData,
    to: sendTo,
    sendAt: scheduledDate?.toDate(),
    referenceUid: reminderUid ?? void 0
  });
};
const deleteScheduledEmailReminder = async (reminderId) => {
  const workflowReminder = await prisma.workflowReminder.findUnique({
    where: {
      id: reminderId
    }
  });
  if (!workflowReminder) {
    console.error("Workflow reminder not found");
    return;
  }
  const { uuid, referenceId } = workflowReminder;
  if (uuid) {
    try {
      const taskId = await tasker.cancelWithReference(uuid, "sendWorkflowEmails");
      if (taskId) {
        await prisma.workflowReminder.delete({
          where: {
            id: reminderId
          }
        });
        return;
      }
    } catch (error) {
      log$4.error(`Error canceling/deleting reminder with tasker. Error: ${error}`);
    }
  }
  try {
    if (!referenceId) {
      await prisma.workflowReminder.delete({
        where: {
          id: reminderId
        }
      });
      return;
    }
    await prisma.workflowReminder.update({
      where: {
        id: reminderId
      },
      data: {
        cancelled: true
      }
    });
  } catch (error) {
    log$4.error(`Error canceling reminder with error ${error}`);
  }
};
const dub = new Dub({
  token: process.env.DUB_API_KEY
});
[
  WorkflowTriggerEvents.BEFORE_EVENT,
  WorkflowTriggerEvents.EVENT_CANCELLED,
  WorkflowTriggerEvents.NEW_EVENT,
  WorkflowTriggerEvents.AFTER_EVENT,
  WorkflowTriggerEvents.RESCHEDULE_EVENT,
  WorkflowTriggerEvents.AFTER_HOSTS_CAL_VIDEO_NO_SHOW,
  WorkflowTriggerEvents.AFTER_GUESTS_CAL_VIDEO_NO_SHOW,
  WorkflowTriggerEvents.BOOKING_REJECTED,
  WorkflowTriggerEvents.BOOKING_REQUESTED,
  WorkflowTriggerEvents.BOOKING_PAYMENT_INITIATED,
  WorkflowTriggerEvents.BOOKING_PAID,
  WorkflowTriggerEvents.BOOKING_NO_SHOW_UPDATED
];
[
  WorkflowActions.EMAIL_HOST,
  WorkflowActions.EMAIL_ATTENDEE,
  WorkflowActions.EMAIL_ADDRESS,
  WorkflowActions.SMS_ATTENDEE,
  WorkflowActions.SMS_NUMBER,
  WorkflowActions.WHATSAPP_ATTENDEE,
  WorkflowActions.WHATSAPP_NUMBER,
  WorkflowActions.CAL_AI_PHONE_CALL
];
[TimeUnit.DAY, TimeUnit.HOUR, TimeUnit.MINUTE];
[
  WorkflowTemplates.CUSTOM,
  WorkflowTemplates.REMINDER,
  WorkflowTemplates.RATING,
  WorkflowTemplates.CANCELLED,
  WorkflowTemplates.COMPLETED,
  WorkflowTemplates.RESCHEDULED
];
[WorkflowTemplates.CUSTOM, WorkflowTemplates.REMINDER];
[
  WorkflowTemplates.CUSTOM,
  WorkflowTemplates.REMINDER,
  WorkflowTemplates.RATING
];
[
  WorkflowTemplates.REMINDER,
  WorkflowTemplates.COMPLETED,
  WorkflowTemplates.CANCELLED,
  WorkflowTemplates.RESCHEDULED
];
const IMMEDIATE_WORKFLOW_TRIGGER_EVENTS = [
  WorkflowTriggerEvents.NEW_EVENT,
  WorkflowTriggerEvents.EVENT_CANCELLED,
  WorkflowTriggerEvents.RESCHEDULE_EVENT,
  WorkflowTriggerEvents.BOOKING_NO_SHOW_UPDATED,
  WorkflowTriggerEvents.BOOKING_PAID,
  WorkflowTriggerEvents.BOOKING_PAYMENT_INITIATED,
  WorkflowTriggerEvents.BOOKING_REJECTED,
  WorkflowTriggerEvents.BOOKING_REQUESTED
];
const bulkShortenLinks = async (links) => {
  if (!process.env.DUB_API_KEY) {
    return links.map((link) => ({ shortLink: link }));
  }
  const linksToShorten = links.filter((link) => link);
  const results = await dub.links.createMany(
    linksToShorten.map((link) => ({
      domain: "sms.cal.com",
      url: link,
      folderId: "fold_wx3NZDKQYbLDbncSubeMu0ss"
    }))
  );
  return links.map((link) => {
    const createdLink = results.find(
      (result) => !("error" in result) && result.url === link
    );
    if (createdLink) {
      return { shortLink: createdLink.shortLink };
    } else {
      return { shortLink: link };
    }
  });
};
const getSMSMessageWithVariables = async (smsMessage, evt, attendeeToBeUsedInSMS, action) => {
  const recipientEmail = getWorkflowRecipientEmail({
    action,
    attendeeEmail: attendeeToBeUsedInSMS.email
  });
  const urls = {
    meetingUrl: bookingMetadataSchema.parse(evt.metadata || {})?.videoCallUrl || "",
    cancelLink: `${evt.bookerUrl ?? WEBSITE_URL}/booking/${evt.uid}?cancel=true${recipientEmail ? `&cancelledBy=${recipientEmail}` : ""}`,
    rescheduleLink: `${evt.bookerUrl ?? WEBSITE_URL}/reschedule/${evt.uid}${recipientEmail ? `?rescheduledBy=${recipientEmail}` : ""}`
  };
  const [{ shortLink: meetingUrl }, { shortLink: cancelLink }, { shortLink: rescheduleLink }] = await bulkShortenLinks([urls.meetingUrl, urls.cancelLink, urls.rescheduleLink]);
  const timeZone = action === WorkflowActions.SMS_ATTENDEE ? attendeeToBeUsedInSMS.timeZone : evt.organizer.timeZone;
  const variables = {
    eventName: evt.title,
    organizerName: evt.organizer.name,
    attendeeName: attendeeToBeUsedInSMS.name,
    attendeeFirstName: attendeeToBeUsedInSMS.firstName,
    attendeeLastName: attendeeToBeUsedInSMS.lastName,
    attendeeEmail: attendeeToBeUsedInSMS.email,
    eventDate: dayjs(evt.startTime).tz(timeZone),
    eventEndTime: dayjs(evt.endTime).tz(timeZone),
    timeZone,
    location: evt.location,
    additionalNotes: evt.additionalNotes,
    responses: evt.responses,
    meetingUrl,
    cancelLink,
    rescheduleLink,
    cancelReason: evt.cancellationReason,
    rescheduleReason: evt.rescheduleReason,
    attendeeTimezone: evt.attendees[0].timeZone,
    eventTimeInAttendeeTimezone: dayjs(evt.startTime).tz(evt.attendees[0].timeZone),
    eventEndTimeInAttendeeTimezone: dayjs(evt.endTime).tz(evt.attendees[0].timeZone)
  };
  const locale = action === WorkflowActions.SMS_ATTENDEE ? attendeeToBeUsedInSMS.language?.locale : evt.organizer.language.locale;
  const customMessage = customTemplate(smsMessage, variables, locale, evt.organizer.timeFormat);
  smsMessage = customMessage.text;
  return smsMessage;
};
const getAttendeeToBeUsedInSMS = (action, evt, reminderPhone) => {
  let attendeeToBeUsedInSMS = null;
  if (action === WorkflowActions.SMS_ATTENDEE) {
    const attendeeWithReminderPhoneAsSMSReminderNumber = reminderPhone && evt.attendees.find((attendee) => attendee.email === evt.responses?.email?.value);
    attendeeToBeUsedInSMS = attendeeWithReminderPhoneAsSMSReminderNumber ? attendeeWithReminderPhoneAsSMSReminderNumber : evt.attendees[0];
  } else {
    attendeeToBeUsedInSMS = evt.attendees[0];
  }
  return attendeeToBeUsedInSMS;
};
const shouldUseTwilio = (trigger, scheduledDate) => {
  if (IMMEDIATE_WORKFLOW_TRIGGER_EVENTS.includes(trigger)) {
    return true;
  }
  if (trigger === WorkflowTriggerEvents.BEFORE_EVENT || trigger === WorkflowTriggerEvents.AFTER_EVENT) {
    const currentDate = dayjs();
    if (scheduledDate && currentDate.isBefore(scheduledDate.subtract(15, "minute")) && !scheduledDate.isAfter(currentDate.add(2, "hour"))) {
      return true;
    }
  }
  return false;
};
class WorkflowOptOutContactRepository {
  static async addPhoneNumber(phoneNumber) {
    await prisma.workflowOptOutContact.upsert({
      where: {
        type_value: {
          type: WorkflowContactType.PHONE,
          value: phoneNumber
        }
      },
      update: {
        optedOut: true
      },
      create: {
        type: WorkflowContactType.PHONE,
        optedOut: true,
        value: phoneNumber
      }
    });
  }
  static async removePhoneNumber(phoneNumber) {
    await prisma.workflowOptOutContact.update({
      where: {
        type_value: {
          type: WorkflowContactType.PHONE,
          value: phoneNumber
        }
      },
      data: {
        optedOut: false
      }
    });
  }
  static async isOptedOut(phoneNumber) {
    const optOutContact = await prisma.workflowOptOutContact.findFirst({
      where: {
        type: WorkflowContactType.PHONE,
        value: phoneNumber,
        optedOut: true
      },
      select: {
        optedOut: true
      }
    });
    return !!optOutContact?.optedOut;
  }
}
class WorkflowReminderRepository {
  static getFutureScheduledAttendeeSMSReminders(phoneNumber) {
    return prisma.workflowReminder.findMany({
      where: {
        method: WorkflowMethods.SMS,
        cancelled: null,
        scheduledDate: { gte: /* @__PURE__ */ new Date() },
        booking: {
          smsReminderNumber: phoneNumber
        },
        workflowStep: {
          action: WorkflowActions.SMS_ATTENDEE
        }
      }
    });
  }
  static deleteWorkflowReminders(ids) {
    if (!ids.length) {
      return [];
    }
    return prisma.workflowReminder.deleteMany({
      where: {
        id: {
          in: ids
        }
      }
    });
  }
}
class WorkflowOptOutService {
  static async optOutPhoneNumber(phoneNumber) {
    await WorkflowOptOutContactRepository.addPhoneNumber(phoneNumber);
    const scheduledReminders = await WorkflowReminderRepository.getFutureScheduledAttendeeSMSReminders(
      phoneNumber
    );
    await deleteMultipleScheduledSMS(
      scheduledReminders.filter((reminder) => !!reminder.referenceId).map((reminder) => reminder.referenceId)
    );
    await WorkflowReminderRepository.deleteWorkflowReminders(
      scheduledReminders.filter((reminder) => !reminder.referenceId).map((reminder) => reminder.id)
    );
  }
  static async addOptOutMessage(message, locale) {
    const t = await getTranslation(locale, "common");
    return `${message}

${t("sms_opt_out_message")}`;
  }
}
const log$3 = logger.getSubLogger({ prefix: ["[smsReminderManager]"] });
const scheduleSMSReminder = async (args) => {
  const {
    evt,
    reminderPhone,
    triggerEvent,
    action,
    timeSpan,
    message = "",
    workflowStepId,
    template,
    sender,
    userId,
    teamId,
    isVerificationPending = false,
    seatReferenceUid,
    verifiedAt
  } = args;
  if (!verifiedAt) {
    log$3.warn(`Workflow step ${workflowStepId} not yet verified`);
    return;
  }
  if (reminderPhone && await WorkflowOptOutContactRepository.isOptedOut(reminderPhone)) {
    log$3.warn(
      `Phone number opted out of SMS workflows`,
      safeStringify({ workflowStep: workflowStepId, eventUid: evt.uid })
    );
    return;
  }
  const { startTime, endTime } = evt;
  const uid = evt.uid;
  const timeUnit = timeSpan.timeUnit?.toLocaleLowerCase();
  let scheduledDate = null;
  const senderID = getSenderId(reminderPhone, sender || SENDER_ID);
  async function getIsNumberVerified() {
    if (action === WorkflowActions.SMS_ATTENDEE)
      return true;
    const verifiedNumber = await prisma.verifiedNumber.findFirst({
      where: {
        OR: [{ userId }, { teamId }],
        phoneNumber: reminderPhone || ""
      }
    });
    if (!!verifiedNumber)
      return true;
    return isVerificationPending;
  }
  const isNumberVerified = await getIsNumberVerified();
  if (triggerEvent === WorkflowTriggerEvents.BEFORE_EVENT) {
    scheduledDate = timeSpan.time && timeUnit ? dayjs(startTime).subtract(timeSpan.time, timeUnit) : null;
  } else if (triggerEvent === WorkflowTriggerEvents.AFTER_EVENT) {
    scheduledDate = timeSpan.time && timeUnit ? dayjs(endTime).add(timeSpan.time, timeUnit) : null;
  }
  if (reminderPhone && isNumberVerified) {
    const useTwilio = shouldUseTwilio(triggerEvent, scheduledDate);
    if (useTwilio) {
      const attendeeToBeUsedInSMS = getAttendeeToBeUsedInSMS(action, evt, reminderPhone);
      const name = action === WorkflowActions.SMS_ATTENDEE ? attendeeToBeUsedInSMS.name : "";
      const attendeeName = action === WorkflowActions.SMS_ATTENDEE ? evt.organizer.name : attendeeToBeUsedInSMS.name;
      const timeZone = action === WorkflowActions.SMS_ATTENDEE ? attendeeToBeUsedInSMS.timeZone : evt.organizer.timeZone;
      let smsMessage = message;
      if (smsMessage) {
        smsMessage = await getSMSMessageWithVariables(smsMessage, evt, attendeeToBeUsedInSMS, action);
      } else if (template === WorkflowTemplates.REMINDER) {
        smsMessage = smsReminderTemplate(
          false,
          evt.organizer.language.locale,
          action,
          evt.organizer.timeFormat,
          evt.startTime,
          evt.title,
          timeZone,
          attendeeName,
          name
        ) || message;
      }
      if (smsMessage.length > 0) {
        const smsMessageWithoutOptOut = smsMessage;
        if (process.env.TWILIO_OPT_OUT_ENABLED === "true") {
          smsMessage = await WorkflowOptOutService.addOptOutMessage(
            smsMessage,
            evt.organizer.language.locale
          );
        }
        log$3.debug(`Sending sms for trigger ${triggerEvent}`, smsMessage);
        if (IMMEDIATE_WORKFLOW_TRIGGER_EVENTS.includes(triggerEvent)) {
          try {
            await sendSmsOrFallbackEmail({
              twilioData: {
                phoneNumber: reminderPhone,
                body: smsMessage,
                sender: senderID,
                bodyWithoutOptOut: smsMessageWithoutOptOut,
                bookingUid: evt.uid,
                userId,
                teamId
              },
              fallbackData: isAttendeeAction(action) ? {
                email: evt.attendees[0].email,
                t: await getTranslation(evt.attendees[0].language.locale, "common"),
                replyTo: evt.organizer.email
              } : void 0
            });
          } catch (error) {
            log$3.error(`Error sending SMS with error ${error}`);
          }
        }
        if ((triggerEvent === WorkflowTriggerEvents.BEFORE_EVENT || triggerEvent === WorkflowTriggerEvents.AFTER_EVENT) && scheduledDate) {
          try {
            const scheduledNotification = await scheduleSmsOrFallbackEmail({
              twilioData: {
                phoneNumber: reminderPhone,
                body: smsMessage,
                scheduledDate: scheduledDate.toDate(),
                sender: senderID,
                bookingUid: evt.uid,
                userId,
                teamId
              },
              fallbackData: isAttendeeAction(action) ? {
                email: evt.attendees[0].email,
                t: await getTranslation(evt.attendees[0].language.locale, "common"),
                replyTo: evt.organizer.email,
                workflowStepId
              } : void 0
            });
            if (scheduledNotification?.sid) {
              await prisma.workflowReminder.create({
                data: {
                  bookingUid: uid,
                  workflowStepId,
                  method: WorkflowMethods.SMS,
                  scheduledDate: scheduledDate.toDate(),
                  scheduled: true,
                  referenceId: scheduledNotification.sid,
                  seatReferenceId: seatReferenceUid
                }
              });
            }
          } catch (error) {
            log$3.error(`Error scheduling SMS with error ${error}`);
          }
        }
      }
      return;
    }
    if (!useTwilio && scheduledDate) {
      await prisma.workflowReminder.create({
        data: {
          bookingUid: uid,
          workflowStepId,
          method: WorkflowMethods.SMS,
          scheduledDate: scheduledDate.toDate(),
          scheduled: false,
          seatReferenceId: seatReferenceUid
        }
      });
    }
  }
};
const deleteScheduledSMSReminder = async (reminderId, referenceId) => {
  try {
    if (referenceId) {
      await cancelSMS(referenceId);
    }
    await prisma.workflowReminder.delete({
      where: {
        id: reminderId
      }
    });
  } catch (error) {
    log$3.error(`Error canceling reminder with error ${error}`);
  }
};
const getContentSidForTemplate = (template) => {
  if (!template) {
    return process.env.TWILIO_WHATSAPP_REMINDER_CONTENT_SID;
  }
  switch (template) {
    case WorkflowTemplates.REMINDER:
      return process.env.TWILIO_WHATSAPP_REMINDER_CONTENT_SID;
    case WorkflowTemplates.CANCELLED:
      return process.env.TWILIO_WHATSAPP_CANCELLED_CONTENT_SID;
    case WorkflowTemplates.RESCHEDULED:
      return process.env.TWILIO_WHATSAPP_RESCHEDULED_CONTENT_SID;
    case WorkflowTemplates.COMPLETED:
      return process.env.TWILIO_WHATSAPP_COMPLETED_CONTENT_SID;
    default:
      return process.env.TWILIO_WHATSAPP_REMINDER_CONTENT_SID;
  }
};
const getContentVariablesForTemplate = (variables) => {
  const { name, attendeeName, eventName, eventDate, startTime, timeZone } = variables;
  const commonVariables = {
    "1": name || "",
    "2": attendeeName || "",
    "3": eventName || "",
    "4": eventDate || "",
    "5": `${startTime ?? ""} ${timeZone ?? ""}`
  };
  return commonVariables;
};
const log$2 = logger.getSubLogger({ prefix: ["[whatsappReminderManager]"] });
const scheduleWhatsappReminder = async (args) => {
  const {
    evt,
    reminderPhone,
    triggerEvent,
    action,
    timeSpan,
    message = "",
    workflowStepId,
    template,
    userId,
    teamId,
    isVerificationPending = false,
    seatReferenceUid,
    verifiedAt
  } = args;
  if (!verifiedAt) {
    log$2.warn(`Workflow step ${workflowStepId} not verified`);
    return;
  }
  const { startTime, endTime } = evt;
  const uid = evt.uid;
  const currentDate = dayjs();
  const timeUnit = timeSpan.timeUnit?.toLocaleLowerCase();
  let scheduledDate = null;
  async function getIsNumberVerified() {
    if (action === WorkflowActions.WHATSAPP_ATTENDEE)
      return true;
    const verifiedNumber = await prisma.verifiedNumber.findFirst({
      where: {
        OR: [{ userId }, { teamId }],
        phoneNumber: reminderPhone || ""
      }
    });
    if (!!verifiedNumber)
      return true;
    return isVerificationPending;
  }
  const isNumberVerified = await getIsNumberVerified();
  if (triggerEvent === WorkflowTriggerEvents.BEFORE_EVENT) {
    scheduledDate = timeSpan.time && timeUnit ? dayjs(startTime).subtract(timeSpan.time, timeUnit) : null;
  } else if (triggerEvent === WorkflowTriggerEvents.AFTER_EVENT) {
    scheduledDate = timeSpan.time && timeUnit ? dayjs(endTime).add(timeSpan.time, timeUnit) : null;
  }
  const name = action === WorkflowActions.WHATSAPP_ATTENDEE ? evt.attendees[0].name : evt.organizer.name;
  const attendeeName = action === WorkflowActions.WHATSAPP_ATTENDEE ? evt.organizer.name : evt.attendees[0].name;
  const timeZone = action === WorkflowActions.WHATSAPP_ATTENDEE ? evt.attendees[0].timeZone : evt.organizer.timeZone;
  const locale = evt.organizer.language.locale;
  const timeFormat = evt.organizer.timeFormat;
  const contentSid = getContentSidForTemplate(template);
  const contentVariables = getContentVariablesForTemplate({
    name,
    attendeeName,
    eventName: evt.title,
    eventDate: dayjs(startTime).tz(timeZone).locale(locale).format("YYYY MMM D"),
    startTime: dayjs(startTime).tz(timeZone).locale(locale).format(timeFormat || "h:mma"),
    timeZone
  });
  let textMessage = message;
  switch (template) {
    case WorkflowTemplates.REMINDER:
      textMessage = whatsappReminderTemplate(
        false,
        locale,
        action,
        timeFormat,
        evt.startTime,
        evt.title,
        timeZone,
        attendeeName,
        name
      ) || message;
      break;
    case WorkflowTemplates.CANCELLED:
      textMessage = whatsappEventCancelledTemplate(
        false,
        locale,
        action,
        timeFormat,
        evt.startTime,
        evt.title,
        timeZone,
        attendeeName,
        name
      ) || message;
      break;
    case WorkflowTemplates.RESCHEDULED:
      textMessage = whatsappEventRescheduledTemplate(
        false,
        locale,
        action,
        timeFormat,
        evt.startTime,
        evt.title,
        timeZone,
        attendeeName,
        name
      ) || message;
      break;
    case WorkflowTemplates.COMPLETED:
      textMessage = whatsappEventCompletedTemplate(
        false,
        locale,
        action,
        timeFormat,
        evt.startTime,
        evt.title,
        timeZone,
        attendeeName,
        name
      ) || message;
      break;
    default:
      textMessage = whatsappReminderTemplate(
        false,
        locale,
        action,
        timeFormat,
        evt.startTime,
        evt.title,
        timeZone,
        attendeeName,
        name
      ) || message;
  }
  log$2.debug(`Sending Whatsapp for trigger ${triggerEvent}`, textMessage);
  if (textMessage.length > 0 && reminderPhone && isNumberVerified) {
    if (IMMEDIATE_WORKFLOW_TRIGGER_EVENTS.includes(triggerEvent)) {
      try {
        await sendSmsOrFallbackEmail({
          twilioData: {
            phoneNumber: reminderPhone,
            body: textMessage,
            sender: "",
            bookingUid: evt.uid,
            userId,
            teamId,
            isWhatsapp: true,
            contentSid,
            contentVariables
          },
          fallbackData: isAttendeeAction(action) ? {
            email: evt.attendees[0].email,
            t: await getTranslation(evt.attendees[0].language.locale ?? "en", "common"),
            replyTo: evt.organizer.email
          } : void 0
        });
      } catch (error) {
        console.log(`Error sending WHATSAPP with error ${error}`);
      }
    } else if ((triggerEvent === WorkflowTriggerEvents.BEFORE_EVENT || triggerEvent === WorkflowTriggerEvents.AFTER_EVENT) && scheduledDate) {
      if (currentDate.isBefore(scheduledDate.subtract(15, "minute")) && !scheduledDate.isAfter(currentDate.add(2, "hour"))) {
        try {
          const scheduledNotification = await scheduleSmsOrFallbackEmail({
            twilioData: {
              phoneNumber: reminderPhone,
              body: textMessage,
              scheduledDate: scheduledDate.toDate(),
              sender: "",
              bookingUid: evt.uid ?? "",
              userId,
              teamId,
              isWhatsapp: true,
              contentSid,
              contentVariables
            },
            fallbackData: isAttendeeAction(action) ? {
              email: evt.attendees[0].email,
              t: await getTranslation(evt.attendees[0].language.locale ?? "en", "common"),
              replyTo: evt.organizer.email,
              workflowStepId
            } : void 0
          });
          if (scheduledNotification?.sid) {
            await prisma.workflowReminder.create({
              data: {
                bookingUid: uid,
                workflowStepId,
                method: WorkflowMethods.WHATSAPP,
                scheduledDate: scheduledDate.toDate(),
                scheduled: true,
                referenceId: scheduledNotification.sid,
                seatReferenceId: seatReferenceUid
              }
            });
          }
        } catch (error) {
          console.log(`Error scheduling WHATSAPP with error ${error}`);
        }
      } else if (scheduledDate.isAfter(currentDate.add(2, "hour"))) {
        await prisma.workflowReminder.create({
          data: {
            bookingUid: uid,
            workflowStepId,
            method: WorkflowMethods.WHATSAPP,
            scheduledDate: scheduledDate.toDate(),
            scheduled: false,
            seatReferenceId: seatReferenceUid
          }
        });
      }
    }
  }
};
const stripe = globalThis.stripe || new Stripe(process.env.STRIPE_PRIVATE_KEY, {
  apiVersion: "2020-08-27"
});
if (process.env.NODE_ENV !== "production") {
  globalThis.stripe = stripe;
}
const stripe$1 = stripe;
function getRequestedSlugError(error, requestedSlug) {
  let message = `Unknown error`;
  let statusCode = 500;
  if (error instanceof client.Prisma.PrismaClientKnownRequestError && error.code === "P2002") {
    statusCode = 400;
    message = `It seems like the requestedSlug: '${requestedSlug}' is already taken. Please contact support at help@cal.com so we can resolve this issue.`;
  } else if (error instanceof Error) {
    message = error.message;
  }
  return { message, statusCode };
}
const log$1 = logger.getSubLogger({ prefix: ["teams/lib/payments"] });
z.object({
  // Redefine paymentId, subscriptionId and subscriptionItemId to ensure that they are present and nonNullable
  paymentId: z.string(),
  subscriptionId: z.string(),
  subscriptionItemId: z.string(),
  orgSeats: teamMetadataSchema.unwrap().shape.orgSeats
});
const checkIfTeamPaymentRequired = async ({ teamId = -1 }) => {
  const team = await prisma.team.findUniqueOrThrow({
    where: { id: teamId },
    select: { metadata: true }
  });
  const metadata = teamMetadataSchema.parse(team.metadata);
  if (!metadata?.paymentId)
    return { url: null };
  const checkoutSession = await stripe$1.checkout.sessions.retrieve(metadata.paymentId);
  if (checkoutSession.payment_status !== "paid")
    return { url: null };
  return { url: `${WEBAPP_URL}/api/teams/${teamId}/upgrade?session_id=${metadata.paymentId}` };
};
const purchaseTeamOrOrgSubscription = async (input) => {
  const {
    teamId,
    seatsToChargeFor,
    seatsUsed,
    userId,
    isOrg,
    pricePerSeat,
    billingPeriod = BillingPeriod.MONTHLY
  } = input;
  const { url } = await checkIfTeamPaymentRequired({ teamId });
  if (url)
    return { url };
  const seats = isOrg ? Math.max(seatsUsed, MINIMUM_NUMBER_OF_ORG_SEATS) : seatsUsed;
  const quantity = seatsToChargeFor ? seatsToChargeFor : seats;
  const customer = await getStripeCustomerIdFromUserId(userId);
  const fixedPrice = await getFixedPrice();
  let priceId;
  if (pricePerSeat) {
    if (isOrg && pricePerSeat === ORGANIZATION_SELF_SERVE_PRICE && seats === ORGANIZATION_SELF_SERVE_MIN_SEATS) {
      priceId = fixedPrice;
    } else {
      const customPriceObj = await getPriceObject(fixedPrice);
      priceId = await createPrice({
        isOrg: !!isOrg,
        teamId,
        pricePerSeat,
        billingPeriod,
        product: customPriceObj.product,
        // We don't expand the object from stripe so just use the product as ID
        currency: customPriceObj.currency
      });
    }
  } else {
    priceId = fixedPrice;
  }
  const session = await stripe$1.checkout.sessions.create({
    customer,
    mode: "subscription",
    allow_promotion_codes: true,
    success_url: `${WEBAPP_URL}/api/teams/${teamId}/upgrade?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${WEBAPP_URL}/settings/my-account/profile`,
    line_items: [
      {
        price: priceId,
        quantity
      }
    ],
    customer_update: {
      address: "auto"
    },
    // Disabled when testing locally as usually developer doesn't setup Tax in Stripe Test mode
    automatic_tax: {
      enabled: IS_PRODUCTION
    },
    metadata: {
      teamId
    },
    subscription_data: {
      metadata: {
        teamId,
        dubCustomerId: userId
      }
    }
  });
  return { url: session.url };
  async function createPrice({
    isOrg: isOrg2,
    teamId: teamId2,
    pricePerSeat: pricePerSeat2,
    billingPeriod: billingPeriod2,
    product,
    currency
  }) {
    try {
      const pricePerSeatInCents = pricePerSeat2 * 100;
      const occurrence = billingPeriod2 === "MONTHLY" ? 1 : 12;
      const yearlyPrice = pricePerSeatInCents * occurrence;
      const customPriceObj = await stripe$1.prices.create({
        nickname: `Custom price for ${isOrg2 ? "Organization" : "Team"} ID: ${teamId2}`,
        unit_amount: yearlyPrice,
        // Stripe expects the amount in cents
        // Use the same currency as in the fixed price to avoid hardcoding it.
        currency,
        recurring: { interval: billingPeriod2 === "MONTHLY" ? "month" : "year" },
        // Define your subscription interval
        product: typeof product === "string" ? product : product.id,
        tax_behavior: "exclusive"
      });
      return customPriceObj.id;
    } catch (e) {
      log$1.error(
        `Error creating custom price for ${isOrg2 ? "Organization" : "Team"} ID: ${teamId2}`,
        safeStringify(e)
      );
      throw new Error("Error in creation of custom price");
    }
  }
  async function getFixedPrice() {
    const fixedPriceId = isOrg ? process.env.STRIPE_ORG_MONTHLY_PRICE_ID : process.env.STRIPE_TEAM_MONTHLY_PRICE_ID;
    if (!fixedPriceId) {
      throw new Error(
        "You need to have STRIPE_ORG_MONTHLY_PRICE_ID and STRIPE_TEAM_MONTHLY_PRICE_ID env variables set"
      );
    }
    log$1.debug("Getting price ID", safeStringify({ fixedPriceId, isOrg, teamId, pricePerSeat }));
    return fixedPriceId;
  }
};
async function getPriceObject(priceId) {
  const priceObj = await stripe$1.prices.retrieve(priceId);
  if (!priceObj)
    throw new Error(`No price found for ID ${priceId}`);
  return priceObj;
}
const objectKeys = (obj) => Object.keys(obj);
const objectKeys$1 = objectKeys;
function getMetadataHelpers(schema, rawMetadata) {
  const metadata = schema.parse(rawMetadata);
  return {
    metadata,
    get: (key) => metadata[key],
    /** This method prevents overwriting the metadata fields that you don't want to change. */
    mergeMetadata: (newMetadata) => {
      const newMetadataToReturn = { ...metadata, ...newMetadata };
      objectKeys$1(newMetadata).forEach((key) => {
        if (newMetadata[key] === void 0) {
          delete newMetadataToReturn[key];
        }
      });
      return newMetadataToReturn;
    }
  };
}
class Redirect {
  constructor(code, url) {
    this.code = code;
    this.url = url;
  }
}
class OrganizationOnboardingRepository {
  static async create(data) {
    logger.debug("Creating organization onboarding", safeStringify(data));
    return await prisma.organizationOnboarding.create({
      // HEKOP
      data: {
        billingPeriod: data.billingPeriod,
        pricePerSeat: data.pricePerSeat,
        seats: data.seats,
        orgOwnerEmail: data.orgOwnerEmail,
        name: data.name,
        slug: data.slug,
        logo: data.logo,
        bio: data.bio,
        stripeCustomerId: data.stripeCustomerId,
        stripeSubscriptionId: data.stripeSubscriptionId,
        invitedMembers: data.invitedMembers || [],
        teams: data.teams || [],
        createdById: data.createdById
      }
    });
  }
  static async findByStripeCustomerId(stripeCustomerId) {
    logger.debug(
      "Finding organization onboarding by stripe customer id",
      safeStringify({ stripeCustomerId })
    );
    return await prisma.organizationOnboarding.findUnique({
      where: {
        stripeCustomerId
      }
    });
  }
  static async findById(id) {
    logger.debug("Finding organization onboarding by id", safeStringify({ id }));
    return await prisma.organizationOnboarding.findUnique({
      where: {
        id
      }
    });
  }
  static async findByOrgOwnerEmail(email) {
    logger.debug("Finding organization onboarding by org owner email", safeStringify({ email }));
    return await prisma.organizationOnboarding.findUnique({
      where: {
        orgOwnerEmail: email
      }
    });
  }
  // TODO: This method should be moved to OrganizationOnboardingService
  static async markAsComplete(id) {
    logger.debug("Marking organization onboarding as complete", { id });
    return await prisma.organizationOnboarding.update({
      where: {
        id
      },
      data: {
        error: null,
        isComplete: true
      }
    });
  }
  static async update(id, data) {
    logger.debug("Updating organization onboarding", safeStringify({ id, data }));
    const { organizationId, createdById: _, ...rest } = data;
    return await prisma.organizationOnboarding.update({
      where: {
        id
      },
      data: {
        ...rest,
        ...organizationId ? { organization: { connect: { id: organizationId } } } : {},
        updatedAt: /* @__PURE__ */ new Date()
      }
    });
  }
  static async findByOrganizationId(organizationId) {
    logger.debug("Finding organization onboarding by organization id", safeStringify({ organizationId }));
    return await prisma.organizationOnboarding.findUnique({
      where: {
        organizationId
      }
    });
  }
  static async findAllBySlug(slug) {
    logger.debug("Finding all organization onboardings by slug", safeStringify({ slug }));
    return await prisma.organizationOnboarding.findMany({
      where: {
        slug
      }
    });
  }
  static async delete(id) {
    logger.debug("Deleting organization onboarding", { id });
    return await prisma.organizationOnboarding.delete({
      where: {
        id
      }
    });
  }
}
class StripeBillingService {
  constructor() {
    this.stripe = new Stripe(process.env.STRIPE_PRIVATE_KEY, {
      apiVersion: "2020-08-27"
    });
  }
  async createCustomer(args) {
    const { email, metadata } = args;
    const customer = await this.stripe.customers.create({
      email,
      metadata: {
        email,
        ...metadata
      }
    });
    return { stripeCustomerId: customer.id };
  }
  async createPaymentIntent(args) {
    const { customerId, amount, metadata } = args;
    const paymentIntent = await this.stripe.paymentIntents.create({
      customer: customerId,
      amount,
      currency: "usd",
      automatic_payment_methods: {
        enabled: true
      },
      metadata
    });
    return {
      id: paymentIntent.id,
      client_secret: paymentIntent.client_secret
    };
  }
  async createOneTimeCheckout(args) {
    const { priceId, quantity, successUrl, cancelUrl, metadata } = args;
    const session = await this.stripe.checkout.sessions.create({
      line_items: [{ price: priceId, quantity }],
      mode: "payment",
      success_url: successUrl,
      cancel_url: cancelUrl,
      metadata,
      invoice_creation: {
        enabled: true
      }
    });
    return {
      checkoutUrl: session.url,
      sessionId: session.id
    };
  }
  async createSubscriptionCheckout(args) {
    const {
      customerId,
      successUrl,
      cancelUrl,
      priceId,
      quantity,
      metadata,
      mode = "subscription",
      allowPromotionCodes = true,
      customerUpdate,
      automaticTax,
      discounts,
      subscriptionData
    } = args;
    const session = await this.stripe.checkout.sessions.create({
      customer: customerId,
      success_url: successUrl,
      cancel_url: cancelUrl,
      mode,
      metadata,
      line_items: [
        {
          price: priceId,
          quantity
        }
      ],
      allow_promotion_codes: allowPromotionCodes,
      customer_update: customerUpdate,
      automatic_tax: automaticTax,
      discounts,
      subscription_data: subscriptionData
    });
    return {
      checkoutUrl: session.url,
      sessionId: session.id
    };
  }
  async createPrice(args) {
    const { amount, currency, interval, productId, nickname, metadata } = args;
    const price = await this.stripe.prices.create({
      nickname,
      unit_amount: amount,
      currency,
      recurring: {
        interval
      },
      product: productId,
      metadata
    });
    return {
      priceId: price.id
    };
  }
  async handleSubscriptionCreation(subscriptionId) {
    throw new Error("Method not implemented.");
  }
  async handleSubscriptionCancel(subscriptionId) {
    await this.stripe.subscriptions.cancel(subscriptionId);
  }
  async handleSubscriptionUpdate(args) {
    const { subscriptionId, subscriptionItemId, membershipCount } = args;
    const subscription = await this.stripe.subscriptions.retrieve(subscriptionId);
    const subscriptionQuantity = subscription.items.data.find(
      (sub) => sub.id === subscriptionItemId
    )?.quantity;
    if (!subscriptionQuantity)
      throw new Error("Subscription not found");
    await this.stripe.subscriptions.update(subscriptionId, {
      items: [{ quantity: membershipCount, id: subscriptionItemId }]
    });
  }
  async handleEndTrial(subscriptionId) {
    const subscription = await this.stripe.subscriptions.retrieve(subscriptionId);
    if (subscription.status !== "trialing") {
      return;
    }
    await this.stripe.subscriptions.update(subscriptionId, {
      trial_end: "now"
    });
  }
  async checkoutSessionIsPaid(paymentId) {
    const checkoutSession = await this.stripe.checkout.sessions.retrieve(paymentId);
    return checkoutSession.payment_status === "paid";
  }
  async getSubscriptionStatus(subscriptionId) {
    const subscription = await this.stripe.subscriptions.retrieve(subscriptionId);
    if (!subscription || !subscription.status)
      return null;
    return subscription.status;
  }
  async getCheckoutSession(checkoutSessionId) {
    const checkoutSession = await this.stripe.checkout.sessions.retrieve(checkoutSessionId);
    return checkoutSession;
  }
  async getCustomer(customerId) {
    const customer = await this.stripe.customers.retrieve(customerId);
    return customer;
  }
  async getSubscriptions(customerId) {
    const subscriptions = await this.stripe.subscriptions.list({ customer: customerId });
    return subscriptions.data;
  }
  async updateCustomer(args) {
    const { customerId, email, userId } = args;
    const metadata = {};
    if (email)
      metadata.email = email;
    if (userId)
      metadata.userId = userId;
    await this.stripe.customers.update(customerId, { metadata });
  }
  async getPrice(priceId) {
    const price = await this.stripe.prices.retrieve(priceId);
    return price;
  }
}
class BillingFactory {
  constructor() {
    return new StripeBillingService();
  }
}
const globalForBilling = global;
const billing = globalForBilling.billingService || new BillingFactory();
if (process.env.NODE_ENV !== "production") {
  globalForBilling.billingService = billing;
}
const TeamBillingPublishResponseStatus = {
  REQUIRES_PAYMENT: "REQUIRES_PAYMENT",
  REQUIRES_UPGRADE: "REQUIRES_UPGRADE",
  SUCCESS: "SUCCESS"
};
const log = logger.getSubLogger({ prefix: ["TeamBilling"] });
const teamPaymentMetadataSchema = teamMetadataStrictSchema.unwrap();
class InternalTeamBilling {
  constructor(team) {
    this.team = team;
  }
  set team(team) {
    const metadata = teamPaymentMetadataSchema.parse(team.metadata || {});
    this._team = { ...team, metadata };
  }
  get team() {
    return this._team;
  }
  async getOrgIfNeeded() {
    if (!this.team.parentId)
      return;
    const parentTeam = await prisma.team.findUniqueOrThrow({
      where: { id: this.team.parentId },
      select: { metadata: true, id: true, parentId: true, isOrganization: true }
    });
    this.team = parentTeam;
  }
  logErrorFromUnknown(error) {
    let message = "Unknown error on InternalTeamBilling.";
    if (error instanceof Error)
      message = error.message;
    log.error(message);
  }
  async cancel() {
    try {
      const { subscriptionId } = this.team.metadata;
      log.info(`Cancelling subscription ${subscriptionId} for team ${this.team.id}`);
      if (!subscriptionId)
        throw Error("missing subscriptionId");
      await billing.handleSubscriptionCancel(subscriptionId);
      await this.downgrade();
      log.info(`Cancelled subscription ${subscriptionId} for team ${this.team.id}`);
    } catch (error) {
      this.logErrorFromUnknown(error);
    }
  }
  // New teams are published on creation, this is for backwards compatibility
  async publish() {
    const { url } = await this.checkIfTeamPaymentRequired();
    const teamId = this.team.id;
    if (url) {
      return { redirectUrl: url, status: TeamBillingPublishResponseStatus.REQUIRES_UPGRADE };
    }
    const requestedSlug = this.team.metadata?.requestedSlug || "";
    const membershipCount = await prisma.membership.count({ where: { teamId } });
    const owner = await prisma.membership.findFirstOrThrow({
      where: { teamId, role: "OWNER" },
      select: {
        userId: true
      }
    });
    try {
      const checkoutSession = await purchaseTeamOrOrgSubscription({
        teamId,
        seatsUsed: membershipCount,
        userId: owner.userId,
        pricePerSeat: null
      });
      if (checkoutSession.url) {
        return {
          redirectUrl: checkoutSession.url,
          status: TeamBillingPublishResponseStatus.REQUIRES_PAYMENT
        };
      }
      const { mergeMetadata } = getMetadataHelpers(teamPaymentMetadataSchema, this.team.metadata);
      const data = {
        metadata: mergeMetadata({ requestedSlug: void 0 })
      };
      if (requestedSlug)
        data.slug = requestedSlug;
      await prisma.team.update({ where: { id: teamId }, data });
      return { status: TeamBillingPublishResponseStatus.SUCCESS, redirectUrl: null };
    } catch (error) {
      if (error instanceof Redirect)
        throw error;
      const { message } = getRequestedSlugError(error, requestedSlug);
      throw Error(message);
    }
  }
  async downgrade() {
    try {
      const { mergeMetadata } = getMetadataHelpers(teamPaymentMetadataSchema, this.team.metadata);
      const metadata = mergeMetadata({
        paymentId: void 0,
        subscriptionId: void 0,
        subscriptionItemId: void 0
      });
      await prisma.team.update({ where: { id: this.team.id }, data: { metadata } });
      log.info(`Downgraded team ${this.team.id}`);
    } catch (error) {
      this.logErrorFromUnknown(error);
    }
  }
  async updateQuantity() {
    try {
      await this.getOrgIfNeeded();
      const { id: teamId, metadata, isOrganization } = this.team;
      const { url } = await this.checkIfTeamPaymentRequired();
      const organizationOnboarding = await OrganizationOnboardingRepository.findByOrganizationId(
        this.team.id
      );
      log.debug("updateQuantity", safeStringify({ url, team: this.team }));
      if (!url && !isOrganization)
        return;
      const { subscriptionId, subscriptionItemId, orgSeats } = metadata;
      const orgMinimumSubscriptionQuantity = orgSeats || MINIMUM_NUMBER_OF_ORG_SEATS;
      const membershipCount = await prisma.membership.count({ where: { teamId } });
      if (isOrganization && membershipCount < orgMinimumSubscriptionQuantity) {
        log.info(
          `Org ${teamId} has less members than the min required ${orgMinimumSubscriptionQuantity}, skipping updating subscription.`
        );
        return;
      }
      if (!subscriptionId)
        throw Error("missing subscriptionId");
      if (!subscriptionItemId)
        throw Error("missing subscriptionItemId");
      await billing.handleSubscriptionUpdate({ subscriptionId, subscriptionItemId, membershipCount });
      log.info(`Updated subscription ${subscriptionId} for team ${teamId} to ${membershipCount} seats.`);
    } catch (error) {
      this.logErrorFromUnknown(error);
    }
  }
  /** Used to prevent double charges for the same team */
  async checkIfTeamPaymentRequired() {
    const { paymentId } = this.team.metadata || {};
    if (!paymentId)
      return { url: null, paymentId: null, paymentRequired: true };
    const checkoutSessionIsPaid = await billing.checkoutSessionIsPaid(paymentId);
    if (!checkoutSessionIsPaid)
      return { url: null, paymentId, paymentRequired: true };
    return {
      url: `${WEBAPP_URL}/api/teams/${this.team.id}/upgrade?session_id=${paymentId}`,
      paymentId,
      paymentRequired: false
    };
  }
  /** Returns the subscription status (active, past_due, trialing, ...) */
  async getSubscriptionStatus() {
    const { subscriptionId } = this.team.metadata;
    if (!subscriptionId)
      return null;
    return await billing.getSubscriptionStatus(subscriptionId);
  }
  /**
   * Ends the trial period for a team subscription by converting it to a regular subscription
   * @returns {Promise<boolean>} True if successful, false otherwise
   */
  async endTrial() {
    try {
      const { subscriptionId } = this.team.metadata;
      log.info(`Ending trial for subscription ${subscriptionId} of team ${this.team.id}`);
      if (!subscriptionId) {
        log.warn(`No subscription ID found for team ${this.team.id}`);
        return false;
      }
      await billing.handleEndTrial(subscriptionId);
      log.info(`Successfully ended trial for team ${this.team.id}`);
      return true;
    } catch (error) {
      this.logErrorFromUnknown(error);
      return false;
    }
  }
}
export {
  InternalTeamBilling as I,
  StripeBillingService as S,
  TeamBillingPublishResponseStatus as T,
  isSMSOrWhatsappAction as a,
  isSMSAction as b,
  scheduleEmailReminder as c,
  isWhatsappAction as d,
  scheduleWhatsappReminder as e,
  isCalAIAction as f,
  scheduleAIPhoneCall as g,
  deleteScheduledEmailReminder as h,
  isAttendeeAction as i,
  deleteScheduledSMSReminder as j,
  deleteScheduledAIPhoneCall as k,
  emailReminderTemplate as l,
  emailRatingTemplate as m,
  stripe$1 as n,
  isEmailAction as o,
  scheduleSMSReminder as s
};
