var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var setWindowFields_exports = {};
__export(setWindowFields_exports, {
  $setWindowFields: () => $setWindowFields
});
module.exports = __toCommonJS(setWindowFields_exports);
var import_core = require("../../core");
var import_lazy = require("../../lazy");
var import_util = require("../../util");
var import_function = require("../expression/custom/function");
var import_dateAdd = require("../expression/date/dateAdd");
var import_internal = require("./_internal");
var import_addFields = require("./addFields");
var import_group = require("./group");
var import_sort = require("./sort");
const SORT_REQUIRED_OPS = /* @__PURE__ */ new Set([
  "$denseRank",
  "$documentNumber",
  "$first",
  "$last",
  "$linearFill",
  "$rank",
  "$shift"
]);
const WINDOW_UNBOUNDED_OPS = /* @__PURE__ */ new Set([
  "$denseRank",
  "$expMovingAvg",
  "$linearFill",
  "$locf",
  "$rank",
  "$shift"
]);
const $setWindowFields = (collection, expr, options) => {
  options = (0, import_core.initOptions)(options);
  options.context.addExpressionOps({ $function: import_function.$function });
  for (const outputExpr of Object.values(expr.output)) {
    const keys = Object.keys(outputExpr);
    const op = keys.find(import_util.isOperator);
    (0, import_util.assert)(
      !!(0, import_core.getOperator)("window", op, options) || !!(0, import_core.getOperator)("accumulator", op, options),
      `'${op}' is not a valid window operator`
    );
    (0, import_util.assert)(
      keys.length > 0 && keys.length <= 2 && (keys.length == 1 || keys.includes("window")),
      "'output' option should have a single window operator."
    );
    if (outputExpr?.window) {
      const { documents, range } = outputExpr.window;
      (0, import_util.assert)(
        !!documents && !range || !documents && !!range || !documents && !range,
        "'window' option supports only one of 'documents' or 'range'."
      );
    }
  }
  if (expr.sortBy) {
    collection = (0, import_sort.$sort)(collection, expr.sortBy, options);
  }
  collection = (0, import_group.$group)(
    collection,
    {
      _id: expr.partitionBy,
      items: { $push: "$$CURRENT" }
    },
    options
  );
  return collection.transform((partitions) => {
    const iterators = [];
    const outputConfig = [];
    for (const [field, outputExpr] of Object.entries(expr.output)) {
      const op = Object.keys(outputExpr).find(import_util.isOperator);
      const config = {
        operatorName: op,
        func: {
          left: (0, import_core.getOperator)("accumulator", op, options),
          right: (0, import_core.getOperator)("window", op, options)
        },
        args: outputExpr[op],
        field,
        window: outputExpr.window
      };
      (0, import_util.assert)(
        !!expr.sortBy || !(SORT_REQUIRED_OPS.has(op) || !config.window),
        `${SORT_REQUIRED_OPS.has(op) ? `'${op}'` : "bounded window operation"} requires a sortBy.`
      );
      (0, import_util.assert)(
        !config.window || !WINDOW_UNBOUNDED_OPS.has(op),
        `${op} does not accept a 'window' field.`
      );
      outputConfig.push(config);
    }
    partitions.forEach((group) => {
      const items = group.items;
      let iterator = (0, import_lazy.Lazy)(items);
      const windowResultMap = {};
      for (const config of outputConfig) {
        const { func, args, field, window } = config;
        const makeResultFunc = (getItemsFn) => {
          let index = -1;
          return (obj) => {
            ++index;
            if (func.left) {
              return func.left(getItemsFn(obj, index), args, options);
            } else if (func.right) {
              return func.right(
                obj,
                getItemsFn(obj, index),
                {
                  parentExpr: expr,
                  inputExpr: args,
                  documentNumber: index + 1,
                  field
                },
                // must use raw options only since it operates over a collection.
                options
              );
            }
          };
        };
        if (window) {
          const { documents, range, unit } = window;
          const boundary = documents || range;
          if (!(0, import_internal.isUnbounded)(window)) {
            const [begin, end] = boundary;
            const toBeginIndex = (currentIndex) => {
              if (begin == "current") return currentIndex;
              if (begin == "unbounded") return 0;
              return Math.max(begin + currentIndex, 0);
            };
            const toEndIndex = (currentIndex) => {
              if (end == "current") return currentIndex + 1;
              if (end == "unbounded") return items.length;
              return end + currentIndex + 1;
            };
            const getItems = (current, index) => {
              if (!!documents || boundary.every(import_util.isString)) {
                return items.slice(toBeginIndex(index), toEndIndex(index));
              }
              const sortKey = Object.keys(expr.sortBy)[0];
              let lower;
              let upper;
              if (unit) {
                const getTime = (amount) => {
                  return (0, import_dateAdd.$dateAdd)(
                    current,
                    {
                      startDate: new Date(current[sortKey]),
                      unit,
                      amount
                    },
                    options
                  ).getTime();
                };
                lower = (0, import_util.isNumber)(begin) ? getTime(begin) : -Infinity;
                upper = (0, import_util.isNumber)(end) ? getTime(end) : Infinity;
              } else {
                const currentValue = current[sortKey];
                lower = (0, import_util.isNumber)(begin) ? currentValue + begin : -Infinity;
                upper = (0, import_util.isNumber)(end) ? currentValue + end : Infinity;
              }
              let array = items;
              if (begin == "current") array = items.slice(index);
              if (end == "current") array = items.slice(0, index + 1);
              return array.filter((o) => {
                const n = +o[sortKey];
                return n >= lower && n <= upper;
              });
            };
            windowResultMap[field] = makeResultFunc(getItems);
          }
        }
        if (!windowResultMap[field]) {
          windowResultMap[field] = makeResultFunc((_) => items);
        }
        iterator = (0, import_addFields.$addFields)(
          iterator,
          {
            [field]: {
              $function: {
                body: (obj) => windowResultMap[field](obj),
                args: ["$$CURRENT"]
              }
            }
          },
          options
        );
      }
      iterators.push(iterator);
    });
    return (0, import_lazy.concat)(...iterators);
  });
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  $setWindowFields
});
