/**
 * Adaptive Quotient Filter Implementation
 * 
 * Based on "Adaptive Quotient Filters" by Richard Wen et al.
 * Provides strongly adaptive filtering with minimal overhead
 * 
 * Key features:
 * - Monotonically adaptive (never repeats false positives)
 * - Variable-length fingerprints that extend on false positives
 * - Support for add, delete, and counters
 * - Strong adaptivity guarantees even under adversarial workloads
 * - Based on quotient filter with Robin Hood hashing
 */

/**
 * Slot types in the AdaptiveQF
 */
const SLOT_TYPE = {
  REMAINDER: 'REMAINDER',
  EXTENSION: 'EXTENSION', 
  COUNTER: 'COUNTER'
}

/**
 * Metadata bits per slot (LEGACY constants — see per-instance `this._meta`).
 *
 * The slot layout is:
 *   [remainder (r bits)] [IS_OCCUPIED (1 bit)] [IS_RUNEND (1 bit)]
 *                      ^bit r                  ^bit r+1
 *   [count (8 bits)] [distance (8 bits)]
 *
 * The historical implementation stored IS_OCCUPIED at bit 0, which
 * overlapped with the remainder's lowest bit — corrupting the read-back
 * of every slot whose remainder had bit 0 set. The fix moves the
 * metadata bits above the remainder (matching the published AQF
 * specification and the standalone maplet implementation in
 * ~/Business/workspace/data_structures/AdaptiveQuotientMaplet/).
 *
 * The legacy exports are kept only as bit-position names so any external
 * code that imported METADATA_BITS still resolves. Each instance
 * computes its actual bit positions in the constructor.
 */
const METADATA_BITS = {
  IS_OCCUPIED: 'occupied',   // placeholder — see this._meta.occupiedBit
  IS_RUNEND: 'runend',       // placeholder — see this._meta.runendBit
  IS_EXTENSION: 'extension'  // placeholder — see this._meta.extensionBit
}

/**
 * Adaptive Quotient Filter
 * 
 * Implements a strongly adaptive filter that extends fingerprints
 * when false positives are detected, ensuring monotonically decreasing
 * false positive rate.
 */
export default class AdaptiveQuotientFilter {
  constructor(options = {}) {
    this.options = {
      capacity: options.capacity || 1024,
      remainderBits: options.remainderBits || 9,  // Target FPR = 2^-9 ≈ 0.2%
      extensionBits: options.extensionBits || 9,  // Bits per extension
      maxExtensions: options.maxExtensions || 10, // Max extensions per fingerprint
      loadFactor: options.loadFactor || 0.75,     // Resize when load factor exceeds this
      maxLoopIterations: options.maxLoopIterations || 100, // Resize when Robin Hood loop exceeds this
      maxCapacity: options.maxCapacity || 1024 * 1024, // Maximum capacity to prevent memory exhaustion
      ...options
    }
    
    // Calculate table size (must be power of 2)
    this.tableSize = Math.pow(2, Math.ceil(Math.log2(this.options.capacity)))
    this.quotientBits = Math.log2(this.tableSize)
    this.fingerprintBits = this.quotientBits + this.options.remainderBits

    // Per-instance metadata bit positions. The remainder occupies the
    // lower `remainderBits` of each slot word; metadata sits immediately
    // above it so it never collides with the stored remainder.
    // Layout: [remainder (r bits)] [OCCUPIED] [RUNEND] [count (8)] [distance (8)]
    this._meta = {
      occupiedBit: 1 << this.options.remainderBits,
      runendBit: 1 << (this.options.remainderBits + 1),
      extensionBit: 1 << (this.options.remainderBits + 2),
      countShift: this.options.remainderBits + 2,
      countMask: 0xff,
      distanceShift: this.options.remainderBits + 10,
      distanceMask: 0xff,
      remainderMask: (1 << this.options.remainderBits) - 1
    }

    // Main filter table: each slot is a 32-bit word holding remainder +
    // metadata + count + distance (see _meta above).
    this.slots = new Uint32Array(this.tableSize)
    this.slotBits = this.options.remainderBits + 2 // remainder + 2 metadata bits
    
    // Reverse map: maps (quotient, remainder) -> list of keys
    this.reverseMap = new Map()
    
    // Statistics
    this.itemCount = 0
    this.adaptationCount = 0
    this.falsePositiveCount = 0
    
    // Hash function (using simple hash for demo)
    this.hashSeed = Math.floor(Math.random() * 0xffffffff)
  }

