/**
 * Condensed Graph - Corrected succinct graph representation
 * 
 * Uses a proper linked-list adjacency structure for correct O(d) access:
 * - Typed arrays for memory efficiency
 * - O(1) edge insertion (prepend to head of list)
 * - O(d) neighbor iteration (follow linked list)
 * - Cache-friendly memory layout
 * 
 * Design:
 * - Each node has a "head" pointer to its first outgoing edge
 * - Edges form linked lists: edge[src] → edge[next] → edge[next] → ...
 * - Insertion is O(1): new edge points to current head, head becomes new edge
 * - Traversal is O(d): follow edges from head until next=0 (sentinel)
 * 
 * This matches graph-core's SuccinctGraph linked-list design, simplified.
 */

import { serializeCondensedGraph, deserializeCondensedGraph } from './graph/CondensedGraphBinary.js';
import { CondensedGraphCore } from './condensed/CondensedGraphCore.js';
import { CondensedGraphRead } from './condensed/CondensedGraphRead.js';
import { CondensedGraphStats } from './condensed/CondensedGraphStats.js';
import { CondensedGraphFinalize } from './condensed/CondensedGraphFinalize.js';

class CondensedGraph {
  constructor(options = {}) {
    // Basic graph metadata
    this.numNodes = 0;
    this.numEdges = 0;
    this.maxDegree = 0;
    this.avgDegree = 0;
    
    // Adjacency storage using typed arrays
    // Each edge stored once in adjacency array
    // Format: [srcId, relId, dstId, possibility, reliability, nextEdgeIndex]
    // Edges form linked lists per source node via nextEdgeIndex
    // nextEdgeIndex = -1 is sentinel (end of list)
    this.adjacency = new Int32Array(options.initialCapacity || 1024 * 6);
    this.values = new Float64Array(options.initialCapacity || 1024);
    this.values.fill(Number.NaN);
    
    // Node metadata
    this.nodeIds = []; // Original node IDs at their internal index positions
    this.nodeHeads = new Int32Array(options.initialCapacity || 1024); // Head edge index for each node
    // Initialize all heads to -1 (sentinel: no edges)
    this.nodeHeads.fill(-1);
    
    // Edge tracking
    this.edgeIndex = 0; // Next available edge index
    
    // Relation name to ID mapping
    this.relationNameToId = new Map();
    this.relationIdToName = [];
    this.nextRelationId = 0;

    // Snapshot-time perfect hash for node keys
    this._mphEnabled = false;
    this._mph = null;

    // Snapshot-time wavelet adjacency per relation
    this._waveletByRelId = null;
    this._waveletAll = null;
    this._dropAdjacencyList = false;
    this._useNodeIdTable = options.useNodeIdTable !== undefined ? options.useNodeIdTable : true;
    this._omitNodeIdTable = options.omitNodeIdTable === true;

    this.edgeSrcIds = null;
    this.edgeRelIds = null;
    this.edgeDstIds = null;
    this.edgePossibilityBits = null;
    this.edgeReliabilityBits = null;
    
    // Per-node degree tracking
    this.nodeDegrees = new Uint32Array(options.initialCapacity || 1024);

    this._core = new CondensedGraphCore(this);
    this._read = new CondensedGraphRead(this);
    this._stats = new CondensedGraphStats(this);
    this._finalize = new CondensedGraphFinalize(this);
  }

  getNodeKey(nodeId) {
    if (!this.nodeIds) return undefined;
    if (typeof this.nodeIds.get === 'function') {
      return this.nodeIds.get(nodeId);
    }
    return this.nodeIds[nodeId];
  }

  getRelationId(relationName) {
    let id = this.relationNameToId.get(relationName);
    if (id === undefined) {
      id = this.nextRelationId++;
      this.relationNameToId.set(relationName, id);
      this.relationIdToName[id] = relationName;
    }
    return id;
  }

  getExistingRelationId(relationName) {
    return this.relationNameToId.get(relationName);
  }

  getRelationName(relationId) {
    return this.relationIdToName[relationId];
  }

  _ensureCapacity(minCapacity) {
    return this._core.ensureCapacity(minCapacity);
  }

  /**
   * Ensure a node exists and return its internal ID
   */
  _ensureNode(node) {
    return this._core.ensureNode(node);
  }

  _ensureNodeId(nodeId, nodeKey = null) {
    return this._core.ensureNodeId(nodeId, nodeKey);
  }

  _ensureNodeByString(nodeKey) {
    return this._core.ensureNodeByString(nodeKey);
  }

  finalizePerfectHash(options = {}) {
    return this._finalize.finalizePerfectHash(options);
  }

  finalizeWaveletAdjacency(options = {}) {
    return this._finalize.finalizeWaveletAdjacency(options);
  }

  _ensureAdjacencyListsForRead(caller) {
    return this._core.ensureAdjacencyListsForRead(caller);
  }

