/**
 * Optimized Reachability Checker using PLTC
 * 
 * This class provides optimized reachability checking using PLTC (Parameterized Linear Time Closure)
 * for O(1) reachability queries with 100% accuracy.
 */

import { DynamicPLTC, TransactionalDynamicPLTC } from '@tenere/pltc-core';

export class ReachabilityChecker {
  constructor(arbiter) {
    this.arbiter = arbiter;
    this.pltcIndex = null;
    this.pltcBackwardIndex = null;
    this._useTransactionalPLTC = false;
    this._pltcBatchDepth = 0;
    // Map<relationName, Set<relationType>> - tracks which direct relation types are used by ChainRules
    this.chainRuleDependencies = new Map();
    this.stats = {
      totalQueries: 0,
      pltcHits: 0,
      fallbackQueries: 0,
      averageQueryTime: 0,
      materializedChainRules: 0,
      materializedLogicalEdges: 0
    };
  }

  /**
   * Initialize the reachability checker with PLTC strategy
   * PLTC handles SCC condensation internally
   */
  initialize(options = {}) {
    const { enableBackwardIndex = true, transactionalPLTC = null } = options;
    if (transactionalPLTC !== null) {
      this._useTransactionalPLTC = transactionalPLTC === true;
    } else {
      this._useTransactionalPLTC = this.arbiter?.options?.transactionalPLTC === true;
    }

    // Extract edges from arbiter relations (raw edges - PLTC will handle SCC internally)
    const forwardEdges = this._extractEdges('forward');
    
    // Forward index (for forward traversal rules)
    // PLTC will handle SCC condensation internally
    this.pltcIndex = this._useTransactionalPLTC
      ? new TransactionalDynamicPLTC()
      : new DynamicPLTC();
    const initStats = this.pltcIndex.initialize(forwardEdges);
    const sccCount = initStats.sccCount !== undefined ? initStats.sccCount : 'N/A';
    if (this.arbiter.logImpl?.info) {
      this.arbiter.logImpl.info({ nodes: initStats.nodes, edges: initStats.edges, sccs: sccCount }, 'PLTC initialized');
    }
    
    // Backward index (for backward traversal rules)
    if (enableBackwardIndex) {
      const backwardEdges = this._extractEdges('backward');
      this.pltcBackwardIndex = this._useTransactionalPLTC
        ? new TransactionalDynamicPLTC()
        : new DynamicPLTC();
      this.pltcBackwardIndex.initialize(backwardEdges);
    }

    // Track ChainRule dependencies (for potential future use)
    // No materialization needed - PLTC's transitive closure on base edges is sufficient
    if (this.arbiter.relationConfigs) {
      for (const [relationName, config] of this.arbiter.relationConfigs.entries()) {
        if (config && config.type === 'chain') {
          this._trackChainRuleDependencies(relationName, config);
        }
      }
    }

    return this.getStats();
  }

  beginUpdateBatch() {
    if (!this._useTransactionalPLTC) return;
    this._pltcBatchDepth++;
    if (this._pltcBatchDepth !== 1) return;
    if (this.pltcIndex && this.pltcIndex.initialized) {
      this.pltcIndex.beginTransaction();
    }
    if (this.pltcBackwardIndex && this.pltcBackwardIndex.initialized) {
      this.pltcBackwardIndex.beginTransaction();
    }
  }

  commitUpdateBatch() {
    if (!this._useTransactionalPLTC) return;
    if (this._pltcBatchDepth === 0) return;
    this._pltcBatchDepth--;
    if (this._pltcBatchDepth !== 0) return;
    if (this.pltcIndex && this.pltcIndex.initialized && this.pltcIndex.currentTransaction) {
      this.pltcIndex.commitTransaction();
    }
    if (this.pltcBackwardIndex && this.pltcBackwardIndex.initialized && this.pltcBackwardIndex.currentTransaction) {
      this.pltcBackwardIndex.commitTransaction();
    }
  }

  addEdgeDynamic(sourceId, targetId) {
    if (this._useTransactionalPLTC) {
      if (this.pltcIndex && this.pltcIndex.initialized) {
        this.pltcIndex.insertEdge(sourceId, targetId);
        if (this._pltcBatchDepth === 0) {
          this.pltcIndex.commitTransaction();
        }
      }
      if (this.pltcBackwardIndex && this.pltcBackwardIndex.initialized) {
        this.pltcBackwardIndex.insertEdge(targetId, sourceId);
        if (this._pltcBatchDepth === 0) {
          this.pltcBackwardIndex.commitTransaction();
        }
      }
      return;
    }

    if (this.pltcIndex && this.pltcIndex.initialized) {
      this.pltcIndex.addEdgeDynamic(sourceId, targetId);
    }
    if (this.pltcBackwardIndex && this.pltcBackwardIndex.initialized) {
      this.pltcBackwardIndex.addEdgeDynamic(targetId, sourceId);
    }
  }

