import { assert } from "./asserts.js";
const MAX_NODE_SIZE = 32;
class BTreeSet {
  #root = emptyLeaf;
  size = 0;
  comparator;
  constructor(comparator, entries) {
    this.comparator = comparator;
    if (entries) {
      for (const key of entries) {
        this.add(key);
      }
    }
  }
  /** Releases the tree so that its size is 0. */
  clear() {
    this.#root = emptyLeaf;
    this.size = 0;
  }
  clone() {
    this.#root.isShared = true;
    const ret = new BTreeSet(this.comparator);
    ret.#root = this.#root;
    ret.size = this.size;
    return ret;
  }
  get(key) {
    return this.#root.get(key, this);
  }
  add(key) {
    if (this.#root.isShared) this.#root = this.#root.clone();
    const result = this.#root.set(key, this);
    if (result === null) return this;
    this.#root = new BNodeInternal([this.#root, result]);
    return this;
  }
  /**
   * Returns true if the key exists in the B+ tree, false if not.
   * Use get() for best performance; use has() if you need to
   * distinguish between "undefined value" and "key not present".
   * @param key Key to detect
   * @description Computational complexity: O(log size)
   */
  has(key) {
    return this.#root.has(key, this);
  }
  /**
   * Removes a single key-value pair from the B+ tree.
   * @param key Key to find
   * @returns true if a pair was found and removed, false otherwise.
   * @description Computational complexity: O(log size)
   */
  delete(key) {
    return this.#delete(key);
  }
  #delete(key) {
    let root = this.#root;
    if (root.isShared) {
      this.#root = root = root.clone();
    }
    try {
      return root.delete(key, this);
    } finally {
      let isShared;
      while (root.keys.length <= 1 && root.isInternal()) {
        isShared ||= root.isShared;
        this.#root = root = root.keys.length === 0 ? emptyLeaf : root.children[0];
      }
      if (isShared) {
        root.isShared = true;
      }
    }
  }
  keys() {
    return valuesFrom(this.#root, this.comparator, void 0, true);
  }
  values() {
    return valuesFrom(this.#root, this.comparator, void 0, true);
  }
  valuesFrom(lowestKey, inclusive = true) {
    return valuesFrom(this.#root, this.comparator, lowestKey, inclusive);
  }
  valuesReversed() {
    return valuesFromReversed(
      this.#maxKey(),
      this.#root,
      this.comparator,
      void 0,
      true
    );
  }
  valuesFromReversed(highestKey, inclusive = true) {
    return valuesFromReversed(
      this.#maxKey(),
      this.#root,
      this.comparator,
      highestKey,
      inclusive
    );
  }
  /** Gets the highest key in the tree. Complexity: O(1) */
  #maxKey() {
    return this.#root.maxKey();
  }
  [Symbol.iterator]() {
    return this.keys();
  }
}
function valuesFrom(root, comparator, lowestKey, inclusive) {
  const info = findPath(lowestKey, root, comparator);
  if (info === void 0) {
    return iterator(() => ({ done: true, value: void 0 }));
  }
  let [nodeQueue, nodeIndex, leaf] = info;
  let i = lowestKey === void 0 ? -1 : indexOf(lowestKey, leaf.keys, 0, comparator) - 1;
  if (!inclusive && i < leaf.keys.length && // +1 because we did -1 above.
  comparator(leaf.keys[i + 1], lowestKey) === 0) {
    i++;
  }
  return iterator(() => {
    for (; ; ) {
      if (++i < leaf.keys.length) {
        return { done: false, value: leaf.keys[i] };
      }
      let level = -1;
      for (; ; ) {
        if (++level >= nodeQueue.length) {
          return { done: true, value: void 0 };
        }
        if (++nodeIndex[level] < nodeQueue[level].length) {
          break;
        }
      }
      for (; level > 0; level--) {
        nodeQueue[level - 1] = nodeQueue[level][nodeIndex[level]].children;
        nodeIndex[level - 1] = 0;
      }
      leaf = nodeQueue[0][nodeIndex[0]];
      i = -1;
    }
  });
}
function valuesFromReversed(maxKey, root, comparator, highestKey, inclusive) {
  if (highestKey === void 0) {
    highestKey = maxKey;
    if (highestKey === void 0)
      return iterator(() => ({ done: true, value: void 0 }));
  }
  let [nodeQueue, nodeIndex, leaf] = findPath(highestKey, root, comparator) || findPath(maxKey, root, comparator);
  assert(
    !nodeQueue[0] || leaf === nodeQueue[0][nodeIndex[0]],
    "BTreeSet: leaf node mismatch in iteration"
  );
  let i = indexOf(highestKey, leaf.keys, 0, comparator);
  if (inclusive && i < leaf.keys.length && comparator(leaf.keys[i], highestKey) <= 0) {
    i++;
  }
  return iterator(() => {
    for (; ; ) {
      if (--i >= 0) {
        return { done: false, value: leaf.keys[i] };
      }
      let level;
      for (level = -1; ; ) {
        if (++level >= nodeQueue.length) {
          return { done: true, value: void 0 };
        }
        if (--nodeIndex[level] >= 0) {
          break;
        }
      }
      for (; level > 0; level--) {
        nodeQueue[level - 1] = nodeQueue[level][nodeIndex[level]].children;
        nodeIndex[level - 1] = nodeQueue[level - 1].length - 1;
      }
      leaf = nodeQueue[0][nodeIndex[0]];
      i = leaf.keys.length;
    }
  });
}
function findPath(key, root, comparator) {
  let nextNode = root;
  const nodeQueue = [];
  const nodeIndex = [];
  if (nextNode.isInternal()) {
    for (let d = 0; nextNode.isInternal(); d++) {
      nodeQueue[d] = nextNode.children;
      nodeIndex[d] = key === void 0 ? 0 : indexOf(key, nextNode.keys, 0, comparator);
      if (nodeIndex[d] >= nodeQueue[d].length) return;
      nextNode = nodeQueue[d][nodeIndex[d]];
    }
    nodeQueue.reverse();
    nodeIndex.reverse();
  }
  return [nodeQueue, nodeIndex, nextNode];
}
function iterator(next) {
  return {
    next,
    [Symbol.iterator]() {
      return this;
    }
  };
}
class BNode {
  // If this is an internal node, _keys[i] is the highest key in children[i].
  keys;
  // True if this node might be within multiple `BTree`s (or have multiple parents).
  // If so, it must be cloned before being mutated to avoid changing an unrelated tree.
  // This is transitive: if it's true, children are also shared even if `isShared!=true`
  // in those children. (Certain operations will propagate isShared=true to children.)
  isShared;
  constructor(keys) {
    this.keys = keys;
    this.isShared = void 0;
  }
  isInternal() {
    return false;
  }
  maxKey() {
    return this.keys[this.keys.length - 1];
  }
  minKey() {
    return this.keys[0];
  }
  clone() {
    return new BNode(this.keys.slice(0));
  }
  get(key, tree) {
    const i = indexOf(key, this.keys, -1, tree.comparator);
    return i < 0 ? void 0 : this.keys[i];
  }
  has(key, tree) {
    const i = indexOf(key, this.keys, -1, tree.comparator);
    return i >= 0 && i < this.keys.length;
  }
  set(key, tree) {
    let i = indexOf(key, this.keys, -1, tree.comparator);
    if (i < 0) {
      i = ~i;
      tree.size++;
      if (this.keys.length < MAX_NODE_SIZE) {
        this.keys.splice(i, 0, key);
        return null;
      }
      const newRightSibling = this.splitOffRightSide();
      let target = this;
      if (i > this.keys.length) {
        i -= this.keys.length;
        target = newRightSibling;
      }
      target.keys.splice(i, 0, key);
      return newRightSibling;
    }
    this.keys[i] = key;
    return null;
  }
  takeFromRight(rhs) {
    this.keys.push(rhs.keys.shift());
  }
  takeFromLeft(lhs) {
    this.keys.unshift(lhs.keys.pop());
  }
  splitOffRightSide() {
    const half = this.keys.length >> 1;
    const keys = this.keys.splice(half);
    return new BNode(keys);
  }
  delete(key, tree) {
    const cmp = tree.comparator;
    const iLow = indexOf(key, this.keys, -1, cmp);
    const iHigh = iLow + 1;
    if (iLow < 0) {
      return false;
    }
    const { keys } = this;
    for (let i = iLow; i < iHigh; i++) {
      const key2 = keys[i];
      if (key2 !== keys[i] || this.isShared === true) {
        throw new Error("BTree illegally changed or cloned in delete");
      }
      this.keys.splice(i, 1);
      tree.size--;
      return true;
    }
    return false;
  }
  mergeSibling(rhs, _) {
    this.keys.push(...rhs.keys);
  }
}
class BNodeInternal extends BNode {
  // Note: conventionally B+ trees have one fewer key than the number of
  // children, but I find it easier to keep the array lengths equal: each
  // keys[i] caches the value of children[i].maxKey().
  children;
  /**
   * This does not mark `children` as shared, so it is the responsibility of the caller
   * to ensure children are either marked shared, or aren't included in another tree.
   */
  constructor(children, keys) {
    if (!keys) {
      keys = [];
      for (let i = 0; i < children.length; i++) {
        keys[i] = children[i].maxKey();
      }
    }
    super(keys);
    this.children = children;
  }
  isInternal() {
    return true;
  }
  clone() {
    const children = this.children.slice(0);
    for (let i = 0; i < children.length; i++) {
      children[i].isShared = true;
    }
    return new BNodeInternal(children, this.keys.slice(0));
  }
  minKey() {
    return this.children[0].minKey();
  }
  get(key, tree) {
    const i = indexOf(key, this.keys, 0, tree.comparator);
    const { children } = this;
    return i < children.length ? children[i].get(key, tree) : void 0;
  }
  has(key, tree) {
    const i = indexOf(key, this.keys, 0, tree.comparator);
    const { children } = this;
    return i < children.length ? children[i].has(key, tree) : false;
  }
  set(key, tree) {
    const c = this.children;
    const cmp = tree.comparator;
    let i = Math.min(indexOf(key, this.keys, 0, cmp), c.length - 1);
    let child = c[i];
    if (child.isShared) {
      c[i] = child = child.clone();
    }
    if (child.keys.length >= MAX_NODE_SIZE) {
      let other;
      if (i > 0 && (other = c[i - 1]).keys.length < MAX_NODE_SIZE && cmp(child.keys[0], key) < 0) {
        if (other.isShared) {
          c[i - 1] = other = other.clone();
        }
        other.takeFromRight(child);
        this.keys[i - 1] = other.maxKey();
      } else if ((other = c[i + 1]) !== void 0 && other.keys.length < MAX_NODE_SIZE && cmp(child.maxKey(), key) < 0) {
        if (other.isShared) c[i + 1] = other = other.clone();
        other.takeFromLeft(child);
        this.keys[i] = c[i].maxKey();
      }
    }
    const result = child.set(key, tree);
    this.keys[i] = child.maxKey();
    if (result === null) return null;
    if (this.keys.length < MAX_NODE_SIZE) {
      this.insert(i + 1, result);
      return null;
    }
    const newRightSibling = this.splitOffRightSide();
    let target = this;
    if (cmp(result.maxKey(), this.maxKey()) > 0) {
      target = newRightSibling;
      i -= this.keys.length;
    }
    target.insert(i + 1, result);
    return newRightSibling;
  }
  /**
   * Inserts `child` at index `i`.
   * This does not mark `child` as shared, so it is the responsibility of the caller
   * to ensure that either child is marked shared, or it is not included in another tree.
   */
  insert(i, child) {
    this.children.splice(i, 0, child);
    this.keys.splice(i, 0, child.maxKey());
  }
  /**
   * Split this node.
   * Modifies this to remove the second half of the items, returning a separate node containing them.
   */
  splitOffRightSide() {
    const half = this.children.length >> 1;
    return new BNodeInternal(
      this.children.splice(half),
      this.keys.splice(half)
    );
  }
  takeFromRight(rhs) {
    this.keys.push(rhs.keys.shift());
    this.children.push(rhs.children.shift());
  }
  takeFromLeft(lhs) {
    this.keys.unshift(lhs.keys.pop());
    this.children.unshift(lhs.children.pop());
  }
  delete(key, tree) {
    const cmp = tree.comparator;
    const { keys } = this;
    const { children } = this;
    let iLow = indexOf(key, this.keys, 0, cmp);
    let i = iLow;
    const iHigh = Math.min(iLow, keys.length - 1);
    if (i <= iHigh) {
      try {
        if (children[i].isShared) {
          children[i] = children[i].clone();
        }
        const result = children[i].delete(key, tree);
        keys[i] = children[i].maxKey();
        return result;
      } finally {
        const half = MAX_NODE_SIZE >> 1;
        if (iLow > 0) iLow--;
        for (i = iHigh; i >= iLow; i--) {
          if (children[i].keys.length <= half) {
            if (children[i].keys.length !== 0) {
              this.tryMerge(i, MAX_NODE_SIZE);
            } else {
              keys.splice(i, 1);
              children.splice(i, 1);
            }
          }
        }
      }
    }
    return false;
  }
  /** Merges child i with child i+1 if their combined size is not too large */
  tryMerge(i, maxSize) {
    const { children } = this;
    if (i >= 0 && i + 1 < children.length) {
      if (children[i].keys.length + children[i + 1].keys.length <= maxSize) {
        if (children[i].isShared)
          children[i] = children[i].clone();
        children[i].mergeSibling(children[i + 1], maxSize);
        children.splice(i + 1, 1);
        this.keys.splice(i + 1, 1);
        this.keys[i] = children[i].maxKey();
        return true;
      }
    }
    return false;
  }
  /**
   * Move children from `rhs` into this.
   * `rhs` must be part of this tree, and be removed from it after this call
   * (otherwise isShared for its children could be incorrect).
   */
  mergeSibling(rhs, maxNodeSize) {
    const oldLength = this.keys.length;
    this.keys.push(...rhs.keys);
    const rhsChildren = rhs.children;
    this.children.push(...rhsChildren);
    if (rhs.isShared && !this.isShared) {
      for (let i = 0; i < rhsChildren.length; i++) {
        rhsChildren[i].isShared = true;
      }
    }
    this.tryMerge(oldLength - 1, maxNodeSize);
  }
}
function indexOf(key, keys, failXor, comparator) {
  let lo = 0;
  let hi = keys.length;
  let mid = hi >> 1;
  while (lo < hi) {
    const c = comparator(keys[mid], key);
    if (c < 0) {
      lo = mid + 1;
    } else if (c > 0) {
      hi = mid;
    } else if (c === 0) {
      return mid;
    } else {
      if (key === key) {
        return keys.length;
      }
      throw new Error("NaN was used as a key");
    }
    mid = lo + hi >> 1;
  }
  return mid ^ failXor;
}
const emptyLeaf = new BNode([]);
emptyLeaf.isShared = true;
export {
  BTreeSet
};
//# sourceMappingURL=btree-set.js.map
