interface AppviewCursorCheckpoint {
	matchingSeen: number;
	scannedSeen: number;
	cursor?: string;
	exhausted: boolean;
}

const APPVIEW_CURSOR_CACHE_LIMIT = 64;
const appviewPageCursorCache = new Map<string, Map<number, AppviewCursorCheckpoint>>();

export const clearAppviewStatefulCursorCache = () => {
	appviewPageCursorCache.clear();
};

export const rememberAppviewCursorCheckpoint = (cacheKey: string, checkpoint: AppviewCursorCheckpoint) => {
	let checkpoints = appviewPageCursorCache.get(cacheKey);
	if (!checkpoints) {
		checkpoints = new Map();
		appviewPageCursorCache.set(cacheKey, checkpoints);
	}

	const existing = checkpoints.get(checkpoint.matchingSeen);
	if (!existing || checkpoint.scannedSeen >= existing.scannedSeen) {
		checkpoints.set(checkpoint.matchingSeen, checkpoint);
	}

	while (checkpoints.size > APPVIEW_CURSOR_CACHE_LIMIT) {
		const first = checkpoints.keys().next().value;
		if (first === undefined) break;
		checkpoints.delete(first);
	}
};

export const getNearestAppviewCursorCheckpoint = (
	cacheKey: string,
	offset: number,
): AppviewCursorCheckpoint => {
	const checkpoints = appviewPageCursorCache.get(cacheKey);
	const start: AppviewCursorCheckpoint = {
		matchingSeen: 0,
		scannedSeen: 0,
		exhausted: false,
	};

	if (!checkpoints) {
		return start;
	}

	let nearest = start;
	for (const checkpoint of checkpoints.values()) {
		if (checkpoint.matchingSeen > offset) {
			continue;
		}

		if (
			checkpoint.matchingSeen > nearest.matchingSeen ||
			(
				checkpoint.matchingSeen === nearest.matchingSeen &&
				checkpoint.scannedSeen > nearest.scannedSeen
			)
		) {
			nearest = checkpoint;
		}
	}

	return nearest;
};
