import { v4 } from "@langchain/core/utils/uuid";
import { BaseMessage, RemoveMessage, coerceMessageLikeToMessage } from "@langchain/core/messages";
//#region src/graph/messages_reducer.ts
/**
* Special value that signifies the intent to remove all previous messages in the state reducer.
* Used as the unique identifier for a `RemoveMessage` instance which, when encountered,
* causes all prior messages to be discarded, leaving only those following this marker.
*/
const REMOVE_ALL_MESSAGES = "__remove_all__";
/**
* Reducer function for combining two sets of messages in LangGraph's state system.
*
* This reducer handles several tasks:
* 1. Normalizes both `left` and `right` message inputs to arrays.
* 2. Coerces any message-like objects into real `BaseMessage` instances.
* 3. Ensures all messages have unique, stable IDs by generating missing ones.
* 4. If a `RemoveMessage` instance is encountered in `right` with the ID `REMOVE_ALL_MESSAGES`,
*    all previous messages are discarded and only the subsequent messages in `right` are returned.
* 5. Otherwise, merges `left` and `right` messages together following these rules:
*    - If a message in `right` shares an ID with a message in `left`:
*      - If it is a `RemoveMessage`, that message (by ID) is marked for removal.
*      - If it is a normal message, it replaces the message with the same ID from `left`.
*    - If a message in `right` **does not exist** in `left`:
*      - If it is a `RemoveMessage`, this is considered an error (cannot remove non-existent ID).
*      - Otherwise, the message is appended.
*    - Messages flagged for removal are omitted from the final output.
*
* @param left - The existing array (or single message) of messages from current state.
* @param right - The new array (or single message) of messages to be applied.
* @returns A new array of `BaseMessage` objects representing the updated state.
*
* @throws Error if a `RemoveMessage` is used to delete a message with an ID that does not exist in the merged list.
*
* @example
* ```ts
* const msg1 = new AIMessage("hello");
* const msg2 = new HumanMessage("hi");
* const removal = new RemoveMessage({ id: msg1.id });
* const newState = messagesStateReducer([msg1], [msg2, removal]);
* // newState will only contain msg2 (msg1 is removed)
* ```
*/
function messagesStateReducer(left, right) {
	const leftArray = Array.isArray(left) ? left : [left];
	const rightArray = Array.isArray(right) ? right : [right];
	const leftMessages = leftArray.map(coerceMessageLikeToMessage);
	const rightMessages = rightArray.map(coerceMessageLikeToMessage);
	for (const m of leftMessages) if (m.id === null || m.id === void 0) {
		m.id = v4();
		m.lc_kwargs.id = m.id;
	}
	let removeAllIdx;
	for (let i = 0; i < rightMessages.length; i += 1) {
		const m = rightMessages[i];
		if (m.id === null || m.id === void 0) {
			m.id = v4();
			m.lc_kwargs.id = m.id;
		}
		if (RemoveMessage.isInstance(m) && m.id === "__remove_all__") removeAllIdx = i;
	}
	if (removeAllIdx != null) return rightMessages.slice(removeAllIdx + 1);
	const merged = [...leftMessages];
	const mergedById = new Map(merged.map((m, i) => [m.id, i]));
	const idsToRemove = /* @__PURE__ */ new Set();
	for (const m of rightMessages) {
		const existingIdx = mergedById.get(m.id);
		if (existingIdx !== void 0) if (RemoveMessage.isInstance(m)) idsToRemove.add(m.id);
		else {
			idsToRemove.delete(m.id);
			merged[existingIdx] = m;
		}
		else {
			if (RemoveMessage.isInstance(m)) throw new Error(`Attempting to delete a message with an ID that doesn't exist ('${m.id}')`);
			mergedById.set(m.id, merged.length);
			merged.push(m);
		}
	}
	return merged.filter((m) => !idsToRemove.has(m.id));
}
/**
* **Experimental.** Batch reducer for use with `DeltaChannel`.
*
* Processes all writes in one pass — dedup by ID and `RemoveMessage`
* tombstoning — without calling {@link messagesStateReducer}.
*
* This reducer is batching-invariant, as required by `DeltaChannel`:
* `reducer(reducer(state, xs), ys) === reducer(state, xs.concat(ys))`.
*
* A `RemoveMessage` carrying the {@link REMOVE_ALL_MESSAGES} sentinel id
* clears all messages accumulated so far (prior state plus earlier writes in
* the same batch) and keeps only the messages that follow it, mirroring
* {@link messagesStateReducer}. Clearing happens in the same single linear
* pass, so the batching-invariant still holds.
*
* Raw object / string inputs are coerced to typed `BaseMessage` objects so
* that HTTP-driven graphs work without a separate coercion step. This is not
* full {@link messagesStateReducer} parity — unknown-id `RemoveMessage`
* errors and missing-id UUID assignment are not handled here.
*
* @param state - The current accumulated list of messages.
* @param writes - Batch of writes, each a single message-like or an array.
* @returns The new accumulated list of messages.
*
* @example
* ```typescript
* import { DeltaChannel, messagesDeltaReducer } from "@langchain/langgraph";
*
* const channel = new DeltaChannel(messagesDeltaReducer);
* ```
*/
function messagesDeltaReducer(state, writes) {
	const flat = [];
	for (const w of writes) if (Array.isArray(w)) flat.push(...w);
	else flat.push(w);
	const stateMsgs = state.length > 0 && BaseMessage.isInstance(state[0]) ? state : state.map(coerceMessageLikeToMessage);
	const msgs = flat.map(coerceMessageLikeToMessage);
	const index = /* @__PURE__ */ new Map();
	for (let i = 0; i < stateMsgs.length; i += 1) {
		const mid = stateMsgs[i].id;
		if (mid != null) index.set(mid, i);
	}
	const result = [...stateMsgs];
	for (const msg of msgs) {
		const mid = msg.id;
		if (RemoveMessage.isInstance(msg) && mid === "__remove_all__") {
			result.length = 0;
			index.clear();
		} else if (mid == null) result.push(msg);
		else if (RemoveMessage.isInstance(msg)) {
			if (index.has(mid)) {
				result[index.get(mid)] = null;
				index.delete(mid);
			}
		} else if (index.has(mid)) result[index.get(mid)] = msg;
		else {
			index.set(mid, result.length);
			result.push(msg);
		}
	}
	return result.filter((m) => m !== null);
}
//#endregion
export { REMOVE_ALL_MESSAGES, messagesDeltaReducer, messagesStateReducer };

//# sourceMappingURL=messages_reducer.js.map