  /**
   * Insert a key into the filter
   * @param {string} key - Key to insert
   * @param {number} count - Count (default 1)
   * @returns {boolean} True if resize is recommended due to excessive loop iterations
   */
  insert(key, count = 1) {
    const fingerprint = this._computeFingerprint(key)
    const quotient = this._extractQuotient(fingerprint)
    const remainder = this._extractRemainder(fingerprint)
    
    // Add to reverse map
    this._addToReverseMap(quotient, remainder, key)
    
    // Insert into filter and check if resize is needed
    const needsResize = this._insertFingerprintWithLoopTracking(quotient, remainder, count)
    
    this.itemCount += count
    return needsResize
  }

  /**
   * Delete a key from the filter
   * @param {string} key - Key to delete
   * @param {number} count - Count to delete (default 1)
   * @returns {boolean} True if key was deleted
   */
  delete(key, count = 1) {
    const fingerprint = this._computeFingerprint(key)
    const quotient = this._extractQuotient(fingerprint)
    const remainder = this._extractRemainder(fingerprint)
    
    const deleted = this._deleteFingerprint(quotient, remainder, count)
    if (deleted) {
      this._removeFromReverseMap(quotient, remainder, key, count)
      this.itemCount -= count
      return true
    }
    
    return false
  }

  /**
   * Query if key might be in the filter
   * @param {string} key - Key to query
   * @returns {boolean} True if key might be present
   */
  query(key) {
    const fingerprint = this._computeFingerprint(key)
    const quotient = this._extractQuotient(fingerprint)
    const remainder = this._extractRemainder(fingerprint)
    
    return this._queryFingerprint(quotient, remainder)
  }

  /**
   * Adapt to a false positive by extending the conflicting fingerprint
   * @param {string} falsePositiveKey - Key that caused false positive
   * @param {string} conflictingKey - Key that conflicts with false positive
   */
  adaptToFalsePositive(falsePositiveKey, conflictingKey) {
    const conflictingFingerprint = this._computeFingerprint(conflictingKey)
    const quotient = this._extractQuotient(conflictingFingerprint)
    const remainder = this._extractRemainder(conflictingFingerprint)
    
    // Find the slot containing the conflicting fingerprint
    const slotIndex = this._findFingerprintSlot(quotient, remainder)
    if (slotIndex === -1) {
      // If conflicting fingerprint not found, insert it first
      this._insertFingerprint(quotient, remainder, 1)
      const newSlotIndex = this._findFingerprintSlot(quotient, remainder)
      if (newSlotIndex === -1) {
        throw new Error('Failed to insert conflicting fingerprint')
      }
      
      // Extend the newly inserted fingerprint
      const extensionBits = this._computeExtensionBits(conflictingKey, falsePositiveKey)
      this._addExtension(newSlotIndex, extensionBits)
    } else {
      // Extend the existing fingerprint
      const extensionBits = this._computeExtensionBits(conflictingKey, falsePositiveKey)
      this._addExtension(slotIndex, extensionBits)
    }
    
    this.adaptationCount++
    this.falsePositiveCount++
  }

  /**
   * Merge another AdaptiveQF into this one
   * @param {AdaptiveQuotientFilter} other - Other filter to merge
   * @returns {AdaptiveQuotientFilter} New merged filter
   */
  static merge(filter1, filter2) {
    // Create new filter with combined capacity
    const mergedCapacity = Math.max(filter1.tableSize, filter2.tableSize) * 2
    const mergedFilter = new AdaptiveQuotientFilter({
      capacity: mergedCapacity,
      remainderBits: Math.max(filter1.options.remainderBits, filter2.options.remainderBits),
      extensionBits: Math.max(filter1.options.extensionBits, filter2.options.extensionBits),
      maxExtensions: Math.max(filter1.options.maxExtensions, filter2.options.maxExtensions)
    })
    
    // Merge all items from both filters
    const allItems = []
    
    // Collect items from filter1
    for (const [minirunId, keyList] of filter1.reverseMap) {
      for (const key of keyList) {
        allItems.push({ key, source: 'filter1' })
      }
    }
    
    // Collect items from filter2
    for (const [minirunId, keyList] of filter2.reverseMap) {
      for (const key of keyList) {
        allItems.push({ key, source: 'filter2' })
      }
    }
    
    // Insert all items into merged filter
    for (const item of allItems) {
      mergedFilter.insert(item.key)
    }
    
    // Merge adaptation information
    mergedFilter.adaptationCount = filter1.adaptationCount + filter2.adaptationCount
    mergedFilter.falsePositiveCount = filter1.falsePositiveCount + filter2.falsePositiveCount
    
    return mergedFilter
  }

