import { RelationSnapshotAccess } from './relation/RelationSnapshotAccess.js';
import { RelationCaches } from './relation/RelationCaches.js';
import { RelationLookup } from './relation/RelationLookup.js';
import { RelationUpdates } from './relation/RelationUpdates.js';
import { RelationGraphTraversal } from './relation/RelationGraphTraversal.js';

export class RelationManager {
  constructor(arbiter) {
    this.arbiter = arbiter;
    this._relationsChanged = false;

    // Performance optimizations
    // RF-08 closure: cache state lives entirely in RelationCaches.
    // The old alias fields (this._relationLookupCache, this._valueLookupCache,
    // etc.) have been removed so the cache file is the only place that
    // owns cache state. Callers must reach in via `this._caches.X`.
    this._caches = new RelationCaches(this);

    // Set for O(1) duplicate detection of relations
    this._relationKeys = new Set();
    this._relationKeyToIndex = new Map();
    this._relationNameToId = new Map();

    this._snapshotAccess = new RelationSnapshotAccess(this);
    this._lookup = new RelationLookup(this);
    this._updates = new RelationUpdates(this);
    this._relationGraph = new RelationGraphTraversal(this);
    this._cacheHits = 0;
    this._cacheMisses = 0;
    
    // Batch operation tracking
    this._batchOperationCount = 0;
    this._lastBatchOptimization = Date.now();
  }

  _getRelationGraph(relation) {
    return this._relationGraph.getGraph(relation);
  }

  _addRelationToGraph(srcId, relation, dstId) {
    this._relationGraph.addRelation(srcId, relation, dstId);
  }

  _removeRelationFromGraph(srcId, relation, dstId) {
    this._relationGraph.removeRelation(srcId, relation, dstId);
  }

  getRelationGraphNeighbors(nodeId, relation, reverse = false) {
    return this._relationGraph.getNeighbors(nodeId, relation, reverse);
  }

  shouldUseRelationGraphTraversal(nodeId, relation, reverse = false) {
    return this._relationGraph.shouldUseTraversal(nodeId, relation, reverse);
  }

  _getRelationDegreeFromIndices(nodeId, relation, reverse = false) {
    this._ensureIndicesBuilt();
    const indices = this.arbiter.indices;
    if (!indices) return 0;
    const key = reverse
      ? indices._makeDstRelKey(nodeId, relation)
      : indices._makeSrcRelKey(nodeId, relation);
    const set = reverse ? indices.relationsByDstRel.get(key) : indices.relationsBySrcRel.get(key);
    return set ? set.size : 0;
  }

  // Fast numeric cache key generation - much faster than string concatenation
  _makeDirectCacheKey(srcId, relation, dstId) {
    return this._caches.makeDirectCacheKey(srcId, relation, dstId);
  }

  _makeSrcRelCacheKey(srcId, relation) {
    return this._caches.makeSrcRelCacheKey(srcId, relation);
  }

  _makeDstRelCacheKey(dstId, relation) {
    return this._caches.makeDstRelCacheKey(dstId, relation);
  }

  _makeValueCacheKey(srcId, relation, dstId) {
    return this._caches.makeValueCacheKey(srcId, relation, dstId);
  }

  // String-based cache key generation - converts strings to IDs internally
  _makeDirectCacheKeyFromStrings(srcKey, relation, dstKey) {
    return this._caches.makeDirectCacheKeyFromStrings(srcKey, relation, dstKey);
  }

  _makeSrcRelCacheKeyFromStrings(srcKey, relation) {
    return this._caches.makeSrcRelCacheKeyFromStrings(srcKey, relation);
  }

  _makeDstRelCacheKeyFromStrings(dstKey, relation) {
    return this._caches.makeDstRelCacheKeyFromStrings(dstKey, relation);
  }

  _makeValueCacheKeyFromStrings(srcKey, relation, dstKey) {
    return this._caches.makeValueCacheKeyFromStrings(srcKey, relation, dstKey);
  }

  _makeCompositeKeyFromStrings(srcKey, relation, dstKey) {
    const srcId = this._resolveNodeId(srcKey);
    const dstId = this._resolveNodeId(dstKey);
    if (srcId === undefined || dstId === undefined) return null;
    return this.arbiter.keyManager.createCompositeKey(srcId, relation, dstId);
  }

