export class RelationCSR {
  constructor(manager) {
    this.manager = manager;
    const options = manager.arbiter?.options || {};
    this.enabled = !!options.useRelationCsrIndex;
    this.deltaThreshold = Number.isFinite(options.relationCsrDeltaThreshold)
      ? options.relationCsrDeltaThreshold
      : 1000;
    this.minDegree = Number.isFinite(options.relationCsrMinDegree)
      ? options.relationCsrMinDegree
      : 200;
    this.byName = new Map();
  }

  buildIndex(relation) {
    const relSet = this.manager.arbiter.indices?.relationsByRel?.get(relation);
    const relations = relSet ? Array.from(relSet) : [];
    const bySrcMap = new Map();
    const byDstMap = new Map();

    for (const rel of relations) {
      let srcList = bySrcMap.get(rel.src);
      if (!srcList) {
        srcList = [];
        bySrcMap.set(rel.src, srcList);
      }
      srcList.push(rel);

      let dstList = byDstMap.get(rel.dst);
      if (!dstList) {
        dstList = [];
        byDstMap.set(rel.dst, dstList);
      }
      dstList.push(rel);
    }

    const bySrcEntries = [];
    const bySrcOffsets = new Map();
    for (const [srcId, list] of bySrcMap.entries()) {
      const start = bySrcEntries.length;
      for (const rel of list) bySrcEntries.push(rel);
      bySrcOffsets.set(srcId, { start, end: bySrcEntries.length });
    }

    const byDstEntries = [];
    const byDstOffsets = new Map();
    for (const [dstId, list] of byDstMap.entries()) {
      const start = byDstEntries.length;
      for (const rel of list) byDstEntries.push(rel);
      byDstOffsets.set(dstId, { start, end: byDstEntries.length });
    }

    return {
      relation,
      bySrcEntries,
      bySrcOffsets,
      byDstEntries,
      byDstOffsets,
      addDeltaBySrc: new Map(),
      addDeltaByDst: new Map(),
      removeDelta: new Set(),
      deltaCount: 0
    };
  }

  getIndex(relation) {
    if (!this.enabled) return null;
    let csr = this.byName.get(relation);
    if (!csr || csr.deltaCount > this.deltaThreshold) {
      csr = this.buildIndex(relation);
      this.byName.set(relation, csr);
    }
    return csr;
  }

  recordAdd(relationObj) {
    if (!this.enabled) return;
    const csr = this.byName.get(relationObj.rel);
    if (!csr) return;
    const key = this.manager._makeRelationKey(relationObj.src, relationObj.rel, relationObj.dst);
    if (csr.removeDelta.has(key)) {
      csr.removeDelta.delete(key);
    }

    let srcList = csr.addDeltaBySrc.get(relationObj.src);
    if (!srcList) {
      srcList = [];
      csr.addDeltaBySrc.set(relationObj.src, srcList);
    }
    srcList.push(relationObj);

    let dstList = csr.addDeltaByDst.get(relationObj.dst);
    if (!dstList) {
      dstList = [];
      csr.addDeltaByDst.set(relationObj.dst, dstList);
    }
    dstList.push(relationObj);

    csr.deltaCount++;
  }

  recordRemove(srcId, relation, dstId) {
    if (!this.enabled) return;
    const csr = this.byName.get(relation);
    if (!csr) return;
    const key = this.manager._makeRelationKey(srcId, relation, dstId);
    csr.removeDelta.add(key);
    csr.deltaCount++;
  }

  getRelationsFromCsr(csr, nodeId, reverse = false) {
    const entries = reverse ? csr.byDstEntries : csr.bySrcEntries;
    const offsets = reverse ? csr.byDstOffsets : csr.bySrcOffsets;
    const deltaMap = reverse ? csr.addDeltaByDst : csr.addDeltaBySrc;
    const range = offsets.get(nodeId);
    const removeDelta = csr.removeDelta;

    const result = [];
    if (range) {
      for (let i = range.start; i < range.end; i++) {
        const rel = entries[i];
        const key = this.manager._makeRelationKey(rel.src, rel.rel, rel.dst);
        if (!removeDelta.has(key)) {
          result.push(rel);
        }
      }
    }

    const delta = deltaMap.get(nodeId);
    if (delta && delta.length) {
      for (const rel of delta) {
        const key = this.manager._makeRelationKey(rel.src, rel.rel, rel.dst);
        if (!removeDelta.has(key)) {
          result.push(rel);
        }
      }
    }

    return result;
  }

  getRelationsForNode(nodeId, relation, reverse = false) {
    if (!this.enabled) return null;
    const degree = this.manager._getRelationDegreeFromIndices(nodeId, relation, reverse);
    if (degree < this.minDegree) return null;
    const csr = this.getIndex(relation);
    if (!csr) return null;
    return this.getRelationsFromCsr(csr, nodeId, reverse);
  }

  clear() {
    this.byName.clear();
  }
}
