export class CondensedGraphRead {
  constructor(graph) {
    this.graph = graph;
  }

  getOutEdges(src) {
    const srcId = this.graph._ensureNode(src);
    if (srcId === -1) return [];

    if (this.graph._waveletAll) {
      return this.graph._waveletAll.getEdgesForSource(srcId);
    }
    if (this.graph._waveletByRelId) {
      throw new Error('getOutEdges requires relation-agnostic wavelet; use relation-specific access instead.');
    }

    this.graph._ensureAdjacencyListsForRead('getOutEdges');
    
    let edgeIdx = this.graph.nodeHeads[srcId];
    const edges = [];
    
    while (edgeIdx !== -1) {
      edges.push(edgeIdx);
      // Move to next edge in linked list
      edgeIdx = this.graph.adjacency[edgeIdx * 6 + 5];
    }
    
    return edges;
  }

  getOutEdgesByRel(src, rel) {
    const srcId = this.graph._ensureNode(src);
    if (srcId === -1) return [];
    const relId = typeof rel === 'string' ? this.graph.getRelationId(rel) : rel;

    if (this.graph._waveletByRelId) {
      const wavelet = this.graph._waveletByRelId[relId];
      if (!wavelet) return [];
      return wavelet.getEdgesForSource(srcId);
    }

    this.graph._ensureAdjacencyListsForRead('getOutEdgesByRel');
    let edgeIdx = this.graph.nodeHeads[srcId];
    const edges = [];
    while (edgeIdx !== -1) {
      const baseIdx = edgeIdx * 6;
      if (this.graph.adjacency[baseIdx + 1] === relId) {
        edges.push(edgeIdx);
      }
      edgeIdx = this.graph.adjacency[baseIdx + 5];
    }
    return edges;
  }

  findEdge(src, rel, dst = null) {
    const srcId = this.graph._ensureNode(src);
    if (srcId === -1) return null;
    
    const relId = typeof rel === 'string' ? this.graph.getRelationId(rel) : rel;
    const dstId = dst !== null ? this.graph._ensureNode(dst) : null;
    if (dst !== null && dstId === -1) return null;

    if (this.graph._waveletByRelId) {
      const wavelet = this.graph._waveletByRelId[relId];
      if (wavelet) {
        if (dstId !== null) {
          const edgeIdx = wavelet.findEdge(srcId, dstId);
          if (edgeIdx !== null) return edgeIdx;
          return null;
        }
        const firstIdx = wavelet.firstEdge(srcId);
        if (firstIdx !== null) return firstIdx;
        return null;
      }
    }

    this.graph._ensureAdjacencyListsForRead('findEdge');
    
    let edgeIdx = this.graph.nodeHeads[srcId];
    
    while (edgeIdx !== -1) {
      const baseIdx = edgeIdx * 6;
      const edgeRelId = this.graph.adjacency[baseIdx + 1];
      const edgeDstId = this.graph.adjacency[baseIdx + 2];
      
      const relMatch = edgeRelId === relId;
      const dstMatch = dstId === null || edgeDstId === dstId;
      
      if (relMatch && dstMatch) {
        return edgeIdx;
      }
      
      // Move to next edge in linked list
      edgeIdx = this.graph.adjacency[baseIdx + 5];
    }
    
    return null;
  }

  getEdge(edgeIdx) {
    let srcId;
    let relId;
    let dstId;
    let possibilityBits;
    let reliabilityBits;

    if (this.graph.adjacency) {
      const baseIdx = edgeIdx * 6;
      srcId = this.graph.adjacency[baseIdx + 0];
      relId = this.graph.adjacency[baseIdx + 1];
      dstId = this.graph.adjacency[baseIdx + 2];
      possibilityBits = this.graph.adjacency[baseIdx + 3];
      reliabilityBits = this.graph.adjacency[baseIdx + 4];
    } else if (this.graph.edgeSrcIds) {
      srcId = this.graph.edgeSrcIds[edgeIdx];
      relId = this.graph.edgeRelIds[edgeIdx];
      dstId = this.graph.edgeDstIds[edgeIdx];
      possibilityBits = this.graph.edgePossibilityBits[edgeIdx];
      reliabilityBits = this.graph.edgeReliabilityBits[edgeIdx];
    } else {
      throw new Error('Edge arrays unavailable for read-only snapshot.');
    }

    const value = this.graph.values[edgeIdx];
    
    return {
      src: this.graph.getNodeKey(srcId),
      relId,
      rel: this.graph.relationIdToName[relId],
      dst: this.graph.getNodeKey(dstId),
      possibility: this.graph._bitsToFloat(possibilityBits),
      reliability: this.graph._bitsToFloat(reliabilityBits),
      value: Number.isNaN(value) ? undefined : value,
      nextEdgeIndex: this.graph.adjacency ? this.graph.adjacency[edgeIdx * 6 + 5] : -1
    };
  }

