import { uuid6 } from "./id.js";
import { ERROR, INTERRUPT, RESUME, SCHEDULED } from "./serde/types.js";
import { JsonPlusSerializer } from "./serde/jsonplus.js";
//#region src/base.ts
function deepCopy(obj) {
	if (typeof obj !== "object" || obj === null) return obj;
	const newObj = Array.isArray(obj) ? [] : {};
	for (const key in obj) if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = deepCopy(obj[key]);
	return newObj;
}
/** @hidden */
function emptyCheckpoint() {
	return {
		v: 4,
		id: uuid6(0),
		ts: (/* @__PURE__ */ new Date()).toISOString(),
		channel_values: {},
		channel_versions: {},
		versions_seen: {}
	};
}
/** @hidden */
function copyCheckpoint(checkpoint) {
	return {
		v: checkpoint.v,
		id: checkpoint.id,
		ts: checkpoint.ts,
		channel_values: { ...checkpoint.channel_values ?? {} },
		channel_versions: { ...checkpoint.channel_versions ?? {} },
		versions_seen: deepCopy(checkpoint.versions_seen ?? {})
	};
}
var BaseCheckpointSaver = class {
	serde = new JsonPlusSerializer();
	constructor(serde) {
		this.serde = serde || this.serde;
	}
	/**
	* Prevent `JSON.stringify` from traversing backend clients (e.g. pg Pool
	* timers) when a checkpointer is present in runnable `configurable`.
	*/
	toJSON() {
		return `[${this.constructor.name}]`;
	}
	async get(config) {
		const value = await this.getTuple(config);
		return value ? value.checkpoint : void 0;
	}
	/**
	* Walk the parent chain returning per-channel writes + seed, used to
	* reconstruct `DeltaChannel` state from `checkpoint_writes`.
	*
	* For each requested channel, walks ancestors of the checkpoint identified
	* by `config` (following `parentConfig`) and accumulates the pending writes
	* for that channel. The walk terminates per-channel at the nearest ancestor
	* whose `channel_values[ch]` is populated; that value is returned as `seed`.
	* If the walk reaches the root without finding a stored value, `seed` is
	* omitted from that channel's entry — the consumer treats the absence as
	* "start empty".
	*
	* Walks the parent chain (not `list({ before })`): for forked threads, only
	* on-path ancestors contribute.
	*
	* The default implementation walks `getTuple` + `parentConfig` once for all
	* channels — each ancestor visited once, not once per channel. Savers with
	* direct storage access (e.g. `MemorySaver`) override for performance; the
	* return contract is fixed here.
	*
	* @remarks Beta. The signature, return shape, and interaction with
	* `DeltaSnapshot` blobs may change. Override at your own risk; the default
	* implementation will continue to work against the public
	* `BaseCheckpointSaver` contract.
	*
	* @param options.config Configuration identifying the target checkpoint.
	* @param options.channels Channel names to walk for. Empty → empty mapping.
	* @returns Per-channel {@link DeltaChannelHistory} for every requested name.
	*/
	async getDeltaChannelHistory(options) {
		const { config, channels } = options;
		if (channels.length === 0) return {};
		const collectedByCh = {};
		const seedByCh = {};
		const remaining = new Set(channels);
		for (const ch of channels) collectedByCh[ch] = [];
		let cursorConfig = (await this.getTuple(config))?.parentConfig;
		while (cursorConfig != null && remaining.size > 0) {
			const tup = await this.getTuple(cursorConfig);
			if (tup === void 0) break;
			if (tup.pendingWrites && tup.pendingWrites.length > 0) {
				const perChannel = {};
				for (const write of tup.pendingWrites) {
					const ch = write[1];
					if (remaining.has(ch)) (perChannel[ch] ??= []).push(write);
				}
				for (const ch of Object.keys(perChannel)) {
					const block = perChannel[ch];
					block.sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
					for (let i = block.length - 1; i >= 0; i -= 1) collectedByCh[ch].push(block[i]);
				}
			}
			for (const ch of Array.from(remaining)) if (Object.prototype.hasOwnProperty.call(tup.checkpoint.channel_values, ch)) {
				seedByCh[ch] = tup.checkpoint.channel_values[ch];
				remaining.delete(ch);
			}
			cursorConfig = tup.parentConfig;
		}
		const result = {};
		for (const ch of channels) {
			const entry = { writes: collectedByCh[ch].slice().reverse() };
			if (Object.prototype.hasOwnProperty.call(seedByCh, ch)) entry.seed = seedByCh[ch];
			result[ch] = entry;
		}
		return result;
	}
	/**
	* Generate the next version ID for a channel.
	*
	* Default is to use integer versions, incrementing by 1. If you override, you can use str/int/float versions,
	* as long as they are monotonically increasing.
	*/
	getNextVersion(current) {
		if (typeof current === "string") throw new Error("Please override this method to use string versions.");
		return current !== void 0 && typeof current === "number" ? current + 1 : 1;
	}
};
function compareChannelVersions(a, b) {
	if (typeof a === "number" && typeof b === "number") return Math.sign(a - b);
	return String(a).localeCompare(String(b));
}
function maxChannelVersion(...versions) {
	return versions.reduce((max, version, idx) => {
		if (idx === 0) return version;
		return compareChannelVersions(max, version) >= 0 ? max : version;
	});
}
/**
* Mapping from error type to error index.
* Regular writes just map to their index in the list of writes being saved.
* Special writes (e.g. errors) map to negative indices, to avoid those writes from
* conflicting with regular writes.
* Each Checkpointer implementation should use this mapping in put_writes.
*/
const WRITES_IDX_MAP = {
	[ERROR]: -1,
	[SCHEDULED]: -2,
	[INTERRUPT]: -3,
	[RESUME]: -4
};
/**
* Metadata keys that are LangGraph's internal framework bookkeeping and
* should not be surfaced as user-meaningful metadata.
*
* Consumed by stream handlers (e.g. the `tasks` debug stream) to drop
* framework keys — which are redundant with a task's own fields and
* namespace — while keeping keys like `lc_agent_name`, `ls_integration`,
* and user-supplied metadata.
*/
const EXCLUDED_METADATA_KEYS = new Set([
	"thread_id",
	"checkpoint_id",
	"checkpoint_ns",
	"checkpoint_map",
	"langgraph_step",
	"langgraph_node",
	"langgraph_triggers",
	"langgraph_path",
	"langgraph_checkpoint_ns"
]);
function getCheckpointId(config) {
	return config.configurable?.checkpoint_id || config.configurable?.thread_ts || "";
}
//#endregion
export { BaseCheckpointSaver, EXCLUDED_METADATA_KEYS, WRITES_IDX_MAP, compareChannelVersions, copyCheckpoint, deepCopy, emptyCheckpoint, getCheckpointId, maxChannelVersion };

//# sourceMappingURL=base.js.map