  /**
   * Resize the filter to new capacity
   * @param {number} newCapacity - New capacity
   * @returns {AdaptiveQuotientFilter} New resized filter
   */
  resize(newCapacity) {
    // Create new filter with new capacity
    const newFilter = new AdaptiveQuotientFilter({
      capacity: newCapacity,
      remainderBits: this.options.remainderBits,
      extensionBits: this.options.extensionBits,
      maxExtensions: this.options.maxExtensions
    })
    
    // Re-insert all items
    for (const [minirunId, keyList] of this.reverseMap) {
      for (const key of keyList) {
        newFilter.insert(key)
      }
    }
    
    // Copy adaptation information
    newFilter.adaptationCount = this.adaptationCount
    newFilter.falsePositiveCount = this.falsePositiveCount
    
    return newFilter
  }

  /**
   * Check if filter needs resizing based on load factor
   * @returns {boolean} True if resize recommended
   */
  needsResize() {
    const loadFactor = this._countOccupiedSlots() / this.tableSize
    // Only resize when load factor exceeds threshold (growing)
    return loadFactor > this.options.loadFactor
  }

  /**
   * Get recommended new capacity for resize
   * @returns {number} Recommended capacity
   */
  getRecommendedCapacity() {
    const currentCapacity = this.tableSize
    const loadFactor = this._countOccupiedSlots() / this.tableSize
    
    if (loadFactor > this.options.loadFactor) {
      // Growing - double capacity
      return currentCapacity * 2
    }
    
    // Don't recommend shrinking - just return current capacity
    return currentCapacity
  }

  /**
   * Get statistics about the filter
   * @returns {Object} Filter statistics
   */
  getStats() {
    const totalSlots = this.tableSize
    const occupiedSlots = this._countOccupiedSlots()
    const extensionSlots = this._countExtensionSlots()
    
    const loadFactor = occupiedSlots / totalSlots
    const spaceEfficiency = this.itemCount / (occupiedSlots + extensionSlots)
    
    // Estimate false positive rate
    const baseFPR = Math.pow(2, -this.options.remainderBits)
    const adaptedFPR = baseFPR / Math.pow(2, this.adaptationCount)
    
    return {
      itemCount: this.itemCount,
      adaptationCount: this.adaptationCount,
      falsePositiveCount: this.falsePositiveCount,
      totalSlots: totalSlots,
      occupiedSlots: occupiedSlots,
      extensionSlots: extensionSlots,
      loadFactor: loadFactor,
      spaceEfficiency: spaceEfficiency,
      estimatedFPR: adaptedFPR,
      memoryBytes: this.slots.length * 4,
      reverseMapSize: this.reverseMap.size,
      needsResize: this.needsResize(),
      recommendedCapacity: this.getRecommendedCapacity()
    }
  }

  /**
   * Compute fingerprint for a key
   * @param {string} key - Key to hash
   * @returns {number} Fingerprint
   * @private
   */
  _computeFingerprint(key) {
    // Handle null/undefined keys
    if (!key) {
      return 0
    }
    
    // Use multiple hash functions for better distribution
    let hash = this.hashSeed
    for (let i = 0; i < key.length; i++) {
      hash = ((hash << 5) - hash + key.charCodeAt(i)) & 0xffffffff
    }
    
    // Ensure we have enough bits for the fingerprint
    const totalBits = this.fingerprintBits + this.options.maxExtensions * this.options.extensionBits
    return Math.abs(hash) % Math.pow(2, totalBits)
  }

  /**
   * Extract quotient from fingerprint
   * @param {number} fingerprint - Full fingerprint
   * @returns {number} Quotient
   * @private
   */
  _extractQuotient(fingerprint) {
    return (fingerprint >>> this.options.remainderBits) & ((1 << this.quotientBits) - 1)
  }

