import { sleep } from "../../../../../../shared/src/sleep.js";
import "../../../../types/pg.js";
import { fromBigInt } from "../lsn.js";
import { id, lit } from "../../../../types/sql.js";
import { AutoResetSignal } from "../../../change-streamer/schema/tables.js";
import { Subscription } from "../../../../types/subscription.js";
import { pipe } from "../../../../types/streams.js";
import { getTypeParsers } from "../../../../db/pg-type-parser.js";
import { PgoutputParser } from "./pgoutput-parser.js";
import postgres from "postgres";
import { defu } from "defu";
import { PG_ADMIN_SHUTDOWN, PG_OBJECT_IN_USE, PG_OBJECT_NOT_IN_PREREQUISITE_STATE } from "@drdgvhbh/postgres-error-codes";
//#region ../zero-cache/src/services/change-source/pg/logical-replication/stream.ts
var DEFAULT_RETRIES_IF_REPLICATION_SLOT_ACTIVE = 5;
async function subscribe(lc, db, slot, publications, lsn, retriesIfReplicationSlotActive = DEFAULT_RETRIES_IF_REPLICATION_SLOT_ACTIVE, applicationName = "zero-replicator") {
	const session = postgres(defu({
		max: 1,
		["fetch_types"]: false,
		["idle_timeout"]: null,
		["max_lifetime"]: null,
		connection: {
			["application_name"]: applicationName,
			replication: "database"
		}
	}, db.options));
	const [{ walSenderTimeoutMs }] = await session`SELECT EXTRACT(EPOCH FROM (setting || unit)::interval) * 1000 
        AS "walSenderTimeoutMs" FROM pg_settings
        WHERE name = 'wal_sender_timeout'`.simple();
	const manualKeepaliveTimeout = Math.floor(walSenderTimeoutMs * .75);
	lc.info?.(`wal_sender_timeout: ${walSenderTimeoutMs}ms. Ensuring manual keepalives at least every ${manualKeepaliveTimeout}ms`);
	const [readable, writable] = await startReplicationStream(lc, session, slot, publications, lsn, retriesIfReplicationSlotActive + 1);
	let lastAckTime = Date.now();
	function sendAck(lsn) {
		writable.write(makeAck(lsn));
		lastAckTime = Date.now();
	}
	const livenessTimer = setInterval(() => {
		if (Date.now() - lastAckTime > manualKeepaliveTimeout) {
			sendAck(0n);
			lc.debug?.(`sent manual keepalive`);
		}
	}, manualKeepaliveTimeout / 5);
	let destroyed = false;
	const parser = new PgoutputParser(await getTypeParsers(db, { returnJsonAsString: true }));
	const messages = Subscription.create({ cleanup: () => {
		destroyed = true;
		readable.destroyed || readable.destroy();
		clearInterval(livenessTimer);
		return session.end();
	} });
	readable.once("close", () => destroyed || lc.warn?.(`replication stream closed by ${db.options.host}`));
	readable.once("error", (e) => e instanceof postgres.PostgresError && e.code === PG_ADMIN_SHUTDOWN || lc.warn?.(`error from ${db.options.host}`, e));
	pipe({
		source: readable,
		sink: messages,
		parse: (buffer) => parseStreamMessage(lc, buffer, parser),
		bufferMessages: 5
	});
	return {
		messages,
		acks: { push: sendAck }
	};
}
/**
* Formats publication names for the START_REPLICATION command.
* The replication protocol expects format: publication_names 'pub1,pub2'
* Each name is escaped to handle any quotes that may have passed validation.
*/
function formatPublicationNames(publications) {
	return publications.map((p) => lit(p).slice(1, -1)).join(",");
}
async function startReplicationStream(lc, session, slot, publications, lsn, maxAttempts) {
	for (let i = 0; i < maxAttempts; i++) try {
		const stream = session.unsafe(`START_REPLICATION SLOT ${id(slot)} LOGICAL ${fromBigInt(lsn)} (
        proto_version '1',
        publication_names '${formatPublicationNames(publications)}',
        messages 'true'
      )`).execute();
		return await Promise.all([stream.readable(), stream.writable()]);
	} catch (e) {
		if (e instanceof postgres.PostgresError) {
			if (e.code === PG_OBJECT_IN_USE) {
				lc.warn?.(`attempt ${i + 1}: ${String(e)}`, e);
				await sleep(10);
				continue;
			}
			if (e.code === PG_OBJECT_NOT_IN_PREREQUISITE_STATE) {
				lc.error?.(`error starting replication stream`, e);
				throw new AutoResetSignal(`unable to start replication stream`, { cause: e });
			}
		}
		throw e;
	}
	throw new Error(`exceeded max attempts (${maxAttempts}) to start the Postgres stream`);
}
function parseStreamMessage(lc, buffer, parser) {
	if (buffer[0] !== 119 && buffer[0] !== 107) {
		lc.warn?.("Unknown message", buffer[0]);
		return null;
	}
	const lsn = buffer.readBigUInt64BE(1);
	if (buffer[0] === 119) return [lsn, parser.parse(buffer.subarray(25))];
	return [lsn, {
		tag: "keepalive",
		shouldRespond: buffer.readInt8(17) !== 0
	}];
}
function makeAck(lsn) {
	const microNow = BigInt(Date.now() - Date.UTC(2e3, 0, 1)) * BigInt(1e3);
	const x = Buffer.alloc(34);
	x[0] = "r".charCodeAt(0);
	x.writeBigInt64BE(lsn, 1);
	x.writeBigInt64BE(lsn, 9);
	x.writeBigInt64BE(lsn, 17);
	x.writeBigInt64BE(microNow, 25);
	return x;
}
//#endregion
export { subscribe };

//# sourceMappingURL=stream.js.map