"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getTrackData = void 0;
const lodash_1 = require("lodash");
/**
 * Fetch and process track data from Awell SDK
 * @param params Input parameters for fetching track data
 * @returns Processed track data with steps and activities
 */
const getTrackData = async ({ awellSdk, pathwayId, trackId, currentActivityId, }) => {
    var _a, _b, _c, _d, _e, _f, _g, _h;
    const startTime = Date.now();
    // Validate empty strings
    if ((0, lodash_1.isEmpty)(pathwayId))
        throw new Error('PathwayId is required');
    if ((0, lodash_1.isEmpty)(trackId))
        throw new Error('TrackId is required');
    if ((0, lodash_1.isEmpty)(currentActivityId))
        throw new Error('CurrentActivityId is required');
    // 1. Make a single combined query for all track data needed
    const combinedQuery = await fetchAllTrackData(awellSdk, pathwayId, trackId);
    // Log query results 'size'
    const activityCount = (_c = (_b = (_a = combinedQuery.careflowActivities) === null || _a === void 0 ? void 0 : _a.activities) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0;
    console.log(`Track data fetched for care flow ${pathwayId}, track ${trackId} with ${activityCount} activities (limit is set to 500)`);
    // 2. Find current activity cutoff date
    const activities = (_e = (_d = combinedQuery.careflowActivities) === null || _d === void 0 ? void 0 : _d.activities) !== null && _e !== void 0 ? _e : [];
    const currentActivity = activities.find((activity) => activity.id === currentActivityId);
    const currentActivityDate = (_f = currentActivity === null || currentActivity === void 0 ? void 0 : currentActivity.date) !== null && _f !== void 0 ? _f : '';
    // 3. Filter activities by date cutoff
    const filteredActivities = filterActivitiesByDate(activities, currentActivityDate);
    // 4. Derive steps from the activity stream
    const trackSteps = buildStepSummaries(filteredActivities);
    // 5. Process data points for activities
    const dataPoints = (_h = (_g = combinedQuery.pathwayDataPoints) === null || _g === void 0 ? void 0 : _g.dataPoints) !== null && _h !== void 0 ? _h : [];
    const activityDataPointsMap = createDataPointsMap(dataPoints);
    // 6. Extract activities with forms for processing
    const activitiesWithForms = extractActivitiesWithForms(filteredActivities);
    console.log(`Processing ${activitiesWithForms.length} forms for care flow ${pathwayId}, track ${trackId}`);
    // 7. Process form definitions and responses
    const { formDefinitionsMap, formResponsesMap } = await processFormDefinitionsAndResponses(awellSdk, pathwayId, activitiesWithForms);
    // 8. Try to extract message activities and fetch message content, but continue if it fails
    let messageContentsMap = new Map();
    try {
        const messageActivities = extractMessageActivities(filteredActivities);
        console.log(`Processing ${messageActivities.length} messages for care flow ${pathwayId}, track ${trackId}`);
        messageContentsMap = await fetchMessageContents(awellSdk, messageActivities);
    }
    catch (error) {
        console.error(`Failed to process messages for care flow ${pathwayId}, track ${trackId}: ${error instanceof Error ? error.message : String(error)}`);
        // Log error and continue without message data
    }
    // 9. Finally process steps with activities
    const processedSteps = trackSteps.map((step) => processStep(step, filteredActivities, activityDataPointsMap, formDefinitionsMap, formResponsesMap, messageContentsMap));
    // Log completion time
    const processingTime = Date.now() - startTime;
    console.log(`Completed processing track data for care flow ${pathwayId}, track ${trackId} in ${processingTime}ms`);
    return {
        steps: processedSteps,
    };
};
exports.getTrackData = getTrackData;
/**
 * Fetch all track data in a single combined query
 */
async function fetchAllTrackData(awellSdk, pathwayId, trackId) {
    try {
        console.log(`Starting track data query for care flow ${pathwayId}, track ${trackId} (activity limit: 500)`);
        const queryStartTime = Date.now();
        const result = (await awellSdk.orchestration.query({
            careflowActivities: {
                __args: {
                    pathway_id: pathwayId,
                    filters: { track_id: trackId },
                    pagination: { offset: 0, count: 500 }, // we should never get more than 500 activities, if there is a track with more than I would say it is justified to cut them off
                },
                activities: {
                    id: true,
                    date: true,
                    action: true,
                    status: true,
                    resolution: true,
                    subject: { type: true, name: true },
                    object: {
                        type: true,
                        name: true,
                        id: true, // id field to get message ID
                    },
                    indirect_object: { type: true, name: true },
                    form: {
                        id: true,
                        title: true,
                    },
                    track: { id: true, title: true },
                    label: {
                        text: true,
                    },
                    context: { track_id: true, step_id: true },
                },
            },
            pathwayDataPoints: {
                __args: {
                    pathway_id: pathwayId,
                },
                dataPoints: {
                    id: true,
                    data_set_id: true,
                    key: true,
                    serialized_value: true,
                    data_point_definition_id: true,
                    date: true,
                    valueType: true,
                    activity_id: true,
                },
            },
        }));
        const queryTime = Date.now() - queryStartTime;
        console.log(`Completed track data query for care flow ${pathwayId}, track ${trackId} in ${queryTime}ms`);
        return result;
    }
    catch (error) {
        console.error(`Failed to fetch track data for care flow ${pathwayId}, track ${trackId}: ${error instanceof Error ? error.message : String(error)}`);
        throw error;
    }
}
/**
 * Filter activities by date (keeping those before or equal to current activity date)
 */
function filterActivitiesByDate(activities, currentActivityDate) {
    if ((0, lodash_1.isEmpty)(currentActivityDate)) {
        // If there's no date cutoff, return all activities sorted by date
        return activities.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
    }
    return activities
        .filter((activity) => activity.date <= currentActivityDate)
        .sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
}
function buildStepSummaries(activities) {
    const activitiesByStep = new Map();
    activities.forEach((activity) => {
        var _a, _b;
        const stepId = (_a = activity.context) === null || _a === void 0 ? void 0 : _a.step_id;
        if ((0, lodash_1.isNil)(stepId) || (0, lodash_1.isEmpty)(stepId))
            return;
        if (!activitiesByStep.has(stepId)) {
            activitiesByStep.set(stepId, []);
        }
        (_b = activitiesByStep.get(stepId)) === null || _b === void 0 ? void 0 : _b.push(activity);
    });
    return Array.from(activitiesByStep.entries())
        .map(([stepId, stepActivities]) => {
        var _a, _b, _c, _d, _e, _f;
        const sortedActivities = stepActivities.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
        const stepLifecycleActivities = sortedActivities.filter((activity) => { var _a; return ((_a = activity.object) === null || _a === void 0 ? void 0 : _a.type) === 'STEP'; });
        const firstActivity = sortedActivities[0];
        const firstStepActivity = (_a = stepLifecycleActivities[0]) !== null && _a !== void 0 ? _a : firstActivity;
        const latestStepActivity = (_b = stepLifecycleActivities[stepLifecycleActivities.length - 1]) !== null && _b !== void 0 ? _b : firstActivity;
        const status = deriveStepStatus(latestStepActivity);
        return {
            id: stepId,
            name: ((_c = firstStepActivity.object) === null || _c === void 0 ? void 0 : _c.type) === 'STEP'
                ? firstStepActivity.object.name
                : `Step ${stepId}`,
            label: {
                text: (_e = (_d = firstStepActivity.label) === null || _d === void 0 ? void 0 : _d.text) !== null && _e !== void 0 ? _e : (((_f = firstStepActivity.object) === null || _f === void 0 ? void 0 : _f.type) === 'STEP'
                    ? firstStepActivity.object.name
                    : ''),
            },
            start_date: firstStepActivity.date,
            end_date: isTerminalStepStatus(status) ? latestStepActivity.date : null,
            status,
        };
    })
        .sort((a, b) => {
        var _a, _b;
        return new Date((_a = a.start_date) !== null && _a !== void 0 ? _a : '').getTime() -
            new Date((_b = b.start_date) !== null && _b !== void 0 ? _b : '').getTime();
    });
}
function deriveStepStatus(activity) {
    switch (activity.action) {
        case 'COMPLETE':
            return 'DONE';
        case 'STOPPED':
            return 'STOPPED';
        case 'DISCARDED':
            return 'DISCARDED';
        case 'EXPIRED':
            return 'EXPIRED';
        case 'SKIPPED':
            return 'SKIPPED';
        case 'POSTPONED':
            return 'POSTPONED';
        case 'ACTIVATE':
            return 'ACTIVE';
        case 'SCHEDULED':
            return 'SCHEDULED';
        default:
            return activity.status;
    }
}
function isTerminalStepStatus(status) {
    return ['DONE', 'STOPPED', 'DISCARDED', 'EXPIRED', 'SKIPPED'].includes(status);
}
/**
 * Create a map of activity ID to data points for faster lookup
 */
function createDataPointsMap(dataPoints) {
    const activityDataPointsMap = new Map();
    dataPoints.forEach((dataPoint) => {
        var _a;
        if (typeof dataPoint.activity_id === 'string' &&
            dataPoint.activity_id !== '') {
            if (!activityDataPointsMap.has(dataPoint.activity_id)) {
                activityDataPointsMap.set(dataPoint.activity_id, []);
            }
            // Add enhanced data point with improved title detection
            const enhancedDataPoint = { ...dataPoint };
            // Apply pattern-based mappings for titles for easier LLM reasoning
            if (typeof dataPoint.data_point_definition_id === 'string' &&
                dataPoint.data_point_definition_id !== '') {
                if (dataPoint.data_point_definition_id.endsWith('-ACTIVATION')) {
                    enhancedDataPoint.definitionTitle = 'Activation Date';
                }
                else if (dataPoint.data_point_definition_id.endsWith('-COMPLETION')) {
                    enhancedDataPoint.definitionTitle = 'Completion Date';
                }
            }
            (_a = activityDataPointsMap.get(dataPoint.activity_id)) === null || _a === void 0 ? void 0 : _a.push(enhancedDataPoint);
        }
    });
    return activityDataPointsMap;
}
/**
 * Extract activities with forms for processing
 */
function extractActivitiesWithForms(activities) {
    return activities
        .filter((activity) => { var _a; return typeof ((_a = activity.form) === null || _a === void 0 ? void 0 : _a.id) === 'string' && activity.form.id !== ''; })
        .map((activity) => {
        var _a;
        return ({
            activityId: activity.id,
            formId: (_a = activity.form) === null || _a === void 0 ? void 0 : _a.id,
        });
    });
}
/**
 * Process form definitions and responses in batches
 */
async function processFormDefinitionsAndResponses(awellSdk, pathwayId, activitiesWithForms) {
    const formDefinitionsMap = new Map();
    const formResponsesMap = {};
    if (activitiesWithForms.length === 0) {
        return { formDefinitionsMap, formResponsesMap };
    }
    // Prepare queries in batches to avoid overloading
    const batchSize = 5;
    const batches = [];
    for (let i = 0; i < activitiesWithForms.length; i += batchSize) {
        batches.push(activitiesWithForms.slice(i, i + batchSize));
    }
    for (const batch of batches) {
        await processBatch(awellSdk, pathwayId, batch, formDefinitionsMap, formResponsesMap);
    }
    return { formDefinitionsMap, formResponsesMap };
}
/**
 * Process a batch of form definitions and responses
 */
async function processBatch(awellSdk, pathwayId, batch, formDefinitionsMap, formResponsesMap) {
    try {
        // Create an array of promises for form definitions and responses
        const formDefinitionPromises = batch.map(async ({ formId }) => {
            try {
                // Only fetch form definition if we haven't already
                if (!formDefinitionsMap.has(formId)) {
                    return (await awellSdk.orchestration.query({
                        form: {
                            __args: {
                                id: formId,
                                pathway_id: pathwayId,
                            },
                            form: {
                                id: true,
                                title: true,
                                key: true,
                                definition_id: true,
                                release_id: true,
                                questions: {
                                    id: true,
                                    key: true,
                                    title: true,
                                    userQuestionType: true,
                                    options: {
                                        label: true,
                                        value: true,
                                        value_string: true,
                                    },
                                },
                            },
                        },
                    }));
                }
                return null;
            }
            catch (error) {
                return null;
            }
        });
        const formResponsePromises = batch.map(async ({ activityId }) => {
            try {
                return await awellSdk.orchestration.query({
                    formResponse: {
                        __args: {
                            pathway_id: pathwayId,
                            activity_id: activityId,
                        },
                        response: {
                            answers: {
                                question_id: true,
                                value: true,
                                label: true,
                                value_type: true,
                            },
                        },
                    },
                });
            }
            catch (error) {
                return null;
            }
        });
        // Await all promises in the batch
        const formDefinitionResults = await Promise.all(formDefinitionPromises);
        const formResponseResults = await Promise.all(formResponsePromises);
        // Process form definitions
        formDefinitionResults.forEach((result, index) => {
            if (!(0, lodash_1.isNil)(result) && !(0, lodash_1.isNil)((0, lodash_1.get)(result, 'form.form'))) {
                const { formId } = batch[index];
                formDefinitionsMap.set(formId, (0, lodash_1.get)(result, 'form.form'));
            }
        });
        // Process form responses
        batch.forEach(({ activityId, formId }, index) => {
            const result = formResponseResults[index];
            if (!(0, lodash_1.isNil)(result) && !(0, lodash_1.isNil)((0, lodash_1.get)(result, 'formResponse.response'))) {
                const responseData = result.formResponse.response;
                // Create the map entry if it doesn't exist
                if (!Object.prototype.hasOwnProperty.call(formResponsesMap, formId)) {
                    formResponsesMap[formId] = {};
                }
                const answers = Array.isArray(responseData.answers)
                    ? responseData.answers
                    : [];
                formResponsesMap[formId][activityId] = {
                    activity_id: activityId,
                    form_id: formId,
                    answers: answers,
                };
            }
        });
    }
    catch (error) {
        console.warn(`Partial form data processed for batch of ${batch.length} forms: ${error instanceof Error ? error.message : String(error)}`);
        // Continue with partial data rather than failing completely
    }
}
/**
 * Extract activities that are messages
 */
function extractMessageActivities(activities) {
    const messageActivities = activities
        .filter((activity) => {
        return (!(0, lodash_1.isNil)(activity.object) &&
            typeof activity.object.type === 'string' &&
            activity.object.type === 'MESSAGE' &&
            !(0, lodash_1.isNil)(activity.object.id) &&
            !(0, lodash_1.isEmpty)(activity.object.id));
    })
        .map((activity) => {
        const messageId = activity.object.id;
        // Extract fallback content from activity if available
        let fallbackSubject = '';
        if (!(0, lodash_1.isNil)(activity.object.name) && !(0, lodash_1.isEmpty)(activity.object.name)) {
            fallbackSubject = activity.object.name;
        }
        return {
            activityId: activity.id,
            messageId,
            fallbackSubject,
            fallbackBody: '',
        };
    });
    return messageActivities;
}
/**
 * Fetch message contents for message activities
 */
async function fetchMessageContents(awellSdk, messageActivities) {
    const messageContentsMap = new Map();
    if (messageActivities.length === 0) {
        return messageContentsMap;
    }
    // Process in batches to avoid overloading
    const batchSize = 5;
    const batches = [];
    for (let i = 0; i < messageActivities.length; i += batchSize) {
        batches.push(messageActivities.slice(i, i + batchSize));
    }
    for (const batch of batches) {
        await Promise.all(batch.map(async ({ activityId, messageId, fallbackSubject, fallbackBody }) => {
            try {
                const response = (await awellSdk.orchestration.query({
                    message: {
                        __args: {
                            id: messageId,
                        },
                        message: {
                            id: true,
                            subject: true,
                            body: true,
                        },
                    },
                }));
                if (!(0, lodash_1.isNil)(response) &&
                    !(0, lodash_1.isNil)(response.message) &&
                    !(0, lodash_1.isNil)(response.message.message)) {
                    messageContentsMap.set(activityId, {
                        subject: response.message.message.subject,
                        body: response.message.message.body,
                    });
                }
                else {
                    // Use fallback content if available
                    const hasFallbackSubject = !(0, lodash_1.isNil)(fallbackSubject) && !(0, lodash_1.isEmpty)(fallbackSubject);
                    const hasFallbackBody = !(0, lodash_1.isNil)(fallbackBody) && !(0, lodash_1.isEmpty)(fallbackBody);
                    if (hasFallbackSubject || hasFallbackBody) {
                        messageContentsMap.set(activityId, {
                            subject: fallbackSubject,
                            body: fallbackBody,
                        });
                    }
                }
            }
            catch (error) {
                // Use fallback content if available after error
                const hasFallbackSubject = !(0, lodash_1.isNil)(fallbackSubject) && !(0, lodash_1.isEmpty)(fallbackSubject);
                const hasFallbackBody = !(0, lodash_1.isNil)(fallbackBody) && !(0, lodash_1.isEmpty)(fallbackBody);
                if (hasFallbackSubject || hasFallbackBody) {
                    messageContentsMap.set(activityId, {
                        subject: fallbackSubject,
                        body: fallbackBody,
                    });
                }
            }
        }));
    }
    return messageContentsMap;
}
/**
 * Process a step with its activities
 */
function processStep(step, trackActivities, activityDataPointsMap, formDefinitionsMap, formResponsesMap, messageContentsMap) {
    var _a;
    const stepActivities = trackActivities
        .filter((activity) => { var _a; return ((_a = activity.context) === null || _a === void 0 ? void 0 : _a.step_id) === step.id; })
        .map((activity) => processActivity(activity, activityDataPointsMap, formDefinitionsMap, formResponsesMap, messageContentsMap));
    return {
        name: step.name,
        label: typeof ((_a = step.label) === null || _a === void 0 ? void 0 : _a.text) === 'string' ? step.label.text : '',
        status: step.status,
        start_date: step.start_date,
        end_date: step.end_date,
        activities: stepActivities,
    };
}
/**
 * Process an activity with its form and data points
 */
function processActivity(activity, activityDataPointsMap, formDefinitionsMap, formResponsesMap, messageContentsMap) {
    var _a, _b, _c, _d;
    const baseActivity = {
        date: activity.date,
        action: activity.action,
        status: activity.status,
        resolution: activity.resolution,
        subject: {
            type: activity.subject.type,
            name: activity.subject.name,
        },
        object: {
            type: activity.object.type,
            name: activity.object.name,
        },
    };
    // Add indirect object if it exists
    if (!(0, lodash_1.isNil)((_a = activity.indirect_object) === null || _a === void 0 ? void 0 : _a.type) &&
        !(0, lodash_1.isEmpty)((_b = activity.indirect_object) === null || _b === void 0 ? void 0 : _b.type) &&
        !(0, lodash_1.isNil)((_c = activity.indirect_object) === null || _c === void 0 ? void 0 : _c.name) &&
        !(0, lodash_1.isEmpty)((_d = activity.indirect_object) === null || _d === void 0 ? void 0 : _d.name)) {
        baseActivity.indirect_object = {
            type: activity.indirect_object.type,
            name: activity.indirect_object.name,
        };
    }
    // Process message if exists
    processActivityMessage(baseActivity, activity, messageContentsMap);
    // Process form if exists
    processActivityForm(baseActivity, activity, formDefinitionsMap, formResponsesMap);
    // Process data points
    processActivityDataPoints(baseActivity, activity, activityDataPointsMap);
    return baseActivity;
}
/**
 * Process message for an activity
 */
function processActivityMessage(baseActivity, activity, messageContentsMap) {
    if (messageContentsMap.has(activity.id)) {
        const messageContent = messageContentsMap.get(activity.id);
        if (!(0, lodash_1.isNil)(messageContent)) {
            baseActivity.message = {
                subject: messageContent.subject,
                body: messageContent.body,
            };
        }
    }
}
/**
 * Process form for an activity
 */
function processActivityForm(baseActivity, activity, formDefinitionsMap, formResponsesMap) {
    if ((0, lodash_1.isNil)(activity.form) ||
        (0, lodash_1.isNil)(activity.form.id) ||
        activity.form.id === '') {
        return;
    }
    const formId = activity.form.id;
    const formTitle = typeof activity.form.title === 'string' && !(0, lodash_1.isEmpty)(activity.form.title)
        ? activity.form.title
        : 'Untitled Form';
    // Check if we have form responses for this activity
    const hasFormResponses = Object.prototype.hasOwnProperty.call(formResponsesMap, formId) &&
        Object.prototype.hasOwnProperty.call(formResponsesMap[formId], activity.id);
    if (hasFormResponses) {
        const formResponseData = formResponsesMap[formId][activity.id];
        const formDefinition = formDefinitionsMap.get(formId);
        // Create form with responses
        if (!(0, lodash_1.isNil)(formDefinition)) {
            // Map questions to their answers with proper formatting
            baseActivity.form = {
                title: formTitle,
                questions: formResponseData.answers.map((answer) => {
                    var _a, _b, _c, _d;
                    const question = formDefinition.questions.find((q) => q.id === answer.question_id);
                    let responseText = (_a = answer.value) !== null && _a !== void 0 ? _a : 'No response';
                    // Handle different question types
                    if (!(0, lodash_1.isNil)(question)) {
                        if (question.userQuestionType === 'YES_NO') {
                            responseText =
                                responseText === '1'
                                    ? 'Yes'
                                    : responseText === '0'
                                        ? 'No'
                                        : 'No response';
                        }
                        else if (question.userQuestionType === 'MULTIPLE_CHOICE' &&
                            typeof answer.value === 'string' &&
                            !(0, lodash_1.isEmpty)(answer.value)) {
                            const option = (_b = question.options) === null || _b === void 0 ? void 0 : _b.find((opt) => opt.value_string === answer.value);
                            responseText = (_c = option === null || option === void 0 ? void 0 : option.label) !== null && _c !== void 0 ? _c : responseText;
                        }
                    }
                    return {
                        title: (_d = question === null || question === void 0 ? void 0 : question.title) !== null && _d !== void 0 ? _d : answer.question_id,
                        response: responseText,
                    };
                }),
            };
        }
        else {
            // Fallback if form definition not available
            baseActivity.form = {
                title: formTitle,
                questions: formResponseData.answers.map((answer) => {
                    var _a;
                    return ({
                        title: answer.question_id,
                        response: (_a = answer.value) !== null && _a !== void 0 ? _a : 'No response',
                    });
                }),
            };
        }
    }
    else {
        // Just include the form title if no responses
        baseActivity.form = {
            title: formTitle,
        };
    }
}
/**
 * Process data points for an activity
 */
function processActivityDataPoints(baseActivity, activity, activityDataPointsMap) {
    const activityDataPoints = activityDataPointsMap.get(activity.id);
    if ((0, lodash_1.isNil)(activityDataPoints) || activityDataPoints.length === 0) {
        return;
    }
    // Determine which data points to include
    let dataPointsToInclude = [];
    const hasForm = !(0, lodash_1.isNil)(activity.form) && !(0, lodash_1.isEmpty)((0, lodash_1.get)(activity.form, 'id'));
    if (hasForm) {
        // If this activity has a form, only include activation data points, as questions/answers will be processed separately
        dataPointsToInclude = activityDataPoints.filter((dp) => !(0, lodash_1.isNil)(dp) &&
            !(0, lodash_1.isNil)(dp.data_point_definition_id) &&
            dp.data_point_definition_id.endsWith('-ACTIVATION'));
    }
    else {
        // If this activity doesn't have a form, include all data points
        dataPointsToInclude = activityDataPoints;
    }
    if (dataPointsToInclude.length > 0) {
        baseActivity.data_points = dataPointsToInclude.map((dp) => ({
            value: dp.serialized_value,
            title: !(0, lodash_1.isNil)(dp.definitionTitle)
                ? dp.definitionTitle
                : !(0, lodash_1.isNil)(dp.key)
                    ? dp.key
                    : dp.data_point_definition_id,
            date: dp.date,
        }));
    }
}
//# sourceMappingURL=getTrackData.js.map