/**
 * data-structure-typed
 *
 * @author Pablo Zeng
 * @copyright Copyright (c) 2022 Pablo Zeng <zrwusa@gmail.com>
 * @license MIT License
 */

import type {
  BTNRep,
  CRUD,
  EntryCallback,
  FamilyPosition, NodePredicate,
  OptNode,
  RBTNColor,
  IterationType,
  RedBlackTreeOptions
} from '../../types';
import { BST } from './bst';
import { IBinaryTree } from '../../interfaces';

export class RedBlackTreeNode<K = any, V = any> {
  key: K;
  value?: V;
  parent?: RedBlackTreeNode<K, V> = undefined;

  /**
   * 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: K, value?: V, color: RBTNColor = 'BLACK') {
    this.key = key;
    this.value = value;
    this.color = color;
  }

  _left?: RedBlackTreeNode<K, V> | null | undefined = undefined;

  /**
   * Get the left child pointer.
   * @remarks Time O(1), Space O(1)
   * @returns Left child node, or null/undefined.
   */

  get left(): RedBlackTreeNode<K, V> | null | undefined {
    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: RedBlackTreeNode<K, V> | null | undefined) {
    if (v) {
      v.parent = this;
    }
    this._left = v;
  }

  _right?: RedBlackTreeNode<K, V> | null | undefined = undefined;

  /**
   * Get the right child pointer.
   * @remarks Time O(1), Space O(1)
   * @returns Right child node, or null/undefined.
   */

  get right(): RedBlackTreeNode<K, V> | null | undefined {
    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: RedBlackTreeNode<K, V> | null | undefined) {
    if (v) {
      v.parent = this;
    }
    this._right = v;
  }

  _height: number = 0;

  /**
   * 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(): number {
    return this._height;
  }

  /* istanbul ignore next -- covered by AVLTree tests (subclass uses height) */
  set height(value: number) {
    this._height = value;
  }

  _color: RBTNColor = 'BLACK';

  /**
   * 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(): RBTNColor {
    return this._color;
  }

  /**
   * Sets the color of the node.
   * @remarks Time O(1), Space O(1)
   *
   * @param value - The new color.
   */
  set color(value: RBTNColor) {
    this._color = value;
  }

  _count: number = 1;

  /**
   * 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(): number {
    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(): 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';
    }

    /* istanbul ignore next -- defensive: unreachable if tree structure is correct */
    return 'MAL_NODE';
  }
}

