import { AbortError } from "../../../../shared/src/abort-error.js";
import { RunningState } from "../running-state.js";
import { getOrCreateCounter } from "../../observability/metrics.js";
import { errorTypeToReadableName } from "../change-streamer/change-streamer.js";
import { Notifier } from "./notifier.js";
import { ReplicationReportRecorder } from "./reporter/recorder.js";
//#region ../zero-cache/src/services/replicator/incremental-sync.ts
/**
* The {@link IncrementalSyncer} manages a logical replication stream from upstream,
* handling application lifecycle events (start, stop) and retrying the
* connection with exponential backoff. The actual handling of the logical
* replication messages is done by the {@link ChangeProcessor}, which runs
* in a worker thread via the {@link WriteWorkerClient}.
*/
var IncrementalSyncer = class {
	#lc;
	#taskID;
	#id;
	#changeStreamer;
	#worker;
	#mode;
	#statusPublisher;
	#notifier;
	#reporter;
	#state = new RunningState("IncrementalSyncer");
	#replicationEvents = getOrCreateCounter("replication", "events", "Number of replication events processed");
	constructor(lc, taskID, id, changeStreamer, worker, mode, statusPublisher) {
		this.#lc = lc;
		this.#taskID = taskID;
		this.#id = id;
		this.#changeStreamer = changeStreamer;
		this.#worker = worker;
		this.#mode = mode;
		this.#statusPublisher = statusPublisher;
		this.#notifier = new Notifier();
		this.#reporter = new ReplicationReportRecorder(lc);
	}
	async run() {
		const lc = this.#lc;
		this.#worker.onError((err) => this.#state.stop(lc, err));
		lc.info?.(`Starting IncrementalSyncer`);
		const { watermark: initialWatermark } = await this.#worker.getSubscriptionState();
		this.#notifier.notifySubscribers();
		while (this.#state.shouldRun()) {
			const { replicaVersion, watermark } = await this.#worker.getSubscriptionState();
			let downstream;
			let unregister = () => {};
			let err;
			try {
				downstream = await this.#changeStreamer.subscribe({
					protocolVersion: 6,
					taskID: this.#taskID,
					id: this.#id,
					mode: this.#mode,
					watermark,
					replicaVersion,
					initial: watermark === initialWatermark
				});
				this.#state.resetBackoff();
				unregister = this.#state.cancelOnStop(downstream);
				this.#statusPublisher?.publish(lc, "Replicating", `Replicating from ${watermark}`);
				let backfillStatus;
				for await (const message of downstream) {
					this.#replicationEvents.add(1);
					switch (message[0]) {
						case "status": {
							const { lagReport } = message[1];
							if (lagReport) {
								const report = { nextSendTimeMs: lagReport.nextSendTimeMs };
								if (lagReport.lastTimings) report.lastTimings = {
									...lagReport.lastTimings,
									replicateTimeMs: Date.now()
								};
								this.#reporter.record(report);
							}
							break;
						}
						case "error": {
							const { type, message: msg } = message[1];
							this.stop(lc, new AbortError(`${errorTypeToReadableName(type)}: ${msg}`));
							break;
						}
						default: {
							const msg = message[1];
							if (msg.tag === "backfill" && msg.status) {
								const { status } = msg;
								if (!backfillStatus) {
									backfillStatus = status;
									this.#statusPublisher?.publish(lc, "Replicating", `Backfilling ${msg.relation.name} table`, 3e3, () => backfillStatus ? { downloadStatus: [{
										...backfillStatus,
										table: msg.relation.name,
										columns: [...msg.relation.rowKey.columns, ...msg.columns]
									}] } : {});
								}
								backfillStatus = status;
							}
							const result = await this.#worker.processMessage(message);
							this.#handleResult(lc, result);
							if (result?.completedBackfill) backfillStatus = void 0;
							break;
						}
					}
				}
				this.#worker.abort();
			} catch (e) {
				err = e;
				this.#worker.abort();
			} finally {
				downstream?.cancel();
				unregister();
				this.#statusPublisher?.stop();
			}
			await this.#state.backoff(lc, err);
		}
		lc.info?.("IncrementalSyncer stopped");
	}
	#handleResult(lc, result) {
		if (!result) return;
		if (result.completedBackfill) {
			const status = result.completedBackfill;
			this.#statusPublisher?.publish(lc, "Replicating", `Backfilled ${status.table} table`, 0, () => ({ downloadStatus: [status] }));
		} else if (result.schemaUpdated) this.#statusPublisher?.publish(lc, "Replicating", "Schema updated");
		if (result.watermark && result.changeLogUpdated) this.#notifier.notifySubscribers({ state: "version-ready" });
	}
	subscribe() {
		return this.#notifier.subscribe();
	}
	stop(lc, err) {
		this.#state.stop(lc, err);
	}
};
//#endregion
export { IncrementalSyncer };

//# sourceMappingURL=incremental-sync.js.map