  _resolveNodeId(keyOrId) {
    if (typeof keyOrId === 'number') return keyOrId;
    return this.arbiter.nodeManager.getNodeId(keyOrId);
  }

  _deleteFromCache(cache, key) {
    this._caches.deleteFromCache(cache, key);
  }

  /**
   * Configure decay settings for a specific relation type
   * @param {string} relationType - The relation type (e.g., 'balance', 'price')
   * @param {Object} decayConfig - Decay configuration
   */
  setRelationDecayConfig(relationType, decayConfig) {
    this.arbiter.valueManager.setDecayConfig(relationType, decayConfig);
  }

  /**
   * Set default decay configuration for all relations
   * @param {Object} decayConfig - Default decay configuration
   */
  setDefaultDecayConfig(decayConfig) {
    this.arbiter.valueManager.setDefaultDecayConfig(decayConfig);
  }

  /**
   * Create a unique key for a relation (O(1) duplicate detection)
   * @private
   */
  _getRelationId(relation) {
    let relationId = this._relationNameToId.get(relation);
    if (relationId === undefined) {
      relationId = this._relationNameToId.size;
      this._relationNameToId.set(relation, relationId);
    }
    return relationId;
  }

  _makeRelationKey(srcId, relation, dstId) {
    const relationId = this._getRelationId(relation);
    return `${srcId}|${relationId}|${dstId}`;
  }

  _makeValueCacheKeyBySrc(srcId, relationId) {
    return this._caches.makeValueCacheKeyBySrc(srcId, relationId);
  }

  _makeValueCacheKeyByDst(dstId, relationId) {
    return this._caches.makeValueCacheKeyByDst(dstId, relationId);
  }

  _invalidateValueRelationCaches(srcId, dstId, relation) {
    this._caches.invalidateValueRelationCaches(srcId, dstId, relation);
  }

  /**
   * Rebuild the relation keys Set from existing relations
   * @private
   */
  _rebuildRelationKeys() {
    this._relationKeys.clear();
    this._relationKeyToIndex.clear();
    this._caches.valueRelationsBySrcCache.clear();
    this._caches.valueRelationsByDstCache.clear();
    this._caches.valueRelationsByNameCache.clear();
    const relations = this.arbiter.relations;
    const total = relations.length;
    for (let i = 0; i < total; i++) {
      const relation = relations[i];
      const relationKey = this._makeRelationKey(relation.src, relation.rel, relation.dst);
      this._relationKeys.add(relationKey);
      this._relationKeyToIndex.set(relationKey, i);
    }
  }

  addRelation(srcKey, relation, dstKey, options) {
    return this._updates.addRelation(srcKey, relation, dstKey, options);
  }

  removeRelation(srcKey, relationName, dstKey, attributes) {
    return this._updates.removeRelation(srcKey, relationName, dstKey, attributes);
  }

  addRelationsBatch(relations, chunkSize = 5000) { // Increased chunk size for better performance
    return this._updates.addRelationsBatch(relations, chunkSize);
  }

  _addRelationInternal(srcKey, relation, dstKey, options = {}) {
    return this._updates._addRelationInternal(srcKey, relation, dstKey, options);
  }

  updateRelationsBatch(updates, chunkSize = 5000) { // Increased chunk size
    return this._updates.updateRelationsBatch(updates, chunkSize);
  }

  _processBatchRemovals(removeOps, chunkSize) {
    return this._updates._processBatchRemovals(removeOps, chunkSize);
  }

  _processBatchAdditions(addOps, chunkSize) {
    return this._updates._processBatchAdditions(addOps, chunkSize);
  }

  _processBatchModifications(modifyOps, chunkSize) {
    return this._updates._processBatchModifications(modifyOps, chunkSize);
  }

  _modifyRelation(srcKey, relation, dstKey, newOptions) {
    return this._updates._modifyRelation(srcKey, relation, dstKey, newOptions);
  }

  updateRelationsBatchTransactional(updates) {
    return this._updates.updateRelationsBatchTransactional(updates);
  }