/**
 * Represents a Red-Black Tree (self-balancing BST) supporting map-like mode and stable O(log n) updates.
 * @remarks Operation complexity depends on the method; see each method's docs.
 * @template K
 * @template V
 * @template R
 * 1. Efficient self-balancing, but not completely balanced. Compared with AVLTree, the addition and deletion efficiency is high, but the query efficiency is slightly lower.
 * 2. It is BST itself. Compared with Heap which is not completely ordered, RedBlackTree is completely ordered.
 *
 * @example
 * // Red-Black Tree with key-value pairs for lookups
 *  interface Employee {
 *       id: number;
 *       name: string;
 *     }
 *
 *     // Create tree with employee data
 *     const employees = new RedBlackTree<number, Employee>([
 *       [1, { id: 1, name: 'Alice' }],
 *       [3, { id: 3, name: 'Charlie' }],
 *       [2, { id: 2, name: 'Bob' }]
 *     ]);
 *
 *     // Retrieve employee by ID
 *     const alice = employees.get(1);
 *     console.log(alice?.name); // 'Alice';
 *
 *     // Verify sorted order by ID
 *     console.log([...employees.keys()]); // [1, 2, 3];
 * @example
 * // Red-Black Tree range search for filtering
 *  interface Product {
 *       name: string;
 *       price: number;
 *     }
 *
 *     const products = new RedBlackTree<number, Product>([
 *       [10, { name: 'Item A', price: 10 }],
 *       [25, { name: 'Item B', price: 25 }],
 *       [40, { name: 'Item C', price: 40 }],
 *       [50, { name: 'Item D', price: 50 }]
 *     ]);
 *
 *     // Find products in price range [20, 45]
 *     const pricesInRange = products.rangeSearch([20, 45], node => {
 *       return products.get(node)?.name;
 *     });
 *
 *     console.log(pricesInRange); // ['Item B', 'Item C'];
 * @example
 * // Red-Black Tree as database index for stock market data
 *  interface StockPrice {
 *       symbol: string;
 *       volume: number;
 *       timestamp: Date;
 *     }
 *
 *     // Simulate real-time stock price index
 *     const priceIndex = new RedBlackTree<number, StockPrice>([
 *       [142.5, { symbol: 'AAPL', volume: 1000000, timestamp: new Date() }],
 *       [335.2, { symbol: 'MSFT', volume: 800000, timestamp: new Date() }],
 *       [3285.04, { symbol: 'AMZN', volume: 500000, timestamp: new Date() }],
 *       [267.98, { symbol: 'META', volume: 750000, timestamp: new Date() }],
 *       [234.57, { symbol: 'GOOGL', volume: 900000, timestamp: new Date() }]
 *     ]);
 *
 *     // Find highest-priced stock
 *     const maxPrice = priceIndex.getRightMost();
 *     console.log(priceIndex.get(maxPrice)?.symbol); // 'AMZN';
 *
 *     // Find stocks in price range [200, 400] for portfolio balancing
 *     const stocksInRange = priceIndex.rangeSearch([200, 400], node => {
 *       const stock = priceIndex.get(node);
 *       return {
 *         symbol: stock?.symbol,
 *         price: node,
 *         volume: stock?.volume
 *       };
 *     });
 *
 *     console.log(stocksInRange.length); // 3;
 *     console.log(stocksInRange.some((s: any) => s.symbol === 'GOOGL')); // true;
 *     console.log(stocksInRange.some((s: any) => s.symbol === 'META')); // true;
 *     console.log(stocksInRange.some((s: any) => s.symbol === 'MSFT')); // true;
 */

