"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const actions_core_1 = require("@segment/actions-core");
const properties_1 = require("../properties");
const utils_1 = require("../utils");
const split_1 = __importDefault(require("lodash/split"));
const versioning_info_1 = require("../versioning-info");
const action = {
    title: 'Upsert Contact',
    description: 'Create or update a contact in HubSpot.',
    defaultSubscription: 'type = "identify"',
    fields: {
        email: {
            label: 'Email',
            type: 'string',
            description: 'The contact’s email. Email is used to uniquely identify contact records in HubSpot. If an existing contact is found with this email, we will update the contact. If a contact is not found, we will create a new contact.',
            required: true,
            format: 'email',
            default: {
                '@path': '$.traits.email'
            }
        },
        company: {
            label: 'Company Name',
            type: 'string',
            description: 'The contact’s company.',
            default: {
                '@path': '$.traits.company'
            }
        },
        firstname: {
            label: 'First Name',
            type: 'string',
            description: 'The contact’s first name.',
            default: {
                '@if': {
                    exists: { '@path': '$.traits.first_name' },
                    then: { '@path': '$.traits.first_name' },
                    else: { '@path': '$.traits.firstName' }
                }
            }
        },
        lastname: {
            label: 'Last Name',
            type: 'string',
            description: 'The contact’s last name.',
            default: {
                '@if': {
                    exists: { '@path': '$.traits.last_name' },
                    then: { '@path': '$.traits.last_name' },
                    else: { '@path': '$.traits.lastName' }
                }
            }
        },
        phone: {
            label: 'Phone',
            type: 'string',
            description: 'The contact’s phone number.',
            default: {
                '@path': '$.traits.phone'
            }
        },
        address: {
            label: 'Street Address',
            type: 'string',
            description: "The contact's street address, including apartment or unit number.",
            default: {
                '@path': '$.traits.address.street'
            }
        },
        city: {
            label: 'City',
            type: 'string',
            description: "The contact's city of residence.",
            default: {
                '@path': '$.traits.address.city'
            }
        },
        state: {
            label: 'State',
            type: 'string',
            description: "The contact's state of residence.",
            default: {
                '@path': '$.traits.address.state'
            }
        },
        country: {
            label: 'Country',
            type: 'string',
            description: "The contact's country of residence.",
            default: {
                '@path': '$.traits.address.country'
            }
        },
        zip: {
            label: 'Postal Code',
            type: 'string',
            description: "The contact's zip code.",
            default: {
                '@if': {
                    exists: { '@path': '$.traits.address.postalCode' },
                    then: { '@path': '$.traits.address.postalCode' },
                    else: { '@path': '$.traits.address.postal_code' }
                }
            }
        },
        website: {
            label: 'Website',
            type: 'string',
            description: 'The contact’s company/other website.',
            default: {
                '@path': '$.traits.website'
            }
        },
        lifecyclestage: {
            label: 'Lifecycle Stage',
            type: 'string',
            description: 'The contact’s stage within the marketing/sales process. See more information on default and custom stages in [HubSpot’s documentation](https://knowledge.hubspot.com/contacts/use-lifecycle-stages). Segment supports moving status forwards or backwards.'
        },
        properties: {
            label: 'Other properties',
            type: 'object',
            description: 'Any other default or custom contact properties. On the left-hand side, input the internal name of the property as seen in your HubSpot account. On the right-hand side, map the Segment field that contains the value. Custom properties must be predefined in HubSpot. See more information in [HubSpot’s documentation](https://knowledge.hubspot.com/crm-setup/manage-your-properties#create-custom-properties).',
            defaultObjectUI: 'keyvalue:only'
        },
        enable_batching: {
            type: 'boolean',
            label: 'Send Batch Data to HubSpot',
            description: 'If true, Segment will batch events before sending to HubSpot’s API endpoint. HubSpot accepts batches of up to 100 events. Note: Contacts created with batch endpoint can’t be associated to a Company from the UpsertCompany Action.',
            default: false
        }
    },
    perform: async (request, { payload, transactionContext }) => {
        const contactProperties = {
            company: payload.company?.trim(),
            firstname: payload.firstname?.trim(),
            lastname: payload.lastname?.trim(),
            phone: payload.phone?.trim(),
            address: payload.address?.trim(),
            city: payload.city?.trim(),
            state: payload.state?.trim(),
            country: payload.country?.trim(),
            zip: payload.zip?.trim(),
            email: payload.email?.trim(),
            website: payload.website?.trim(),
            lifecyclestage: payload.lifecyclestage?.toLowerCase().trim(),
            ...(0, utils_1.flattenObject)(payload.properties)
        };
        try {
            const response = await updateContact(request, payload.email, contactProperties);
            transactionContext?.setTransaction('contact_id', response.data.id);
            if (payload.lifecyclestage) {
                const currentLCS = response.data.properties['lifecyclestage'];
                const hasLCSChanged = currentLCS === payload.lifecyclestage.toLowerCase();
                if (hasLCSChanged)
                    return response;
                await updateContact(request, payload.email, { lifecyclestage: '' });
                return updateContact(request, payload.email, contactProperties);
            }
            return response;
        }
        catch (ex) {
            if (ex?.response?.status == 404) {
                const result = await createContact(request, contactProperties);
                transactionContext?.setTransaction('contact_id', result.data.id);
                return result;
            }
            throw ex;
        }
    },
    performBatch: async (request, { payload }) => {
        let contactsUpsertMap = mapUpsertContactPayload(payload);
        const readResponse = await readContactsBatch(request, Object.keys(contactsUpsertMap));
        contactsUpsertMap = updateActionsForBatchedContacts(readResponse, contactsUpsertMap);
        const createList = [];
        const updateList = [];
        for (const [_, { action, payload }] of Object.entries(contactsUpsertMap)) {
            if (action === 'create') {
                createList.push(payload);
            }
            else if (action === 'update') {
                delete payload['properties']['email'];
                updateList.push({
                    id: payload.id,
                    properties: payload.properties
                });
            }
        }
        if (createList.length > 0) {
            await createContactsBatch(request, createList);
        }
        if (updateList.length > 0) {
            const updateContactResponse = await updateContactsBatch(request, updateList);
            await checkAndRetryUpdatingLifecycleStage(request, updateContactResponse, contactsUpsertMap);
        }
    }
};
async function createContact(request, contactProperties) {
    return request(`${properties_1.HUBSPOT_BASE_URL}/crm/${versioning_info_1.HUBSPOT_CRM_API_VERSION}/objects/contacts`, {
        method: 'POST',
        json: {
            properties: contactProperties
        }
    });
}
async function updateContact(request, email, properties) {
    return request(`${properties_1.HUBSPOT_BASE_URL}/crm/${versioning_info_1.HUBSPOT_CRM_API_VERSION}/objects/contacts/${email}?idProperty=email`, {
        method: 'PATCH',
        json: {
            properties: properties
        }
    });
}
async function readContactsBatch(request, emails) {
    const requestPayload = {
        properties: ['email', 'lifecyclestage', 'hs_additional_emails'],
        idProperty: 'email',
        inputs: emails.map((email) => ({
            id: email
        }))
    };
    return request(`${properties_1.HUBSPOT_BASE_URL}/crm/${versioning_info_1.HUBSPOT_CRM_API_VERSION}/objects/contacts/batch/read`, {
        method: 'POST',
        json: requestPayload
    });
}
async function createContactsBatch(request, contactCreatePayload) {
    return request(`${properties_1.HUBSPOT_BASE_URL}/crm/${versioning_info_1.HUBSPOT_CRM_API_VERSION}/objects/contacts/batch/create`, {
        method: 'POST',
        json: {
            inputs: contactCreatePayload
        }
    });
}
async function updateContactsBatch(request, contactUpdatePayload) {
    return request(`${properties_1.HUBSPOT_BASE_URL}/crm/${versioning_info_1.HUBSPOT_CRM_API_VERSION}/objects/contacts/batch/update`, {
        method: 'POST',
        json: {
            inputs: contactUpdatePayload
        }
    });
}
function mapUpsertContactPayload(payload) {
    const contactsUpsertMap = {};
    for (const contact of payload) {
        contactsUpsertMap[contact.email.toLowerCase()] = {
            action: 'undefined',
            payload: {
                properties: {
                    company: contact.company,
                    firstname: contact.firstname,
                    lastname: contact.lastname,
                    phone: contact.phone,
                    address: contact.address,
                    city: contact.city,
                    state: contact.state,
                    country: contact.country,
                    zip: contact.zip,
                    email: contact.email.toLowerCase(),
                    website: contact.website,
                    lifecyclestage: contact.lifecyclestage?.toLowerCase(),
                    ...(0, utils_1.flattenObject)(contact.properties)
                }
            }
        };
    }
    return contactsUpsertMap;
}
function updateActionsForBatchedContacts(readResponse, contactsUpsertMap) {
    if (readResponse.data?.results && readResponse.data.results.length > 0) {
        contactsUpsertMap = createPayloadToUpdateContact(readResponse, contactsUpsertMap);
    }
    if (readResponse.data?.numErrors && readResponse.data.errors) {
        for (const error of readResponse.data.errors) {
            if (error.status === 'error' && error.category === 'OBJECT_NOT_FOUND') {
                for (const id of error.context.ids) {
                    contactsUpsertMap[id].action = 'create';
                }
            }
            else {
                throw new actions_core_1.IntegrationError(error.message, error.category, 400);
            }
        }
    }
    return contactsUpsertMap;
}
async function checkAndRetryUpdatingLifecycleStage(request, updateContactResponse, contactsUpsertMap) {
    const resetLifeCycleStagePayload = [];
    const retryLifeCycleStagePayload = [];
    for (const result of updateContactResponse.data.results) {
        const key = Object.keys(contactsUpsertMap).find((key) => contactsUpsertMap[key].payload.id == result.id);
        const desiredLifeCycleStage = key ? contactsUpsertMap[key]?.payload?.properties?.lifecyclestage : null;
        const currentLifeCycleStage = result.properties.lifecyclestage;
        if (desiredLifeCycleStage && desiredLifeCycleStage !== currentLifeCycleStage) {
            resetLifeCycleStagePayload.push({
                id: result.id,
                properties: {
                    lifecyclestage: ''
                }
            });
            retryLifeCycleStagePayload.push({
                id: result.id,
                properties: {
                    lifecyclestage: desiredLifeCycleStage
                }
            });
        }
    }
    if (retryLifeCycleStagePayload.length > 0) {
        await updateContactsBatch(request, resetLifeCycleStagePayload);
        await updateContactsBatch(request, retryLifeCycleStagePayload);
    }
}
function createPayloadToUpdateContact(readResponse, contactsUpsertMap) {
    for (const result of readResponse.data.results) {
        let email;
        if (!contactsUpsertMap[`${result.properties.email}`]) {
            if (result.properties.hs_additional_emails) {
                const secondaryEmails = (0, split_1.default)(result.properties.hs_additional_emails, ';');
                email = Object.keys(contactsUpsertMap).find((key) => secondaryEmails.includes(key));
            }
        }
        else {
            email = result.properties.email;
        }
        if (email) {
            contactsUpsertMap[email].action = 'update';
            contactsUpsertMap[email].payload.id = result.id;
        }
    }
    return contactsUpsertMap;
}
exports.default = action;
//# sourceMappingURL=index.js.map