import { a as InvalidCursorError, i as encodeCursor, n as clampLimit, r as decodeCursor, t as deriveStored } from "./derive-CA4PI3P5.js";
//#region src/in-memory.ts
function snapshotRecord(record) {
	return JSON.parse(JSON.stringify(record));
}
function snapshotStored(stored) {
	return {
		id: stored.id,
		createdAt: stored.createdAt,
		record: snapshotRecord(stored.record)
	};
}
function compareKeysetDesc(a, b) {
	if (a.createdAtMs !== b.createdAtMs) return b.createdAtMs - a.createdAtMs;
	return b.seq - a.seq;
}
function isBeforeCursor(key, cursor) {
	return key.createdAtMs < cursor.createdAtMs || key.createdAtMs === cursor.createdAtMs && key.seq < cursor.seq;
}
function makeInMemoryRecordStore(opts = {}) {
	const now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
	const makeId = opts.makeId ?? (() => crypto.randomUUID());
	const rows = [];
	let seqCounter = 0;
	return {
		async append(record) {
			const derived = deriveStored(record, now, makeId);
			const seq = ++seqCounter;
			const key = {
				createdAtMs: derived.createdAtMs,
				seq
			};
			const stored = {
				id: derived.id,
				createdAt: derived.createdAt,
				record: derived.record
			};
			rows.push({
				key,
				stored
			});
			return snapshotStored(stored);
		},
		async list(filter = {}) {
			let candidates = [...rows].sort((a, b) => compareKeysetDesc(a.key, b.key));
			if (filter.cursor !== void 0) {
				const cursor = decodeCursor(filter.cursor);
				candidates = candidates.filter((row) => isBeforeCursor(row.key, cursor));
			}
			const limit = clampLimit(filter.limit);
			const pageRows = candidates.slice(0, limit);
			const hasMore = candidates.length > limit;
			return {
				items: pageRows.map((row) => snapshotStored(row.stored)),
				nextCursor: hasMore ? encodeCursor(pageRows[pageRows.length - 1].key) : null
			};
		}
	};
}
//#endregion
//#region src/conformance.ts
function stableStringify(value) {
	if (value === null || typeof value !== "object") return JSON.stringify(value);
	if (Array.isArray(value)) return `[${value.map((item) => stableStringify(item)).join(",")}]`;
	const object = value;
	return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(object[key])}`).join(",")}}`;
}
function deepEqual(a, b) {
	return stableStringify(a) === stableStringify(b);
}
function assert(condition, message) {
	if (!condition) throw new Error(message);
}
function assertDeepEqual(actual, expected, label) {
	if (!deepEqual(actual, expected)) throw new Error(`${label}: expected ${stableStringify(expected)} but got ${stableStringify(actual)}`);
}
async function freshStore(makeStore, opts) {
	return await makeStore(opts);
}
async function runRecordStoreConformance(makeStore) {
	await scenarioAppendThenListNewestFirst(makeStore);
	await scenarioCursorRoundTrip(makeStore);
	await scenarioLimitClamp(makeStore);
	await scenarioEmptyStore(makeStore);
	await scenarioSameCreatedAtMsBatch(makeStore);
	await scenarioCanonicalization(makeStore);
	await scenarioImmutabilityAliasing(makeStore);
}
async function scenarioAppendThenListNewestFirst(makeStore) {
	let appendCount = 0;
	const store = await freshStore(makeStore, {
		now: () => appendCount === 0 ? "2026-01-01T00:00:00.000Z" : "2026-01-02T00:00:00.000Z",
		makeId: () => {
			appendCount += 1;
			return `id-${appendCount}`;
		}
	});
	await store.append({ n: 1 });
	await store.append({ n: 2 });
	const page = await store.list();
	assert(page.items.length === 2, "append-then-list: expected 2 items");
	assertDeepEqual(page.items[0].record, { n: 2 }, "append-then-list: newest first");
	assertDeepEqual(page.items[1].record, { n: 1 }, "append-then-list: older second");
}
async function scenarioCursorRoundTrip(makeStore) {
	let appendCount = 0;
	const store = await freshStore(makeStore, {
		now: () => {
			appendCount += 1;
			return `2026-01-${String(appendCount).padStart(2, "0")}T00:00:00.000Z`;
		},
		makeId: () => {
			return `id-${appendCount}`;
		}
	});
	const total = 7;
	for (let i = 1; i <= total; i++) await store.append({ n: i });
	const fullRecords = (await store.list({ limit: total })).items.map((item) => item.record);
	const pageSize = 3;
	const collected = [];
	let cursor = void 0;
	for (;;) {
		const page = await store.list({
			limit: pageSize,
			...cursor ? { cursor } : {}
		});
		collected.push(...page.items.map((item) => item.record));
		if (page.nextCursor === null) break;
		cursor = page.nextCursor;
	}
	assert(collected.length === total, "cursor round-trip: collected count mismatch");
	assertDeepEqual(collected, fullRecords, "cursor round-trip: items mismatch");
	assert(new Set(collected.map((item) => stableStringify(item))).size === total, "cursor round-trip: duplicate items detected");
}
async function scenarioLimitClamp(makeStore) {
	const store = await freshStore(makeStore);
	for (let i = 0; i < 5; i++) await store.append({ n: i });
	for (const { limit, expected } of [
		{
			limit: void 0,
			expected: 50
		},
		{
			limit: NaN,
			expected: 50
		},
		{
			limit: 0,
			expected: 50
		},
		{
			limit: -1,
			expected: 50
		},
		{
			limit: 9999,
			expected: 200
		},
		{
			limit: 2,
			expected: 2
		}
	]) {
		assert(clampLimit(limit) === expected, `limit clamp: clampLimit(${String(limit)}) expected ${expected}`);
		const page = await store.list({ limit });
		assert(page.items.length === Math.min(expected, 5), `limit clamp: list(limit=${String(limit)}) returned ${page.items.length} items`);
	}
}
async function scenarioEmptyStore(makeStore) {
	assertDeepEqual(await (await freshStore(makeStore)).list(), {
		items: [],
		nextCursor: null
	}, "empty store");
}
async function scenarioSameCreatedAtMsBatch(makeStore) {
	const fixedNow = "2026-06-17T12:00:00.000Z";
	let idCounter = 0;
	const store = await freshStore(makeStore, {
		now: () => fixedNow,
		makeId: () => `batch-id-${++idCounter}`
	});
	const batchSize = 5;
	for (let i = 1; i <= batchSize; i++) await store.append({ n: i });
	const full = await store.list({ limit: batchSize });
	assert(full.items.every((item) => item.createdAt === new Date(Date.parse(fixedNow)).toISOString()), "same-createdAtMs batch: createdAt must be canonical");
	const expectedOrder = Array.from({ length: batchSize }, (_, i) => ({ n: batchSize - i }));
	assertDeepEqual(full.items.map((item) => item.record), expectedOrder, "same-createdAtMs batch: newest-inserted first via seq");
	const pageSize = 2;
	const collected = [];
	let cursor = void 0;
	for (;;) {
		const page = await store.list({
			limit: pageSize,
			...cursor ? { cursor } : {}
		});
		collected.push(...page.items.map((item) => item.record));
		if (page.nextCursor === null) break;
		cursor = page.nextCursor;
	}
	assert(collected.length === batchSize, "same-createdAtMs batch: pagination count mismatch");
	assertDeepEqual(collected, expectedOrder, "same-createdAtMs batch: pagination order mismatch");
	assert(new Set(collected.map((item) => stableStringify(item))).size === batchSize, "same-createdAtMs batch: duplicate items across pages");
}
async function scenarioCanonicalization(makeStore) {
	const noMillis = await (await freshStore(makeStore, {
		now: () => "2026-01-01T00:00:00Z",
		makeId: () => "canon-1"
	})).append({ tag: "no-millis" });
	assert(noMillis.createdAt === "2026-01-01T00:00:00.000Z", `canonicalization: no-millis clock stored ${noMillis.createdAt}`);
	const offset = await (await freshStore(makeStore, {
		now: () => "2026-01-01T02:00:00+02:00",
		makeId: () => "canon-2"
	})).append({ tag: "offset" });
	assert(offset.createdAt === "2026-01-01T00:00:00.000Z", `canonicalization: offset clock stored ${offset.createdAt}`);
}
async function scenarioImmutabilityAliasing(makeStore) {
	const store = await freshStore(makeStore, {
		now: () => "2026-01-01T00:00:00.000Z",
		makeId: () => "alias-1"
	});
	const input = {
		nested: { value: 1 },
		list: [1, 2]
	};
	await store.append(input);
	input.nested = { value: 999 };
	input.list.push(3);
	input.extra = true;
	assertDeepEqual((await store.list()).items[0].record, {
		nested: { value: 1 },
		list: [1, 2]
	}, "immutability: stored record must not reflect caller mutation");
}
//#endregion
export { InvalidCursorError, makeInMemoryRecordStore, runRecordStoreConformance };
