"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.replayLogs = replayLogs;
const rocksdb_js_1 = require("@harperfast/rocksdb-js");
const logger = __importStar(require("../utility/logging/harper_logger.js"));
const DatabaseTransaction_ts_1 = require("./DatabaseTransaction.js");
const node_worker_threads_1 = require("node:worker_threads");
const RequestTarget_ts_1 = require("./RequestTarget.js");
let warnedReplayHappening = false;
function replayLogs(rootStore, tables) {
    if (!node_worker_threads_1.isMainThread)
        return; // ideally we don't do it like this, but for now this is predictable
    return new Promise((resolve) => {
        const acquired = rootStore.tryLock('replayLogs', async () => {
            resolve();
        });
        if (!acquired)
            return;
        const tableById = new Map();
        for (const tableName in tables) {
            const table = tables[tableName];
            tableById.set(table.tableId, table);
        }
        // replay all the logs
        let transaction;
        let lastTimestamp = 0;
        let writes = 0;
        const txnLog = rootStore.auditStore;
        for (const auditRecord of txnLog.getRange({ startFromLastFlushed: true, readUncommitted: true })) {
            const { type, tableId, nodeId, recordId, version, residencyId, expiresAt, originatingOperation, username } = auditRecord;
            try {
                const Table = tableById.get(tableId);
                if (!Table)
                    continue;
                const context = { nodeId, alreadyLogged: true, version, expiresAt, user: { name: username } };
                const { primaryStore } = Table;
                const target = new RequestTarget_ts_1.RequestTarget();
                target.id = null;
                const tableInstance = Table.getResource(target, context, {});
                // TODO: If this throws an error due to being unable to access structures, we need to iterate through
                // other transaction logs to get the latest structure. Ultimately we may have to skip records
                if (!warnedReplayHappening) {
                    warnedReplayHappening = true;
                    console.warn('Harper was not properly shutdown, replaying transaction logs to synchronize database');
                }
                const record = auditRecord.getValue(primaryStore);
                if (lastTimestamp !== version) {
                    lastTimestamp = version;
                    try {
                        // commit the last transaction since we are starting a new one
                        transaction?.directCommitSync();
                    }
                    catch (error) {
                        logger.error('Error committing replay transaction', error);
                    }
                    transaction = new DatabaseTransaction_ts_1.DatabaseTransaction();
                    transaction.db = primaryStore;
                    transaction.timestamp = version;
                    // we treat this as a retry, because it is (and we want to skip validation and writing to the transaction log)
                    transaction.retries = 1;
                }
                context.transaction = transaction;
                const options = { context, residencyId, nodeId, originatingOperation };
                writes++;
                switch (type) {
                    case 'put':
                        tableInstance._writeUpdate(recordId, record, true, options);
                        tableInstance.save(); // requires an explicit save
                        break;
                    case 'patch':
                        tableInstance._writeUpdate(recordId, record, false, options);
                        tableInstance.save(); // requires an explicit save
                        break;
                    case 'message':
                        tableInstance._writePublish(recordId, record, options);
                        break;
                    case 'relocate':
                        tableInstance._writeRelocate(recordId, options);
                        break;
                    case 'delete':
                        tableInstance._writeDelete(recordId, options);
                        break;
                    case 'invalidate':
                        tableInstance._writeInvalidate(recordId, record, options);
                        break;
                    case 'structures': {
                        const rocksTransaction = new rocksdb_js_1.Transaction(primaryStore.store);
                        const structuresAsBinary = auditRecord.getBinaryValue(primaryStore);
                        const updatedStructures = structuresAsBinary ? primaryStore.decoder.decode(structuresAsBinary) : undefined;
                        const existingStructures = primaryStore.getSync(Symbol.for('structures'), {
                            transaction: rocksTransaction,
                        });
                        if (existingStructures) {
                            if (existingStructures instanceof Array) {
                                if (updatedStructures.length < existingStructures.length) {
                                    logger.warn(`Found ${existingStructures.length} structures in audit store, but ${updatedStructures.length} in replay log. Using ${updatedStructures.length} structures.`);
                                }
                            }
                            else {
                                if (existingStructures.get('named').length > updatedStructures.get('named').length) {
                                    logger.warn(`Found named ${existingStructures.length} structures in audit store, but ${updatedStructures.length} in replay log. Using named ${updatedStructures.length} structures.`);
                                }
                                if (existingStructures.get('typed').length > updatedStructures.get('typed').length) {
                                    logger.warn(`Found named ${existingStructures.length} structures in audit store, but ${updatedStructures.length} in replay log. Using named ${updatedStructures.length} structures.`);
                                }
                            }
                        }
                        primaryStore.putSync(Symbol.for('structures'), asBinary(structuresAsBinary), {
                            transaction: rocksTransaction,
                        });
                        rocksTransaction.commitSync();
                        primaryStore.decoder.structure = updatedStructures;
                    }
                }
            }
            catch (err) {
                logger.error(`Error writing from replay of log`, err, {
                    version,
                });
            }
        }
        try {
            transaction?.directCommitSync();
        }
        catch (error) {
            logger.error('Error committing replay transaction', error);
        }
        if (writes > 0)
            logger.warn(`Replayed ${writes} records in ${rootStore.databaseName} database`);
        // we never actually release the lock because we only want to ever run one time
        // rootStore.unlock('replayLogs');
    });
}
function asBinary(buffer) {
    return { ['\x10binary-data\x02']: buffer };
}
//# sourceMappingURL=replayLogs.js.map