import { CompressedBitvector } from './succinct/CompressedBitvector.js';
import { WaveletTree } from './succinct/WaveletTree.js';

class WaveletAdjacency {
  constructor(numNodes, waveletTree, boundaryBitvector, edgeIndices) {
    this.numNodes = numNodes;
    this.waveletTree = waveletTree;
    this.boundaryBitvector = boundaryBitvector;
    this.edgeIndices = edgeIndices;
    this.length = edgeIndices.length;
  }

  static build(numNodes, dstsBySrc, edgeIdxBySrc) {
    const totalNodes = numNodes || 0;
    const boundaryBits = [];
    const dstSequence = [];
    const edgeSequence = [];

    let position = 0;
    for (let srcId = 0; srcId < totalNodes; srcId++) {
      boundaryBits.push(1);
      position += 1;

      const dsts = dstsBySrc[srcId];
      const edges = edgeIdxBySrc[srcId];
      if (dsts && dsts.length) {
        for (let i = 0; i < dsts.length; i++) {
          dstSequence.push(dsts[i]);
          edgeSequence.push(edges[i]);
          boundaryBits.push(0);
          position += 1;
        }
      }
    }

    boundaryBits.push(1);
    const boundaryBitvector = new CompressedBitvector(boundaryBits);
    const dstArray = new Uint32Array(dstSequence);
    const edgeArray = new Int32Array(edgeSequence);
    const waveletTree = dstArray.length ? new WaveletTree(dstArray, totalNodes) : null;

    return new WaveletAdjacency(totalNodes, waveletTree, boundaryBitvector, edgeArray);
  }

  _rangeForSource(srcId) {
    if (srcId < 0 || srcId >= this.numNodes) return null;
    const startPos = this.boundaryBitvector.select1(srcId + 1);
    const endPos = this.boundaryBitvector.select1(srcId + 2);
    if (startPos === -1 || endPos === -1) return null;
    const start = this.boundaryBitvector.rank0(startPos);
    const end = this.boundaryBitvector.rank0(endPos);
    if (start < 0 || end < start) return null;
    return { start, end };
  }

  hasEdge(srcId, dstId) {
    if (!this.waveletTree) return false;
    const range = this._rangeForSource(srcId);
    if (!range) return false;
    if (range.start >= range.end) return false;

    const before = this.waveletTree.rank(dstId, range.start - 1);
    const after = this.waveletTree.rank(dstId, range.end - 1);
    return after > before;
  }

  findEdge(srcId, dstId) {
    if (!this.waveletTree) return null;
    const range = this._rangeForSource(srcId);
    if (!range) return null;
    if (range.start >= range.end) return null;

    const before = this.waveletTree.rank(dstId, range.start - 1);
    const after = this.waveletTree.rank(dstId, range.end - 1);
    if (after <= before) return null;

    const pos = this.waveletTree.select(dstId, before + 1);
    if (pos < range.start || pos >= range.end) return null;
    return this.edgeIndices[pos];
  }

  getEdgesForSource(srcId) {
    const range = this._rangeForSource(srcId);
    if (!range) return [];
    const count = range.end - range.start;
    if (count <= 0) return [];
    const result = new Array(count);
    for (let i = 0; i < count; i++) {
      result[i] = this.edgeIndices[range.start + i];
    }
    return result;
  }

  forEachEdgeForSource(srcId, callback) {
    const range = this._rangeForSource(srcId);
    if (!range) return;
    for (let i = range.start; i < range.end; i++) {
      callback(this.edgeIndices[i], i - range.start);
    }
  }

  firstEdge(srcId) {
    const range = this._rangeForSource(srcId);
    if (!range) return null;
    if (range.start >= range.end) return null;
    return this.edgeIndices[range.start];
  }

  getMemoryUsage() {
    const boundaryBytes = this.boundaryBitvector ? this.boundaryBitvector.getMemoryUsage() : 0;
    const waveletBytes = this.waveletTree ? this.waveletTree.getMemoryUsage() : 0;
    const edgeBytes = this.edgeIndices ? this.edgeIndices.byteLength : 0;
    return boundaryBytes + waveletBytes + edgeBytes;
  }
}

export { WaveletAdjacency };