  getDirectRelation(srcId, relation, dstId, options = null) {
    return this._lookup.getDirectRelation(srcId, relation, dstId, options);
  }

  getRelationsFromSrc(srcId, relation, options = null) {
    return this._lookup.getRelationsFromSrc(srcId, relation, options);
  }

  getRelationsToDst(dstId, relation, options = null) {
    return this._lookup.getRelationsToDst(dstId, relation, options);
  }

  getRelationsByName(relation, options = null) {
    return this._lookup.getRelationsByName(relation, options);
  }

  _getSnapshotDirectRelation(srcId, relation, dstId) {
    return this._snapshotAccess.getDirectRelation(srcId, relation, dstId);
  }

  _getSnapshotRelationsFromSrc(srcId, relation) {
    return this._snapshotAccess.getRelationsFromSrc(srcId, relation);
  }

  _getSnapshotRelationsToDst(dstId, relation) {
    return this._snapshotAccess.getRelationsToDst(dstId, relation);
  }

  _getSnapshotRelationsByName(relation) {
    return this._snapshotAccess.getRelationsByName(relation);
  }


  _mergeRelationLists(persistent, partial) {
    if (!partial || partial.length === 0) return persistent || [];
    if (!persistent || persistent.length === 0) return partial;
    const merged = [];
    const seen = new Set();
    for (const rel of persistent) {
      merged.push(rel);
      seen.add(this._makeRelationKey(rel.src, rel.rel, rel.dst));
    }
    for (const rel of partial) {
      const key = this._makeRelationKey(rel.src, rel.rel, rel.dst);
      if (seen.has(key)) continue;
      merged.push(rel);
    }
    return merged;
  }

  _ensureIndicesBuilt() {
    if ((!this.arbiter.indicesBuilt || this._relationsChanged) && this.arbiter.relations.length > 0) {
      this.arbiter.indices.buildIndices(this.arbiter.relations);
      this.arbiter.indicesBuilt = true;
      this._relationsChanged = false;
      
      // Rebuild relation keys Set for O(1) duplicate detection
      this._rebuildRelationKeys();
    }
  }

  /**
   * Get the value and timestamps for a direct relation if present
   * Now returns both point value and blurred interval with caching
   */
  getValueRelation(srcId, relation, dstId, options = null) {
    const partialContext = options?.partialGraphContext || null;
    const partialRel = partialContext ? partialContext.getDirectRelation(srcId, relation, dstId) : null;
    // Try value cache first - use numeric key
    const valueCacheKey = this._makeValueCacheKey(srcId, relation, dstId);
    const cachedValue = this._caches.valueLookupCache.get(valueCacheKey);
    if (cachedValue) {
      this._cacheHits++;
      return cachedValue;
    }
    
    this._ensureIndicesBuilt();
    const rel = this.arbiter.indices.getDirectRelation(srcId, relation, dstId);
    if (rel && rel.value !== undefined) {
      const callerNow = options && options.now !== undefined && options.now !== null ? options.now : null;
      const blurred = this.arbiter.valueManager.getBlurredValue(rel, callerNow);
      const result = {
        pointValue: rel.value, // Original crisp value
        value: rel.value, // Backward compatibility
        blurredInterval: blurred.interval,
        currentPossibility: blurred.possibility, // Decayed possibility
        originalPossibility: rel.possibility, // Original possibility
        reliability: blurred.reliability,
        updated_last_at: rel.updated_last_at,
        changed_last_at: rel.changed_last_at
      };
      
      // Cache value relations as they're frequently accessed - hLRU manages size
      this._caches.valueLookupCache.set(valueCacheKey, result);
      this._caches.valueLookupCacheKeys.add(valueCacheKey);
      this._cacheMisses++;
      return result;
    }
    if (partialRel && partialRel.value !== undefined) {
      const possibility = partialRel.possibility !== undefined ? partialRel.possibility : 1.0;
      this._cacheMisses++;
      return {
        pointValue: partialRel.value,
        value: partialRel.value,
        blurredInterval: { min: partialRel.value, max: partialRel.value },
        currentPossibility: possibility,
        originalPossibility: possibility,
        reliability: partialRel.reliability || 1.0,
        updated_last_at: partialRel.updated_last_at,
        changed_last_at: partialRel.changed_last_at,
        source: 'partial'
      };
    }
    this._cacheMisses++;
    return null;
  }