  _ensureAdjacencyListsForWrite() {
    return this._core.ensureAdjacencyListsForWrite();
  }

  _restoreAdjacencyLists() {
    return this._core.restoreAdjacencyLists();
  }

  _lookupNodeId(nodeKey) {
    if (!this._mphEnabled || !this._mph) return -1;
    return this._mph.lookup(nodeKey);
  }

  /**
   * Add a directed edge to the graph
   * Uses linked list: prepend to head of source's edge list (O(1))
   * 
   * @param {number|string} src - Source node ID or key
   * @param {string} relation - Relation name
   * @param {number|string} dst - Destination node ID or key
   * @param {Object} options - {possibility, reliability, value}
   * @returns {number} Edge index
   */
  addEdge(src, relation, dst, options = {}) {
    return this._core.addEdge(src, relation, dst, options);
  }

  /**
   * Convert float to 16-bit representation for adjacency storage
   */
  _floatToBits(value) {
    const clamped = Math.max(0, Math.min(1, value));
    return Math.round(clamped * 65535);
  }

  /**
   * Convert 16-bit representation back to float
   */
  _bitsToFloat(bits) {
    return bits / 65535;
  }

  /**
   * Get all outgoing edges from a source node
   * Follows linked list from head (O(d) where d = out-degree)
   * 
   * @param {number|string} src - Source node ID or key
   * @returns {Array} Array of edge indices
   */
  getOutEdges(src) {
    return this._read.getOutEdges(src);
  }

  getOutEdgesByRel(src, rel) {
    return this._read.getOutEdgesByRel(src, rel);
  }

  /**
   * Get specific outgoing edge by relation (and optionally destination)
   * Follows linked list (O(d) where d = out-degree)
   * 
   * @param {number|string} src - Source node ID or key
   * @param {number|string} rel - Relation ID or name
   * @param {number|string} dst - Destination node ID or key (optional)
   * @returns {number|null} Edge index or null
   */
  findEdge(src, rel, dst = null) {
    return this._read.findEdge(src, rel, dst);
  }

  /**
   * Get edge at specific index
   * 
   * @param {number} edgeIdx - Edge index
   * @returns {Object} Edge data with string keys
   */
  getEdge(edgeIdx) {
    return this._read.getEdge(edgeIdx);
  }

  /**
   * Find all edges matching criteria
   * 
   * @param {number|null} srcId - Source node ID filter
   * @param {number|null} relId - Relation ID filter
   * @param {number|null} dstId - Destination node ID filter
   * @returns {Array} Array of matching edge indices
   */
  findEdges(src = null, rel = null, dst = null) {
    return this._read.findEdges(src, rel, dst);
  }

  /**
   * Get node degree (outgoing edge count)
   * 
   * @param {number|string} node - Node ID or key
   * @returns {number} Number of outgoing edges
   */
  getDegree(node) {
    return this._read.getDegree(node);
  }

  /**
   * Check if there's an edge from src to dst (regardless of relation)
   * 
   * @param {number|string} src - Source node ID or key
   * @param {number|string} dst - Destination node ID or key
   * @returns {boolean} True if any edge exists from src to dst
   */
  isAdjacent(src, dst) {
    return this._read.isAdjacent(src, dst);
  }

  /**
   * Remove an edge by index
   */
  removeEdge(edgeIdx) {
    return this._core.removeEdge(edgeIdx);
  }

  /**
   * Iterate over all outgoing edges from a node
   * Follows linked list (O(d) where d = out-degree)
   * 
   * @param {number|string} src - Source node ID or key
   * @param {Function} callback - Callback for each edge
   */
  forEachOutEdge(src, callback) {
    return this._read.forEachOutEdge(src, callback);
  }

  forEachOutEdgeByRel(src, rel, callback) {
    return this._read.forEachOutEdgeByRel(src, rel, callback);
  }

  /**
   * Batch add multiple edges
   */
  addEdges(edges) {
    return this._core.addEdges(edges);
  }

  /**
   * Get memory usage statistics
   */
  getStats() {
    return this._stats.getStats();
  }

  /**
   * Check if an edge exists
   * 
   * @param {number|string} src - Source node ID or key
   * @param {number|string} rel - Relation ID or name
   * @param {number|string} dst - Destination node ID or key
   * @returns {boolean} True if edge exists
   */
  hasEdge(src, rel, dst) {
    return this._read.hasEdge(src, rel, dst);
  }

  /**
   * Clear all graph data
   */
  clear() {
    return this._core.clear();
  }

  toBinary() {
    return serializeCondensedGraph(this);
  }

  static fromBinary(buffer) {
    const graph = deserializeCondensedGraph(buffer, () => new CondensedGraph());
    if (!graph._mphEnabled) {
      graph.finalizePerfectHash();
    }
    return graph;
  }
}

export { CondensedGraph };
