"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const actions_core_1 = require("@segment/actions-core");
const versioning_info_1 = require("../versioning-info");
const action = {
    title: 'Upsert Profile',
    description: 'Create or update Memora profiles by importing a CSV file. Profiles are uploaded via a pre-signed URL and processed asynchronously. If a profile already exists, its traits are merged (new keys added, existing keys overwritten).',
    defaultSubscription: 'type = "identify"',
    fields: {
        enable_batching: {
            label: 'Enable Batching',
            description: 'Enable batching of requests to Memora. Batches are uploaded as CSV files.',
            type: 'boolean',
            default: true,
            unsafe_hidden: true
        },
        batch_size: {
            label: 'Batch Size',
            description: 'Maximum number of profiles to include in each CSV import. Actual batch sizes may be lower.',
            type: 'number',
            default: 1000,
            unsafe_hidden: true
        },
        memora_store: {
            label: 'Memora Store',
            description: 'The Memora Store ID to use for this profile. This should be a valid Memora Store associated with your Twilio account.',
            type: 'string',
            required: true,
            dynamic: true
        },
        contact_identifiers: {
            label: 'Contact Identifiers',
            description: 'Contact identifiers (email and/or phone). At least one identifier is required.',
            type: 'object',
            required: true,
            additionalProperties: false,
            properties: {
                email: {
                    label: 'Email',
                    description: 'User email address',
                    type: 'string',
                    format: 'email'
                },
                phone: {
                    label: 'Phone',
                    description: 'User phone number',
                    type: 'string'
                }
            },
            default: {
                email: { '@path': '$.properties.email' },
                phone: { '@path': '$.properties.phone' }
            }
        },
        contact_traits: {
            label: 'Other Contact Traits',
            description: 'Additional contact traits for the profile. These fields are dynamically loaded from the selected Memora Store.',
            type: 'object',
            required: false,
            additionalProperties: true,
            dynamic: true
        }
    },
    dynamicFields: {
        memora_store: async (request, { settings }) => {
            return fetchMemoraStores(request, settings);
        },
        contact_traits: {
            __keys__: async (request, { settings, payload }) => {
                if (!payload.memora_store) {
                    return { choices: [], error: { message: 'Please select a Memora Store first', code: 'STORE_REQUIRED' } };
                }
                return fetchContactTraits(request, settings, payload.memora_store);
            }
        }
    },
    perform: async (request, { payload, settings, logger }) => {
        return upsertProfiles(request, [payload], settings, logger);
    },
    performBatch: async (request, { payload: payloads, settings, logger }) => {
        return upsertProfiles(request, payloads, settings, logger);
    }
};
function validateAndCollectFields(payloads) {
    const allFields = new Set();
    payloads.forEach((payload, index) => {
        const identifiers = payload.contact_identifiers || {};
        if (!identifiers.email && !identifiers.phone) {
            throw new actions_core_1.IntegrationError(`Profile at index ${index} must contain at least one identifier (email or phone)`, 'MISSING_IDENTIFIER', 400);
        }
        if (identifiers.email)
            allFields.add('email');
        if (identifiers.phone)
            allFields.add('phone');
        if (payload.contact_traits && typeof payload.contact_traits === 'object') {
            const traits = payload.contact_traits;
            Object.keys(traits).forEach((key) => {
                if (traits[key] !== undefined) {
                    allFields.add(key);
                }
            });
        }
    });
    if (allFields.size === 0) {
        throw new actions_core_1.IntegrationError('No profile fields found for import', 'EMPTY_PROFILE', 400);
    }
    return allFields;
}
async function requestImportUrl(request, storeId, fileSize, columnMappings, settings, logger) {
    const timestamp = Date.now();
    const filename = `memora-segment-import-${storeId}-${timestamp}.csv`;
    try {
        const importResponse = await request(`${versioning_info_1.BASE_URL}/${versioning_info_1.API_VERSION}/Stores/${storeId}/Profiles/Imports`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                ...(settings.twilioAccount && { 'X-Pre-Auth-Context': settings.twilioAccount })
            },
            username: settings.username,
            password: settings.password,
            json: {
                filename,
                fileSize,
                columnMappings
            }
        });
        const importId = importResponse.data.importId;
        const uploadUrl = importResponse.data.url;
        logger?.info?.(`Memora import initiated: ${importId}`);
        return { importId, uploadUrl };
    }
    catch (error) {
        logger?.error?.(`Error initiating Memora import: ${error instanceof Error ? error.message : String(error)}`);
        throw error;
    }
}
async function uploadCSVToMemora(request, uploadUrl, csvBuffer, importId, profileCount, logger) {
    try {
        await request(uploadUrl, {
            method: 'PUT',
            headers: {
                'Content-Type': 'text/csv'
            },
            body: csvBuffer
        });
        logger?.info?.(`CSV uploaded successfully to Memora (importId: ${importId}, ${profileCount} profiles)`);
        return {
            data: {
                importId,
                profileCount,
                success: true
            }
        };
    }
    catch (error) {
        logger?.error?.(`Error uploading CSV to Memora (importId: ${importId}): ${error instanceof Error ? error.message : String(error)}`);
        throw error;
    }
}
async function upsertProfiles(request, payloads, settings, logger) {
    if (!payloads || payloads.length === 0) {
        throw new actions_core_1.IntegrationError('No profiles provided for import', 'EMPTY_BATCH', 400);
    }
    const storeId = payloads[0]?.memora_store;
    const allFields = validateAndCollectFields(payloads);
    const { csv, columnMappings } = convertToCSV(payloads, Array.from(allFields));
    const csvBuffer = Buffer.from(csv, 'utf-8');
    const { importId, uploadUrl } = await requestImportUrl(request, storeId, csvBuffer.length, columnMappings, settings, logger);
    return uploadCSVToMemora(request, uploadUrl, csvBuffer, importId, payloads.length, logger);
}
function convertToCSV(payloads, fields) {
    const escapeCSVValue = (value) => {
        if (value.includes(',') || value.includes('"') || value.includes('\n')) {
            return `"${value.replace(/"/g, '""')}"`;
        }
        return value;
    };
    const header = fields.map(escapeCSVValue).join(',');
    const rows = payloads.map((payload) => {
        return fields
            .map((field) => {
            let value;
            if (field === 'email' || field === 'phone') {
                const identifiers = payload.contact_identifiers;
                value = identifiers?.[field];
            }
            else {
                const traits = payload.contact_traits;
                value = traits?.[field];
            }
            if (value === undefined || value === null) {
                return '';
            }
            const stringValue = String(value);
            return escapeCSVValue(stringValue);
        })
            .join(',');
    });
    const csv = [header, ...rows].join('\n');
    const columnMappings = fields.map((field) => ({
        columnName: field,
        traitGroup: 'Contact',
        traitName: field
    }));
    return { csv, columnMappings };
}
async function fetchContactTraits(request, settings, storeId) {
    try {
        const response = await request(`${versioning_info_1.BASE_URL}/${versioning_info_1.API_VERSION}/ControlPlane/Stores/${storeId}/TraitGroups/Contact?includeTraits=true&pageSize=100`, {
            method: 'GET',
            headers: {
                ...(settings.twilioAccount && { 'X-Pre-Auth-Context': settings.twilioAccount })
            },
            username: settings.username,
            password: settings.password,
            skipResponseCloning: true
        });
        const traitsObj = response?.data?.traitGroup?.traits || {};
        const choices = Object.entries(traitsObj)
            .filter(([_, trait]) => trait.idTypePromotion !== 'email' && trait.idTypePromotion !== 'phone')
            .map(([traitName, trait]) => ({
            label: trait.displayName || traitName,
            value: traitName,
            description: trait.description || `${trait.displayName} (${trait.dataType})`
        }));
        return {
            choices
        };
    }
    catch (error) {
        const statusCode = error?.response?.status || 'unknown';
        const errorMsg = error?.response?.data?.message || (error instanceof Error ? error.message : String(error));
        return {
            choices: [],
            error: {
                message: `Unable to fetch contact traits (HTTP ${statusCode}: ${errorMsg}). You can still manually enter field names.`,
                code: 'FETCH_ERROR'
            }
        };
    }
}
async function fetchMemoraStores(request, settings) {
    try {
        const response = await request(`${versioning_info_1.BASE_URL}/${versioning_info_1.API_VERSION}/ControlPlane/Stores?pageSize=100&orderBy=ASC`, {
            method: 'GET',
            headers: {
                ...(settings.twilioAccount && { 'X-Pre-Auth-Context': settings.twilioAccount })
            },
            username: settings.username,
            password: settings.password,
            skipResponseCloning: true
        });
        const stores = response?.data?.stores || [];
        const choices = stores.map((storeId) => ({
            label: storeId,
            value: storeId
        }));
        return {
            choices
        };
    }
    catch (error) {
        return {
            choices: [],
            error: {
                message: 'Unable to fetch memora stores. You can still manually enter a memora store ID.',
                code: 'FETCH_ERROR'
            }
        };
    }
}
exports.default = action;
//# sourceMappingURL=index.js.map