UNPKG

red-black-tree-typed

Version:
7,947 lines 225 kB
"use strict";
var redBlackTreeTyped = (() => {
  var __defProp = Object.defineProperty;
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
  var __getOwnPropNames = Object.getOwnPropertyNames;
  var __hasOwnProp = Object.prototype.hasOwnProperty;
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
  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 __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);

  // src/index.ts
  var src_exports = {};
  __export(src_exports, {
    BST: () => BST,
    BSTNode: () => BSTNode,
    BinaryTree: () => BinaryTree,
    BinaryTreeNode: () => BinaryTreeNode,
    DFSOperation: () => DFSOperation,
    ERR: () => ERR,
    Range: () => Range,
    RedBlackTree: () => RedBlackTree,
    RedBlackTreeNode: () => RedBlackTreeNode,
    raise: () => raise
  });

  // src/utils/utils.ts
  function isPrimitiveComparable(value) {
    const valueType = typeof value;
    if (valueType === "number") return true;
    return valueType === "bigint" || valueType === "string" || valueType === "boolean";
  }
  function tryObjectToPrimitive(obj) {
    if (typeof obj.valueOf === "function") {
      const valueOfResult = obj.valueOf();
      if (valueOfResult !== obj) {
        if (isPrimitiveComparable(valueOfResult)) return valueOfResult;
        if (typeof valueOfResult === "object" && valueOfResult !== null) return tryObjectToPrimitive(valueOfResult);
      }
    }
    if (typeof obj.toString === "function") {
      const stringResult = obj.toString();
      if (stringResult !== "[object Object]") return stringResult;
    }
    return null;
  }
  function isComparable(value, isForceObjectComparable = false) {
    if (value === null || value === void 0) return false;
    if (isPrimitiveComparable(value)) return true;
    if (typeof value !== "object") return false;
    if (value instanceof Date) return true;
    if (isForceObjectComparable) return true;
    const comparableValue = tryObjectToPrimitive(value);
    if (comparableValue === null || comparableValue === void 0) return false;
    return isPrimitiveComparable(comparableValue);
  }
  var makeTrampolineThunk = (computation) => ({
    isThunk: true,
    // Marker indicating this is a thunk
    fn: computation
    // The deferred computation function
  });
  var isTrampolineThunk = (value) => typeof value === "object" && // Must be an object
  value !== null && // Must not be null
  "isThunk" in value && // Must have the 'isThunk' property
  value.isThunk;
  function trampoline(initial) {
    let current = initial;
    while (isTrampolineThunk(current)) {
      current = current.fn();
    }
    return current;
  }
  function makeTrampoline(fn) {
    return (...args) => trampoline(fn(...args));
  }

  // src/common/error.ts
  function raise(ErrorClass, message) {
    throw new ErrorClass(message);
  }
  var ERR = {
    // Range / index
    indexOutOfRange: (index, min, max, ctx) => `${ctx ? ctx + ": " : ""}Index ${index} is out of range [${min}, ${max}].`,
    invalidIndex: (ctx) => `${ctx ? ctx + ": " : ""}Index must be an integer.`,
    // Type / argument
    invalidArgument: (reason, ctx) => `${ctx ? ctx + ": " : ""}${reason}`,
    comparatorRequired: (ctx) => `${ctx ? ctx + ": " : ""}Comparator is required for non-number/non-string/non-Date keys.`,
    invalidKey: (reason, ctx) => `${ctx ? ctx + ": " : ""}${reason}`,
    notAFunction: (name, ctx) => `${ctx ? ctx + ": " : ""}${name} must be a function.`,
    invalidEntry: (ctx) => `${ctx ? ctx + ": " : ""}Each entry must be a [key, value] tuple.`,
    invalidNaN: (ctx) => `${ctx ? ctx + ": " : ""}NaN is not a valid key.`,
    invalidDate: (ctx) => `${ctx ? ctx + ": " : ""}Invalid Date key.`,
    reduceEmpty: (ctx) => `${ctx ? ctx + ": " : ""}Reduce of empty structure with no initial value.`,
    callbackReturnType: (expected, got, ctx) => `${ctx ? ctx + ": " : ""}Callback must return ${expected}; got ${got}.`,
    // State / operation
    invalidOperation: (reason, ctx) => `${ctx ? ctx + ": " : ""}${reason}`,
    // Matrix
    matrixDimensionMismatch: (op) => `Matrix: Dimensions must be compatible for ${op}.`,
    matrixSingular: () => "Matrix: Singular matrix, inverse does not exist.",
    matrixNotSquare: () => "Matrix: Must be square for inversion.",
    matrixNotRectangular: () => "Matrix: Must be rectangular for transposition.",
    matrixRowMismatch: (expected, got) => `Matrix: Expected row length ${expected}, but got ${got}.`,
    // Order statistic
    orderStatisticNotEnabled: (method, ctx) => `${ctx ? ctx + ": " : ""}${method}() requires enableOrderStatistic: true.`
  };

  // src/common/index.ts
  var DFSOperation = /* @__PURE__ */ ((DFSOperation2) => {
    DFSOperation2[DFSOperation2["VISIT"] = 0] = "VISIT";
    DFSOperation2[DFSOperation2["PROCESS"] = 1] = "PROCESS";
    return DFSOperation2;
  })(DFSOperation || {});
  var Range = class {
    constructor(low, high, includeLow = true, includeHigh = true) {
      this.low = low;
      this.high = high;
      this.includeLow = includeLow;
      this.includeHigh = includeHigh;
    }
    // Determine whether a key is within the range
    isInRange(key, comparator) {
      const lowCheck = this.includeLow ? comparator(key, this.low) >= 0 : comparator(key, this.low) > 0;
      const highCheck = this.includeHigh ? comparator(key, this.high) <= 0 : comparator(key, this.high) < 0;
      return lowCheck && highCheck;
    }
  };

  // src/data-structures/base/iterable-element-base.ts
  var IterableElementBase = class {
    /**
     * Create a new iterable base.
     *
     * @param options Optional behavior overrides. When provided, a `toElementFn`
     * is used to convert a raw element (`R`) into a public element (`E`).
     *
     * @remarks
     * Time O(1), Space O(1).
     */
    constructor(options) {
      /**
       * The converter used to transform a raw element (`R`) into a public element (`E`).
       *
       * @remarks
       * Time O(1), Space O(1).
       */
      __publicField(this, "_toElementFn");
      if (options) {
        const { toElementFn } = options;
        if (typeof toElementFn === "function") this._toElementFn = toElementFn;
        else if (toElementFn) raise(TypeError, "toElementFn must be a function type");
      }
    }
    /**
     * Exposes the current `toElementFn`, if configured.
     *
     * @returns The converter function or `undefined` when not set.
     * @remarks
     * Time O(1), Space O(1).
     */
    get toElementFn() {
      return this._toElementFn;
    }
    /**
     * Returns an iterator over the structure's elements.
     *
     * @param args Optional iterator arguments forwarded to the internal iterator.
     * @returns An `IterableIterator<E>` that yields the elements in traversal order.
     *
     * @remarks
     * Producing the iterator is O(1); consuming the entire iterator is Time O(n) with O(1) extra space.
     */
    *[Symbol.iterator](...args) {
      yield* this._getIterator(...args);
    }
    /**
     * Returns an iterator over the values (alias of the default iterator).
     *
     * @returns An `IterableIterator<E>` over all elements.
     * @remarks
     * Creating the iterator is O(1); full iteration is Time O(n), Space O(1).
     */
    *values() {
      for (const item of this) yield item;
    }
    /**
     * Tests whether all elements satisfy the predicate.
     *
     * @template TReturn
     * @param predicate Function invoked for each element with signature `(value, index, self)`.
     * @param thisArg Optional `this` binding for the predicate.
     * @returns `true` if every element passes; otherwise `false`.
     *
     * @remarks
     * Time O(n) in the worst case; may exit early when the first failure is found. Space O(1).
     */
    every(predicate, thisArg) {
      let index = 0;
      for (const item of this) {
        if (thisArg === void 0) {
          if (!predicate(item, index++, this)) return false;
        } else {
          const fn = predicate;
          if (!fn.call(thisArg, item, index++, this)) return false;
        }
      }
      return true;
    }
    /**
     * Tests whether at least one element satisfies the predicate.
     *
     * @param predicate Function invoked for each element with signature `(value, index, self)`.
     * @param thisArg Optional `this` binding for the predicate.
     * @returns `true` if any element passes; otherwise `false`.
     *
     * @remarks
     * Time O(n) in the worst case; may exit early on first success. Space O(1).
     */
    some(predicate, thisArg) {
      let index = 0;
      for (const item of this) {
        if (thisArg === void 0) {
          if (predicate(item, index++, this)) return true;
        } else {
          const fn = predicate;
          if (fn.call(thisArg, item, index++, this)) return true;
        }
      }
      return false;
    }
    /**
     * Invokes a callback for each element in iteration order.
     *
     * @param callbackfn Function invoked per element with signature `(value, index, self)`.
     * @param thisArg Optional `this` binding for the callback.
     * @returns `void`.
     *
     * @remarks
     * Time O(n), Space O(1).
     */
    forEach(callbackfn, thisArg) {
      let index = 0;
      for (const item of this) {
        if (thisArg === void 0) {
          callbackfn(item, index++, this);
        } else {
          const fn = callbackfn;
          fn.call(thisArg, item, index++, this);
        }
      }
    }
    // Implementation signature
    find(predicate, thisArg) {
      let index = 0;
      for (const item of this) {
        if (thisArg === void 0) {
          if (predicate(item, index++, this)) return item;
        } else {
          const fn = predicate;
          if (fn.call(thisArg, item, index++, this)) return item;
        }
      }
      return;
    }
    /**
     * Checks whether a strictly-equal element exists in the structure.
     *
     * @param element The element to test with `===` equality.
     * @returns `true` if an equal element is found; otherwise `false`.
     *
     * @remarks
     * Time O(n) in the worst case. Space O(1).
     */
    has(element) {
      for (const ele of this) if (ele === element) return true;
      return false;
    }
    /**
     * Check whether a value exists (Array-compatible alias for `has`).
     * @remarks Provided for familiarity when migrating from Array. Time O(n), Space O(1).
     * @param element - Element to search for (uses `===`).
     * @returns `true` if found.
     */
    includes(element) {
      return this.has(element);
    }
    /**
     * Return an iterator of `[index, value]` pairs (Array-compatible).
     * @remarks Provided for familiarity when migrating from Array. Time O(n), Space O(1) per step.
     */
    *entries() {
      let index = 0;
      for (const value of this) {
        yield [index++, value];
      }
    }
    /**
     * Return an iterator of numeric indices (Array-compatible).
     * @remarks Provided for familiarity when migrating from Array. Time O(n), Space O(1) per step.
     */
    *keys() {
      let index = 0;
      for (const _ of this) {
        yield index++;
      }
    }
    /**
     * Reduces all elements to a single accumulated value.
     *
     * @overload
     * @param callbackfn Reducer of signature `(acc, value, index, self) => nextAcc`. The first element is used as the initial accumulator.
     * @returns The final accumulated value typed as `E`.
     *
     * @overload
     * @param callbackfn Reducer of signature `(acc, value, index, self) => nextAcc`.
     * @param initialValue The initial accumulator value of type `E`.
     * @returns The final accumulated value typed as `E`.
     *
     * @overload
     * @template U The accumulator type when it differs from `E`.
     * @param callbackfn Reducer of signature `(acc: U, value, index, self) => U`.
     * @param initialValue The initial accumulator value of type `U`.
     * @returns The final accumulated value typed as `U`.
     *
     * @remarks
     * Time O(n), Space O(1). Throws if called on an empty structure without `initialValue`.
     */
    reduce(callbackfn, initialValue) {
      let index = 0;
      const iter = this[Symbol.iterator]();
      let acc;
      if (arguments.length >= 2) {
        acc = initialValue;
      } else {
        const first = iter.next();
        if (first.done) raise(TypeError, "Reduce of empty structure with no initial value");
        acc = first.value;
        index = 1;
      }
      for (const value of iter) {
        acc = callbackfn(acc, value, index++, this);
      }
      return acc;
    }
    /**
     * Materializes the elements into a new array.
     *
     * @returns A shallow array copy of the iteration order.
     * @remarks
     * Time O(n), Space O(n).
     */
    toArray() {
      return [...this];
    }
    /**
     * Returns a representation of the structure suitable for quick visualization.
     * Defaults to an array of elements; subclasses may override to provide richer visuals.
     *
     * @returns A visual representation (array by default).
     * @remarks
     * Time O(n), Space O(n).
     */
    toVisual() {
      return [...this];
    }
    /**
     * Prints `toVisual()` to the console. Intended for quick debugging.
     *
     * @returns `void`.
     * @remarks
     * Time O(n) due to materialization, Space O(n) for the intermediate representation.
     */
    print() {
      console.log(this.toVisual());
    }
  };

  // src/data-structures/base/linear-base.ts
  var LinearBase = class _LinearBase extends IterableElementBase {
    /**
     * Construct a linear container with runtime options.
     * @param options - `{ maxLen?, ... }` bounds/behavior options.
     * @remarks Time O(1), Space O(1)
     */
    constructor(options) {
      super(options);
      __publicField(this, "_maxLen", -1);
      if (options) {
        const { maxLen } = options;
        if (typeof maxLen === "number" && maxLen > 0 && maxLen % 1 === 0) this._maxLen = maxLen;
      }
    }
    /**
     * Upper bound for length (if positive), or `-1` when unbounded.
     * @returns Maximum allowed length.
     * @remarks Time O(1), Space O(1)
     */
    get maxLen() {
      return this._maxLen;
    }
    /**
     * First index of a value from the left.
     * @param searchElement - Value to match.
     * @param fromIndex - Start position (supports negative index).
     * @returns Index or `-1` if not found.
     * @remarks Time O(n), Space O(1)
     */
    indexOf(searchElement, fromIndex = 0) {
      if (this.length === 0) return -1;
      if (fromIndex < 0) fromIndex = this.length + fromIndex;
      if (fromIndex < 0) fromIndex = 0;
      for (let i = fromIndex; i < this.length; i++) {
        const element = this.at(i);
        if (element === searchElement) return i;
      }
      return -1;
    }
    /**
     * Last index of a value from the right.
     * @param searchElement - Value to match.
     * @param fromIndex - Start position (supports negative index).
     * @returns Index or `-1` if not found.
     * @remarks Time O(n), Space O(1)
     */
    lastIndexOf(searchElement, fromIndex = this.length - 1) {
      if (this.length === 0) return -1;
      if (fromIndex >= this.length) fromIndex = this.length - 1;
      if (fromIndex < 0) fromIndex = this.length + fromIndex;
      for (let i = fromIndex; i >= 0; i--) {
        const element = this.at(i);
        if (element === searchElement) return i;
      }
      return -1;
    }
    /**
     * Find the first index matching a predicate.
     * @param predicate - `(element, index, self) => boolean`.
     * @param thisArg - Optional `this` for callback.
     * @returns Index or `-1`.
     * @remarks Time O(n), Space O(1)
     */
    findIndex(predicate, thisArg) {
      for (let i = 0; i < this.length; i++) {
        const item = this.at(i);
        if (item !== void 0 && predicate.call(thisArg, item, i, this)) return i;
      }
      return -1;
    }
    /**
     * Concatenate elements and/or containers.
     * @param items - Elements or other containers.
     * @returns New container with combined elements (`this` type).
     * @remarks Time O(sum(length)), Space O(sum(length))
     */
    concat(...items) {
      const newList = this.clone();
      for (const item of items) {
        if (item instanceof _LinearBase) {
          newList.pushMany(item);
        } else {
          newList.push(item);
        }
      }
      return newList;
    }
    /**
     * In-place stable order via array sort semantics.
     * @param compareFn - Comparator `(a, b) => number`.
     * @returns This container.
     * @remarks Time O(n log n), Space O(n) (materializes to array temporarily)
     */
    sort(compareFn) {
      const arr = this.toArray();
      arr.sort(compareFn);
      this.clear();
      for (const item of arr) this.push(item);
      return this;
    }
    /**
     * Remove and/or insert elements at a position (array-compatible).
     * @param start - Start index (supports negative index).
     * @param deleteCount - How many to remove.
     * @param items - Elements to insert.
     * @returns Removed elements as a new list (`this` type).
     * @remarks Time O(n + m), Space O(min(n, m)) where `m = items.length`
     */
    splice(start, deleteCount = 0, ...items) {
      const removedList = this._createInstance();
      start = start < 0 ? this.length + start : start;
      start = Math.max(0, Math.min(start, this.length));
      deleteCount = Math.max(0, Math.min(deleteCount, this.length - start));
      for (let i = 0; i < deleteCount; i++) {
        const removed = this.deleteAt(start);
        if (removed !== void 0) {
          removedList.push(removed);
        }
      }
      for (let i = 0; i < items.length; i++) {
        this.addAt(start + i, items[i]);
      }
      return removedList;
    }
    /**
     * Join all elements into a string.
     * @param separator - Separator string.
     * @returns Concatenated string.
     * @remarks Time O(n), Space O(n)
     */
    join(separator = ",") {
      return this.toArray().join(separator);
    }
    /**
     * Snapshot elements into a reversed array.
     * @returns New reversed array.
     * @remarks Time O(n), Space O(n)
     */
    toReversedArray() {
      const array = [];
      for (let i = this.length - 1; i >= 0; i--) {
        array.push(this.at(i));
      }
      return array;
    }
    reduceRight(callbackfn, initialValue) {
      let accumulator = initialValue != null ? initialValue : 0;
      for (let i = this.length - 1; i >= 0; i--) {
        accumulator = callbackfn(accumulator, this.at(i), i, this);
      }
      return accumulator;
    }
    /**
     * Create a shallow copy of a subrange.
     * @param start - Inclusive start (supports negative index).
     * @param end - Exclusive end (supports negative index).
     * @returns New list with the range (`this` type).
     * @remarks Time O(n), Space O(n)
     */
    slice(start = 0, end = this.length) {
      start = start < 0 ? this.length + start : start;
      end = end < 0 ? this.length + end : end;
      const newList = this._createInstance();
      for (let i = start; i < end; i++) {
        newList.push(this.at(i));
      }
      return newList;
    }
    /**
     * Fill a range with a value.
     * @param value - Value to set.
     * @param start - Inclusive start.
     * @param end - Exclusive end.
     * @returns This list.
     * @remarks Time O(n), Space O(1)
     */
    fill(value, start = 0, end = this.length) {
      start = start < 0 ? this.length + start : start;
      end = end < 0 ? this.length + end : end;
      if (start < 0) start = 0;
      if (end > this.length) end = this.length;
      if (start >= end) return this;
      for (let i = start; i < end; i++) {
        this.setAt(i, value);
      }
      return this;
    }
    /**
     * Return a new instance of the same type with elements in reverse order (non-mutating).
     * @remarks Provided for familiarity when migrating from Array (ES2023 `toReversed`). Time O(n), Space O(n).
     * @returns A new reversed instance.
     */
    toReversed() {
      const cloned = this.clone();
      cloned.reverse();
      return cloned;
    }
  };

  // src/data-structures/base/iterable-entry-base.ts
  var IterableEntryBase = class {
    /**
     * Default iterator yielding `[key, value]` entries.
     * @returns Iterator of `[K, V]`.
     * @remarks Time O(n) to iterate, Space O(1)
     */
    *[Symbol.iterator](...args) {
      yield* this._getIterator(...args);
    }
    /**
     * Iterate over `[key, value]` pairs (may yield `undefined` values).
     * @returns Iterator of `[K, V | undefined]`.
     * @remarks Time O(n), Space O(1)
     */
    *entries() {
      for (const item of this) {
        yield item;
      }
    }
    /**
     * Iterate over keys only.
     * @returns Iterator of keys.
     * @remarks Time O(n), Space O(1)
     */
    *keys() {
      for (const item of this) {
        yield item[0];
      }
    }
    /**
     * Iterate over values only.
     * @returns Iterator of values.
     * @remarks Time O(n), Space O(1)
     */
    *values() {
      for (const item of this) {
        yield item[1];
      }
    }
    /**
     * Test whether all entries satisfy the predicate.
     * @param predicate - `(key, value, index, self) => boolean`.
     * @param thisArg - Optional `this` for callback.
     * @returns `true` if all pass; otherwise `false`.
     * @remarks Time O(n), Space O(1)
     */
    every(predicate, thisArg) {
      let index = 0;
      for (const item of this) {
        if (!predicate.call(thisArg, item[1], item[0], index++, this)) {
          return false;
        }
      }
      return true;
    }
    /**
     * Test whether any entry satisfies the predicate.
     * @param predicate - `(key, value, index, self) => boolean`.
     * @param thisArg - Optional `this` for callback.
     * @returns `true` if any passes; otherwise `false`.
     * @remarks Time O(n), Space O(1)
     */
    some(predicate, thisArg) {
      let index = 0;
      for (const item of this) {
        if (predicate.call(thisArg, item[1], item[0], index++, this)) {
          return true;
        }
      }
      return false;
    }
    /**
     * Visit each entry, left-to-right.
     * @param callbackfn - `(key, value, index, self) => void`.
     * @param thisArg - Optional `this` for callback.
     * @remarks Time O(n), Space O(1)
     */
    forEach(callbackfn, thisArg) {
      let index = 0;
      for (const item of this) {
        const [key, value] = item;
        callbackfn.call(thisArg, value, key, index++, this);
      }
    }
    /**
     * Find the first entry that matches a predicate.
     * @param callbackfn - `(key, value, index, self) => boolean`.
     * @param thisArg - Optional `this` for callback.
     * @returns Matching `[key, value]` or `undefined`.
     * @remarks Time O(n), Space O(1)
     */
    find(callbackfn, thisArg) {
      let index = 0;
      for (const item of this) {
        const [key, value] = item;
        if (callbackfn.call(thisArg, value, key, index++, this)) return item;
      }
      return;
    }
    /**
     * Whether the given key exists.
     * @param key - Key to test.
     * @returns `true` if found; otherwise `false`.
     * @remarks Time O(n) generic, Space O(1)
     */
    has(key) {
      for (const item of this) {
        const [itemKey] = item;
        if (itemKey === key) return true;
      }
      return false;
    }
    /**
     * Whether there exists an entry with the given value.
     * @param value - Value to test.
     * @returns `true` if found; otherwise `false`.
     * @remarks Time O(n), Space O(1)
     */
    hasValue(value) {
      for (const [, elementValue] of this) {
        if (elementValue === value) return true;
      }
      return false;
    }
    /**
     * Get the value under a key.
     * @param key - Key to look up.
     * @returns Value or `undefined`.
     * @remarks Time O(n) generic, Space O(1)
     */
    get(key) {
      for (const item of this) {
        const [itemKey, value] = item;
        if (itemKey === key) return value;
      }
      return;
    }
    /**
     * Reduce entries into a single accumulator.
     * @param callbackfn - `(acc, value, key, index, self) => acc`.
     * @param initialValue - Initial accumulator.
     * @returns Final accumulator.
     * @remarks Time O(n), Space O(1)
     */
    reduce(callbackfn, initialValue) {
      let accumulator = initialValue;
      let index = 0;
      for (const item of this) {
        const [key, value] = item;
        accumulator = callbackfn(accumulator, value, key, index++, this);
      }
      return accumulator;
    }
    /**
     * Converts data structure to `[key, value]` pairs.
     * @returns Array of entries.
     * @remarks Time O(n), Space O(n)
     */
    toArray() {
      return [...this];
    }
    /**
     * Visualize the iterable as an array of `[key, value]` pairs (or a custom string).
     * @returns Array of entries (default) or a string.
     * @remarks Time O(n), Space O(n)
     */
    toVisual() {
      return [...this];
    }
    /**
     * Print a human-friendly representation to the console.
     * @remarks Time O(n), Space O(n)
     */
    print() {
      console.log(this.toVisual());
    }
  };

  // src/data-structures/queue/queue.ts
  var Queue = class _Queue extends LinearBase {
    /**
     * Create a Queue and optionally bulk-insert elements.
     * @remarks Time O(N), Space O(N)
     * @param [elements] - Iterable of elements (or raw records if toElementFn is set).
     * @param [options] - Options such as toElementFn, maxLen, and autoCompactRatio.
     * @returns New Queue instance.
     */
    constructor(elements = [], options) {
      super(options);
      __publicField(this, "_elements", []);
      __publicField(this, "_offset", 0);
      __publicField(this, "_autoCompactRatio", 0.5);
      if (options) {
        const { autoCompactRatio = 0.5 } = options;
        this._autoCompactRatio = autoCompactRatio;
      }
      this.pushMany(elements);
    }
    /**
     * Get the underlying array buffer.
     * @remarks Time O(1), Space O(1)
     * @returns Backing array of elements.
     */
    get elements() {
      return this._elements;
    }
    /**
     * Get the current start offset into the array.
     * @remarks Time O(1), Space O(1)
     * @returns Zero-based offset.
     */
    get offset() {
      return this._offset;
    }
    /**
     * Get the compaction threshold (offset/size).
     * @remarks Time O(1), Space O(1)
     * @returns Auto-compaction ratio in (0,1].
     */
    get autoCompactRatio() {
      return this._autoCompactRatio;
    }
    /**
     * Set the compaction threshold.
     * @remarks Time O(1), Space O(1)
     * @param value - New ratio; compacts when offset/size exceeds this value.
     * @returns void
     */
    set autoCompactRatio(value) {
      this._autoCompactRatio = value;
    }
    /**
      * Get the number of elements currently in the queue.
      * @remarks Time O(1), Space O(1)
      * @returns Current length.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Track queue length
    *  const q = new Queue<number>();
    *     console.log(q.length); // 0;
    *     q.push(1);
    *     q.push(2);
    *     console.log(q.length); // 2;
      */
    get length() {
      return this.elements.length - this._offset;
    }
    /**
      * Get the first element (front) without removing it.
      * @remarks Time O(1), Space O(1)
      * @returns Front element or undefined.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // View the front element
    *  const q = new Queue<string>(['first', 'second', 'third']);
    *     console.log(q.first); // 'first';
    *     console.log(q.length); // 3;
      */
    get first() {
      return this.length > 0 ? this.elements[this._offset] : void 0;
    }
    /**
     * Peek at the front element without removing it (alias for `first`).
     * @remarks Time O(1), Space O(1)
     * @returns Front element or undefined.
     */
    peek() {
      return this.first;
    }
    /**
     * Get the last element (back) without removing it.
     * @remarks Time O(1), Space O(1)
     * @returns Back element or undefined.
     */
    get last() {
      return this.length > 0 ? this.elements[this.elements.length - 1] : void 0;
    }
    /**
     * Create a queue from an array of elements.
     * @remarks Time O(N), Space O(N)
     * @template E
     * @param elements - Array of elements to enqueue in order.
     * @returns A new queue populated from the array.
     */
    static fromArray(elements) {
      return new _Queue(elements);
    }
    /**
      * Check whether the queue is empty.
      * @remarks Time O(1), Space O(1)
      * @returns True if length is 0.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Queue for...of iteration and isEmpty check
    *  const queue = new Queue<string>(['A', 'B', 'C', 'D']);
    *
    *     const elements: string[] = [];
    *     for (const item of queue) {
    *       elements.push(item);
    *     }
    *
    *     // Verify all elements are iterated in order
    *     console.log(elements); // ['A', 'B', 'C', 'D'];
    *
    *     // Process all elements
    *     while (queue.length > 0) {
    *       queue.shift();
    *     }
    *
    *     console.log(queue.length); // 0;
      */
    isEmpty() {
      return this.length === 0;
    }
    /**
      * Enqueue one element at the back.
      * @remarks Time O(1), Space O(1)
      * @param element - Element to enqueue.
      * @returns True on success.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // basic Queue creation and push operation
    *  // Create a simple Queue with initial values
    *     const queue = new Queue([1, 2, 3, 4, 5]);
    *
    *     // Verify the queue maintains insertion order
    *     console.log([...queue]); // [1, 2, 3, 4, 5];
    *
    *     // Check length
    *     console.log(queue.length); // 5;
      */
    push(element) {
      this.elements.push(element);
      if (this._maxLen > 0 && this.length > this._maxLen) this.shift();
      return true;
    }
    /**
     * Enqueue many elements from an iterable.
     * @remarks Time O(N), Space O(1)
     * @param elements - Iterable of elements (or raw records if toElementFn is set).
     * @returns Array of per-element success flags.
     */
    pushMany(elements) {
      const ans = [];
      for (const el of elements) {
        if (this.toElementFn) ans.push(this.push(this.toElementFn(el)));
        else ans.push(this.push(el));
      }
      return ans;
    }
    /**
      * Dequeue one element from the front (amortized via offset).
      * @remarks Time O(1) amortized, Space O(1)
      * @returns Removed element or undefined.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Queue shift and peek operations
    *  const queue = new Queue<number>([10, 20, 30, 40]);
    *
    *     // Peek at the front element without removing it
    *     console.log(queue.first); // 10;
    *
    *     // Remove and get the first element (FIFO)
    *     const first = queue.shift();
    *     console.log(first); // 10;
    *
    *     // Verify remaining elements and length decreased
    *     console.log([...queue]); // [20, 30, 40];
    *     console.log(queue.length); // 3;
      */
    shift() {
      if (this.length === 0) return void 0;
      const first = this.first;
      this._offset += 1;
      if (this.elements.length > 0 && this.offset / this.elements.length > this.autoCompactRatio) this.compact();
      return first;
    }
    /**
      * Delete the first occurrence of a specific element.
      * @remarks Time O(N), Space O(1)
      * @param element - Element to remove (strict equality via Object.is).
      * @returns True if an element was removed.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Remove specific element
    *  const q = new Queue<number>([1, 2, 3, 2]);
    *     q.delete(2);
    *     console.log(q.length); // 3;
      */
    delete(element) {
      for (let i = this._offset; i < this.elements.length; i++) {
        if (Object.is(this.elements[i], element)) {
          this.elements.splice(i, 1);
          return true;
        }
      }
      return false;
    }
    /**
      * Get the element at a given logical index.
      * @remarks Time O(1), Space O(1)
      * @param index - Zero-based index from the front.
      * @returns Element or undefined.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Access element by index
    *  const q = new Queue<string>(['a', 'b', 'c']);
    *     console.log(q.at(0)); // 'a';
    *     console.log(q.at(2)); // 'c';
      */
    at(index) {
      if (index < 0 || index >= this.length) return void 0;
      return this._elements[this._offset + index];
    }
    /**
     * Delete the element at a given index.
     * @remarks Time O(N), Space O(1)
     * @param index - Zero-based index from the front.
     * @returns Removed element or undefined.
     */
    deleteAt(index) {
      if (index < 0 || index >= this.length) return void 0;
      const gi = this._offset + index;
      const [deleted] = this.elements.splice(gi, 1);
      return deleted;
    }
    /**
     * Insert a new element at a given index.
     * @remarks Time O(N), Space O(1)
     * @param index - Zero-based index from the front.
     * @param newElement - Element to insert.
     * @returns True if inserted.
     */
    addAt(index, newElement) {
      if (index < 0 || index > this.length) return false;
      this._elements.splice(this._offset + index, 0, newElement);
      return true;
    }
    /**
     * Replace the element at a given index.
     * @remarks Time O(1), Space O(1)
     * @param index - Zero-based index from the front.
     * @param newElement - New element to set.
     * @returns True if updated.
     */
    setAt(index, newElement) {
      if (index < 0 || index >= this.length) return false;
      this._elements[this._offset + index] = newElement;
      return true;
    }
    /**
     * Delete the first element that satisfies a predicate.
     * @remarks Time O(N), Space O(N)
     * @param predicate - Function (value, index, queue) → boolean to decide deletion.
     * @returns True if a match was removed.
     */
    deleteWhere(predicate) {
      for (let i = 0; i < this.length; i++) {
        if (predicate(this._elements[this._offset + i], i, this)) {
          this.deleteAt(i);
          return true;
        }
      }
      return false;
    }
    /**
     * Reverse the queue in-place by compacting then reversing.
     * @remarks Time O(N), Space O(N)
     * @returns This queue.
     */
    reverse() {
      this._elements = this.elements.slice(this._offset).reverse();
      this._offset = 0;
      return this;
    }
    /**
      * Remove all elements and reset offset.
      * @remarks Time O(1), Space O(1)
      * @returns void
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Remove all elements
    *  const q = new Queue<number>([1, 2, 3]);
    *     q.clear();
    *     console.log(q.length); // 0;
      */
    clear() {
      this._elements = [];
      this._offset = 0;
    }
    /**
      * Compact storage by discarding consumed head elements.
      * @remarks Time O(N), Space O(N)
      * @returns True when compaction performed.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Reclaim unused memory
    *  const q = new Queue<number>([1, 2, 3, 4, 5]);
    *     q.shift();
    *     q.shift();
    *     q.compact();
    *     console.log(q.length); // 3;
      */
    compact() {
      this._elements = this.elements.slice(this._offset);
      this._offset = 0;
      return true;
    }
    /**
     * Remove and/or insert elements at a position (array-like).
     * @remarks Time O(N + M), Space O(M)
     * @param start - Start index (clamped to [0, length]).
     * @param [deleteCount] - Number of elements to remove (default 0).
     * @param [items] - Elements to insert after `start`.
     * @returns A new queue containing the removed elements (typed as `this`).
     */
    splice(start, deleteCount = 0, ...items) {
      start = Math.max(0, Math.min(start, this.length));
      deleteCount = Math.max(0, Math.min(deleteCount, this.length - start));
      const gi = this._offset + start;
      const removedArray = this._elements.splice(gi, deleteCount, ...items);
      if (this.elements.length > 0 && this.offset / this.elements.length > this.autoCompactRatio) this.compact();
      const removed = this._createInstance({ toElementFn: this.toElementFn, maxLen: this._maxLen });
      removed._setAutoCompactRatio(this._autoCompactRatio);
      removed.pushMany(removedArray);
      return removed;
    }
    /**
      * Deep clone this queue and its parameters.
      * @remarks Time O(N), Space O(N)
      * @returns A new queue with the same content and options.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Create independent copy
    *  const q = new Queue<number>([1, 2, 3]);
    *     const copy = q.clone();
    *     copy.shift();
    *     console.log(q.length); // 3;
    *     console.log(copy.length); // 2;
      */
    clone() {
      const out = this._createInstance({ toElementFn: this.toElementFn, maxLen: this._maxLen });
      out._setAutoCompactRatio(this._autoCompactRatio);
      for (let i = this._offset; i < this.elements.length; i++) out.push(this.elements[i]);
      return out;
    }
    /**
      * Filter elements into a new queue of the same class.
      * @remarks Time O(N), Space O(N)
      * @param predicate - Predicate (element, index, queue) → boolean to keep element.
      * @param [thisArg] - Value for `this` inside the predicate.
      * @returns A new queue with kept elements.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Filter elements
    *  const q = new Queue<number>([1, 2, 3, 4, 5]);
    *     const evens = q.filter(x => x % 2 === 0);
    *     console.log(evens.length); // 2;
      */
    filter(predicate, thisArg) {
      const out = this._createInstance({ toElementFn: this.toElementFn, maxLen: this._maxLen });
      out._setAutoCompactRatio(this._autoCompactRatio);
      let index = 0;
      for (const v of this) {
        if (predicate.call(thisArg, v, index, this)) out.push(v);
        index++;
      }
      return out;
    }
    /**
      * Map each element to a new element in a possibly different-typed queue.
      * @remarks Time O(N), Space O(N)
      * @template EM
      * @template RM
      * @param callback - Mapping function (element, index, queue) → newElement.
      * @param [options] - Options for the output queue (e.g., toElementFn, maxLen, autoCompactRatio).
      * @param [thisArg] - Value for `this` inside the callback.
      * @returns A new Queue with mapped elements.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Transform elements
    *  const q = new Queue<number>([1, 2, 3]);
    *     const doubled = q.map(x => x * 2);
    *     console.log(doubled.toArray()); // [2, 4, 6];
      */
    map(callback, options, thisArg) {
      var _a, _b;
      const out = new this.constructor([], {
        toElementFn: options == null ? void 0 : options.toElementFn,
        maxLen: (_a = options == null ? void 0 : options.maxLen) != null ? _a : this._maxLen,
        autoCompactRatio: (_b = options == null ? void 0 : options.autoCompactRatio) != null ? _b : this._autoCompactRatio
      });
      let index = 0;
      for (const v of this)
        out.push(thisArg === void 0 ? callback(v, index++, this) : callback.call(thisArg, v, index++, this));
      return out;
    }
    /**
     * Map each element to a new value of the same type.
     * @remarks Time O(N), Space O(N)
     * @param callback - Mapping function (element, index, queue) → element.
     * @param [thisArg] - Value for `this` inside the callback.
     * @returns A new queue with mapped elements (same element type).
     */
    mapSame(callback, thisArg) {
      var _a;
      const Ctor = this.constructor;
      const out = new Ctor([], {
        toElementFn: this.toElementFn,
        maxLen: this._maxLen,
        autoCompactRatio: this._autoCompactRatio
      });
      (_a = out._setAutoCompactRatio) == null ? void 0 : _a.call(out, this._autoCompactRatio);
      let index = 0;
      for (const v of this) {
        const mv = thisArg === void 0 ? callback(v, index++, this) : callback.call(thisArg, v, index++, this);
        out.push(mv);
      }
      return out;
    }
    /**
     * (Protected) Set the internal auto-compaction ratio.
     * @remarks Time O(1), Space O(1)
     * @param value - New ratio to assign.
     * @returns void
     */
    _setAutoCompactRatio(value) {
      this._autoCompactRatio = value;
    }
    /**
     * (Protected) Iterate elements from front to back.
     * @remarks Time O(N), Space O(1)
     * @returns Iterator of E.
     */
    *_getIterator() {
      for (let i = this._offset; i < this.elements.length; i++) yield this.elements[i];
    }
    /**
     * (Protected) Iterate elements from back to front.
     * @remarks Time O(N), Space O(1)
     * @returns Iterator of E.
     */
    *_getReverseIterator() {
      for (let i = this.length - 1; i >= 0; i--) {
        const cur = this.at(i);
        if (cur !== void 0) yield cur;
      }
    }
    /**
     * (Protected) Create an empty instance of the same concrete class.
     * @remarks Time O(1), Space O(1)
     * @param [options] - Options forwarded to the constructor.
     * @returns An empty like-kind queue instance.
     */
    _createInstance(options) {
      const Ctor = this.constructor;
      return new Ctor([], options);
    }
    /**
     * (Protected) Create a like-kind queue and seed it from an iterable.
     * @remarks Time O(N), Space O(N)
     * @template EM
     * @template RM
     * @param [elements] - Iterable used to seed the new queue.
     * @param [options] - Options forwarded to the constructor.
     * @returns A like-kind Queue instance.
     */
    _createLike(elements = [], options) {
      const Ctor = this.constructor;
      return new Ctor(elements, options);
    }
  };

  // src/data-structures/binary-tree/binary-tree.ts
  var BinaryTreeNode = class {
    /**
     * Creates an instance of BinaryTreeNode.
     * @remarks Time O(1), Space O(1)
     *
     * @param key - The key of the node.
     * @param [value] - The value associated with the key.
     */
    constructor(key, value) {
      __publicField(this, "key");
      __publicField(this, "value");
      __publicField(this, "parent");
      __publicField(this, "_left");
      __publicField(this, "_right");
      __publicField(this, "_height", 0);
      __publicField(this, "_color", "BLACK");
      __publicField(this, "_count", 1);
      this.key = key;
      this.value = value;
    }
    /**
     * Gets the left child of the node.
     * @remarks Time O(1), Space O(1)
     *
     * @returns The left child.
     */
    get left() {
      return this._left;
    }
    /**
     * Sets the left child of the node and updates its parent reference.
     * @remarks Time O(1), Space O(1)
     *
     * @param v - The node to set as the left child.
     */
    set left(v) {
      if (v) {
        v.parent = this;
      }
      this._left = v;
    }
    /**
     * Gets the right child of the node.
     * @remarks Time O(1), Space O(1)
     *
     * @returns The right child.
     */
    get right() {
      return this._right;
    }
    /**
     * Sets the right child of the node and updates its parent reference.
     * @remarks Time O(1), Space O(1)
     *
     * @param v - The node to set as the right child.
     */
    set right(v) {
      if (v) {
        v.parent = this;
      }
      this._right = v;
    }
    /**
     * Gets the height of the node (used in self-balancing trees).
     * @remarks Time O(1), Space O(1)
     *
     * @returns The height.
     */
    get height() {
      return this._height;
    }
    /**
     * Sets the height of the node.
     * @remarks Time O(1), Space O(1)
     *
     * @param value - The new height.
     */
    set height(value) {
      this._height = value;
    }
    /**
     * Gets the color of the node (used in Red-Black trees).
     * @remarks Time O(1), Space O(1)
     *
     * @returns The node's color.
     */
    get color() {
      return this._color;
    }
    /**
     * Sets the color of the node.
     * @remarks Time O(1), Space O(1)
     *
     * @param value - The new color.
     */
    set color(value) {
      this._color = value;
    }
    /**
     * Gets the count of nodes in the subtree rooted at this node (used in order-statistic trees).
     * @remarks Time O(1), Space O(1)
     *
     * @returns The subtree node count.
     */
    get count() {
      return this._count;
    }
    /**
     * Sets the count of nodes in the subtree.
     * @remarks Time O(1), Space O(1)
     *
     * @param value - The new count.
     */
    set count(value) {
      this._count = value;
    }
    /**
     * Gets the position of the node relative to its parent.
     * @remarks Time O(1), Space O(1)
     *
     * @returns The family position (e.g., 'ROOT', 'LEFT', 'RIGHT').
     */
    get familyPosition() {
      if (!this.parent) {
        return this.left || this.right ? "ROOT" : "ISOLATED";
      }
      if (this.parent.left === this) {
        return this.left || this.right ? "ROOT_LEFT" : "LEFT";
      } else if (this.parent.right === this) {
        return this.left || this.right ? "ROOT_RIGHT" : "RIGHT";
      }
      return "MAL_NODE";
    }
  };
  var BinaryTree = class _BinaryTree extends IterableEntryBase {
    /**
     * Creates an instance of BinaryTree.
     * @remarks Time O(N * M), where N is the number of items in `keysNodesEntriesOrRaws` and M is the tree size at insertion time (due to O(M) `set` operation). Space O(N) for storing the nodes.
     *
     * @param [keysNodesEntriesOrRaws=[]] - An iterable of items to set.
     * @param [options] - Configuration options for the tree.
     */
    constructor(keysNodesEntriesOrRaws = [], options) {
      super();
      __publicField(this, "iterationType", "ITERATIVE");
      __publicField(this, "_isMapMode", true);
      __publicField(this, "_isDuplicate", false);
      // Map mode acceleration store:
      // - isMapMode=false: unused
      // - isMapMode=true: key -> node reference (O(1) has/getNode + fast get)
      __publicField(this, "_store", /* @__PURE__ */ new Map());
      __publicField(this, "_root");
      __publicField(this, "_size", 0);
      __publicField(this, "_NIL", new BinaryTreeNode(NaN));
      __publicField(this, "_toEntryFn");
      /**
       * (Protected) Default callback function, returns the node's key.
       * @remarks Time O(1)
       *
       * @param node - The node.
       * @returns The node's key or undefined.
       */
      __publicField(this, "_DEFAULT_NODE_CALLBACK", (node) => node == null ? void 0 : node.key);
      if (options) {
        const { iterationType, toEntryFn, isMapMode, isDuplicate } = options;
        if (iterationType) this.iterationType = iterationType;
        if (isMapMode !== void 0) this._isMapMode = isMapMode;
        if (isDuplicate !== void 0) this._isDuplicate = isDuplicate;
        if (typeof toEntryFn === "function") this._toEntryFn = toEntryFn;
        else if (toEntryFn) raise(TypeError, ERR.notAFunction("toEntryFn", "BinaryTree"));
      }
      if (keysNodesEntriesOrRaws) this.setMany(keysNodesEntriesOrRaws);
    }
    /**
     * Gets whether the tree is in Map mode.
     * @remarks In Map mode (default), values are stored in an external Map, and nodes only hold keys. If false, values are stored directly on the nodes. Time O(1)
     *
     * @returns True if in Map mode, false otherwise.
     */
    get isMapMode() {
      return this._isMapMode;
    }
    /**
     * Gets whether the tree allows duplicate keys.
     * @remarks Time O(1)
     *
     * @returns True if duplicates are allowed, false otherwise.
     */
    get isDuplicate() {
      return this._isDuplicate;
    }
    /**
     * Gets the external value store (used in Map mode).
     * @remarks Time O(1)
     *
     * @returns The map storing key-value pairs.
     */
    get store() {
      return this._store;
    }
    /**
     * Gets the root node of the tree.
     * @remarks Time O(1)
     *
     * @returns The root node.
     */
    get root() {
      return this._root;
    }
    /**
     * Gets the number of nodes in the tree.
     * @remarks Time O(1)
     *
     * @returns The size of the tree.
     */
    get size() {
      return this._size;
    }
    /**
     * Gets the sentinel NIL node (used in self-balancing trees like Red-Black Tree).
     * @remarks Time O(1)
     *
     * @returns The NIL node.
     */
    get NIL() {
      return this._NIL;
    }
    /**
     * Gets the function used to convert raw data objects (R) into [key, value] entries.
     * @remarks Time O(1)
     *
     * @returns The conversion function.
     */
    get toEntryFn() {
      return this._toEntryFn;
    }
    /**
     * (Protected) Creates a new node.
     * @remarks Time O(1), Space O(1)
     *
     * @param key - The key for the new node.
     * @param [value] - The value for the new node (used if not in Map mode).
     * @returns The newly created node.
     */
    createNode(key, value) {
      return new BinaryTreeNode(key, value);
    }
    /**
     * Creates a new, empty tree of the same type and configuration.
     * @remarks Time O(1) (excluding options cloning), Space O(1)
     *
     * @param [options] - Optional overrides for the new tree's options.
     * @returns A new, empty tree instance.
     */
    createTree(options) {
      return this._createInstance(options);
    }
    /**
     * Ensures the input is a node. If it's a key or entry, it searches for the node.
     * @remarks Time O(1) if a node is passed. O(N) if a key or entry is passed (due to `getNode` performing a full search). Space O(1) if iterative search, O(H) if recursive (where H is height, O(N) worst-case).
     *
     * @param keyNodeOrEntry - The item to resolve to a node.
     * @param [iterationType=this.iterationType] - The traversal method to use if searching.
     * @returns The resolved node, or null/undefined if not found or input is null/undefined.
     */
    ensureNode(keyNodeOrEntry, iterationType = this.iterationType) {
      if (keyNodeOrEntry === null) return null;
      if (keyNodeOrEntry === void 0) return;
      if (keyNodeOrEntry === this._NIL) return;
      if (this.isNode(keyNodeOrEntry)) return keyNodeOrEntry;
      if (this.isEntry(keyNodeOrEntry)) {
        const key = keyNodeOrEntry[0];
        if (key === null) return null;
        if (key === void 0) return;
        return this.getNode(key, this._root, iterationType);
      }
      return this.getNode(keyNodeOrEntry, this._root, iterationType);
    }
    /**
     * Checks if the given item is a `BinaryTreeNode` instance.
     * @remarks Time O(1), Space O(1)
     *
     * @param keyNodeOrEntry - The item to check.
     * @returns True if it's a node, false otherwise.
     */
    isNode(keyNodeOrEntry) {
      return keyNodeOrEntry instanceof BinaryTreeNode;
    }
    /**
     * Checks if the given item is a raw data object (R) that needs conversion via `toEntryFn`.
     * @remarks Time O(1), Space O(1)
     *
     * @param keyNodeEntryOrRaw - The item to check.
     * @returns True if it's a raw object, false otherwise.
     */
    isRaw(keyNodeEntryOrRaw) {
      return this._toEntryFn !== void 0 && typeof keyNodeEntryOrRaw === "object";
    }
    /**
     * Checks if the given item is a "real" node (i.e., not null, undefined, or NIL).
     * @remarks Time O(1), Space O(1)
     *
     * @param keyNodeOrEntry - The item to check.
     * @returns True if it's a real node, false otherwise.
     */
    isRealNode(keyNodeOrEntry) {
      if (keyNodeOrEntry === this._NIL || keyNodeOrEntry === null || keyNodeOrEntry === void 0) return false;
      return this.isNode(keyNodeOrEntry);
    }
    /**
     * Checks if the given item is either a "real" node or null.
     * @remarks Time O(1), Space O(1)
     *
     * @param keyNodeOrEntry - The item to check.
     * @returns True if it's a real node or null, false otherwise.
     */
    isRealNodeOrNull(keyNodeOrEntry) {
      return keyNodeOrEntry === null || this.isRealNode(keyNodeOrEntry);
    }
    /**
     * Checks if the given item is the sentinel NIL node.
     * @remarks Time O(1), Space O(1)
     *
     * @param keyNodeOrEntry - The item to check.
     * @returns True if it's the NIL node, false otherwise.
     */
    isNIL(keyNodeOrEntry) {
      return keyNodeOrEntry === this._NIL;
    }
    /**
     * Checks if the given item is a `Range` object.
     * @remarks Time O(1), Space O(1)
     *
     * @param keyNodeEntryOrPredicate - The item to check.
     * @returns True if it's a Range, false otherwise.
     */
    isRange(keyNodeEntryOrPredicate) {
      return keyNodeEntryOrPredicate instanceof Range;
    }
    /**
     * Checks if a node is a leaf (has no real children).
     * @remarks Time O(N) if a key/entry is passed (due to `ensureNode`). O(1) if a node is passed. Space O(1) or O(H) (from `ensureNode`).
     *
     * @param keyNodeOrEntry - The node to check.
     * @returns True if the node is a leaf, false otherwise.
     */
    isLeaf(keyNodeOrEntry) {
      keyNodeOrEntry = this.ensureNode(keyNodeOrEntry);
      if (keyNodeOrEntry === void 0) return false;
      if (keyNodeOrEntry === null) return true;
      return !this.isRealNode(keyNodeOrEntry.left) && !this.isRealNode(keyNodeOrEntry.right);
    }
    /**
     * Checks if the given item is a [key, value] entry pair.
     * @remarks Time O(1), Space O(1)
     *
     * @param keyNodeOrEntry - The item to check.
     * @returns True if it's an entry, false otherwise.
     */
    isEntry(keyNodeOrEntry) {
      return Array.isArray(keyNodeOrEntry) && keyNodeOrEntry.length === 2;
    }
    /**
     * Checks if the given key is valid (comparable or null).
     * @remarks Time O(1), Space O(1)
     *
     * @param key - The key to validate.
     * @returns True if the key is valid, false otherwise.
     */
    isValidKey(key) {
      if (key === null) return true;
      return isComparable(key);
    }
    /**
      * Adds a new node to the tree.
      * @remarks Time O(N) — level-order traversal to find an empty slot. Space O(N) for the BFS queue. BST/Red-Black Tree/AVL Tree subclasses override to O(log N).
      *
      * @param keyNodeOrEntry - The key, node, or entry to add.
      * @returns True if the addition was successful, false otherwise.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Add a single node
    *  const tree = new BinaryTree<number>();
    *     tree.add(1);
    *     tree.add(2);
    *     tree.add(3);
    *     console.log(tree.size); // 3;
    *     console.log(tree.has(1)); // true;
      */
    add(keyNodeOrEntry) {
      return this.set(keyNodeOrEntry);
    }
    /**
      * Adds or updates a new node to the tree.
      * @remarks Time O(N) — level-order traversal to find an empty slot. Space O(N) for the BFS queue. BST/Red-Black Tree/AVL Tree subclasses override to O(log N).
      *
      * @param keyNodeOrEntry - The key, node, or entry to set or update.
      * @param [value] - The value, if providing just a key.
      * @returns True if the addition was successful, false otherwise.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // basic BinaryTree creation and insertion
    *  // Create a BinaryTree with entries
    *     const entries: [number, string][] = [
    *       [6, 'six'],
    *       [1, 'one'],
    *       [2, 'two'],
    *       [7, 'seven'],
    *       [5, 'five'],
    *       [3, 'three'],
    *       [4, 'four'],
    *       [9, 'nine'],
    *       [8, 'eight']
    *     ];
    *
    *     const tree = new BinaryTree(entries);
    *
    *     // Verify size
    *     console.log(tree.size); // 9;
    *
    *     // Add new element
    *     tree.set(10, 'ten');
    *     console.log(tree.size); // 10;
      */
    set(keyNodeOrEntry, value) {
      const [newNode] = this._keyValueNodeOrEntryToNodeAndValue(keyNodeOrEntry, value);
      if (newNode === void 0) return false;
      if (!this._root) {
        this._setRoot(newNode);
        if (this._isMapMode && newNode !== null && newNode !== void 0) this._store.set(newNode.key, newNode);
        if (newNode !== null) this._size = 1;
        return true;
      }
      const queue = new Queue([this._root]);
      let potentialParent;
      while (queue.length > 0) {
        const cur = queue.shift();
        if (!cur) continue;
        if (!this._isDuplicate) {
          if (newNode !== null && cur.key === newNode.key) {
            this._replaceNode(cur, newNode);
            if (this._isMapMode && newNode !== null) this._store.set(cur.key, newNode);
            return true;
          }
        }
        if (potentialParent === void 0 && (cur.left === void 0 || cur.right === void 0)) {
          potentialParent = cur;
        }
        if (cur.left !== null) {
          if (cur.left) queue.push(cur.left);
        }
        if (cur.right !== null) {
          if (cur.right) queue.push(cur.right);
        }
      }
      if (potentialParent) {
        if (potentialParent.left === void 0) {
          potentialParent.left = newNode;
        } else if (potentialParent.right === void 0) {
          potentialParent.right = newNode;
        }
        if (this._isMapMode && newNode !== null && newNode !== void 0) this._store.set(newNode.key, newNode);
        if (newNode !== null) this._size++;
        return true;
      }
      return false;
    }
    /**
      * Adds multiple items to the tree.
      * @remarks Time O(N * M), where N is the number of items to set and M is the size of the tree at insertion (due to O(M) `set` operation). Space O(M) (from `set`) + O(N) (for the `inserted` array).
      *
      * @param keysNodesEntriesOrRaws - An iterable of items to set.
      * @returns An array of booleans indicating the success of each individual `set` operation.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Bulk add
    *  const tree = new BinaryTree<number>();
    *     tree.addMany([1, 2, 3, 4, 5]);
    *     console.log(tree.size); // 5;
      */
    addMany(keysNodesEntriesOrRaws) {
      return this.setMany(keysNodesEntriesOrRaws);
    }
    /**
      * Adds or updates multiple items to the tree.
      * @remarks Time O(N * M), where N is the number of items to set and M is the size of the tree at insertion (due to O(M) `set` operation). Space O(M) (from `set`) + O(N) (for the `inserted` array).
      *
      * @param keysNodesEntriesOrRaws - An iterable of items to set or update.
      * @param [values] - An optional parallel iterable of values.
      * @returns An array of booleans indicating the success of each individual `set` operation.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Set multiple entries
    *  const tree = new BinaryTree<number, string>();
    *     tree.setMany([[1, 'a'], [2, 'b'], [3, 'c']]);
    *     console.log(tree.size); // 3;
      */
    setMany(keysNodesEntriesOrRaws, values) {
      const inserted = [];
      let valuesIterator;
      if (values) {
        valuesIterator = values[Symbol.iterator]();
      }
      for (let keyNodeEntryOrRaw of keysNodesEntriesOrRaws) {
        let value = void 0;
        if (valuesIterator) {
          const valueResult = valuesIterator.next();
          if (!valueResult.done) {
            value = valueResult.value;
          }
        }
        if (this.isRaw(keyNodeEntryOrRaw)) keyNodeEntryOrRaw = this._toEntryFn(keyNodeEntryOrRaw);
        inserted.push(this.set(keyNodeEntryOrRaw, value));
      }
      return inserted;
    }
    /**
      * Merges another tree into this one by seting all its nodes.
      * @remarks Time O(N * M), same as `setMany`, where N is the size of `anotherTree` and M is the size of this tree. Space O(M) (from `set`).
      *
      * @param anotherTree - The tree to merge.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Combine trees
    *  const t1 = new BinaryTree<number>([1, 2]);
    *     const t2 = new BinaryTree<number>([3, 4]);
    *     t1.merge(t2);
    *     console.log(t1.size); // 4;
      */
    merge(anotherTree) {
      this.setMany(anotherTree, []);
    }
    /**
     * Deletes a node from the tree (internal, returns balancing metadata).
     * @remarks Time O(N) — O(N) to find the node + O(H) for predecessor swap. Space O(1). BST/Red-Black Tree/AVL Tree subclasses override to O(log N).
     * @internal Used by AVL/BST subclasses that need balancing metadata after deletion.
     *
     * @param keyNodeEntryRawOrPredicate - The node to delete.
     * @returns An array containing deletion results with balancing metadata.
     */
    _deleteInternal(keyNodeEntryRawOrPredicate) {
      const deletedResult = [];
      if (!this._root) return deletedResult;
      const curr = this.getNode(keyNodeEntryRawOrPredicate);
      if (!curr) return deletedResult;
      const parent = curr == null ? void 0 : curr.parent;
      let needBalanced;
      let orgCurrent = curr;
      if (!curr.left && !curr.right && !parent) {
        this._setRoot(void 0);
      } else if (curr.left) {
        const leftSubTreeRightMost = this.getRightMost((node) => node, curr.left);
        if (leftSubTreeRightMost) {
          const parentOfLeftSubTreeMax = leftSubTreeRightMost.parent;
          orgCurrent = this._swapProperties(curr, leftSubTreeRightMost);
          if (this._isMapMode) {
            this._store.set(curr.key, curr);
            this._store.set(leftSubTreeRightMost.key, leftSubTreeRightMost);
          }
          if (parentOfLeftSubTreeMax) {
            if (parentOfLeftSubTreeMax.right === leftSubTreeRightMost)
              parentOfLeftSubTreeMax.right = leftSubTreeRightMost.left;
            else parentOfLeftSubTreeMax.left = leftSubTreeRightMost.left;
            needBalanced = parentOfLeftSubTreeMax;
          }
        }
      } else if (parent) {
        const { familyPosition: fp } = curr;
        if (fp === "LEFT" || fp === "ROOT_LEFT") {
          parent.left = curr.right;
        } else if (fp === "RIGHT" || fp === "ROOT_RIGHT") {
          parent.right = curr.right;
        }
        needBalanced = parent;
      } else {
        this._setRoot(curr.right);
        curr.right = void 0;
      }
      this._size = this._size - 1;
      deletedResult.push({ deleted: orgCurrent, needBalanced });
      if (this._isMapMode && orgCurrent) this._store.delete(orgCurrent.key);
      return deletedResult;
    }
    /**
      * Deletes a node from the tree.
      * @remarks Time O(N) — O(N) to find the node + O(H) for predecessor swap. Space O(1). BST/Red-Black Tree/AVL Tree subclasses override to O(log N).
      *
      * @param keyNodeEntryRawOrPredicate - The node to delete.
      * @returns True if the node was found and deleted, false otherwise.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Remove a node
    *  const tree = new BinaryTree<number>([1, 2, 3, 4, 5]);
    *     tree.delete(3);
    *     console.log(tree.has(3)); // false;
    *     console.log(tree.size); // 4;
      */
    delete(keyNodeEntryRawOrPredicate) {
      return this._deleteInternal(keyNodeEntryRawOrPredicate).length > 0;
    }
    /**
     * Searches the tree for nodes matching a predicate.
     * @remarks Time O(N) — full DFS scan; may visit every node. Space O(H) for call/explicit stack (O(N) worst-case). BST subclasses with key search override to O(log N).
     *
     * @template C - The type of the callback function.
     * @param keyNodeEntryOrPredicate - The key, node, entry, or predicate function to search for.
     * @param [onlyOne=false] - If true, stops after finding the first match.
     * @param [callback=this._DEFAULT_NODE_CALLBACK] - A function to call on matching nodes.
     * @param [startNode=this._root] - The node to start the search from.
     * @param [iterationType=this.iterationType] - Whether to use 'RECURSIVE' or 'ITERATIVE' search.
     * @returns An array of results from the callback function for each matching node.
     */
    search(keyNodeEntryOrPredicate, onlyOne = false, callback = this._DEFAULT_NODE_CALLBACK, startNode = this._root, iterationType = this.iterationType) {
      if (keyNodeEntryOrPredicate === void 0) return [];
      if (keyNodeEntryOrPredicate === null) return [];
      startNode = this.ensureNode(startNode);
      if (!startNode) return [];
      const predicate = this._ensurePredicate(keyNodeEntryOrPredicate);
      const ans = [];
      if (iterationType === "RECURSIVE") {
        const dfs = (cur) => {
          if (predicate(cur)) {
            ans.push(callback(cur));
            if (onlyOne) return;
          }
          if (!this.isRealNode(cur.left) && !this.isRealNode(cur.right)) return;
          if (this.isRealNode(cur.left)) dfs(cur.left);
          if (this.isRealNode(cur.right)) dfs(cur.right);
        };
        dfs(startNode);
      } else {
        const stack = [startNode];
        while (stack.length > 0) {
          const cur = stack.pop();
          if (this.isRealNode(cur)) {
            if (predicate(cur)) {
              ans.push(callback(cur));
              if (onlyOne) return ans;
            }
            if (this.isRealNode(cur.left)) stack.push(cur.left);
            if (this.isRealNode(cur.right)) stack.push(cur.right);
          }
        }
      }
      return ans;
    }
    getNodes(keyNodeEntryOrPredicate, onlyOne = false, startNode = this._root, iterationType = this.iterationType) {
      return this.search(keyNodeEntryOrPredicate, onlyOne, (node) => node, startNode, iterationType);
    }
    /**
      * Gets the first node matching a predicate.
      * @remarks Time O(N) via `search`. Space O(H) or O(N). BST/Red-Black Tree/AVL Tree subclasses override to O(log N) for key lookups.
      *
      * @param keyNodeEntryOrPredicate - The key, node, entry, or predicate function to search for.
      * @param [startNode=this._root] - The node to start the search from.
      * @param [iterationType=this.iterationType] - The traversal method.
      * @returns The first matching node, or undefined if not found.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Get node by key
    *  const tree = new BinaryTree<number, string>([[1, 'root'], [2, 'child']]);
    *     console.log(tree.getNode(2)?.value); // 'child';
      */
    getNode(keyNodeEntryOrPredicate, startNode = this._root, iterationType = this.iterationType) {
      if (this._isMapMode && keyNodeEntryOrPredicate !== null && keyNodeEntryOrPredicate !== void 0) {
        if (!this._isPredicate(keyNodeEntryOrPredicate)) {
          const key = this._extractKey(keyNodeEntryOrPredicate);
          if (key === null || key === void 0) return;
          return this._store.get(key);
        }
      }
      return this.search(keyNodeEntryOrPredicate, true, (node) => node, startNode, iterationType)[0];
    }
    /**
      * Gets the value associated with a key.
      * @remarks Time O(1) in Map mode, O(N) otherwise (via `getNode`). Space O(1) in Map mode, O(H) or O(N) otherwise. BST subclasses override non-Map-mode to O(log N).
      *
      * @param keyNodeEntryOrPredicate - The key, node, or entry to get the value for.
      * @param [startNode=this._root] - The node to start searching from (if not in Map mode).
      * @param [iterationType=this.iterationType] - The traversal method (if not in Map mode).
      * @returns The associated value, or undefined.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Retrieve value by key
    *  const tree = new BinaryTree<number, string>([[1, 'root'], [2, 'left'], [3, 'right']]);
    *     console.log(tree.get(2)); // 'left';
    *     console.log(tree.get(99)); // undefined;
      */
    get(keyNodeEntryOrPredicate, startNode = this._root, iterationType = this.iterationType) {
      var _a, _b;
      if (this._isMapMode) {
        const key = this._extractKey(keyNodeEntryOrPredicate);
        if (key === null || key === void 0) return;
        return (_a = this._store.get(key)) == null ? void 0 : _a.value;
      }
      return (_b = this.getNode(keyNodeEntryOrPredicate, startNode, iterationType)) == null ? void 0 : _b.value;
    }
    has(keyNodeEntryOrPredicate, startNode = this._root, iterationType = this.iterationType) {
      if (this._isMapMode && keyNodeEntryOrPredicate !== void 0 && keyNodeEntryOrPredicate !== null) {
        if (!this._isPredicate(keyNodeEntryOrPredicate)) {
          const key = this._extractKey(keyNodeEntryOrPredicate);
          if (key === null || key === void 0) return false;
          return this._store.has(key);
        }
      }
      return this.search(keyNodeEntryOrPredicate, true, (node) => node, startNode, iterationType).length > 0;
    }
    /**
      * Clears the tree of all nodes and values.
      * @remarks Time O(N) if in Map mode (due to `_store.clear()`), O(1) otherwise. Space O(1)
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Remove all nodes
    *  const tree = new BinaryTree<number>([1, 2, 3]);
    *     tree.clear();
    *     console.log(tree.isEmpty()); // true;
      */
    clear() {
      this._clearNodes();
      if (this._isMapMode) this._clearValues();
    }
    /**
      * Checks if the tree is empty.
      * @remarks Time O(1), Space O(1)
      *
      * @returns True if the tree has no nodes, false otherwise.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Check empty
    *  console.log(new BinaryTree().isEmpty()); // true;
      */
    isEmpty() {
      return this._size === 0;
    }
    /**
     * Checks if the tree is perfectly balanced.
     * @remarks A tree is perfectly balanced if the difference between min and max height is at most 1. Time O(N), as it requires two full traversals (`getMinHeight` and `getHeight`). Space O(H) or O(N) (from height calculation).
     *
     * @param [startNode=this._root] - The node to start checking from.
     * @returns True if perfectly balanced, false otherwise.
     */
    isPerfectlyBalanced(startNode = this._root) {
      return this.getMinHeight(startNode) + 1 >= this.getHeight(startNode);
    }
    /**
      * Checks if the tree is a valid Binary Search Tree (BST).
      * @remarks Time O(N), as it must visit every node. Space O(H) for the call stack (recursive) or explicit stack (iterative), where H is the tree height (O(N) worst-case).
      *
      * @param [startNode=this._root] - The node to start checking from.
      * @param [iterationType=this.iterationType] - The traversal method.
      * @returns True if it's a valid BST, false otherwise.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Check BST property
    *  const tree = new BinaryTree<number>([1, 2, 3]);
    *     // BinaryTree doesn't guarantee BST order
    *     console.log(typeof tree.isBST()); // 'boolean';
      */
    isBST(startNode = this._root, iterationType = this.iterationType) {
      const startNodeSired = this.ensureNode(startNode);
      if (!startNodeSired) return true;
      if (iterationType === "RECURSIVE") {
        const dfs = (cur, min, max) => {
          if (!this.isRealNode(cur)) return true;
          const numKey = Number(cur.key);
          if (numKey <= min || numKey >= max) return false;
          return dfs(cur.left, min, numKey) && dfs(cur.right, numKey, max);
        };
        const isStandardBST = dfs(startNodeSired, Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER);
        const isInverseBST = dfs(startNodeSired, Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER);
        return isStandardBST || isInverseBST;
      } else {
        const checkBST = (checkMax = false) => {
          const stack = [];
          let prev = checkMax ? Number.MAX_SAFE_INTEGER : Number.MIN_SAFE_INTEGER;
          let curr = startNodeSired;
          while (this.isRealNode(curr) || stack.length > 0) {
            while (this.isRealNode(curr)) {
              stack.push(curr);
              curr = curr.left;
            }
            curr = stack.pop();
            const numKey = Number(curr.key);
            if (!this.isRealNode(curr) || !checkMax && prev >= numKey || checkMax && prev <= numKey) return false;
            prev = numKey;
            curr = curr.right;
          }
          return true;
        };
        const isStandardBST = checkBST();
        const isInverseBST = checkBST(true);
        return isStandardBST || isInverseBST;
      }
    }
    /**
      * Gets the depth of a node (distance from `startNode`).
      * @remarks Time O(H), where H is the depth of the `dist` node relative to `startNode`. O(N) worst-case. Space O(1).
      *
      * @param dist - The node to find the depth of.
      * @param [startNode=this._root] - The node to measure depth from (defaults to root).
      * @returns The depth (0 if `dist` is `startNode`).
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Get depth of a node
    *  const tree = new BinaryTree<number>([1, 2, 3, 4, 5]);
    *     const node = tree.getNode(4);
    *     console.log(tree.getDepth(node!)); // 2;
      */
    getDepth(dist, startNode = this._root) {
      let distEnsured = this.ensureNode(dist);
      const beginRootEnsured = this.ensureNode(startNode);
      let depth = 0;
      while (distEnsured == null ? void 0 : distEnsured.parent) {
        if (distEnsured === beginRootEnsured) {
          return depth;
        }
        depth++;
        distEnsured = distEnsured.parent;
      }
      return depth;
    }
    /**
      * Gets the maximum height of the tree (longest path from startNode to a leaf).
      * @remarks Time O(N), as it must visit every node. Space O(H) for recursive stack (O(N) worst-case) or O(N) for iterative stack (storing node + depth).
      *
      * @param [startNode=this._root] - The node to start measuring from.
      * @param [iterationType=this.iterationType] - The traversal method.
      * @returns The height ( -1 for an empty tree, 0 for a single-node tree).
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Get tree height
    *  const tree = new BinaryTree<number>([1, 2, 3, 4, 5]);
    *     console.log(tree.getHeight()); // 2;
      */
    getHeight(startNode = this._root, iterationType = this.iterationType) {
      startNode = this.ensureNode(startNode);
      if (!this.isRealNode(startNode)) return -1;
      if (iterationType === "RECURSIVE") {
        const _getMaxHeight = (cur) => {
          if (!this.isRealNode(cur)) return -1;
          const leftHeight = _getMaxHeight(cur.left);
          const rightHeight = _getMaxHeight(cur.right);
          return Math.max(leftHeight, rightHeight) + 1;
        };
        return _getMaxHeight(startNode);
      } else {
        const stack = [{ node: startNode, depth: 0 }];
        let maxHeight = 0;
        while (stack.length > 0) {
          const { node, depth } = stack.pop();
          if (this.isRealNode(node.left)) stack.push({ node: node.left, depth: depth + 1 });
          if (this.isRealNode(node.right)) stack.push({ node: node.right, depth: depth + 1 });
          maxHeight = Math.max(maxHeight, depth);
        }
        return maxHeight;
      }
    }
    /**
     * Gets the minimum height of the tree (shortest path from startNode to a leaf).
     * @remarks Time O(N), as it must visit every node. Space O(H) for recursive stack (O(N) worst-case) or O(N) for iterative (due to `depths` Map).
     *
     * @param [startNode=this._root] - The node to start measuring from.
     * @param [iterationType=this.iterationType] - The traversal method.
     * @returns The minimum height (-1 for empty, 0 for single node).
     */
    getMinHeight(startNode = this._root, iterationType = this.iterationType) {
      startNode = this.ensureNode(startNode);
      if (!startNode) return -1;
      if (iterationType === "RECURSIVE") {
        const _getMinHeight = (cur) => {
          if (!this.isRealNode(cur)) return 0;
          if (!this.isRealNode(cur.left) && !this.isRealNode(cur.right)) return 0;
          const leftMinHeight = _getMinHeight(cur.left);
          const rightMinHeight = _getMinHeight(cur.right);
          return Math.min(leftMinHeight, rightMinHeight) + 1;
        };
        return _getMinHeight(startNode);
      } else {
        const stack = [];
        let node = startNode, last = null;
        const depths = /* @__PURE__ */ new Map();
        while (stack.length > 0 || node) {
          if (this.isRealNode(node)) {
            stack.push(node);
            node = node.left;
          } else {
            node = stack[stack.length - 1];
            if (!this.isRealNode(node.right) || last === node.right) {
              node = stack.pop();
              if (this.isRealNode(node)) {
                const leftMinHeight = this.isRealNode(node.left) ? depths.get(node.left) : -1;
                const rightMinHeight = this.isRealNode(node.right) ? depths.get(node.right) : -1;
                depths.set(node, 1 + Math.min(leftMinHeight, rightMinHeight));
                last = node;
                node = null;
              }
            } else node = node.right;
          }
        }
        return depths.get(startNode);
      }
    }
    /**
     * Gets the path from a given node up to the root.
     * @remarks Time O(H), where H is the depth of the `beginNode`. O(N) worst-case. Space O(H) for the result array.
     *
     * @template C - The type of the callback function.
     * @param beginNode - The node to start the path from.
     * @param [callback=this._DEFAULT_NODE_CALLBACK] - A function to call on each node in the path.
     * @param [isReverse=false] - If true, returns the path from root-to-node.
     * @returns An array of callback results.
     */
    getPathToRoot(beginNode, callback = this._DEFAULT_NODE_CALLBACK, isReverse = false) {
      const result = [];
      let beginNodeEnsured = this.ensureNode(beginNode);
      if (!beginNodeEnsured) return result;
      while (beginNodeEnsured.parent) {
        result.push(callback(beginNodeEnsured));
        beginNodeEnsured = beginNodeEnsured.parent;
      }
      result.push(callback(beginNodeEnsured));
      return isReverse ? result.reverse() : result;
    }
    /**
     * Finds the leftmost node in a subtree (the node with the smallest key in a BST).
     * @remarks Time O(H), where H is the height of the left spine. O(N) worst-case. Space O(H) for recursive/trampoline stack.
     *
     * @template C - The type of the callback function.
     * @param [callback=this._DEFAULT_NODE_CALLBACK] - A function to call on the leftmost node.
     * @param [startNode=this._root] - The subtree root to search from.
     * @param [iterationType=this.iterationType] - The traversal method.
     * @returns The callback result for the leftmost node.
     */
    getLeftMost(callback = this._DEFAULT_NODE_CALLBACK, startNode = this._root, iterationType = this.iterationType) {
      if (this.isNIL(startNode)) return callback(void 0);
      const ensuredStartNode = this.ensureNode(startNode);
      if (!this.isRealNode(ensuredStartNode)) return callback(void 0);
      if (iterationType === "RECURSIVE") {
        const dfs = (cur) => {
          const { left } = cur;
          if (!this.isRealNode(left)) return cur;
          return dfs(left);
        };
        return callback(dfs(ensuredStartNode));
      } else {
        const dfs = makeTrampoline((cur) => {
          const { left } = cur;
          if (!this.isRealNode(left)) return cur;
          return makeTrampolineThunk(() => dfs(left));
        });
        return callback(dfs(ensuredStartNode));
      }
    }
    /**
     * Finds the rightmost node in a subtree (the node with the largest key in a BST).
     * @remarks Time O(H), where H is the height of the right spine. O(N) worst-case. Space O(H) for recursive/trampoline stack.
     *
     * @template C - The type of the callback function.
     * @param [callback=this._DEFAULT_NODE_CALLBACK] - A function to call on the rightmost node.
     * @param [startNode=this._root] - The subtree root to search from.
     * @param [iterationType=this.iterationType] - The traversal method.
     * @returns The callback result for the rightmost node.
     */
    getRightMost(callback = this._DEFAULT_NODE_CALLBACK, startNode = this._root, iterationType = this.iterationType) {
      if (this.isNIL(startNode)) return callback(void 0);
      startNode = this.ensureNode(startNode);
      if (!startNode) return callback(void 0);
      if (iterationType === "RECURSIVE") {
        const dfs = (cur) => {
          const { right } = cur;
          if (!this.isRealNode(right)) return cur;
          return dfs(right);
        };
        return callback(dfs(startNode));
      } else {
        const dfs = makeTrampoline((cur) => {
          const { right } = cur;
          if (!this.isRealNode(right)) return cur;
          return makeTrampolineThunk(() => dfs(right));
        });
        return callback(dfs(startNode));
      }
    }
    /**
     * Gets the Morris traversal predecessor (rightmost node in the left subtree, or node itself).
     * @remarks This is primarily a helper for Morris traversal. Time O(H), where H is the height of the left subtree. O(N) worst-case. Space O(1).
     *
     * @param node - The node to find the predecessor for.
     * @returns The Morris predecessor.
     */
    getPredecessor(node) {
      if (this.isRealNode(node.left)) {
        let predecessor = node.left;
        while (!this.isRealNode(predecessor) || this.isRealNode(predecessor.right) && predecessor.right !== node) {
          if (this.isRealNode(predecessor)) {
            predecessor = predecessor.right;
          }
        }
        return predecessor;
      } else {
        return node;
      }
    }
    /**
     * Gets the in-order successor of a node in a BST.
     * @remarks Time O(H), where H is the tree height. O(N) worst-case. Space O(H) (due to `getLeftMost` stack).
     *
     * @param [x] - The node to find the successor of.
     * @returns The successor node, or null/undefined if none exists.
     */
    getSuccessor(x) {
      x = this.ensureNode(x);
      if (!this.isRealNode(x)) return void 0;
      if (this.isRealNode(x.right)) {
        return this.getLeftMost((node) => node, x.right);
      }
      let y = x.parent;
      while (this.isRealNode(y) && x === y.right) {
        x = y;
        y = y.parent;
      }
      return y;
    }
    /**
     * Performs a Depth-First Search (DFS) traversal.
     * @remarks Time O(N), visits every node. Space O(H) for the call/explicit stack. O(N) worst-case.
     *
     * @template C - The type of the callback function.
     * @param [callback=this._DEFAULT_NODE_CALLBACK] - Function to call on each node.
     * @param [pattern='IN'] - The traversal order ('IN', 'PRE', 'POST').
     * @param [onlyOne=false] - If true, stops after the first callback.
     * @param [startNode=this._root] - The node to start from.
     * @param [iterationType=this.iterationType] - The traversal method.
     * @param [includeNull=false] - If true, includes null nodes in the traversal.
     * @returns An array of callback results.
     */
    dfs(callback = this._DEFAULT_NODE_CALLBACK, pattern = "IN", onlyOne = false, startNode = this._root, iterationType = this.iterationType, includeNull = false) {
      startNode = this.ensureNode(startNode);
      if (!startNode) return [];
      return this._dfs(callback, pattern, onlyOne, startNode, iterationType, includeNull);
    }
    /**
     * Performs a Breadth-First Search (BFS) or Level-Order traversal.
     * @remarks Time O(N), visits every node. Space O(N) in the worst case for the queue (e.g., a full last level).
     *
     * @template C - The type of the callback function.
     * @param [callback=this._DEFAULT_NODE_CALLBACK] - Function to call on each node.
     * @param [startNode=this._root] - The node to start from.
     * @param [iterationType=this.iterationType] - The traversal method ('RECURSIVE' BFS is less common but supported here).
     * @param [includeNull=false] - If true, includes null nodes in the traversal.
     * @returns An array of callback results.
     */
    bfs(callback = this._DEFAULT_NODE_CALLBACK, startNode = this._root, iterationType = this.iterationType, includeNull = false) {
      startNode = this.ensureNode(startNode);
      if (!startNode) return [];
      const ans = [];
      if (iterationType === "RECURSIVE") {
        const queue = new Queue([
          startNode
        ]);
        const dfs = (level) => {
          if (queue.length === 0) return;
          const current = queue.shift();
          ans.push(callback(current));
          if (includeNull) {
            if (current && this.isRealNodeOrNull(current.left)) queue.push(current.left);
            if (current && this.isRealNodeOrNull(current.right)) queue.push(current.right);
          } else {
            if (this.isRealNode(current.left)) queue.push(current.left);
            if (this.isRealNode(current.right)) queue.push(current.right);
          }
          dfs(level + 1);
        };
        dfs(0);
      } else {
        const queue = new Queue([startNode]);
        while (queue.length > 0) {
          const levelSize = queue.length;
          for (let i = 0; i < levelSize; i++) {
            const current = queue.shift();
            ans.push(callback(current));
            if (includeNull) {
              if (current && this.isRealNodeOrNull(current.left)) queue.push(current.left);
              if (current && this.isRealNodeOrNull(current.right)) queue.push(current.right);
            } else {
              if (this.isRealNode(current.left)) queue.push(current.left);
              if (this.isRealNode(current.right)) queue.push(current.right);
            }
          }
        }
      }
      return ans;
    }
    /**
     * Finds all leaf nodes in the tree.
     * @remarks Time O(N), visits every node. Space O(H) for recursive or iterative stack.
     *
     * @template C - The type of the callback function.
     * @param [callback=this._DEFAULT_NODE_CALLBACK] - Function to call on each leaf node.
     * @param [startNode=this._root] - The node to start from.
     * @param [iterationType=this.iterationType] - The traversal method.
     * @returns An array of callback results.
     */
    leaves(callback = this._DEFAULT_NODE_CALLBACK, startNode = this._root, iterationType = this.iterationType) {
      startNode = this.ensureNode(startNode);
      const leaves = [];
      if (!this.isRealNode(startNode)) return [];
      if (iterationType === "RECURSIVE") {
        const dfs = (cur) => {
          if (this.isLeaf(cur)) {
            leaves.push(callback(cur));
          }
          if (!this.isRealNode(cur.left) && !this.isRealNode(cur.right)) return;
          if (this.isRealNode(cur.left)) dfs(cur.left);
          if (this.isRealNode(cur.right)) dfs(cur.right);
        };
        dfs(startNode);
      } else {
        const stack = [startNode];
        while (stack.length > 0) {
          const cur = stack.pop();
          if (this.isRealNode(cur)) {
            if (this.isLeaf(cur)) {
              leaves.push(callback(cur));
            }
            if (this.isRealNode(cur.right)) stack.push(cur.right);
            if (this.isRealNode(cur.left)) stack.push(cur.left);
          }
        }
      }
      return leaves;
    }
    /**
     * Returns a 2D array of nodes, grouped by level.
     * @remarks Time O(N), visits every node. Space O(N) for the result array and the queue/stack.
     *
     * @template C - The type of the callback function.
     * @param [callback=this._DEFAULT_NODE_CALLBACK] - Function to call on each node.
     * @param [startNode=this._root] - The node to start from.
     * @param [iterationType=this.iterationType] - The traversal method.
     * @param [includeNull=false] - If true, includes null nodes.
     * @returns A 2D array of callback results.
     */
    listLevels(callback = this._DEFAULT_NODE_CALLBACK, startNode = this._root, iterationType = this.iterationType, includeNull = false) {
      startNode = this.ensureNode(startNode);
      const levelsNodes = [];
      if (!startNode) return levelsNodes;
      if (iterationType === "RECURSIVE") {
        const _recursive = (node, level) => {
          if (!levelsNodes[level]) levelsNodes[level] = [];
          levelsNodes[level].push(callback(node));
          if (includeNull) {
            if (node && this.isRealNodeOrNull(node.left)) _recursive(node.left, level + 1);
            if (node && this.isRealNodeOrNull(node.right)) _recursive(node.right, level + 1);
          } else {
            if (node && node.left) _recursive(node.left, level + 1);
            if (node && node.right) _recursive(node.right, level + 1);
          }
        };
        _recursive(startNode, 0);
      } else {
        const stack = [[startNode, 0]];
        while (stack.length > 0) {
          const head = stack.pop();
          const [node, level] = head;
          if (!levelsNodes[level]) levelsNodes[level] = [];
          levelsNodes[level].push(callback(node));
          if (includeNull) {
            if (node && this.isRealNodeOrNull(node.right)) stack.push([node.right, level + 1]);
            if (node && this.isRealNodeOrNull(node.left)) stack.push([node.left, level + 1]);
          } else {
            if (node && node.right) stack.push([node.right, level + 1]);
            if (node && node.left) stack.push([node.left, level + 1]);
          }
        }
      }
      return levelsNodes;
    }
    /**
     * Performs a Morris (threaded) traversal.
     * @remarks This traversal uses O(1) extra space (excluding the result array) by temporarily modifying the tree's right child pointers. Time O(N), as each node is visited a constant number of times. Space O(1) (excluding the `ans` array).
     *
     * @template C - The type of the callback function.
     * @param [callback=this._DEFAULT_NODE_CALLBACK] - Function to call on each node.
     * @param [pattern='IN'] - The traversal order ('IN', 'PRE', 'POST').
     * @param [startNode=this._root] - The node to start from.
     * @returns An array of callback results.
     */
    morris(callback = this._DEFAULT_NODE_CALLBACK, pattern = "IN", startNode = this._root) {
      startNode = this.ensureNode(startNode);
      if (!startNode) return [];
      const ans = [];
      let cur = startNode;
      const _reverseEdge = (node) => {
        let pre = null;
        let next = null;
        while (node) {
          next = node.right;
          node.right = pre;
          pre = node;
          node = next;
        }
        return pre;
      };
      const _printEdge = (node) => {
        const tail = _reverseEdge(node);
        let cur2 = tail;
        while (cur2) {
          ans.push(callback(cur2));
          cur2 = cur2.right;
        }
        _reverseEdge(tail);
      };
      switch (pattern) {
        case "IN":
          while (cur) {
            if (cur.left) {
              const predecessor = this.getPredecessor(cur);
              if (!predecessor.right) {
                predecessor.right = cur;
                cur = cur.left;
                continue;
              } else {
                predecessor.right = null;
              }
            }
            ans.push(callback(cur));
            cur = cur.right;
          }
          break;
        case "PRE":
          while (cur) {
            if (cur.left) {
              const predecessor = this.getPredecessor(cur);
              if (!predecessor.right) {
                predecessor.right = cur;
                ans.push(callback(cur));
                cur = cur.left;
                continue;
              } else {
                predecessor.right = null;
              }
            } else {
              ans.push(callback(cur));
            }
            cur = cur.right;
          }
          break;
        case "POST":
          while (cur) {
            if (cur.left) {
              const predecessor = this.getPredecessor(cur);
              if (predecessor.right === null) {
                predecessor.right = cur;
                cur = cur.left;
                continue;
              } else {
                predecessor.right = null;
                _printEdge(cur.left);
              }
            }
            cur = cur.right;
          }
          _printEdge(startNode);
          break;
      }
      return ans;
    }
    /**
      * Clones the tree.
      * @remarks Time O(N * M), where N is the number of nodes and M is the tree size during insertion (due to `bfs` + `set`, and `set` is O(M)). Space O(N) for the new tree and the BFS queue.
      *
      * @returns A new, cloned instance of the tree.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Deep copy
    *  const tree = new BinaryTree<number>([1, 2, 3]);
    *     const copy = tree.clone();
    *     copy.delete(1);
    *     console.log(tree.has(1)); // true;
      */
    clone() {
      const out = this._createInstance();
      this._clone(out);
      return out;
    }
    /**
      * Creates a new tree containing only the entries that satisfy the predicate.
      * @remarks Time O(N * M), where N is nodes in this tree, and M is size of the new tree during insertion (O(N) iteration + O(M) `set` for each item). Space O(N) for the new tree.
      *
      * @param predicate - A function to test each [key, value] pair.
      * @param [thisArg] - `this` context for the predicate.
      * @returns A new, filtered tree.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Filter nodes by condition
    *  const tree = new BinaryTree<number>([1, 2, 3, 4]);
    *     const result = tree.filter((_, key) => key > 2);
    *     console.log(result.size); // 2;
      */
    filter(predicate, thisArg) {
      const out = this._createInstance();
      let i = 0;
      for (const [k, v] of this) if (predicate.call(thisArg, v, k, i++, this)) out.set([k, v]);
      return out;
    }
    /**
      * Creates a new tree by mapping each [key, value] pair to a new entry.
      * @remarks Time O(N * M), where N is nodes in this tree, and M is size of the new tree during insertion. Space O(N) for the new tree.
      *
      * @template MK - New key type.
      * @template MV - New value type.
      * @template MR - New raw type.
      * @param cb - A function to map each [key, value] pair.
      * @param [options] - Options for the new tree.
      * @param [thisArg] - `this` context for the callback.
      * @returns A new, mapped tree.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Transform to new tree
    *  const tree = new BinaryTree<number, number>([[1, 10], [2, 20]]);
    *     const mapped = tree.map((v, key) => [key, (v ?? 0) + 1] as [number, number]);
    *     console.log([...mapped.values()]); // contains 11;
      */
    map(cb, options, thisArg) {
      const out = this._createLike([], options);
      let i = 0;
      for (const [k, v] of this) out.set(cb.call(thisArg, v, k, i++, this));
      return out;
    }
    /**
     * Generates a string representation of the tree for visualization.
     * @remarks Time O(N), visits every node. Space O(N*H) or O(N^2) in the worst case, as the string width can grow significantly.
     *
     * @param [startNode=this._root] - The node to start printing from.
     * @param [options] - Options to control the output (e.g., show nulls).
     * @returns The string representation of the tree.
     */
    toVisual(startNode = this._root, options) {
      const opts = { isShowUndefined: false, isShowNull: true, isShowRedBlackNIL: false, ...options };
      startNode = this.ensureNode(startNode);
      let output = "";
      if (!startNode) return output;
      if (opts.isShowUndefined) output += `U for undefined
`;
      if (opts.isShowNull) output += `N for null
`;
      if (opts.isShowRedBlackNIL) output += `S for Sentinel Node(NIL)
`;
      const display = (root) => {
        const [lines] = this._displayAux(root, opts);
        let paragraph = "";
        for (const line of lines) {
          paragraph += line + "\n";
        }
        output += paragraph;
      };
      display(startNode);
      return output;
    }
    /**
      * Prints a visual representation of the tree to the console.
      * @remarks Time O(N) (via `toVisual`). Space O(N*H) or O(N^2) (via `toVisual`).
      *
      * @param [options] - Options to control the output.
      * @param [startNode=this._root] - The node to start printing from.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Display tree
    *  const tree = new BinaryTree<number>([1, 2, 3]);
    *     expect(() => tree.print()).not.toThrow();
      */
    print(options, startNode = this._root) {
      console.log(this.toVisual(startNode, options));
    }
    /**
     * (Protected) Core DFS implementation.
     * @remarks Time O(N), visits every node satisfying predicates. Space O(H) for call/explicit stack. O(N) worst-case.
     *
     * @template C - Callback type.
     * @param [callback=this._DEFAULT_NODE_CALLBACK] - Function to call on nodes.
     * @param [pattern='IN'] - Traversal order.
     * @param [onlyOne=false] - Stop after first match.
     * @param [startNode=this._root] - Starting node.
     * @param [iterationType=this.iterationType] - Traversal method.
     * @param [includeNull=false] - Include nulls.
     * @param [shouldVisitLeft] - Predicate to traverse left.
     * @param [shouldVisitRight] - Predicate to traverse right.
     * @param [shouldVisitRoot] - Predicate to visit root.
     * @param [shouldProcessRoot] - Predicate to process root.
     * @returns Array of callback results.
     */
    _dfs(callback = this._DEFAULT_NODE_CALLBACK, pattern = "IN", onlyOne = false, startNode = this._root, iterationType = this.iterationType, includeNull = false, shouldVisitLeft = (node) => !!node, shouldVisitRight = (node) => !!node, shouldVisitRoot = (node) => {
      if (includeNull) return this.isRealNodeOrNull(node);
      return this.isRealNode(node);
    }, shouldProcessRoot = (node) => this.isRealNodeOrNull(node)) {
      startNode = this.ensureNode(startNode);
      if (!startNode) return [];
      const ans = [];
      if (iterationType === "RECURSIVE") {
        const dfs = (node) => {
          if (!shouldVisitRoot(node)) return;
          const visitLeft = () => {
            if (shouldVisitLeft(node) && (node == null ? void 0 : node.left) !== void 0) dfs(node == null ? void 0 : node.left);
          };
          const visitRight = () => {
            if (shouldVisitRight(node) && (node == null ? void 0 : node.right) !== void 0) dfs(node == null ? void 0 : node.right);
          };
          switch (pattern) {
            case "IN":
              visitLeft();
              if (shouldProcessRoot(node)) {
                ans.push(callback(node));
                if (onlyOne) return;
              }
              visitRight();
              break;
            case "PRE":
              if (shouldProcessRoot(node)) {
                ans.push(callback(node));
                if (onlyOne) return;
              }
              visitLeft();
              visitRight();
              break;
            case "POST":
              visitLeft();
              visitRight();
              if (shouldProcessRoot(node)) {
                ans.push(callback(node));
                if (onlyOne) return;
              }
              break;
          }
        };
        dfs(startNode);
      } else {
        const stack = [{ opt: 0 /* VISIT */, node: startNode }];
        const pushLeft = (cur) => {
          var _a;
          if (shouldVisitLeft(cur.node)) stack.push({ opt: 0 /* VISIT */, node: (_a = cur.node) == null ? void 0 : _a.left });
        };
        const pushRight = (cur) => {
          var _a;
          if (shouldVisitRight(cur.node)) stack.push({ opt: 0 /* VISIT */, node: (_a = cur.node) == null ? void 0 : _a.right });
        };
        const pushRoot = (cur) => {
          if (shouldVisitRoot(cur.node)) stack.push({ opt: 1 /* PROCESS */, node: cur.node });
        };
        while (stack.length > 0) {
          const cur = stack.pop();
          if (cur === void 0) continue;
          if (!shouldVisitRoot(cur.node)) continue;
          if (cur.opt === 1 /* PROCESS */) {
            if (shouldProcessRoot(cur.node) && cur.node !== void 0) {
              ans.push(callback(cur.node));
              if (onlyOne) return ans;
            }
          } else {
            switch (pattern) {
              case "IN":
                pushRight(cur);
                pushRoot(cur);
                pushLeft(cur);
                break;
              case "PRE":
                pushRight(cur);
                pushLeft(cur);
                pushRoot(cur);
                break;
              case "POST":
                pushRoot(cur);
                pushRight(cur);
                pushLeft(cur);
                break;
            }
          }
        }
      }
      return ans;
    }
    /**
     * (Protected) Gets the iterator for the tree (default in-order).
     * @remarks Time O(N) for full iteration. O(H) to get the first element. Space O(H) for the iterative stack. O(H) for recursive stack.
     *
     * @param [node=this._root] - The node to start iteration from.
     * @returns An iterator for [key, value] pairs.
     */
    *_getIterator(node = this._root) {
      if (!node) return;
      if (this.iterationType === "ITERATIVE") {
        const stack = [];
        let current = node;
        while (current || stack.length > 0) {
          while (this.isRealNode(current)) {
            stack.push(current);
            current = current.left;
          }
          current = stack.pop();
          if (this.isRealNode(current)) {
            yield [current.key, current.value];
            current = current.right;
          }
        }
      } else {
        if (node.left && this.isRealNode(node)) {
          yield* this[Symbol.iterator](node.left);
        }
        yield [node.key, node.value];
        if (node.right && this.isRealNode(node)) {
          yield* this[Symbol.iterator](node.right);
        }
      }
    }
    /**
     * (Protected) Snapshots the current tree's configuration options.
     * @remarks Time O(1)
     *
     * @template TK, TV, TR - Generic types for the options.
     * @returns The options object.
     */
    _snapshotOptions() {
      return {
        iterationType: this.iterationType,
        toEntryFn: this.toEntryFn,
        isMapMode: this.isMapMode,
        isDuplicate: this.isDuplicate
      };
    }
    /**
     * (Protected) Creates a new, empty instance of the same tree constructor.
     * @remarks Time O(1)
     *
     * @template TK, TV, TR - Generic types for the new instance.
     * @param [options] - Options for the new tree.
     * @returns A new, empty tree.
     */
    _createInstance(options) {
      const Ctor = this.constructor;
      return new Ctor([], { ...this._snapshotOptions(), ...options != null ? options : {} });
    }
    /**
     * (Protected) Creates a new instance of the same tree constructor, potentially with different generic types.
     * @remarks Time O(N) (or as per constructor) due to processing the iterable.
     *
     * @template TK, TV, TR - Generic types for the new instance.
     * @param [iter=[]] - An iterable to populate the new tree.
     * @param [options] - Options for the new tree.
     * @returns A new tree.
     */
    _createLike(iter = [], options) {
      const Ctor = this.constructor;
      return new Ctor(iter, { ...this._snapshotOptions(), ...options != null ? options : {} });
    }
    /**
     * (Protected) Converts a key, node, or entry into a standardized [node, value] tuple.
     * @remarks Time O(1)
     *
     * @param keyNodeOrEntry - The input item.
     * @param [value] - An optional value (used if input is just a key).
     * @returns A tuple of [node, value].
     */
    _keyValueNodeOrEntryToNodeAndValue(keyNodeOrEntry, value) {
      if (keyNodeOrEntry === void 0) return [void 0, void 0];
      if (keyNodeOrEntry === null) return [null, void 0];
      if (this.isNode(keyNodeOrEntry)) return [keyNodeOrEntry, value];
      if (this.isEntry(keyNodeOrEntry)) {
        const [key, entryValue] = keyNodeOrEntry;
        if (key === void 0) return [void 0, void 0];
        else if (key === null) return [null, void 0];
        const finalValue = value != null ? value : entryValue;
        return [this.createNode(key, finalValue), finalValue];
      }
      return [this.createNode(keyNodeOrEntry, value), value];
    }
    /**
     * (Protected) Helper for cloning. Performs a BFS and sets all nodes to the new tree.
     * @remarks Time O(N * M) (O(N) BFS + O(M) `set` for each node).
     *
     * @param cloned - The new, empty tree instance to populate.
     */
    _clone(cloned) {
      this.bfs(
        (node) => {
          if (node === null) cloned.set(null);
          else {
            cloned.set([node.key, node.value]);
          }
        },
        this._root,
        this.iterationType,
        true
        // Include nulls
      );
    }
    /**
     * (Protected) Recursive helper for `toVisual`.
     * @remarks Time O(N), Space O(N*H) or O(N^2)
     *
     * @param node - The current node.
     * @param options - Print options.
     * @returns Layout information for this subtree.
     */
    _displayAux(node, options) {
      const emptyDisplayLayout = [["\u2500"], 1, 0, 0];
      const newFrame = (n) => ({
        node: n,
        stage: 0,
        leftLayout: emptyDisplayLayout,
        rightLayout: emptyDisplayLayout
      });
      const stack = [newFrame(node)];
      let result = emptyDisplayLayout;
      const setChildResult = (layout) => {
        if (stack.length === 0) {
          result = layout;
          return;
        }
        const parent = stack[stack.length - 1];
        if (parent.stage === 1) parent.leftLayout = layout;
        else parent.rightLayout = layout;
      };
      while (stack.length > 0) {
        const frame = stack[stack.length - 1];
        const cur = frame.node;
        if (frame.stage === 0) {
          if (this._isDisplayLeaf(cur, options)) {
            stack.pop();
            const layout = this._resolveDisplayLeaf(cur, options, emptyDisplayLayout);
            setChildResult(layout);
            continue;
          }
          frame.stage = 1;
          stack.push(newFrame(cur.left));
        } else if (frame.stage === 1) {
          frame.stage = 2;
          stack.push(newFrame(cur.right));
        } else {
          stack.pop();
          const line = this.isNIL(cur) ? "S" : String(cur.key);
          const layout = _BinaryTree._buildNodeDisplay(line, line.length, frame.leftLayout, frame.rightLayout);
          setChildResult(layout);
        }
      }
      return result;
    }
    static _buildNodeDisplay(line, width, left, right) {
      const [leftLines, leftWidth, leftHeight, leftMiddle] = left;
      const [rightLines, rightWidth, rightHeight, rightMiddle] = right;
      const firstLine = " ".repeat(Math.max(0, leftMiddle + 1)) + "_".repeat(Math.max(0, leftWidth - leftMiddle - 1)) + line + "_".repeat(Math.max(0, rightMiddle)) + " ".repeat(Math.max(0, rightWidth - rightMiddle));
      const secondLine = (leftHeight > 0 ? " ".repeat(leftMiddle) + "/" + " ".repeat(leftWidth - leftMiddle - 1) : " ".repeat(leftWidth)) + " ".repeat(width) + (rightHeight > 0 ? " ".repeat(rightMiddle) + "\\" + " ".repeat(rightWidth - rightMiddle - 1) : " ".repeat(rightWidth));
      const mergedLines = [firstLine, secondLine];
      for (let i = 0; i < Math.max(leftHeight, rightHeight); i++) {
        const leftLine = i < leftHeight ? leftLines[i] : " ".repeat(leftWidth);
        const rightLine = i < rightHeight ? rightLines[i] : " ".repeat(rightWidth);
        mergedLines.push(leftLine + " ".repeat(width) + rightLine);
      }
      return [
        mergedLines,
        leftWidth + width + rightWidth,
        Math.max(leftHeight, rightHeight) + 2,
        leftWidth + Math.floor(width / 2)
      ];
    }
    /**
     * Check if a node is a display leaf (empty, null, undefined, NIL, or real leaf).
     */
    _isDisplayLeaf(node, options) {
      const { isShowNull, isShowUndefined, isShowRedBlackNIL } = options;
      if (node === null && !isShowNull) return true;
      if (node === void 0 && !isShowUndefined) return true;
      if (this.isNIL(node) && !isShowRedBlackNIL) return true;
      if (node === null || node === void 0) return true;
      const hasDisplayableLeft = this._hasDisplayableChild(node.left, options);
      const hasDisplayableRight = this._hasDisplayableChild(node.right, options);
      return !hasDisplayableLeft && !hasDisplayableRight;
    }
    _hasDisplayableChild(child, options) {
      if (child === null) return !!options.isShowNull;
      if (child === void 0) return !!options.isShowUndefined;
      if (this.isNIL(child)) return !!options.isShowRedBlackNIL;
      return true;
    }
    /**
     * Resolve a display leaf node to its layout.
     */
    _resolveDisplayLeaf(node, options, emptyDisplayLayout) {
      const { isShowNull, isShowUndefined, isShowRedBlackNIL } = options;
      if (node === null && !isShowNull) return emptyDisplayLayout;
      if (node === void 0 && !isShowUndefined) return emptyDisplayLayout;
      if (this.isNIL(node) && !isShowRedBlackNIL) return emptyDisplayLayout;
      if (node !== null && node !== void 0) {
        const line2 = this.isNIL(node) ? "S" : String(node.key);
        return _BinaryTree._buildNodeDisplay(line2, line2.length, emptyDisplayLayout, emptyDisplayLayout);
      }
      const line = node === void 0 ? "U" : "N";
      return _BinaryTree._buildNodeDisplay(line, line.length, [[""], 1, 0, 0], [[""], 1, 0, 0]);
    }
    /**
     * (Protected) Swaps the key/value properties of two nodes.
     * @remarks Time O(1)
     *
     * @param srcNode - The source node.
     * @param destNode - The destination node.
     * @returns The `destNode` (now holding `srcNode`'s properties).
     */
    _swapProperties(srcNode, destNode) {
      srcNode = this.ensureNode(srcNode);
      destNode = this.ensureNode(destNode);
      if (srcNode && destNode) {
        const { key, value } = destNode;
        const tempNode = this.createNode(key, value);
        if (tempNode) {
          destNode.key = srcNode.key;
          if (!this._isMapMode) destNode.value = srcNode.value;
          srcNode.key = tempNode.key;
          if (!this._isMapMode) srcNode.value = tempNode.value;
        }
        return destNode;
      }
      return void 0;
    }
    /**
     * (Protected) Replaces a node in the tree with a new node, maintaining children and parent links.
     * @remarks Time O(1)
     *
     * @param oldNode - The node to be replaced.
     * @param newNode - The node to insert.
     * @returns The `newNode`.
     */
    _replaceNode(oldNode, newNode) {
      if (oldNode.parent) {
        if (oldNode.parent.left === oldNode) {
          oldNode.parent.left = newNode;
        } else if (oldNode.parent.right === oldNode) {
          oldNode.parent.right = newNode;
        }
      }
      newNode.left = oldNode.left;
      newNode.right = oldNode.right;
      newNode.parent = oldNode.parent;
      if (this._root === oldNode) {
        this._setRoot(newNode);
      }
      return newNode;
    }
    /**
     * (Protected) Sets the root node and clears its parent reference.
     * @remarks Time O(1)
     *
     * @param v - The node to set as root.
     */
    _setRoot(v) {
      if (v) {
        v.parent = void 0;
      }
      this._root = v;
    }
    _ensurePredicate(keyNodeEntryOrPredicate) {
      if (keyNodeEntryOrPredicate === null || keyNodeEntryOrPredicate === void 0)
        return (node) => node ? false : false;
      if (this._isPredicate(keyNodeEntryOrPredicate)) return keyNodeEntryOrPredicate;
      if (this.isRealNode(keyNodeEntryOrPredicate))
        return (node) => node === keyNodeEntryOrPredicate;
      if (this.isEntry(keyNodeEntryOrPredicate)) {
        const [key] = keyNodeEntryOrPredicate;
        return (node) => {
          if (!node) return false;
          return node.key === key;
        };
      }
      return (node) => {
        if (!node) return false;
        return node.key === keyNodeEntryOrPredicate;
      };
    }
    /**
     * (Protected) Checks if an item is a predicate function.
     * @remarks Time O(1)
     *
     * @param p - The item to check.
     * @returns True if it's a function.
     */
    _isPredicate(p) {
      return typeof p === "function";
    }
    /**
     * (Protected) Extracts the key from a key, node, or entry.
     * @remarks Time O(1)
     *
     * @param keyNodeOrEntry - The item.
     * @returns The extracted key.
     */
    _extractKey(keyNodeOrEntry) {
      if (keyNodeOrEntry === null) return null;
      if (keyNodeOrEntry === void 0) return;
      if (keyNodeOrEntry === this._NIL) return;
      if (this.isNode(keyNodeOrEntry)) return keyNodeOrEntry.key;
      if (this.isEntry(keyNodeOrEntry)) return keyNodeOrEntry[0];
      return keyNodeOrEntry;
    }
    /**
     * (Protected) Sets a value in the external store (Map mode).
     * @remarks Time O(1) (average for Map.set).
     *
     * @param key - The key.
     * @param value - The value.
     * @returns True if successful.
     */
    _setValue(key, value) {
      if (key === null || key === void 0) return false;
      const node = this._store.get(key);
      if (!node) return false;
      node.value = value;
      return true;
    }
    /**
     * (Protected) Clears all nodes from the tree.
     * @remarks Time O(1)
     */
    _clearNodes() {
      this._setRoot(void 0);
      this._size = 0;
    }
    /**
     * (Protected) Clears all values from the external store.
     * @remarks Time O(N)
     */
    _clearValues() {
      this._store.clear();
    }
  };

  // src/data-structures/binary-tree/bst.ts
  var BSTNode = class {
    /**
     * Creates an instance of BSTNode.
     * @remarks Time O(1), Space O(1)
     *
     * @param key - The key of the node.
     * @param [value] - The value associated with the key.
     */
    constructor(key, value) {
      __publicField(this, "key");
      __publicField(this, "value");
      __publicField(this, "parent");
      __publicField(this, "_left");
      __publicField(this, "_right");
      __publicField(this, "_height", 0);
      __publicField(this, "_color", "BLACK");
      __publicField(this, "_count", 1);
      this.key = key;
      this.value = value;
    }
    /**
     * Gets the left child of the node.
     * @remarks Time O(1), Space O(1)
     *
     * @returns The left child.
     */
    get left() {
      return this._left;
    }
    /**
     * Sets the left child of the node and updates its parent reference.
     * @remarks Time O(1), Space O(1)
     *
     * @param v - The node to set as the left child.
     */
    set left(v) {
      if (v) v.parent = this;
      this._left = v;
    }
    /**
     * Gets the right child of the node.
     * @remarks Time O(1), Space O(1)
     *
     * @returns The right child.
     */
    get right() {
      return this._right;
    }
    /**
     * Sets the right child of the node and updates its parent reference.
     * @remarks Time O(1), Space O(1)
     *
     * @param v - The node to set as the right child.
     */
    set right(v) {
      if (v) v.parent = this;
      this._right = v;
    }
    /**
     * Gets the height of the node (used in self-balancing trees).
     * @remarks Time O(1), Space O(1)
     *
     * @returns The height.
     */
    /* istanbul ignore next -- covered by AVLTree/RedBlackTree tests (subclass uses height) */
    get height() {
      return this._height;
    }
    /**
     * Sets the height of the node.
     * @remarks Time O(1), Space O(1)
     *
     * @param value - The new height.
     */
    /* istanbul ignore next -- covered by AVLTree/RedBlackTree tests (subclass uses height) */
    set height(value) {
      this._height = value;
    }
    /**
     * Gets the color of the node (used in Red-Black trees).
     * @remarks Time O(1), Space O(1)
     *
     * @returns The node's color.
     */
    /* istanbul ignore next -- covered by RedBlackTree tests (subclass uses color) */
    get color() {
      return this._color;
    }
    /**
     * Sets the color of the node.
     * @remarks Time O(1), Space O(1)
     *
     * @param value - The new color.
     */
    /* istanbul ignore next -- covered by RedBlackTree tests (subclass uses color) */
    set color(value) {
      this._color = value;
    }
    /**
     * Gets the count of nodes in the subtree rooted at this node (used in order-statistic trees).
     * @remarks Time O(1), Space O(1)
     *
     * @returns The subtree node count.
     */
    /* istanbul ignore next -- internal field used by subclasses */
    get count() {
      return this._count;
    }
    /**
     * Sets the count of nodes in the subtree.
     * @remarks Time O(1), Space O(1)
     *
     * @param value - The new count.
     */
    /* istanbul ignore next -- internal field used by subclasses */
    set count(value) {
      this._count = value;
    }
    /**
     * Gets the position of the node relative to its parent.
     * @remarks Time O(1), Space O(1)
     *
     * @returns The family position (e.g., 'ROOT', 'LEFT', 'RIGHT').
     */
    get familyPosition() {
      if (!this.parent) {
        return this.left || this.right ? "ROOT" : "ISOLATED";
      }
      if (this.parent.left === this) {
        return this.left || this.right ? "ROOT_LEFT" : "LEFT";
      } else if (this.parent.right === this) {
        return this.left || this.right ? "ROOT_RIGHT" : "RIGHT";
      }
      return "MAL_NODE";
    }
  };
  var BST = class extends BinaryTree {
    /**
     * Creates an instance of BST.
     * @remarks Time O(N log N) or O(N^2) depending on `isBalanceAdd` in `addMany` and input order. Space O(N).
     *
     * @param [keysNodesEntriesOrRaws=[]] - An iterable of items to set.
     * @param [options] - Configuration options for the BST, including comparator.
     */
    constructor(keysNodesEntriesOrRaws = [], options) {
      super([], options);
      __publicField(this, "_root");
      __publicField(this, "_enableOrderStatistic", false);
      /**
         * The comparator function used to determine the order of keys in the tree.
      
         * @remarks Time O(1) Space O(1)
         */
      __publicField(this, "_comparator");
      if (options) {
        if ("comparator" in options && options.comparator !== void 0) {
          this._comparator = options.comparator;
        } else {
          this._comparator = this._createDefaultComparator();
        }
        if (options.enableOrderStatistic) {
          this._enableOrderStatistic = true;
        }
      } else {
        this._comparator = this._createDefaultComparator();
      }
      if (keysNodesEntriesOrRaws) this.setMany(keysNodesEntriesOrRaws);
    }
    /**
     * Gets the root node of the tree.
     * @remarks Time O(1)
     *
     * @returns The root node.
     */
    get root() {
      return this._root;
    }
    /**
     * Gets the comparator function used by the tree.
     * @remarks Time O(1)
     *
     * @returns The comparator function.
     */
    get comparator() {
      return this._comparator;
    }
    /**
     * (Protected) Creates a new BST node.
     * @remarks Time O(1), Space O(1)
     *
     * @param key - The key for the new node.
     * @param [value] - The value for the new node (used if not in Map mode).
     * @returns The newly created BSTNode.
     */
    createNode(key, value) {
      return new BSTNode(key, value);
    }
    /**
     * Ensures the input is a node. If it's a key or entry, it searches for the node.
     * @remarks Time O(log N) (height of the tree), O(N) worst-case.
     *
     * @param keyNodeOrEntry - The item to resolve to a node.
     * @param [iterationType=this.iterationType] - The traversal method to use if searching.
     * @returns The resolved node, or undefined if not found.
     */
    ensureNode(keyNodeOrEntry, iterationType = this.iterationType) {
      var _a;
      return (_a = super.ensureNode(keyNodeOrEntry, iterationType)) != null ? _a : void 0;
    }
    /**
     * Checks if the given item is a `BSTNode` instance.
     * @remarks Time O(1), Space O(1)
     *
     * @param keyNodeOrEntry - The item to check.
     * @returns True if it's a BSTNode, false otherwise.
     */
    isNode(keyNodeOrEntry) {
      return keyNodeOrEntry instanceof BSTNode;
    }
    /**
     * Checks if the given key is valid (comparable).
     * @remarks Time O(1)
     *
     * @param key - The key to validate.
     * @returns True if the key is valid, false otherwise.
     */
    isValidKey(key) {
      return isComparable(key);
    }
    /**
     * Performs a Depth-First Search (DFS) traversal.
     * @remarks Time O(N), visits every node. Space O(log N) for the call/explicit stack. O(N) worst-case.
     *
     * @template C - The type of the callback function.
     * @param [callback=this._DEFAULT_NODE_CALLBACK] - Function to call on each node.
     * @param [pattern='IN'] - The traversal order ('IN', 'PRE', 'POST').
     * @param [onlyOne=false] - If true, stops after the first callback.
     * @param [startNode=this._root] - The node to start from.
     * @param [iterationType=this.iterationType] - The traversal method.
     * @returns An array of callback results.
     */
    dfs(callback = this._DEFAULT_NODE_CALLBACK, pattern = "IN", onlyOne = false, startNode = this._root, iterationType = this.iterationType) {
      return super.dfs(callback, pattern, onlyOne, startNode, iterationType);
    }
    /**
     * Performs a Breadth-First Search (BFS) or Level-Order traversal.
     * @remarks Time O(N), visits every node. Space O(N) in the worst case for the queue.
     *
     * @template C - The type of the callback function.
     * @param [callback=this._DEFAULT_NODE_CALLBACK] - Function to call on each node.
     * @param [startNode=this._root] - The node to start from.
     * @param [iterationType=this.iterationType] - The traversal method.
     * @returns An array of callback results.
     */
    bfs(callback = this._DEFAULT_NODE_CALLBACK, startNode = this._root, iterationType = this.iterationType) {
      return super.bfs(callback, startNode, iterationType, false);
    }
    /**
     * Returns a 2D array of nodes, grouped by level.
     * @remarks Time O(N), visits every node. Space O(N) for the result array and the queue/stack.
     *
     * @template C - The type of the callback function.
     * @param [callback=this._DEFAULT_NODE_CALLBACK] - Function to call on each node.
     * @param [startNode=this._root] - The node to start from.
     * @param [iterationType=this.iterationType] - The traversal method.
     * @returns A 2D array of callback results.
     */
    listLevels(callback = this._DEFAULT_NODE_CALLBACK, startNode = this._root, iterationType = this.iterationType) {
      return super.listLevels(callback, startNode, iterationType, false);
    }
    /**
      * Gets the first node matching a predicate.
      * @remarks Time O(log N) if searching by key, O(N) if searching by predicate. Space O(log N) or O(N).
      *
      * @param keyNodeEntryOrPredicate - The key, node, entry, or predicate function to search for.
      * @param [startNode=this._root] - The node to start the search from.
      * @param [iterationType=this.iterationType] - The traversal method.
      * @returns The first matching node, or undefined if not found.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Get node object by key
    *  const bst = new BST<number, string>([[5, 'root'], [3, 'left'], [7, 'right']]);
    *     const node = bst.getNode(3);
    *     console.log(node?.key); // 3;
    *     console.log(node?.value); // 'left';
      */
    getNode(keyNodeEntryOrPredicate, startNode = this._root, iterationType = this.iterationType) {
      var _a, _b;
      if (keyNodeEntryOrPredicate === null || keyNodeEntryOrPredicate === void 0) return void 0;
      if (this._isPredicate(keyNodeEntryOrPredicate)) {
        return (_a = this.getNodes(keyNodeEntryOrPredicate, true, startNode, iterationType)[0]) != null ? _a : void 0;
      }
      if (keyNodeEntryOrPredicate instanceof Range) {
        return (_b = this.getNodes(
          keyNodeEntryOrPredicate,
          true,
          startNode,
          iterationType
        )[0]) != null ? _b : void 0;
      }
      let targetKey;
      if (this.isNode(keyNodeEntryOrPredicate)) {
        targetKey = keyNodeEntryOrPredicate.key;
      } else if (this.isEntry(keyNodeEntryOrPredicate)) {
        const k = keyNodeEntryOrPredicate[0];
        if (k === null || k === void 0) return void 0;
        targetKey = k;
      } else {
        targetKey = keyNodeEntryOrPredicate;
      }
      const start = this.ensureNode(startNode);
      if (!start) return void 0;
      const NIL = this._NIL;
      let cur = start;
      const cmpFn = this._comparator;
      while (cur && cur !== NIL) {
        const c = cmpFn(targetKey, cur.key);
        if (c === 0) return cur;
        cur = c < 0 ? cur._left : cur._right;
      }
      return void 0;
    }
    /**
     * Searches the tree for nodes matching a predicate, key, or range.
     * @remarks This is an optimized search for a BST. If searching by key or range, it prunes branches.
     * Time O(H + M) for key/range search (H=height, M=matches). O(N) for predicate search.
     * Space O(log N) for the stack.
     *
     * @template C - The type of the callback function.
     * @param keyNodeEntryOrPredicate - The key, node, entry, predicate, or range to search for.
     * @param [onlyOne=false] - If true, stops after finding the first match.
     * @param [callback=this._DEFAULT_NODE_CALLBACK] - A function to call on matching nodes.
     * @param [startNode=this._root] - The node to start the search from.
     * @param [iterationType=this.iterationType] - Whether to use 'RECURSIVE' or 'ITERATIVE' search.
     * @returns An array of results from the callback function for each matching node.
     */
    search(keyNodeEntryOrPredicate, onlyOne = false, callback = this._DEFAULT_NODE_CALLBACK, startNode = this._root, iterationType = this.iterationType) {
      if (keyNodeEntryOrPredicate === void 0) return [];
      if (keyNodeEntryOrPredicate === null) return [];
      startNode = this.ensureNode(startNode);
      if (!startNode) return [];
      const isRange = this.isRange(keyNodeEntryOrPredicate);
      const isPred = !isRange && this._isPredicate(keyNodeEntryOrPredicate);
      if (!isRange && !isPred) {
        let targetKey;
        if (this.isNode(keyNodeEntryOrPredicate)) {
          targetKey = keyNodeEntryOrPredicate.key;
        } else if (this.isEntry(keyNodeEntryOrPredicate)) {
          const k = keyNodeEntryOrPredicate[0];
          if (k !== null && k !== void 0) targetKey = k;
        } else {
          targetKey = keyNodeEntryOrPredicate;
        }
        if (targetKey === void 0) return [];
        const NIL = this._NIL;
        const cmpFn = this._comparator;
        let cur = startNode;
        while (cur && cur !== NIL) {
          const c = cmpFn(targetKey, cur.key);
          if (c === 0) return [callback(cur)];
          cur = c < 0 ? cur._left : cur._right;
        }
        return [];
      }
      let predicate;
      if (isRange) {
        predicate = (node) => {
          if (!node) return false;
          return keyNodeEntryOrPredicate.isInRange(node.key, this._comparator);
        };
      } else {
        predicate = this._ensurePredicate(keyNodeEntryOrPredicate);
      }
      const shouldVisitLeft = (cur) => {
        if (!cur) return false;
        if (!this.isRealNode(cur.left)) return false;
        if (isRange) {
          const range = keyNodeEntryOrPredicate;
          const leftS = range.low;
          const leftI = range.includeLow;
          return leftI && this._compare(cur.key, leftS) >= 0 || !leftI && this._compare(cur.key, leftS) > 0;
        }
        if (!isRange && !this._isPredicate(keyNodeEntryOrPredicate)) {
          const benchmarkKey = this._extractKey(keyNodeEntryOrPredicate);
          return benchmarkKey !== null && benchmarkKey !== void 0 && this._compare(cur.key, benchmarkKey) > 0;
        }
        return true;
      };
      const shouldVisitRight = (cur) => {
        if (!cur) return false;
        if (!this.isRealNode(cur.right)) return false;
        if (isRange) {
          const range = keyNodeEntryOrPredicate;
          const rightS = range.high;
          const rightI = range.includeHigh;
          return rightI && this._compare(cur.key, rightS) <= 0 || !rightI && this._compare(cur.key, rightS) < 0;
        }
        if (!isRange && !this._isPredicate(keyNodeEntryOrPredicate)) {
          const benchmarkKey = this._extractKey(keyNodeEntryOrPredicate);
          return benchmarkKey !== null && benchmarkKey !== void 0 && this._compare(cur.key, benchmarkKey) < 0;
        }
        return true;
      };
      return super._dfs(
        callback,
        "IN",
        // In-order is efficient for range/key search
        onlyOne,
        startNode,
        iterationType,
        false,
        shouldVisitLeft,
        shouldVisitRight,
        () => true,
        // shouldVisitRoot (always visit)
        (cur) => !!cur && predicate(cur)
        // shouldProcessRoot (only process if predicate matches)
      );
    }
    /**
     * Performs an optimized search for nodes within a given key range.
     * @remarks Time O(H + M), where H is tree height and M is the number of matches.
     *
     * @template C - The type of the callback function.
     * @param range - A `Range` object or a `[low, high]` tuple.
     * @param [callback=this._DEFAULT_NODE_CALLBACK] - A function to call on matching nodes.
     * @param [startNode=this._root] - The node to start the search from.
     * @param [iterationType=this.iterationType] - The traversal method.
     * @returns An array of callback results.
     */
    rangeSearch(range, callback = this._DEFAULT_NODE_CALLBACK, startNode = this._root, iterationType = this.iterationType) {
      const searchRange = range instanceof Range ? range : new Range(range[0], range[1]);
      return this.search(searchRange, false, callback, startNode, iterationType);
    }
    getByRank(k, callback = this._DEFAULT_NODE_CALLBACK, iterationType = this.iterationType) {
      if (!this._enableOrderStatistic) {
        raise(Error, ERR.orderStatisticNotEnabled("getByRank"));
      }
      if (k < 0 || k >= this._size) return void 0;
      let actualCallback = void 0;
      let actualIterationType = this.iterationType;
      if (typeof callback === "string") {
        actualIterationType = callback;
      } else if (callback) {
        actualCallback = callback;
        if (iterationType) {
          actualIterationType = iterationType;
        }
      }
      const node = actualIterationType === "RECURSIVE" ? this._getByRankRecursive(this._root, k) : this._getByRankIterative(this._root, k);
      if (!node) return void 0;
      return actualCallback ? actualCallback(node) : node.key;
    }
    getRank(keyNodeEntryOrPredicate, iterationType = this.iterationType) {
      var _a;
      if (!this._enableOrderStatistic) {
        raise(Error, ERR.orderStatisticNotEnabled("getRank"));
      }
      if (!this._root || this._size === 0) return -1;
      let actualIterationType = this.iterationType;
      if (iterationType) actualIterationType = iterationType;
      let key;
      if (typeof keyNodeEntryOrPredicate === "function") {
        const results = this.search(keyNodeEntryOrPredicate, true);
        if (results.length === 0 || results[0] === void 0) return -1;
        key = results[0];
      } else if (keyNodeEntryOrPredicate === null || keyNodeEntryOrPredicate === void 0) {
        return -1;
      } else if (this.isNode(keyNodeEntryOrPredicate)) {
        key = keyNodeEntryOrPredicate.key;
      } else if (Array.isArray(keyNodeEntryOrPredicate)) {
        key = (_a = keyNodeEntryOrPredicate[0]) != null ? _a : void 0;
        if (key === void 0 || key === null) return -1;
      } else {
        key = keyNodeEntryOrPredicate;
      }
      if (key === void 0) return -1;
      return actualIterationType === "RECURSIVE" ? this._getRankRecursive(this._root, key) : this._getRankIterative(this._root, key);
    }
    rangeByRank(start, end, callback = this._DEFAULT_NODE_CALLBACK, iterationType = this.iterationType) {
      if (!this._enableOrderStatistic) {
        raise(Error, ERR.orderStatisticNotEnabled("rangeByRank"));
      }
      if (this._size === 0) return [];
      const lo = Math.max(0, start);
      const hi = Math.min(this._size - 1, end);
      if (lo > hi) return [];
      let actualCallback = void 0;
      let actualIterationType = this.iterationType;
      if (typeof callback === "string") {
        actualIterationType = callback;
      } else if (callback) {
        actualCallback = callback;
        if (iterationType) {
          actualIterationType = iterationType;
        }
      }
      const results = [];
      const count = hi - lo + 1;
      const startNode = actualIterationType === "RECURSIVE" ? this._getByRankRecursive(this._root, lo) : this._getByRankIterative(this._root, lo);
      if (!startNode) return [];
      let collected = 0;
      const cb = actualCallback != null ? actualCallback : this._DEFAULT_NODE_CALLBACK;
      let current = startNode;
      while (current && collected < count) {
        results.push(cb(current));
        collected++;
        if (collected < count) {
          current = this._next(current);
        }
      }
      return results;
    }
    /**
      * Adds a new node to the BST based on key comparison.
      * @remarks Time O(log N), where H is tree height. O(N) worst-case (unbalanced tree), O(log N) average. Space O(1).
      *
      * @param keyNodeOrEntry - The key, node, or entry to set.
      * @param [value] - The value, if providing just a key.
      * @returns True if the addition was successful, false otherwise.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Set a key-value pair
    *  const bst = new BST<number, string>();
    *     bst.set(1, 'one');
    *     bst.set(2, 'two');
    *     console.log(bst.get(1)); // 'one';
      */
    set(keyNodeOrEntry, value) {
      const [newNode] = this._keyValueNodeOrEntryToNodeAndValue(keyNodeOrEntry, value);
      if (newNode === void 0) return false;
      if (this._root === void 0) {
        this._setRoot(newNode);
        if (this._isMapMode && this.isRealNode(newNode)) this._store.set(newNode.key, newNode);
        this._size++;
        this._updateCount(newNode);
        return true;
      }
      let current = this._root;
      while (current !== void 0) {
        if (this._compare(current.key, newNode.key) === 0) {
          this._replaceNode(current, newNode);
          if (this._isMapMode && this.isRealNode(newNode)) this._store.set(current.key, newNode);
          return true;
        } else if (this._compare(current.key, newNode.key) > 0) {
          if (current.left === void 0) {
            current.left = newNode;
            if (this._isMapMode && this.isRealNode(newNode)) this._store.set(newNode.key, newNode);
            this._size++;
            this._updateCountAlongPath(newNode);
            return true;
          }
          if (current.left !== null) current = current.left;
        } else {
          if (current.right === void 0) {
            current.right = newNode;
            if (this._isMapMode && this.isRealNode(newNode)) this._store.set(newNode.key, newNode);
            this._size++;
            this._updateCountAlongPath(newNode);
            return true;
          }
          if (current.right !== null) current = current.right;
        }
      }
      return false;
    }
    /**
      * Adds multiple items to the tree.
      * @remarks If `isBalanceAdd` is true, sorts the input and builds a balanced tree. Time O(N log N) (due to sort and balanced set).
      * If false, adds items one by one. Time O(N * H), which is O(N^2) worst-case.
      * Space O(N) for sorting and recursion/iteration stack.
      *
      * @param keysNodesEntriesOrRaws - An iterable of items to set.
      * @param [values] - An optional parallel iterable of values.
      * @param [isBalanceAdd=true] - If true, builds a balanced tree from the items.
      * @param [iterationType=this.iterationType] - The traversal method for balanced set (recursive or iterative).
      * @returns An array of booleans indicating the success of each individual `set` operation.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Set multiple key-value pairs
    *  const bst = new BST<number, string>();
    *     bst.setMany([[1, 'a'], [2, 'b'], [3, 'c']]);
    *     console.log(bst.size); // 3;
    *     console.log(bst.get(2)); // 'b';
      */
    setMany(keysNodesEntriesOrRaws, values, isBalanceAdd = true, iterationType = this.iterationType) {
      const inserted = [];
      const valuesIterator = values == null ? void 0 : values[Symbol.iterator]();
      if (!isBalanceAdd) {
        for (let kve of keysNodesEntriesOrRaws) {
          const val = valuesIterator == null ? void 0 : valuesIterator.next().value;
          if (this.isRaw(kve)) kve = this._toEntryFn(kve);
          inserted.push(this.set(kve, val));
        }
        return inserted;
      }
      const realBTNExemplars = [];
      let i = 0;
      for (const kve of keysNodesEntriesOrRaws) {
        realBTNExemplars.push({ key: kve, value: valuesIterator == null ? void 0 : valuesIterator.next().value, orgIndex: i++ });
      }
      const sorted = realBTNExemplars.sort(({ key: a }, { key: b }) => {
        let keyA, keyB;
        if (this.isRaw(a)) keyA = this._toEntryFn(a)[0];
        else if (this.isEntry(a)) keyA = a[0];
        else if (this.isRealNode(a)) keyA = a.key;
        else keyA = a;
        if (this.isRaw(b)) keyB = this._toEntryFn(b)[0];
        else if (this.isEntry(b)) keyB = b[0];
        else if (this.isRealNode(b)) keyB = b.key;
        else keyB = b;
        if (keyA != null && keyB != null) return this._compare(keyA, keyB);
        return 0;
      });
      const _dfs = (arr) => {
        if (arr.length === 0) return;
        const mid = Math.floor((arr.length - 1) / 2);
        const { key, value, orgIndex } = arr[mid];
        if (this.isRaw(key)) {
          const entry = this._toEntryFn(key);
          inserted[orgIndex] = this.set(entry);
        } else {
          inserted[orgIndex] = this.set(key, value);
        }
        _dfs(arr.slice(0, mid));
        _dfs(arr.slice(mid + 1));
      };
      const _iterate = () => {
        const n = sorted.length;
        const stack = [[0, n - 1]];
        while (stack.length > 0) {
          const popped = stack.pop();
          if (!popped) continue;
          const [l, r] = popped;
          if (l > r) continue;
          const m = l + Math.floor((r - l) / 2);
          const { key, value, orgIndex } = sorted[m];
          if (this.isRaw(key)) {
            const entry = this._toEntryFn(key);
            inserted[orgIndex] = this.set(entry);
          } else {
            inserted[orgIndex] = this.set(key, value);
          }
          stack.push([m + 1, r]);
          stack.push([l, m - 1]);
        }
      };
      if (iterationType === "RECURSIVE") _dfs(sorted);
      else _iterate();
      return inserted;
    }
    ceiling(keyNodeEntryOrPredicate, callback = this._DEFAULT_NODE_CALLBACK, iterationType) {
      let actualCallback = void 0;
      let actualIterationType = this.iterationType;
      if (typeof callback === "string") {
        actualIterationType = callback;
      } else if (callback) {
        actualCallback = callback;
        if (iterationType) {
          actualIterationType = iterationType;
        }
      }
      const node = this._bound(keyNodeEntryOrPredicate, true, actualIterationType);
      if (!actualCallback) {
        return node == null ? void 0 : node.key;
      }
      return node ? actualCallback(node) : void 0;
    }
    higher(keyNodeEntryOrPredicate, callback = this._DEFAULT_NODE_CALLBACK, iterationType) {
      let actualCallback = void 0;
      let actualIterationType = this.iterationType;
      if (typeof callback === "string") {
        actualIterationType = callback;
      } else if (callback) {
        actualCallback = callback;
        if (iterationType) {
          actualIterationType = iterationType;
        }
      }
      const node = this._bound(keyNodeEntryOrPredicate, false, actualIterationType);
      if (!actualCallback) {
        return node == null ? void 0 : node.key;
      }
      return node ? actualCallback(node) : void 0;
    }
    floor(keyNodeEntryOrPredicate, callback = this._DEFAULT_NODE_CALLBACK, iterationType) {
      if (keyNodeEntryOrPredicate === null || keyNodeEntryOrPredicate === void 0) {
        if (typeof callback === "string" || !callback) {
          return void 0;
        }
        return void 0;
      }
      let actualCallback = void 0;
      let actualIterationType = this.iterationType;
      if (typeof callback === "string") {
        actualIterationType = callback;
      } else if (callback) {
        actualCallback = callback;
        if (iterationType) {
          actualIterationType = iterationType;
        }
      }
      if (this._isPredicate(keyNodeEntryOrPredicate)) {
        const node = this._floorByPredicate(keyNodeEntryOrPredicate, actualIterationType);
        if (!actualCallback) {
          return node == null ? void 0 : node.key;
        }
        return node ? actualCallback(node) : void 0;
      }
      let targetKey;
      if (this.isNode(keyNodeEntryOrPredicate)) {
        targetKey = keyNodeEntryOrPredicate.key;
      } else if (this.isEntry(keyNodeEntryOrPredicate)) {
        const key = keyNodeEntryOrPredicate[0];
        if (key === null || key === void 0) {
          if (typeof callback === "string" || !callback) {
            return void 0;
          }
          return void 0;
        }
        targetKey = key;
      } else {
        targetKey = keyNodeEntryOrPredicate;
      }
      if (targetKey !== void 0) {
        const node = this._floorByKey(targetKey, actualIterationType);
        if (!actualCallback) {
          return node == null ? void 0 : node.key;
        }
        return node ? actualCallback(node) : void 0;
      }
      if (typeof callback === "string" || !callback) {
        return void 0;
      }
      return void 0;
    }
    lower(keyNodeEntryOrPredicate, callback, iterationType) {
      if (keyNodeEntryOrPredicate === null || keyNodeEntryOrPredicate === void 0) {
        if (typeof callback === "string" || !callback) {
          return void 0;
        }
        return void 0;
      }
      let actualCallback = void 0;
      let actualIterationType = this.iterationType;
      if (typeof callback === "string") {
        actualIterationType = callback;
      } else if (callback) {
        actualCallback = callback;
        if (iterationType) {
          actualIterationType = iterationType;
        }
      }
      if (this._isPredicate(keyNodeEntryOrPredicate)) {
        const node = this._lowerByPredicate(keyNodeEntryOrPredicate, actualIterationType);
        if (!actualCallback) {
          return node == null ? void 0 : node.key;
        }
        return node ? actualCallback(node) : void 0;
      }
      let targetKey;
      if (this.isNode(keyNodeEntryOrPredicate)) {
        targetKey = keyNodeEntryOrPredicate.key;
      } else if (this.isEntry(keyNodeEntryOrPredicate)) {
        const key = keyNodeEntryOrPredicate[0];
        if (key === null || key === void 0) {
          if (typeof callback === "string" || !callback) {
            return void 0;
          }
          return void 0;
        }
        targetKey = key;
      } else {
        targetKey = keyNodeEntryOrPredicate;
      }
      if (targetKey !== void 0) {
        const node = this._lowerByKey(targetKey, actualIterationType);
        if (!actualCallback) {
          return node == null ? void 0 : node.key;
        }
        return node ? actualCallback(node) : void 0;
      }
      if (typeof callback === "string" || !callback) {
        return void 0;
      }
      return void 0;
    }
    /**
     * Traverses the tree and returns nodes that are lesser or greater than a target node.
     * @remarks Time O(N), as it performs a full traversal. Space O(log N) or O(N).
     *
     * @template C - The type of the callback function.
     * @param [callback=this._DEFAULT_NODE_CALLBACK] - Function to call on matching nodes.
     * @param [lesserOrGreater=-1] - -1 for lesser, 1 for greater, 0 for equal.
     * @param [targetNode=this._root] - The node to compare against.
     * @param [iterationType=this.iterationType] - The traversal method.
     * @returns An array of callback results.
     */
    lesserOrGreaterTraverse(callback = this._DEFAULT_NODE_CALLBACK, lesserOrGreater = -1, targetNode = this._root, iterationType = this.iterationType) {
      const targetNodeEnsured = this.ensureNode(targetNode);
      const ans = [];
      if (!this._root || !targetNodeEnsured) return ans;
      const targetKey = targetNodeEnsured.key;
      if (iterationType === "RECURSIVE") {
        const dfs = (cur) => {
          const compared = this._compare(cur.key, targetKey);
          if (Math.sign(compared) == lesserOrGreater) ans.push(callback(cur));
          if (this.isRealNode(cur.left)) dfs(cur.left);
          if (this.isRealNode(cur.right)) dfs(cur.right);
        };
        dfs(this._root);
        return ans;
      } else {
        const queue = new Queue([this._root]);
        while (queue.length > 0) {
          const cur = queue.shift();
          if (this.isRealNode(cur)) {
            const compared = this._compare(cur.key, targetKey);
            if (Math.sign(compared) == lesserOrGreater) ans.push(callback(cur));
            if (this.isRealNode(cur.left)) queue.push(cur.left);
            if (this.isRealNode(cur.right)) queue.push(cur.right);
          }
        }
        return ans;
      }
    }
    /**
      * Rebuilds the tree to be perfectly balanced.
      * @remarks Time O(N) (O(N) for DFS, O(N) for sorted build). Space O(N) for node array and recursion stack.
      *
      * @param [iterationType=this.iterationType] - The traversal method for the initial node export.
      * @returns True if successful, false if the tree was empty.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Rebalance the tree
    *  const bst = new BST<number>();
    *     // Insert in sorted order (worst case for BST)
    *     for (let i = 1; i <= 7; i++) bst.add(i);
    *     console.log(bst.isAVLBalanced()); // false;
    *     bst.perfectlyBalance();
    *     console.log(bst.isAVLBalanced()); // true;
      */
    perfectlyBalance(iterationType = this.iterationType) {
      const nodes = this.dfs((node) => node, "IN", false, this._root, iterationType);
      const n = nodes.length;
      this._clearNodes();
      if (n === 0) return false;
      const build = (l, r, parent) => {
        if (l > r) return void 0;
        const m = l + (r - l >> 1);
        const root = nodes[m];
        const leftChild = build(l, m - 1, root);
        const rightChild = build(m + 1, r, root);
        root.left = leftChild;
        root.right = rightChild;
        root.parent = parent;
        return root;
      };
      const newRoot = build(0, n - 1, void 0);
      this._setRoot(newRoot);
      this._size = n;
      return true;
    }
    /**
      * Checks if the tree meets the AVL balance condition (height difference <= 1).
      * @remarks Time O(N), as it must visit every node to compute height. Space O(log N) for recursion or O(N) for iterative map.
      *
      * @param [iterationType=this.iterationType] - The traversal method.
      * @returns True if the tree is AVL balanced, false otherwise.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Check if tree is height-balanced
    *  const bst = new BST<number>([3, 1, 5, 2, 4]);
    *     console.log(bst.isAVLBalanced()); // true;
      */
    isAVLBalanced(iterationType = this.iterationType) {
      if (!this._root) return true;
      let balanced = true;
      if (iterationType === "RECURSIVE") {
        const _height = (cur) => {
          if (!cur) return 0;
          const leftHeight = _height(cur.left);
          const rightHeight = _height(cur.right);
          if (Math.abs(leftHeight - rightHeight) > 1) balanced = false;
          return Math.max(leftHeight, rightHeight) + 1;
        };
        _height(this._root);
      } else {
        const stack = [];
        let node = this._root, last = void 0;
        const depths = /* @__PURE__ */ new Map();
        while (stack.length > 0 || node) {
          if (node) {
            stack.push(node);
            if (node.left !== null) node = node.left;
          } else {
            node = stack[stack.length - 1];
            if (!node.right || last === node.right) {
              node = stack.pop();
              if (node) {
                const left = node.left ? depths.get(node.left) : -1;
                const right = node.right ? depths.get(node.right) : -1;
                if (Math.abs(left - right) > 1) return false;
                depths.set(node, 1 + Math.max(left, right));
                last = node;
                node = void 0;
              }
            } else node = node.right;
          }
        }
      }
      return balanced;
    }
    /**
      * Creates a new BST by mapping each [key, value] pair to a new entry.
      * @remarks Time O(N * H), where N is nodes in this tree, and H is height of the new tree during insertion.
      * Space O(N) for the new tree.
      *
      * @template MK - New key type.
      * @template MV - New value type.
      * @template MR - New raw type.
      * @param callback - A function to map each [key, value] pair.
      * @param [options] - Options for the new BST.
      * @param [thisArg] - `this` context for the callback.
      * @returns A new, mapped BST.
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Transform to new tree
    *  const bst = new BST<number, number>([[1, 10], [2, 20], [3, 30]]);
    *     const doubled = bst.map((value, key) => [key, (value ?? 0) * 2] as [number, number]);
    *     console.log([...doubled.values()]); // [20, 40, 60];
      */
    map(callback, options, thisArg) {
      const out = this._createLike([], options);
      let index = 0;
      for (const [key, value] of this) {
        out.set(callback.call(thisArg, value, key, index++, this));
      }
      return out;
    }
    /**
     * Deletes nodes that match a key, node, entry, predicate, or range.
     *
     * @remarks
     * Time Complexity: O(N) for search + O(M log N) for M deletions, where N is tree size.
     * Space Complexity: O(M) for storing matched nodes and result map.
     *
     * @template K - The key type.
     * @template V - The value type.
     *
     * @param keyNodeEntryOrPredicate - The search criteria. Can be one of:
     *   - A key (type K): searches for exact key match using the comparator.
     *   - A BSTNode: searches for the matching node in the tree.
     *   - An entry tuple: searches for the key-value pair.
     *   - A NodePredicate function: tests each node and returns true for matches.
     *   - A Range object: searches for nodes whose keys fall within the specified range (inclusive/exclusive based on range settings).
     *   - null or undefined: treated as no match, returns empty results.
     *
     * @param onlyOne - If true, stops the search after finding the first match and only deletes that one node.
     *   If false (default), searches for and deletes all matching nodes.
     *
     * @param startNode - The node to start the search from. Can be:
     *   - A key, node, or entry: the method resolves it to a node and searches from that subtree.
     *   - null or undefined: defaults to the root, searching the entire tree.
     *   - Default value: this._root (the tree's root).
     *
     * @param iterationType - Controls the internal traversal implementation:
     *   - 'RECURSIVE': uses recursive function calls for traversal.
     *   - 'ITERATIVE': uses explicit stack-based iteration.
     *   - Default: this.iterationType (the tree's default iteration mode).
     *
     * @returns A Map<K, boolean> containing the deletion results:
     *   - Key: the matched node's key.
     *   - Value: true if the deletion succeeded, false if it failed (e.g., key not found during deletion phase).
     *   - If no nodes match the search criteria, the returned map is empty.
     */
    deleteWhere(keyNodeEntryOrPredicate, onlyOne = false, startNode = this._root, iterationType = this.iterationType) {
      const toDelete = this.search(keyNodeEntryOrPredicate, onlyOne, (node) => node, startNode, iterationType);
      let deleted = false;
      for (const node of toDelete) {
        if (this.delete(node)) deleted = true;
      }
      return deleted;
    }
    /**
     * (Protected) Creates the default comparator function for keys that don't have a custom comparator.
     * @remarks Time O(1) Space O(1)
     * @returns The default comparator function.
     */
    _createDefaultComparator() {
      return (a, b) => {
        if (isComparable(a) && isComparable(b)) {
          if (a > b) return 1;
          if (a < b) return -1;
          return 0;
        }
        if (a instanceof Date && b instanceof Date) {
          const ta = a.getTime();
          const tb = b.getTime();
          if (Number.isNaN(ta) || Number.isNaN(tb)) raise(TypeError, ERR.invalidDate("BST"));
          return ta > tb ? 1 : ta < tb ? -1 : 0;
        }
        if (typeof a === "object" || typeof b === "object") {
          raise(TypeError, ERR.comparatorRequired("BST"));
        }
        return 0;
      };
    }
    /**
     * (Protected) Binary search for floor by key with pruning optimization.
     * Performs standard BST binary search, choosing left or right subtree based on comparator result.
     * Finds first node where key <= target.
     * @remarks Time O(h) where h is tree height.
     *
     * @param key - The target key to search for.
     * @param iterationType - The iteration type (RECURSIVE or ITERATIVE).
     * @returns The first node with key <= target, or undefined if none exists.
     */
    _floorByKey(key, iterationType) {
      var _a, _b;
      if (iterationType === "RECURSIVE") {
        const dfs = (cur) => {
          if (!this.isRealNode(cur)) return void 0;
          const cmp = this.comparator(cur.key, key);
          if (cmp <= 0) {
            const rightResult = dfs(cur.right);
            return rightResult != null ? rightResult : cur;
          } else {
            return dfs(cur.left);
          }
        };
        return dfs(this.root);
      } else {
        let current = this.root;
        let result = void 0;
        while (this.isRealNode(current)) {
          const cmp = this.comparator(current.key, key);
          if (cmp <= 0) {
            result = current;
            current = (_a = current.right) != null ? _a : void 0;
          } else {
            current = (_b = current.left) != null ? _b : void 0;
          }
        }
        return result;
      }
    }
    /**
     * (Protected) In-order traversal search for floor by predicate.
     * Falls back to linear in-order traversal when predicate-based search is required.
     * Returns the last node that satisfies the predicate function.
     * @remarks Time Complexity: O(n) since it may visit every node.
     * Space Complexity: O(h) for recursion, O(h) for iterative stack.
     *
     * @param predicate - The predicate function to test nodes.
     * @param iterationType - The iteration type (RECURSIVE or ITERATIVE).
     * @returns The last node satisfying predicate (highest key), or undefined if none found.
     */
    _floorByPredicate(predicate, iterationType) {
      if (iterationType === "RECURSIVE") {
        let result = void 0;
        const dfs = (cur) => {
          if (!this.isRealNode(cur)) return;
          if (this.isRealNode(cur.left)) dfs(cur.left);
          if (predicate(cur)) {
            result = cur;
          }
          if (this.isRealNode(cur.right)) dfs(cur.right);
        };
        dfs(this.root);
        return result;
      } else {
        const stack = [];
        let current = this.root;
        let result = void 0;
        while (stack.length > 0 || this.isRealNode(current)) {
          if (this.isRealNode(current)) {
            stack.push(current);
            current = current.left;
          } else {
            const node = stack.pop();
            if (!this.isRealNode(node)) break;
            if (predicate(node)) {
              result = node;
            }
            current = node.right;
          }
        }
        return result;
      }
    }
    /**
     * (Protected) Binary search for lower by key with pruning optimization.
     * Performs standard BST binary search, choosing left or right subtree based on comparator result.
     * Finds first node where key < target.
     * @remarks Time O(h) where h is tree height.
     *
     * @param key - The target key to search for.
     * @param iterationType - The iteration type (RECURSIVE or ITERATIVE).
     * @returns The first node with key < target, or undefined if none exists.
     */
    _lowerByKey(key, iterationType) {
      var _a, _b;
      if (iterationType === "RECURSIVE") {
        const dfs = (cur) => {
          if (!this.isRealNode(cur)) return void 0;
          const cmp = this.comparator(cur.key, key);
          if (cmp < 0) {
            const rightResult = dfs(cur.right);
            return rightResult != null ? rightResult : cur;
          } else {
            return dfs(cur.left);
          }
        };
        return dfs(this.root);
      } else {
        let current = this.root;
        let result = void 0;
        while (this.isRealNode(current)) {
          const cmp = this.comparator(current.key, key);
          if (cmp < 0) {
            result = current;
            current = (_a = current.right) != null ? _a : void 0;
          } else {
            current = (_b = current.left) != null ? _b : void 0;
          }
        }
        return result;
      }
    }
    /**
     * (Protected) In-order traversal search for lower by predicate.
     * Falls back to linear in-order traversal when predicate-based search is required.
     * Returns the node that satisfies the predicate and appears last in in-order traversal.
     * @remarks Time Complexity: O(n) since it may visit every node.
     * Space Complexity: O(h) for recursion, O(h) for iterative stack.
     *
     * @param predicate - The predicate function to test nodes.
     * @param iterationType - The iteration type (RECURSIVE or ITERATIVE).
     * @returns The last node satisfying predicate (highest key < target), or undefined if none found.
     */
    _lowerByPredicate(predicate, iterationType) {
      if (iterationType === "RECURSIVE") {
        let result = void 0;
        const dfs = (cur) => {
          if (!this.isRealNode(cur)) return;
          if (this.isRealNode(cur.left)) dfs(cur.left);
          if (predicate(cur)) {
            result = cur;
          }
          if (this.isRealNode(cur.right)) dfs(cur.right);
        };
        dfs(this.root);
        return result;
      } else {
        const stack = [];
        let current = this.root;
        let result = void 0;
        while (stack.length > 0 || this.isRealNode(current)) {
          if (this.isRealNode(current)) {
            stack.push(current);
            current = current.left;
          } else {
            const node = stack.pop();
            if (!this.isRealNode(node)) break;
            if (predicate(node)) {
              result = node;
            }
            current = node.right;
          }
        }
        return result;
      }
    }
    /**
     * (Protected) Core bound search implementation supporting all parameter types.
     * Unified logic for both lowerBound and upperBound.
     * Resolves various input types (Key, Node, Entry, Predicate) using parent class utilities.
     * @param keyNodeEntryOrPredicate - The key, node, entry, or predicate function to search for.
     * @param isLower - True for lowerBound (>=), false for upperBound (>).
     * @param iterationType - The iteration type (RECURSIVE or ITERATIVE).
     * @returns The first matching node, or undefined if no such node exists.
     */
    _bound(keyNodeEntryOrPredicate, isLower, iterationType) {
      if (keyNodeEntryOrPredicate === null || keyNodeEntryOrPredicate === void 0) {
        return void 0;
      }
      if (this._isPredicate(keyNodeEntryOrPredicate)) {
        return this._boundByPredicate(keyNodeEntryOrPredicate, iterationType);
      }
      let targetKey;
      if (this.isNode(keyNodeEntryOrPredicate)) {
        targetKey = keyNodeEntryOrPredicate.key;
      } else if (this.isEntry(keyNodeEntryOrPredicate)) {
        const key = keyNodeEntryOrPredicate[0];
        if (key === null || key === void 0) {
          return void 0;
        }
        targetKey = key;
      } else {
        targetKey = keyNodeEntryOrPredicate;
      }
      if (targetKey !== void 0) {
        return this._boundByKey(targetKey, isLower, iterationType);
      }
      return void 0;
    }
    /**
     * (Protected) Binary search for bound by key with pruning optimization.
     * Performs standard BST binary search, choosing left or right subtree based on comparator result.
     * For lowerBound: finds first node where key >= target.
     * For upperBound: finds first node where key > target.
     * @param key - The target key to search for.
     * @param isLower - True for lowerBound (>=), false for upperBound (>).
     * @param iterationType - The iteration type (RECURSIVE or ITERATIVE).
     * @returns The first node matching the bound condition, or undefined if none exists.
     */
    _boundByKey(key, isLower, iterationType) {
      var _a, _b;
      if (iterationType === "RECURSIVE") {
        const dfs = (cur) => {
          if (!this.isRealNode(cur)) return void 0;
          const cmp = this.comparator(cur.key, key);
          const condition = isLower ? cmp >= 0 : cmp > 0;
          if (condition) {
            const leftResult = dfs(cur.left);
            return leftResult != null ? leftResult : cur;
          } else {
            return dfs(cur.right);
          }
        };
        return dfs(this.root);
      } else {
        let current = this.root;
        let result = void 0;
        while (this.isRealNode(current)) {
          const cmp = this.comparator(current.key, key);
          const condition = isLower ? cmp >= 0 : cmp > 0;
          if (condition) {
            result = current;
            current = (_a = current.left) != null ? _a : void 0;
          } else {
            current = (_b = current.right) != null ? _b : void 0;
          }
        }
        return result;
      }
    }
    /**
     * (Protected) In-order traversal search by predicate.
     * Falls back to linear in-order traversal when predicate-based search is required.
     * Returns the first node that satisfies the predicate function.
     * Note: Predicate-based search cannot leverage BST's binary search optimization.
     * Time Complexity: O(n) since it may visit every node.
     * @param predicate - The predicate function to test nodes.
     * @param iterationType - The iteration type (RECURSIVE or ITERATIVE).
     * @returns The first node satisfying predicate, or undefined if none found.
     */
    _boundByPredicate(predicate, iterationType) {
      if (iterationType === "RECURSIVE") {
        let result = void 0;
        const dfs = (cur) => {
          if (result || !this.isRealNode(cur)) return;
          if (this.isRealNode(cur.left)) dfs(cur.left);
          if (!result && predicate(cur)) {
            result = cur;
          }
          if (!result && this.isRealNode(cur.right)) dfs(cur.right);
        };
        dfs(this.root);
        return result;
      } else {
        const stack = [];
        let current = this.root;
        while (stack.length > 0 || this.isRealNode(current)) {
          if (this.isRealNode(current)) {
            stack.push(current);
            current = current.left;
          } else {
            const node = stack.pop();
            if (!this.isRealNode(node)) break;
            if (predicate(node)) {
              return node;
            }
            current = node.right;
          }
        }
        return void 0;
      }
    }
    /**
     * (Protected) Creates a new, empty instance of the same BST constructor.
     * @remarks Time O(1)
     *
     * @template TK, TV, TR - Generic types for the new instance.
     * @param [options] - Options for the new BST.
     * @returns A new, empty BST.
     */
    _createInstance(options) {
      const Ctor = this.constructor;
      return new Ctor([], { ...this._snapshotOptions(), ...options != null ? options : {} });
    }
    /**
     * (Protected) Creates a new instance of the same BST constructor, potentially with different generic types.
     * @remarks Time O(N log N) or O(N^2) (from constructor) due to processing the iterable.
     *
     * @template TK, TV, TR - Generic types for the new instance.
     * @param [iter=[]] - An iterable to populate the new BST.
     * @param [options] - Options for the new BST.
     * @returns A new BST.
     */
    _createLike(iter = [], options) {
      const Ctor = this.constructor;
      return new Ctor(iter, { ...this._snapshotOptions(), ...options != null ? options : {} });
    }
    /**
     * (Protected) Snapshots the current BST's configuration options.
     * @remarks Time O(1)
     *
     * @template TK, TV, TR - Generic types for the options.
     * @returns The options object.
     */
    _snapshotOptions() {
      return {
        ...super._snapshotOptions(),
        comparator: this._comparator,
        enableOrderStatistic: this._enableOrderStatistic
      };
    }
    /**
     * (Protected) Converts a key, node, or entry into a standardized [node, value] tuple.
     * @remarks Time O(1)
     *
     * @param keyNodeOrEntry - The input item.
     * @param [value] - An optional value (used if input is just a key).
     * @returns A tuple of [node, value].
     */
    _keyValueNodeOrEntryToNodeAndValue(keyNodeOrEntry, value) {
      const [node, entryValue] = super._keyValueNodeOrEntryToNodeAndValue(keyNodeOrEntry, value);
      if (node === null) return [void 0, void 0];
      return [node, value != null ? value : entryValue];
    }
    /**
     * (Protected) Sets the root node and clears its parent reference.
     * @remarks Time O(1)
     *
     * @param v - The node to set as root.
     */
    /**
     * (Protected) Recalculates the subtree count for a single node.
     * @remarks Time O(1). Only active when enableOrderStatistic is true.
     */
    _updateCount(node) {
      if (!this._enableOrderStatistic) return;
      node._count = 1 + (this.isRealNode(node.left) ? node.left._count : 0) + (this.isRealNode(node.right) ? node.right._count : 0);
    }
    /**
     * (Protected) Updates subtree counts from a node up to the root.
     * @remarks Time O(log n). Only active when enableOrderStatistic is true.
     */
    _updateCountAlongPath(node) {
      if (!this._enableOrderStatistic) return;
      let current = node;
      while (current) {
        this._updateCount(current);
        current = current.parent;
      }
    }
    /**
     * (Protected) Finds the node at position k in tree order (iterative).
     * @remarks Time O(log n), Space O(1)
     */
    _getByRankIterative(node, k) {
      let current = node;
      let remaining = k;
      while (current) {
        const leftCount = this.isRealNode(current.left) ? current.left._count : 0;
        if (remaining < leftCount) {
          current = current.left;
        } else if (remaining === leftCount) {
          return current;
        } else {
          remaining = remaining - leftCount - 1;
          current = current.right;
        }
      }
      return void 0;
    }
    /**
     * (Protected) Finds the node at position k in tree order (recursive).
     * @remarks Time O(log n), Space O(log n) call stack
     */
    _getByRankRecursive(node, k) {
      if (!node) return void 0;
      const leftCount = this.isRealNode(node.left) ? node.left._count : 0;
      if (k < leftCount) return this._getByRankRecursive(node.left, k);
      if (k === leftCount) return node;
      return this._getByRankRecursive(node.right, k - leftCount - 1);
    }
    /**
     * (Protected) Computes the rank of a key iteratively.
     * @remarks Time O(log n), Space O(1)
     */
    _getRankIterative(node, key) {
      let rank = 0;
      let current = node;
      while (this.isRealNode(current)) {
        const cmp = this._compare(current.key, key);
        if (cmp > 0) {
          current = current.left;
        } else if (cmp < 0) {
          rank += (this.isRealNode(current.left) ? current.left._count : 0) + 1;
          current = current.right;
        } else {
          rank += this.isRealNode(current.left) ? current.left._count : 0;
          return rank;
        }
      }
      return rank;
    }
    /**
     * (Protected) Computes the rank of a key recursively.
     * @remarks Time O(log n), Space O(log n) call stack
     */
    _getRankRecursive(node, key) {
      if (!node) return 0;
      const cmp = this._compare(node.key, key);
      if (cmp > 0) {
        return this._getRankRecursive(node.left, key);
      } else if (cmp < 0) {
        return (this.isRealNode(node.left) ? node.left._count : 0) + 1 + this._getRankRecursive(node.right, key);
      } else {
        return this.isRealNode(node.left) ? node.left._count : 0;
      }
    }
    /**
     * (Protected) Finds the in-order successor of a node.
     * @remarks Time O(log n), Space O(1)
     */
    _next(node) {
      if (this.isRealNode(node.right)) {
        let current2 = node.right;
        while (this.isRealNode(current2.left)) {
          current2 = current2.left;
        }
        return current2;
      }
      let current = node;
      let parent = current.parent;
      while (parent && current === parent.right) {
        current = parent;
        parent = parent.parent;
      }
      return parent;
    }
    _setRoot(v) {
      if (v) v.parent = void 0;
      this._root = v;
    }
    /**
     * (Protected) Compares two keys using the tree's comparator and reverse setting.
     * @remarks Time O(1) Space O(1)
     *
     * @param a - The first key.
     * @param b - The second key.
     * @returns A number (1, -1, or 0) representing the comparison.
     */
    _compare(a, b) {
      return this._comparator(a, b);
    }
    /**
     * (Private) Deletes a node by its key.
     * @remarks Standard BST deletion algorithm. Time O(log N), O(N) worst-case. Space O(1).
     *
     * @param key - The key of the node to delete.
     * @returns True if the node was found and deleted, false otherwise.
     */
    _deleteByKey(key) {
      let node = this._root;
      while (node) {
        const cmp = this._compare(node.key, key);
        if (cmp === 0) break;
        node = cmp > 0 ? node.left : node.right;
      }
      if (!node) return false;
      const transplant = (u, v) => {
        const p = u == null ? void 0 : u.parent;
        if (!p) {
          this._setRoot(v);
        } else if (p.left === u) {
          p.left = v;
        } else {
          p.right = v;
        }
        if (v) v.parent = p;
      };
      const minNode = (x) => {
        if (!x) return void 0;
        while (x.left !== void 0 && x.left !== null) x = x.left;
        return x;
      };
      let countUpdateStart;
      if (node.left === void 0) {
        countUpdateStart = node.parent;
        transplant(node, node.right);
      } else if (node.right === void 0) {
        countUpdateStart = node.parent;
        transplant(node, node.left);
      } else {
        const succ = minNode(node.right);
        if (succ.parent !== node) {
          countUpdateStart = succ.parent;
          transplant(succ, succ.right);
          succ.right = node.right;
          if (succ.right) succ.right.parent = succ;
        } else {
          countUpdateStart = succ;
        }
        transplant(node, succ);
        succ.left = node.left;
        if (succ.left) succ.left.parent = succ;
      }
      this._updateCountAlongPath(countUpdateStart);
      this._size = Math.max(0, this._size - 1);
      return true;
    }
  };

  // src/data-structures/binary-tree/red-black-tree.ts
  var RedBlackTreeNode = class {
    /**
     * Create a Red-Black Tree node.
     * @remarks Time O(1), Space O(1)
     * @param key - Node key.
     * @param [value] - Node value (unused in map mode trees).
     * @param color - Node color.
     */
    constructor(key, value, color = "BLACK") {
      __publicField(this, "key");
      __publicField(this, "value");
      __publicField(this, "parent");
      __publicField(this, "_left");
      __publicField(this, "_right");
      __publicField(this, "_height", 0);
      __publicField(this, "_color", "BLACK");
      __publicField(this, "_count", 1);
      this.key = key;
      this.value = value;
      this.color = color;
    }
    /**
     * Get the left child pointer.
     * @remarks Time O(1), Space O(1)
     * @returns Left child node, or null/undefined.
     */
    get left() {
      return this._left;
    }
    /**
     * Set the left child and update its parent pointer.
     * @remarks Time O(1), Space O(1)
     * @param v - New left node, or null/undefined.
     * @returns void
     */
    set left(v) {
      if (v) {
        v.parent = this;
      }
      this._left = v;
    }
    /**
     * Get the right child pointer.
     * @remarks Time O(1), Space O(1)
     * @returns Right child node, or null/undefined.
     */
    get right() {
      return this._right;
    }
    /**
     * Set the right child and update its parent pointer.
     * @remarks Time O(1), Space O(1)
     * @param v - New right node, or null/undefined.
     * @returns void
     */
    set right(v) {
      if (v) {
        v.parent = this;
      }
      this._right = v;
    }
    /**
     * Gets the height of the node (used in self-balancing trees).
     * @remarks Time O(1), Space O(1)
     *
     * @returns The height.
     */
    /* istanbul ignore next -- covered by AVLTree tests (subclass uses height) */
    get height() {
      return this._height;
    }
    /* istanbul ignore next -- covered by AVLTree tests (subclass uses height) */
    set height(value) {
      this._height = value;
    }
    /**
     * Gets the color of the node (used in Red-Black trees).
     * @remarks Time O(1), Space O(1)
     *
     * @returns The node's color.
     */
    get color() {
      return this._color;
    }
    /**
     * Sets the color of the node.
     * @remarks Time O(1), Space O(1)
     *
     * @param value - The new color.
     */
    set color(value) {
      this._color = value;
    }
    /**
     * Gets the count of nodes in the subtree rooted at this node (used in order-statistic trees).
     * @remarks Time O(1), Space O(1)
     *
     * @returns The subtree node count.
     */
    /* istanbul ignore next -- internal field, exercised indirectly via tree operations */
    get count() {
      return this._count;
    }
    /**
     * Gets the position of the node relative to its parent.
     * @remarks Time O(1), Space O(1)
     *
     * @returns The family position (e.g., 'ROOT', 'LEFT', 'RIGHT').
     */
    get familyPosition() {
      if (!this.parent) {
        return this.left || this.right ? "ROOT" : "ISOLATED";
      }
      if (this.parent.left === this) {
        return this.left || this.right ? "ROOT_LEFT" : "LEFT";
      } else if (this.parent.right === this) {
        return this.left || this.right ? "ROOT_RIGHT" : "RIGHT";
      }
      return "MAL_NODE";
    }
  };
  var RedBlackTree = class extends BST {
    constructor(keysNodesEntriesOrRaws = [], options) {
      super([], options);
      __publicField(this, "_root");
      /**
       * (Internal) Header sentinel:
       * - header.parent -> root
       * - header._left  -> min (or NIL)
       * - header._right -> max (or NIL)
       *
       * IMPORTANT:
       * - This header is NOT part of the actual tree.
       * - Do NOT use `header.left` / `header.right` accessors for wiring: those setters update `NIL.parent`
       *   and can corrupt sentinel invariants / cause hangs. Only touch `header._left/_right`.
       */
      __publicField(this, "_header");
      /**
       * (Internal) Cache of the current minimum and maximum nodes.
       * Used for fast-path insert/update when keys are monotonic or near-boundary.
       */
      __publicField(this, "_minNode");
      __publicField(this, "_maxNode");
      this._root = this.NIL;
      this._header = new RedBlackTreeNode(void 0, void 0, "BLACK");
      this._header.parent = this.NIL;
      this._header._left = this.NIL;
      this._header._right = this.NIL;
      if (keysNodesEntriesOrRaws) {
        this.setMany(keysNodesEntriesOrRaws);
      }
    }
    /**
     * Get the current root node.
     * @remarks Time O(1), Space O(1)
     * @returns Root node, or undefined.
     */
    get root() {
      return this._root;
    }
    /**
     * Create a red-black node for the given key/value (value ignored in map mode).
     * @remarks Time O(1), Space O(1)
     * @param key - See parameter type for details.
     * @param [value] - See parameter type for details.
     * @param color - See parameter type for details.
     * @returns A new RedBlackTreeNode instance.
     */
    createNode(key, value, color = "BLACK") {
      return new RedBlackTreeNode(key, value, color);
    }
    /**
     * Type guard: check whether the input is a RedBlackTreeNode.
     * @remarks Time O(1), Space O(1)
     * @param keyNodeOrEntry - See parameter type for details.
     * @returns True if the value is a RedBlackTreeNode.
     */
    isNode(keyNodeOrEntry) {
      return keyNodeOrEntry instanceof RedBlackTreeNode;
    }
    /**
      * Remove all nodes, clear the key→value store (if in map mode) and internal caches.
      * @remarks Time O(n), Space O(1)
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Remove all entries
    *  const rbt = new RedBlackTree<number>([1, 2, 3]);
    *     rbt.clear();
    *     console.log(rbt.isEmpty()); // true;
      */
    clear() {
      super.clear();
      this._root = this.NIL;
      this._header.parent = this.NIL;
      this._setMinCache(void 0);
      this._setMaxCache(void 0);
    }
    /**
     * (Internal) Find a node by key using a tight BST walk (no allocations).
     *
     * NOTE: This uses `header.parent` as the canonical root pointer.
     * @remarks Time O(log n) average, Space O(1)
     */
    _findNodeByKey(key) {
      var _a, _b, _c;
      const NIL = this.NIL;
      const cmp = this._compare.bind(this);
      let cur = (_a = this._header.parent) != null ? _a : NIL;
      while (cur !== NIL) {
        const c = cmp(key, cur.key);
        if (c < 0) cur = (_b = cur.left) != null ? _b : NIL;
        else if (c > 0) cur = (_c = cur.right) != null ? _c : NIL;
        else return cur;
      }
      return void 0;
    }
    /**
     * (Internal) In-order predecessor of a node in a BST.
     * @remarks Time O(log n) average, Space O(1)
     */
    _predecessorOf(node) {
      const NIL = this.NIL;
      if (node.left && node.left !== NIL) {
        let cur2 = node.left;
        while (cur2.right && cur2.right !== NIL) cur2 = cur2.right;
        return cur2;
      }
      let cur = node;
      let p = node.parent;
      while (p && cur === p.left) {
        cur = p;
        p = p.parent;
      }
      return p;
    }
    /**
     * (Internal) In-order successor of a node in a BST.
     * @remarks Time O(log n) average, Space O(1)
     */
    _successorOf(node) {
      const NIL = this.NIL;
      if (node.right && node.right !== NIL) {
        let cur2 = node.right;
        while (cur2.left && cur2.left !== NIL) cur2 = cur2.left;
        return cur2;
      }
      let cur = node;
      let p = node.parent;
      while (p && cur === p.right) {
        cur = p;
        p = p.parent;
      }
      return p;
    }
    /**
     * (Internal) Attach a new node directly under a known parent/side (no search).
     *
     * This is a performance-oriented helper used by boundary fast paths and hinted insertion.
     * It will:
     * - wire parent/child pointers (using accessors, so parent pointers are updated)
     * - initialize children to NIL
     * - mark the new node RED, then run insert fix-up
     *
     * Precondition: the chosen slot (parent.left/parent.right) is empty (NIL/null/undefined).
     * @remarks Time O(log n) average, Space O(1)
     */
    _attachNewNode(parent, side, node) {
      const NIL = this.NIL;
      node.parent = parent;
      if (side === "left") parent.left = node;
      else parent.right = node;
      node.left = NIL;
      node.right = NIL;
      node.color = "RED";
      this._updateCountAlongPath(node);
      this._insertFixup(node);
      if (this.isRealNode(this._root)) this._root.color = "BLACK";
    }
    /**
     * (Internal) a single source of truth for min/max is header._left/_right.
     * Keep legacy _minNode/_maxNode mirrored for compatibility.
     * @remarks Time O(1), Space O(1)
     */
    /**
     * (Internal) Update min cache pointers (header._left is the canonical min pointer).
     * @remarks Time O(1), Space O(1)
     */
    _setMinCache(node) {
      this._minNode = node;
      this._header._left = node != null ? node : this.NIL;
    }
    /**
     * (Internal) Update max cache pointers (header._right is the canonical max pointer).
     * @remarks Time O(1), Space O(1)
     */
    _setMaxCache(node) {
      this._maxNode = node;
      this._header._right = node != null ? node : this.NIL;
    }
    /**
     * (Internal) Core set implementation returning the affected node.
     *
     * Hot path goals:
     * - Avoid double walks (search+insert): do a single traversal that either updates or inserts.
     * - Use header min/max caches to fast-path boundary inserts.
     * - Keep header._left/_right as canonical min/max pointers.
     *
     * Return value:
     * - `{ node, created:false }` when an existing key is updated
     * - `{ node, created:true }` when a new node is inserted
     * - `undefined` only on unexpected internal failure.
     * @remarks Time O(log n) average, Space O(1)
     */
    _setKVNode(key, nextValue) {
      var _a, _b, _c, _d, _e, _f, _g;
      const NIL = this.NIL;
      const comparator = this._comparator;
      const header = this._header;
      const minN = (_a = header._left) != null ? _a : NIL;
      if (minN !== NIL) {
        const cMin = comparator(key, minN.key);
        if (cMin === 0) {
          minN.value = nextValue;
          if (this._isMapMode) this._store.set(key, minN);
          return { node: minN, created: false };
        }
        const minL = minN.left;
        if (cMin < 0 && (minL === NIL || minL === null || minL === void 0)) {
          const newNode2 = this.createNode(key, nextValue);
          this._attachNewNode(minN, "left", newNode2);
          if (this._isMapMode) this._store.set(newNode2.key, newNode2);
          this._size++;
          this._setMinCache(newNode2);
          if (header._right === NIL) this._setMaxCache(newNode2);
          return { node: newNode2, created: true };
        }
        if (cMin > 0) {
          const maxN = (_b = header._right) != null ? _b : NIL;
          const cMax = comparator(key, maxN.key);
          if (cMax === 0) {
            maxN.value = nextValue;
            if (this._isMapMode) this._store.set(key, maxN);
            return { node: maxN, created: false };
          }
          const maxR = maxN.right;
          if (cMax > 0 && (maxR === NIL || maxR === null || maxR === void 0)) {
            const newNode2 = this.createNode(key, nextValue);
            this._attachNewNode(maxN, "right", newNode2);
            if (this._isMapMode) this._store.set(newNode2.key, newNode2);
            this._size++;
            this._setMaxCache(newNode2);
            if (header._left === NIL) this._setMinCache(newNode2);
            return { node: newNode2, created: true };
          }
        }
      }
      const cmp = comparator;
      const isMapMode = this._isMapMode;
      const store = this._store;
      let current = (_c = this._header.parent) != null ? _c : NIL;
      let parent;
      let lastCompared = 0;
      while (current !== NIL) {
        parent = current;
        lastCompared = cmp(key, current.key);
        if (lastCompared < 0) current = (_d = current.left) != null ? _d : NIL;
        else if (lastCompared > 0) current = (_e = current.right) != null ? _e : NIL;
        else {
          current.value = nextValue;
          if (isMapMode) store.set(key, current);
          return { node: current, created: false };
        }
      }
      const newNode = this.createNode(key, nextValue);
      newNode.parent = parent;
      if (!parent) {
        this._setRoot(newNode);
      } else if (lastCompared < 0) {
        parent.left = newNode;
      } else {
        parent.right = newNode;
      }
      newNode.left = NIL;
      newNode.right = NIL;
      newNode.color = "RED";
      this._updateCountAlongPath(newNode);
      this._insertFixup(newNode);
      if (this.isRealNode(this._root)) this._root.color = "BLACK";
      else return void 0;
      if (isMapMode) store.set(newNode.key, newNode);
      this._size++;
      const hMin = (_f = this._header._left) != null ? _f : NIL;
      const hMax = (_g = this._header._right) != null ? _g : NIL;
      if (hMin === NIL || hMax === NIL) {
        this._setMinCache(newNode);
        this._setMaxCache(newNode);
      } else if (parent === hMax && lastCompared > 0) {
        this._setMaxCache(newNode);
      } else if (parent === hMin && lastCompared < 0) {
        this._setMinCache(newNode);
      } else {
        if (cmp(newNode.key, hMin.key) < 0) this._setMinCache(newNode);
        if (cmp(newNode.key, hMax.key) > 0) this._setMaxCache(newNode);
      }
      return { node: newNode, created: true };
    }
    /**
     * (Internal) Boolean wrapper around `_setKVNode`.
     *
     * Includes a map-mode update fast-path:
     * - If `isMapMode=true` and the key already exists in `_store`, then updating the value does not
     *   require any tree search/rotation (tree shape depends only on key).
     * - This path is intentionally limited to `nextValue !== undefined` to preserve existing
     *   semantics for `undefined` values.
     * @remarks Time O(log n) average, Space O(1)
     */
    _setKV(key, nextValue) {
      if (this._isMapMode) {
        const store = this._store;
        const node = store.get(key);
        if (node) {
          node.value = nextValue;
          return true;
        }
      }
      return this._setKVNode(key, nextValue) !== void 0;
    }
    /**
     * Insert/update using a hint node to speed up nearby insertions.
     *
     * close to the expected insertion position (often the previously returned node in a loop).
     *
     * When the hint is a good fit (sorted / nearly-sorted insertion), this can avoid most of the
     * normal root-to-leaf search and reduce constant factors.
     *
     * When the hint does not match (random workloads), this will fall back to the normal set path.
     * @remarks Time O(log n) average, Space O(1)
     */
    setWithHintNode(key, value, hint) {
      var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
      if (!hint || !this.isRealNode(hint)) {
        return (_a = this._setKVNode(key, value)) == null ? void 0 : _a.node;
      }
      const cmp = this._compare.bind(this);
      const c0 = cmp(key, hint.key);
      if (c0 === 0) {
        hint.value = value;
        if (this._isMapMode) this._store.set(key, hint);
        return hint;
      }
      if (c0 < 0) {
        if (!this.isRealNode(hint.left)) {
          const newNode = this.createNode(key, value);
          if (!this.isRealNode(newNode)) return void 0;
          this._attachNewNode(hint, "left", newNode);
          if (this._isMapMode) this._store.set(key, newNode);
          this._size++;
          const NIL = this.NIL;
          const hMin = (_b = this._header._left) != null ? _b : NIL;
          if (hMin === NIL || this._compare(newNode.key, hMin.key) < 0) this._setMinCache(newNode);
          const hMax = (_c = this._header._right) != null ? _c : NIL;
          if (hMax === NIL || this._compare(newNode.key, hMax.key) > 0) this._setMaxCache(newNode);
          return newNode;
        }
        const pred = this._predecessorOf(hint);
        if (pred && cmp(pred.key, key) >= 0) {
          return (_d = this._setKVNode(key, value)) == null ? void 0 : _d.node;
        }
        if (pred && !this.isRealNode(pred.right)) {
          const newNode = this.createNode(key, value);
          if (!this.isRealNode(newNode)) return void 0;
          this._attachNewNode(pred, "right", newNode);
          if (this._isMapMode) this._store.set(key, newNode);
          this._size++;
          const NIL = this.NIL;
          const hMin = (_e = this._header._left) != null ? _e : NIL;
          if (hMin === NIL || this._compare(newNode.key, hMin.key) < 0) this._setMinCache(newNode);
          const hMax = (_f = this._header._right) != null ? _f : NIL;
          if (hMax === NIL || this._compare(newNode.key, hMax.key) > 0) this._setMaxCache(newNode);
          return newNode;
        }
        return (_g = this._setKVNode(key, value)) == null ? void 0 : _g.node;
      }
      if (!this.isRealNode(hint.right)) {
        const newNode = this.createNode(key, value);
        if (!this.isRealNode(newNode)) return void 0;
        this._attachNewNode(hint, "right", newNode);
        if (this._isMapMode) this._store.set(key, newNode);
        this._size++;
        const NIL = this.NIL;
        const hMin = (_h = this._header._left) != null ? _h : NIL;
        if (hMin === NIL || this._compare(newNode.key, hMin.key) < 0) this._setMinCache(newNode);
        const hMax = (_i = this._header._right) != null ? _i : NIL;
        if (hMax === NIL || this._compare(newNode.key, hMax.key) > 0) this._setMaxCache(newNode);
        return newNode;
      }
      const succ = this._successorOf(hint);
      if (succ && cmp(succ.key, key) <= 0) {
        return (_j = this._setKVNode(key, value)) == null ? void 0 : _j.node;
      }
      if (succ && !this.isRealNode(succ.left)) {
        const newNode = this.createNode(key, value);
        if (!this.isRealNode(newNode)) return void 0;
        this._attachNewNode(succ, "left", newNode);
        if (this._isMapMode) this._store.set(key, newNode);
        this._size++;
        const NIL = this.NIL;
        const hMin = (_k = this._header._left) != null ? _k : NIL;
        if (hMin === NIL || this._compare(newNode.key, hMin.key) < 0) this._setMinCache(newNode);
        const hMax = (_l = this._header._right) != null ? _l : NIL;
        if (hMax === NIL || this._compare(newNode.key, hMax.key) > 0) this._setMaxCache(newNode);
        return newNode;
      }
      return (_m = this._setKVNode(key, value)) == null ? void 0 : _m.node;
    }
    /**
     * Boolean wrapper for setWithHintNode.
     * @remarks Time O(log n) average, Space O(1)
     */
    setWithHint(key, value, hint) {
      return this.setWithHintNode(key, value, hint) !== void 0;
    }
    /**
      * Insert or update a key/value (map mode) or key-only (set mode).
      *
      * This method is optimized for:
      * - monotonic inserts via min/max boundary fast paths
      * - updates via a single-pass search (no double walk)
      *
      * @remarks Time O(log n) average, Space O(1)
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // basic Red-Black Tree with simple number keys
    *  // Create a simple Red-Black Tree with numeric keys
    *     const tree = new RedBlackTree([5, 2, 8, 1, 9]);
    *
    *     tree.print();
    *     //   _2___
    *     //  /     \
    *     //  1    _8_
    *     //      /   \
    *     //      5   9
    *
    *     // Verify the tree maintains sorted order
    *     console.log([...tree.keys()]); // [1, 2, 5, 8, 9];
    *
    *     // Check size
    *     console.log(tree.size); // 5;
      */
    set(keyNodeOrEntry, value) {
      if (!this.isNode(keyNodeOrEntry)) {
        if (keyNodeOrEntry === null || keyNodeOrEntry === void 0) return false;
        if (this.isEntry(keyNodeOrEntry)) {
          const key = keyNodeOrEntry[0];
          if (key === null || key === void 0) return false;
          const nextValue = value != null ? value : keyNodeOrEntry[1];
          return this._setKV(key, nextValue);
        }
        return this._setKV(keyNodeOrEntry, value);
      }
      const [newNode, newValue] = this._keyValueNodeOrEntryToNodeAndValue(keyNodeOrEntry, value);
      if (!this.isRealNode(newNode)) return false;
      const insertStatus = this._insert(newNode);
      if (insertStatus === "CREATED") {
        if (this.isRealNode(this._root)) {
          this._root.color = "BLACK";
        } else {
          return false;
        }
        if (this._isMapMode) {
          const n = this.getNode(newNode.key);
          if (this.isRealNode(n)) {
            n.value = newValue;
            this._store.set(n.key, n);
          }
        }
        this._size++;
        return true;
      }
      if (insertStatus === "UPDATED") {
        if (this._isMapMode) {
          const n = this.getNode(newNode.key);
          if (this.isRealNode(n)) {
            n.value = newValue;
            this._store.set(n.key, n);
          }
        }
        return true;
      }
      return false;
    }
    /**
      * Delete a node by key/node/entry and rebalance as needed.
      * @remarks Time O(log n) average, Space O(1)
      * @param keyNodeEntryRawOrPredicate - Key, node, or [key, value] entry identifying the node to delete.
      * @returns Array with deletion metadata (removed node, rebalancing hint if any).
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Remove and rebalance
    *  const rbt = new RedBlackTree<number>([10, 5, 15, 3, 7]);
    *     rbt.delete(5);
    *     console.log(rbt.has(5)); // false;
    *     console.log(rbt.size); // 4;
      */
    delete(keyNodeEntryRawOrPredicate) {
      var _a, _b, _c;
      if (keyNodeEntryRawOrPredicate === null) return false;
      let nodeToDelete;
      if (this._isPredicate(keyNodeEntryRawOrPredicate)) nodeToDelete = this.getNode(keyNodeEntryRawOrPredicate);
      else nodeToDelete = this.isRealNode(keyNodeEntryRawOrPredicate) ? keyNodeEntryRawOrPredicate : this.getNode(keyNodeEntryRawOrPredicate);
      if (!nodeToDelete) {
        return false;
      }
      const willDeleteMin = nodeToDelete === this._minNode;
      const willDeleteMax = nodeToDelete === this._maxNode;
      const nextMin = willDeleteMin ? this._successorOf(nodeToDelete) : void 0;
      const nextMax = willDeleteMax ? this._predecessorOf(nodeToDelete) : void 0;
      let originalColor = nodeToDelete.color;
      const NIL = this.NIL;
      let replacementNode = NIL;
      if (!this.isRealNode(nodeToDelete.left)) {
        replacementNode = (_a = nodeToDelete.right) != null ? _a : NIL;
        this._transplant(nodeToDelete, replacementNode);
      } else if (!this.isRealNode(nodeToDelete.right)) {
        replacementNode = nodeToDelete.left;
        this._transplant(nodeToDelete, replacementNode);
      } else {
        const successor = this.getLeftMost((node) => node, nodeToDelete.right);
        if (successor) {
          originalColor = successor.color;
          replacementNode = (_b = successor.right) != null ? _b : NIL;
          if (successor.parent === nodeToDelete) {
            replacementNode.parent = successor;
          } else {
            this._transplant(successor, replacementNode);
            successor.right = nodeToDelete.right;
            if (successor.right) {
              successor.right.parent = successor;
            }
          }
          this._transplant(nodeToDelete, successor);
          successor.left = nodeToDelete.left;
          if (successor.left) {
            successor.left.parent = successor;
          }
          successor.color = nodeToDelete.color;
        }
      }
      if (this._isMapMode) this._store.delete(nodeToDelete.key);
      this._size--;
      this._updateCountAlongPath((_c = replacementNode == null ? void 0 : replacementNode.parent) != null ? _c : replacementNode);
      if (this._size <= 0) {
        this._setMinCache(void 0);
        this._setMaxCache(void 0);
      } else {
        if (willDeleteMin) this._setMinCache(nextMin);
        if (willDeleteMax) this._setMaxCache(nextMax);
        if (!this._minNode || !this.isRealNode(this._minNode)) {
          this._setMinCache(this.isRealNode(this._root) ? this.getLeftMost((n) => n, this._root) : void 0);
        }
        if (!this._maxNode || !this.isRealNode(this._maxNode)) {
          this._setMaxCache(this.isRealNode(this._root) ? this.getRightMost((n) => n, this._root) : void 0);
        }
      }
      if (originalColor === "BLACK") {
        this._deleteFixup(replacementNode);
      }
      return true;
    }
    /**
     * Transform entries into a like-kind red-black tree with possibly different key/value types.
     * @remarks Time O(n) average, Space O(n)
     * @template MK
     * @template MV
     * @template MR
     * @param callback - Mapping function from (key, value, index, tree) to a new [key, value].
     * @param [options] - See parameter type for details.
     * @param [thisArg] - See parameter type for details.
     * @returns A new RedBlackTree with mapped entries.
     */
    /**
      * Red-Black trees are self-balancing — `perfectlyBalance` rebuilds via
      * sorted bulk insert, which naturally produces a balanced RBT.
      * @remarks Time O(N), Space O(N)
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Rebalance tree
    *  const rbt = new RedBlackTree<number>([1, 2, 3, 4, 5]);
    *     rbt.perfectlyBalance();
    *     console.log(rbt.isAVLBalanced()); // true;
      */
    perfectlyBalance(_iterationType) {
      const entries = [];
      for (const [key, value] of this) entries.push([key, value]);
      if (entries.length <= 1) return true;
      this.clear();
      this.setMany(
        entries.map(([k]) => k),
        entries.map(([, v]) => v),
        true
        // isBalanceAdd
      );
      return true;
    }
    /**
      * Transform to new tree
     
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       * @example
    * // Transform to new tree
    *  const rbt = new RedBlackTree<number, number>([[1, 10], [2, 20]]);
    *     const doubled = rbt.map((v, k) => [k, (v ?? 0) * 2] as [number, number]);
    *     console.log([...doubled.values()]); // [20, 40];
      */
    map(callback, options, thisArg) {
      const out = this._createLike([], options);
      let index = 0;
      for (const [key, value] of this) {
        out.set(callback.call(thisArg, value, key, index++, this));
      }
      return out;
    }
    /**
     * (Internal) Create an empty instance of the same concrete tree type.
     * @remarks Time O(1) average, Space O(1)
     */
    _createInstance(options) {
      const Ctor = this.constructor;
      return new Ctor([], { ...this._snapshotOptions(), ...options != null ? options : {} });
    }
    /**
     * (Internal) Create a like-kind tree (same concrete class) populated from an iterable.
     * @remarks Time O(m log m) average (m = iterable length), Space O(m)
     */
    _createLike(iter = [], options) {
      const Ctor = this.constructor;
      return new Ctor(iter, { ...this._snapshotOptions(), ...options != null ? options : {} });
    }
    /**
     * (Internal) Set the root pointer and keep header.parent in sync.
     * @remarks Time O(1), Space O(1)
     */
    _setRoot(v) {
      const NIL = this.NIL;
      if (v) {
        v.parent = void 0;
      }
      this._root = v;
      this._header.parent = v != null ? v : NIL;
    }
    /**
     * (Internal) Replace a node in place while preserving its color.
     * @remarks Time O(1) average, Space O(1)
     */
    _replaceNode(oldNode, newNode) {
      newNode.color = oldNode.color;
      return super._replaceNode(oldNode, newNode);
    }
    /**
     * (Protected) Standard BST insert followed by red-black fix-up.
     * @remarks Time O(log n) average, Space O(1)
     * @param node - Node to insert.
     * @returns Status string: 'CREATED' or 'UPDATED'.
     */
    _insert(node) {
      var _a, _b, _c;
      const NIL = this.NIL;
      const cmp = this._compare.bind(this);
      let current = (_a = this._header.parent) != null ? _a : NIL;
      let parent;
      let lastCompared = 0;
      while (current !== NIL) {
        parent = current;
        lastCompared = cmp(node.key, current.key);
        if (lastCompared < 0) {
          current = (_b = current.left) != null ? _b : NIL;
        } else if (lastCompared > 0) {
          current = (_c = current.right) != null ? _c : NIL;
        } else {
          this._replaceNode(current, node);
          return "UPDATED";
        }
      }
      node.parent = parent;
      if (!parent) {
        this._setRoot(node);
      } else if (lastCompared < 0) {
        parent.left = node;
      } else {
        parent.right = node;
      }
      node.left = NIL;
      node.right = NIL;
      node.color = "RED";
      this._updateCountAlongPath(node);
      this._insertFixup(node);
      return "CREATED";
    }
    /**
     * (Protected) Transplant a subtree in place of another during deletion.
     * @remarks Time O(1), Space O(1)
     * @param u - Node to replace.
     * @param v - Replacement subtree root (may be undefined).
     * @returns void
     */
    _transplant(u, v) {
      if (!u.parent) {
        this._setRoot(v);
      } else if (u === u.parent.left) {
        u.parent.left = v;
      } else {
        u.parent.right = v;
      }
      if (v) {
        v.parent = u.parent;
      }
    }
    /**
     * (Protected) Restore red-black properties after insertion (recolor/rotate).
     * @remarks Time O(log n) average, Space O(1)
     * @param z - Recently inserted node.
     * @returns void
     */
    _insertFixup(z) {
      const leftRotate = this._leftRotate.bind(this);
      const rightRotate = this._rightRotate.bind(this);
      while (z) {
        const p = z.parent;
        if (!p || p.color !== "RED") break;
        const gp = p.parent;
        if (!gp) break;
        if (p === gp.left) {
          const y = gp.right;
          if ((y == null ? void 0 : y.color) === "RED") {
            p.color = "BLACK";
            y.color = "BLACK";
            gp.color = "RED";
            z = gp;
            continue;
          }
          if (z === p.right) {
            z = p;
            leftRotate(z);
          }
          const p2 = z == null ? void 0 : z.parent;
          const gp2 = p2 == null ? void 0 : p2.parent;
          if (p2 && gp2) {
            p2.color = "BLACK";
            gp2.color = "RED";
            rightRotate(gp2);
          }
        } else {
          const y = gp.left;
          if ((y == null ? void 0 : y.color) === "RED") {
            p.color = "BLACK";
            y.color = "BLACK";
            gp.color = "RED";
            z = gp;
            continue;
          }
          if (z === p.left) {
            z = p;
            rightRotate(z);
          }
          const p2 = z == null ? void 0 : z.parent;
          const gp2 = p2 == null ? void 0 : p2.parent;
          if (p2 && gp2) {
            p2.color = "BLACK";
            gp2.color = "RED";
            leftRotate(gp2);
          }
        }
        break;
      }
      if (this.isRealNode(this._root)) this._root.color = "BLACK";
    }
    /**
     * (Protected) Restore red-black properties after deletion (recolor/rotate).
     * @remarks Time O(log n) average, Space O(1)
     * @param node - Child that replaced the deleted node (may be undefined).
     * @returns void
     */
    _deleteFixup(node) {
      if (!node) return;
      const NIL = this.NIL;
      let current = node;
      while (current !== this.root && current.color === "BLACK") {
        const parent = current.parent;
        if (!parent) break;
        const nodeIsLeft = current === parent.left;
        let sibling = nodeIsLeft ? parent.right : parent.left;
        if (sibling && sibling.color === "RED") {
          sibling.color = "BLACK";
          parent.color = "RED";
          if (nodeIsLeft) {
            this._leftRotate(parent);
            sibling = parent.right;
          } else {
            this._rightRotate(parent);
            sibling = parent.left;
          }
        }
        const sibLeft = sibling == null ? void 0 : sibling.left;
        const sibRight = sibling == null ? void 0 : sibling.right;
        const sibLeftBlack = !sibLeft || sibLeft === NIL || sibLeft.color === "BLACK";
        const sibRightBlack = !sibRight || sibRight === NIL || sibRight.color === "BLACK";
        if (sibLeftBlack && sibRightBlack) {
          if (sibling) sibling.color = "RED";
          current = parent;
        } else {
          if (nodeIsLeft) {
            if (sibRightBlack) {
              if (sibLeft) sibLeft.color = "BLACK";
              if (sibling) sibling.color = "RED";
              if (sibling) this._rightRotate(sibling);
              sibling = parent.right;
            }
            if (sibling) sibling.color = parent.color;
            parent.color = "BLACK";
            if (sibling == null ? void 0 : sibling.right) sibling.right.color = "BLACK";
            this._leftRotate(parent);
          } else {
            if (sibLeftBlack) {
              if (sibRight) sibRight.color = "BLACK";
              if (sibling) sibling.color = "RED";
              if (sibling) this._leftRotate(sibling);
              sibling = parent.left;
            }
            if (sibling) sibling.color = parent.color;
            parent.color = "BLACK";
            if (sibling == null ? void 0 : sibling.left) sibling.left.color = "BLACK";
            this._rightRotate(parent);
          }
          current = this.root;
        }
      }
      current.color = "BLACK";
    }
    /**
     * (Protected) Perform a left rotation around x.
     * @remarks Time O(1), Space O(1)
     * @param x - Pivot node to rotate around.
     * @returns void
     */
    _leftRotate(x) {
      if (!x || !x.right) {
        return;
      }
      const y = x.right;
      x.right = y.left;
      if (y.left && y.left !== this.NIL) {
        y.left.parent = x;
      }
      y.parent = x.parent;
      if (!x.parent) {
        this._setRoot(y);
      } else if (x === x.parent.left) {
        x.parent.left = y;
      } else {
        x.parent.right = y;
      }
      y.left = x;
      x.parent = y;
      this._updateCount(x);
      this._updateCount(y);
    }
    /**
     * (Protected) Perform a right rotation around y.
     * @remarks Time O(1), Space O(1)
     * @param y - Pivot node to rotate around.
     * @returns void
     */
    _rightRotate(y) {
      if (!y || !y.left) {
        return;
      }
      const x = y.left;
      y.left = x.right;
      if (x.right && x.right !== this.NIL) {
        x.right.parent = y;
      }
      x.parent = y.parent;
      if (!y.parent) {
        this._setRoot(x);
      } else if (y === y.parent.left) {
        y.parent.left = x;
      } else {
        y.parent.right = x;
      }
      x.right = y;
      y.parent = x;
      this._updateCount(y);
      this._updateCount(x);
    }
  };
  return __toCommonJS(src_exports);
})();
/**
 * data-structure-typed
 *
 * @author Pablo Zeng
 * @copyright Copyright (c) 2022 Pablo Zeng <zrwusa@gmail.com>
 * @license MIT License
 */
//# sourceMappingURL=red-black-tree-typed.js.map