  removeEdgeDynamic(sourceId, targetId) {
    const edgeCount = this.arbiter.relations.filter(
      r => r.src === sourceId && r.dst === targetId
    ).length;
    if (edgeCount > 1) return; // Still reachable via other parallel edges
    if (this._useTransactionalPLTC) {
      if (this.pltcIndex && this.pltcIndex.initialized) {
        this.pltcIndex.deleteEdge(sourceId, targetId);
        if (this._pltcBatchDepth === 0) {
          this.pltcIndex.commitTransaction();
        }
      }
      if (this.pltcBackwardIndex && this.pltcBackwardIndex.initialized) {
        this.pltcBackwardIndex.deleteEdge(targetId, sourceId);
        if (this._pltcBatchDepth === 0) {
          this.pltcBackwardIndex.commitTransaction();
        }
      }
      return;
    }

    if (this.pltcIndex && this.pltcIndex.initialized) {
      this.pltcIndex.removeEdgeDynamic(sourceId, targetId);
    }
    if (this.pltcBackwardIndex && this.pltcBackwardIndex.initialized) {
      this.pltcBackwardIndex.removeEdgeDynamic(targetId, sourceId);
    }
  }

  /**
   * Extract edges from arbiter relations
   * @param {string} direction - 'forward' or 'backward'
   * @returns {Array<Array<number>>} Array of [source, target] edges
   */
  _extractEdges(direction = 'forward') {
    const edges = [];
    const seen = new Set();
    
    for (const relation of this.arbiter.relations) {
      const { src, dst } = relation;
      
      if (direction === 'forward') {
        const edgeKey = `${src}:${dst}`;
        if (!seen.has(edgeKey)) {
          seen.add(edgeKey);
          edges.push([src, dst]);
        }
      } else {
        // Backward: reverse the edge direction
        const edgeKey = `${dst}:${src}`;
        if (!seen.has(edgeKey)) {
          seen.add(edgeKey);
          edges.push([dst, src]);
        }
      }
    }
    
    return edges;
  }

  /**
   * Check if there's a path from source to target
   * 
   * PLTC provides 100% accurate reachability checks (no false negatives).
   * Returns:
   * - false: Definitely no path exists (fast negative filtering)
   * - true: Path exists, but rule evaluation still needs to check relation types
   * - null: PLTC not initialized, delegate to rule evaluation
   */
  isReachable(sourceId, targetId, options = {}) {
    const startTime = Date.now();
    this.stats.totalQueries++;

    try {
      // Use PLTC index for fast reachability check
      const hasPath = this._checkWithPLTC(sourceId, targetId, options);
      
      // If PLTC index is not available, delegate to rule evaluation
      if (hasPath === null) {
        this.stats.fallbackQueries++;
        return null; // Let the rule handle the reachability check
      }
      
      // Update statistics
      const queryTime = Date.now() - startTime;
      this.stats.averageQueryTime = 
        (this.stats.averageQueryTime * (this.stats.totalQueries - 1) + queryTime) / this.stats.totalQueries;

      this.stats.pltcHits++;
      
      // PLTC is 100% accurate - no uncertainty
      // If false: Definitely no path (fail fast)
      // If true: Path exists, but rule evaluation needs to check relation types
      return hasPath;

    } catch (error) {
      // If PLTC index fails, return null to let the rule handle it
      console.warn('PLTC index failed, delegating to rule evaluation:', error);
      this.stats.fallbackQueries++;
      return null; // Let the rule handle the reachability check
    }
  }

  /**
   * Check reachability using PLTC index
   * PLTC handles SCC checking internally
   * 
   * @param {number} sourceId - Source node ID
   * @param {number} targetId - Target node ID
   * @param {Object} options - Options (direction: 'forward' | 'backward')
   * @returns {boolean|null} True if reachable, false if not, null if index not available
   */
  _checkWithPLTC(sourceId, targetId, options = {}) {
    const { direction = 'forward' } = options;
    
    // Select appropriate index based on direction
    const index = direction === 'backward' ? this.pltcBackwardIndex : this.pltcIndex;
    
    if (!index || !index.initialized) {
      return null; // Return null to indicate PLTC index is not available
    }
    
    // Query PLTC directly - it handles SCC checking internally
    return index.query(sourceId, targetId);
      }

