import { LinkedList } from "./array.js";
import { fromThreadBoundaryError, toThreadBoundaryError } from "./error.js";
export { IteratorEventType };
var IteratorEventType;
(function (IteratorEventType) {
    IteratorEventType["next"] = "iterator.next";
    IteratorEventType["done"] = "iterator.done";
    IteratorEventType["error"] = "iterator.error";
})(IteratorEventType || (IteratorEventType = {}));
export class AsyncIterableBridgeCaller {
    events;
    nextRequestId = 0;
    // TODO: Consider expiring the requests after no reply for long enough, t
    pending = new Map();
    constructor(events) {
        this.events = events;
        events.onResponse(this.onResponse.bind(this));
    }
    get pendingCount() {
        return this.pending.size;
    }
    getAsyncIterable(callArgs) {
        const self = this;
        return {
            [Symbol.asyncIterator]() {
                const id = self.nextRequestId++;
                const req = {
                    items: new LinkedList(),
                    done: false,
                    error: null,
                    onNext: null,
                };
                self.pending.set(id, req);
                self.events.emitRequest({
                    callArgs,
                    id,
                });
                return {
                    async next() {
                        while (true) {
                            const item = req.items.shift();
                            if (item !== null) {
                                return { value: item, done: false };
                            }
                            if (req.error) {
                                throw req.error;
                            }
                            if (req.done) {
                                // Is it correct to return undefined on done: true?
                                return { value: undefined, done: true };
                            }
                            await new Promise((resolve) => {
                                req.onNext = resolve;
                            });
                        }
                    },
                    async return() {
                        // This will be reached if the consumer called 'break' or 'return' early in the loop.
                        self.pending.delete(id);
                        return { value: undefined, done: true };
                    },
                };
            },
        };
    }
    onResponse(data) {
        const req = this.pending.get(data.id);
        if (!req) {
            // TODO: Log or track, can happen if consumer returns source early
            return;
        }
        switch (data.type) {
            case IteratorEventType.done:
                // What if it's already done?
                req.done = true;
                // Do not expect more responses
                this.pending.delete(data.id);
                break;
            case IteratorEventType.error:
                // What if there's already an error?
                req.error = fromThreadBoundaryError(data.error);
                // Do not expect more responses
                this.pending.delete(data.id);
                break;
            case IteratorEventType.next:
                // Should check that's it's already done or error?
                req.items.push(data.item);
                break;
        }
        req.onNext?.();
    }
}
export class AsyncIterableBridgeHandler {
    events;
    handler;
    constructor(events, handler) {
        this.events = events;
        this.handler = handler;
        events.onRequest(this.onRequest.bind(this));
    }
    async onRequest(data) {
        try {
            for await (const item of this.handler(data.callArgs)) {
                this.events.emitResponse({
                    type: IteratorEventType.next,
                    id: data.id,
                    item,
                });
            }
            this.events.emitResponse({
                type: IteratorEventType.done,
                id: data.id,
            });
        }
        catch (e) {
            this.events.emitResponse({
                type: IteratorEventType.error,
                id: data.id,
                error: toThreadBoundaryError(e),
            });
        }
    }
}
//# sourceMappingURL=asyncIterableToEvents.js.map