  /**
   * Extract remainder from fingerprint
   * @param {number} fingerprint - Full fingerprint
   * @returns {number} Remainder
   * @private
   */
  _extractRemainder(fingerprint) {
    return fingerprint & ((1 << this.options.remainderBits) - 1)
  }

  /**
   * Insert a fingerprint into the filter using Robin Hood hashing
   * @param {number} quotient - Quotient
   * @param {number} remainder - Remainder
   * @param {number} count - Count
   * @private
   */
  _insertFingerprint(quotient, remainder, count) {
    const canonicalSlot = quotient % this.tableSize
    
    // Check if fingerprint already exists
    let existingSlot = this._findFingerprintSlot(quotient, remainder)
    if (existingSlot !== -1) {
      // Update count for existing fingerprint
      const currentCount = this._getSlotCount(existingSlot)
      this._setSlotCount(existingSlot, currentCount + count)
      return
    }
    
    // Find insertion point using Robin Hood hashing
    let slotIndex = canonicalSlot
    let distance = 0
    
    while (this._isSlotOccupied(slotIndex)) {
      const slotDistance = this._getSlotDistance(slotIndex)
      if (distance > slotDistance) {
        // Robin Hood: steal from richer slot
        const stolen = this._swapSlotContents(slotIndex, remainder, count, distance)
        remainder = stolen.remainder
        count = stolen.count
        distance = stolen.distance
      }
      slotIndex = (slotIndex + 1) % this.tableSize
      distance++
    }
    
    // Insert into empty slot
    this._setSlotContents(slotIndex, remainder, count, distance)
  }

  /**
   * Insert a fingerprint into the filter with loop iteration tracking
   * @param {number} quotient - Quotient
   * @param {number} remainder - Remainder
   * @param {number} count - Count
   * @returns {boolean} True if resize is recommended due to excessive loop iterations
   * @private
   */
  _insertFingerprintWithLoopTracking(quotient, remainder, count) {
    const canonicalSlot = quotient % this.tableSize
    
    // Check if fingerprint already exists
    let existingSlot = this._findFingerprintSlot(quotient, remainder)
    if (existingSlot !== -1) {
      // Update count for existing fingerprint
      const currentCount = this._getSlotCount(existingSlot)
      this._setSlotCount(existingSlot, currentCount + count)
      return false
    }
    
    // Find insertion point using Robin Hood hashing
    let slotIndex = canonicalSlot
    let distance = 0
    let loopIterations = 0
    
    while (this._isSlotOccupied(slotIndex)) {
      loopIterations++
      
      // Check if we've exceeded the maximum loop iterations
      if (loopIterations > this.options.maxLoopIterations) {
        // Insert anyway but signal that resize is needed
        this._setSlotContents(slotIndex, remainder, count, distance)
        return true
      }
      
      const slotDistance = this._getSlotDistance(slotIndex)
      if (distance > slotDistance) {
        // Robin Hood: steal from richer slot
        const stolen = this._swapSlotContents(slotIndex, remainder, count, distance)
        remainder = stolen.remainder
        count = stolen.count
        distance = stolen.distance
      }
      slotIndex = (slotIndex + 1) % this.tableSize
      distance++
    }
    
    // Insert into empty slot
    this._setSlotContents(slotIndex, remainder, count, distance)
    return false
  }

  /**
   * Delete a fingerprint from the filter
   * @param {number} quotient - Quotient
   * @param {number} remainder - Remainder
   * @param {number} count - Count to delete
   * @returns {boolean} True if deleted
   * @private
   */
  _deleteFingerprint(quotient, remainder, count) {
    const canonicalSlot = quotient % this.tableSize
    let slotIndex = canonicalSlot
    
    // Find the fingerprint
    while (this._isSlotOccupied(slotIndex)) {
      if (this._getSlotRemainder(slotIndex) === remainder) {
        const currentCount = this._getSlotCount(slotIndex)
        if (currentCount >= count) {
          if (currentCount === count) {
            // Delete entire slot
            this._clearSlot(slotIndex)
            this._shiftSlotsBack(slotIndex)
          } else {
            // Decrease count
            this._setSlotCount(slotIndex, currentCount - count)
          }
          return true
        }
        return false
      }
      slotIndex = (slotIndex + 1) % this.tableSize
    }
    
    return false
  }

