"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.send = send;
exports.validatePhone = validatePhone;
exports.toDateFormat = toDateFormat;
const actions_core_1 = require("@segment/actions-core");
const constants_1 = require("../constants");
const chunk_1 = __importDefault(require("lodash/chunk"));
async function send(request, payload, isBatch) {
    const msResponse = new actions_core_1.MultiStatusResponse();
    const indexedPayloads = payload.map((p, index) => ({
        ...p,
        index,
        action: p.traits_or_props[p.segment_audience_key] === true ? 'add' : 'remove'
    }));
    validate(indexedPayloads);
    await upsertContacts(request, indexedPayloads, 'add');
    await upsertContacts(request, indexedPayloads, 'remove');
    await removeFromList(request, indexedPayloads);
    indexedPayloads.forEach((p) => {
        if (p.error) {
            const { status, errortype, errormessage } = p.error;
            msResponse.setErrorResponseAtIndex(p.index, { status, errortype, errormessage });
        }
        else {
            msResponse.setSuccessResponseAtIndex(p.index, {
                status: 200,
                sent: p,
                body: 'success'
            });
        }
    });
    if (isBatch) {
        return msResponse;
    }
    else {
        if (indexedPayloads[0].error) {
            const { status, errortype, errormessage } = indexedPayloads[0].error;
            throw new actions_core_1.IntegrationError(errormessage, errortype, status);
        }
        else {
            return;
        }
    }
}
function validate(payloads, invalidEmails) {
    if (payloads[0].external_audience_id == null) {
        assignErrors(payloads, undefined, 400, 'external_audience_id value is missing from payload', actions_core_1.ErrorCodes.PAYLOAD_VALIDATION_FAILED);
        return;
    }
    payloads.forEach((p) => {
        validatePayload(p, invalidEmails);
    });
}
function validatePayload(payload, invalidEmails) {
    if (payload.identifiers.email && invalidEmails?.includes(payload.identifiers.email)) {
        assignErrors([payload], undefined, 400, `Sendgrid rejected email '${payload.identifiers.email}' as being not valid`, actions_core_1.ErrorCodes.PAYLOAD_VALIDATION_FAILED);
        return;
    }
    if (payload.identifiers.phone_number_id && !validatePhone(payload.identifiers.phone_number_id)) {
        assignErrors([payload], undefined, 400, `phone_number_id '${payload.identifiers.phone_number_id}' is not valid`, actions_core_1.ErrorCodes.PAYLOAD_VALIDATION_FAILED);
        return;
    }
    if (payload.user_attributes?.phone_number && !validatePhone(payload.user_attributes.phone_number)) {
        assignErrors([payload], undefined, 400, `user_attributes.phone_number '${payload.user_attributes.phone_number}' is not valid`, actions_core_1.ErrorCodes.PAYLOAD_VALIDATION_FAILED);
        return;
    }
    for (const [key, value] of Object.entries(payload.custom_text_fields ?? {})) {
        if (typeof value !== 'string') {
            assignErrors([payload], undefined, 400, `custom_text_fields ${key} value ${value} is not a string`, actions_core_1.ErrorCodes.PAYLOAD_VALIDATION_FAILED);
            return;
        }
    }
    for (const [key, value] of Object.entries(payload.custom_number_fields ?? {})) {
        if (typeof value !== 'number') {
            assignErrors([payload], undefined, 400, `custom_number_fields ${key} value ${value} is not a number`, actions_core_1.ErrorCodes.PAYLOAD_VALIDATION_FAILED);
            return;
        }
    }
    if (payload.custom_date_fields) {
        for (const [key, value] of Object.entries(payload.custom_date_fields)) {
            if (typeof value !== 'string') {
                assignErrors([payload], undefined, 400, `custom_number_fields ${key} value ${value} is not a valid date`, actions_core_1.ErrorCodes.PAYLOAD_VALIDATION_FAILED);
                return;
            }
            else {
                const date = toDateFormat(value);
                if (date === undefined) {
                    assignErrors([payload], undefined, 400, `custom_number_fields ${key} value ${value} is not a valid date`, actions_core_1.ErrorCodes.PAYLOAD_VALIDATION_FAILED);
                    return;
                }
                payload.custom_date_fields[key] = date;
            }
        }
    }
    const requiredIdentifiers = [
        payload.identifiers.email,
        payload.identifiers.phone_number_id,
        payload.identifiers.external_id,
        payload.identifiers.anonymous_id
    ];
    if (!requiredIdentifiers.some(Boolean)) {
        assignErrors([payload], undefined, 400, 'At least one identifier from Email Address, Phone Number ID, Anonymous ID or External ID is required.', actions_core_1.ErrorCodes.PAYLOAD_VALIDATION_FAILED);
        return;
    }
}
async function upsertContacts(request, payloads, action) {
    try {
        const json = upsertJSON(payloads, action);
        if (json.contacts.length > 0) {
            await upsertRequest(request, json);
        }
        return;
    }
    catch (error) {
        const { status, data } = error.response;
        if (status === 400) {
            const invalidEmails = Array.isArray(data.errors)
                ? data.errors
                    .map(({ message }) => /email '(.+?)' is not valid/.exec(message)?.[1])
                    .filter((email) => !!email)
                : [];
            if (invalidEmails.length >= 0) {
                payloads.forEach(p => {
                    const email = p.identifiers.email;
                    if (email && invalidEmails.includes(email)) {
                        assignErrors([p], action, status, `SendGrid rejected email address ${email} as invalid`, actions_core_1.ErrorCodes.PAYLOAD_VALIDATION_FAILED);
                    }
                    else {
                        assignErrors([p], action, 429, 'Batch payload rejected by SendGrid due to at least one invalid email. Batch payload will be retried without the invalid email(s).', actions_core_1.ErrorCodes.RETRYABLE_ERROR);
                    }
                });
                return;
            }
        }
        assignErrors(payloads, action, status, 'Error occurred while upserting this contact to Sendgrid', actions_core_1.ErrorCodes.UNKNOWN_ERROR);
        return;
    }
}
function upsertJSON(payloads, action) {
    const json = {
        list_ids: action === 'add' ? [payloads[0].external_audience_id] : [],
        contacts: payloads
            .filter((payload) => !payload.error)
            .filter((payload) => payload.action === action)
            .map((payload) => {
            const { identifiers: { email = undefined, phone_number_id = undefined, external_id = undefined, anonymous_id = undefined }, user_attributes = undefined, custom_text_fields = undefined, custom_number_fields = undefined, custom_date_fields = undefined } = payload;
            const custom_fields = {
                ...custom_text_fields,
                ...custom_number_fields,
                ...custom_date_fields
            };
            return {
                email,
                phone_number_id,
                external_id,
                anonymous_id,
                ...user_attributes,
                custom_fields: Object.keys(custom_fields).length > 0 ? custom_fields : undefined
            };
        })
    };
    return JSON.parse(JSON.stringify(json));
}
async function upsertRequest(request, json) {
    return await request(constants_1.UPSERT_CONTACTS_URL, {
        method: 'put',
        json
    });
}
async function removeFromList(request, payloads) {
    if (payloads.length === 0) {
        return;
    }
    const identifierTypes = ['email', 'phone_number_id', 'external_id', 'anonymous_id'];
    const chunks = chunkPayloads(payloads);
    const queries = chunks.map((c) => identifierTypes
        .map((idType) => getQueryPart(idType, c))
        .filter((query) => query !== '')
        .join(' OR '));
    let responses = [];
    try {
        responses = await Promise.all(queries.map((query) => request(constants_1.SEARCH_CONTACTS_URL, {
            method: 'post',
            json: {
                query
            }
        })));
    }
    catch (error) {
        assignErrors(payloads, 'remove', 400, `Error occurred while searching for Contacts in SendGrid`, actions_core_1.ErrorCodes.UNKNOWN_ERROR);
        return;
    }
    const contact_ids = responses.flatMap((response) => response.data.result.map((item) => item.id));
    const chunkedContactIds = (0, chunk_1.default)(contact_ids, constants_1.MAX_CHUNK_SIZE_REMOVE);
    const listId = payloads[0].external_audience_id;
    try {
        await Promise.all(chunkedContactIds.map(async (c) => {
            const url = constants_1.REMOVE_CONTACTS_FROM_LIST_URL.replace('{list_id}', listId).replace('{contact_ids}', c.join(','));
            if (c.length > 0) {
                await request(url, {
                    method: 'delete'
                });
            }
        }));
    }
    catch (error) {
        assignErrors(payloads, 'remove', 400, `Error occurred while removing Contacts from a List in SendGrid`, actions_core_1.ErrorCodes.UNKNOWN_ERROR);
        return;
    }
}
function chunkPayloads(payloads) {
    const chunks = [];
    let currentChunk = [];
    let currentChunkSize = 0;
    for (const payload of payloads) {
        if (payload.error || payload.action !== 'remove') {
            continue;
        }
        const ids = payload.identifiers;
        const idCount = [ids.email, ids.phone_number_id, ids.external_id, ids.anonymous_id].filter(Boolean).length;
        if (currentChunkSize + idCount > constants_1.MAX_CHUNK_SIZE_SEARCH) {
            chunks.push(currentChunk);
            currentChunk = [];
            currentChunkSize = 0;
        }
        currentChunk.push(payload);
        currentChunkSize += idCount;
    }
    if (currentChunk.length) {
        chunks.push(currentChunk);
    }
    return chunks;
}
function getQueryPart(identifier, payloads) {
    const values = payloads
        .filter((payload) => payload.identifiers[identifier] !== undefined && payload.identifiers[identifier] !== null)
        .map((payload) => payload.identifiers[identifier]);
    const part = values.length > 0 ? `${identifier} IN (${values.map((value) => `'${value}'`).join(',')})` : '';
    return part;
}
function validatePhone(phone) {
    return constants_1.E164_REGEX.test(phone);
}
function toDateFormat(dateString) {
    const date = new Date(dateString);
    return isNaN(date.getTime()) ? undefined : `${(date.getMonth() + 1).toString().padStart(2, '0')}/${date.getDate().toString().padStart(2, '0')}/${date.getFullYear()}`;
}
function assignErrors(payloads, action, status = 400, errormessage = "Unknown error", errortype = actions_core_1.ErrorCodes.UNKNOWN_ERROR) {
    payloads
        .filter((p) => action ? p.action === action : true)
        .forEach((p) => {
        p.error = {
            errormessage,
            errortype,
            status,
        };
    });
}
//# sourceMappingURL=utils.js.map