"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
function _export(target, all) {
    for(var name in all)Object.defineProperty(target, name, {
        enumerable: true,
        get: Object.getOwnPropertyDescriptor(all, name).get
    });
}
_export(exports, {
    get normalizeQuestionConfigForImport () {
        return normalizeQuestionConfigForImport;
    },
    get programDefinitionSchema () {
        return programDefinitionSchema;
    },
    get sanitizeProgramDefinitionPreview () {
        return sanitizeProgramDefinitionPreview;
    },
    get saveProgramDefinition () {
        return saveProgramDefinition;
    }
});
const _zod = require("zod");
const _constants = require("@tamanu/constants");
const _logging = require("@tamanu/shared/services/logging");
const _importRows = require("../importer/importRows");
const _idify = require("./idify");
const _programDataElementValidation = require("./programDataElementValidation");
const _validation = require("./validation");
const DATA_ELEMENT_TYPES_REQUIRING_OPTIONS = [
    _constants.PROGRAM_DATA_ELEMENT_TYPES.SELECT,
    _constants.PROGRAM_DATA_ELEMENT_TYPES.RADIO,
    _constants.PROGRAM_DATA_ELEMENT_TYPES.MULTI_SELECT
];
const DATA_ELEMENT_TYPES_WITH_NORMAL_RANGE = [
    _constants.PROGRAM_DATA_ELEMENT_TYPES.CALCULATED,
    _constants.PROGRAM_DATA_ELEMENT_TYPES.NUMBER,
    _constants.PROGRAM_DATA_ELEMENT_TYPES.RESULT
];
const PATIENT_ISSUE_TYPE_VALUES = Object.values(_constants.PATIENT_ISSUE_TYPES);
const SUPPORTED_CONFIG_KEYS_BY_QUESTION_TYPE = {
    [_constants.PROGRAM_DATA_ELEMENT_TYPES.AUTOCOMPLETE]: [
        'scope',
        'source',
        'where'
    ],
    [_constants.PROGRAM_DATA_ELEMENT_TYPES.CALCULATED]: [
        'rounding',
        'unit'
    ],
    [_constants.PROGRAM_DATA_ELEMENT_TYPES.NUMBER]: [
        'rounding',
        'unit'
    ],
    [_constants.PROGRAM_DATA_ELEMENT_TYPES.PATIENT_DATA]: [
        'column',
        'source',
        'where',
        'writeToPatient'
    ],
    [_constants.PROGRAM_DATA_ELEMENT_TYPES.PATIENT_ISSUE]: [
        'issueNote',
        'issueType'
    ],
    [_constants.PROGRAM_DATA_ELEMENT_TYPES.RESULT]: [
        'rounding',
        'unit'
    ],
    [_constants.PROGRAM_DATA_ELEMENT_TYPES.SURVEY_ANSWER]: [
        'source'
    ],
    [_constants.PROGRAM_DATA_ELEMENT_TYPES.SURVEY_LINK]: [
        'source'
    ],
    [_constants.PROGRAM_DATA_ELEMENT_TYPES.SURVEY_RESULT]: [
        'source'
    ],
    [_constants.PROGRAM_DATA_ELEMENT_TYPES.USER_DATA]: [
        'column'
    ]
};
const SUPPORTED_WRITE_TO_PATIENT_KEYS = [
    'fieldName',
    'fieldType'
];
const normalizeOptions = (options)=>{
    if (!options) return [];
    if (typeof options === 'string') {
        return options.split(',').map((option)=>option.trim()).filter(Boolean);
    }
    if (Array.isArray(options)) return options;
    return Object.values(options);
};
const hasOptions = (options)=>normalizeOptions(options).length > 0;
const parseObjectField = (value)=>{
    if (!value) return null;
    if (typeof value === 'string') {
        try {
            const parsedValue = JSON.parse(value);
            return parsedValue && typeof parsedValue === 'object' && !Array.isArray(parsedValue) ? parsedValue : null;
        } catch  {
            return null;
        }
    }
    return typeof value === 'object' && !Array.isArray(value) ? value : null;
};
const pickKnownKeys = (object, keys)=>Object.fromEntries(Object.entries(object).filter(([key])=>keys.includes(key)));
const normalizeCalculation = (calculation)=>typeof calculation === 'string' ? calculation.trim().replace(/^=\s*/, '') : calculation;
const sanitizeQuestionConfig = (question)=>{
    const config = parseObjectField(question.config);
    if (!config) return question;
    const sanitizedQuestion = {
        ...question
    };
    const normalRange = config.normalRange;
    if (normalRange && DATA_ELEMENT_TYPES_WITH_NORMAL_RANGE.includes(question.type)) {
        const validationCriteria = parseObjectField(question.validationCriteria) || {};
        sanitizedQuestion.validationCriteria = validationCriteria.normalRange ? validationCriteria : {
            ...validationCriteria,
            normalRange
        };
    }
    const supportedKeys = SUPPORTED_CONFIG_KEYS_BY_QUESTION_TYPE[question.type] || [];
    const sanitizedConfig = pickKnownKeys(config, supportedKeys);
    if (sanitizedConfig.writeToPatient && typeof sanitizedConfig.writeToPatient === 'object' && !Array.isArray(sanitizedConfig.writeToPatient)) {
        sanitizedConfig.writeToPatient = pickKnownKeys(sanitizedConfig.writeToPatient, SUPPORTED_WRITE_TO_PATIENT_KEYS);
    }
    if (Object.keys(sanitizedConfig).length === 0) {
        delete sanitizedQuestion.config;
        return sanitizedQuestion;
    }
    return {
        ...sanitizedQuestion,
        config: sanitizedConfig
    };
};
const sanitizeSurveyMetadata = (survey)=>{
    if (survey.status !== 'draft') return survey;
    const sanitizedSurvey = {
        ...survey
    };
    delete sanitizedSurvey.status;
    return sanitizedSurvey;
};
const sanitizeQuestion = (question)=>{
    const sanitizedQuestion = sanitizeQuestionConfig(question);
    const calculation = normalizeCalculation(sanitizedQuestion.calculation);
    return calculation == null ? sanitizedQuestion : {
        ...sanitizedQuestion,
        calculation
    };
};
const sanitizeProgramDefinitionPreview = (programDefinition)=>({
        ...programDefinition,
        surveys: programDefinition.surveys.map(sanitizeSurveyMetadata),
        surveySheets: programDefinition.surveySheets.map((surveySheet)=>({
                ...surveySheet,
                questions: surveySheet.questions.map(sanitizeQuestion)
            }))
    });