  /**
   * Query if fingerprint exists in filter
   * @param {number} quotient - Quotient
   * @param {number} remainder - Remainder
   * @returns {boolean} True if fingerprint exists
   * @private
   */
  _queryFingerprint(quotient, remainder) {
    const canonicalSlot = quotient % this.tableSize
    let slotIndex = canonicalSlot
    
    while (this._isSlotOccupied(slotIndex)) {
      if (this._getSlotRemainder(slotIndex) === remainder) {
        return true
      }
      slotIndex = (slotIndex + 1) % this.tableSize
    }
    
    return false
  }

  /**
   * Find slot containing specific fingerprint
   * @param {number} quotient - Quotient
   * @param {number} remainder - Remainder
   * @returns {number} Slot index or -1 if not found
   * @private
   */
  _findFingerprintSlot(quotient, remainder) {
    const canonicalSlot = quotient % this.tableSize
    let slotIndex = canonicalSlot
    
    while (this._isSlotOccupied(slotIndex)) {
      if (this._getSlotRemainder(slotIndex) === remainder) {
        return slotIndex
      }
      slotIndex = (slotIndex + 1) % this.tableSize
    }
    
    return -1
  }

  /**
   * Add extension bits to a fingerprint
   * @param {number} slotIndex - Slot index
   * @param {number} extensionBits - Extension bits to add
   * @private
   */
  _addExtension(slotIndex, extensionBits) {
    // Find next available slot for extension
    let extensionSlot = (slotIndex + 1) % this.tableSize
    while (this._isSlotOccupied(extensionSlot)) {
      extensionSlot = (extensionSlot + 1) % this.tableSize
    }
    
    // Set extension slot
    this._setSlotContents(extensionSlot, extensionBits, 0, 0)
    this._setSlotType(extensionSlot, SLOT_TYPE.EXTENSION)
  }

  /**
   * Compute extension bits to distinguish two keys
   * @param {string} key1 - First key
   * @param {string} key2 - Second key
   * @returns {number} Extension bits
   * @private
   */
  _computeExtensionBits(key1, key2) {
    // Find first differing bit and use it as extension
    const minLen = Math.min(key1.length, key2.length)
    for (let i = 0; i < minLen; i++) {
      if (key1[i] !== key2[i]) {
        return key1.charCodeAt(i) & ((1 << this.options.extensionBits) - 1)
      }
    }
    
    // If one is prefix of other, use length difference
    return Math.abs(key1.length - key2.length) & ((1 << this.options.extensionBits) - 1)
  }

  /**
   * Add key to reverse map
   * @param {number} quotient - Quotient
   * @param {number} remainder - Remainder
   * @param {string} key - Key
   * @private
   */
  _addToReverseMap(quotient, remainder, key) {
    const minirunId = `${quotient}:${remainder}`
    if (!this.reverseMap.has(minirunId)) {
      this.reverseMap.set(minirunId, [])
    }
    this.reverseMap.get(minirunId).push(key)
  }

  /**
   * Remove key from reverse map
   * @param {number} quotient - Quotient
   * @param {number} remainder - Remainder
   * @param {string} key - Key
   * @param {number} count - Count to remove
   * @private
   */
  _removeFromReverseMap(quotient, remainder, key, count) {
    const minirunId = `${quotient}:${remainder}`
    const keyList = this.reverseMap.get(minirunId)
    if (keyList) {
      for (let i = 0; i < count && i < keyList.length; i++) {
        const index = keyList.indexOf(key)
        if (index !== -1) {
          keyList.splice(index, 1)
        }
      }
      if (keyList.length === 0) {
        this.reverseMap.delete(minirunId)
      }
    }
  }

  /**
   * Check if slot is occupied
   * @param {number} slotIndex - Slot index
   * @returns {boolean} True if occupied
   * @private
   */
  _isSlotOccupied(slotIndex) {
    return !!(this.slots[slotIndex] & this._meta.occupiedBit)
  }

  /**
   * Get remainder from slot
   * @param {number} slotIndex - Slot index
   * @returns {number} Remainder
   * @private
   */
  _getSlotRemainder(slotIndex) {
    return this.slots[slotIndex] & this._meta.remainderMask
  }

  /**
   * Get count from slot
   * @param {number} slotIndex - Slot index
   * @returns {number} Count
   * @private
   */
  _getSlotCount(slotIndex) {
    // Count is stored in bits after remainder and metadata
    const countBits = (this.slots[slotIndex] >>> this._meta.countShift) & this._meta.countMask
    return countBits || 1
  }

