/**
 * @license
 * Copyright 2026 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
/**
 * A mock implementation of AgentSession for testing.
 * Allows queuing responses that will be yielded when send() is called.
 */
export class MockAgentSession {
    _events = [];
    _responses = [];
    _streams = new Map();
    _activeStreamIds = new Set();
    _lastStreamId;
    _nextEventId = 1;
    _streamResolvers = new Map();
    title;
    model;
    config;
    constructor(initialEvents = []) {
        this._events = [...initialEvents];
    }
    /**
     * All events that have occurred in this session so far.
     */
    get events() {
        return this._events;
    }
    /**
     * Queues a sequence of events to be "emitted" by the agent in response to the
     * next send() call.
     */
    pushResponse(events, options) {
        // We store them as data and normalize them when send() is called
        this._responses.push({ events, options });
    }
    /**
     * Appends events to an existing stream and notifies any waiting listeners.
     */
    pushToStream(streamId, events, options) {
        const stream = this._streams.get(streamId);
        if (!stream) {
            throw new Error(`Stream not found: ${streamId}`);
        }
        const now = new Date().toISOString();
        for (const eventData of events) {
            const event = {
                ...eventData,
                id: eventData.id ?? `e-${this._nextEventId++}`,
                timestamp: eventData.timestamp ?? now,
                streamId: eventData.streamId ?? streamId,
            };
            stream.push(event);
        }
        if (options?.close &&
            !events.some((eventData) => eventData.type === 'stream_end')) {
            stream.push({
                id: `e-${this._nextEventId++}`,
                timestamp: now,
                streamId,
                type: 'stream_end',
                reason: 'completed',
            });
        }
        this._notify(streamId);
    }
    _notify(streamId) {
        const resolvers = this._streamResolvers.get(streamId);
        if (resolvers) {
            this._streamResolvers.delete(streamId);
            for (const resolve of resolvers)
                resolve();
        }
    }
    async send(payload) {
        const { events: response, options } = this._responses.shift() ?? {
            events: [],
        };
        const streamId = response[0]?.streamId ?? `mock-stream-${this._streams.size + 1}`;
        const now = new Date().toISOString();
        if (!response.some((eventData) => eventData.type === 'stream_start')) {
            response.unshift({
                type: 'stream_start',
                streamId,
            });
        }
        const startIndex = response.findIndex((eventData) => eventData.type === 'stream_start');
        if ('message' in payload && payload.message) {
            response.splice(startIndex + 1, 0, {
                type: 'message',
                role: 'user',
                content: payload.message,
                _meta: payload._meta,
            });
        }
        else if ('elicitations' in payload && payload.elicitations) {
            payload.elicitations.forEach((elicitation, i) => {
                response.splice(startIndex + 1 + i, 0, {
                    type: 'elicitation_response',
                    ...elicitation,
                    _meta: payload._meta,
                });
            });
        }
        else if ('update' in payload && payload.update) {
            if (payload.update.title)
                this.title = payload.update.title;
            if (payload.update.model)
                this.model = payload.update.model;
            if (payload.update.config) {
                this.config = payload.update.config;
            }
            response.splice(startIndex + 1, 0, {
                type: 'session_update',
                ...payload.update,
                _meta: payload._meta,
            });
        }
        else if ('action' in payload && payload.action) {
            throw new Error(`Actions not supported in MockAgentSession: ${payload.action.type}`);
        }
        if (!options?.keepOpen &&
            !response.some((eventData) => eventData.type === 'stream_end')) {
            response.push({
                type: 'stream_end',
                reason: 'completed',
                streamId,
            });
        }
        const normalizedResponse = [];
        for (const eventData of response) {
            const event = {
                ...eventData,
                id: eventData.id ?? `e-${this._nextEventId++}`,
                timestamp: eventData.timestamp ?? now,
                streamId: eventData.streamId ?? streamId,
            };
            normalizedResponse.push(event);
        }
        this._streams.set(streamId, normalizedResponse);
        this._activeStreamIds.add(streamId);
        this._lastStreamId = streamId;
        return { streamId };
    }
    async *stream(options) {
        let streamId = options?.streamId;
        if (options?.eventId) {
            const event = this._events.find((eventData) => eventData.id === options.eventId);
            if (!event) {
                throw new Error(`Event not found: ${options.eventId}`);
            }
            streamId = streamId ?? event.streamId;
        }
        streamId = streamId ?? this._lastStreamId;
        if (!streamId) {
            return;
        }
        const events = this._streams.get(streamId);
        if (!events) {
            throw new Error(`Stream not found: ${streamId}`);
        }
        let i = 0;
        if (options?.eventId) {
            const idx = events.findIndex((eventData) => eventData.id === options.eventId);
            if (idx !== -1) {
                i = idx + 1;
            }
            else {
                // This should theoretically not happen if the event was found in this._events
                // but the trajectories match.
                throw new Error(`Event ${options.eventId} not found in stream ${streamId}`);
            }
        }
        while (true) {
            if (i < events.length) {
                const event = events[i++];
                // Add to session trajectory if not already present
                if (!this._events.some((eventData) => eventData.id === event.id)) {
                    this._events.push(event);
                }
                yield event;
                // If it's a stream_end, we're done with this stream
                if (event.type === 'stream_end') {
                    this._activeStreamIds.delete(streamId);
                    return;
                }
            }
            else {
                // No more events in the array currently. Check if we're still active.
                if (!this._activeStreamIds.has(streamId)) {
                    // If we weren't terminated by a stream_end but we're no longer active,
                    // it was an abort.
                    const abortEvent = {
                        id: `e-${this._nextEventId++}`,
                        timestamp: new Date().toISOString(),
                        streamId,
                        type: 'stream_end',
                        reason: 'aborted',
                    };
                    if (!this._events.some((e) => e.id === abortEvent.id)) {
                        this._events.push(abortEvent);
                    }
                    yield abortEvent;
                    return;
                }
                // Wait for notification (new event or abort)
                await new Promise((resolve) => {
                    const resolvers = this._streamResolvers.get(streamId) ?? [];
                    resolvers.push(resolve);
                    this._streamResolvers.set(streamId, resolvers);
                });
            }
        }
    }
    async abort() {
        if (this._lastStreamId) {
            const streamId = this._lastStreamId;
            this._activeStreamIds.delete(streamId);
            this._notify(streamId);
        }
    }
}
//# sourceMappingURL=mock.js.map