"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.nextValue = void 0;
exports.iteratorFinally = iteratorFinally;
function iteratorFinally(iterable, onFinally) {
    if (!onFinally) {
        // noop if no onFinally
        return (async function* Noop() {
            yield* iterable;
        }());
    }
    let started = false;
    let ended = false;
    let error;
    let onFinallyTask;
    // ensure finally only runs once
    let onFinallyOnce = (err) => {
        if (!onFinallyTask) {
            // eslint-disable-next-line promise/no-promise-in-callback
            onFinallyTask = Promise.resolve(onFinally(err)).finally(() => {
                onFinallyOnce = () => { };
            });
        }
        return onFinallyTask;
    };
    // wraps return/throw to call onFinally even if generator was never started
    function handleFinally(originalFn) {
        return async (...args) => {
            // * only await onFinally if not started
            // * call original return/throw *immediately* in either case
            // Otherwise:
            // * if started, iterator won't stop until onFinally finishes
            // * if not started, iterator can still be started before onFinally finishes
            // This function handles both cases, but note here as a reminder.
            ended = true;
            if (started) {
                return originalFn(...args);
            }
            // otherwise iteration can still start if finally function still pending
            try {
                return await originalFn(...args);
            }
            catch (err) {
                if (!error) {
                    error = err;
                }
                throw err;
            }
            finally {
                await onFinallyOnce(error);
            }
        };
    }
    // wrap in generator to track if generator was started
    const gen = (async function* TrackStarted() {
        started = true;
        try {
            yield* iterable;
        }
        catch (err) {
            if (!error) {
                error = err;
            }
            throw err;
        }
        finally {
            await onFinallyOnce(error);
        }
    }());
    const it = gen[Symbol.asyncIterator].bind(gen);
    let g;
    // copy cancel api across if exists
    if ('cancel' in iterable) {
        g = Object.assign(gen, {
            cancel: (err) => iterable.cancel(err),
            isCancelled: () => iterable.isCancelled()
        });
    }
    else {
        g = gen;
    }
    // replace generator methods
    return Object.assign(g, {
        return: handleFinally(g.return.bind(g)),
        throw: handleFinally(g.throw.bind(g)),
        [Symbol.asyncIterator]() {
            // if ended before started
            if (ended && !started) {
                // return a generator that simply runs finally script (once)
                return (async function* generatorRunFinally() {
                    try {
                        // NOTE: native generators do not throw if gen.throw(err) called before started
                        // so we should do the same here
                        if ('return' in iterable) {
                            await iterable.return(undefined); // runs onFinally for nested iterable
                        }
                    }
                    finally {
                        await onFinallyOnce();
                    }
                }());
            }
            return it();
        }
    });
}
const nextValue = async (source) => {
    const item = source.next();
    return (await item).value;
};
exports.nextValue = nextValue;
//# sourceMappingURL=iterators.js.map