class RankSelectStructure {
  constructor(bitvector, length, blockSize = 512, blockRank = null) {
    this.bitvector = bitvector;
    this.length = length;
    this.blockSize = blockSize;
    this.numBlocks = Math.ceil(length / blockSize);
    this.blockRank = blockRank || new Uint32Array(this.numBlocks + 1);

    if (!blockRank) {
      this._buildRankStructures();
    }
  }

  static fromPacked(bitvector, length, blockSize, blockRank) {
    return new RankSelectStructure(bitvector, length, blockSize, blockRank);
  }

  _buildRankStructures() {
    let rank = 0;
    for (let block = 0; block < this.numBlocks; block++) {
      this.blockRank[block] = rank;
      const start = block * this.blockSize;
      const end = Math.min(start + this.blockSize, this.length);
      for (let i = start; i < end; i++) {
        const wordIndex = i >>> 5;
        const bitIndex = i & 31;
        const bit = (this.bitvector[wordIndex] >>> bitIndex) & 1;
        rank += bit;
      }
    }
    this.blockRank[this.numBlocks] = rank;
  }

  access(i) {
    if (i < 0 || i >= this.length) {
      throw new Error(`Index ${i} out of bounds [0, ${this.length})`);
    }
    const wordIndex = i >>> 5;
    const bitIndex = i & 31;
    return (this.bitvector[wordIndex] >>> bitIndex) & 1;
  }

  rank1(i) {
    if (i < 0) return 0;
    if (i >= this.length) i = this.length - 1;

    const block = (i / this.blockSize) | 0;
    let rank = this.blockRank[block];
    const start = block * this.blockSize;
    rank += this._popcountRange(start, i);
    return rank;
  }

  rank0(i) {
    if (i < 0) return 0;
    if (i >= this.length) i = this.length - 1;
    return (i + 1) - this.rank1(i);
  }

  select1(k) {
    if (k < 1) return -1;
    const total = this.blockRank[this.numBlocks];
    if (k > total) return -1;

    let left = 0;
    let right = this.length - 1;
    while (left < right) {
      const mid = (left + right) >> 1;
      if (this.rank1(mid) < k) {
        left = mid + 1;
      } else {
        right = mid;
      }
    }
    return left;
  }

  select0(k) {
    if (k < 1) return -1;
    const total = this.length - this.blockRank[this.numBlocks];
    if (k > total) return -1;

    let left = 0;
    let right = this.length - 1;
    while (left < right) {
      const mid = (left + right) >> 1;
      if (this.rank0(mid) < k) {
        left = mid + 1;
      } else {
        right = mid;
      }
    }
    return left;
  }

  getMemoryUsage() {
    return this.bitvector.byteLength + this.blockRank.byteLength;
  }

  _popcount32(value) {
    value -= (value >>> 1) & 0x55555555;
    value = (value & 0x33333333) + ((value >>> 2) & 0x33333333);
    return (((value + (value >>> 4)) & 0x0F0F0F0F) * 0x01010101) >>> 24;
  }

  _popcountRange(start, end) {
    let count = 0;
    let i = start;
    while (i <= end) {
      const wordIndex = i >>> 5;
      const bitIndex = i & 31;
      const remaining = end - i + 1;
      if (bitIndex === 0 && remaining >= 32) {
        count += this._popcount32(this.bitvector[wordIndex]);
        i += 32;
        continue;
      }

      const take = Math.min(32 - bitIndex, remaining);
      let mask = 0;
      if (take === 32) {
        mask = 0xFFFFFFFF;
      } else {
        mask = ((1 << take) - 1) << bitIndex;
      }
      count += this._popcount32(this.bitvector[wordIndex] & mask);
      i += take;
    }
    return count;
  }
}

export { RankSelectStructure };
