import jsforce from "@jsforce/jsforce-node";
import { RRule } from "rrule";
import { z } from "zod";
import { r as routingFormResponseInDbSchema, a as zodNonRouterField, S as SalesforceRecordEnum, W as WhenToWriteToRecord, b as SalesforceFieldType, D as DateFieldTypeData, R as RoutingReasons } from "./zod--LUuGs8w.js";
import { b as getLocation } from "./CalEventParser-tm9lJVKT.js";
import { p as prisma, A as APP_CREDENTIAL_SHARING_ENABLED, l as logger, a as safeStringify, q as WEBAPP_URL } from "./i18n-tiaWqB1b.js";
import { g as getFieldIdentifier, c as checkIfFreeEmailDomain } from "./getFieldIdentifier-V_HVmaX7.js";
import { f as appMeta } from "./utils-kKqt-Je5.js";
import { g as getParsedAppKeysFromSlug } from "./getParsedAppKeysFromSlug-QgCsBSun.js";
import { Client, cacheExchange, fetchExchange } from "@urql/core";
function parseRoutingFormResponse(rawResponse, formFields) {
  const response = routingFormResponseInDbSchema.parse(rawResponse);
  const fields = zodNonRouterField.array().parse(formFields);
  return { response, fields };
}
class RoutingFormResponseDataFactory {
  constructor(deps) {
    this.deps = deps;
  }
  async createWithBookingUid(bookingUid) {
    const log = this.deps.logger.getSubLogger({
      prefix: ["[routingFormFieldService]", { bookingUid }]
    });
    const formResponse = await this.deps.routingFormResponseRepo.findByBookingUidIncludeForm(bookingUid);
    if (!formResponse) {
      log.error("Form response not found");
      throw new Error("Form response not found");
    }
    return parseRoutingFormResponse(formResponse.response, formResponse.form.fields);
  }
  async createWithResponseId(responseId) {
    const log = this.deps.logger.getSubLogger({
      prefix: ["[routingFormFieldService]", { responseId }]
    });
    const formResponse = await this.deps.routingFormResponseRepo.findByIdIncludeForm(responseId);
    if (!formResponse) {
      log.error("Form response not found");
      throw new Error("Form response not found");
    }
    return parseRoutingFormResponse(formResponse.response, formResponse.form.fields);
  }
}
class RetryableError extends Error {
  constructor(message) {
    super(message);
  }
}
class PrismaAssignmentReasonRepository {
  static async findLatestReasonFromBookingUid(bookingUid) {
    return await prisma.assignmentReason.findFirst({
      where: {
        booking: {
          uid: bookingUid
        }
      },
      orderBy: {
        createdAt: "desc"
      }
    });
  }
}
class PrismaRoutingFormResponseRepository {
  constructor(prismaClient = prisma) {
    this.prismaClient = prismaClient;
  }
  findByIdIncludeForm(id) {
    return this.prismaClient.app_RoutingForms_FormResponse.findUnique({
      where: {
        id
      },
      include: {
        form: {
          select: {
            fields: true
          }
        }
      }
    });
  }
  findByBookingUidIncludeForm(bookingUid) {
    return this.prismaClient.app_RoutingForms_FormResponse.findUnique({
      where: {
        routedToBookingUid: bookingUid
      },
      include: {
        form: {
          select: {
            fields: true
          }
        }
      }
    });
  }
}
const minimumTokenResponseSchema = z.object({
  access_token: z.string()
}).passthrough().superRefine((tokenObject, ctx) => {
  if (!Object.values(tokenObject).some((value) => typeof value === "number")) {
    ctx.addIssue({
      code: z.ZodIssueCode.custom,
      message: "Missing a field that defines a token expiry date. Check the specific app package to see how token expiry is defined."
    });
  }
});
const parseRefreshTokenResponse = (response, schema) => {
  let refreshTokenResponse;
  const credentialSyncingEnabled = APP_CREDENTIAL_SHARING_ENABLED && process.env.CALCOM_CREDENTIAL_SYNC_ENDPOINT;
  if (APP_CREDENTIAL_SHARING_ENABLED && process.env.CALCOM_CREDENTIAL_SYNC_ENDPOINT) {
    refreshTokenResponse = minimumTokenResponseSchema.safeParse(response);
  } else {
    refreshTokenResponse = schema.safeParse(response);
  }
  if (!refreshTokenResponse.success) {
    throw new Error("Invalid refreshed tokens were returned");
  }
  if (!refreshTokenResponse.data.refresh_token && credentialSyncingEnabled) {
    refreshTokenResponse.data.refresh_token = "refresh_token";
  }
  return refreshTokenResponse.data;
};
const parseRefreshTokenResponse$1 = parseRefreshTokenResponse;
function findFieldValueByIdentifier(data, identifier) {
  const field = data.fields.find((field2) => getFieldIdentifier(field2) === identifier);
  if (!field) {
    return { success: false, error: `Field with identifier ${identifier} not found` };
  }
  const fieldValue = data.response[field.id]?.value;
  return { success: true, data: fieldValue ?? null };
}
const salesforceAppKeysSchema = z.object({
  consumer_key: z.string(),
  consumer_secret: z.string()
});
const getSalesforceAppKeys = async () => {
  return getParsedAppKeysFromSlug("salesforce", salesforceAppKeysSchema);
};
function getAllPossibleWebsiteValuesFromEmailDomain(emailDomain) {
  return [
    emailDomain,
    `www.${emailDomain}`,
    `http://www.${emailDomain}`,
    `http://${emailDomain}`,
    `https://www.${emailDomain}`,
    `https://${emailDomain}`
  ];
}
function getDominantAccountId(contacts) {
  const log = logger.getSubLogger({ prefix: [`[getDominantAccountId]:${contacts}`] });
  const iterateLength = Math.ceil(contacts.length / 2);
  const accountIdCounts = {};
  for (const contact of contacts) {
    const accountId = contact.AccountId;
    accountIdCounts[accountId] = (accountIdCounts[accountId] || 0) + 1;
    if (accountIdCounts[accountId] > iterateLength)
      return accountId;
  }
  let dominantAccountId;
  let highestCount = 0;
  for (const accountId in accountIdCounts) {
    if (accountIdCounts[accountId] > highestCount) {
      highestCount = accountIdCounts[accountId];
      dominantAccountId = accountId;
    }
  }
  log.info("Dominant AccountId", safeStringify({ dominantAccountId }));
  return dominantAccountId;
}
const GetAccountRecordsForRrSkipDocument = {
  kind: "Document",
  definitions: [
    {
      kind: "OperationDefinition",
      operation: "query",
      name: { kind: "Name", value: "GetAccountRecordsForRRSkip" },
      variableDefinitions: [
        {
          kind: "VariableDefinition",
          variable: { kind: "Variable", name: { kind: "Name", value: "email" } },
          type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "Email" } } }
        },
        {
          kind: "VariableDefinition",
          variable: { kind: "Variable", name: { kind: "Name", value: "websites" } },
          type: {
            kind: "NonNullType",
            type: {
              kind: "ListType",
              type: {
                kind: "NonNullType",
                type: { kind: "NamedType", name: { kind: "Name", value: "Url" } }
              }
            }
          }
        },
        {
          kind: "VariableDefinition",
          variable: { kind: "Variable", name: { kind: "Name", value: "emailDomain" } },
          type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "Email" } } }
        }
      ],
      selectionSet: {
        kind: "SelectionSet",
        selections: [
          {
            kind: "Field",
            name: { kind: "Name", value: "uiapi" },
            selectionSet: {
              kind: "SelectionSet",
              selections: [
                {
                  kind: "Field",
                  name: { kind: "Name", value: "query" },
                  selectionSet: {
                    kind: "SelectionSet",
                    selections: [
                      {
                        kind: "Field",
                        name: { kind: "Name", value: "Contact" },
                        arguments: [
                          {
                            kind: "Argument",
                            name: { kind: "Name", value: "where" },
                            value: {
                              kind: "ObjectValue",
                              fields: [
                                {
                                  kind: "ObjectField",
                                  name: { kind: "Name", value: "Email" },
                                  value: {
                                    kind: "ObjectValue",
                                    fields: [
                                      {
                                        kind: "ObjectField",
                                        name: { kind: "Name", value: "eq" },
                                        value: { kind: "Variable", name: { kind: "Name", value: "email" } }
                                      }
                                    ]
                                  }
                                }
                              ]
                            }
                          }
                        ],
                        selectionSet: {
                          kind: "SelectionSet",
                          selections: [
                            {
                              kind: "Field",
                              name: { kind: "Name", value: "edges" },
                              selectionSet: {
                                kind: "SelectionSet",
                                selections: [
                                  {
                                    kind: "Field",
                                    name: { kind: "Name", value: "node" },
                                    selectionSet: {
                                      kind: "SelectionSet",
                                      selections: [
                                        { kind: "Field", name: { kind: "Name", value: "Id" } },
                                        {
                                          kind: "Field",
                                          name: { kind: "Name", value: "Email" },
                                          selectionSet: {
                                            kind: "SelectionSet",
                                            selections: [
                                              { kind: "Field", name: { kind: "Name", value: "value" } }
                                            ]
                                          }
                                        },
                                        {
                                          kind: "Field",
                                          name: { kind: "Name", value: "OwnerId" },
                                          selectionSet: {
                                            kind: "SelectionSet",
                                            selections: [
                                              { kind: "Field", name: { kind: "Name", value: "value" } }
                                            ]
                                          }
                                        },
                                        {
                                          kind: "Field",
                                          name: { kind: "Name", value: "AccountId" },
                                          selectionSet: {
                                            kind: "SelectionSet",
                                            selections: [
                                              { kind: "Field", name: { kind: "Name", value: "value" } }
                                            ]
                                          }
                                        },
                                        {
                                          kind: "Field",
                                          name: { kind: "Name", value: "Account" },
                                          selectionSet: {
                                            kind: "SelectionSet",
                                            selections: [
                                              {
                                                kind: "Field",
                                                name: { kind: "Name", value: "Website" },
                                                selectionSet: {
                                                  kind: "SelectionSet",
                                                  selections: [
                                                    { kind: "Field", name: { kind: "Name", value: "value" } }
                                                  ]
                                                }
                                              },
                                              {
                                                kind: "Field",
                                                name: { kind: "Name", value: "Owner" },
                                                selectionSet: {
                                                  kind: "SelectionSet",
                                                  selections: [
                                                    { kind: "Field", name: { kind: "Name", value: "Id" } },
                                                    {
                                                      kind: "Field",
                                                      name: { kind: "Name", value: "Email" },
                                                      selectionSet: {
                                                        kind: "SelectionSet",
                                                        selections: [
                                                          {
                                                            kind: "Field",
                                                            name: { kind: "Name", value: "value" }
                                                          }
                                                        ]
                                                      }
                                                    }
                                                  ]
                                                }
                                              }
                                            ]
                                          }
                                        }
                                      ]
                                    }
                                  }
                                ]
                              }
                            }
                          ]
                        }
                      },
                      {
                        kind: "Field",
                        name: { kind: "Name", value: "Account" },
                        arguments: [
                          {
                            kind: "Argument",
                            name: { kind: "Name", value: "where" },
                            value: {
                              kind: "ObjectValue",
                              fields: [
                                {
                                  kind: "ObjectField",
                                  name: { kind: "Name", value: "Website" },
                                  value: {
                                    kind: "ObjectValue",
                                    fields: [
                                      {
                                        kind: "ObjectField",
                                        name: { kind: "Name", value: "in" },
                                        value: {
                                          kind: "Variable",
                                          name: { kind: "Name", value: "websites" }
                                        }
                                      }
                                    ]
                                  }
                                }
                              ]
                            }
                          }
                        ],
                        selectionSet: {
                          kind: "SelectionSet",
                          selections: [
                            {
                              kind: "Field",
                              name: { kind: "Name", value: "edges" },
                              selectionSet: {
                                kind: "SelectionSet",
                                selections: [
                                  {
                                    kind: "Field",
                                    name: { kind: "Name", value: "node" },
                                    selectionSet: {
                                      kind: "SelectionSet",
                                      selections: [
                                        { kind: "Field", name: { kind: "Name", value: "Id" } },
                                        {
                                          kind: "Field",
                                          name: { kind: "Name", value: "Website" },
                                          selectionSet: {
                                            kind: "SelectionSet",
                                            selections: [
                                              { kind: "Field", name: { kind: "Name", value: "value" } }
                                            ]
                                          }
                                        },
                                        {
                                          kind: "Field",
                                          name: { kind: "Name", value: "Owner" },
                                          selectionSet: {
                                            kind: "SelectionSet",
                                            selections: [
                                              { kind: "Field", name: { kind: "Name", value: "Id" } },
                                              {
                                                kind: "Field",
                                                name: { kind: "Name", value: "Email" },
                                                selectionSet: {
                                                  kind: "SelectionSet",
                                                  selections: [
                                                    { kind: "Field", name: { kind: "Name", value: "value" } }
                                                  ]
                                                }
                                              }
                                            ]
                                          }
                                        }
                                      ]
                                    }
                                  }
                                ]
                              }
                            }
                          ]
                        }
                      },
                      {
                        kind: "Field",
                        alias: { kind: "Name", value: "relatedContacts" },
                        name: { kind: "Name", value: "Contact" },
                        arguments: [
                          {
                            kind: "Argument",
                            name: { kind: "Name", value: "where" },
                            value: {
                              kind: "ObjectValue",
                              fields: [
                                {
                                  kind: "ObjectField",
                                  name: { kind: "Name", value: "and" },
                                  value: {
                                    kind: "ListValue",
                                    values: [
                                      {
                                        kind: "ObjectValue",
                                        fields: [
                                          {
                                            kind: "ObjectField",
                                            name: { kind: "Name", value: "Email" },
                                            value: {
                                              kind: "ObjectValue",
                                              fields: [
                                                {
                                                  kind: "ObjectField",
                                                  name: { kind: "Name", value: "like" },
                                                  value: {
                                                    kind: "Variable",
                                                    name: { kind: "Name", value: "emailDomain" }
                                                  }
                                                }
                                              ]
                                            }
                                          }
                                        ]
                                      },
                                      {
                                        kind: "ObjectValue",
                                        fields: [
                                          {
                                            kind: "ObjectField",
                                            name: { kind: "Name", value: "Email" },
                                            value: {
                                              kind: "ObjectValue",
                                              fields: [
                                                {
                                                  kind: "ObjectField",
                                                  name: { kind: "Name", value: "ne" },
                                                  value: {
                                                    kind: "Variable",
                                                    name: { kind: "Name", value: "email" }
                                                  }
                                                }
                                              ]
                                            }
                                          }
                                        ]
                                      },
                                      {
                                        kind: "ObjectValue",
                                        fields: [
                                          {
                                            kind: "ObjectField",
                                            name: { kind: "Name", value: "AccountId" },
                                            value: {
                                              kind: "ObjectValue",
                                              fields: [
                                                {
                                                  kind: "ObjectField",
                                                  name: { kind: "Name", value: "ne" },
                                                  value: { kind: "NullValue" }
                                                }
                                              ]
                                            }
                                          }
                                        ]
                                      }
                                    ]
                                  }
                                }
                              ]
                            }
                          }
                        ],
                        selectionSet: {
                          kind: "SelectionSet",
                          selections: [
                            {
                              kind: "Field",
                              name: { kind: "Name", value: "edges" },
                              selectionSet: {
                                kind: "SelectionSet",
                                selections: [
                                  {
                                    kind: "Field",
                                    name: { kind: "Name", value: "node" },
                                    selectionSet: {
                                      kind: "SelectionSet",
                                      selections: [
                                        { kind: "Field", name: { kind: "Name", value: "Id" } },
                                        {
                                          kind: "Field",
                                          name: { kind: "Name", value: "Email" },
                                          selectionSet: {
                                            kind: "SelectionSet",
                                            selections: [
                                              { kind: "Field", name: { kind: "Name", value: "value" } }
                                            ]
                                          }
                                        },
                                        {
                                          kind: "Field",
                                          name: { kind: "Name", value: "AccountId" },
                                          selectionSet: {
                                            kind: "SelectionSet",
                                            selections: [
                                              { kind: "Field", name: { kind: "Name", value: "value" } }
                                            ]
                                          }
                                        },
                                        {
                                          kind: "Field",
                                          name: { kind: "Name", value: "Account" },
                                          selectionSet: {
                                            kind: "SelectionSet",
                                            selections: [
                                              { kind: "Field", name: { kind: "Name", value: "Id" } },
                                              {
                                                kind: "Field",
                                                name: { kind: "Name", value: "Owner" },
                                                selectionSet: {
                                                  kind: "SelectionSet",
                                                  selections: [
                                                    { kind: "Field", name: { kind: "Name", value: "Id" } },
                                                    {
                                                      kind: "Field",
                                                      name: { kind: "Name", value: "Email" },
                                                      selectionSet: {
                                                        kind: "SelectionSet",
                                                        selections: [
                                                          {
                                                            kind: "Field",
                                                            name: { kind: "Name", value: "value" }
                                                          }
                                                        ]
                                                      }
                                                    }
                                                  ]
                                                }
                                              }
                                            ]
                                          }
                                        }
                                      ]
                                    }
                                  }
                                ]
                              }
                            }
                          ]
                        }
                      }
                    ]
                  }
                }
              ]
            }
          }
        ]
      }
    }
  ]
};
const documents = {
  "\n  query GetAccountRecordsForRRSkip($email: Email!, $websites: [Url!]!, $emailDomain: Email!) {\n    uiapi {\n      query {\n        Contact(where: { Email: { eq: $email } }) {\n          edges {\n            node {\n              Id\n              Email {\n                value\n              }\n              OwnerId {\n                value\n              }\n              AccountId {\n                value\n              }\n              Account {\n                Website {\n                  value\n                }\n                Owner {\n                  Id\n                  Email {\n                    value\n                  }\n                }\n              }\n            }\n          }\n        }\n        Account(where: { Website: { in: $websites } }) {\n          edges {\n            node {\n              Id\n              Website {\n                value\n              }\n              Owner {\n                Id\n                Email {\n                  value\n                }\n              }\n            }\n          }\n        }\n        relatedContacts: Contact(\n          where: {\n            and: [{ Email: { like: $emailDomain } }, { Email: { ne: $email } }, { AccountId: { ne: null } }]\n          }\n        ) {\n          edges {\n            node {\n              Id\n              Email {\n                value\n              }\n              AccountId {\n                value\n              }\n              Account {\n                Id\n                Owner {\n                  Id\n                  Email {\n                    value\n                  }\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n": GetAccountRecordsForRrSkipDocument
};
function graphql(source) {
  return documents[source] ?? {};
}
const GetAccountRecordsForRRSkip = graphql(`
  query GetAccountRecordsForRRSkip($email: Email!, $websites: [Url!]!, $emailDomain: Email!) {
    uiapi {
      query {
        Contact(where: { Email: { eq: $email } }) {
          edges {
            node {
              Id
              Email {
                value
              }
              OwnerId {
                value
              }
              AccountId {
                value
              }
              Account {
                Website {
                  value
                }
                Owner {
                  Id
                  Email {
                    value
                  }
                }
              }
            }
          }
        }
        Account(where: { Website: { in: $websites } }) {
          edges {
            node {
              Id
              Website {
                value
              }
              Owner {
                Id
                Email {
                  value
                }
              }
            }
          }
        }
        relatedContacts: Contact(
          where: {
            and: [{ Email: { like: $emailDomain } }, { Email: { ne: $email } }, { AccountId: { ne: null } }]
          }
        ) {
          edges {
            node {
              Id
              Email {
                value
              }
              AccountId {
                value
              }
              Account {
                Id
                Owner {
                  Id
                  Email {
                    value
                  }
                }
              }
            }
          }
        }
      }
    }
  }
`);
class SalesforceGraphQLClient {
  constructor({ accessToken, instanceUrl }) {
    this.version = "v63.0";
    this.accessToken = accessToken;
    this.client = new Client({
      url: `${instanceUrl}/services/data/${this.version}/graphql`,
      exchanges: [cacheExchange, fetchExchange],
      fetchOptions: () => {
        return {
          headers: { authorization: `Bearer ${this.accessToken}` },
          "Content-Type": "application/json"
        };
      }
    });
    this.log = logger.getSubLogger({ prefix: ["[SalesforceGraphQLClient]"] });
  }
  /**
   * Returns the owner of an account. There are three methods we use to the find the account owner
   * 1. If there is a contact with that matches the email, return the account owner
   * 2. If no contact is found, then find an account that is an exact match of the email domain
   * 3. If no account is found, then find contacts that match the email domain and find the account that the majority of contacts are connect to
   */
  async GetAccountRecordsForRRSkip(email) {
    const log = logger.getSubLogger({ prefix: [`[getAccountRecordsForRRSkip]:${email}`] });
    const emailDomain = email.split("@")[1];
    const websites = this.getAllPossibleAccountWebsiteFromEmailDomain(emailDomain);
    log.info(`Query against email and email domain of ${emailDomain}`);
    const query = await this.client.query(GetAccountRecordsForRRSkip, {
      email,
      websites,
      emailDomain: `%@${emailDomain}`
    });
    const queryData = query?.data;
    if (query?.error) {
      const errors = query.error;
      if (errors.graphQLErrors.length) {
        log.error("GraphQL error", safeStringify({ errors }));
      }
      if (errors.networkError) {
        log.error("Network error", safeStringify({ errors }));
      }
    }
    if (!queryData) {
      log.error("No query data found", safeStringify({ error: query?.error }));
      return [];
    }
    if (queryData.uiapi.query.Contact) {
      const contact = queryData.uiapi.query.Contact?.edges?.[0]?.node;
      if (contact) {
        log.info(`Existing contact found with id ${contact.Id}`);
        return [
          {
            id: contact.AccountId?.value || contact.Id,
            email: contact.Email?.value,
            ownerId: contact.Account?.Owner?.Id,
            ownerEmail: contact.Account?.Owner?.Email?.value,
            recordType: SalesforceRecordEnum.ACCOUNT
          }
        ];
      }
    }
    if (queryData.uiapi.query.Account) {
      const account = queryData.uiapi.query.Account?.edges?.[0]?.node;
      if (account) {
        log.info(
          `Existing account with website that matches email domain of ${emailDomain} found with id ${account.Id}`
        );
        return [
          {
            id: account.Id,
            email: "",
            ownerId: account.Owner?.Id,
            ownerEmail: account.Owner?.Email?.value,
            recordType: SalesforceRecordEnum.ACCOUNT
          }
        ];
      }
    }
    if (queryData.uiapi.query.relatedContacts) {
      const relatedContactsResults = queryData.uiapi.query.relatedContacts?.edges;
      if (!relatedContactsResults)
        return [];
      const relatedContacts = relatedContactsResults.reduce((contacts, edge) => {
        const node = edge?.node;
        if (!node) {
          log.error("A related contact query didn't include a node");
          return contacts;
        }
        if (!node.AccountId?.value) {
          log.error(`A related contact with id ${node.Id} didn't have an account id`);
          return contacts;
        }
        if (!node.Account?.Owner?.Id) {
          log.error(`A related contact with id ${node.Id} didn't have an account owner id`);
          return contacts;
        }
        if (!node.Account?.Owner?.Email?.value) {
          log.error(`A related contact with id ${node.Id} didn't have an account owner email`);
          return contacts;
        }
        contacts.push({
          id: node.Id,
          AccountId: node.AccountId.value,
          ownerId: node.Account.Owner.Id,
          ownerEmail: node.Account.Owner.Email.value
        });
        return contacts;
      }, []);
      const dominantAccountId = getDominantAccountId(relatedContacts);
      if (!dominantAccountId) {
        log.error(
          "Could not find dominant account id with the following contacts",
          safeStringify({ relatedContacts })
        );
        return [];
      }
      const contactUnderAccount = relatedContacts.find((contact) => contact.AccountId === dominantAccountId);
      if (!contactUnderAccount) {
        log.error(
          `Could not find a contact under the dominant account id ${dominantAccountId}`,
          safeStringify({ relatedContacts })
        );
        return [];
      }
      log.info(`Account found via related contacts with account id ${dominantAccountId}`);
      return [
        {
          id: dominantAccountId,
          email: "",
          ownerId: contactUnderAccount.ownerId,
          ownerEmail: contactUnderAccount.ownerEmail,
          recordType: SalesforceRecordEnum.ACCOUNT
        }
      ];
    }
    log.info("No account found for attendee");
    return [];
  }
  getAllPossibleAccountWebsiteFromEmailDomain(emailDomain) {
    return getAllPossibleWebsiteValuesFromEmailDomain(emailDomain);
  }
}
class SFObjectToUpdateNotFoundError extends RetryableError {
  constructor(message) {
    super(message);
  }
}
const sfApiErrors = {
  INVALID_EVENTWHOIDS: "INVALID_FIELD: No such column 'EventWhoIds' on sobject of type Event"
};
const salesforceTokenSchema = z.object({
  id: z.string(),
  issued_at: z.string(),
  instance_url: z.string(),
  signature: z.string(),
  access_token: z.string(),
  scope: z.string(),
  token_type: z.string()
});
class SalesforceCRMService {
  constructor(credential, appOptions, testMode = false) {
    this.integrationName = "";
    this.calWarnings = [];
    this.doNotCreateEvent = false;
    this.fallbackToContact = false;
    this.getClient = async (credential2) => {
      const { consumer_key, consumer_secret } = await getSalesforceAppKeys();
      const credentialKey2 = credential2.key;
      if (!credentialKey2.refresh_token)
        throw new Error(`Refresh token is missing for credential ${credential2.id}`);
      try {
        const response = await fetch("https://login.salesforce.com/services/oauth2/token", {
          method: "POST",
          headers: {
            "Content-Type": "application/x-www-form-urlencoded"
          },
          body: new URLSearchParams({
            grant_type: "refresh_token",
            client_id: consumer_key,
            client_secret: consumer_secret,
            refresh_token: credentialKey2.refresh_token
          })
        });
        if (!response.ok) {
          const message = `${response.statusText}: ${JSON.stringify(await response.json())}`;
          throw new Error(message);
        }
        const accessTokenJson = await response.json();
        const accessTokenParsed = parseRefreshTokenResponse$1(accessTokenJson, salesforceTokenSchema);
        await prisma.credential.update({
          where: { id: credential2.id },
          data: { key: { ...accessTokenParsed, refresh_token: credentialKey2.refresh_token } }
        });
      } catch (err) {
        console.error(err);
      }
      return new jsforce.Connection({
        oauth2: {
          clientId: consumer_key,
          clientSecret: consumer_secret,
          redirectUri: `${WEBAPP_URL}/api/integrations/salesforce/callback`
        },
        instanceUrl: credentialKey2.instance_url,
        accessToken: credentialKey2.access_token,
        refreshToken: credentialKey2.refresh_token
      });
    };
    this.getSalesforceUserIdFromEmail = async (email) => {
      const conn = await this.conn;
      const query = await conn.query(
        `SELECT Id, Email FROM User WHERE Email = '${email}' AND IsActive = true LIMIT 1`
      );
      if (query.records.length > 0) {
        return query.records[0].Id;
      }
    };
    this.getSalesforceUserFromUserId = async (userId) => {
      const conn = await this.conn;
      return await conn.query(`SELECT Id, Email, Name FROM User WHERE Id = '${userId}' AND IsActive = true`);
    };
    this.getSalesforceEventBody = (event) => {
      return `${event.organizer.language.translate("invitee_timezone")}: ${event.attendees[0].timeZone} \r
\r
 ${event.organizer.language.translate("share_additional_notes")}\r
${event.additionalNotes || "-"}`;
    };
    this.salesforceCreateEventApiCall = async (event, options) => {
      const conn = await this.conn;
      return await conn.sobject("Event").create({
        StartDateTime: new Date(event.startTime).toISOString(),
        EndDateTime: new Date(event.endTime).toISOString(),
        Subject: event.title,
        Description: this.getSalesforceEventBody(event),
        Location: getLocation(event),
        ...options,
        ...event.recurringEvent && {
          IsRecurrence2: true,
          Recurrence2PatternText: new RRule(event.recurringEvent).toString()
        }
      });
    };
    this.salesforceCreateEvent = async (event, contacts) => {
      const log = logger.getSubLogger({ prefix: [`[salesforceCreateEvent]:${event.uid}`] });
      const appOptions2 = this.getAppOptions();
      const writeToEventRecord = await this.generateWriteToEventBody(event);
      log.info(`Writing to event fields: ${Object.keys(writeToEventRecord)} `);
      let ownerId = void 0;
      if (event?.organizer?.email) {
        ownerId = await this.getSalesforceUserIdFromEmail(event.organizer.email);
      } else {
        log.warn("No organizer email found for event", event?.organizer);
      }
      log.info(`Organizer found with Salesforce id ${ownerId}`);
      const [firstContact] = contacts;
      if (!firstContact?.id) {
        log.error("No contacts found for event", { contacts });
        throw new SFObjectToUpdateNotFoundError("No contacts found for event");
      }
      const eventWhoIds = contacts.reduce((contactIds, contact) => {
        if (contact?.id) {
          contactIds.push(contact.id);
        }
        return contactIds;
      }, []);
      if (eventWhoIds.length !== contacts.length) {
        log.warn(`Not all contacts contain ids ${contacts}`);
      }
      const createdEvent = await this.salesforceCreateEventApiCall(event, {
        EventWhoIds: eventWhoIds,
        ...writeToEventRecord,
        ...ownerId && { OwnerId: ownerId }
      }).catch(async (reason) => {
        if (reason === sfApiErrors.INVALID_EVENTWHOIDS) {
          this.calWarnings.push(
            `Please enable option "Allow Users to Relate Multiple Contacts to Tasks and Events" under
           "Setup > Feature Settings > Sales > Activity Settings" to be able to create events with
           multiple contact attendees.`
          );
          return await this.salesforceCreateEventApiCall(event, {
            WhoId: firstContact.id
          }).catch((reason2) => Promise.reject(reason2));
        }
        log.error(`Error creating event: ${JSON.stringify(reason)}`);
        return await this.salesforceCreateEventApiCall(event, {
          WhoId: firstContact.id,
          ...ownerId && { OwnerId: ownerId }
        }).catch((reason2) => {
          log.error(`Error creating simple event: ${JSON.stringify(reason2)}`);
          return Promise.reject(reason2);
        });
      });
      if (appOptions2?.onBookingChangeRecordOwner && appOptions2?.onBookingChangeRecordOwnerName) {
        if (ownerId) {
          await this.checkRecordOwnerNameFromRecordId(firstContact.id, ownerId);
        } else {
          log.warn(
            `Could not find owner with email ${event.organizer.email} to change record ${firstContact.id} ownership to`
          );
        }
      }
      if (appOptions2?.onBookingWriteToRecord && appOptions2?.onBookingWriteToRecordFields) {
        await this.writeToRecord({
          // TODO: firstContact id is assumed to not be undefined. But current code doesn't check for it.
          recordId: firstContact.id,
          fieldsToWriteTo: appOptions2.onBookingWriteToRecordFields,
          startTime: event.startTime,
          organizerEmail: event.organizer?.email,
          calEventResponses: event.responses,
          bookingUid: event?.uid
        });
      }
      return createdEvent;
    };
    this.salesforceUpdateEvent = async (uid, event) => {
      const conn = await this.conn;
      return await conn.sobject("Event").update({
        Id: uid,
        StartDateTime: new Date(event.startTime).toISOString(),
        EndDateTime: new Date(event.endTime).toISOString(),
        Subject: event.title,
        Description: this.getSalesforceEventBody(event),
        Location: getLocation(event),
        ...event.recurringEvent && {
          IsRecurrence2: true,
          Recurrence2PatternText: new RRule(event.recurringEvent).toString()
        }
      });
    };
    this.salesforceDeleteEvent = async (uid, event) => {
      const appOptions2 = this.getAppOptions();
      const conn = await this.conn;
      if (appOptions2?.onCancelWriteToEventRecord) {
        const fieldsToWriteTo = appOptions2?.onCancelWriteToEventRecordFields;
        if (!fieldsToWriteTo || !Object.keys(fieldsToWriteTo)) {
          return Promise.resolve();
        }
        return await this.writeToRecord({
          recordId: uid,
          fieldsToWriteTo,
          startTime: event.startTime,
          organizerEmail: event.organizer?.email,
          calEventResponses: event.responses,
          bookingUid: event?.uid
        });
      }
      return await conn.sobject("Event").delete(uid);
    };
    this.integrationName = "salesforce_other_calendar";
    if (!testMode) {
      this.conn = this.getClient(credential).then((c) => c);
    }
    this.log = logger.getSubLogger({ prefix: [`[[lib] ${this.integrationName}`] });
    this.appOptions = appOptions;
    const credentialKey = credential.key;
    this.accessToken = credentialKey.access_token;
    this.instanceUrl = credentialKey.instance_url;
  }
  getAppOptions() {
    return this.appOptions;
  }
  async handleEventCreation(event, contacts) {
    const sfEvent = await this.salesforceCreateEvent(event, contacts);
    if (sfEvent.success) {
      this.log.info("event:creation:ok", { sfEvent });
      return {
        uid: sfEvent.id,
        id: sfEvent.id,
        type: "salesforce_other_calendar",
        password: "",
        url: "",
        additionalInfo: { contacts, sfEvent, calWarnings: this.calWarnings }
      };
    }
    this.log.info("event:creation:notOk", { event, sfEvent, contacts });
    return Promise.reject({
      calError: "Something went wrong when creating an event in Salesforce"
    });
  }
  async createEvent(event, contacts) {
    const skipEventCreation = this.getDoNotCreateEvent();
    if (skipEventCreation)
      return void 0;
    const sfEvent = await this.salesforceCreateEvent(event, contacts);
    if (sfEvent.success) {
      return {
        uid: sfEvent.id,
        id: sfEvent.id,
        type: "salesforce_other_calendar",
        password: "",
        url: "",
        additionalInfo: { contacts, sfEvent, calWarnings: this.calWarnings }
      };
    }
    this.log.debug("event:creation:notOk", { event, sfEvent, contacts });
    return Promise.reject("Something went wrong when creating an event in Salesforce");
  }
  async updateEvent(uid, event) {
    const updatedEvent = await this.salesforceUpdateEvent(uid, event);
    if (updatedEvent.success) {
      return {
        uid: updatedEvent.id,
        id: updatedEvent.id,
        type: "salesforce_other_calendar",
        password: "",
        url: "",
        additionalInfo: { calWarnings: this.calWarnings }
      };
    } else {
      return Promise.reject({ calError: "Something went wrong when updating the event in Salesforce" });
    }
  }
  async deleteEvent(uid, event) {
    await this.salesforceDeleteEvent(uid, event).then(() => {
      return Promise.resolve();
    }).catch((error) => {
      this.log.error(`Error canceling event ${uid} with error ${error}`);
      return Promise.reject({ calError: "Something went wrong when deleting the event in Salesforce" });
    });
  }
  async getContacts({
    emails,
    includeOwner,
    forRoundRobinSkip
  }) {
    const log = logger.getSubLogger({ prefix: [`[getContacts]:${emails}`] });
    try {
      const conn = await this.conn;
      const emailArray = Array.isArray(emails) ? emails : [emails];
      const appOptions = this.getAppOptions();
      const recordToSearch = (forRoundRobinSkip ? appOptions?.roundRobinSkipCheckRecordOn : appOptions?.createEventOn) ?? SalesforceRecordEnum.CONTACT;
      let soql;
      log.info(
        "Getting contacts for emails",
        safeStringify({
          emailArray,
          includeOwner,
          forRoundRobinSkip,
          recordToSearch,
          appOptions
        })
      );
      if (recordToSearch === SalesforceRecordEnum.ACCOUNT && forRoundRobinSkip) {
        try {
          const client = new SalesforceGraphQLClient({
            accessToken: this.accessToken,
            instanceUrl: this.instanceUrl
          });
          return await client.GetAccountRecordsForRRSkip(emailArray[0]);
        } catch (error) {
          log.error("Error getting account records for round robin skip", safeStringify({ error }));
          return [];
        }
      }
      if (recordToSearch === SalesforceRecordEnum.ACCOUNT) {
        const attendeeEmail = emailArray[0];
        log.info("[recordToSearch=ACCOUNT] Searching contact for email", safeStringify({ attendeeEmail }));
        soql = `SELECT Id, Email, OwnerId, AccountId, Account.Owner.Email, Account.Website FROM ${SalesforceRecordEnum.CONTACT} WHERE Email = '${attendeeEmail}' AND AccountId != null`;
      } else {
        soql = `SELECT Id, Email, OwnerId, Owner.Email FROM ${recordToSearch} WHERE Email IN ('${emailArray.join(
          "','"
        )}')`;
      }
      const results = await conn.query(soql);
      log.info("Query results", safeStringify({ recordCount: results.records?.length }));
      let records = [];
      if (recordToSearch === SalesforceRecordEnum.CONTACT && appOptions?.roundRobinSkipFallbackToLeadOwner && forRoundRobinSkip) {
        const searchResult = await conn.search(
          `FIND {${emailArray[0]}} IN EMAIL FIELDS RETURNING Lead(Id, Email, OwnerId, Owner.Email), Contact(Id, Email, OwnerId, Owner.Email)`
        );
        if (searchResult.searchRecords.length > 0) {
          const contactQuery = searchResult.searchRecords.filter(
            (record) => record.attributes?.type === SalesforceRecordEnum.CONTACT
          );
          if (contactQuery.length > 0) {
            records = contactQuery;
          } else {
            const leadQuery = searchResult.searchRecords.filter(
              (record) => record.attributes?.type === SalesforceRecordEnum.LEAD
            );
            if (leadQuery.length > 0) {
              records = leadQuery;
            }
          }
        }
      }
      if (appOptions.createEventOn === SalesforceRecordEnum.LEAD && appOptions.createEventOnLeadCheckForContact && !forRoundRobinSkip) {
        const contactSearch = await conn.query(
          `SELECT Id, Email, OwnerId, Owner.Email FROM ${SalesforceRecordEnum.CONTACT} WHERE Email IN ('${emailArray.join("','")}')`
        );
        if (contactSearch?.records?.length > 0) {
          records = contactSearch.records;
          this.setFallbackToContact(true);
          log.info(
            "Found matching contacts, falling back to contact",
            safeStringify({
              contactCount: records.length
            })
          );
        }
      }
      if (!records.length && results?.records?.length) {
        records = results.records;
      }
      if (!records.length) {
        log.info("No records found");
        return [];
      }
      const includeOwnerData = (includeOwner || forRoundRobinSkip) && !await this.shouldSkipAttendeeIfFreeEmailDomain(emailArray[0]);
      const includeAccountRecordType = forRoundRobinSkip && recordToSearch === SalesforceRecordEnum.ACCOUNT;
      return records.map((record) => {
        const ownerEmail = recordToSearch === SalesforceRecordEnum.ACCOUNT && record?.attributes?.type !== SalesforceRecordEnum.ACCOUNT ? record?.Account?.Owner?.Email : record?.Owner?.Email;
        return {
          id: includeAccountRecordType ? record?.AccountId : record?.Id || "",
          email: record?.Email || "",
          recordType: includeAccountRecordType ? SalesforceRecordEnum.ACCOUNT : record?.attributes?.type,
          ...includeOwnerData && {
            ownerId: record?.OwnerId,
            ownerEmail
          }
        };
      });
    } catch (error) {
      log.error("Error in getContacts", safeStringify(error));
      return [];
    }
  }
  async createContacts(contactsToCreate, organizerEmail, calEventResponses) {
    const log = logger.getSubLogger({ prefix: [`[createContacts]`] });
    const conn = await this.conn;
    const appOptions = this.getAppOptions();
    const createEventOn = appOptions.createEventOn ?? SalesforceRecordEnum.CONTACT;
    const organizerId = organizerEmail ? await this.getSalesforceUserIdFromEmail(organizerEmail) : void 0;
    const createdContacts = [];
    log.info("createContacts", safeStringify({ createEventOn, organizerId, contactsToCreate }));
    if (createEventOn === SalesforceRecordEnum.CONTACT) {
      await Promise.all(
        contactsToCreate.map(async (attendee) => {
          return await this.createAttendeeRecord({
            attendee,
            recordType: SalesforceRecordEnum.CONTACT,
            organizerId
          }).then((result) => {
            createdContacts.push(...result);
          });
        })
      );
    }
    if (!contactsToCreate[0]?.email) {
      this.log.warn(`createContact: no attendee email found `, contactsToCreate);
      return [];
    }
    if (createEventOn === SalesforceRecordEnum.LEAD) {
      const attendee = contactsToCreate[0];
      if (appOptions.createNewContactUnderAccount) {
        const accountId = await this.getAccountIdBasedOnEmailDomainOfContacts(attendee.email);
        if (accountId) {
          const createdAccountContacts = await this.createNewContactUnderAnAccount({
            attendee,
            accountId,
            organizerId
          });
          if (createdAccountContacts.length > 0) {
            createdContacts.push(...createdAccountContacts);
          }
        } else {
          await this.createAttendeeRecord({
            attendee,
            recordType: SalesforceRecordEnum.LEAD,
            organizerId,
            calEventResponses
          }).then((result) => {
            createdContacts.push(...result);
          });
        }
      } else {
        await this.createAttendeeRecord({
          attendee,
          recordType: SalesforceRecordEnum.LEAD,
          organizerId,
          calEventResponses
        }).then((result) => {
          createdContacts.push(...result);
        });
      }
    }
    if (createEventOn === SalesforceRecordEnum.ACCOUNT) {
      if (!appOptions.createNewContactUnderAccount && !appOptions.createLeadIfAccountNull) {
        this.setDoNotCreateEvent(true);
        return [{ id: "Do not create event", email: "placeholder" }];
      }
      const attendee = contactsToCreate[0];
      const accountId = await this.getAccountIdBasedOnEmailDomainOfContacts(attendee.email);
      let contactCreated = false;
      if (accountId && appOptions.createNewContactUnderAccount) {
        const createdAccountContacts = await this.createNewContactUnderAnAccount({
          attendee,
          accountId,
          organizerId
        });
        if (createdAccountContacts.length > 0) {
          createdContacts.push(...createdAccountContacts);
          contactCreated = true;
        }
      }
      if (!accountId && appOptions.createLeadIfAccountNull && !contactCreated) {
        const leadQuery = await conn.query(
          `SELECT Id, Email FROM Lead WHERE Email = '${attendee.email}' LIMIT 1`
        );
        if (leadQuery.records.length > 0) {
          const contact = leadQuery.records[0];
          return [{ id: contact.Id, email: contact.Email }];
        }
        for (const attendee2 of contactsToCreate) {
          try {
            const createBody = await this.generateCreateRecordBody({
              attendee: attendee2,
              recordType: SalesforceRecordEnum.LEAD,
              organizerId,
              calEventResponses
            });
            const result = await conn.sobject(SalesforceRecordEnum.LEAD).create(createBody);
            if (result.success) {
              createdContacts.push({ id: result.id, email: attendee2.email });
            }
          } catch (error) {
            if (error.name === "DUPLICATES_DETECTED") {
              const existingId = this.getExistingIdFromDuplicateError(error);
              if (existingId) {
                log.info("Using existing record:", existingId);
                createdContacts.push({ id: existingId, email: attendee2.email });
              }
            } else {
              log.error("Error creating lead:", error);
            }
          }
        }
      }
    }
    if (createdContacts.length === 0) {
      log.error(`No contacts created for these app options ${safeStringify(appOptions)}`);
    }
    return createdContacts;
  }
  async handleAttendeeNoShow(bookingUid, attendees) {
    const appOptions = this.getAppOptions();
    const { sendNoShowAttendeeData = false, sendNoShowAttendeeDataField = {} } = appOptions;
    const conn = await this.conn;
    if (!sendNoShowAttendeeData && !sendNoShowAttendeeDataField) {
      this.log.warn(`No show settings not set for bookingUid ${bookingUid}`);
      return;
    }
    const salesforceEvents = await prisma.bookingReference.findMany({
      where: {
        type: appMeta.type,
        booking: {
          uid: bookingUid
        }
      }
    });
    const salesforceEntity = await conn.describe("Event");
    const fields = salesforceEntity.fields;
    const noShowField = fields.find((field) => field.name === sendNoShowAttendeeDataField);
    if (!noShowField || noShowField.type !== "boolean") {
      this.log.warn(
        `No show field on Salesforce doesn't exist or is not of type boolean for bookingUid ${bookingUid}`
      );
      return;
    }
    for (const event of salesforceEvents) {
      const salesforceEvent = await conn.query(`SELECT WhoId FROM Event WHERE Id = '${event.uid}'`);
      let salesforceAttendeeEmail = void 0;
      const contactQuery = await conn.query(
        `SELECT Email FROM Contact WHERE Id = '${salesforceEvent.records[0].WhoId}'`
      );
      const leadQuery = await conn.query(
        `SELECT Email FROM Lead WHERE Id = '${salesforceEvent.records[0].WhoId}'`
      );
      if (contactQuery.records.length > 0) {
        salesforceAttendeeEmail = contactQuery.records[0].Email;
      } else if (leadQuery.records.length > 0) {
        salesforceAttendeeEmail = leadQuery.records[0].Email;
      } else {
        this.log.warn(
          `Could not find attendee for bookingUid ${bookingUid} and salesforce event id ${event.uid}`
        );
      }
      if (salesforceAttendeeEmail) {
        const noShowData = attendees.find((attendee) => attendee.email === salesforceAttendeeEmail);
        if (!noShowData) {
          this.log.warn(
            `No show data could not be found for ${salesforceAttendeeEmail} and bookingUid ${bookingUid}`
          );
        } else {
          await conn.sobject("Event").update({
            Id: event.uid,
            [sendNoShowAttendeeDataField]: noShowData.noShow
          });
        }
      }
    }
  }
  getExistingIdFromDuplicateError(error) {
    if (error.duplicateResult && error.duplicateResult.matchResults) {
      for (const matchResult of error.duplicateResult.matchResults) {
        if (matchResult.matchRecords && matchResult.matchRecords.length > 0) {
          return matchResult.matchRecords[0].record.Id;
        }
      }
    }
    return null;
  }
  setDoNotCreateEvent(boolean) {
    this.doNotCreateEvent = boolean;
  }
  getDoNotCreateEvent() {
    return this.doNotCreateEvent;
  }
  getDominantAccountId(contacts) {
    return getDominantAccountId(contacts);
  }
  async createAttendeeRecord({
    attendee,
    recordType,
    organizerId,
    accountId,
    calEventResponses
  }) {
    const log = logger.getSubLogger({ prefix: [`[createAttendeeRecord]:${attendee.email}`] });
    log.info("createAttendeeRecord", safeStringify({ attendee, recordType, organizerId, accountId }));
    const conn = await this.conn;
    const createBody = await this.generateCreateRecordBody({
      attendee,
      recordType,
      organizerId,
      calEventResponses
    });
    return await conn.sobject(recordType).create({
      ...createBody,
      AccountId: accountId
    }).then((result) => {
      if (result.success) {
        return [{ id: result.id, email: attendee.email }];
      } else {
        return [];
      }
    }).catch((error) => {
      this.log.error(`Error creating Salesforce contact for ${attendee.email} with error ${error}`);
      return [];
    });
  }
  async generateCreateRecordBody({
    attendee,
    recordType,
    organizerId,
    calEventResponses
  }) {
    const [FirstName, LastName] = attendee.name ? attendee.name.split(" ") : [attendee.email, ""];
    const company = await this.getCompanyNameFromBookingResponse(calEventResponses) ?? attendee.email.split("@")[1].split(".")[0];
    return {
      LastName: LastName || "-",
      FirstName,
      Email: attendee.email,
      ...organizerId && { OwnerId: organizerId },
      ...recordType === SalesforceRecordEnum.LEAD && { Company: company }
    };
  }
  async ensureFieldsExistOnObject(fieldsToTest, sobject) {
    const log = logger.getSubLogger({ prefix: [`[ensureFieldsExistOnObject]`] });
    const conn = await this.conn;
    const fieldSet = new Set(fieldsToTest);
    const foundFields = [];
    try {
      const salesforceEntity = await conn.describe(sobject);
      const fields = salesforceEntity.fields;
      for (const field of fields) {
        if (foundFields.length === fieldSet.size)
          break;
        if (fieldSet.has(field.name)) {
          foundFields.push(field);
        }
      }
      return foundFields;
    } catch (e) {
      log.error(`Error ensuring fields ${fieldsToTest} exist on object ${sobject} with error ${e}`);
      return [];
    }
  }
  async checkRecordOwnerNameFromRecordId(id, newOwnerId) {
    const conn = await this.conn;
    const appOptions = this.getAppOptions();
    this.log.info(`Changing owner for record ${id}`);
    const recordType = this.determineRecordTypeById(id);
    if (!appOptions?.onBookingChangeRecordOwnerName) {
      this.log.warn(
        `No appOptions.onBookingChangeRecordOwnerName found for ${this.integrationName} on checkRecordOwnerNameFromRecordId`
      );
      return;
    }
    const recordQuery = await conn.query(
      `SELECT OwnerId, Owner.Name FROM ${recordType} WHERE Id = '${id}'`
    );
    if (!recordQuery || !recordQuery.records.length) {
      this.log.warn(`Could not find record for id ${id} and type ${recordType}`);
    }
    const owner = recordQuery.records[0].Owner;
    if (!appOptions.onBookingChangeRecordOwnerName.includes(owner?.Name)) {
      this.log.warn(
        `Current owner name ${owner?.Name} for record ${id} does not match the option ${appOptions.onBookingChangeRecordOwnerName}`
      );
    }
    await conn.sobject(recordType).update({
      // First field is there WHERE statement
      Id: id,
      OwnerId: newOwnerId
    }).catch((error) => {
      this.log.warn(
        `Error changing record ${id} of type ${recordType} owner to ${newOwnerId} with error ${JSON.stringify(
          error
        )}`
      );
    });
  }
  getAllPossibleAccountWebsiteFromEmailDomain(emailDomain) {
    const websites = getAllPossibleWebsiteValuesFromEmailDomain(emailDomain);
    return websites.map((website) => `'${website}'`).join(", ");
  }
  async getAccountIdBasedOnEmailDomainOfContacts(email) {
    const conn = await this.conn;
    const emailDomain = email.split("@")[1];
    const log = logger.getSubLogger({ prefix: [`[getAccountIdBasedOnEmailDomainOfContacts]:${email}`] });
    log.info("getAccountIdBasedOnEmailDomainOfContacts", safeStringify({ email, emailDomain }));
    const accountQuery = await conn.query(
      `SELECT Id, Website FROM Account WHERE Website IN (${this.getAllPossibleAccountWebsiteFromEmailDomain(
        emailDomain
      )}) LIMIT 1`
    );
    if (accountQuery.records.length > 0) {
      const account = accountQuery.records[0];
      log.info(
        "Found account based on email domain",
        safeStringify({ emailDomain, accountWebsite: account.Website, accountId: account.Id })
      );
      return account.Id;
    }
    const response = await conn.query(
      `SELECT Id, Email, AccountId FROM Contact WHERE Email LIKE '%@${emailDomain}' AND AccountId != null`
    );
    const accountId = this.getDominantAccountId(response.records);
    if (accountId) {
      log.info("Found account based on other contacts", safeStringify({ accountId }));
    } else {
      log.info("No account found");
    }
    return accountId;
  }
  setFallbackToContact(boolean) {
    this.fallbackToContact = boolean;
  }
  getFallbackToContact() {
    return this.fallbackToContact;
  }
  async writeToRecord({
    recordId,
    startTime,
    fieldsToWriteTo,
    organizerEmail,
    calEventResponses,
    bookingUid
  }) {
    const conn = await this.conn;
    const personRecordType = this.determineRecordTypeById(recordId);
    const fieldsToWriteOn = Object.keys(fieldsToWriteTo);
    const existingFields = await this.ensureFieldsExistOnObject(fieldsToWriteOn, personRecordType);
    if (!existingFields.length) {
      this.log.warn(`No fields found for record type ${personRecordType}`);
      return;
    }
    const personRecord = await this.fetchPersonRecord(recordId, existingFields, personRecordType);
    if (!personRecord) {
      this.log.warn(`No personRecord found for contactId ${recordId}`);
      return;
    }
    this.log.info(`Writing to recordId ${recordId} on fields ${fieldsToWriteOn}`);
    const writeOnRecordBody = await this.buildRecordUpdatePayload({
      existingFields,
      personRecord,
      fieldsToWriteTo,
      startTime,
      bookingUid,
      organizerEmail,
      calEventResponses,
      recordId
    });
    this.log.info(
      `Final writeOnRecordBody contains fields ${Object.keys(writeOnRecordBody)} for record ${recordId}`
    );
    await conn.sobject(personRecordType).update({
      Id: recordId,
      ...writeOnRecordBody
    }).catch((e) => {
      this.log.error(`Error updating person record for contactId ${recordId}`, e);
    });
  }
  async buildRecordUpdatePayload({
    existingFields,
    personRecord,
    fieldsToWriteTo,
    startTime,
    bookingUid,
    organizerEmail,
    calEventResponses,
    recordId
  }) {
    const log = logger.getSubLogger({ prefix: [`[buildRecordUpdatePayload] ${recordId}`] });
    const writeOnRecordBody = {};
    let fieldTypeHandled = false;
    for (const field of existingFields) {
      const fieldConfig = fieldsToWriteTo[field.name];
      if (!fieldConfig) {
        log.error(`No field config found for field ${field.name}`);
        continue;
      }
      log.info(
        `Processing field ${field.name} with type ${field.type} and config ${JSON.stringify(fieldConfig)}`
      );
      if (fieldConfig.whenToWrite === WhenToWriteToRecord.FIELD_EMPTY && personRecord[field.name]) {
        log.info(
          `Field ${field.name} on contactId ${personRecord?.Id} already exists with value ${personRecord[field.name]}`
        );
        continue;
      }
      if (fieldConfig.fieldType === SalesforceFieldType.CUSTOM) {
        fieldTypeHandled = true;
        const extractedValue = await this.getTextFieldValue({
          fieldValue: fieldConfig.value,
          fieldLength: field.length,
          calEventResponses,
          bookingUid,
          recordId,
          fieldName: field.name
        });
        if (extractedValue) {
          writeOnRecordBody[field.name] = extractedValue;
          continue;
        }
      }
      if (field.type === SalesforceFieldType.TEXT || field.type === SalesforceFieldType.TEXTAREA || field.type === SalesforceFieldType.PHONE) {
        fieldTypeHandled = true;
        const extractedText = await this.getTextFieldValue({
          fieldValue: fieldConfig.value,
          fieldLength: field.length,
          calEventResponses,
          bookingUid,
          recordId,
          fieldName: field.name
        });
        if (extractedText) {
          writeOnRecordBody[field.name] = extractedText;
          continue;
        }
      } else if ((field.type === SalesforceFieldType.DATE || field.type === SalesforceFieldType.DATETIME) && startTime && organizerEmail) {
        fieldTypeHandled = true;
        const dateValue = await this.getDateFieldValue(
          fieldConfig.value,
          startTime,
          bookingUid,
          organizerEmail
        );
        if (dateValue) {
          writeOnRecordBody[field.name] = dateValue;
          continue;
        }
      } else if (field.type === SalesforceFieldType.PICKLIST) {
        fieldTypeHandled = true;
        const picklistValue = await this.getPicklistFieldValue({
          fieldConfigValue: fieldConfig.value,
          salesforceField: field,
          calEventResponses,
          bookingUid,
          recordId
        });
        if (picklistValue) {
          writeOnRecordBody[field.name] = picklistValue;
          continue;
        }
      } else if (field.type === SalesforceFieldType.CHECKBOX) {
        fieldTypeHandled = true;
        const checkboxValue = !!fieldConfig.value;
        writeOnRecordBody[field.name] = checkboxValue;
        continue;
      }
      if (!fieldTypeHandled) {
        log.error(`Salesforce field type ${field.type} not handled for fieldConfig ${fieldConfig}`);
      }
      log.error(
        `No value found for field ${field.name} with value ${personRecord[field.name]}, field config ${JSON.stringify(fieldConfig)} and Salesforce config ${JSON.stringify(field)}`
      );
    }
    return writeOnRecordBody;
  }
  async generateWriteToEventBody(event) {
    const appOptions = this.getAppOptions();
    const customFieldInputsEnabled = appOptions?.onBookingWriteToEventObject && appOptions?.onBookingWriteToEventObjectMap;
    if (!customFieldInputsEnabled)
      return {};
    if (!appOptions?.onBookingWriteToEventObjectMap)
      return {};
    const customFieldInputs = customFieldInputsEnabled ? await this.ensureFieldsExistOnObject(Object.keys(appOptions.onBookingWriteToEventObjectMap), "Event") : [];
    const confirmedCustomFieldInputs = {};
    for (const field of customFieldInputs) {
      confirmedCustomFieldInputs[field.name] = await this.getTextFieldValue({
        fieldValue: appOptions.onBookingWriteToEventObjectMap[field.name],
        fieldLength: field.length,
        calEventResponses: event.responses,
        bookingUid: event?.uid,
        recordId: event?.uid ?? "Cal booking",
        fieldName: field.name
      });
    }
    return confirmedCustomFieldInputs;
  }
  async getTextFieldValue({
    fieldValue,
    fieldLength,
    calEventResponses,
    bookingUid,
    recordId,
    fieldName
  }) {
    const log = logger.getSubLogger({ prefix: [`[getTextFieldValue]: ${recordId} - ${fieldName}`] });
    if (!fieldValue.startsWith("{") && !fieldValue.endsWith("}")) {
      log.info("Returning static value");
      return fieldValue;
    }
    let valueToWrite = fieldValue;
    if (fieldValue.startsWith("{form:")) {
      if (!bookingUid) {
        log.error(`BookingUid not passed. Cannot get form responses without it`);
        return;
      }
      const formValue = await this.getTextValueFromRoutingFormResponse(fieldValue, bookingUid, recordId);
      valueToWrite = formValue || "";
    } else if (fieldValue.startsWith("{utm:")) {
      if (!bookingUid) {
        log.error(`BookingUid not passed. Cannot get tracking values without it`);
        return;
      }
      valueToWrite = await this.getTextValueFromBookingTracking(fieldValue, bookingUid);
    } else if (fieldValue === "{assignmentReason}") {
      if (!bookingUid) {
        log.error(`BookingUid not passed. Cannot get assignment reason without it`);
        return;
      }
      valueToWrite = await this.getAssignmentReason(bookingUid);
      if (!valueToWrite) {
        log.error(`No assignment reason found for bookingUid ${bookingUid}`);
        return;
      }
    } else {
      if (!calEventResponses) {
        log.error(`CalEventResponses not passed. Cannot get booking form responses`);
        return;
      }
      valueToWrite = this.getTextValueFromBookingResponse(fieldValue, calEventResponses);
    }
    if (valueToWrite === fieldValue) {
      log.error("No responses found returning nothing");
      return;
    }
    return fieldLength ? valueToWrite.substring(0, fieldLength) : valueToWrite;
  }
  async getTextValueFromRoutingFormResponse(fieldValue, bookingUid, recordId) {
    const log = logger.getSubLogger({
      prefix: [`[getTextValueFromRoutingFormResponse]: ${recordId} - bookingUid: ${bookingUid}`]
    });
    let value;
    const regex = /\{form:(.*?)\}/;
    const regexMatch = fieldValue.match(regex);
    if (!regexMatch) {
      log.error("Could not find regex match to {form:}");
      return fieldValue;
    }
    const identifierField = regexMatch?.[1];
    if (!identifierField) {
      log.error(`Could not find matching regex string ${regexMatch}`);
      return fieldValue;
    }
    const routingFormResponseDataFactory = new RoutingFormResponseDataFactory({
      logger: log,
      routingFormResponseRepo: new PrismaRoutingFormResponseRepository()
    });
    const findFieldResult = findFieldValueByIdentifier(
      await routingFormResponseDataFactory.createWithBookingUid(bookingUid),
      identifierField
    );
    if (findFieldResult.success) {
      value = findFieldResult.data;
      return String(value);
    }
    log.error(
      `Could not find field value for identifier ${identifierField} in bookingUid ${bookingUid}`,
      `failed with error: ${findFieldResult.error}`
    );
    return fieldValue;
  }
  async getTextValueFromBookingTracking(fieldValue, bookingUid) {
    const log = logger.getSubLogger({
      prefix: [`[getTextValueFromBookingTracking]: ${bookingUid}`]
    });
    const tracking = await prisma.tracking.findFirst({
      where: {
        booking: {
          uid: bookingUid
        }
      }
    });
    if (!tracking) {
      log.warn(`No tracking found for bookingUid ${bookingUid}`);
      return "";
    }
    const utmParam = fieldValue.split(":")[1].slice(0, -1);
    return tracking[`utm_${utmParam}`]?.toString() ?? "";
  }
  async getAssignmentReason(bookingId) {
    const assignmentReason = await PrismaAssignmentReasonRepository.findLatestReasonFromBookingUid(bookingId);
    return assignmentReason?.reasonString ?? "";
  }
  getTextValueFromBookingResponse(fieldValue, calEventResponses) {
    const regexValueToReplace = /\{(.*?)\}/g;
    return fieldValue.replace(regexValueToReplace, (match, captured) => {
      return calEventResponses[captured]?.value ? calEventResponses[captured].value.toString() : match;
    });
  }
  async getDateFieldValue(fieldValue, startTime, bookingUid, organizerEmail) {
    if (fieldValue === DateFieldTypeData.BOOKING_START_DATE) {
      return new Date(startTime).toISOString();
    }
    if (fieldValue === DateFieldTypeData.BOOKING_CREATED_DATE && bookingUid) {
      if (!organizerEmail) {
        this.log.warn(`No organizer email found for bookingUid ${bookingUid}`);
      }
      const booking = await prisma.booking.findUnique({
        where: { uid: bookingUid },
        select: { createdAt: true }
      });
      if (!booking) {
        this.log.warn(`No booking found for ${bookingUid}`);
        return null;
      }
      return new Date(booking.createdAt).toISOString();
    }
    if (!bookingUid) {
      this.log.warn(`No uid for booking with organizer ${organizerEmail}`);
    }
    if (fieldValue === DateFieldTypeData.BOOKING_CANCEL_DATE) {
      return (/* @__PURE__ */ new Date()).toISOString();
    }
    return null;
  }
  async getPicklistFieldValue({
    fieldConfigValue,
    salesforceField,
    calEventResponses,
    bookingUid,
    recordId
  }) {
    const log = logger.getSubLogger({ prefix: [`[getPicklistFieldValue] ${recordId}`] });
    const picklistOptions = salesforceField.picklistValues;
    if (!picklistOptions || !picklistOptions.length) {
      log.warn(`No picklist values found for field ${salesforceField.name}`);
      return null;
    }
    const fieldTextValue = await this.getTextFieldValue({
      fieldValue: fieldConfigValue,
      fieldLength: salesforceField.length,
      calEventResponses,
      bookingUid,
      recordId,
      fieldName: salesforceField.name
    });
    if (!fieldTextValue) {
      log.warn(`No text value found for field ${salesforceField.name}`);
      return null;
    }
    const picklistValue = picklistOptions.find((option) => option.active && option.value === fieldTextValue);
    if (!picklistValue) {
      log.warn(`No picklist value found for field ${salesforceField.name} and value ${fieldTextValue}`);
      return null;
    }
    return picklistValue.value;
  }
  async fetchPersonRecord(contactId, existingFields, personRecordType) {
    const conn = await this.conn;
    const existingFieldNames = existingFields.map((field) => field.name);
    const query = await conn.query(
      `SELECT Id, ${existingFieldNames.join(", ")} FROM ${personRecordType} WHERE Id = '${contactId}'`
    );
    if (!query.records.length) {
      this.log.warn(`Could not find person record with id ${contactId}`);
      return null;
    }
    return query.records[0];
  }
  async createNewContactUnderAnAccount({
    attendee,
    accountId,
    organizerId
  }) {
    const log = logger.getSubLogger({ prefix: [`[createNewContactUnderAnAccount]:${attendee.email}`] });
    log.info("createNewContactUnderAnAccount", safeStringify({ attendee, accountId, organizerId }));
    const conn = await this.conn;
    const userQuery = await conn.query(
      `SELECT Id, Email FROM Contact WHERE Email = '${attendee.email}' LIMIT 1`
    );
    if (userQuery.records.length > 0) {
      const contact = userQuery.records[0];
      await conn.sobject(SalesforceRecordEnum.CONTACT).update({
        // The first argument is the WHERE clause
        Id: contact.Id,
        AccountId: accountId
      });
      return [{ id: contact.Id, email: contact.Email }];
    }
    return await this.createAttendeeRecord({
      attendee,
      recordType: SalesforceRecordEnum.CONTACT,
      accountId,
      organizerId
    });
  }
  async findUserEmailFromLookupField(attendeeEmail, fieldName, salesforceObject) {
    const conn = await this.conn;
    const existingFields = await this.ensureFieldsExistOnObject([fieldName], salesforceObject);
    if (!existingFields.length)
      return;
    const lookupField = existingFields[0];
    if (salesforceObject === SalesforceRecordEnum.ACCOUNT) {
      const accountId = await this.getAccountIdBasedOnEmailDomainOfContacts(attendeeEmail);
      if (!accountId)
        return;
      const accountQuery = await conn.query(
        `SELECT ${lookupField.name} FROM ${SalesforceRecordEnum.ACCOUNT} WHERE Id = '${accountId}'`
      );
      if (!accountQuery.records.length)
        return;
      const lookupFieldUserId = accountQuery.records[0][lookupField.name];
      if (!lookupFieldUserId)
        return;
      const userQuery = await this.getSalesforceUserFromUserId(lookupFieldUserId);
      if (!userQuery.records.length)
        return;
      const user = userQuery.records[0];
      return { email: user.Email, recordType: RoutingReasons.ACCOUNT_LOOKUP_FIELD };
    }
  }
  /** Search the booking questions for the Company field value rather than relying on the email domain  */
  async getCompanyNameFromBookingResponse(calEventResponses) {
    const appOptions = this.getAppOptions();
    const companyFieldName = "Company";
    const defaultTextValueLength = 225;
    const { onBookingWriteToRecordFields = void 0 } = appOptions;
    if (!onBookingWriteToRecordFields || !calEventResponses)
      return;
    if (!(companyFieldName in onBookingWriteToRecordFields))
      return;
    const companyValue = await this.getTextFieldValue({
      fieldValue: onBookingWriteToRecordFields[companyFieldName].value,
      fieldLength: defaultTextValueLength,
      calEventResponses,
      recordId: "New lead",
      fieldName: companyFieldName
    });
    if (companyValue && companyValue === onBookingWriteToRecordFields[companyFieldName].value)
      return;
    return companyValue;
  }
  async shouldSkipAttendeeIfFreeEmailDomain(attendeeEmail) {
    const appOptions = this.getAppOptions();
    if (!appOptions.ifFreeEmailDomainSkipOwnerCheck)
      return false;
    const response = await checkIfFreeEmailDomain(attendeeEmail);
    return response;
  }
  async incompleteBookingWriteToRecord(email, writeToRecordObject) {
    const conn = await this.conn;
    const personRecord = await this.findProspectByEmail(email);
    if (!personRecord) {
      this.log.info(`No contact or lead found for email ${email}`);
      return;
    }
    const recordType = this.determineRecordTypeById(personRecord.Id);
    const existingFields = await this.ensureFieldsExistOnObject(Object.keys(writeToRecordObject), recordType);
    const writeOnRecordBody = await this.buildRecordUpdatePayload({
      existingFields,
      personRecord,
      fieldsToWriteTo: writeToRecordObject,
      recordId: personRecord.Id
    });
    await conn.sobject(recordType).update({
      Id: personRecord.Id,
      ...writeOnRecordBody
    }).catch((e) => {
      const contactId = personRecord?.Id || "unknown";
      const errorMessage = `Error updating person record for contactId '${contactId}': ${e instanceof Error ? e.message : String(e)}`;
      throw new Error(errorMessage);
    });
  }
  /** All salesforce ids have a 3 character prefix associated with the record type
   * https://help.salesforce.com/s/articleView?id=000385203&type=1
   */
  determineRecordTypeById(id) {
    switch (id.substring(0, 3)) {
      case "003":
        return SalesforceRecordEnum.CONTACT;
      case "001":
        return SalesforceRecordEnum.ACCOUNT;
      case "00Q":
        return SalesforceRecordEnum.LEAD;
      case "00U":
        return SalesforceRecordEnum.EVENT;
      default:
        this.log.warn(`Unhandled record id type ${id}`);
        return SalesforceRecordEnum.CONTACT;
    }
  }
  /** Prioritizes contacts over leads */
  async findProspectByEmail(email) {
    const contact = await this.findContactByEmail(email);
    if (contact)
      return contact;
    const lead = await this.findLeadByEmail(email);
    if (lead)
      return lead;
    return null;
  }
  async findContactByEmail(email) {
    const conn = await this.conn;
    const contactsQuery = await conn.query(
      `SELECT Id, Email FROM ${SalesforceRecordEnum.CONTACT} WHERE Email = '${email}' LIMIT 1`
    );
    if (contactsQuery.records.length > 0) {
      return contactsQuery.records[0];
    }
  }
  async findLeadByEmail(email) {
    const conn = await this.conn;
    const leadsQuery = await conn.query(
      `SELECT Id, Email FROM ${SalesforceRecordEnum.LEAD} WHERE Email = '${email}' LIMIT 1`
    );
    if (leadsQuery.records.length > 0) {
      return leadsQuery.records[0];
    }
  }
}
export {
  SalesforceCRMService as default
};