const programDefinitionQuestionSchema = _zod.z.object({
    code: _zod.z.string().trim().min(1),
    // Required: the saved ProgramDataElement.name powers the form response
    // viewer's "Indicator" column. The build prompt instructs the AI to derive
    // it from the source label or fall back to the question text, never the
    // code — this constraint stops the LLM silently omitting it.
    name: _zod.z.string().trim().min(1),
    text: _zod.z.string().trim().min(1),
    type: _zod.z.string().trim().min(1),
    options: _zod.z.union([
        _zod.z.string(),
        _zod.z.array(_zod.z.string()),
        _zod.z.record(_zod.z.string(), _zod.z.string())
    ]).optional(),
    newScreen: _zod.z.boolean().optional(),
    visibilityCriteria: _zod.z.union([
        _zod.z.string(),
        _zod.z.record(_zod.z.string(), _zod.z.unknown())
    ]).optional(),
    validationCriteria: _zod.z.union([
        _zod.z.string(),
        _zod.z.record(_zod.z.string(), _zod.z.unknown())
    ]).optional(),
    detail: _zod.z.string().optional(),
    config: _zod.z.union([
        _zod.z.string(),
        _zod.z.record(_zod.z.string(), _zod.z.unknown())
    ]).optional(),
    calculation: _zod.z.string().optional(),
    visibilityStatus: _zod.z.string().trim().optional(),
    visualisationConfig: _zod.z.union([
        _zod.z.string(),
        _zod.z.record(_zod.z.string(), _zod.z.unknown())
    ]).optional()
}).superRefine((question, ctx)=>{
    if (DATA_ELEMENT_TYPES_REQUIRING_OPTIONS.includes(question.type) && !hasOptions(question.options)) {
        ctx.addIssue({
            code: _zod.z.ZodIssueCode.custom,
            path: [
                'options'
            ],
            message: `${question.type} questions must include options`
        });
    }
});
const surveyMetadataSchema = _zod.z.object({
    code: _zod.z.string().trim().min(1),
    name: _zod.z.string().trim().min(1),
    surveyType: _zod.z.string().trim().optional(),
    status: _zod.z.string().trim().optional(),
    isSensitive: _zod.z.boolean().optional(),
    notifiable: _zod.z.boolean().optional(),
    notifyEmailAddresses: _zod.z.array(_zod.z.string().trim()).optional(),
    visibilityCriteria: _zod.z.union([
        _zod.z.string(),
        _zod.z.record(_zod.z.string(), _zod.z.unknown())
    ]).optional(),
    visibilityStatus: _zod.z.string().trim().optional()
});
const surveySheetSchema = _zod.z.object({
    surveyName: _zod.z.string().trim().min(1),
    questions: _zod.z.array(programDefinitionQuestionSchema).min(1)
});
const programDefinitionSchema = _zod.z.object({
    title: _zod.z.string().trim().min(1),
    programCode: _zod.z.string().trim().optional(),
    programName: _zod.z.string().trim().optional(),
    surveys: _zod.z.array(surveyMetadataSchema).min(1),
    surveySheets: _zod.z.array(surveySheetSchema).min(1)
}).superRefine((programDefinition, ctx)=>{
    const sheetNames = new Set(programDefinition.surveySheets.map(({ surveyName })=>surveyName));
    for (const [index, survey] of programDefinition.surveys.entries()){
        if (!sheetNames.has(survey.name)) {
            ctx.addIssue({
                code: _zod.z.ZodIssueCode.custom,
                path: [
                    'surveys',
                    index,
                    'name'
                ],
                message: `Survey "${survey.name}" must have a matching surveySheet`
            });
        }
    }
});
const createCode = (value)=>(0, _idify.idify)(value) || 'generatedform';
const getAvailableSurveyCode = async (Survey, baseCode)=>{
    let code = baseCode;
    let suffix = 1;
    while(await Survey.findOne({
        where: {
            code
        },
        attributes: [
            'id'
        ]
    })){
        suffix += 1;
        code = `${baseCode}${suffix}`;
    }
    return code;
};
const replaceQuestionReferences = (value, sortedQuestionCodeEntries)=>{
    if (!value) return value;
    let stringValue = typeof value === 'string' ? value : JSON.stringify(value);
    for (const [oldCode, newCode] of sortedQuestionCodeEntries){
        stringValue = stringValue.replaceAll(`pde-${oldCode}`, `pde-${newCode}`);
    }
    return stringValue;
};
const stringifyField = (value, sortedQuestionCodeEntries = [])=>{
    if (!value) return '';
    return replaceQuestionReferences(value, sortedQuestionCodeEntries);
};
const parseConfig = (config)=>{
    if (!config || typeof config !== 'string') return config;
    try {
        return JSON.parse(config);
    } catch  {
        return config;
    }
};
const normalizePatientIssueType = (issueType)=>{
    if (PATIENT_ISSUE_TYPE_VALUES.includes(issueType)) return issueType;
    return /urgent|warning|alert|risk|danger|emergency/i.test(issueType || '') ? _constants.PATIENT_ISSUE_TYPES.WARNING : _constants.PATIENT_ISSUE_TYPES.ISSUE;
};
const normalizeQuestionConfigForImport = (question)=>{
    if (question.type !== _constants.PROGRAM_DATA_ELEMENT_TYPES.PATIENT_ISSUE) return question.config;
    const config = parseConfig(question.config);
    if (!config || typeof config !== 'object' || Array.isArray(config)) return question.config;
    return {
        ...config,
        issueType: normalizePatientIssueType(config.issueType)
    };
};
const createSurveyImportRows = ({ programId, surveyDefinition, surveySheet, surveyCode })=>{
    const surveyId = `${programId}-${surveyCode}`;
    const originalSurveyCode = createCode(surveyDefinition.code);
    const questionCodeMap = new Map(surveySheet.questions.map((question, questionIndex)=>{
        const originalQuestionCode = createCode(question.code);
        const questionCode = originalQuestionCode.startsWith(originalSurveyCode) ? `${surveyCode}${originalQuestionCode.slice(originalSurveyCode.length)}` : `${surveyCode}${String(questionIndex + 1).padStart(3, '0')}`;
        return [
            originalQuestionCode,
            questionCode
        ];
    }));
    const sortedQuestionCodeEntries = [
        ...questionCodeMap.entries()
    ].sort(([oldCodeA], [oldCodeB])=>oldCodeB.length - oldCodeA.length);
    const rows = [
        {
            model: 'Survey',
            sheetRow: -2,
            values: {
                id: surveyId,
                code: surveyCode,
                name: surveyDefinition.name,
                sheetName: surveyDefinition.name,
                rowIndex: -2,
                surveyType: surveyDefinition.surveyType || _constants.SURVEY_TYPES.PROGRAMS,
                isSensitive: surveyDefinition.isSensitive || false,
                programId,
                visibilityCriteria: stringifyField(surveyDefinition.visibilityCriteria, sortedQuestionCodeEntries),
                visibilityStatus: surveyDefinition.visibilityStatus || _constants.VISIBILITY_STATUSES.CURRENT,
                notifiable: surveyDefinition.notifiable || false,
                notifyEmailAddresses: surveyDefinition.notifyEmailAddresses || []
            }
        }
    ];
    let screenIndex = -1;
    let componentIndex = 0;
    for (const [questionIndex, question] of surveySheet.questions.entries()){
        if (questionIndex === 0 || question.newScreen) {
            screenIndex += 1;
            componentIndex = 0;
        }
        const questionCode = questionCodeMap.get(createCode(question.code));
        const dataElementId = `pde-${questionCode}`;
        const type = question.type || _constants.PROGRAM_DATA_ELEMENT_TYPES.TEXT;
        const defaultOptions = DATA_ELEMENT_TYPES_REQUIRING_OPTIONS.includes(type) ? JSON.stringify(normalizeOptions(question.options)) : '';
        const questionConfig = normalizeQuestionConfigForImport({
            ...question,
            type
        });
        rows.push({
            model: 'ProgramDataElement',
            sheetRow: questionIndex,
            values: {
                id: dataElementId,
                code: questionCode,
                name: question.name || questionCode,
                type,
                defaultText: question.text,
                defaultOptions,
                visualisationConfig: stringifyField(question.visualisationConfig, sortedQuestionCodeEntries)
            }
        }, {
            model: 'SurveyScreenComponent',
            sheetRow: questionIndex,
            values: {
                id: `${surveyId}-${questionCode}`,
                surveyId,
                dataElementId,
                screenIndex,
                componentIndex,
                text: '',
                options: '',
                visibilityCriteria: stringifyField(question.visibilityCriteria, sortedQuestionCodeEntries),
                validationCriteria: stringifyField(question.validationCriteria, sortedQuestionCodeEntries),
                detail: question.detail || '',
                config: stringifyField(questionConfig, sortedQuestionCodeEntries),
                calculation: stringifyField(question.calculation, sortedQuestionCodeEntries),
                visibilityStatus: question.visibilityStatus || _constants.VISIBILITY_STATUSES.CURRENT,
                type
            }
        });
        componentIndex += 1;
    }
    return {
        rows,
        surveyId,
        surveyInfo: rows[0].values
    };
};
const validateSurvey = async ({ context, rows, surveyInfo })=>{
    if (surveyInfo.surveyType === _constants.SURVEY_TYPES.VITALS) {
        await (0, _validation.validateVitalsSurvey)(context, surveyInfo);
    }
    if (_constants.CHARTING_SURVEY_TYPES.includes(surveyInfo.surveyType)) {
        await (0, _validation.validateChartingSurvey)(context, surveyInfo);
    }
    (0, _validation.ensureRequiredQuestionsPresent)(surveyInfo, rows);
    return (0, _programDataElementValidation.validateProgramDataElementRecords)(rows, {
        context,
        sheetName: surveyInfo.name,
        surveyType: surveyInfo.surveyType
    });
};
const saveProgramDefinition = async ({ db, models, programId, programDefinition })=>{
    const errors = [];
    const context = {
        errors,
        log: _logging.log.child({
            importer: 'programDefinition'
        }),
        models
    };
    const customPatientFieldIds = (await models.PatientFieldDefinition.findAll({
        attributes: [
            'id'
        ]
    })).map((field)=>field.id);
    const surveyIds = [];
    await db.transaction(async ()=>{
        for (const surveyDefinition of programDefinition.surveys){
            const surveySheet = programDefinition.surveySheets.find(({ surveyName })=>surveyName === surveyDefinition.name);
            const surveyCode = await getAvailableSurveyCode(models.Survey, createCode(surveyDefinition.code));
            const { rows, surveyId, surveyInfo } = createSurveyImportRows({
                programId,
                surveyCode,
                surveyDefinition,
                surveySheet
            });
            const stats = await validateSurvey({
                context,
                rows,
                surveyInfo
            });
            if (errors.length > 0) throw errors[0];
            await (0, _importRows.importRows)(context, {
                rows,
                sheetName: surveyInfo.name,
                stats
            }, {
                models,
                programId,
                customPatientFieldIds
            });
            if (errors.length > 0) throw errors[0];
            surveyIds.push(surveyId);
        }
    });
    return models.Survey.findAll({
        where: {
            id: surveyIds
        }
    });
};

//# sourceMappingURL=programDefinition.js.map