"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 pullIncomingChanges () {
        return pullIncomingChanges;
    },
    get streamIncomingChanges () {
        return streamIncomingChanges;
    }
});
const _config = /*#__PURE__*/ _interop_require_default(require("config"));
const _lodash = require("lodash");
const _logging = require("@tamanu/shared/services/logging");
const _constants = require("@tamanu/constants");
const _sync = require("@tamanu/database/sync");
const _sleepAsync = require("@tamanu/utils/sleepAsync");
const _calculatePageLimit = require("./calculatePageLimit");
function _interop_require_default(obj) {
    return obj && obj.__esModule ? obj : {
        default: obj
    };
}
const { persistedCacheBatchSize, pauseBetweenCacheBatchInMilliseconds } = _config.default.sync;
const pullIncomingChanges = async (centralServer, sequelize, sessionId, since)=>{
    const start = Date.now();
    // initiating pull also returns the sync tick (or point on the sync timeline) that the
    // central server considers this session will be up to after pulling all changes
    _logging.log.info('FacilitySyncManager.pull.waitingForCentral', {
        mode: 'polling'
    });
    const { totalToPull, pullUntil } = await centralServer.initiatePull(sessionId, since);
    _logging.log.info('FacilitySyncManager.pulling', {
        since,
        totalToPull
    });
    let fromId;
    let limit = (0, _calculatePageLimit.calculatePageLimit)();
    let totalPulled = 0;
    // pull changes a page at a time
    while(totalPulled < totalToPull){
        _logging.log.debug('FacilitySyncManager.pull.pullingPage', {
            fromId,
            limit
        });
        const startTime = Date.now();
        const records = await centralServer.pull(sessionId, {
            fromId,
            limit
        });
        const { id, sortOrder } = records[records.length - 1];
        fromId = btoa(JSON.stringify({
            sortOrder,
            id
        }));
        totalPulled += records.length;
        const pullTime = Date.now() - startTime;
        if (!records.length) {
            _logging.log.debug(`FacilitySyncManager.pull.noMoreChanges`);
            break;
        }
        _logging.log.info('FacilitySyncManager.savingChangesToSnapshot', {
            count: records.length
        });
        const recordsToSave = records.map((r)=>{
            delete r.sortOrder;
            return {
                ...r,
                data: {
                    ...r.data,
                    updatedAtSyncTick: _sync.SYNC_TICK_FLAGS.INCOMING_FROM_CENTRAL_SERVER
                },
                direction: _sync.SYNC_SESSION_DIRECTION.INCOMING
            };
        });
        // This is an attempt to avoid storing all the pulled data
        // in the memory because we might run into memory issue when:
        // 1. During the first sync when there is a lot of data to load
        // 2. When a huge number of data is imported to sync and the facility syncs it down
        // So store the data in a sync snapshot table instead and will persist it to the actual tables later
        for (const batchOfRows of (0, _lodash.chunk)(recordsToSave, persistedCacheBatchSize)){
            await (0, _sync.insertSnapshotRecords)(sequelize, sessionId, batchOfRows);
            await (0, _sleepAsync.sleepAsync)(pauseBetweenCacheBatchInMilliseconds);
        }
        limit = (0, _calculatePageLimit.calculatePageLimit)(limit, pullTime);
    }
    _logging.log.info('FacilitySyncManager.pulled', {
        durationMs: Date.now() - start
    });
    return {
        totalPulled: totalToPull,
        pullUntil
    };
};
const streamIncomingChanges = async (centralServer, sequelize, sessionId, since)=>{
    const start = Date.now();
    // initiating pull also returns the sync tick (or point on the sync timeline) that the
    // central server considers this session will be up to after pulling all changes
    _logging.log.info('FacilitySyncManager.pull.waitingForCentral', {
        mode: 'streaming'
    });
    const { totalToPull, pullUntil } = await centralServer.initiatePull(sessionId, since);
    const WRITE_BATCH_SIZE = Math.min(persistedCacheBatchSize, totalToPull);
    const writeBatch = async (records)=>{
        if (records.length === 0) return;
        await (0, _sync.insertSnapshotRecords)(sequelize, sessionId, records.map((r)=>({
                ...r,
                // mark as never updated, so we don't push it back to the central server until the next local update
                data: {
                    ...r.data,
                    updatedAtSyncTick: _sync.SYNC_TICK_FLAGS.INCOMING_FROM_CENTRAL_SERVER
                },
                direction: _sync.SYNC_SESSION_DIRECTION.INCOMING
            })));
    };
    _logging.log.info('FacilitySyncManager.pulling', {
        since,
        totalToPull
    });
    let totalPulled = 0; // statistics
    let records = []; // for batching writes
    let writes = []; // ongoing write promises
    // keep track of the ID we're on so we can resume the stream
    // on disconnect from where we left off rather than the start
    let fromId;
    const endpointFn = ()=>({
            endpoint: `sync/${sessionId}/pull/stream`,
            query: {
                fromId
            }
        });
    stream: for await (const { kind, message } of centralServer.stream(endpointFn)){
        if (records.length >= WRITE_BATCH_SIZE) {
            // do writes in the background while we're continuing to stream data
            writes.push(writeBatch(records));
            records = [];
        }
        handler: switch(kind){
            case _constants.SYNC_STREAM_MESSAGE_KIND.PULL_CHANGE:
                records.push(message);
                totalPulled += 1;
                fromId = message.id;
                break handler;
            case _constants.SYNC_STREAM_MESSAGE_KIND.END:
                _logging.log.debug(`FacilitySyncManager.pull.noMoreChanges`);
                break stream;
            default:
                _logging.log.warn('FacilitySyncManager.pull.unknownMessageKind', {
                    kind
                });
        }
    }
    if (records.length > 0) {
        writes.push(writeBatch(records));
    }
    await Promise.all(writes);
    _logging.log.info('FacilitySyncManager.pulled', {
        durationMs: Date.now() - start
    });
    return {
        totalPulled,
        totalToPull,
        pullUntil
    };
};

//# sourceMappingURL=pullIncomingChanges.js.map