  findEdges(src = null, rel = null, dst = null) {
    const edges = [];
    
    for (let i = 0; i < this.graph.edgeIndex; i++) {
      const edge = this.getEdge(i);
      const srcMatch = src === null || edge.src === src;
      const relMatch = rel === null || edge.relId === rel;
      const dstMatch = dst === null || edge.dst === dst;
      
      if (srcMatch && relMatch && dstMatch) {
        edges.push(i);
      }
    }
    
    return edges;
  }

  getDegree(node) {
    const nodeId = this.graph._ensureNode(node);
    if (nodeId === -1) return 0;
    return this.graph.nodeDegrees[nodeId] || 0;
  }

  isAdjacent(src, dst) {
    const srcId = this.graph._ensureNode(src);
    const dstId = this.graph._ensureNode(dst);
    if (srcId === -1 || dstId === -1) return false;

    if (this.graph._waveletAll) {
      return this.graph._waveletAll.hasEdge(srcId, dstId);
    }
    if (this.graph._waveletByRelId) {
      throw new Error('isAdjacent requires relation-agnostic wavelet; use relation-specific checks instead.');
    }

    this.graph._ensureAdjacencyListsForRead('isAdjacent');
    
    let edgeIdx = this.graph.nodeHeads[srcId];
    
    while (edgeIdx !== -1) {
      const baseIdx = edgeIdx * 6;
      const edgeDstId = this.graph.adjacency[baseIdx + 2];
      
      if (edgeDstId === dstId) {
        return true;
      }
      
      edgeIdx = this.graph.adjacency[baseIdx + 5];
    }
    
    return false;
  }

  forEachOutEdge(src, callback) {
    const srcId = this.graph._ensureNode(src);
    if (srcId === -1) return;

    if (this.graph._waveletAll) {
      this.graph._waveletAll.forEachEdgeForSource(srcId, (edgeIdx) => {
        callback(this.getEdge(edgeIdx), edgeIdx);
      });
      return;
    }

    this.graph._ensureAdjacencyListsForRead('forEachOutEdge');
    
    let edgeIdx = this.graph.nodeHeads[srcId];
    
    while (edgeIdx !== -1) {
      const baseIdx = edgeIdx * 6;
      callback(this.getEdge(edgeIdx), edgeIdx);
      edgeIdx = this.graph.adjacency[baseIdx + 5];
    }
  }

  forEachOutEdgeByRel(src, rel, callback) {
    const srcId = this.graph._ensureNode(src);
    if (srcId === -1) return;
    const relId = typeof rel === 'string' ? this.graph.getRelationId(rel) : rel;

    if (this.graph._waveletByRelId) {
      const wavelet = this.graph._waveletByRelId[relId];
      if (!wavelet) return;
      wavelet.forEachEdgeForSource(srcId, (edgeIdx) => {
        callback(this.getEdge(edgeIdx), edgeIdx);
      });
      return;
    }

    this.graph._ensureAdjacencyListsForRead('forEachOutEdgeByRel');
    let edgeIdx = this.graph.nodeHeads[srcId];
    while (edgeIdx !== -1) {
      const baseIdx = edgeIdx * 6;
      if (this.graph.adjacency[baseIdx + 1] === relId) {
        callback(this.getEdge(edgeIdx), edgeIdx);
      }
      edgeIdx = this.graph.adjacency[baseIdx + 5];
    }
  }

  hasEdge(src, rel, dst) {
    const edge = this.findEdge(src, rel, dst);
    return edge !== null;
  }
}