  /**
   * Get all nodes reachable from source
   * Note: PLTC doesn't provide enumeration, so we fall back to BFS
   */
  getReachableNodes(sourceId, maxResults = 1000) {
    // PLTC doesn't support enumeration, use fallback
    return this._getReachableNodesFallback(sourceId, maxResults);
  }

  /**
   * Get all nodes that can reach target
   * Note: PLTC doesn't provide enumeration, so we fall back to BFS
   */
  getReachingNodes(targetId, maxResults = 1000) {
    // PLTC doesn't support enumeration, use fallback
    return this._getReachingNodesFallback(targetId, maxResults);
  }

  /**
   * Fallback method for getting reachable nodes
   */
  _getReachableNodesFallback(sourceId, maxResults) {
    const visited = new Set();
    const queue = [sourceId];
    const result = [];
    visited.add(sourceId);

    while (queue.length > 0 && result.length < maxResults) {
      const current = queue.shift();
      result.push(current);

      const outgoing = this.arbiter.relations.filter(r => r.src === current);
      
      for (const relation of outgoing) {
        const next = relation.dst;
        if (!visited.has(next)) {
          visited.add(next);
          queue.push(next);
        }
      }
    }

    return result;
  }

  /**
   * Fallback method for getting reaching nodes
   */
  _getReachingNodesFallback(targetId, maxResults) {
    const visited = new Set();
    const queue = [targetId];
    const result = [];
    visited.add(targetId);

    while (queue.length > 0 && result.length < maxResults) {
      const current = queue.shift();
      result.push(current);

      const incoming = this.arbiter.relations.filter(r => r.dst === current);
      
      for (const relation of incoming) {
        const prev = relation.src;
        if (!visited.has(prev)) {
          visited.add(prev);
          queue.push(prev);
        }
      }
    }

    return result;
  }

  /**
   * Get statistics
   */
  getStats() {
    return {
      ...this.stats,
      strategy: 'pltc',
      pltcInitialized: this.pltcIndex ? this.pltcIndex.initialized : false,
      pltcBackwardInitialized: this.pltcBackwardIndex ? this.pltcBackwardIndex.initialized : false,
      pltcStats: this.pltcIndex ? {
        initialized: this.pltcIndex.initialized,
        graphStats: this.pltcIndex.graph ? this.pltcIndex.graph.getStats() : null
      } : null,
      sccCount: this.pltcIndex?.sccUnionFind?.getComponentCount() || 0,
      materializedChainRules: this.stats.materializedChainRules || 0,
      materializedLogicalEdges: this.stats.materializedLogicalEdges || 0
    };
  }

  /**
   * Track ChainRule dependencies (which relation types are used in the chain)
   * No materialization needed - PLTC's transitive closure on base edges handles reachability
   * @param {string} relationName - The relation name (e.g., 'employee_resource_access')
   * @param {Object} config - The ChainRule configuration
   */
  _trackChainRuleDependencies(relationName, config) {
    if (config.type !== 'chain') {
      return;
    }

    const { steps } = config;
    if (!steps || steps.length === 0) {
      return;
    }

    // Track which direct relation types are used by this ChainRule
    const dependencies = this._findChainDependencies(steps);
    this.chainRuleDependencies.set(relationName, dependencies);
  }

  /**
   * Find which direct relation types are used by a ChainRule (for incremental updates)
   * @param {Array} steps - Chain steps
   * @returns {Set<string>} Set of relation names used in the chain
   */
  _findChainDependencies(steps) {
    const dependencies = new Set();
    for (const step of steps) {
      if (step.relation) {
        dependencies.add(step.relation);
    }
    }
    return dependencies;
  }


  /**
   * Clear all indexes
   */
  clear() {
    if (this.pltcIndex) {
      this.pltcIndex = null;
    }
    if (this.pltcBackwardIndex) {
      this.pltcBackwardIndex = null;
    }
    this.chainRuleDependencies.clear();
    this.stats = {
      totalQueries: 0,
      pltcHits: 0,
      fallbackQueries: 0,
      averageQueryTime: 0,
      materializedChainRules: 0,
      materializedLogicalEdges: 0
    };
  }

  adaptToFalsePositive(sourceId, targetId, falsePositiveKey, conflictingKey) {
    // PLTC provides 100% accuracy — no false positives to adapt.
    // Stub for AQF backward compatibility.
    return { adapted: false, reason: 'pltc_no_false_positives' };
  }

  getAdaptationStats() {
    return {
      adaptations: 0,
      falsePositivesDetected: 0,
      reason: 'pltc_100_percent_accuracy'
    };
  }
}