  /**
   * Get all value-carrying relations from srcId for a given relation
   * Now includes proper decay and blurred intervals with optimized filtering
   */
  getAllValueRelationsFromSrc(srcId, relation, options = null) {
    const partialContext = options?.partialGraphContext || null;
    if (partialContext) {
      this._ensureIndicesBuilt();
      const persistent = this.arbiter.indices.getRelationsFromSrc(srcId, relation);
      const partial = partialContext.getRelationsFromSrc(srcId, relation);
      return this._mergeValueRelations(persistent, partial);
    }
    this._ensureIndicesBuilt();
    const relationId = this._getRelationId(relation);
    const cacheKey = this._makeValueCacheKeyBySrc(srcId, relationId);
    const cached = this._caches.valueRelationsBySrcCache.get(cacheKey);
    if (cached) return cached;

    const rels = this.arbiter.indices.getRelationsFromSrc(srcId, relation);
    const results = [];
    for (let i = 0; i < rels.length; i++) {
      const rel = rels[i];
      if (rel.value === undefined || typeof rel.value !== 'number') continue;
      const possibility = rel.possibility !== undefined ? rel.possibility : 1.0;
      results.push({
        src: rel.src,
        rel: rel.rel,
        dst: rel.dst,
        value: rel.value,
        possibility,
        reliability: rel.reliability || 1.0,
        changed_last_at: rel.changed_last_at,
        updated_last_at: rel.updated_last_at,
        stateId: rel.stateId,
        pointValue: rel.value,
        blurredInterval: { min: rel.value, max: rel.value },
        currentPossibility: possibility,
        originalPossibility: possibility,
        decayApplied: false
      });
    }

    this._caches.valueRelationsBySrcCache.set(cacheKey, results);
    return results;
  }

  /**
   * Get all value-carrying relations to dstId for a given relation
   * Now includes proper decay and blurred intervals with optimized filtering
   */
  getAllValueRelationsToDst(dstId, relation, options = null) {
    const partialContext = options?.partialGraphContext || null;
    if (partialContext) {
      this._ensureIndicesBuilt();
      const persistent = this.arbiter.indices.getRelationsToDst(dstId, relation);
      const partial = partialContext.getRelationsToDst(dstId, relation);
      return this._mergeValueRelations(persistent, partial);
    }
    this._ensureIndicesBuilt();
    const relationId = this._getRelationId(relation);
    const cacheKey = this._makeValueCacheKeyByDst(dstId, relationId);
    const cached = this._caches.valueRelationsByDstCache.get(cacheKey);
    if (cached) return cached;

    const rels = this.arbiter.indices.getRelationsToDst(dstId, relation);
    const results = [];
    for (let i = 0; i < rels.length; i++) {
      const rel = rels[i];
      if (rel.value === undefined || typeof rel.value !== 'number') continue;
      const possibility = rel.possibility !== undefined ? rel.possibility : 1.0;
      results.push({
        src: rel.src,
        rel: rel.rel,
        dst: rel.dst,
        value: rel.value,
        possibility,
        reliability: rel.reliability || 1.0,
        changed_last_at: rel.changed_last_at,
        updated_last_at: rel.updated_last_at,
        stateId: rel.stateId,
        pointValue: rel.value,
        blurredInterval: { min: rel.value, max: rel.value },
        currentPossibility: possibility,
        originalPossibility: possibility,
        decayApplied: false
      });
    }

    this._caches.valueRelationsByDstCache.set(cacheKey, results);
    return results;
  }

