"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AggregationCursor = void 0;
const aggregate_1 = require("../operations/aggregate");
const execute_operation_1 = require("../operations/execute_operation");
const utils_1 = require("../utils");
const abstract_cursor_1 = require("./abstract_cursor");
/** @internal */
const kPipeline = Symbol('pipeline');
/** @internal */
const kOptions = Symbol('options');
/**
 * The **AggregationCursor** class is an internal class that embodies an aggregation cursor on MongoDB
 * allowing for iteration over the results returned from the underlying query. It supports
 * one by one document iteration, conversion to an array or can be iterated as a Node 4.X
 * or higher stream
 * @public
 */
class AggregationCursor extends abstract_cursor_1.AbstractCursor {
    /** @internal */
    constructor(topology, namespace, pipeline = [], options = {}) {
        super(topology, namespace, options);
        this[kPipeline] = pipeline;
        this[kOptions] = options;
    }
    get pipeline() {
        return this[kPipeline];
    }
    clone() {
        const clonedOptions = (0, utils_1.mergeOptions)({}, this[kOptions]);
        delete clonedOptions.session;
        return new AggregationCursor(this.topology, this.namespace, this[kPipeline], {
            ...clonedOptions
        });
    }
    map(transform) {
        return super.map(transform);
    }
    /** @internal */
    _initialize(session, callback) {
        const aggregateOperation = new aggregate_1.AggregateOperation(this.namespace, this[kPipeline], {
            ...this[kOptions],
            ...this.cursorOptions,
            session
        });
        (0, execute_operation_1.executeOperation)(this.topology, aggregateOperation, (err, response) => {
            if (err || response == null)
                return callback(err);
            // TODO: NODE-2882
            callback(undefined, { server: aggregateOperation.server, session, response });
        });
    }
    explain(verbosity, callback) {
        if (typeof verbosity === 'function')
            (callback = verbosity), (verbosity = true);
        if (verbosity == null)
            verbosity = true;
        return (0, execute_operation_1.executeOperation)(this.topology, new aggregate_1.AggregateOperation(this.namespace, this[kPipeline], {
            ...this[kOptions],
            ...this.cursorOptions,
            explain: verbosity
        }), callback);
    }
    group($group) {
        (0, abstract_cursor_1.assertUninitialized)(this);
        this[kPipeline].push({ $group });
        return this;
    }
    /** Add a limit stage to the aggregation pipeline */
    limit($limit) {
        (0, abstract_cursor_1.assertUninitialized)(this);
        this[kPipeline].push({ $limit });
        return this;
    }
    /** Add a match stage to the aggregation pipeline */
    match($match) {
        (0, abstract_cursor_1.assertUninitialized)(this);
        this[kPipeline].push({ $match });
        return this;
    }
    /** Add an out stage to the aggregation pipeline */
    out($out) {
        (0, abstract_cursor_1.assertUninitialized)(this);
        this[kPipeline].push({ $out });
        return this;
    }
    /**
     * Add a project stage to the aggregation pipeline
     *
     * @remarks
     * In order to strictly type this function you must provide an interface
     * that represents the effect of your projection on the result documents.
     *
     * By default chaining a projection to your cursor changes the returned type to the generic {@link Document} type.
     * You should specify a parameterized type to have assertions on your final results.
     *
     * @example
     * ```typescript
     * // Best way
     * const docs: AggregationCursor<{ a: number }> = cursor.project<{ a: number }>({ _id: 0, a: true });
     * // Flexible way
     * const docs: AggregationCursor<Document> = cursor.project({ _id: 0, a: true });
     * ```
     *
     * @remarks
     * In order to strictly type this function you must provide an interface
     * that represents the effect of your projection on the result documents.
     *
     * **Note for Typescript Users:** adding a transform changes the return type of the iteration of this cursor,
     * it **does not** return a new instance of a cursor. This means when calling project,
     * you should always assign the result to a new variable in order to get a correctly typed cursor variable.
     * Take note of the following example:
     *
     * @example
     * ```typescript
     * const cursor: AggregationCursor<{ a: number; b: string }> = coll.aggregate([]);
     * const projectCursor = cursor.project<{ a: number }>({ _id: 0, a: true });
     * const aPropOnlyArray: {a: number}[] = await projectCursor.toArray();
     *
     * // or always use chaining and save the final cursor
     *
     * const cursor = coll.aggregate().project<{ a: string }>({
     *   _id: 0,
     *   a: { $convert: { input: '$a', to: 'string' }
     * }});
     * ```
     */
    project($project) {
        (0, abstract_cursor_1.assertUninitialized)(this);
        this[kPipeline].push({ $project });
        return this;
    }
    /** Add a lookup stage to the aggregation pipeline */
    lookup($lookup) {
        (0, abstract_cursor_1.assertUninitialized)(this);
        this[kPipeline].push({ $lookup });
        return this;
    }
    /** Add a redact stage to the aggregation pipeline */
    redact($redact) {
        (0, abstract_cursor_1.assertUninitialized)(this);
        this[kPipeline].push({ $redact });
        return this;
    }
    /** Add a skip stage to the aggregation pipeline */
    skip($skip) {
        (0, abstract_cursor_1.assertUninitialized)(this);
        this[kPipeline].push({ $skip });
        return this;
    }
    /** Add a sort stage to the aggregation pipeline */
    sort($sort) {
        (0, abstract_cursor_1.assertUninitialized)(this);
        this[kPipeline].push({ $sort });
        return this;
    }
    /** Add a unwind stage to the aggregation pipeline */
    unwind($unwind) {
        (0, abstract_cursor_1.assertUninitialized)(this);
        this[kPipeline].push({ $unwind });
        return this;
    }
    // deprecated methods
    /** @deprecated Add a geoNear stage to the aggregation pipeline */
    geoNear($geoNear) {
        (0, abstract_cursor_1.assertUninitialized)(this);
        this[kPipeline].push({ $geoNear });
        return this;
    }
}
exports.AggregationCursor = AggregationCursor;
//# sourceMappingURL=aggregation_cursor.js.map