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 core_exports = {};
__export(core_exports, {
  ComputeOptions: () => ComputeOptions,
  Context: () => Context,
  OpType: () => OpType,
  ProcessingMode: () => ProcessingMode,
  computeValue: () => computeValue,
  getOperator: () => getOperator,
  initOptions: () => initOptions,
  redact: () => redact,
  useOperators: () => useOperators
});
module.exports = __toCommonJS(core_exports);
var import_util = require("./util");
var ProcessingMode = /* @__PURE__ */ ((ProcessingMode2) => {
  ProcessingMode2[ProcessingMode2["CLONE_OFF"] = 0] = "CLONE_OFF";
  ProcessingMode2[ProcessingMode2["CLONE_INPUT"] = 1] = "CLONE_INPUT";
  ProcessingMode2[ProcessingMode2["CLONE_OUTPUT"] = 2] = "CLONE_OUTPUT";
  ProcessingMode2[ProcessingMode2["CLONE_ALL"] = 3] = "CLONE_ALL";
  return ProcessingMode2;
})(ProcessingMode || {});
class ComputeOptions {
  #options;
  /** Reference to the root object when processing subgraphs of the object. */
  #root;
  #local;
  constructor(options, root, local) {
    this.#options = options;
    this.update(root, local);
  }
  /**
   * Initialize new ComputeOptions.
   * @returns {ComputeOptions}
   */
  static init(options, root, local) {
    return !(options instanceof ComputeOptions) ? new ComputeOptions(options, root, local) : new ComputeOptions(options.#options, options.root ?? root, {
      ...options.#local,
      ...local,
      variables: Object.assign(
        {},
        options.#local?.variables,
        local?.variables
      )
    });
  }
  /**
   * Updates the internal state.
   *
   * @param root The new root context for this object.
   * @param local The new local state to merge into current if it exists.
   * @returns
   */
  update(root, local) {
    this.#root = root;
    const variables = Object.assign(
      {},
      this.#local?.variables,
      local?.variables
    );
    if (Object.keys(variables).length) {
      this.#local = { ...local, variables };
    } else {
      this.#local = local ?? {};
    }
    return this;
  }
  getOptions() {
    return Object.freeze({
      ...this.#options,
      context: Context.from(this.#options.context)
    });
  }
  get root() {
    return this.#root;
  }
  get local() {
    return this.#local;
  }
  get idKey() {
    return this.#options.idKey;
  }
  get collation() {
    return this.#options?.collation;
  }
  get processingMode() {
    return this.#options?.processingMode || 0 /* CLONE_OFF */;
  }
  get useStrictMode() {
    return this.#options?.useStrictMode;
  }
  get scriptEnabled() {
    return this.#options?.scriptEnabled;
  }
  get useGlobalContext() {
    return this.#options?.useGlobalContext;
  }
  get hashFunction() {
    return this.#options?.hashFunction;
  }
  get collectionResolver() {
    return this.#options?.collectionResolver;
  }
  get jsonSchemaValidator() {
    return this.#options?.jsonSchemaValidator;
  }
  get variables() {
    return this.#options?.variables;
  }
  get context() {
    return this.#options?.context;
  }
}
function initOptions(options) {
  return options instanceof ComputeOptions ? options.getOptions() : Object.freeze({
    idKey: "_id",
    scriptEnabled: true,
    useStrictMode: true,
    useGlobalContext: true,
    processingMode: 0 /* CLONE_OFF */,
    ...options,
    context: options?.context ? Context.from(options?.context) : Context.init()
  });
}
var OpType = /* @__PURE__ */ ((OpType2) => {
  OpType2["ACCUMULATOR"] = "accumulator";
  OpType2["EXPRESSION"] = "expression";
  OpType2["PIPELINE"] = "pipeline";
  OpType2["PROJECTION"] = "projection";
  OpType2["QUERY"] = "query";
  OpType2["WINDOW"] = "window";
  return OpType2;
})(OpType || {});
class Context {
  #operators = /* @__PURE__ */ new Map();
  constructor() {
  }
  static init() {
    return new Context();
  }
  static from(ctx) {
    const instance = Context.init();
    if ((0, import_util.isNil)(ctx)) return instance;
    ctx.#operators.forEach((v, k) => instance.addOperators(k, v));
    return instance;
  }
  addOperators(type, operators) {
    if (!this.#operators.has(type)) this.#operators.set(type, {});
    for (const [name, fn] of Object.entries(operators)) {
      if (!this.getOperator(type, name)) {
        this.#operators.get(type)[name] = fn;
      }
    }
    return this;
  }
  getOperator(type, name) {
    const ops = this.#operators.get(type) ?? {};
    return ops[name] ?? null;
  }
  addAccumulatorOps(ops) {
    return this.addOperators("accumulator" /* ACCUMULATOR */, ops);
  }
  addExpressionOps(ops) {
    return this.addOperators("expression" /* EXPRESSION */, ops);
  }
  addQueryOps(ops) {
    return this.addOperators("query" /* QUERY */, ops);
  }
  addPipelineOps(ops) {
    return this.addOperators("pipeline" /* PIPELINE */, ops);
  }
  addProjectionOps(ops) {
    return this.addOperators("projection" /* PROJECTION */, ops);
  }
  addWindowOps(ops) {
    return this.addOperators("window" /* WINDOW */, ops);
  }
}
const GC = Context.init();
const GC_REGISTRY = {
  ["accumulator" /* ACCUMULATOR */]: GC.addAccumulatorOps.bind(GC),
  ["expression" /* EXPRESSION */]: GC.addExpressionOps.bind(GC),
  ["pipeline" /* PIPELINE */]: GC.addPipelineOps.bind(GC),
  ["projection" /* PROJECTION */]: GC.addProjectionOps.bind(GC),
  ["query" /* QUERY */]: GC.addQueryOps.bind(GC),
  ["window" /* WINDOW */]: GC.addWindowOps.bind(GC)
};
function useOperators(type, operators) {
  for (const [name, fn] of Object.entries(operators)) {
    (0, import_util.assert)(
      (0, import_util.isFunction)(fn) && (0, import_util.isOperator)(name),
      `'${name}' is not a valid operator`
    );
    const currentFn = getOperator(type, name, null);
    (0, import_util.assert)(
      !currentFn || fn === currentFn,
      `${name} already exists for '${type}' operators. Cannot change operator function once registered.`
    );
  }
  GC_REGISTRY[type](operators);
}
function getOperator(type, name, options) {
  const { context: ctx, useGlobalContext: fallback } = options || {};
  const fn = ctx ? ctx.getOperator(type, name) : null;
  return !fn && fallback ? GC.getOperator(type, name) : fn;
}
function computeValue(obj, expr, operator, options) {
  const copts = ComputeOptions.init(options, obj);
  return !!operator && (0, import_util.isOperator)(operator) ? computeOperator(obj, expr, operator, copts) : computeExpression(obj, expr, copts);
}
const SYSTEM_VARS = ["$$ROOT", "$$CURRENT", "$$REMOVE", "$$NOW"];
function computeExpression(obj, expr, options) {
  if ((0, import_util.isString)(expr) && expr.length > 0 && expr[0] === "$") {
    if (REDACT_ACTIONS.includes(expr)) return expr;
    let ctx = options.root;
    const arr = expr.split(".");
    if (SYSTEM_VARS.includes(arr[0])) {
      switch (arr[0]) {
        case "$$ROOT":
          break;
        case "$$CURRENT":
          ctx = obj;
          break;
        case "$$REMOVE":
          ctx = void 0;
          break;
        case "$$NOW":
          ctx = /* @__PURE__ */ new Date();
          break;
      }
      expr = expr.slice(arr[0].length + 1);
    } else if (arr[0].slice(0, 2) === "$$") {
      ctx = Object.assign(
        {},
        // global vars
        options.variables,
        // current item is added before local variables because the binding may be changed.
        { this: obj },
        // local vars
        options?.local?.variables
      );
      const name = arr[0].slice(2);
      (0, import_util.assert)((0, import_util.has)(ctx, name), `Use of undefined variable: ${name}`);
      expr = expr.slice(2);
    } else {
      expr = expr.slice(1);
    }
    return expr === "" ? ctx : (0, import_util.resolve)(ctx, expr);
  }
  if ((0, import_util.isArray)(expr)) {
    return expr.map((item) => computeExpression(obj, item, options));
  }
  if ((0, import_util.isObject)(expr)) {
    const result = {};
    const elems = Object.entries(expr);
    for (const [key, val] of elems) {
      if ((0, import_util.isOperator)(key)) {
        (0, import_util.assert)(elems.length == 1, "expression must have single operator.");
        return computeOperator(obj, val, key, options);
      }
      result[key] = computeExpression(obj, val, options);
    }
    return result;
  }
  return expr;
}
function computeOperator(obj, expr, operator, options) {
  const callExpression = getOperator(
    "expression" /* EXPRESSION */,
    operator,
    options
  );
  if (callExpression) return callExpression(obj, expr, options);
  const callAccumulator = getOperator(
    "accumulator" /* ACCUMULATOR */,
    operator,
    options
  );
  (0, import_util.assert)(!!callAccumulator, `accumulator '${operator}' is not registered.`);
  if (!(0, import_util.isArray)(obj)) {
    obj = computeExpression(obj, expr, options);
    expr = null;
  }
  (0, import_util.assert)((0, import_util.isArray)(obj), `arguments must resolve to array for ${operator}.`);
  return callAccumulator(obj, expr, options);
}
const REDACT_ACTIONS = ["$$KEEP", "$$PRUNE", "$$DESCEND"];
function redact(obj, expr, options) {
  const action = computeValue(obj, expr, null, options);
  switch (action) {
    case "$$KEEP":
      return obj;
    case "$$PRUNE":
      return void 0;
    case "$$DESCEND": {
      if (!(0, import_util.has)(expr, "$cond")) return obj;
      const output = {};
      for (const [key, value] of Object.entries(obj)) {
        if ((0, import_util.isArray)(value)) {
          const res = new Array();
          for (let elem of value) {
            if ((0, import_util.isObject)(elem)) {
              elem = redact(elem, expr, options.update(elem));
            }
            if (!(0, import_util.isNil)(elem)) res.push(elem);
          }
          output[key] = res;
        } else if ((0, import_util.isObject)(value)) {
          const res = redact(
            value,
            expr,
            options.update(value)
          );
          if (!(0, import_util.isNil)(res)) output[key] = res;
        } else {
          output[key] = value;
        }
      }
      return output;
    }
    default:
      return action;
  }
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  ComputeOptions,
  Context,
  OpType,
  ProcessingMode,
  computeValue,
  getOperator,
  initOptions,
  redact,
  useOperators
});
