import { assert } from "../../../shared/src/asserts.js";
import { DEFAULT_HEAD_NAME, assertSnapshotCommitDD31, baseSnapshotFromCommit, commitFromHash, commitFromHead, compareCookiesForSnapshots, localMutationsDD31, localMutationsGreaterThan } from "../db/commit.js";
import { withRead, withWrite } from "../with-transactions.js";
import { getClientGroup, setClientGroup } from "./client-groups.js";
import { assertClientV6, assertHasClientState, getClientGroupIDForClient, mustGetClient, setClient } from "./clients.js";
import { rebaseMutationAndPutCommit } from "../db/rebase.js";
import { GatherMemoryOnlyVisitor } from "./gather-mem-only-visitor.js";
//#region ../replicache/src/persist/persist.ts
/**
* Persists the client's memdag state to the client's perdag client group.
*
* Persists the base snapshot from memdag to the client's perdag client group,
* but only if it’s newer than the client's perdag client group’s base snapshot.
* The base snapshot is persisted by gathering all memory-only chunks in the dag
* subgraph rooted at the base snapshot's commit and writing them to the perdag.
* Once the base snapshot is persisted, rebases onto this new base snapshot all
* local commits from the client's perdag client group that are not already
* reflected in the base snapshot.
*
* Whether or not the base snapshot is persisted, rebases onto the client's
* perdag client group all memdag local commits not already in the client's
* perdag client group's history.
*
* Also updates the `lastMutationIDs` and `lastServerAckdMutationIDs` properties
* of the client's client group's entry in the `ClientGroupMap`.
*/
async function persistDD31(lc, clientID, memdag, perdag, mutators, closed, formatVersion, getZeroData, onGatherMemOnlyChunksForTest = () => Promise.resolve()) {
	if (closed()) return;
	const [perdagLMID, perdagBaseSnapshot, mainClientGroupID] = await withRead(perdag, async (perdagRead) => {
		await assertHasClientState(clientID, perdagRead);
		const mainClientGroupID = await getClientGroupIDForClient(clientID, perdagRead);
		assert(mainClientGroupID, `No main client group for clientID: ${clientID}`);
		const [, perdagMainClientGroupHeadCommit] = await getClientGroupInfo(perdagRead, mainClientGroupID);
		const perdagLMID = await perdagMainClientGroupHeadCommit.getMutationID(clientID, perdagRead);
		const perdagBaseSnapshot = await baseSnapshotFromCommit(perdagMainClientGroupHeadCommit, perdagRead);
		assertSnapshotCommitDD31(perdagBaseSnapshot);
		return [
			perdagLMID,
			perdagBaseSnapshot,
			mainClientGroupID
		];
	});
	if (closed()) return;
	const [newMemdagMutations, memdagBaseSnapshot, gatheredChunks] = await withRead(memdag, async (memdagRead) => {
		const memdagHeadCommit = await commitFromHead(DEFAULT_HEAD_NAME, memdagRead);
		const newMutations = await localMutationsGreaterThan(memdagHeadCommit, { [clientID]: perdagLMID || 0 }, memdagRead);
		const memdagBaseSnapshot = await baseSnapshotFromCommit(memdagHeadCommit, memdagRead);
		assertSnapshotCommitDD31(memdagBaseSnapshot);
		let gatheredChunks;
		if (compareCookiesForSnapshots(memdagBaseSnapshot, perdagBaseSnapshot) > 0) {
			await onGatherMemOnlyChunksForTest();
			const memdagBaseSnapshotHash = memdagBaseSnapshot.chunk.hash;
			const visitor = new GatherMemoryOnlyVisitor(memdagRead);
			await visitor.visit(memdagBaseSnapshotHash);
			gatheredChunks = visitor.gatheredChunks;
		}
		return [
			newMutations,
			memdagBaseSnapshot,
			gatheredChunks
		];
	});
	if (closed()) return;
	let memdagBaseSnapshotPersisted = false;
	const zeroDataForMemdagBaseSnapshot = getZeroData && await getZeroData(memdagBaseSnapshot.chunk.hash);
	await withWrite(perdag, async (perdagWrite) => {
		const [mainClientGroup, latestPerdagMainClientGroupHeadCommit] = await getClientGroupInfo(perdagWrite, mainClientGroupID);
		let newMainClientGroupHeadHash = latestPerdagMainClientGroupHeadCommit.chunk.hash;
		let mutationIDs = { ...mainClientGroup.mutationIDs };
		let { lastServerAckdMutationIDs } = mainClientGroup;
		if (gatheredChunks) {
			const client = await mustGetClient(clientID, perdagWrite);
			assertClientV6(client);
			const latestPerdagBaseSnapshot = await baseSnapshotFromCommit(latestPerdagMainClientGroupHeadCommit, perdagWrite);
			assertSnapshotCommitDD31(latestPerdagBaseSnapshot);
			if (compareCookiesForSnapshots(memdagBaseSnapshot, latestPerdagBaseSnapshot) > 0) {
				memdagBaseSnapshotPersisted = true;
				await Promise.all(Array.from(gatheredChunks.values(), (c) => perdagWrite.putChunk(c)));
				await setClient(clientID, {
					...client,
					persistHash: memdagBaseSnapshot.chunk.hash
				}, perdagWrite);
				newMainClientGroupHeadHash = memdagBaseSnapshot.chunk.hash;
				const mainClientGroupLocalMutations = await localMutationsDD31(mainClientGroup.headHash, perdagWrite);
				lastServerAckdMutationIDs = memdagBaseSnapshot.meta.lastMutationIDs;
				mutationIDs = { ...lastServerAckdMutationIDs };
				newMainClientGroupHeadHash = await rebase(mainClientGroupLocalMutations, newMainClientGroupHeadHash, perdagWrite, mutators, mutationIDs, lc, formatVersion, zeroDataForMemdagBaseSnapshot);
			}
		}
		let zeroDataForPerdagHeadCommit;
		if (!memdagBaseSnapshotPersisted) zeroDataForPerdagHeadCommit = getZeroData && await getZeroData(newMainClientGroupHeadHash, { openLazySourceRead: perdagWrite });
		newMainClientGroupHeadHash = await rebase(newMemdagMutations, newMainClientGroupHeadHash, perdagWrite, mutators, mutationIDs, lc, formatVersion, zeroDataForPerdagHeadCommit ?? zeroDataForMemdagBaseSnapshot);
		await setClientGroup(mainClientGroupID, {
			...mainClientGroup,
			headHash: newMainClientGroupHeadHash,
			mutationIDs,
			lastServerAckdMutationIDs
		}, perdagWrite);
	});
	if (gatheredChunks && memdagBaseSnapshotPersisted) await withWrite(memdag, (memdagWrite) => memdagWrite.chunksPersisted([...gatheredChunks.keys()]));
}
async function getClientGroupInfo(perdagRead, clientGroupID) {
	const clientGroup = await getClientGroup(clientGroupID, perdagRead);
	assert(clientGroup, `No client group for clientGroupID: ${clientGroupID}`);
	return [clientGroup, await commitFromHash(clientGroup.headHash, perdagRead)];
}
async function rebase(mutations, basis, write, mutators, mutationIDs, lc, formatVersion, zeroData) {
	for (let i = mutations.length - 1; i >= 0; i--) {
		const mutationCommit = mutations[i];
		const { meta } = mutationCommit;
		const newMainHead = await commitFromHash(basis, write);
		if (await mutationCommit.getMutationID(meta.clientID, write) > await newMainHead.getMutationID(meta.clientID, write)) {
			mutationIDs[meta.clientID] = meta.mutationID;
			basis = (await rebaseMutationAndPutCommit(mutationCommit, write, basis, mutators, lc, meta.clientID, formatVersion, zeroData)).chunk.hash;
		}
	}
	return basis;
}
//#endregion
export { persistDD31 };

//# sourceMappingURL=persist.js.map