export class CondensedGraphIndices {
  constructor(graph, options = {}) {
    this.graph = graph;
    this._relationMetadata = options.relationMetadata || null;
    this.outgoingEdges = new Map();
    this.incomingEdges = new Map();
    this._relationsByRelId = new Map();
    this._relationsBySrcRel = new Map();
    this._relationsByDstRel = new Map();
    this._directBySrcRelDst = new Map();
    this._relations = [];
    this._builtAt = Date.now();

    this._build();
  }

  clear() {
    this._relationsByRelId.clear();
    this._relationsBySrcRel.clear();
    this._relationsByDstRel.clear();
    this._directBySrcRelDst.clear();
    this.outgoingEdges.clear();
    this.incomingEdges.clear();
    this._relations.length = 0;
  }

  buildIndices() {
    // No-op for snapshot indices.
  }

  _build() {
    this.clear();
    const edgeCount = this.graph.edgeIndex;
    const adjacency = this.graph.adjacency;
    const srcIds = this.graph.edgeSrcIds;
    const relIds = this.graph.edgeRelIds;
    const dstIds = this.graph.edgeDstIds;
    const possBits = this.graph.edgePossibilityBits;
    const relBits = this.graph.edgeReliabilityBits;
    const values = this.graph.values;
    const builtAt = this._builtAt;

    for (let edgeIdx = 0; edgeIdx < edgeCount; edgeIdx++) {
      let srcId;
      let relId;
      let dstId;
      let possibility;
      let reliability;

      if (adjacency) {
        const baseIdx = edgeIdx * 6;
        srcId = adjacency[baseIdx + 0];
        relId = adjacency[baseIdx + 1];
        dstId = adjacency[baseIdx + 2];
        possibility = this.graph._bitsToFloat(adjacency[baseIdx + 3]);
        reliability = this.graph._bitsToFloat(adjacency[baseIdx + 4]);
      } else if (srcIds) {
        srcId = srcIds[edgeIdx];
        relId = relIds[edgeIdx];
        dstId = dstIds[edgeIdx];
        possibility = this.graph._bitsToFloat(possBits[edgeIdx]);
        reliability = this.graph._bitsToFloat(relBits[edgeIdx]);
      } else {
        throw new Error('Adjacency storage missing and edge arrays not available.');
      }
      const value = values[edgeIdx];
      const relName = this.graph.getRelationName(relId);

      const relation = {
        src: srcId,
        rel: relName,
        dst: dstId,
        possibility,
        reliability,
        value: Number.isNaN(value) ? undefined : value,
        updated_last_at: builtAt,
        changed_last_at: builtAt,
        stateId: `snapshot-${edgeIdx}`
      };

      // Merge per-relation metadata (validity, decay config) so the
      // restored indices are lossless, not just the snapshot access layer.
      const meta = this._relationMetadata ? this._relationMetadata[`${srcId}|${relName}|${dstId}`] : null;
      if (meta) {
        if (meta.validity !== undefined) relation.validity = meta.validity;
        if (meta.decayConfig !== undefined) relation.decayConfig = meta.decayConfig;
      }

      this._relations[edgeIdx] = relation;

      const relSet = this._relationsByRelId.get(relId);
      if (relSet) relSet.push(edgeIdx);
      else this._relationsByRelId.set(relId, [edgeIdx]);

      const srcKey = `${srcId}|${relId}`;
      const srcList = this._relationsBySrcRel.get(srcKey);
      if (srcList) srcList.push(edgeIdx);
      else this._relationsBySrcRel.set(srcKey, [edgeIdx]);

      const dstKey = `${dstId}|${relId}`;
      const dstList = this._relationsByDstRel.get(dstKey);
      if (dstList) dstList.push(edgeIdx);
      else this._relationsByDstRel.set(dstKey, [edgeIdx]);

      this._directBySrcRelDst.set(`${srcId}|${relId}|${dstId}`, edgeIdx);

      let outgoing = this.outgoingEdges.get(srcId);
      if (!outgoing) {
        outgoing = [];
        this.outgoingEdges.set(srcId, outgoing);
      }
      outgoing.push(dstId);

      let incoming = this.incomingEdges.get(dstId);
      if (!incoming) {
        incoming = [];
        this.incomingEdges.set(dstId, incoming);
      }
      incoming.push(srcId);
    }
  }

  getDirectRelation(srcId, relation, dstId) {
    const relId = this.graph.getExistingRelationId(relation);
    if (relId === undefined) return null;
    const edgeIdx = this._directBySrcRelDst.get(`${srcId}|${relId}|${dstId}`);
    if (edgeIdx === undefined) return null;
    return this._relations[edgeIdx] || null;
  }

  getRelationsFromSrc(srcId, relation) {
    const relId = this.graph.getExistingRelationId(relation);
    if (relId === undefined) return [];
    const edgeList = this._relationsBySrcRel.get(`${srcId}|${relId}`);
    if (!edgeList || edgeList.length === 0) return [];
    const result = new Array(edgeList.length);
    for (let i = 0; i < edgeList.length; i++) {
      result[i] = this._relations[edgeList[i]];
    }
    return result;
  }

  getRelationsToDst(dstId, relation) {
    const relId = this.graph.getExistingRelationId(relation);
    if (relId === undefined) return [];
    const edgeList = this._relationsByDstRel.get(`${dstId}|${relId}`);
    if (!edgeList || edgeList.length === 0) return [];
    const result = new Array(edgeList.length);
    for (let i = 0; i < edgeList.length; i++) {
      result[i] = this._relations[edgeList[i]];
    }
    return result;
  }

  getRelationsByName(relation) {
    const relId = this.graph.getExistingRelationId(relation);
    if (relId === undefined) return [];
    const edgeList = this._relationsByRelId.get(relId);
    if (!edgeList || edgeList.length === 0) return [];
    const result = new Array(edgeList.length);
    for (let i = 0; i < edgeList.length; i++) {
      result[i] = this._relations[edgeList[i]];
    }
    return result;
  }
}