  /**
   * Get all value-carrying relations for a given relation name
   * Now includes proper decay and blurred intervals with optimized filtering
   */
  getAllValueRelationsByName(relation, options = null) {
    const partialContext = options?.partialGraphContext || null;
    if (partialContext) {
      this._ensureIndicesBuilt();
      const persistent = this.arbiter.indices.getRelationsByName(relation);
      const partial = partialContext.getRelationsByName(relation);
      return this._mergeValueRelations(persistent, partial);
    }
    this._ensureIndicesBuilt();
    const relationId = this._getRelationId(relation);
    const cached = this._caches.valueRelationsByNameCache.get(relationId);
    if (cached) return cached;

    const rels = this.arbiter.indices.getRelationsByName(relation);
    const results = [];
    for (let i = 0; i < rels.length; i++) {
      const rel = rels[i];
      if (rel.value === undefined || typeof rel.value !== 'number') continue;
      const possibility = rel.possibility !== undefined ? rel.possibility : 1.0;
      results.push({
        src: rel.src,
        rel: rel.rel,
        dst: rel.dst,
        value: rel.value,
        possibility,
        reliability: rel.reliability || 1.0,
        changed_last_at: rel.changed_last_at,
        updated_last_at: rel.updated_last_at,
        stateId: rel.stateId,
        pointValue: rel.value,
        blurredInterval: { min: rel.value, max: rel.value },
        currentPossibility: possibility,
        originalPossibility: possibility,
        decayApplied: false
      });
    }

    this._caches.valueRelationsByNameCache.set(relationId, results);
    return results;
  }

  /**
   * Get aggregated interval values from multiple relations
   */
  getAggregatedIntervalValue(srcId, relation, aggregator = 'max') {
    const valueRels = this.getAllValueRelationsFromSrc(srcId, relation);
    if (valueRels.length === 0) {
      return {
        interval: null,
        possibility: 0,
        reliability: 0
      };
    }
    
    // Use the enhanced relations that already have decay applied
    const intervalValues = valueRels.map((rel) => ({
      interval: rel.blurredInterval,
      possibility: rel.currentPossibility,
      reliability: rel.reliability
    }));
    
    return this.arbiter.valueManager.aggregateCrispValues(intervalValues, aggregator);
  }

  /**
   * Compare values between two relations using interval arithmetic
   * @param {Object} leftRelation - Left relation object or { srcId, relation, dstId }
   * @param {Object} rightRelation - Right relation object or { srcId, relation, dstId }
   * @param {string} comparator - Comparison operator
   * @param {number} epsilon - Optional epsilon for equality
   * @returns {number} Possibility (0-1) that comparison holds
   */
  compareRelationValues(leftRelation, rightRelation, comparator, epsilon = null, options = null) {
    // Get actual relation objects if needed
    const leftRel = leftRelation.value !== undefined ? leftRelation : 
      this.getDirectRelation(leftRelation.srcId, leftRelation.relation, leftRelation.dstId);
    const rightRel = rightRelation.value !== undefined ? rightRelation : 
      this.getDirectRelation(rightRelation.srcId, rightRelation.relation, rightRelation.dstId);
    
    if (!leftRel || !rightRel || leftRel.value === undefined || rightRel.value === undefined) {
      return 0; // Cannot compare if either has no value
    }
    
    const callerNow = options && options.now !== undefined && options.now !== null ? options.now : null;
    const leftBlurred = this.arbiter.valueManager.getBlurredValue(leftRel, callerNow);
    const rightBlurred = this.arbiter.valueManager.getBlurredValue(rightRel, callerNow);
    
    if (!leftBlurred.interval || !rightBlurred.interval) {
      return 0; // Cannot compare null intervals
    }
    
    // Compare intervals and weight by possibilities
    const comparisonPossibility = this.arbiter.valueManager.compareIntervals(
      leftBlurred.interval,
      rightBlurred.interval,
      comparator,
      epsilon
    );
    
    // Final possibility is comparison result weighted by both operand possibilities
    return comparisonPossibility * leftBlurred.possibility * rightBlurred.possibility;
  }

