import { _ as uniqueStrings, l as normalizeStringEntries } from "./string-normalization-WNUDCpXX.js";
import "./string-coerce-runtime-CEGJWkQ_.js";
import { t as getOptionalTelegramRuntime } from "./runtime-CySuXZyV.js";
import fs from "node:fs";
import path from "node:path";
import { createHash } from "node:crypto";
//#region extensions/telegram/src/message-dispatch-dedupe.ts
const TELEGRAM_MESSAGE_DISPATCH_TTL_MS = 10080 * 60 * 1e3;
const TELEGRAM_MESSAGE_DISPATCH_DEDUPE_NAMESPACE = "telegram.message-dispatch-dedupe";
const TELEGRAM_MESSAGE_DISPATCH_DEDUPE_MAX_ENTRIES = 4096;
const TELEGRAM_MESSAGE_DISPATCH_DEDUPE_LOGICAL_MAX_ENTRIES = 5e4;
const TELEGRAM_MESSAGE_DISPATCH_DEDUPE_BUCKET_COUNT = 256;
const TELEGRAM_MESSAGE_DISPATCH_DEDUPE_BUCKET_MAX_KEYS = 256;
const TELEGRAM_MESSAGE_DISPATCH_DEDUPE_LOCK_TTL_MS = 3e4;
const TELEGRAM_MESSAGE_DISPATCH_DEDUPE_LOCK_RETRY_MS = 10;
const TELEGRAM_MESSAGE_DISPATCH_DEDUPE_LOCK_ATTEMPTS = 50;
function openDispatchDedupeStore() {
	return getOptionalTelegramRuntime()?.state.openKeyedStore({
		namespace: TELEGRAM_MESSAGE_DISPATCH_DEDUPE_NAMESPACE,
		maxEntries: TELEGRAM_MESSAGE_DISPATCH_DEDUPE_MAX_ENTRIES
	});
}
function resolveDispatchScopeKey(storePath) {
	return createHash("sha256").update(storePath, "utf8").digest("hex").slice(0, 24);
}
function dedupeEntryKey(scopeKey, namespace, key) {
	return createHash("sha256").update(`${scopeKey}\0${namespace}\0${key}`, "utf8").digest("hex").slice(0, 32);
}
function dedupeBucketId(key) {
	return (Number.parseInt(createHash("sha256").update(key, "utf8").digest("hex").slice(0, 8), 16) % TELEGRAM_MESSAGE_DISPATCH_DEDUPE_BUCKET_COUNT).toString(16).padStart(2, "0");
}
function dedupeBucketEntryKey(scopeKey, namespace, bucketId) {
	return createHash("sha256").update(`${scopeKey}\0${namespace}\0${bucketId}`, "utf8").digest("hex").slice(0, 32);
}
function dedupeLegacyBucketEntryKey(params) {
	const sourceKey = createHash("sha256").update(params.sourcePath, "utf8").digest("hex").slice(0, 12);
	return dedupeBucketEntryKey(params.scopeKey, params.namespace, `${params.bucketId}:${sourceKey}`);
}
function dedupeBucketLockKey(bucketKey) {
	return `${bucketKey}:lock`;
}
async function sleep(ms) {
	await new Promise((resolve) => {
		setTimeout(resolve, ms);
	});
}
function pruneDedupeBucketEntries(entries, now) {
	for (const [key, timestamp] of Object.entries(entries)) {
		if (typeof timestamp !== "number" || !Number.isFinite(timestamp)) {
			delete entries[key];
			continue;
		}
		if (now - timestamp >= TELEGRAM_MESSAGE_DISPATCH_TTL_MS) delete entries[key];
	}
	const keys = Object.keys(entries);
	if (keys.length <= TELEGRAM_MESSAGE_DISPATCH_DEDUPE_BUCKET_MAX_KEYS) return;
	for (const key of keys.toSorted((left, right) => entries[left] - entries[right]).slice(0, keys.length - TELEGRAM_MESSAGE_DISPATCH_DEDUPE_BUCKET_MAX_KEYS)) delete entries[key];
}
function createDedupeBucketRecord(params) {
	return {
		scopeKey: params.scopeKey,
		namespace: params.namespace,
		bucketId: params.bucketId,
		entries: { ...params.entries }
	};
}
function normalizeDedupeBucketRecord(value, params) {
	const entries = value?.scopeKey === params.scopeKey && value.namespace === params.namespace && value.bucketId === params.bucketId && value.entries && typeof value.entries === "object" ? { ...value.entries } : {};
	pruneDedupeBucketEntries(entries, params.now);
	return createDedupeBucketRecord({
		scopeKey: params.scopeKey,
		namespace: params.namespace,
		bucketId: params.bucketId,
		entries
	});
}
async function lookupDedupeBucketContains(params) {
	if (normalizeDedupeBucketRecord(await params.store.lookup(params.bucketKey), params).entries[params.key] !== void 0) return true;
	for (const entry of await params.store.entries()) {
		if (entry.key === params.bucketKey || entry.value.scopeKey !== params.scopeKey || entry.value.namespace !== params.namespace || entry.value.bucketId !== params.bucketId) continue;
		if (normalizeDedupeBucketRecord(entry.value, params).entries[params.key] !== void 0) return true;
	}
	return false;
}
function sanitizeFileSegment(value) {
	const trimmed = value.trim();
	if (!trimmed) return "default";
	return trimmed.replace(/[^a-zA-Z0-9_-]/g, "_");
}
function resolveTelegramMessageDispatchLegacyPath(params) {
	return path.join(path.dirname(params.storePath), `${path.basename(params.storePath)}.telegram-message-dispatch-${sanitizeFileSegment(params.namespace)}.json`);
}
function buildTelegramMessageDispatchReplayKey(msg) {
	const chatId = msg.chat?.id;
	const messageId = msg.message_id;
	if (chatId == null || typeof messageId !== "number" || messageId <= 0) return null;
	return JSON.stringify([
		"message",
		String(chatId),
		messageId
	]);
}
function createTelegramMessageDispatchReplayGuard(params) {
	const scopeKey = resolveDispatchScopeKey(params.storePath);
	const onStateError = params.onDiskError;
	let store;
	const inflight = /* @__PURE__ */ new Map();
	const committedInMemory = /* @__PURE__ */ new Map();
	const bucketWriteQueue = /* @__PURE__ */ new Map();
	function getStore() {
		if (store) return store;
		try {
			store = openDispatchDedupeStore();
			return store;
		} catch (error) {
			onStateError?.(error);
			return;
		}
	}
	function pruneCommittedInMemory(now = Date.now()) {
		for (const [entryKey, entry] of committedInMemory) if (entry.expiresAt <= now || committedInMemory.size > TELEGRAM_MESSAGE_DISPATCH_DEDUPE_LOGICAL_MAX_ENTRIES) committedInMemory.delete(entryKey);
	}
	function rememberCommittedInMemory(entryKey, namespace, now) {
		committedInMemory.set(entryKey, {
			namespace,
			expiresAt: now + TELEGRAM_MESSAGE_DISPATCH_TTL_MS
		});
		pruneCommittedInMemory(now);
	}
	function hasCommittedInMemory(entryKey, now = Date.now()) {
		const entry = committedInMemory.get(entryKey);
		if (!entry) return false;
		if (entry.expiresAt <= now) {
			committedInMemory.delete(entryKey);
			return false;
		}
		return true;
	}
	function rememberPendingClaim(entryKey) {
		let resolve;
		let reject;
		const promise = new Promise((resolvePromise, rejectPromise) => {
			resolve = resolvePromise;
			reject = rejectPromise;
		});
		promise.catch(() => {});
		const pending = {
			promise,
			resolve,
			reject
		};
		inflight.set(entryKey, pending);
		return pending;
	}
	function enqueueBucketWrite(bucketKey, write) {
		const next = (bucketWriteQueue.get(bucketKey) ?? Promise.resolve()).catch(() => void 0).then(write);
		const queued = next.then(() => void 0, () => void 0);
		bucketWriteQueue.set(bucketKey, queued);
		queued.finally(() => {
			if (bucketWriteQueue.get(bucketKey) === queued) bucketWriteQueue.delete(bucketKey);
		});
		return next;
	}
	async function withBucketLock(paramsLocal) {
		const lockKey = dedupeBucketLockKey(paramsLocal.bucketKey);
		const lockValue = createDedupeBucketRecord({
			scopeKey,
			namespace: `${paramsLocal.namespace}:lock`,
			bucketId: paramsLocal.bucketId
		});
		let locked = false;
		for (let attempt = 0; attempt < TELEGRAM_MESSAGE_DISPATCH_DEDUPE_LOCK_ATTEMPTS; attempt += 1) {
			if (await paramsLocal.store.registerIfAbsent(lockKey, lockValue, { ttlMs: TELEGRAM_MESSAGE_DISPATCH_DEDUPE_LOCK_TTL_MS })) {
				locked = true;
				break;
			}
			await sleep(TELEGRAM_MESSAGE_DISPATCH_DEDUPE_LOCK_RETRY_MS);
		}
		if (!locked) throw new Error(`timed out acquiring Telegram dispatch dedupe bucket lock: ${paramsLocal.bucketId}`);
		try {
			return await paramsLocal.write();
		} finally {
			await paramsLocal.store.delete(lockKey);
		}
	}
	return {
		async claim(key, options) {
			const namespace = options?.namespace?.trim() || "global";
			const entryKey = dedupeEntryKey(scopeKey, namespace, key);
			const bucketId = dedupeBucketId(key);
			const bucketKey = dedupeBucketEntryKey(scopeKey, namespace, bucketId);
			if (hasCommittedInMemory(entryKey)) return { kind: "duplicate" };
			const existing = inflight.get(entryKey);
			if (existing) return {
				kind: "inflight",
				pending: existing.promise
			};
			const pending = rememberPendingClaim(entryKey);
			const storeEntry = getStore();
			if (!storeEntry) return { kind: "claimed" };
			try {
				if (await lookupDedupeBucketContains({
					store: storeEntry,
					scopeKey,
					namespace,
					bucketId,
					bucketKey,
					key,
					now: Date.now()
				})) {
					pending.resolve(false);
					inflight.delete(entryKey);
					return { kind: "duplicate" };
				}
				return { kind: "claimed" };
			} catch (error) {
				onStateError?.(error);
				return { kind: "claimed" };
			}
		},
		async commit(key, options) {
			const namespace = options?.namespace?.trim() || "global";
			const now = options?.now ?? Date.now();
			const entryKey = dedupeEntryKey(scopeKey, namespace, key);
			const bucketId = dedupeBucketId(key);
			const bucketKey = dedupeBucketEntryKey(scopeKey, namespace, bucketId);
			const storeResult = getStore();
			if (!storeResult) {
				rememberCommittedInMemory(entryKey, namespace, now);
				inflight.get(entryKey)?.resolve(true);
				inflight.delete(entryKey);
				return false;
			}
			try {
				await enqueueBucketWrite(bucketKey, async () => {
					await withBucketLock({
						store: storeResult,
						namespace,
						bucketId,
						bucketKey,
						write: async () => {
							const bucket = normalizeDedupeBucketRecord(await storeResult.lookup(bucketKey), {
								scopeKey,
								namespace,
								bucketId,
								now
							});
							bucket.entries[key] = now;
							pruneDedupeBucketEntries(bucket.entries, now);
							await storeResult.register(bucketKey, bucket, { ttlMs: TELEGRAM_MESSAGE_DISPATCH_TTL_MS });
						}
					});
				});
				rememberCommittedInMemory(entryKey, namespace, now);
				inflight.get(entryKey)?.resolve(true);
				return true;
			} catch (error) {
				rememberCommittedInMemory(entryKey, namespace, now);
				inflight.get(entryKey)?.resolve(true);
				onStateError?.(error);
				return false;
			} finally {
				inflight.delete(entryKey);
			}
		},
		release(key, options) {
			const namespace = options?.namespace?.trim() || "global";
			const entryKey = dedupeEntryKey(scopeKey, namespace, key);
			const pending = inflight.get(entryKey);
			if (pending) {
				pending.reject(options?.error ?? /* @__PURE__ */ new Error(`claim released before commit: ${namespace}`));
				inflight.delete(entryKey);
			}
		},
		async hasRecent(key, options) {
			const namespace = options?.namespace?.trim() || "global";
			const entryKey = dedupeEntryKey(scopeKey, namespace, key);
			const bucketId = dedupeBucketId(key);
			const bucketKey = dedupeBucketEntryKey(scopeKey, namespace, bucketId);
			if (hasCommittedInMemory(entryKey)) return true;
			const storeValue = getStore();
			if (!storeValue) return false;
			try {
				return await lookupDedupeBucketContains({
					store: storeValue,
					scopeKey,
					namespace,
					bucketId,
					bucketKey,
					key,
					now: Date.now()
				});
			} catch (error) {
				onStateError?.(error);
				return false;
			}
		},
		async warmup(namespace = "global") {
			pruneCommittedInMemory();
			const memoryCount = [...committedInMemory.values()].filter((entry) => entry.namespace === namespace).length;
			const storeLocal = getStore();
			if (!storeLocal) return memoryCount;
			try {
				const now = Date.now();
				return (await storeLocal.entries()).filter((entry) => entry.value.scopeKey === scopeKey && entry.value.namespace === namespace).reduce((count, entry) => {
					const bucket = normalizeDedupeBucketRecord(entry.value, {
						scopeKey,
						namespace,
						bucketId: entry.value.bucketId,
						now
					});
					return count + Object.keys(bucket.entries).length;
				}, 0) + memoryCount;
			} catch (error) {
				onStateError?.(error);
				return memoryCount;
			}
		},
		clearMemory() {
			inflight.clear();
			committedInMemory.clear();
		},
		memorySize() {
			pruneCommittedInMemory();
			return inflight.size + committedInMemory.size;
		}
	};
}
async function claimTelegramMessageDispatchReplay(params) {
	const key = buildTelegramMessageDispatchReplayKey(params.msg);
	if (!key) return { kind: "invalid" };
	let releaseRetries = 0;
	while (true) {
		const claim = await params.guard.claim(key, { namespace: params.accountId });
		if (claim.kind === "claimed") return {
			kind: "claimed",
			key
		};
		if (claim.kind === "duplicate") return { kind: "duplicate" };
		try {
			await claim.pending;
			return { kind: "duplicate" };
		} catch {
			releaseRetries += 1;
			if (releaseRetries > 1) return { kind: "duplicate" };
		}
	}
}
function normalizeReplayKeys(keys) {
	return uniqueStrings(normalizeStringEntries(keys ?? []));
}
async function commitTelegramMessageDispatchReplay(params) {
	const keys = normalizeReplayKeys(params.keys);
	await Promise.all(keys.map((key) => params.guard.commit(key, { namespace: params.accountId })));
}
function releaseTelegramMessageDispatchReplay(params) {
	const keys = normalizeReplayKeys(params.keys);
	for (const key of keys) params.guard.release(key, {
		namespace: params.accountId,
		error: params.error
	});
}
function listTelegramLegacyMessageDispatchDedupeEntries(params) {
	const filePath = params.persistedPath ?? resolveTelegramMessageDispatchLegacyPath(params);
	if (!fs.existsSync(filePath)) return [];
	const now = Date.now();
	let parsed;
	try {
		parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
	} catch {
		return [];
	}
	if (!parsed || typeof parsed !== "object") return [];
	const scopeKey = resolveDispatchScopeKey(params.storePath);
	const buckets = /* @__PURE__ */ new Map();
	for (const [key, value] of Object.entries(parsed)) {
		if (typeof value !== "number" || !Number.isFinite(value)) continue;
		const ttlMs = TELEGRAM_MESSAGE_DISPATCH_TTL_MS - Math.max(0, now - value);
		if (ttlMs <= 0) continue;
		const bucketId = dedupeBucketId(key);
		const bucketKey = dedupeLegacyBucketEntryKey({
			scopeKey,
			namespace: params.namespace,
			bucketId,
			sourcePath: filePath
		});
		const bucket = buckets.get(bucketKey) ?? {
			value: createDedupeBucketRecord({
				scopeKey,
				namespace: params.namespace,
				bucketId
			}),
			ttlMs: 0
		};
		bucket.value.entries[key] = value;
		bucket.ttlMs = Math.max(bucket.ttlMs, ttlMs);
		buckets.set(bucketKey, bucket);
	}
	return [...buckets.entries()].map(([key, bucket]) => ({
		key,
		value: bucket.value,
		ttlMs: bucket.ttlMs
	}));
}
//#endregion
export { createTelegramMessageDispatchReplayGuard as a, resolveTelegramMessageDispatchLegacyPath as c, commitTelegramMessageDispatchReplay as i, TELEGRAM_MESSAGE_DISPATCH_DEDUPE_NAMESPACE as n, listTelegramLegacyMessageDispatchDedupeEntries as o, claimTelegramMessageDispatchReplay as r, releaseTelegramMessageDispatchReplay as s, TELEGRAM_MESSAGE_DISPATCH_DEDUPE_MAX_ENTRIES as t };
