"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.sendImportJobRequest = exports.constructBulkProfileImportPayload = exports.createImportJobPayload = void 0;
exports.getListIdDynamicData = getListIdDynamicData;
exports.addProfileToList = addProfileToList;
exports.removeProfileFromList = removeProfileFromList;
exports.createProfile = createProfile;
exports.buildHeaders = buildHeaders;
exports.getProfiles = getProfiles;
exports.formatSubscribeProfile = formatSubscribeProfile;
exports.formatSubscribeRequestBody = formatSubscribeRequestBody;
exports.formatUnsubscribeRequestBody = formatUnsubscribeRequestBody;
exports.formatUnsubscribeProfile = formatUnsubscribeProfile;
exports.getList = getList;
exports.createList = createList;
exports.validateAndConvertPhoneNumber = validateAndConvertPhoneNumber;
exports.processPhoneNumber = processPhoneNumber;
exports.updateMultiStatusWithKlaviyoErrors = updateMultiStatusWithKlaviyoErrors;
exports.removeBulkProfilesFromList = removeBulkProfilesFromList;
exports.sendBatchedProfileImportJobRequest = sendBatchedProfileImportJobRequest;
exports.sendBatchedTrackEvent = sendBatchedTrackEvent;
exports.updateMultiStatusWithSuccessData = updateMultiStatusWithSuccessData;
exports.validateProfilePayload = validateProfilePayload;
const actions_core_1 = require("@segment/actions-core");
const config_1 = require("./config");
const google_libphonenumber_1 = require("google-libphonenumber");
const dayjs_1 = __importDefault(require("../../lib/dayjs"));
const properties_1 = require("./properties");
const phoneUtil = google_libphonenumber_1.PhoneNumberUtil.getInstance();
async function getListIdDynamicData(request) {
    try {
        const result = await request(`${config_1.API_URL}/lists/`, {
            method: 'get'
        });
        const choices = JSON.parse(result.content).data.map((list) => {
            return { value: list.id, label: list.attributes.name };
        });
        return {
            choices
        };
    }
    catch (err) {
        return {
            choices: [],
            nextPage: '',
            error: {
                message: err.message ?? 'Unknown error',
                code: err.status + '' ?? 'Unknown error'
            }
        };
    }
}
async function addProfileToList(request, id, list_id) {
    const listData = {
        data: [
            {
                type: 'profile',
                id: id
            }
        ]
    };
    const list = await request(`${config_1.API_URL}/lists/${list_id}/relationships/profiles/`, {
        method: 'POST',
        json: listData
    });
    return list;
}
async function removeProfileFromList(request, ids, list_id) {
    const listData = {
        data: ids.map((id) => ({ type: 'profile', id }))
    };
    const response = await request(`${config_1.API_URL}/lists/${list_id}/relationships/profiles/`, {
        method: 'DELETE',
        json: listData
    });
    return response;
}
async function createProfile(request, email, external_id, phone_number, additionalAttributes) {
    try {
        const profileData = {
            data: {
                type: 'profile',
                attributes: {
                    email,
                    external_id,
                    phone_number,
                    ...additionalAttributes
                }
            }
        };
        const profile = await request(`${config_1.API_URL}/profiles/`, {
            method: 'POST',
            json: profileData
        });
        const rs = await profile.json();
        return rs.data.id;
    }
    catch (error) {
        const { response } = error;
        if (response?.status == 409) {
            const rs = await response.json();
            return rs.errors[0].meta.duplicate_profile_id;
        }
    }
}
function buildHeaders(authKey) {
    return {
        Authorization: `Klaviyo-API-Key ${authKey}`,
        Accept: 'application/json',
        revision: config_1.REVISION_DATE,
        'Content-Type': 'application/json'
    };
}
const createImportJobPayload = (profiles, listId) => ({
    data: {
        type: 'profile-bulk-import-job',
        attributes: {
            profiles: {
                data: profiles.map(({ list_id, enable_batching, batch_size, override_list_id, country_code, batch_keys, ...attributes }) => ({
                    type: 'profile',
                    attributes
                }))
            }
        },
        ...(listId
            ? {
                relationships: {
                    lists: {
                        data: [{ type: 'list', id: listId }]
                    }
                }
            }
            : {})
    }
});
exports.createImportJobPayload = createImportJobPayload;
const constructBulkProfileImportPayload = (profiles, listId) => ({
    data: {
        type: 'profile-bulk-import-job',
        attributes: {
            profiles: {
                data: profiles
            }
        },
        ...(listId ? { relationships: { lists: { data: [{ type: 'list', id: listId }] } } } : {})
    }
});
exports.constructBulkProfileImportPayload = constructBulkProfileImportPayload;
const sendImportJobRequest = async (request, importJobPayload) => {
    return await request(`${config_1.API_URL}/profile-bulk-import-jobs/`, {
        method: 'POST',
        json: importJobPayload
    });
};
exports.sendImportJobRequest = sendImportJobRequest;
async function getProfiles(request, emails, external_ids, phoneNumbers) {
    const profileIds = [];
    if (external_ids?.length) {
        const filterId = `external_id,["${external_ids.join('","')}"]`;
        const response = await request(`${config_1.API_URL}/profiles/?filter=any(${filterId})`, {
            method: 'GET'
        });
        const res = await response.json();
        profileIds.push(...res.data.map((profile) => profile.id));
    }
    if (emails?.length) {
        const filterEmail = `email,["${emails.join('","')}"]`;
        const response = await request(`${config_1.API_URL}/profiles/?filter=any(${filterEmail})`, {
            method: 'GET'
        });
        const res = await response.json();
        profileIds.push(...res.data.map((profile) => profile.id));
    }
    if (phoneNumbers?.length) {
        const filterPhone = `phone_number,["${phoneNumbers.join('","')}"]`;
        const response = await request(`${config_1.API_URL}/profiles/?filter=any(${filterPhone})`, {
            method: 'GET'
        });
        const res = await response.json();
        profileIds.push(...res.data.map((profile) => profile.id));
    }
    return Array.from(new Set(profileIds));
}
function formatSubscribeProfile(email, phone_number, consented_at, historical_import = false) {
    const profileToSubscribe = {
        type: 'profile',
        attributes: {
            subscriptions: {}
        }
    };
    if (email) {
        profileToSubscribe.attributes.email = email;
        profileToSubscribe.attributes.subscriptions.email = {
            marketing: {
                consent: 'SUBSCRIBED'
            }
        };
        if (historical_import && consented_at) {
            profileToSubscribe.attributes.subscriptions.email.marketing.consented_at = consented_at;
        }
    }
    if (phone_number) {
        profileToSubscribe.attributes.phone_number = phone_number;
        profileToSubscribe.attributes.subscriptions.sms = {
            marketing: {
                consent: 'SUBSCRIBED'
            }
        };
        if (historical_import && consented_at) {
            profileToSubscribe.attributes.subscriptions.sms.marketing.consented_at = consented_at;
        }
    }
    return profileToSubscribe;
}
function formatSubscribeRequestBody(profiles, list_id, custom_source, historical_import = false) {
    if (!Array.isArray(profiles)) {
        profiles = [profiles];
    }
    const subData = {
        data: {
            type: 'profile-subscription-bulk-create-job',
            attributes: {
                historical_import: historical_import ?? false,
                profiles: {
                    data: profiles
                }
            }
        }
    };
    subData.data.attributes.custom_source = custom_source || '-59';
    if (list_id) {
        subData.data.relationships = {
            list: {
                data: {
                    type: 'list',
                    id: list_id
                }
            }
        };
    }
    return subData;
}
function formatUnsubscribeRequestBody(profiles, list_id) {
    if (!Array.isArray(profiles)) {
        profiles = [profiles];
    }
    const unsubData = {
        data: {
            type: 'profile-subscription-bulk-delete-job',
            attributes: {
                profiles: {
                    data: profiles
                }
            }
        }
    };
    if (list_id) {
        unsubData.data.relationships = {
            list: {
                data: {
                    type: 'list',
                    id: list_id
                }
            }
        };
    }
    return unsubData;
}
function formatUnsubscribeProfile(email, phone_number) {
    const profileToUnSubscribe = {
        type: 'profile',
        attributes: {
            subscriptions: {}
        }
    };
    if (email) {
        profileToUnSubscribe.attributes.email = email;
        profileToUnSubscribe.attributes.subscriptions.email = {
            marketing: {
                consent: 'UNSUBSCRIBED'
            }
        };
    }
    if (phone_number) {
        profileToUnSubscribe.attributes.phone_number = phone_number;
        profileToUnSubscribe.attributes.subscriptions.sms = {
            marketing: {
                consent: 'UNSUBSCRIBED'
            },
            transactional: {
                consent: 'UNSUBSCRIBED'
            }
        };
    }
    return profileToUnSubscribe;
}
async function getList(request, settings, listId) {
    const apiKey = settings.api_key;
    const response = await request(`${config_1.API_URL}/lists/${listId}`, {
        method: 'GET',
        headers: buildHeaders(apiKey),
        throwHttpErrors: false
    });
    if (!response.ok) {
        const errorResponse = await response.json();
        const klaviyoErrorDetail = errorResponse.errors[0].detail;
        throw new actions_core_1.APIError(klaviyoErrorDetail, response.status);
    }
    const r = await response.json();
    const externalId = r.data.id;
    if (externalId !== listId) {
        throw new actions_core_1.IntegrationError("Unable to verify ownership over audience. Segment Audience ID doesn't match The Klaviyo List Id.", 'INVALID_REQUEST_DATA', 400);
    }
    return {
        successMessage: `Using existing list '${r.data.attributes.name}' (id: ${listId})`,
        savedData: {
            id: listId,
            name: r.data.attributes.name
        }
    };
}
async function createList(request, settings, listName) {
    const apiKey = settings.api_key;
    if (!listName) {
        throw new actions_core_1.PayloadValidationError('Missing audience name value');
    }
    if (!apiKey) {
        throw new actions_core_1.PayloadValidationError('Missing Api Key value');
    }
    const response = await request(`${config_1.API_URL}/lists`, {
        method: 'POST',
        headers: buildHeaders(apiKey),
        json: {
            data: { type: 'list', attributes: { name: listName } }
        }
    });
    const r = await response.json();
    return {
        successMessage: `List '${r.data.attributes.name}' (id: ${r.data.id}) created successfully!`,
        savedData: {
            id: r.data.id,
            name: r.data.attributes.name
        }
    };
}
function validateAndConvertPhoneNumber(phone, countryCode) {
    if (!phone)
        return;
    const e164Regex = /^\+[1-9]\d{1,14}$/;
    if (e164Regex.test(phone)) {
        return phone;
    }
    if (countryCode) {
        try {
            const parsedPhone = phoneUtil.parse(phone, countryCode);
            const isValid = phoneUtil.isValidNumberForRegion(parsedPhone, countryCode);
            if (!isValid) {
                return null;
            }
            return phoneUtil.format(parsedPhone, google_libphonenumber_1.PhoneNumberFormat.E164);
        }
        catch (error) {
            return null;
        }
    }
    return null;
}
function processPhoneNumber(initialPhoneNumber, country_code) {
    if (!initialPhoneNumber) {
        return;
    }
    const phone_number = validateAndConvertPhoneNumber(initialPhoneNumber, country_code);
    if (!phone_number) {
        throw new actions_core_1.PayloadValidationError(`${initialPhoneNumber} is not a valid phone number and cannot be converted to E.164 format.`);
    }
    return phone_number;
}
async function updateMultiStatusWithKlaviyoErrors(payloads, err, multiStatusResponse, validPayloadIndicesBitmap) {
    const errorResponse = await err?.response?.json();
    payloads.forEach((payload, index) => {
        multiStatusResponse.setErrorResponseAtIndex(validPayloadIndicesBitmap[index], {
            status: err?.response?.status || 400,
            errormessage: err?.response?.statusText,
            sent: payload,
            body: JSON.stringify(errorResponse)
        });
    });
}
async function removeBulkProfilesFromList(request, payloads, statsContext) {
    const multiStatusResponse = new actions_core_1.MultiStatusResponse();
    const { filteredPayloads, validPayloadIndicesBitmap } = validateAndPrepareRemoveBulkProfilePayloads(payloads, multiStatusResponse);
    if (!filteredPayloads.length) {
        return multiStatusResponse;
    }
    const emails = extractField(filteredPayloads, 'email');
    const externalIds = extractField(filteredPayloads, 'external_id');
    const phoneNumbers = extractField(filteredPayloads, 'phone_number');
    if (statsContext) {
        const { tags, statsClient } = statsContext;
        const set = new Set();
        filteredPayloads.forEach((x) => set.add(x.list_id));
        statsClient?.histogram('actions-klaviyo.remove_profile_from_list.unique_list_id', set.size, tags);
    }
    const listId = filteredPayloads[0]?.list_id;
    try {
        const profileIds = await getProfiles(request, emails, externalIds, phoneNumbers);
        let response = null;
        if (profileIds.length) {
            response = await removeProfileFromList(request, profileIds, listId);
        }
        updateMultiStatusWithSuccessData(filteredPayloads, validPayloadIndicesBitmap, multiStatusResponse, response);
    }
    catch (error) {
        if (error instanceof actions_core_1.HTTPError) {
            await updateMultiStatusWithKlaviyoErrors(payloads, error, multiStatusResponse, validPayloadIndicesBitmap);
        }
        else {
            throw error;
        }
    }
    return multiStatusResponse;
}
function extractField(payloads, field) {
    return payloads.map((profile) => profile[field]).filter(Boolean);
}
function validateAndPrepareRemoveBulkProfilePayloads(payloads, multiStatusResponse) {
    const filteredPayloads = [];
    const validPayloadIndicesBitmap = [];
    payloads.forEach((payload, originalBatchIndex) => {
        const { validPayload, error } = validateAndConstructRemoveProfilePayloads(payload);
        if (error) {
            multiStatusResponse.setErrorResponseAtIndex(originalBatchIndex, error);
        }
        else {
            filteredPayloads.push(validPayload);
            validPayloadIndicesBitmap.push(originalBatchIndex);
        }
    });
    return { filteredPayloads, validPayloadIndicesBitmap };
}
function validateAndConstructRemoveProfilePayloads(payload) {
    const { phone_number, email, external_id } = payload;
    const response = {};
    if (!email && !phone_number && !external_id) {
        response.error = {
            status: 400,
            errortype: 'PAYLOAD_VALIDATION_FAILED',
            errormessage: 'One of External ID, Phone Number or Email is required.'
        };
        return response;
    }
    if (phone_number) {
        const validPhoneNumber = validateAndConvertPhoneNumber(phone_number, payload.country_code);
        if (!validPhoneNumber) {
            response.error = {
                status: 400,
                errortype: 'PAYLOAD_VALIDATION_FAILED',
                errormessage: 'Phone number could not be converted to E.164 format.'
            };
            return response;
        }
        payload.phone_number = validPhoneNumber;
    }
    return { validPayload: payload };
}
function validateAndConstructProfilePayload(payload) {
    const { phone_number, email, external_id } = payload;
    const response = {};
    if (!email && !phone_number && !external_id) {
        response.error = {
            status: 400,
            errortype: 'PAYLOAD_VALIDATION_FAILED',
            errormessage: 'One of External ID, Phone Number or Email is required.'
        };
        return response;
    }
    if (phone_number) {
        const validPhoneNumber = validateAndConvertPhoneNumber(phone_number, payload.country_code);
        if (!validPhoneNumber) {
            response.error = {
                status: 400,
                errortype: 'PAYLOAD_VALIDATION_FAILED',
                errormessage: 'Phone number could not be converted to E.164 format.'
            };
            return response;
        }
        payload.phone_number = validPhoneNumber;
        delete payload.country_code;
    }
    const { list_id, enable_batching, batch_size, country_code, batch_keys, ...attributes } = payload;
    response.validPayload = { type: 'profile', attributes: attributes };
    return response;
}
function validateAndPrepareBatchedProfileImportPayloads(payloads, multiStatusResponse) {
    const filteredPayloads = [];
    const validPayloadIndicesBitmap = [];
    payloads.forEach((payload, originalBatchIndex) => {
        const { validPayload, error } = validateAndConstructProfilePayload(payload);
        if (error) {
            multiStatusResponse.setErrorResponseAtIndex(originalBatchIndex, error);
        }
        else {
            filteredPayloads.push(validPayload);
            validPayloadIndicesBitmap.push(originalBatchIndex);
        }
    });
    return { filteredPayloads, validPayloadIndicesBitmap };
}
async function sendBatchedProfileImportJobRequest(request, payloads, statsContext) {
    const multiStatusResponse = new actions_core_1.MultiStatusResponse();
    const { filteredPayloads, validPayloadIndicesBitmap } = validateAndPrepareBatchedProfileImportPayloads(payloads, multiStatusResponse);
    if (!filteredPayloads.length) {
        return multiStatusResponse;
    }
    if (statsContext) {
        const { tags, statsClient } = statsContext;
        const set = new Set();
        filteredPayloads.forEach((x) => set.add(x.list_id));
        statsClient?.histogram('actions-klaviyo.add_profile_to_list.unique_list_id', set.size, tags);
    }
    const importJobPayload = (0, exports.constructBulkProfileImportPayload)(filteredPayloads, payloads[0]?.list_id);
    try {
        const response = await (0, exports.sendImportJobRequest)(request, importJobPayload);
        updateMultiStatusWithSuccessData(filteredPayloads, validPayloadIndicesBitmap, multiStatusResponse, response);
    }
    catch (error) {
        if (error instanceof actions_core_1.HTTPError) {
            await updateMultiStatusWithKlaviyoErrors(payloads, error, multiStatusResponse, validPayloadIndicesBitmap);
        }
        else {
            throw error;
        }
    }
    return multiStatusResponse;
}
async function sendBatchedTrackEvent(request, payloads) {
    const multiStatusResponse = new actions_core_1.MultiStatusResponse();
    const { filteredPayloads, validPayloadIndicesBitmap } = validateAndPreparePayloads(payloads, multiStatusResponse);
    if (!filteredPayloads.length) {
        return multiStatusResponse;
    }
    const payloadToSend = {
        data: {
            type: 'event-bulk-create-job',
            attributes: {
                'events-bulk-create': {
                    data: filteredPayloads
                }
            }
        }
    };
    try {
        const response = await request(`${config_1.API_URL}/event-bulk-create-jobs/`, {
            method: 'POST',
            json: payloadToSend
        });
        updateMultiStatusWithSuccessData(filteredPayloads, validPayloadIndicesBitmap, multiStatusResponse, response);
    }
    catch (err) {
        if (err instanceof actions_core_1.HTTPError) {
            const errorResponse = await err?.response?.json();
            handleKlaviyoAPIErrorResponse(payloads, errorResponse, multiStatusResponse, validPayloadIndicesBitmap, properties_1.eventBulkCreateRegex);
        }
        else {
            throw err;
        }
    }
    return multiStatusResponse;
}
function validateAndPreparePayloads(payloads, multiStatusResponse) {
    const filteredPayloads = [];
    const validPayloadIndicesBitmap = [];
    payloads.forEach((payload, originalBatchIndex) => {
        const { email, phone_number, external_id, anonymous_id, country_code } = payload.profile;
        if (!email && !phone_number && !external_id && !anonymous_id) {
            multiStatusResponse.setErrorResponseAtIndex(originalBatchIndex, {
                status: 400,
                errortype: 'PAYLOAD_VALIDATION_FAILED',
                errormessage: 'One of External ID, Anonymous ID, Phone Number or Email is required.'
            });
            return;
        }
        if (phone_number) {
            const validPhoneNumber = validateAndConvertPhoneNumber(phone_number, country_code);
            if (!validPhoneNumber) {
                multiStatusResponse.setErrorResponseAtIndex(originalBatchIndex, {
                    status: 400,
                    errortype: 'PAYLOAD_VALIDATION_FAILED',
                    errormessage: 'Phone number could not be converted to E.164 format.'
                });
                return;
            }
            payload.profile.phone_number = validPhoneNumber;
            delete payload?.profile?.country_code;
        }
        const profileToAdd = constructBulkCreateEventPayload(payload);
        filteredPayloads.push(profileToAdd);
        validPayloadIndicesBitmap.push(originalBatchIndex);
    });
    return { filteredPayloads, validPayloadIndicesBitmap };
}
function constructBulkCreateEventPayload(payload) {
    return {
        type: 'event-bulk-create',
        attributes: {
            profile: {
                data: {
                    type: 'profile',
                    attributes: payload.profile
                }
            },
            events: {
                data: [
                    {
                        type: 'event',
                        attributes: {
                            metric: {
                                data: {
                                    type: 'metric',
                                    attributes: {
                                        name: payload.metric_name
                                    }
                                }
                            },
                            properties: { ...payload.properties },
                            time: payload?.time ? (0, dayjs_1.default)(payload.time).toISOString() : undefined,
                            value: payload.value,
                            unique_id: payload.unique_id
                        }
                    }
                ]
            }
        }
    };
}
function handleKlaviyoAPIErrorResponse(payloads, response, multiStatusResponse, validPayloadIndicesBitmap, regex) {
    if (response?.errors && Array.isArray(response.errors)) {
        const invalidIndexSet = new Set();
        response.errors.forEach((error) => {
            const indexInOriginalPayload = getIndexFromErrorPointer(error.source.pointer, validPayloadIndicesBitmap, regex);
            if (indexInOriginalPayload !== -1 && !multiStatusResponse.isErrorResponseAtIndex(indexInOriginalPayload)) {
                multiStatusResponse.setErrorResponseAtIndex(indexInOriginalPayload, {
                    status: error.status,
                    errormessage: error.detail,
                    sent: payloads[indexInOriginalPayload],
                    body: JSON.stringify(error)
                });
                invalidIndexSet.add(indexInOriginalPayload);
            }
        });
        for (const index of validPayloadIndicesBitmap) {
            if (!invalidIndexSet.has(index)) {
                multiStatusResponse.setErrorResponseAtIndex(index, {
                    errormessage: "This event wasn't delivered because of few bad events in the same batch to Klaviyo. This will be retried",
                    errortype: 'RETRYABLE_BATCH_FAILURE',
                    status: 429,
                    sent: payloads[index],
                    body: 'Retry'
                });
            }
        }
    }
}
function getIndexFromErrorPointer(pointer, validPayloadIndicesBitmap, regex) {
    const match = regex.exec(pointer);
    if (match && match[1]) {
        const index = parseInt(match[1], 10);
        return validPayloadIndicesBitmap[index] !== undefined ? validPayloadIndicesBitmap[index] : -1;
    }
    return -1;
}
function updateMultiStatusWithSuccessData(filteredPayloads, validPayloadIndicesBitmap, multiStatusResponse, response) {
    filteredPayloads.forEach((payload, index) => {
        multiStatusResponse.setSuccessResponseAtIndex(validPayloadIndicesBitmap[index], {
            status: 200,
            sent: payload,
            body: JSON.stringify(response?.data) || 'success'
        });
    });
}
function validateProfilePayload(payload) {
    const response = {};
    if (!payload.email && !payload.phone_number && !payload.external_id) {
        response.error = {
            status: 400,
            errortype: 'PAYLOAD_VALIDATION_FAILED',
            errormessage: 'One of External ID, Phone Number or Email is required.'
        };
        return response;
    }
    if (payload.phone_number) {
        const validPhoneNumber = validateAndConvertPhoneNumber(payload.phone_number, payload.country_code);
        if (!validPhoneNumber) {
            response.error = {
                status: 400,
                errortype: 'PAYLOAD_VALIDATION_FAILED',
                errormessage: 'Phone number could not be converted to E.164 format.'
            };
            return response;
        }
        payload.phone_number = validPhoneNumber;
        delete payload.country_code;
    }
    response.payload = payload;
    return response;
}
//# sourceMappingURL=functions.js.map