import z__default from "zod";
import { l as logger, p as prisma, ac as APP_NAME, f as symmetricDecrypt } from "./orgDomains-eTg-cjth.js";
class CloseCom {
  constructor(apiKey, options) {
    this.apiUrl = "https://api.close.com/api/v1";
    this.me = async () => {
      return this._get({ urlPath: "/me/" });
    };
    this.contact = {
      search: async ({ emails }) => {
        return this._post({
          urlPath: "/data/search/",
          data: closeComQueries.contact.search(emails)
        });
      },
      create: async (data) => {
        return this._post({ urlPath: "/contact/", data: closeComQueries.contact.create(data) });
      },
      update: async ({
        contactId,
        ...data
      }) => {
        return this._put({
          urlPath: `/contact/${contactId}/`,
          data: closeComQueries.contact.update(data)
        });
      },
      delete: async (contactId) => {
        return this._delete({
          urlPath: `/contact/${contactId}/`
        });
      }
    };
    this.lead = {
      list: async ({
        query
      }) => {
        return this._get({ urlPath: "/lead", query });
      },
      status: async () => {
        return this._get({ urlPath: `/status/lead/` });
      },
      update: async (leadId, data) => {
        return this._put({
          urlPath: `/lead/${leadId}`,
          data
        });
      },
      create: async (data) => {
        return this._post({
          urlPath: "/lead/",
          data: closeComQueries.lead.create(data)
        });
      },
      delete: async (leadId) => {
        return this._delete({ urlPath: `/lead/${leadId}/` });
      }
    };
    this.customActivity = {
      type: {
        create: async (data) => {
          return this._post({
            urlPath: "/custom_activity",
            data: closeComQueries.customActivity.type.create(data)
          });
        },
        get: async () => {
          return this._get({ urlPath: "/custom_activity" });
        }
      }
    };
    this.customField = {
      activity: {
        create: async (data) => {
          return this._post({ urlPath: "/custom_field/activity/", data });
        },
        get: async ({ query }) => {
          return this._get({ urlPath: "/custom_field/activity/", query });
        }
      },
      contact: {
        create: async (data) => {
          return this._post({ urlPath: "/custom_field/contact/", data });
        },
        get: async ({ query }) => {
          return this._get({ urlPath: "/custom_field/contact/", query });
        }
      },
      shared: {
        get: async ({ query }) => {
          return this._get({ urlPath: "/custom_field/shared/", query });
        }
      }
    };
    this.activity = {
      custom: {
        create: async (data) => {
          return this._post({ urlPath: "/activity/custom/", data });
        },
        delete: async (uuid) => {
          return this._delete({ urlPath: `/activity/custom/${uuid}/` });
        },
        update: async (uuid, data) => {
          return this._put({ urlPath: `/activity/custom/${uuid}/`, data });
        }
      }
    };
    this._get = async ({ urlPath, query }) => {
      return await this._request({ urlPath, method: "get", query });
    };
    this._post = async ({ urlPath, data }) => {
      return this._request({ urlPath, method: "post", data });
    };
    this._put = async ({ urlPath, data }) => {
      return this._request({ urlPath, method: "put", data });
    };
    this._delete = async ({ urlPath }) => {
      return this._request({ urlPath, method: "delete" });
    };
    this._request = async ({
      urlPath,
      data,
      method,
      query
    }) => {
      this.log.debug(method, urlPath, query, data);
      const headers = await this.getHeaders();
      const queryString = query ? `?${new URLSearchParams(String(query)).toString()}` : "";
      return await fetch(`${this.apiUrl}${urlPath}${queryString}`, {
        headers,
        method,
        body: JSON.stringify(data)
      }).then(async (response) => {
        if (!response.ok) {
          const message = `[Close.com app] An error has occurred: ${response.status}`;
          this.log.error(await response.json());
          throw new Error(message);
        }
        return await response.json();
      });
    };
    this.log = logger.getSubLogger({ prefix: [`[[lib] close.com`] });
    this.api_key = apiKey;
    this.refresh_token = options?.refresh_token;
    this.expires_at = options?.expires_at;
    this.isOAuth = options?.isOAuth || false;
    this.userId = options?.userId;
  }
  async getHeaders() {
    if (this.isOAuth) {
      if (await this.shouldRefreshToken()) {
        await this.refreshAccessToken();
      }
      return {
        Authorization: `Bearer ${this.api_key}`,
        "Content-Type": "application/json"
      };
    }
    return {
      Authorization: `Basic ${Buffer.from(`${this.api_key}:`).toString("base64")}`,
      "Content-Type": "application/json"
    };
  }
  async shouldRefreshToken() {
    if (!this.expires_at)
      return false;
    const fiveMinutesInMs = 5 * 60 * 1e3;
    return Date.now() + fiveMinutesInMs >= this.expires_at;
  }
  async refreshAccessToken() {
    if (!this.refresh_token)
      throw new Error("No refresh token available");
    if (!this.userId)
      throw new Error("No userId available for token refresh");
    try {
      const response = await fetch("https://api.close.com/oauth2/token/", {
        method: "POST",
        headers: {
          "Content-Type": "application/x-www-form-urlencoded"
        },
        body: new URLSearchParams({
          grant_type: "refresh_token",
          refresh_token: this.refresh_token,
          client_id: process.env.CLOSECOM_CLIENT_ID,
          client_secret: process.env.CLOSECOM_CLIENT_SECRET
        })
      });
      if (!response.ok) {
        throw new Error("Failed to refresh token");
      }
      const data = await response.json();
      this.api_key = data.access_token;
      this.refresh_token = data.refresh_token;
      this.expires_at = Date.now() + data.expires_in * 1e3;
      try {
        const credential = await prisma.credential.findFirst({
          where: {
            userId: this.userId,
            type: "closecom_crm"
          },
          select: {
            id: true
          }
        });
        if (!credential?.id) {
          throw new Error("Credential not found");
        }
        await prisma.credential.update({
          where: {
            id: credential.id
          },
          data: {
            key: {
              access_token: data.access_token,
              refresh_token: data.refresh_token,
              expires_at: this.expires_at
            }
          }
        });
      } catch (error) {
        this.log.error("Failed to update credentials in database", error);
      }
      return {
        access_token: data.access_token,
        refresh_token: data.refresh_token,
        expires_at: this.expires_at
      };
    } catch (error) {
      this.log.error("Token refresh process failed", {
        error,
        userId: this.userId
      });
      throw error;
    }
  }
}
const closeComQueries = {
  contact: {
    search(contactEmails) {
      return {
        limit: null,
        _fields: {
          contact: ["id", "name", "emails"]
        },
        query: {
          negate: false,
          queries: [
            {
              negate: false,
              object_type: "contact",
              type: "object_type"
            },
            {
              negate: false,
              queries: [
                {
                  negate: false,
                  related_object_type: "contact_email",
                  related_query: {
                    negate: false,
                    queries: contactEmails.map((contactEmail) => ({
                      condition: {
                        mode: "full_words",
                        type: "text",
                        value: contactEmail
                      },
                      field: {
                        field_name: "email",
                        object_type: "contact_email",
                        type: "regular_field"
                      },
                      negate: false,
                      type: "field_condition"
                    })),
                    type: "or"
                  },
                  this_object_type: "contact",
                  type: "has_related"
                }
              ],
              type: "and"
            }
          ],
          type: "and"
        },
        results_limit: null,
        sort: []
      };
    },
    create(data) {
      return {
        lead_id: data.leadId,
        name: data.person.name ?? data.person.email,
        emails: [{ email: data.person.email, type: "office" }]
      };
    },
    update({ person, leadId, ...rest }) {
      return {
        ...leadId && { lead_id: leadId },
        name: person.name ?? person.email,
        emails: [{ email: person.email, type: "office" }],
        ...rest
      };
    }
  },
  lead: {
    create({ companyName, contactEmail, contactName, description }) {
      return {
        name: companyName,
        ...description ? { description } : {},
        ...contactEmail && contactName ? {
          contacts: [
            {
              name: contactName,
              email: contactEmail,
              emails: [
                {
                  type: "office",
                  email: contactEmail
                }
              ]
            }
          ]
        } : {}
      };
    }
  },
  customActivity: {
    type: {
      create({ name, description }) {
        return {
          name,
          description,
          api_create_only: false,
          editable_with_roles: ["admin"]
        };
      }
    }
  },
  customField: {
    activity: {
      create({
        custom_activity_type_id,
        name,
        type,
        required,
        accepts_multiple_values
      }) {
        return {
          custom_activity_type_id,
          name,
          type,
          required,
          accepts_multiple_values,
          editable_with_roles: []
        };
      }
    }
  }
};
async function getCloseComContactIds(persons, closeCom, leadFromCalComId) {
  const closeComContacts = await closeCom.contact.search({
    emails: persons.map((att) => att.email)
  });
  if (closeComContacts.data.length < persons.length && leadFromCalComId) {
    const personsEmails = persons.map((att) => att.email);
    const existingContactsEmails = closeComContacts.data.flatMap(
      (cont) => cont.emails.filter((em) => personsEmails.includes(em.email)).map((ems) => ems.email)
    );
    const nonExistingContacts = persons.filter((person) => !existingContactsEmails.includes(person.email));
    const createdContacts = await Promise.all(
      nonExistingContacts.map(
        async (per) => await closeCom.contact.create({
          person: per,
          leadId: leadFromCalComId
        })
      )
    );
    if (createdContacts.length === nonExistingContacts.length) {
      return Promise.resolve(
        closeComContacts.data.map((cont) => cont.id).concat(createdContacts.map((cont) => cont.id))
      );
    } else {
      return Promise.reject("Some contacts were not possible to create in Close.com");
    }
  } else {
    return Promise.resolve(closeComContacts.data.map((cont) => cont.id));
  }
}
async function getCustomActivityTypeInstanceData(event, customFields, closeCom) {
  const leadFromCalComId = await getCloseComLeadId(closeCom);
  const contactsIds = await getCloseComContactIds(event.attendees, closeCom, leadFromCalComId);
  const customActivityTypeAndFieldsIds = await getCloseComCustomActivityTypeFieldsIds(customFields, closeCom);
  const customActivityFieldsValues = [
    contactsIds.length > 1 ? contactsIds.slice(1) : null,
    // Attendee
    event.startTime,
    // Date & Time
    event.attendees[0].timeZone,
    // Time Zone
    contactsIds[0],
    // Organizer
    event.additionalNotes ?? null
    // Additional Notes
  ];
  return Object.assign(
    {},
    {
      custom_activity_type_id: customActivityTypeAndFieldsIds.activityType,
      lead_id: leadFromCalComId
    },
    ...customActivityTypeAndFieldsIds.fields.map((fieldId, index) => {
      return {
        [`custom.${fieldId}`]: customActivityFieldsValues[index]
      };
    })
  );
}
async function getCustomFieldsIds(entity, customFields, closeCom, custom_activity_type_id) {
  const allFields = await closeCom.customField[entity].get({
    query: { _fields: ["name", "id"].concat(entity === "activity" ? ["custom_activity_type_id"] : []) }
  });
  let relevantFields;
  if (entity === "activity") {
    relevantFields = allFields.data.filter(
      (fie) => fie.custom_activity_type_id === custom_activity_type_id
    );
  } else {
    relevantFields = allFields.data;
  }
  const customFieldsNames = relevantFields.map((fie) => fie.name);
  const customFieldsExist = customFields.map((cusFie) => customFieldsNames.includes(cusFie[0]));
  return await Promise.all(
    customFieldsExist.flatMap(async (exist, idx) => {
      if (!exist && entity !== "shared") {
        const [name, type, required, multiple] = customFields[idx];
        let created;
        if (entity === "activity" && custom_activity_type_id) {
          created = await closeCom.customField[entity].create({
            name,
            type,
            required,
            accepts_multiple_values: multiple,
            editable_with_roles: [],
            custom_activity_type_id
          });
          return created.id;
        } else {
          if (entity === "contact") {
            created = await closeCom.customField[entity].create({
              name,
              type,
              required,
              accepts_multiple_values: multiple,
              editable_with_roles: []
            });
            return created.id;
          }
        }
      } else {
        const index = customFieldsNames.findIndex((val) => val === customFields[idx][0]);
        if (index >= 0) {
          return relevantFields[index].id;
        } else {
          throw Error("Couldn't find the field index");
        }
      }
      return "";
    })
  ).then((results) => results.filter((id) => id));
}
async function getCloseComCustomActivityTypeFieldsIds(customFields, closeCom) {
  const customActivities = await closeCom.customActivity.type.get();
  const calComCustomActivity = customActivities.data.filter((act) => act.name === `${APP_NAME} Activity`);
  if (calComCustomActivity.length > 0) {
    const fields = await getCustomFieldsIds("activity", customFields, closeCom, calComCustomActivity[0].id);
    return {
      activityType: calComCustomActivity[0].id,
      fields
    };
  } else {
    const { id: activityType } = await closeCom.customActivity.type.create({
      name: `${APP_NAME} Activity`,
      description: `Bookings in your ${APP_NAME} account`
    });
    const fields = await Promise.all(
      customFields.map(async ([name, type, required, multiple]) => {
        const creation = await closeCom.customField.activity.create({
          custom_activity_type_id: activityType,
          name,
          type,
          required,
          accepts_multiple_values: multiple,
          editable_with_roles: []
        });
        return creation.id;
      })
    );
    return {
      activityType,
      fields
    };
  }
}
async function getCloseComLeadId(closeCom, leadInfo = {
  companyName: `From ${APP_NAME}`,
  description: `Generic Lead for Contacts created by ${APP_NAME}`
}) {
  const closeComLeadNames = await closeCom.lead.list({ query: { _fields: ["name", "id"] } });
  const searchLeadFromCalCom = closeComLeadNames.data.filter(
    (lead) => lead.name === leadInfo.companyName
  );
  if (searchLeadFromCalCom.length === 0) {
    const createdLeadFromCalCom = await closeCom.lead.create(leadInfo);
    return createdLeadFromCalCom.id ?? "";
  } else {
    return searchLeadFromCalCom[0].id ?? "";
  }
}
const credentialSchema = z__default.object({
  encrypted: z__default.string().optional(),
  access_token: z__default.string().optional(),
  refresh_token: z__default.string().optional(),
  expires_at: z__default.number().optional()
}).refine(
  (data) => {
    return !!data.encrypted || !!data.access_token;
  },
  {
    message: "Either API key or OAuth credentials must be provided"
  }
);
const CALENDSO_ENCRYPTION_KEY = process.env.CALENDSO_ENCRYPTION_KEY || "";
const calComCustomActivityFields = [
  // Field name, field type, required?, multiple values?
  ["Attendees", "contact", false, true],
  ["Date & Time", "datetime", true, false],
  ["Time zone", "text", true, false],
  ["Organizer", "contact", true, false],
  ["Additional notes", "text", false, false]
];
class CloseComCRMService {
  constructor(credential) {
    this.integrationName = "";
    this.closeComUpdateCustomActivity = async (uid, event) => {
      const customActivityTypeInstanceData = await getCustomActivityTypeInstanceData(
        event,
        calComCustomActivityFields,
        this.closeCom
      );
      const customActivityTypeInstance = await this.closeCom.activity.custom.create(
        customActivityTypeInstanceData
      );
      return this.closeCom.activity.custom.update(uid, customActivityTypeInstance);
    };
    this.closeComDeleteCustomActivity = async (uid) => {
      return this.closeCom.activity.custom.delete(uid);
    };
    this.integrationName = "closecom_other_calendar";
    this.log = logger.getSubLogger({ prefix: [`[[lib] ${this.integrationName}`] });
    const parsedKey = credentialSchema.safeParse(credential.key);
    if (!parsedKey.success) {
      throw new Error(
        `Invalid credentials for userId ${credential.userId} and appId ${credential.appId}: ${parsedKey.error}`
      );
    }
    if (parsedKey.data.encrypted) {
      const decrypted = symmetricDecrypt(parsedKey.data.encrypted, CALENDSO_ENCRYPTION_KEY);
      const { api_key } = JSON.parse(decrypted);
      this.closeCom = new CloseCom(api_key);
    } else if (parsedKey.data.access_token) {
      this.closeCom = new CloseCom(parsedKey.data.access_token, {
        refresh_token: parsedKey.data.refresh_token,
        expires_at: parsedKey.data.expires_at,
        isOAuth: true,
        userId: credential.userId
      });
    } else {
      throw new Error("No valid authentication method found");
    }
  }
  async createEvent(event) {
    const customActivityTypeInstanceData = await getCustomActivityTypeInstanceData(
      event,
      calComCustomActivityFields,
      this.closeCom
    );
    const customActivityTypeInstance = await this.closeCom.activity.custom.create(
      customActivityTypeInstanceData
    );
    return Promise.resolve({
      uid: customActivityTypeInstance.id,
      id: customActivityTypeInstance.id,
      type: this.integrationName,
      password: "",
      url: "",
      additionalInfo: {
        customActivityTypeInstanceData
      },
      success: true
    });
  }
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  async updateEvent(uid, event) {
    const updatedEvent = await this.closeComUpdateCustomActivity(uid, event);
    return {
      id: updatedEvent.id
    };
  }
  async deleteEvent(uid) {
    await this.closeComDeleteCustomActivity(uid);
  }
  async getContacts({ emails }) {
    const contactsQuery = await this.closeCom.contact.search({
      emails: Array.isArray(emails) ? emails : [emails]
    });
    return contactsQuery.data.map((contact) => {
      return {
        id: contact.id,
        email: contact.emails[0].email,
        name: contact.name
      };
    });
  }
  async createContacts(contactsToCreate) {
    const contacts = [];
    const lead = await this.closeCom.lead.create({
      contactName: contactsToCreate[0].name,
      contactEmail: contactsToCreate[0].email
    });
    contacts.push({
      id: lead.contacts[0].id,
      email: lead.contacts[0].emails[0].email
    });
    if (contactsToCreate.length > 1) {
      const createContactPromise = [];
      for (const contact of contactsToCreate) {
        createContactPromise.push(
          this.closeCom.contact.create({
            leadId: lead.id,
            person: {
              email: contact.email,
              name: contact.name
            }
          })
        );
        const createdContacts = await Promise.all(createContactPromise);
        for (const createdContact of createdContacts) {
          contacts.push({
            id: createdContact.id,
            email: createdContact.emails[0].email
          });
        }
      }
    }
    return contacts;
  }
  getAppOptions() {
    console.log("No options implemented");
  }
  async handleAttendeeNoShow() {
    console.log("Not implemented");
  }
}
export {
  CloseComCRMService as default
};
