class MingoError extends Error {
}
const MISSING = Symbol("missing");
const ERR_CYCLE_FOUND = "mingo: cycle detected while processing object/array";
const DEFAULT_HASH_FUNCTION = (value) => {
  const s = stringify(value);
  let hash = 0;
  let i = s.length;
  while (i) hash = (hash << 5) - hash ^ s.charCodeAt(--i);
  return hash >>> 0;
};
const isPrimitive = (v) => typeof v !== "object" && typeof v !== "function" || v === null;
const isScalar = (v) => isPrimitive(v) || isDate(v) || isRegExp(v);
const SORT_ORDER = {
  undefined: 1,
  null: 2,
  number: 3,
  string: 4,
  symbol: 5,
  object: 6,
  array: 7,
  arraybuffer: 8,
  boolean: 9,
  date: 10,
  regexp: 11,
  function: 12
};
function compare(a, b) {
  if (a === MISSING) a = void 0;
  if (b === MISSING) b = void 0;
  const customOrder = 100;
  const [u, v] = [a, b].map(
    (n) => SORT_ORDER[isTypedArray(n) ? "arraybuffer" : typeOf(n)] ?? customOrder
    /*custom objects have highest sort order*/
  );
  if (u !== v) return u - v;
  if (u === customOrder) {
    a = stringify(a);
    b = stringify(b);
  }
  return isEqual(a, b) ? 0 : a < b ? -1 : 1;
}
class ValueMap extends Map {
  // The hash function
  #hashFn = DEFAULT_HASH_FUNCTION;
  // maps the hashcode to key set
  #keyMap = /* @__PURE__ */ new Map();
  // returns a tuple of [<masterKey>, <hash>]. Expects an object key.
  #unpack = (key) => {
    const hash = this.#hashFn(key);
    return [(this.#keyMap.get(hash) || []).find((k) => isEqual(k, key)), hash];
  };
  constructor() {
    super();
  }
  /**
   * Returns a new {@link ValueMap} object.
   * @param fn An optional custom hash function
   */
  static init(fn) {
    const m = new ValueMap();
    if (fn) m.#hashFn = fn;
    return m;
  }
  clear() {
    super.clear();
    this.#keyMap.clear();
  }
  /**
   * @returns true if an element in the Map existed and has been removed, or false if the element does not exist.
   */
  delete(key) {
    if (isPrimitive(key)) return super.delete(key);
    const [masterKey, hash] = this.#unpack(key);
    if (!super.delete(masterKey)) return false;
    this.#keyMap.set(
      hash,
      this.#keyMap.get(hash).filter((k) => !isEqual(k, masterKey))
    );
    return true;
  }
  /**
   * Returns a specified element from the Map object. If the value that is associated to the provided key is an object, then you will get a reference to that object and any change made to that object will effectively modify it inside the Map.
   * @returns Returns the element associated with the specified key. If no element is associated with the specified key, undefined is returned.
   */
  get(key) {
    if (isPrimitive(key)) return super.get(key);
    const [masterKey, _] = this.#unpack(key);
    return super.get(masterKey);
  }
  /**
   * @returns boolean indicating whether an element with the specified key exists or not.
   */
  has(key) {
    if (isPrimitive(key)) return super.has(key);
    const [masterKey, _] = this.#unpack(key);
    return super.has(masterKey);
  }
  /**
   * Adds a new element with a specified key and value to the Map. If an element with the same key already exists, the element will be updated.
   */
  set(key, value) {
    if (isPrimitive(key)) return super.set(key, value);
    const [masterKey, hash] = this.#unpack(key);
    if (super.has(masterKey)) {
      super.set(masterKey, value);
    } else {
      super.set(key, value);
      const keys = this.#keyMap.get(hash) || [];
      keys.push(key);
      this.#keyMap.set(hash, keys);
    }
    return this;
  }
  /**
   * @returns the number of elements in the Map.
   */
  get size() {
    return super.size;
  }
}
function assert(condition, message) {
  if (!condition) throw new MingoError(message);
}
function typeOf(v) {
  if (v === null) return "null";
  const t = typeof v;
  if (t !== "object" && t in SORT_ORDER) return t;
  if (isArray(v)) return "array";
  if (isDate(v)) return "date";
  if (isRegExp(v)) return "regexp";
  const s = Object.prototype.toString.call(v);
  return s === "[object Object]" ? v?.constructor?.name?.toLowerCase() || "object" : s.substring(8, s.length - 1).toLowerCase();
}
const isBoolean = (v) => typeof v === "boolean";
const isString = (v) => typeof v === "string";
const isSymbol = (v) => typeof v === "symbol";
const isNumber = (v) => !isNaN(v) && typeof v === "number";
const isArray = Array.isArray;
const isObject = (v) => typeOf(v) === "object";
const isObjectLike = (v) => !isPrimitive(v);
const isDate = (v) => v instanceof Date;
const isRegExp = (v) => v instanceof RegExp;
const isFunction = (v) => typeof v === "function";
const isNil = (v) => v === null || v === void 0;
const truthy = (arg, strict = true) => !!arg || strict && arg === "";
const isEmpty = (x) => isNil(x) || isString(x) && !x || isArray(x) && x.length === 0 || isObject(x) && Object.keys(x).length === 0;
const ensureArray = (x) => isArray(x) ? x : [x];
const has = (obj, prop) => !!obj && Object.prototype.hasOwnProperty.call(obj, prop);
const isTypedArray = (v) => typeof ArrayBuffer !== "undefined" && ArrayBuffer.isView(v);
const cloneDeep = (v, refs) => {
  if (isNil(v) || isBoolean(v) || isNumber(v) || isString(v)) return v;
  if (isDate(v)) return new Date(v);
  if (isRegExp(v)) return new RegExp(v);
  if (isTypedArray(v)) {
    const ctor = v.constructor;
    return new ctor(v);
  }
  if (!(refs instanceof Set)) refs = /* @__PURE__ */ new Set();
  if (refs.has(v)) throw new Error(ERR_CYCLE_FOUND);
  refs.add(v);
  try {
    if (isArray(v)) {
      const arr = new Array(v.length);
      for (let i = 0; i < v.length; i++) arr[i] = cloneDeep(v[i], refs);
      return arr;
    }
    if (isObject(v)) {
      const obj = {};
      for (const k of Object.keys(v)) obj[k] = cloneDeep(v[k], refs);
      return obj;
    }
  } finally {
    refs.delete(v);
  }
  return v;
};
const isMissing = (v) => v === MISSING;
function merge(target, input) {
  if (isMissing(target) || isNil(target)) return input;
  if (isMissing(input) || isNil(input)) return target;
  if (isPrimitive(target) || isPrimitive(input)) return input;
  if (isArray(target) && isArray(input)) {
    assert(
      target.length === input.length,
      "arrays must be of equal length to merge."
    );
  }
  for (const k of Object.keys(input)) {
    target[k] = merge(target[k], input[k]);
  }
  return target;
}
function intersection(input, hashFunc = DEFAULT_HASH_FUNCTION) {
  const vmaps = [ValueMap.init(hashFunc), ValueMap.init(hashFunc)];
  if (input.length === 0) return [];
  if (input.some((arr) => arr.length === 0)) return [];
  if (input.length === 1) return [...input];
  input[input.length - 1].forEach((v) => vmaps[0].set(v, true));
  for (let i = input.length - 2; i > -1; i--) {
    input[i].forEach((v) => {
      if (vmaps[0].has(v)) vmaps[1].set(v, true);
    });
    if (vmaps[1].size === 0) return [];
    vmaps.reverse();
    vmaps[1].clear();
  }
  return Array.from(vmaps[0].keys());
}
function flatten(xs, depth = 1) {
  const arr = new Array();
  function flatten2(ys, n) {
    for (let i = 0, len = ys.length; i < len; i++) {
      if (isArray(ys[i]) && (n > 0 || n < 0)) {
        flatten2(ys[i], Math.max(-1, n - 1));
      } else {
        arr.push(ys[i]);
      }
    }
  }
  flatten2(xs, depth);
  return arr;
}
function getMembersOf(o) {
  const props = {};
  while (o) {
    for (const k of Object.getOwnPropertyNames(o))
      if (!(k in props)) props[k] = o[k];
    o = Object.getPrototypeOf(o);
  }
  return props;
}
const hasCustomString = (o) => o !== null && o !== void 0 && o["toString"] !== Object.prototype.toString;
function isEqual(a, b) {
  if (a === b || Object.is(a, b)) return true;
  if (a === null || b === null) return false;
  if (typeof a !== typeof b) return false;
  if (typeof a !== "object") return false;
  if (isDate(a)) return isDate(b) && +a === +b;
  if (isRegExp(a)) return isRegExp(b) && a.toString() === b.toString();
  const t = typeOf(a);
  if (t !== typeOf(b)) return false;
  switch (t) {
    case "array":
      if (a.length !== b.length) return false;
      for (let i = 0, size = a.length; i < size; i++) {
        if (!isEqual(a[i], b[i])) return false;
      }
      return true;
    case "object": {
      const ka = Object.keys(a);
      const kb = Object.keys(b);
      if (ka.length !== kb.length) return false;
      for (const k of ka) {
        if (!(k in b && isEqual(a[k], b[k]))) return false;
      }
      return true;
    }
    default:
      return hasCustomString(a) && a.toString() === b.toString();
  }
}
function unique(input, hashFunc = DEFAULT_HASH_FUNCTION) {
  const m = ValueMap.init(hashFunc);
  input.forEach((v) => m.set(v, true));
  return Array.from(m.keys());
}
function stringify(v, refs) {
  if (v === null) return "null";
  if (v === void 0) return "undefined";
  if (isString(v) || isNumber(v) || isBoolean(v)) return JSON.stringify(v);
  if (isDate(v)) return v.toISOString();
  if (isRegExp(v) || isSymbol(v) || isFunction(v))
    return v.toString();
  if (!(refs instanceof Set)) refs = /* @__PURE__ */ new Set();
  if (refs.has(v)) throw new Error(ERR_CYCLE_FOUND);
  try {
    refs.add(v);
    if (isArray(v)) return "[" + v.map((s2) => stringify(s2, refs)).join(",") + "]";
    if (isObject(v)) {
      const keys = Object.keys(v).sort();
      return "{" + keys.map((k) => `${k}:${stringify(v[k], refs)}`).join() + "}";
    }
    const s = hasCustomString(v) ? v.toString() : stringify(getMembersOf(v), refs);
    return typeOf(v) + "(" + s + ")";
  } finally {
    refs.delete(v);
  }
}
function hashCode(value, hashFunc) {
  if (isNil(value)) return null;
  hashFunc = hashFunc || DEFAULT_HASH_FUNCTION;
  return hashFunc(value);
}
function groupBy(collection, keyFunc, hashFunc = DEFAULT_HASH_FUNCTION) {
  if (collection.length < 1) return /* @__PURE__ */ new Map();
  const result = ValueMap.init(hashFunc);
  for (let i = 0; i < collection.length; i++) {
    const obj = collection[i];
    const key = keyFunc(obj, i) ?? null;
    let a = result.get(key);
    if (!a) {
      a = [obj];
      result.set(key, a);
    } else {
      a.push(obj);
    }
  }
  return result;
}
function getValue(obj, key) {
  return isObjectLike(obj) ? obj[key] : void 0;
}
function unwrap(arr, depth) {
  if (depth < 1) return arr;
  while (depth-- && arr.length === 1) arr = arr[0];
  return arr;
}
function resolve(obj, selector, options) {
  let depth = 0;
  function resolve2(o, path) {
    let value = o;
    for (let i = 0; i < path.length; i++) {
      const field = path[i];
      const isText = /^\d+$/.exec(field) === null;
      if (isText && isArray(value)) {
        if (i === 0 && depth > 0) break;
        depth += 1;
        const subpath = path.slice(i);
        value = value.reduce((acc, item) => {
          const v = resolve2(item, subpath);
          if (v !== void 0) acc.push(v);
          return acc;
        }, []);
        break;
      } else {
        value = getValue(value, field);
      }
      if (value === void 0) break;
    }
    return value;
  }
  const res = isScalar(obj) ? obj : resolve2(obj, selector.split("."));
  return isArray(res) && options?.unwrapArray ? unwrap(res, depth) : res;
}
function resolveGraph(obj, selector, options) {
  const sep = selector.indexOf(".");
  const key = sep == -1 ? selector : selector.substring(0, sep);
  const next = selector.substring(sep + 1);
  const hasNext = sep != -1;
  if (isArray(obj)) {
    const isIndex = /^\d+$/.test(key);
    const arr = isIndex && options?.preserveIndex ? [...obj] : [];
    if (isIndex) {
      const index = parseInt(key);
      let value2 = getValue(obj, index);
      if (hasNext) {
        value2 = resolveGraph(value2, next, options);
      }
      if (options?.preserveIndex) {
        arr[index] = value2;
      } else {
        arr.push(value2);
      }
    } else {
      for (const item of obj) {
        const value2 = resolveGraph(item, selector, options);
        if (options?.preserveMissing) {
          arr.push(value2 == void 0 ? MISSING : value2);
        } else if (value2 != void 0 || options?.preserveIndex) {
          arr.push(value2);
        }
      }
    }
    return arr;
  }
  const res = options?.preserveKeys ? { ...obj } : {};
  let value = getValue(obj, key);
  if (hasNext) {
    value = resolveGraph(value, next, options);
  }
  if (value === void 0) return void 0;
  res[key] = value;
  return res;
}
function filterMissing(obj) {
  if (isArray(obj)) {
    for (let i = obj.length - 1; i >= 0; i--) {
      if (obj[i] === MISSING) {
        obj.splice(i, 1);
      } else {
        filterMissing(obj[i]);
      }
    }
  } else if (isObject(obj)) {
    for (const k in obj) {
      if (has(obj, k)) {
        filterMissing(obj[k]);
      }
    }
  }
}
const NUMBER_RE = /^\d+$/;
function walk(obj, selector, fn, options) {
  const names = selector.split(".");
  const key = names[0];
  const next = names.slice(1).join(".");
  if (names.length === 1) {
    if (isObject(obj) || isArray(obj) && NUMBER_RE.test(key)) {
      fn(obj, key);
    }
  } else {
    if (options?.buildGraph && isNil(obj[key])) {
      obj[key] = {};
    }
    const item = obj[key];
    if (!item) return;
    const isNextArrayIndex = !!(names.length > 1 && NUMBER_RE.test(names[1]));
    if (isArray(item) && options?.descendArray && !isNextArrayIndex) {
      item.forEach((e) => walk(e, next, fn, options));
    } else {
      walk(item, next, fn, options);
    }
  }
}
function setValue(obj, selector, value) {
  walk(
    obj,
    selector,
    (item, key) => {
      item[key] = isFunction(value) ? value(item[key]) : value;
    },
    { buildGraph: true }
  );
}
function removeValue(obj, selector, options) {
  walk(
    obj,
    selector,
    (item, key) => {
      if (isArray(item)) {
        item.splice(parseInt(key), 1);
      } else if (isObject(item)) {
        delete item[key];
      }
    },
    options
  );
}
const OPERATOR_NAME_PATTERN = /^\$[a-zA-Z0-9_]+$/;
function isOperator(name) {
  return OPERATOR_NAME_PATTERN.test(name);
}
function normalize(expr) {
  if (isScalar(expr)) {
    return isRegExp(expr) ? { $regex: expr } : { $eq: expr };
  }
  if (isObjectLike(expr)) {
    if (!Object.keys(expr).some(isOperator)) return { $eq: expr };
    if (has(expr, "$regex")) {
      const newExpr = { ...expr };
      newExpr["$regex"] = new RegExp(
        expr["$regex"],
        expr["$options"]
      );
      delete newExpr["$options"];
      return newExpr;
    }
  }
  return expr;
}
function findInsertIndex(sorted, item, comparator = compare) {
  let lo = 0;
  let hi = sorted.length - 1;
  while (lo <= hi) {
    const mid = Math.round(lo + (hi - lo) / 2);
    if (comparator(item, sorted[mid]) < 0) {
      hi = mid - 1;
    } else if (comparator(item, sorted[mid]) > 0) {
      lo = mid + 1;
    } else {
      return mid;
    }
  }
  return lo;
}
export {
  MingoError,
  ValueMap,
  assert,
  cloneDeep,
  compare,
  ensureArray,
  filterMissing,
  findInsertIndex,
  flatten,
  groupBy,
  has,
  hashCode,
  intersection,
  isArray,
  isBoolean,
  isDate,
  isEmpty,
  isEqual,
  isFunction,
  isNil,
  isNumber,
  isObject,
  isObjectLike,
  isOperator,
  isRegExp,
  isString,
  isSymbol,
  merge,
  normalize,
  removeValue,
  resolve,
  resolveGraph,
  setValue,
  stringify,
  truthy,
  typeOf,
  unique,
  walk
};