  _mergeValueRelations(persistent, partial) {
    const merged = [];
    const seen = new Set();
    const addRelation = (rel, source) => {
      if (rel.value === undefined || typeof rel.value !== 'number') return;
      const possibility = rel.possibility !== undefined ? rel.possibility : 1.0;
      const key = this._makeRelationKey(rel.src, rel.rel, rel.dst);
      if (seen.has(key)) return;
      seen.add(key);
      merged.push({
        src: rel.src,
        rel: rel.rel,
        dst: rel.dst,
        value: rel.value,
        possibility,
        reliability: rel.reliability || 1.0,
        changed_last_at: rel.changed_last_at,
        updated_last_at: rel.updated_last_at,
        stateId: rel.stateId,
        pointValue: rel.value,
        blurredInterval: { min: rel.value, max: rel.value },
        currentPossibility: possibility,
        originalPossibility: possibility,
        decayApplied: false,
        source
      });
    };

    if (persistent) {
      for (const rel of persistent) addRelation(rel, 'persistent');
    }
    if (partial) {
      for (const rel of partial) addRelation(rel, 'partial');
    }
    return merged;
  }

  /**
   * Get raw value-carrying relations for local context using similarity with optimized processing
   */
  getRawValueRelationsForLocalContext(
    contextNodeId, 
    direction, 
    relationName, 
    currentRelationForContext, 
    similarityOptions = { count: 5 }
  ) {
    this._ensureIndicesBuilt();
    const collectedRelations = new Map();

    // Create a unique numeric key for currentRelationForContext to exclude it
    const currentRelKey = currentRelationForContext ? 
      this._makeCompositeKeyFromStrings(currentRelationForContext.src, currentRelationForContext.rel, currentRelationForContext.dst)
      : null;

    const processAndAdd = (relations, similarityScore = 1.0) => {
      if (!relations) return;
      
      // Optimized processing
      for (let i = 0; i < relations.length; i++) {
        const rel = relations[i];
        // Ensure relation has a numeric value
        if (rel.value === undefined || rel.value === null || typeof rel.value !== 'number') {
          continue;
        }
        const relKey = this._makeCompositeKeyFromStrings(rel.src, rel.rel, rel.dst);
        // Exclude the current relation being evaluated and avoid duplicates
        if (relKey !== currentRelKey && !collectedRelations.has(relKey)) {
          collectedRelations.set(relKey, { relation: rel, similarityScore });
        }
      }
    };

    // 1. Fetch relations for contextNodeId itself (similarity score = 1.0)
    let ownRelations = [];
    if (direction === 'src') {
      ownRelations = this.arbiter.indices.getRelationsFromSrc(contextNodeId, relationName);
    } else { // 'dst'
      ownRelations = this.arbiter.indices.getRelationsToDst(contextNodeId, relationName);
    }
    processAndAdd(ownRelations, 1.0);

    // 2. Fetch relations for similar nodes if similarity manager is available
    if (this.arbiter.similarityManager && typeof this.arbiter.similarityManager.findSimilarNodes === 'function') {
      try {
        // Convert nodeId to nodeKey for SimilarityManager
        const contextNodeKey = this.arbiter.nodeManager?.getNodeKey(contextNodeId) || 
                              this.arbiter.keyByNodeId?.get(contextNodeId);
        
        if (contextNodeKey) {
          const similarResults = this.arbiter.similarityManager.findSimilarNodes(
            contextNodeKey, 
            similarityOptions.count || 5,
            similarityOptions.efSearch
          );
          
          if (similarResults && similarResults.length > 0) {
            for (const similarResult of similarResults) {
              const similarNodeId = similarResult.nodeId;
              const similarityScore = similarResult.similarity || 0.5; // Default if not provided
              
              if (similarNodeId === contextNodeId) continue; // Skip self if returned by similarity search

              let relationsFromSimilar = [];
              if (direction === 'src') {
                relationsFromSimilar = this.arbiter.indices.getRelationsFromSrc(similarNodeId, relationName);
              } else { // 'dst'
                relationsFromSimilar = this.arbiter.indices.getRelationsToDst(similarNodeId, relationName);
              }
              processAndAdd(relationsFromSimilar, similarityScore);
            }
          }
        }
      } catch (error) {
        // Proceed without similarity if it fails
      }
    }
    
    return Array.from(collectedRelations.values());
  }

