"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 saveChangesForModel () {
        return saveChangesForModel;
    },
    get saveIncomingChanges () {
        return saveIncomingChanges;
    }
});
const _config = /*#__PURE__*/ _interop_require_default(require("config"));
const _sleepAsync = require("@tamanu/utils/sleepAsync");
const _log = require("@tamanu/shared/services/logging/log");
const _sortInDependencyOrder = require("../utils/sortInDependencyOrder");
const _findSyncSnapshotRecords = require("./findSyncSnapshotRecords");
const _countSyncSnapshotRecords = require("./countSyncSnapshotRecords");
const _constants = require("./constants");
const _saveChanges = require("./saveChanges");
const _extractChangelogFromSnapshotRecords = require("../utils/audit/extractChangelogFromSnapshotRecords");
const _insertChangelogRecords = require("../utils/audit/insertChangelogRecords");
function _interop_require_default(obj) {
    return obj && obj.__esModule ? obj : {
        default: obj
    };
}
const { persistedCacheBatchSize, pauseBetweenPersistedCacheBatchesInMilliseconds } = _config.default.sync;
const saveChangesForModel = async (model, changes, isCentralServer, log)=>{
    const sanitizeData = (d)=>isCentralServer ? model.sanitizeForCentralServer(d) : model.sanitizeForFacilityServer(d);
    // split changes into create, update, delete
    const incomingRecords = changes.filter((c)=>c.data.id).map((c)=>c.data);
    const idsForIncomingRecords = incomingRecords.map((r)=>r.id);
    // add all records that already exist in the db to the list to be updated
    // even if they are being deleted or restored, we should also run an update query to keep the data in sync
    const existingRecords = (await model.findByIds(idsForIncomingRecords, false)).map((r)=>r.get({
            plain: true
        }));
    const idToExistingRecord = Object.fromEntries(existingRecords.map((e)=>[
            e.id,
            e
        ]));
    // follow the same pattern for incoming records
    // https://github.com/beyondessential/tamanu/pull/4854#discussion_r1403828225
    const idToIncomingRecord = Object.fromEntries(changes.filter((c)=>c.data.id).map((e)=>[
            e.data.id,
            e
        ]));
    const idsForUpdate = new Set();
    const idsForRestore = new Set();
    const idsForDelete = new Set();
    existingRecords.forEach((existing)=>{
        // compares incoming and existing records by id
        const incoming = idToIncomingRecord[existing.id];
        idsForUpdate.add(existing.id);
        // Restores only originate from central server
        if (isCentralServer === false && existing.deletedAt && !incoming?.isDeleted) {
            idsForRestore.add(existing.id);
        }
        if (!existing.deletedAt && incoming?.isDeleted) {
            idsForDelete.add(existing.id);
        }
        if (existing.deletedAt && incoming?.isDeleted) {
        // don't do anything related to deletion if incoming record
        // is deleted and existing record is already deleted
        }
    });
    const recordsForCreate = changes.filter((c)=>idToExistingRecord[c.data.id] === undefined).map(({ data, isDeleted })=>{
        // validateRecord(data, null); TODO add in validation
        // pass in 'isDeleted' to be able to create new records even if they are soft deleted.
        return {
            ...sanitizeData(data),
            isDeleted
        };
    });
    const recordsForUpdate = changes.filter((r)=>idsForUpdate.has(r.data.id)).map(({ data })=>{
        // validateRecord(data, null); TODO add in validation
        return sanitizeData(data);
    });
    const recordsForRestore = changes.filter((r)=>idsForRestore.has(r.data.id)).map(({ data })=>{
        // validateRecord(data, null); TODO add in validation
        return sanitizeData(data);
    });
    const recordsForDelete = changes.filter((r)=>idsForDelete.has(r.data.id)).map(({ data })=>{
        // validateRecord(data, null); TODO add in validation
        return sanitizeData(data);
    });
    // run each import process
    log.debug('Sync: saveIncomingChanges: Creating new records', {
        count: recordsForCreate.length
    });
    if (recordsForCreate.length > 0) {
        await (0, _saveChanges.saveCreates)(model, recordsForCreate);
    }
    log.debug('Sync: saveIncomingChanges: Updating existing records', {
        count: recordsForUpdate.length
    });
    if (recordsForUpdate.length > 0) {
        await (0, _saveChanges.saveUpdates)(model, recordsForUpdate, idToExistingRecord, isCentralServer);
    }
    log.debug('Sync: saveIncomingChanges: Soft deleting old records', {
        count: recordsForDelete.length
    });
    if (recordsForDelete.length > 0) {
        await (0, _saveChanges.saveDeletes)(model, recordsForDelete);
    }
    log.debug('Sync: saveIncomingChanges: Restoring deleted records', {
        count: recordsForRestore.length
    });
    if (recordsForRestore.length > 0) {
        await (0, _saveChanges.saveRestores)(model, recordsForRestore);
    }
};
const saveChangesForModelInBatches = async (model, sequelize, sessionId, recordType, isCentralServer, log)=>{
    const syncRecordsCount = await (0, _countSyncSnapshotRecords.countSyncSnapshotRecords)(sequelize, sessionId, _constants.SYNC_SESSION_DIRECTION.INCOMING, model.tableName);
    const batchCount = Math.ceil(syncRecordsCount / persistedCacheBatchSize);
    log.debug('Sync: saveIncomingChanges', {
        total: syncRecordsCount,
        batch: batchCount,
        pauseMs: pauseBetweenPersistedCacheBatchesInMilliseconds
    });
    let fromId;
    for(let batchIndex = 0; batchIndex < batchCount; batchIndex++){
        const batchRecords = await (0, _findSyncSnapshotRecords.findSyncSnapshotRecords)({
            sequelize
        }, sessionId, _constants.SYNC_SESSION_DIRECTION.INCOMING, fromId, persistedCacheBatchSize, recordType);
        fromId = batchRecords[batchRecords.length - 1]?.id;
        try {
            log.info('Sync: Persisting cache to table', {
                count: batchRecords.length,
                total: syncRecordsCount
            });
            const { snapshotRecords, changelogRecords } = (0, _extractChangelogFromSnapshotRecords.extractChangelogFromSnapshotRecords)(batchRecords);
            await saveChangesForModel(model, snapshotRecords, isCentralServer, log);
            await (0, _insertChangelogRecords.insertChangelogRecords)(model.sequelize.models, changelogRecords);
            await (0, _sleepAsync.sleepAsync)(pauseBetweenPersistedCacheBatchesInMilliseconds);
        } catch (error) {
            log.error('Failed to save changes');
            throw error;
        }
    }
};
const saveIncomingChanges = async (sequelize, pulledModels, sessionId, isCentralServer = false)=>{
    const sortedModels = (0, _sortInDependencyOrder.sortInDependencyOrder)(pulledModels);
    for (const [i, model] of sortedModels.entries()){
        await saveChangesForModelInBatches(model, sequelize, sessionId, model.tableName, isCentralServer, _log.log.child({
            sessionId,
            table: model.tableName,
            nthTable: `${i + 1}/${sortedModels.length}`
        }));
    }
};

//# sourceMappingURL=saveIncomingChanges.js.map