"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.handleJobExecutionError = exports.processBatchPayload = exports.createIdentifierExtractors = exports.handlePartialFailureResponse = exports.verifyCustomerId = exports.handleUpdate = exports.validateAndFormatToE164 = exports.formatPhone = exports.commonEmailValidation = exports.isHashedInformation = exports.hash = exports.GoogleAdsError = exports.FLAGON_NAME_PHONE_VALIDATION_CHECK = exports.FLAGON_NAME = exports.CANARY_API_VERSION = exports.API_VERSION = void 0;
exports.formatCustomVariables = formatCustomVariables;
exports.getCustomVariables = getCustomVariables;
exports.memoizedGetCustomVariables = memoizedGetCustomVariables;
exports.getConversionActionId = getConversionActionId;
exports.getConversionActionDynamicData = getConversionActionDynamicData;
exports.handleGoogleErrors = handleGoogleErrors;
exports.convertTimestamp = convertTimestamp;
exports.timestampToEpochMicroseconds = timestampToEpochMicroseconds;
exports.getApiVersion = getApiVersion;
exports.getListIds = getListIds;
exports.createGoogleAudience = createGoogleAudience;
exports.getGoogleAudience = getGoogleAudience;
exports.formatToE164 = formatToE164;
exports.getSessionAttributesKeyValuePairs = getSessionAttributesKeyValuePairs;
const crypto_1 = require("crypto");
const actions_core_1 = require("@segment/actions-core");
const formats_1 = require("ajv-formats/dist/formats");
const actions_core_2 = require("@segment/actions-core");
const constants_1 = require("./constants");
const hashing_utils_1 = require("../../lib/hashing-utils");
exports.API_VERSION = 'v21';
exports.CANARY_API_VERSION = 'v21';
exports.FLAGON_NAME = 'google-enhanced-canary-version';
exports.FLAGON_NAME_PHONE_VALIDATION_CHECK = 'google-enhanced-phone-validation-check';
const google_libphonenumber_1 = require("google-libphonenumber");
const phoneUtil = google_libphonenumber_1.PhoneNumberUtil.getInstance();
class GoogleAdsError extends actions_core_2.HTTPError {
}
exports.GoogleAdsError = GoogleAdsError;
function formatCustomVariables(customVariables, customVariableIdsResults) {
    const resourceNames = {};
    Object.entries(customVariableIdsResults).forEach(([_, customVariablesIds]) => {
        resourceNames[customVariablesIds.conversionCustomVariable.name] =
            customVariablesIds.conversionCustomVariable.resourceName;
    });
    const variables = [];
    Object.entries(customVariables).forEach(([key, value]) => {
        if (resourceNames[key] != undefined) {
            const variable = {
                conversionCustomVariable: resourceNames[key],
                value: value
            };
            variables.push(variable);
        }
    });
    return variables;
}
const hash = (value) => {
    if (value === undefined) {
        return;
    }
    const hash = (0, crypto_1.createHash)('sha256');
    hash.update(value);
    return hash.digest('hex');
};
exports.hash = hash;
async function getCustomVariables(customerId, auth, request, features, statsContext) {
    return await request(`https://googleads.googleapis.com/${getApiVersion(features, statsContext)}/customers/${customerId}/googleAds:searchStream`, {
        method: 'post',
        headers: {
            authorization: `Bearer ${auth?.accessToken}`,
            'developer-token': `${process.env.ADWORDS_DEVELOPER_TOKEN}`
        },
        json: {
            query: `SELECT conversion_custom_variable.id, conversion_custom_variable.name FROM conversion_custom_variable`
        }
    });
}
function memoizedGetCustomVariables() {
    const cache = new Map();
    return async (customerId, auth, request, features, statsContext) => {
        if (cache.has(customerId)) {
            return cache.get(customerId);
        }
        else {
            const result = getCustomVariables(customerId, auth, request, features, statsContext);
            cache.set(customerId, result);
            return result;
        }
    };
}
async function getConversionActionId(customerId, auth, request, features, statsContext) {
    return request(`https://googleads.googleapis.com/${getApiVersion(features, statsContext)}/customers/${customerId}/googleAds:searchStream`, {
        method: 'post',
        headers: {
            authorization: `Bearer ${auth?.accessToken}`,
            'developer-token': `${process.env.ADWORDS_DEVELOPER_TOKEN}`
        },
        json: {
            query: `SELECT conversion_action.id, conversion_action.name FROM conversion_action`
        }
    });
}
async function getConversionActionDynamicData(request, settings, auth, features, statsContext) {
    try {
        settings.customerId = settings.customerId.replace(/-/g, '');
        const results = await getConversionActionId(settings.customerId, auth, request, features, statsContext);
        const res = JSON.parse(results.content);
        const choices = res[0].results.map((input) => {
            return { value: input.conversionAction.id, label: input.conversionAction.name };
        });
        return {
            choices
        };
    }
    catch (err) {
        return {
            choices: [],
            nextPage: '',
            error: {
                message: err.response?.statusText ?? 'Unknown error',
                code: err.response?.status + '' ?? '500'
            }
        };
    }
}
function handleGoogleErrors(response) {
    if (response.data.partialFailureError) {
        throw new actions_core_1.IntegrationError(response.data.partialFailureError.message, 'INVALID_ARGUMENT', 400);
    }
}
function convertTimestamp(timestamp) {
    if (!timestamp) {
        return undefined;
    }
    return timestamp.replace(/T/, ' ').replace(/(\.\d+)?Z/, '+00:00');
}
function timestampToEpochMicroseconds(timestamp) {
    if (!timestamp) {
        return undefined;
    }
    const date = new Date(timestamp);
    if (!isNaN(date.getTime())) {
        return (date.getTime() * 1000).toString();
    }
    return undefined;
}
function getApiVersion(features, statsContext) {
    const statsClient = statsContext?.statsClient;
    const tags = statsContext?.tags;
    const version = features && features[exports.FLAGON_NAME] ? exports.CANARY_API_VERSION : exports.API_VERSION;
    tags?.push(`version:${version}`);
    statsClient?.incr(`google_api_version`, 1, tags);
    return version;
}
const isHashedInformation = (information) => new RegExp(/[0-9abcdef]{64}/gi).test(information);
exports.isHashedInformation = isHashedInformation;
const commonEmailValidation = (email) => {
    const googleDomain = new RegExp('^(gmail|googlemail).s*', 'g');
    let normalizedEmail = email.toLowerCase().trim();
    const emailParts = normalizedEmail.split('@');
    if (emailParts.length > 1 && emailParts[1].match(googleDomain)) {
        emailParts[0] = emailParts[0].replace('.', '');
        normalizedEmail = `${emailParts[0]}@${emailParts[1]}`;
    }
    if (!formats_1.fullFormats.email.test(normalizedEmail)) {
        throw new actions_core_1.PayloadValidationError("Email provided doesn't seem to be in a valid format.");
    }
    return normalizedEmail;
};
exports.commonEmailValidation = commonEmailValidation;
async function getListIds(request, settings, auth, features, statsContext) {
    const json = {
        query: `SELECT user_list.id, user_list.name FROM user_list`
    };
    try {
        const response = await request(`https://googleads.googleapis.com/${getApiVersion(features, statsContext)}/customers/${settings.customerId}/googleAds:search`, {
            method: 'post',
            headers: {
                'developer-token': `${process.env.ADWORDS_DEVELOPER_TOKEN}`,
                authorization: `Bearer ${auth?.accessToken}`
            },
            json
        });
        const choices = response.data.results.map((input) => {
            return { value: input.userList.id, label: input.userList.name };
        });
        return {
            choices
        };
    }
    catch (err) {
        return {
            choices: [],
            nextPage: '',
            error: {
                message: err.response?.statusText ?? 'Unknown error',
                code: err.response?.status + '' ?? '500'
            }
        };
    }
}
async function createGoogleAudience(request, input, auth, features, statsContext) {
    if (input.audienceSettings.external_id_type === 'MOBILE_ADVERTISING_ID' && !input.audienceSettings.app_id) {
        throw new actions_core_1.PayloadValidationError('App ID is required when external ID type is mobile advertising ID.');
    }
    if (!auth?.refresh_token ||
        !process.env.GOOGLE_ENHANCED_CONVERSIONS_CLIENT_ID ||
        !process.env.GOOGLE_ENHANCED_CONVERSIONS_CLIENT_SECRET) {
        throw new actions_core_1.PayloadValidationError('Oauth credentials missing.');
    }
    const res = await request('https://www.googleapis.com/oauth2/v4/token', {
        method: 'POST',
        body: new URLSearchParams({
            refresh_token: auth.refresh_token,
            client_id: process.env.GOOGLE_ENHANCED_CONVERSIONS_CLIENT_ID,
            client_secret: process.env.GOOGLE_ENHANCED_CONVERSIONS_CLIENT_SECRET,
            grant_type: 'refresh_token'
        })
    });
    const accessToken = res.data.access_token;
    const statsClient = statsContext?.statsClient;
    const statsTags = statsContext?.tags;
    const json = {
        operations: [
            {
                create: {
                    crmBasedUserList: {
                        uploadKeyType: input.audienceSettings.external_id_type,
                        appId: input.audienceSettings.app_id
                    },
                    membershipLifeSpan: '540',
                    name: `${input.audienceName}`
                }
            }
        ]
    };
    const response = await request(`https://googleads.googleapis.com/${getApiVersion(features, statsContext)}/customers/${input.settings.customerId}/userLists:mutate`, {
        method: 'post',
        headers: {
            'developer-token': `${process.env.ADWORDS_DEVELOPER_TOKEN}`,
            authorization: `Bearer ${accessToken}`
        },
        json
    });
    const name = response.data.results[0].resourceName;
    if (!name) {
        statsClient?.incr('createAudience.error', 1, statsTags);
        throw new actions_core_1.IntegrationError('Failed to receive a created customer list id.', 'INVALID_RESPONSE', 400);
    }
    statsClient?.incr('createAudience.success', 1, statsTags);
    return name.split('/')[3];
}
async function getGoogleAudience(request, settings, externalId, auth, features, statsContext) {
    if (!auth?.refresh_token ||
        !process.env.GOOGLE_ENHANCED_CONVERSIONS_CLIENT_ID ||
        !process.env.GOOGLE_ENHANCED_CONVERSIONS_CLIENT_SECRET) {
        throw new actions_core_1.PayloadValidationError('Oauth credentials missing.');
    }
    const res = await request('https://www.googleapis.com/oauth2/v4/token', {
        method: 'POST',
        body: new URLSearchParams({
            refresh_token: auth.refresh_token,
            client_id: process.env.GOOGLE_ENHANCED_CONVERSIONS_CLIENT_ID,
            client_secret: process.env.GOOGLE_ENHANCED_CONVERSIONS_CLIENT_SECRET,
            grant_type: 'refresh_token'
        })
    });
    const accessToken = res.data.access_token;
    const statsClient = statsContext?.statsClient;
    const statsTags = statsContext?.tags;
    const json = {
        query: `SELECT user_list.id, user_list.name FROM user_list where user_list.id = '${externalId}'`
    };
    const response = await request(`https://googleads.googleapis.com/${getApiVersion(features, statsContext)}/customers/${settings.customerId}/googleAds:search`, {
        method: 'post',
        headers: {
            'developer-token': `${process.env.ADWORDS_DEVELOPER_TOKEN}`,
            authorization: `Bearer ${accessToken}`
        },
        json
    });
    const id = response.data.results[0].userList.id;
    if (!id) {
        statsClient?.incr('getAudience.error', 1, statsTags);
        throw new actions_core_1.IntegrationError('Failed to receive a customer list.', 'INVALID_RESPONSE', 400);
    }
    statsClient?.incr('getAudience.success', 1, statsTags);
    return response.data;
}
function formatToE164(phoneNumber, countryCode) {
    const numericPhoneNumber = phoneNumber.replace(/\D/g, '');
    let formattedPhoneNumber = numericPhoneNumber;
    const formattedCountryCode = countryCode.replace(/\D/g, '');
    if (!numericPhoneNumber.startsWith(formattedCountryCode)) {
        formattedPhoneNumber = formattedCountryCode + numericPhoneNumber;
    }
    if (!formattedPhoneNumber.startsWith('+')) {
        formattedPhoneNumber = '+' + formattedPhoneNumber;
    }
    return formattedPhoneNumber;
}
const formatPhone = (phone, countryCode, features, statsContext) => {
    if ((0, exports.isHashedInformation)(phone)) {
        return phone;
    }
    let formattedPhone;
    if (features && features[exports.FLAGON_NAME_PHONE_VALIDATION_CHECK]) {
        formattedPhone = (0, exports.validateAndFormatToE164)(phone, countryCode ?? '+1', statsContext);
    }
    else {
        formattedPhone = formatToE164(phone, countryCode ?? '+1');
    }
    if (phone == formattedPhone) {
        statsContext?.statsClient?.incr('validateAndFormatPhone.pre_formatted', 1, statsContext?.tags);
    }
    return formattedPhone;
};
exports.formatPhone = formatPhone;
const validateAndFormatToE164 = (phoneNumber, countryCode, statsContext) => {
    try {
        let regionCode = 'US';
        if (countryCode) {
            const intCode = parseInt(countryCode.replace(/\D/g, ''));
            regionCode = phoneUtil.getRegionCodeForCountryCode(intCode) || 'US';
        }
        const parsedNumber = phoneUtil.parseAndKeepRawInput(phoneNumber, regionCode);
        if (!phoneUtil.isValidNumber(parsedNumber)) {
            statsContext?.statsClient?.incr('validateAndFormatPhone.error', 1, statsContext?.tags);
            throw new actions_core_1.PayloadValidationError('Invalid phone number or country code.');
        }
        statsContext?.statsClient?.incr('validateAndFormatPhone.success', 1, statsContext?.tags);
        return phoneUtil.format(parsedNumber, google_libphonenumber_1.PhoneNumberFormat.E164);
    }
    catch (error) {
        statsContext?.statsClient?.incr('validateAndFormatPhone.error', 1, statsContext?.tags);
        throw new actions_core_1.PayloadValidationError(error.message || 'Invalid phone number or country code.');
    }
};
exports.validateAndFormatToE164 = validateAndFormatToE164;
const extractUserIdentifiers = (payloads, idType, syncMode, features, statsContext) => {
    const removeUserIdentifiers = [];
    const addUserIdentifiers = [];
    const identifierFunctions = {
        MOBILE_ADVERTISING_ID: (payload) => ({
            mobileId: payload.mobile_advertising_id?.trim()
        }),
        CRM_ID: (payload) => ({
            thirdPartyUserId: payload.crm_id?.trim()
        }),
        CONTACT_INFO: (payload) => {
            const identifiers = [];
            if (payload.email) {
                identifiers.push({
                    hashedEmail: (0, hashing_utils_1.processHashing)(payload.email, 'sha256', 'hex', exports.commonEmailValidation)
                });
            }
            if (payload.phone) {
                identifiers.push({
                    hashedPhoneNumber: (0, hashing_utils_1.processHashing)(payload.phone, 'sha256', 'hex', (value) => (0, exports.formatPhone)(value, payload.phone_country_code, features, statsContext))
                });
            }
            if (payload.first_name || payload.last_name || payload.country_code || payload.postal_code) {
                const addressInfo = {};
                if (payload.first_name) {
                    addressInfo.hashedFirstName = (0, hashing_utils_1.processHashing)(payload.first_name, 'sha256', 'hex');
                }
                if (payload.last_name) {
                    addressInfo.hashedLastName = (0, hashing_utils_1.processHashing)(payload.last_name, 'sha256', 'hex');
                }
                addressInfo.countryCode = payload.country_code ?? '';
                addressInfo.postalCode = payload.postal_code ?? '';
                identifiers.push({ addressInfo });
            }
            return identifiers;
        }
    };
    for (const payload of payloads) {
        if (payload.event_name === 'Audience Entered' ||
            syncMode === 'add' ||
            (syncMode === 'mirror' && payload.event_name === 'new')) {
            addUserIdentifiers.push({ create: { userIdentifiers: identifierFunctions[idType](payload) } });
        }
        else if (payload.event_name === 'Audience Exited' ||
            syncMode === 'delete' ||
            (syncMode === 'mirror' && payload.event_name === 'deleted')) {
            removeUserIdentifiers.push({ remove: { userIdentifiers: identifierFunctions[idType](payload) } });
        }
    }
    return [addUserIdentifiers, removeUserIdentifiers];
};
const createOfflineUserJob = async (request, offlineUserJobPayload, settings, features, statsContext) => {
    const url = `https://googleads.googleapis.com/${getApiVersion(features, statsContext)}/customers/${settings.customerId}/offlineUserDataJobs:create`;
    try {
        const response = await request(url, {
            method: 'post',
            headers: {
                'developer-token': `${process.env.ADWORDS_DEVELOPER_TOKEN}`
            },
            json: offlineUserJobPayload
        });
        return { success: true, data: response.data.resourceName };
    }
    catch (error) {
        statsContext?.statsClient?.incr('error.createJob', 1, statsContext?.tags);
        return { success: false, error };
    }
};
const handleGoogleAdsError = (error) => {
    const errors = error?.response?.data?.error?.details ?? [];
    for (const errorDetails of errors) {
        for (const errorItem of errorDetails.errors) {
            if (errorItem?.errorCode?.databaseError === 'CONCURRENT_MODIFICATION') {
                throw new actions_core_1.RetryableError(errorItem?.message ??
                    'Multiple requests were attempting to modify the same resource at once. Retry the request.', 500);
            }
        }
    }
    throw error;
};
const addOperations = async (request, addOperationPayload, resourceName, features, statsContext) => {
    const url = `https://googleads.googleapis.com/${getApiVersion(features, statsContext)}/${resourceName}:addOperations`;
    try {
        const response = await request(url, {
            method: 'post',
            headers: { 'developer-token': `${process.env.ADWORDS_DEVELOPER_TOKEN}` },
            json: addOperationPayload
        });
        return { success: true, data: response.data };
    }
    catch (error) {
        statsContext?.statsClient?.incr('error.addOperations', 1, statsContext?.tags);
        return { success: false, error };
    }
};
const processOperations = async (request, userIdentifiers, resourceName, validPayloadIndicesBitmap, failedPayloadIndices, multiStatusResponse, features, statsContext) => {
    const operationPayload = { operations: userIdentifiers, enablePartialFailure: true };
    const { success, data, error } = await addOperations(request, operationPayload, resourceName, features, statsContext);
    if (!success) {
        handleGoogleAdsAPIErrorResponse(error, validPayloadIndicesBitmap, multiStatusResponse, operationPayload, failedPayloadIndices);
    }
    const partialFailureError = data?.partialFailureError;
    if (partialFailureError) {
        (0, exports.handlePartialFailureResponse)(partialFailureError, validPayloadIndicesBitmap, multiStatusResponse, userIdentifiers, failedPayloadIndices);
    }
};
const handleUpdate = async (request, settings, audienceSettings, payloads, hookListId, hookListType, syncMode, features, statsContext) => {
    const externalAudienceId = hookListId || payloads[0]?.external_audience_id;
    if (!externalAudienceId) {
        throw new actions_core_1.PayloadValidationError('External Audience ID is required.');
    }
    const id_type = hookListType ?? audienceSettings.external_id_type;
    const [adduserIdentifiers, removeUserIdentifiers] = extractUserIdentifiers(payloads, id_type, syncMode, features, statsContext);
    const offlineUserJobPayload = createOfflineUserJobPayload(externalAudienceId, payloads[0], settings.customerId);
    const offlineUserJobResponse = await createOfflineUserJob(request, offlineUserJobPayload, settings, features, statsContext);
    if (!offlineUserJobResponse.success) {
        return handleGoogleAdsError(offlineUserJobResponse.error);
    }
    const resourceName = offlineUserJobResponse.data;
    if (adduserIdentifiers.length > 0) {
        const addOperationPayload = { operations: adduserIdentifiers, enable_warnings: true };
        const addResponse = await addOperations(request, addOperationPayload, resourceName, features, statsContext);
        if (!addResponse.success) {
            return handleGoogleAdsError(addResponse.error);
        }
    }
    if (removeUserIdentifiers.length > 0) {
        const removeOperationPayload = { operations: removeUserIdentifiers, enable_warnings: true };
        const removeResponse = await addOperations(request, removeOperationPayload, resourceName, features, statsContext);
        if (!removeResponse.success) {
            return handleGoogleAdsError(removeResponse.error);
        }
    }
    const executedJob = await runOfflineUserJob(request, resourceName, features, statsContext);
    if (!executedJob.success) {
        return handleGoogleAdsError(executedJob.error);
    }
    statsContext?.statsClient?.incr('success.offlineUpdateAudience', 1, statsContext?.tags);
    return executedJob.data;
};
exports.handleUpdate = handleUpdate;
const verifyCustomerId = (customerId) => {
    if (!customerId) {
        throw new actions_core_1.PayloadValidationError('Customer ID is required.');
    }
    return customerId.replace(/-/g, '');
};
exports.verifyCustomerId = verifyCustomerId;
const handleGoogleAdsAPIErrorResponse = (error, validPayloadIndicesBitmap, multiStatusResponse, payload, failedPayloadIndices) => {
    const errorData = error?.response?.data?.error;
    const parsedError = parseGoogleAdsError(errorData);
    validPayloadIndicesBitmap.forEach((index) => {
        multiStatusResponse.setErrorResponseAtIndex(index, {
            ...parsedError,
            body: error,
            sent: payload
        });
        failedPayloadIndices?.add(index);
    });
};
const parseGoogleAdsError = (error) => {
    const hasConcurrentModificationError = error?.details?.some((detail) => {
        return detail?.errors?.some((e) => e?.errorCode?.databaseError === 'CONCURRENT_MODIFICATION');
    });
    return hasConcurrentModificationError
        ? {
            errormessage: "This event wasn't delivered because of CONCURRENT_MODIFICATION error. Multiple requests were attempting to modify the same resource at once. Retry the request.",
            errortype: 'RETRYABLE_BATCH_FAILURE',
            status: 429
        }
        : {
            errormessage: error?.message,
            status: error?.code
        };
};
const updateMultiStatusResponseWithSuccess = (executedJob, validPayloadIndicesBitmap, multiStatusResponse, sentBody, failedPayloadIndices) => {
    validPayloadIndicesBitmap.forEach((index) => {
        if (!failedPayloadIndices.has(index)) {
            multiStatusResponse.setSuccessResponseAtIndex(index, {
                status: 200,
                sent: sentBody,
                body: executedJob.data
            });
        }
    });
};
const handlePartialFailureResponse = (partialFailureError, validPayloadIndicesBitmap, multiStatusResponse, userIdentifiers, failedPayloadIndices) => {
    partialFailureError?.details?.forEach((detail) => {
        detail.errors?.forEach((error) => {
            const failedIndex = error.location?.fieldPathElements?.find((field) => field.fieldName === 'operations')?.index;
            if (failedIndex >= 0) {
                const originalIndex = validPayloadIndicesBitmap[failedIndex];
                multiStatusResponse.setErrorResponseAtIndex(originalIndex, {
                    status: constants_1.STATUS_CODE_MAPPING?.[partialFailureError.code]?.status ?? 500,
                    errormessage: error.message,
                    sent: userIdentifiers?.[failedIndex],
                    body: error
                });
                failedPayloadIndices.add(originalIndex);
            }
        });
    });
};
exports.handlePartialFailureResponse = handlePartialFailureResponse;
const runOfflineUserJob = async (request, resourceName, features, statsContext) => {
    const url = `https://googleads.googleapis.com/${getApiVersion(features, statsContext)}/${resourceName}:run`;
    try {
        const response = await request(url, {
            method: 'post',
            headers: {
                'developer-token': `${process.env.ADWORDS_DEVELOPER_TOKEN}`
            }
        });
        return { success: true, data: response.data };
    }
    catch (error) {
        statsContext?.statsClient?.incr('error.runJob', 1, statsContext?.tags);
        return { success: false, error };
    }
};
const createIdentifierExtractors = (features) => ({
    MOBILE_ADVERTISING_ID: (payload) => {
        return payload.mobile_advertising_id?.trim() ? { mobileId: payload.mobile_advertising_id.trim() } : null;
    },
    CRM_ID: (payload) => {
        return payload.crm_id?.trim() ? { thirdPartyUserId: payload.crm_id.trim() } : null;
    },
    CONTACT_INFO: (payload) => {
        const identifiers = [];
        if (payload.email) {
            identifiers.push({
                hashedEmail: (0, hashing_utils_1.processHashing)(payload.email, 'sha256', 'hex', exports.commonEmailValidation)
            });
        }
        if (payload.phone) {
            identifiers.push({
                hashedPhoneNumber: (0, hashing_utils_1.processHashing)(payload.phone, 'sha256', 'hex', (value) => (0, exports.formatPhone)(value, payload.phone_country_code, features))
            });
        }
        if (payload.first_name || payload.last_name || payload.country_code || payload.postal_code) {
            identifiers.push({
                addressInfo: {
                    hashedFirstName: payload.first_name ? (0, hashing_utils_1.processHashing)(payload.first_name, 'sha256', 'hex') : undefined,
                    hashedLastName: payload.last_name ? (0, hashing_utils_1.processHashing)(payload.last_name, 'sha256', 'hex') : undefined,
                    countryCode: payload.country_code || '',
                    postalCode: payload.postal_code || ''
                }
            });
        }
        return identifiers.length > 0 ? identifiers : null;
    }
});
exports.createIdentifierExtractors = createIdentifierExtractors;
const extractBatchUserIdentifiers = (payloads, idType, multiStatusResponse, syncMode, features) => {
    const removeUserIdentifiers = [];
    const addUserIdentifiers = [];
    const validPayloadIndicesBitmap = [];
    const extractors = (0, exports.createIdentifierExtractors)(features);
    payloads.forEach((payload, index) => {
        let userIdentifiers;
        try {
            userIdentifiers = extractors[idType]?.(payload);
        }
        catch (error) {
            if (error instanceof actions_core_1.PayloadValidationError) {
                multiStatusResponse.setErrorResponseAtIndex(index, {
                    status: 400,
                    errortype: 'PAYLOAD_VALIDATION_FAILED',
                    errormessage: error.message
                });
            }
            return;
        }
        if (!userIdentifiers) {
            multiStatusResponse?.setErrorResponseAtIndex(index, {
                status: 400,
                errortype: 'PAYLOAD_VALIDATION_FAILED',
                errormessage: `Missing or Invalid data for ${idType}.`
            });
            return;
        }
        const operationType = determineOperationType(payload, syncMode);
        if (!operationType) {
            multiStatusResponse.setErrorResponseAtIndex(index, {
                status: 400,
                errortype: 'PAYLOAD_VALIDATION_FAILED',
                errormessage: 'Could not determine Operation Type.'
            });
            return;
        }
        validPayloadIndicesBitmap.push(index);
        if (operationType === 'add') {
            addUserIdentifiers.push({ create: { userIdentifiers } });
        }
        else {
            removeUserIdentifiers.push({ remove: { userIdentifiers } });
        }
    });
    return { addUserIdentifiers, removeUserIdentifiers, validPayloadIndicesBitmap };
};
const determineOperationType = (payload, syncMode) => {
    if (payload.event_name === 'Audience Entered' ||
        syncMode === 'add' ||
        (syncMode === 'mirror' && payload.event_name === 'new')) {
        return 'add';
    }
    else if (payload.event_name === 'Audience Exited' ||
        syncMode === 'delete' ||
        (syncMode === 'mirror' && payload.event_name === 'deleted')) {
        return 'remove';
    }
    return null;
};
const createOfflineUserJobPayload = (audienceId, payload, customerId) => ({
    job: {
        type: 'CUSTOMER_MATCH_USER_LIST',
        customerMatchUserListMetadata: {
            userList: `customers/${customerId}/userLists/${audienceId}`,
            consent: {
                adUserData: payload?.ad_user_data_consent_state,
                adPersonalization: payload?.ad_personalization_consent_state
            }
        }
    }
});
const processBatchPayload = async (request, settings, audienceSettings, payloads, hookListId, hookListType, syncMode, features, statsContext) => {
    const externalAudienceId = hookListId || payloads[0]?.external_audience_id;
    if (!externalAudienceId) {
        throw new actions_core_1.PayloadValidationError('External Audience ID is required.');
    }
    const multiStatusResponse = new actions_core_1.MultiStatusResponse();
    const id_type = hookListType ?? audienceSettings.external_id_type;
    const { addUserIdentifiers, removeUserIdentifiers, validPayloadIndicesBitmap } = extractBatchUserIdentifiers(payloads, id_type, multiStatusResponse, syncMode, features);
    const offlineUserJobPayload = createOfflineUserJobPayload(externalAudienceId, payloads[0], settings.customerId);
    const { success, data: resourceName, error } = await createOfflineUserJob(request, offlineUserJobPayload, settings, features, statsContext);
    if (!success) {
        handleGoogleAdsAPIErrorResponse(error, validPayloadIndicesBitmap, multiStatusResponse, offlineUserJobPayload);
        return multiStatusResponse;
    }
    const failedPayloadIndices = new Set();
    if (addUserIdentifiers.length > 0) {
        await processOperations(request, addUserIdentifiers, resourceName, validPayloadIndicesBitmap, failedPayloadIndices, multiStatusResponse, features, statsContext);
    }
    if (removeUserIdentifiers.length > 0) {
        await processOperations(request, removeUserIdentifiers, resourceName, validPayloadIndicesBitmap, failedPayloadIndices, multiStatusResponse, features, statsContext);
    }
    if (failedPayloadIndices.size === validPayloadIndicesBitmap.length) {
        return multiStatusResponse;
    }
    const executedJob = await runOfflineUserJob(request, resourceName, features, statsContext);
    statsContext?.statsClient?.incr('success.offlineUpdateAudience', 1, statsContext?.tags);
    const sentBody = `/${resourceName}:run`;
    if (!executedJob.success) {
        (0, exports.handleJobExecutionError)(executedJob, validPayloadIndicesBitmap, multiStatusResponse, sentBody, failedPayloadIndices);
    }
    else {
        updateMultiStatusResponseWithSuccess(executedJob, validPayloadIndicesBitmap, multiStatusResponse, sentBody, failedPayloadIndices);
    }
    return multiStatusResponse;
};
exports.processBatchPayload = processBatchPayload;
const handleJobExecutionError = (executedJob, validPayloadIndicesBitmap, multiStatusResponse, sentBody, failedPayloadIndices) => {
    const executedJobError = executedJob.error?.response?.data?.error;
    const parsedError = parseGoogleAdsError(executedJobError);
    validPayloadIndicesBitmap.forEach((index) => {
        if (!failedPayloadIndices.has(index)) {
            multiStatusResponse.setErrorResponseAtIndex(index, {
                ...parsedError,
                sent: sentBody,
                body: executedJob.error
            });
        }
    });
};
exports.handleJobExecutionError = handleJobExecutionError;
function getSessionAttributesKeyValuePairs(payload) {
    const { session_attributes_key_value_pairs: { gad_source, gad_campaignid, landing_page_url, session_start_time_usec, landing_page_referrer, landing_page_user_agent } = {} } = payload;
    const sessionStartTimeUsec = typeof session_start_time_usec === 'string'
        ? timestampToEpochMicroseconds(session_start_time_usec)
        : undefined;
    const entries = [
        ['gad_source', gad_source],
        ['gad_campaignid', gad_campaignid],
        ['landing_page_url', landing_page_url],
        ['session_start_time_usec', sessionStartTimeUsec],
        ['landing_page_referrer', landing_page_referrer],
        ['landing_page_user_agent', landing_page_user_agent]
    ];
    const keyValuePairList = entries
        .filter(([_, value]) => value !== undefined && value !== null && value !== '')
        .map(([key, value]) => ({
        sessionAttributeKey: key,
        sessionAttributeValue: value
    }));
    return (keyValuePairList.length > 0
        ? { sessionAttributesKeyValuePairs: { keyValuePairs: keyValuePairList } }
        : {});
}
//# sourceMappingURL=functions.js.map