  /**
   * Get raw value-carrying relations by relation name with optimized filtering
   */
  getRawValueRelationsByName(relationName, relationToExclude = null) {
    this._ensureIndicesBuilt();
    const rels = this.arbiter.indices.getRelationsByName(relationName);
    
    // Create exclusion key if needed using string-based composite key
    const excludeKey = relationToExclude ? 
      this._makeCompositeKeyFromStrings(relationToExclude.src, relationToExclude.rel, relationToExclude.dst) : null;
    
    // Optimized filtering
    const results = [];
    for (let i = 0; i < rels.length; i++) {
      const r = rels[i];
      // Must have numeric value
      if (r.value === undefined || r.value === null || typeof r.value !== 'number') {
        continue;
      }
      
      // Exclude the specified relation
      if (excludeKey) {
        const relKey = this._makeCompositeKeyFromStrings(r.src, r.rel, r.dst);
        if (relKey === excludeKey) {
          continue;
        }
      }
      
      results.push(r);
    }
    
    return results;
  }

  /**
   * Cache management methods
   */
  _invalidateRelationCaches(srcId, relation, dstId) {
    // Any relation mutation invalidates the graph version so derived-result
    // caches (ChainRule result cache) stamp/validate against it — a graph
    // change can never serve a stale chain result.
    if (this.arbiter && typeof this.arbiter._graphVersion === 'number') {
      this.arbiter._graphVersion++;
    }
    this._caches.invalidateRelationCaches(srcId, relation, dstId);
  }

  _batchInvalidateCaches(cacheKeys) {
    this._caches.batchInvalidateCaches(cacheKeys);
  }

  _cleanupCaches() {
    this._caches.cleanupCaches();
  }

  _clearAllCaches() {
    this._caches.clearAll();
    this._relationGraph.clear();
  }

  /**
   * Get cache performance statistics
   */
  getCacheStats() {
    const totalRequests = this._cacheHits + this._cacheMisses;
    return {
      cacheHits: this._cacheHits,
      cacheMisses: this._cacheMisses,
      hitRate: totalRequests > 0 ? (this._cacheHits / totalRequests).toFixed(3) : '0.000',
      relationCacheSize: this._caches.relationLookupCache.size(), // .size() for hLRU
      valueCacheSize: this._caches.valueLookupCache.size(), // .size() for hLRU
      batchOperations: this._batchOperationCount
    };
  }

  /**
   * Reset cache statistics
   */
  resetCacheStats() {
    this._cacheHits = 0;
    this._cacheMisses = 0;
    this._batchOperationCount = 0;
  }

  /**
   * Update PLTC indices when a relation is added
   * @private
   * @param {number} srcId - Source node ID
   * @param {number} dstId - Destination node ID
   * @param {string|null} relation - Relation name (null for batch operations)
   */
  _updatePLTCIndicesOnAdd(srcId, dstId, relation = null) {
    const reachabilityChecker = this.arbiter.reachabilityChecker;
    
    if (!reachabilityChecker) {
      return; // ReachabilityChecker not initialized
    }
    
    try {
      // PLTC handles SCC internally - pass original node IDs
      reachabilityChecker.addEdgeDynamic(srcId, dstId);
      
      // No ChainRule re-materialization needed - PLTC handles incremental updates automatically
    } catch (error) {
      // Silently handle errors - PLTC update failures shouldn't break relation addition
      // The index will be rebuilt on next initialization if needed
      if (this.arbiter.logImpl?.warn) {
        this.arbiter.logImpl.warn({ err: error }, 'PLTC index update failed on relation add');
      }
    }
  }

  /**
   * Update PLTC indices when a relation is removed
   * @private
   * @param {number} srcId - Source node ID
   * @param {number} dstId - Destination node ID
   * @param {string|null} relation - Relation name (null for batch operations)
   */
  _updatePLTCIndicesOnRemove(srcId, dstId, relation = null) {
    const reachabilityChecker = this.arbiter.reachabilityChecker;
    
    if (!reachabilityChecker) {
      return; // ReachabilityChecker not initialized
    }
    
    try {
      // PLTC handles SCC internally - pass original node IDs
      reachabilityChecker.removeEdgeDynamic(srcId, dstId);
      
      // No ChainRule re-materialization needed - PLTC handles incremental updates automatically
    } catch (error) {
      // Silently handle errors - PLTC update failures shouldn't break relation removal
      // The index will be rebuilt on next initialization if needed
      console.warn('PLTC index update failed on relation remove:', error);
    }
  }
} 
