import { assert } from "../../../shared/src/asserts.js";
import { must } from "../../../shared/src/must.js";
import { DatabaseInitError } from "../../../zqlite/src/db.js";
import { getNormalizedZeroConfig } from "../config/zero-config.js";
import { deleteLiteDB } from "../db/delete-lite-db.js";
import { warmupConnections } from "../db/warmup.js";
import { initEventSink } from "../observability/events.js";
import { initializeCustomChangeSource } from "../services/change-source/custom/change-source.js";
import { initializePostgresChangeSource } from "../services/change-source/pg/change-source.js";
import { BackupMonitor } from "../services/change-streamer/backup-monitor.js";
import { ChangeStreamerHttpServer } from "../services/change-streamer/change-streamer-http.js";
import { initializeStreamer } from "../services/change-streamer/change-streamer-service.js";
import { AutoResetSignal } from "../services/change-streamer/schema/tables.js";
import { exitAfter, runUntilKilled } from "../services/life-cycle.js";
import { pgClient } from "../types/pg.js";
import { parentWorker, singleProcessMode, } from "../types/processes.js";
import { getShardConfig } from "../types/shards.js";
import { createLogContext } from "./logging.js";
import { startOtelAuto } from "./otel-start.js";
export default async function runWorker(parent, env, ...args) {
    assert(args.length > 0, `parent startMs not specified`);
    const parentStartMs = parseInt(args[0]);
    const config = getNormalizedZeroConfig({ env, argv: args.slice(1) });
    const { taskID, changeStreamer: { port, address, protocol }, upstream, change, replica, initialSync, litestream, } = config;
    startOtelAuto(createLogContext(config, { worker: 'change-streamer' }, false));
    const lc = createLogContext(config, { worker: 'change-streamer' }, true);
    initEventSink(lc, config);
    // Kick off DB connection warmup in the background.
    const changeDB = pgClient(lc, change.db, {
        max: change.maxConns,
        connection: { ['application_name']: 'zero-change-streamer' },
    });
    void warmupConnections(lc, changeDB, 'change');
    const { autoReset } = config;
    const shard = getShardConfig(config);
    let changeStreamer;
    for (const first of [true, false]) {
        try {
            // Note: This performs initial sync of the replica if necessary.
            const { changeSource, subscriptionState } = upstream.type === 'pg'
                ? await initializePostgresChangeSource(lc, upstream.db, shard, replica.file, initialSync)
                : await initializeCustomChangeSource(lc, upstream.db, shard, replica.file);
            changeStreamer = await initializeStreamer(lc, shard, taskID, address, protocol, changeDB, changeSource, subscriptionState, autoReset ?? false);
            break;
        }
        catch (e) {
            if (first && e instanceof AutoResetSignal) {
                lc.warn?.(`resetting replica ${replica.file}`, e);
                // TODO: Make deleteLiteDB work with litestream. It will probably have to be
                //       a semantic wipe instead of a file delete.
                deleteLiteDB(replica.file);
                continue; // execute again with a fresh initial-sync
            }
            if (e instanceof DatabaseInitError) {
                throw new Error(`Cannot open ZERO_REPLICA_FILE at "${replica.file}". Please check that the path is valid.`, { cause: e });
            }
            throw e;
        }
    }
    // impossible: upstream must have advanced in order for replication to be stuck.
    assert(changeStreamer, `resetting replica did not advance replicaVersion`);
    const { backupURL, port: metricsPort } = litestream;
    const backupMonitor = !backupURL
        ? null
        : new BackupMonitor(lc, backupURL, `http://localhost:${metricsPort}/metrics`, changeStreamer, 
        // The time between when the zero-cache was started to when the
        // change-streamer is ready to start serves as the initial delay for
        // watermark cleanup (as it either includes a similar replica
        // restoration/preparation step, or an initial-sync, which
        // generally takes longer).
        //
        // Consider: Also account for permanent volumes?
        Date.now() - parentStartMs);
    const changeStreamerWebServer = new ChangeStreamerHttpServer(lc, config, { port }, parent, changeStreamer, backupMonitor);
    parent.send(['ready', { ready: true }]);
    const services = [changeStreamer, changeStreamerWebServer];
    if (backupMonitor) {
        services.push(backupMonitor);
    }
    return runUntilKilled(lc, parent, ...services);
}
// fork()
if (!singleProcessMode()) {
    void exitAfter(() => runWorker(must(parentWorker), process.env, ...process.argv.slice(2)));
}
//# sourceMappingURL=change-streamer.js.map