export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implements IBinaryTree<K, V, R> {
  constructor(
    keysNodesEntriesOrRaws: Iterable<
      K | RedBlackTreeNode<K, V> | [K | null | undefined, V | undefined] | null | undefined | R
    > = [],
    options?: RedBlackTreeOptions<K, V, R>
  ) {

    super([], options);

    this._root = this.NIL;

    // Not part of the actual tree; used only as an internal cache hub.
    this._header = new RedBlackTreeNode<K, V>(undefined as K, undefined, 'BLACK');
    this._header.parent = this.NIL;
    // Avoid using accessors here: they would set NIL.parent and can corrupt sentinel invariants.
    this._header._left = this.NIL;
    this._header._right = this.NIL;

    if (keysNodesEntriesOrRaws) {
      this.setMany(keysNodesEntriesOrRaws);
    }
  }

  protected override _root: RedBlackTreeNode<K, V> | undefined;

  /**
   * (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`.
   */
  protected _header: RedBlackTreeNode<K, V>;

  /**
   * (Internal) Cache of the current minimum and maximum nodes.
   * Used for fast-path insert/update when keys are monotonic or near-boundary.
   */
  protected _minNode: RedBlackTreeNode<K, V> | undefined;
  protected _maxNode: RedBlackTreeNode<K, V> | undefined;

  /**
   * Get the current root node.
   * @remarks Time O(1), Space O(1)
   * @returns Root node, or undefined.
   */
  override get root(): RedBlackTreeNode<K, V> | undefined {
    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.
   */
  override createNode(key: K, value?: V, color: RBTNColor = 'BLACK'): RedBlackTreeNode<K, V> {
    return new RedBlackTreeNode<K, V>(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.
   */

  override isNode(
    keyNodeOrEntry: K | RedBlackTreeNode<K, V> | [K | null | undefined, V | undefined] | null | undefined
  ): keyNodeOrEntry is RedBlackTreeNode<K, V> {
    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;
   */
  override clear() {
    super.clear();
    this._root = this.NIL;
    this._header.parent = this.NIL;
    this._setMinCache(undefined);
    this._setMaxCache(undefined);
  }


  /**
   * (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)
   */
  protected _findNodeByKey(key: K): RedBlackTreeNode<K, V> | undefined {
    const NIL = this.NIL;
    const cmp = this._compare.bind(this);

    let cur = (this._header.parent) ?? NIL;
    while (cur !== NIL) {
      const c = cmp(key, cur.key);
      if (c < 0) cur = cur.left ?? NIL;
      else if (c > 0) cur = cur.right ?? NIL;
      else return cur;
    }
    return undefined;
  }

  /**
   * (Internal) In-order predecessor of a node in a BST.
   * @remarks Time O(log n) average, Space O(1)
   */
  protected _predecessorOf(node: RedBlackTreeNode<K, V>): RedBlackTreeNode<K, V> | undefined {
    const NIL = this.NIL;
    if (node.left && node.left !== NIL) {
      let cur = node.left;
      while (cur.right && cur.right !== NIL) cur = cur.right;
      return cur;
    }
    let cur: RedBlackTreeNode<K, V> | undefined = 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)
   */
  protected _successorOf(node: RedBlackTreeNode<K, V>): RedBlackTreeNode<K, V> | undefined {
    const NIL = this.NIL;
    if (node.right && node.right !== NIL) {
      let cur = node.right;
      while (cur.left && cur.left !== NIL) cur = cur.left;
      return cur;
    }
    let cur: RedBlackTreeNode<K, V> | undefined = 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)
   */
  protected _attachNewNode(parent: RedBlackTreeNode<K, V>, side: 'left' | 'right', node: RedBlackTreeNode<K, V>): void {
    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)
   */
  protected _setMinCache(node: RedBlackTreeNode<K, V> | undefined): void {
    this._minNode = node;
    this._header._left = node ?? this.NIL;
  }

  /**
   * (Internal) Update max cache pointers (header._right is the canonical max pointer).
   * @remarks Time O(1), Space O(1)
   */
  protected _setMaxCache(node: RedBlackTreeNode<K, V> | undefined): void {
    this._maxNode = node;
    this._header._right = 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)
   */
  protected _setKVNode(key: K, nextValue?: V): { node: RedBlackTreeNode<K, V>; created: boolean } | undefined {
    const NIL = this.NIL;
    const comparator = this._comparator;

    // Read via header to avoid undefined checks (header uses NIL when empty).
    const header = this._header;
    const minN = header._left ?? NIL;
    if (minN !== NIL) {
      const cMin = comparator(key, minN.key);
      if (cMin === 0) {
        minN.value = nextValue as V;
        if (this._isMapMode) this._store.set(key, minN);
        return { node: minN, created: false };
      }
      // Boundary attach: if key is smaller than current min and min has no left child.
      // Inline NIL/null/undefined check to avoid isRealNode overhead on hot path.
      const minL = minN.left;
      if (cMin < 0 && (minL === NIL || minL === null || minL === undefined)) {
        const newNode = this.createNode(key, nextValue);
        this._attachNewNode(minN, 'left', newNode);
        if (this._isMapMode) this._store.set(newNode.key, newNode);
        this._size++;
        this._setMinCache(newNode);
        // If max is not initialized yet (tree had 0/1 nodes), mirror max too.
        if (header._right  === NIL) this._setMaxCache(newNode);
        return { node: newNode, created: true };
      }

      // Only touch max when key is not less than min.
      if (cMin > 0) {
        const maxN = header._right ?? NIL;
        // Boundary attach: if key is greater than current max and max has no right child.
        const cMax = comparator(key, maxN.key);
        if (cMax === 0) {
          maxN.value = nextValue as V;
          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 === undefined)) {
          const newNode = this.createNode(key, nextValue);
          this._attachNewNode(maxN, 'right', newNode);
          if (this._isMapMode) this._store.set(newNode.key, newNode);
          this._size++;
          this._setMaxCache(newNode);
          if (header._left === NIL) this._setMinCache(newNode);
          return { node: newNode, created: true };
        }
      }
    }

    // Normal path: single-pass search + insert/update (avoid double-walking the tree).
    const cmp = comparator;
    const isMapMode = this._isMapMode;
    const store = this._store;
    let current = this._header.parent ?? NIL;
    let parent: RedBlackTreeNode<K, V> | undefined;
    let lastCompared = 0;

    while (current !== NIL) {
      parent = current;
      lastCompared = cmp(key, current.key);
      if (lastCompared < 0) current = current.left ?? NIL;
      else if (lastCompared > 0) current = current.right ?? NIL;
      else {
        // Update existing.
        current.value = nextValue as V;
        if (isMapMode) store.set(key, current);
        return { node: current, created: false };
      }
    }

    // Insert new.
    const newNode = this.createNode(key, nextValue);
    // createNode always returns a real node in RedBlackTree.
    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 undefined;

    if (isMapMode) store.set(newNode.key, newNode);
    this._size++;

    // Maintain min/max caches on insertion (header.left/right are canonical).
    const hMin = this._header._left ?? NIL;
    const hMax = this._header._right ?? NIL;

    // Fast-path: empty tree or attaching directly to an extreme.
    if (hMin === NIL || hMax === NIL) {
      this._setMinCache(newNode);
      this._setMaxCache(newNode);
    } else /* istanbul ignore next -- boundary fast-paths at top of _setKVNode intercept boundary inserts before normal path */ if (parent === hMax && lastCompared > 0) {
      /* istanbul ignore next */ this._setMaxCache(newNode);
    } else /* istanbul ignore next */ if (parent === hMin && lastCompared < 0) {
      /* istanbul ignore next */ 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)
   */
  protected _setKV(key: K, nextValue?: V): boolean {
    if (this._isMapMode) {
      const store = this._store;
      const node = store.get(key);
      if (node) {
        node.value = nextValue as V;
        return true;
      }
    }

    return this._setKVNode(key, nextValue) !== undefined;
  }

  /**
   * 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: K, value: V, hint?: RedBlackTreeNode<K, V>): RedBlackTreeNode<K, V> | undefined {
    if (!hint || !this.isRealNode(hint)) {
      return this._setKVNode(key, value)?.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) {
      // Ultra-fast path: direct attach if the target slot is empty.
      if (!this.isRealNode(hint.left)) {
        const newNode = this.createNode(key, value);
        if (!this.isRealNode(newNode)) return undefined;
        this._attachNewNode(hint, 'left', newNode);
        if (this._isMapMode) this._store.set(key, newNode);
        this._size++;
        // Maintain header/min/max caches.
        const NIL = this.NIL;
        const hMin = this._header._left ?? NIL;
        if (hMin === NIL || this._compare(newNode.key, hMin.key) < 0) this._setMinCache(newNode);
        const hMax = this._header._right ?? 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 this._setKVNode(key, value)?.node;
      }

      // Try attach as right of pred.
      if (pred && !this.isRealNode(pred.right)) {
        const newNode = this.createNode(key, value);
        if (!this.isRealNode(newNode)) return undefined;
        this._attachNewNode(pred, 'right', newNode);
        if (this._isMapMode) this._store.set(key, newNode);
        this._size++;
        // Maintain header/min/max caches.
        const NIL = this.NIL;
        const hMin = this._header._left ?? NIL;
        if (hMin === NIL || this._compare(newNode.key, hMin.key) < 0) this._setMinCache(newNode);
        const hMax = this._header._right ?? NIL;
        if (hMax === NIL || this._compare(newNode.key, hMax.key) > 0) this._setMaxCache(newNode);
        return newNode;
      }

      /* istanbul ignore next -- structurally unreachable: predecessor never has a right child (it's the max of left subtree) */
      return this._setKVNode(key, value)?.node;
    }

    // c0 > 0
    // Ultra-fast path: direct attach if the target slot is empty.
    if (!this.isRealNode(hint.right)) {
      const newNode = this.createNode(key, value);
      if (!this.isRealNode(newNode)) return undefined;
      this._attachNewNode(hint, 'right', newNode);
      if (this._isMapMode) this._store.set(key, newNode);
      this._size++;
      // Maintain header/min/max caches.
      const NIL = this.NIL;
      const hMin = this._header._left ?? NIL;
      if (hMin === NIL || this._compare(newNode.key, hMin.key) < 0) this._setMinCache(newNode);
      const hMax = this._header._right ?? 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 this._setKVNode(key, value)?.node;
    }

    if (succ && !this.isRealNode(succ.left)) {
      const newNode = this.createNode(key, value);
      if (!this.isRealNode(newNode)) return undefined;
      this._attachNewNode(succ, 'left', newNode);
      if (this._isMapMode) this._store.set(key, newNode);
      this._size++;
      // Maintain header/min/max caches.
      const NIL = this.NIL;
      const hMin = this._header._left ?? NIL;
      if (hMin === NIL || this._compare(newNode.key, hMin.key) < 0) this._setMinCache(newNode);
      const hMax = this._header._right ?? NIL;
      if (hMax === NIL || this._compare(newNode.key, hMax.key) > 0) this._setMaxCache(newNode);
      return newNode;
    }

    /* istanbul ignore next -- structurally unreachable: successor never has a left child (it's the min of right subtree) */
    return this._setKVNode(key, value)?.node;
  }

  /**
   * Boolean wrapper for setWithHintNode.
   * @remarks Time O(log n) average, Space O(1)
   */
  setWithHint(key: K, value: V, hint?: RedBlackTreeNode<K, V>): boolean {
    return this.setWithHintNode(key, value, hint) !== undefined;
  }

  /**
   * 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;
   */
  override set(
    keyNodeOrEntry: K | RedBlackTreeNode<K, V> | [K | null | undefined, V | undefined] | null | undefined,
    value?: V
  ): boolean {
    // Common path: tree.set(key, value) or tree.set([key, value]).
    if (!this.isNode(keyNodeOrEntry)) {
      if (keyNodeOrEntry === null || keyNodeOrEntry === undefined) return false;

      if (this.isEntry(keyNodeOrEntry)) {
        const key = keyNodeOrEntry[0];
        if (key === null || key === undefined) return false;
        const nextValue = value ?? keyNodeOrEntry[1];
        return this._setKV(key, nextValue);
      }

      // key-only
      return this._setKV(keyNodeOrEntry, value);
    }

    // Node insertion path (advanced usage)
    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 as V;
          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 as V;
          this._store.set(n.key, n);
        }
      }
      return true;
    }
    /* istanbul ignore next -- defensive: _insert only returns CREATED|UPDATED */
    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;
   */
  override delete(
    keyNodeEntryRawOrPredicate: BTNRep<K, V, RedBlackTreeNode<K, V>> | NodePredicate<RedBlackTreeNode<K, V> | null>
  ): boolean {
    if (keyNodeEntryRawOrPredicate === null) return false;

    let nodeToDelete: OptNode<RedBlackTreeNode<K, V>>;
    if (this._isPredicate(keyNodeEntryRawOrPredicate)) nodeToDelete = this.getNode(keyNodeEntryRawOrPredicate);
    else nodeToDelete = this.isRealNode(keyNodeEntryRawOrPredicate) ? keyNodeEntryRawOrPredicate : this.getNode(keyNodeEntryRawOrPredicate);

    if (!nodeToDelete) {
      return false;
    }

    // Track min/max cache updates before structural modifications.
    const willDeleteMin = nodeToDelete === this._minNode;
    const willDeleteMax = nodeToDelete === this._maxNode;
    const nextMin = willDeleteMin ? this._successorOf(nodeToDelete) : undefined;
    const nextMax = willDeleteMax ? this._predecessorOf(nodeToDelete) : undefined;

    let originalColor = nodeToDelete.color;
    const NIL = this.NIL;
    let replacementNode: RedBlackTreeNode<K, V> = NIL;

    if (!this.isRealNode(nodeToDelete.left)) {
      // No real left child → replace with right (may be NIL)
      replacementNode = nodeToDelete.right ?? NIL;
      this._transplant(nodeToDelete, replacementNode);
    } else if (!this.isRealNode(nodeToDelete.right)) {
      // No real right child → replace with left
      replacementNode = nodeToDelete.left;
      this._transplant(nodeToDelete, replacementNode);
    } else {
      // Two children → find in-order successor
      const successor = this.getLeftMost(node => node, nodeToDelete.right);
      if (successor) {
        originalColor = successor.color;
        replacementNode = successor.right ?? NIL;

        if (successor.parent === nodeToDelete) {
          // Even if replacementNode is NIL, set its parent for fixup
          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--;

    // Update order-statistic counts from replacement up to root
    this._updateCountAlongPath(replacementNode?.parent as RedBlackTreeNode<K, V> | undefined ?? replacementNode);

    // Update min/max caches.
    if (this._size <= 0) {
      this._setMinCache(undefined);
      this._setMaxCache(undefined);
    } else {
      if (willDeleteMin) this._setMinCache(nextMin);
      if (willDeleteMax) this._setMaxCache(nextMax);
      // Fallback if successor/predecessor was unavailable.
      if (!this._minNode || !this.isRealNode(this._minNode)) {
        this._setMinCache(this.isRealNode(this._root) ? this.getLeftMost(n => n, this._root) : undefined);
      }
      if (!this._maxNode || !this.isRealNode(this._maxNode)) {
        this._setMaxCache(this.isRealNode(this._root) ? this.getRightMost(n => n, this._root) : undefined);
      }
    }

    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;
   */
  override perfectlyBalance(_iterationType?: IterationType): boolean {
    // Extract sorted entries, clear, re-insert — RBT self-balances on insert
    const entries: [K, V | undefined][] = [];
    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];
   */
  override map<MK = K, MV = V, MR = any>(
    callback: EntryCallback<K, V | undefined, [MK, MV]>,
    options?: Partial<RedBlackTreeOptions<MK, MV, MR>>,
    thisArg?: unknown
  ): RedBlackTree<MK, MV, MR> {
    const out = this._createLike<MK, MV, MR>([], 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)
   */
  protected override _createInstance<TK = K, TV = V, TR = R>(options?: Partial<RedBlackTreeOptions<TK, TV, TR>>): this {
    const Ctor = this.constructor as unknown as new (
      iter?: Iterable<TK | RedBlackTreeNode<TK, TV> | [TK | null | undefined, TV | undefined] | null | undefined | TR>,
      opts?: RedBlackTreeOptions<TK, TV, TR>
    ) => RedBlackTree<TK, TV, TR>;
    return new Ctor([], { ...this._snapshotOptions<TK, TV, TR>(), ...(options ?? {}) }) as unknown as this;
  }

  /**
   * (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)
   */
  protected override _createLike<TK = K, TV = V, TR = R>(
    iter: Iterable<
      TK | RedBlackTreeNode<TK, TV> | [TK | null | undefined, TV | undefined] | null | undefined | TR
    > = [],
    options?: Partial<RedBlackTreeOptions<TK, TV, TR>>
  ): RedBlackTree<TK, TV, TR> {
    const Ctor = this.constructor as unknown as new (
      iter?: Iterable<TK | RedBlackTreeNode<TK, TV> | [TK | null | undefined, TV | undefined] | null | undefined | TR>,
      opts?: RedBlackTreeOptions<TK, TV, TR>
    ) => RedBlackTree<TK, TV, TR>;
    return new Ctor(iter, { ...this._snapshotOptions<TK, TV, TR>(), ...(options ?? {}) });
  }

  /**
   * (Internal) Set the root pointer and keep header.parent in sync.
   * @remarks Time O(1), Space O(1)
   */
  protected override _setRoot(v: RedBlackTreeNode<K, V> | undefined) {
    const NIL = this.NIL;
    if (v) {
      v.parent = undefined;
    }
    this._root = v;
    // Keep header root pointer in sync even for internal operations (rotations/transplants)
    // and for subclasses that may bypass _setKVNode.
    this._header.parent = v ?? NIL;
  }

  /**
   * (Internal) Replace a node in place while preserving its color.
   * @remarks Time O(1) average, Space O(1)
   */
  protected override _replaceNode(
    oldNode: RedBlackTreeNode<K, V>,
    newNode: RedBlackTreeNode<K, V>
  ): RedBlackTreeNode<K, V> {
    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'.
   */
  protected _insert(node: RedBlackTreeNode<K, V>): CRUD {
    const NIL = this.NIL;
    const cmp = this._compare.bind(this);

    let current = this._header.parent ?? NIL;
    let parent: RedBlackTreeNode<K, V> | undefined;
    let lastCompared = 0;

    while (current !== NIL) {
      parent = current;
      lastCompared = cmp(node.key, current.key);
      if (lastCompared < 0) {
        current = current.left ?? NIL;
      } else if (lastCompared > 0) {
        current = current.right ?? 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';

    // Update counts along insertion path before fixup
    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
   */
  protected _transplant(u: RedBlackTreeNode<K, V>, v: RedBlackTreeNode<K, V> | undefined): void {
    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
   */
  protected _insertFixup(z: RedBlackTreeNode<K, V> | undefined): void {
    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?.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?.parent;
        const gp2 = p2?.parent;
        if (p2 && gp2) {
          p2.color = 'BLACK';
          gp2.color = 'RED';
          rightRotate(gp2);
        }
      } else {
        const y = gp.left;
        if (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?.parent;
        const gp2 = 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
   */
  protected _deleteFixup(node: RedBlackTreeNode<K, V> | undefined): void {
    // Standard CLRS RB-DELETE-FIXUP: restore black-height invariant.
    // `node` is the child that replaced the deleted node (may be NIL sentinel).
    // If RED → just recolor BLACK (trivial fix). If BLACK → double-black repair.
    if (!node) return;

    const NIL = this.NIL;
    let current: RedBlackTreeNode<K, V> = node;

    while (current !== this.root && current.color === 'BLACK') {
      const parent: RedBlackTreeNode<K, V> | undefined = current.parent;
      if (!parent) break;

      const nodeIsLeft = current === parent.left;
      let sibling = nodeIsLeft ? parent.right : parent.left;

      // Case 1: sibling is RED → rotate to get a BLACK sibling
      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?.left;
      const sibRight = sibling?.right;
      const sibLeftBlack = !sibLeft || sibLeft === NIL || sibLeft.color === 'BLACK';
      const sibRightBlack = !sibRight || sibRight === NIL || sibRight.color === 'BLACK';

      if (sibLeftBlack && sibRightBlack) {
        // Case 2: sibling's children are both BLACK → recolor sibling RED, move up
        if (sibling) sibling.color = 'RED';
        current = parent;
      } else {
        if (nodeIsLeft) {
          // Case 3: sibling's right child is BLACK → rotate sibling right first
          if (sibRightBlack) {
            if (sibLeft) sibLeft.color = 'BLACK';
            if (sibling) sibling.color = 'RED';
            if (sibling) this._rightRotate(sibling);
            sibling = parent.right;
          }
          // Case 4: sibling's right child is RED → final rotation
          if (sibling) sibling.color = parent.color;
          parent.color = 'BLACK';
          if (sibling?.right) sibling.right.color = 'BLACK';
          this._leftRotate(parent);
        } else {
          // Case 3 (mirror): sibling's left child is BLACK → rotate sibling left first
          if (sibLeftBlack) {
            if (sibRight) sibRight.color = 'BLACK';
            if (sibling) sibling.color = 'RED';
            if (sibling) this._leftRotate(sibling);
            sibling = parent.left;
          }
          // Case 4 (mirror): sibling's left child is RED → final rotation
          if (sibling) sibling.color = parent.color;
          parent.color = 'BLACK';
          if (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
   */
  protected _leftRotate(x: RedBlackTreeNode<K, V> | undefined): void {
    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;

    // Update counts: x first (now child), then y (now parent)
    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
   */
  protected _rightRotate(y: RedBlackTreeNode<K, V> | undefined): void {
    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;

    // Update counts: y first (now child), then x (now parent)
    this._updateCount(y);
    this._updateCount(x);
  }
}
