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

  ensureCapacity(minCapacity) {
    const currentCapacity = this.graph.adjacency.length / 6;
    if (minCapacity <= currentCapacity) return;
    
    const newCapacity = Math.max(currentCapacity * 2, minCapacity);
    const newAdjacency = new Int32Array(newCapacity * 6);
    newAdjacency.set(this.graph.adjacency.subarray(0, currentCapacity * 6));
    this.graph.adjacency = newAdjacency;

    const newValues = new Float64Array(newCapacity);
    newValues.set(this.graph.values.subarray(0, currentCapacity));
    newValues.fill(Number.NaN, currentCapacity);
    this.graph.values = newValues;
    
    // Extend degree and head arrays
    const newDegrees = new Uint16Array(newCapacity);
    const newHeads = new Int32Array(newCapacity);
    newDegrees.set(this.graph.nodeDegrees.subarray(0, currentCapacity));
    newHeads.set(this.graph.nodeHeads.subarray(0, currentCapacity));
    newHeads.fill(-1, currentCapacity);
    
    this.graph.nodeDegrees = newDegrees;
    this.graph.nodeHeads = newHeads;
  }

  ensureNode(node) {
    if (typeof node === 'number') {
      this.ensureNodeId(node);
      return node;
    }
    if (this.graph._mphEnabled) {
      return this.graph._lookupNodeId(node);
    }
    const nodeId = this.graph.nodeIds.indexOf(node);
    if (nodeId === -1) {
      return this.ensureNodeByString(node);
    }
    return nodeId;
  }

  ensureNodeId(nodeId, nodeKey = null) {
    const hasNodeArray = Array.isArray(this.graph.nodeIds);
    if (this.graph.nodeHeads) {
      if (nodeId >= this.graph.nodeHeads.length) {
        this.ensureCapacity(nodeId + 1);
      }
      if (hasNodeArray && this.graph.nodeHeads[nodeId] === 0 && this.graph.nodeDegrees[nodeId] === 0 && this.graph.nodeIds[nodeId] === undefined) {
        this.graph.nodeHeads[nodeId] = -1;
      }
    }
    if (nodeKey && hasNodeArray && this.graph.nodeIds[nodeId] === undefined) {
      this.graph.nodeIds[nodeId] = nodeKey;
    }
    if (nodeId >= this.graph.numNodes) {
      this.graph.numNodes = nodeId + 1;
    }
  }

  ensureNodeByString(nodeKey) {
    if (this.graph._mphEnabled) {
      return -1;
    }
    if (!Array.isArray(this.graph.nodeIds)) return -1;
    const nodeId = this.graph.nodeIds.indexOf(nodeKey);
    if (nodeId === -1) {
      const newId = this.graph.numNodes;
      this.ensureCapacity(this.graph.numNodes + 1);
      this.graph.nodeIds[newId] = nodeKey;
      this.graph.nodeHeads[newId] = -1; // Initialize head to -1 (no edges)
      this.graph.nodeDegrees[newId] = 0;
      this.graph.numNodes++;
      return newId;
    }
    return nodeId;
  }

  restoreAdjacencyLists() {
    if (!this.graph.adjacency) {
      throw new Error('Adjacency list storage removed; wavelet adjacency is required for reads.');
    }
    if (this.graph.nodeHeads) return;
    const heads = new Int32Array(this.graph.numNodes || 0);
    heads.fill(-1);
    this.graph.nodeHeads = heads;

    for (let edgeIdx = 0; edgeIdx < this.graph.edgeIndex; edgeIdx++) {
      const baseIdx = edgeIdx * 6;
      const srcId = this.graph.adjacency[baseIdx + 0];
      const currentHead = this.graph.nodeHeads[srcId];
      this.graph.adjacency[baseIdx + 5] = currentHead;
      this.graph.nodeHeads[srcId] = edgeIdx;
    }
  }

  ensureAdjacencyListsForRead(caller) {
    if (!this.graph.adjacency) {
      throw new Error(`[CondensedGraph] Adjacency list storage removed; wavelet adjacency is required (${caller}).`);
    }
    if (this.graph.nodeHeads) return;
    if (this.graph._dropAdjacencyList && !this.graph._warnedAdjacencyRestoreForRead) {
      this.graph._warnedAdjacencyRestoreForRead = true;
      console.warn(`[CondensedGraph] Read path restored adjacency lists (${caller}); wavelet adjacency should be used for read-only snapshots.`);
    }
    this.restoreAdjacencyLists();
  }

  ensureAdjacencyListsForWrite() {
    if (this.graph.nodeHeads) return;
    this.restoreAdjacencyLists();
  }

  addEdge(src, relation, dst, options = {}) {
    this.ensureCapacity(this.graph.edgeIndex + 1);

    if (this.graph._waveletByRelId || this.graph._waveletAll) {
      this.graph._waveletByRelId = null;
      this.graph._waveletAll = null;
    }

    this.ensureAdjacencyListsForWrite();
    
    const srcId = typeof src === 'number' ? src : this.ensureNode(src);
    const dstId = typeof dst === 'number' ? dst : this.ensureNode(dst);
    const relId = this.graph.getRelationId(relation);
    const possibility = options.possibility !== undefined ? options.possibility : 1.0;
    const reliability = options.reliability !== undefined ? options.reliability : 1.0;
    const value = options.value !== undefined ? options.value : Number.NaN;
    
    const edgeIndex = this.graph.edgeIndex;
    
    // Store edge at end of adjacency array
    const baseIdx = edgeIndex * 6;
    this.graph.adjacency[baseIdx + 0] = srcId;
    this.graph.adjacency[baseIdx + 1] = relId;
    this.graph.adjacency[baseIdx + 2] = dstId;
    this.graph.adjacency[baseIdx + 3] = this.graph._floatToBits(possibility);
    this.graph.adjacency[baseIdx + 4] = this.graph._floatToBits(reliability);
    this.graph.values[edgeIndex] = value;
    
    // Prepend to source's edge list (linked list head insertion)
    // New edge points to current head, head becomes new edge
    // Use 0xFFFFFFFF (-1) as sentinel for "no edges"
    // Edge 0 is valid, so we need to distinguish sentinel from edge index 0
    const currentHead = this.graph.nodeHeads[srcId];
    this.graph.adjacency[baseIdx + 5] = currentHead;
    this.graph.nodeHeads[srcId] = edgeIndex;
    
    // Update source node's degree (clamp to Uint16 max to avoid silent wrap)
    const currentDegree = this.graph.nodeDegrees[srcId];
    if (currentDegree < 65535) {
      this.graph.nodeDegrees[srcId] = currentDegree + 1;
    }
    
    // Update stats
    this.graph.numEdges++;
    this.graph.edgeIndex++;
    if (this.graph.nodeDegrees[srcId] > this.graph.maxDegree) {
      this.graph.maxDegree = this.graph.nodeDegrees[srcId];
    }
    
    return edgeIndex;
  }

  removeEdge(edgeIdx) {
    if (this.graph._waveletByRelId || this.graph._waveletAll) {
      this.graph._waveletByRelId = null;
      this.graph._waveletAll = null;
    }

    this.ensureAdjacencyListsForWrite();
    const edge = this.graph.getEdge(edgeIdx);
    const srcId = this.ensureNode(edge.src);
    if (srcId === -1) return;
    
    // Find and remove edge from node's linked list
    let prevIdx = -1;
    let currIdx = this.graph.nodeHeads[srcId];
    
    while (currIdx !== -1 && currIdx !== edgeIdx) {
      prevIdx = currIdx;
      const nextIdx = this.graph.adjacency[currIdx * 6 + 5];
      if (nextIdx === -1) break;
      currIdx = nextIdx;
    }
    
    if (currIdx === edgeIdx) {
      // Found the edge, remove it from linked list
      const nextIdx = this.graph.adjacency[currIdx * 6 + 5];
      
      if (prevIdx === -1) {
        // Edge is head of list
        this.graph.nodeHeads[srcId] = nextIdx;
      } else {
        // Edge is in middle of list
        this.graph.adjacency[prevIdx * 6 + 5] = nextIdx;
      }
      
      this.graph.nodeDegrees[srcId]--;
      this.graph.numEdges--;
    }
  }

  addEdges(edges) {
    const capacityNeeded = this.graph.edgeIndex + edges.length;
    this.ensureCapacity(capacityNeeded);
    
    for (const edge of edges) {
      this.addEdge(edge.src, edge.relation, edge.dst, edge);
    }
  }

  clear() {
    this.graph.numNodes = 0;
    this.graph.numEdges = 0;
    this.graph.maxDegree = 0;
    this.graph.avgDegree = 0;
    this.graph.edgeIndex = 0;
    this.graph.nodeIds = [];
    this.graph.nodeDegrees = new Uint16Array(1024);
    this.graph.nodeHeads = new Int32Array(1024);
    this.graph.nodeHeads.fill(-1);
    this.graph.adjacency = new Int32Array(1024 * 6);
    this.graph.values = new Float64Array(1024);
    this.graph.values.fill(Number.NaN);
    this.graph.relationNameToId = new Map();
    this.graph.relationIdToName = [];
    this.graph.nextRelationId = 0;
  }
}