  /**
   * Get distance from canonical slot
   * @param {number} slotIndex - Slot index
   * @returns {number} Distance
   * @private
   */
  _getSlotDistance(slotIndex) {
    // Distance is stored in bits after count
    return (this.slots[slotIndex] >>> this._meta.distanceShift) & this._meta.distanceMask
  }

  /**
   * Set slot contents
   * @param {number} slotIndex - Slot index
   * @param {number} remainder - Remainder
   * @param {number} count - Count
   * @param {number} distance - Distance from canonical slot
   * @private
   */
  _setSlotContents(slotIndex, remainder, count, distance) {
    const meta = this._meta

    // Clear slot first
    this.slots[slotIndex] = 0

    // Set remainder (lower bits) - mask to ensure it fits
    this.slots[slotIndex] |= (remainder & meta.remainderMask)

    // Set occupied bit (metadata sits above the remainder so it does
    // not collide with the stored remainder — this was the AQF-001 bug)
    this.slots[slotIndex] |= meta.occupiedBit

    // Set count (8 bits after remainder + metadata) - ensure count is at least 1
    const actualCount = Math.max(1, count)
    this.slots[slotIndex] |= (actualCount & meta.countMask) << meta.countShift

    // Set distance (8 bits after count)
    this.slots[slotIndex] |= (distance & meta.distanceMask) << meta.distanceShift
  }

  /**
   * Set slot count
   * @param {number} slotIndex - Slot index
   * @param {number} count - New count
   * @private
   */
  _setSlotCount(slotIndex, count) {
    const remainder = this._getSlotRemainder(slotIndex)
    const distance = this._getSlotDistance(slotIndex)
    this._setSlotContents(slotIndex, remainder, count, distance)
  }

  /**
   * Set slot type
   * @param {number} slotIndex - Slot index
   * @param {string} type - Slot type
   * @private
   */
  _setSlotType(slotIndex, type) {
    if (type === SLOT_TYPE.EXTENSION) {
      this.slots[slotIndex] |= this._meta.runendBit
    } else {
      this.slots[slotIndex] &= ~this._meta.runendBit
    }
  }

  /**
   * Clear slot
   * @param {number} slotIndex - Slot index
   * @private
   */
  _clearSlot(slotIndex) {
    this.slots[slotIndex] = 0
  }

  /**
   * Swap slot contents (for Robin Hood hashing)
   * @param {number} slotIndex - Slot index
   * @param {number} remainder - New remainder
   * @param {number} count - New count
   * @param {number} distance - New distance
   * @returns {Object} Old slot values
   * @private
   */
  _swapSlotContents(slotIndex, remainder, count, distance) {
    const oldRemainder = this._getSlotRemainder(slotIndex)
    const oldCount = this._getSlotCount(slotIndex)
    const oldDistance = this._getSlotDistance(slotIndex)
    
    this._setSlotContents(slotIndex, remainder, count, distance)
    
    // Return old values for caller to continue
    return { remainder: oldRemainder, count: oldCount, distance: oldDistance }
  }

  /**
   * Shift slots back after deletion
   * @param {number} deletedSlot - Deleted slot index
   * @private
   */
  _shiftSlotsBack(deletedSlot) {
    let currentSlot = deletedSlot
    let nextSlot = (currentSlot + 1) % this.tableSize
    
    while (this._isSlotOccupied(nextSlot)) {
      // Move slot forward
      this.slots[currentSlot] = this.slots[nextSlot]
      this._clearSlot(nextSlot)
      
      currentSlot = nextSlot
      nextSlot = (currentSlot + 1) % this.tableSize
    }
  }

  /**
   * Count occupied slots
   * @returns {number} Count of occupied slots
   * @private
   */
  _countOccupiedSlots() {
    let count = 0
    for (let i = 0; i < this.tableSize; i++) {
      if (this._isSlotOccupied(i)) {
        count++
      }
    }
    return count
  }

  /**
   * Count extension slots
   * @returns {number} Count of extension slots
   * @private
   */
  _countExtensionSlots() {
    let count = 0
    for (let i = 0; i < this.tableSize; i++) {
      if (this._isSlotOccupied(i) && (this.slots[i] & this._meta.runendBit)) {
        count++
      }
    }
    return count
  }
}

export { SLOT_TYPE, METADATA_BITS }