/**
 * Graph operations including traversal, reachability, and path finding
 */
export class GraphOperations {
  constructor(arbiter, traversal, reachabilityChecker) {
    this.arbiter = arbiter;
    this.traversal = traversal;
    this.reachabilityChecker = reachabilityChecker;
  }

  /**
   * Estimate graph distance between two nodes
   * @param {string} key1 - First node key
   * @param {string} key2 - Second node key
   * @returns {number} Estimated distance
   */
  estimateGraphDistance(key1, key2) {
    return this.traversal.estimateGraphDistance(key1, key2);
  }

  /**
   * Get shortest path length between two nodes
   * @param {string} key1 - First node key
   * @param {string} key2 - Second node key
   * @returns {number} Shortest path length
   */
  shortestPathLength(key1, key2) {
    return this.traversal.shortestPathLength(key1, key2);
  }

  /**
   * Walk from a starting node
   * @param {string} startKey - Starting node key
   * @param {number} steps - Number of steps to take
   * @param {Function} stepFn - Step function
   * @param {Function} neighborWeightsFn - Neighbor weights function
   * @returns {Array} Walk results
   */
  walk(startKey, steps, stepFn = () => {}, neighborWeightsFn = null) {
    return this.traversal.walk(startKey, steps, stepFn, neighborWeightsFn);
  }

  /**
   * Perform Monte Carlo walk
   * @param {string} startKey - Starting node key
   * @param {number} steps - Number of steps
   * @param {object} options - Walk options
   * @returns {Array} Walk results
   */
  monteCarloWalk(startKey, steps, options = {}) {
    return this.traversal.monteCarloWalk(startKey, steps, options);
  }

  /**
   * Get situation tree for a node
   * @param {number} nodeId - Node ID
   * @param {number} maxDepth - Maximum depth
   * @param {Set} visited - Visited nodes set
   * @returns {object} Situation tree
   */
  getSituationTree(nodeId, maxDepth = 2, visited = new Set()) {
    return this.traversal.getSituationTree(nodeId, maxDepth, visited);
  }

  /**
   * Find paths between two nodes
   * @param {number} startId - Start node ID
   * @param {number} endId - End node ID
   * @param {string} relation - Relation type
   * @param {number} maxDepth - Maximum depth
   * @param {Set} visited - Visited nodes
   * @param {Array} currentPath - Current path
   * @param {number} currentPoss - Current possibility
   * @param {boolean} reverse - Whether to traverse in reverse
   * @returns {Array} Found paths
   */
  findPaths(startId, endId, relation, maxDepth, visited, currentPath = [], currentPoss = 1.0, reverse = false) {
    return this.traversal.findPaths(startId, endId, relation, maxDepth, 0, 1.0, visited, currentPath, currentPoss, 1.0, reverse);
  }

  /**
   * Check if one node is reachable from another
   * @param {string} sourceKey - Source node key
   * @param {string} targetKey - Target node key
   * @returns {boolean} True if reachable
   */
  isReachable(sourceKey, targetKey) {
    const sourceId = this.arbiter.nodeIdByKey.get(sourceKey);
    const targetId = this.arbiter.nodeIdByKey.get(targetKey);
    
    if (sourceId === undefined || targetId === undefined) {
      return false;
    }
    
    return this.reachabilityChecker.isReachable(sourceId, targetId);
  }

  /**
   * Get all nodes reachable from source
   * @param {string} sourceKey - Source node key
   * @param {number} maxResults - Maximum number of results
   * @returns {Array} Array of reachable node keys
   */
  getReachableNodes(sourceKey, maxResults = 1000) {
    const sourceId = this.arbiter.nodeIdByKey.get(sourceKey);
    
    if (sourceId === undefined) {
      return [];
    }
    
    const reachableIds = this.reachabilityChecker.getReachableNodes(sourceId, maxResults);
    return reachableIds.map(id => this.arbiter.keyByNodeId.get(id)).filter(key => key !== undefined);
  }

  /**
   * Get all nodes that can reach target
   * @param {string} targetKey - Target node key
   * @param {number} maxResults - Maximum number of results
   * @returns {Array} Array of reaching node keys
   */
  getReachingNodes(targetKey, maxResults = 1000) {
    const targetId = this.arbiter.nodeIdByKey.get(targetKey);
    
    if (targetId === undefined) {
      return [];
    }
    
    const reachingIds = this.reachabilityChecker.getReachingNodes(targetId, maxResults);
    return reachingIds.map(id => this.arbiter.keyByNodeId.get(id)).filter(key => key !== undefined);
  }

  /**
   * Get reachability checker statistics
   * @returns {object} Reachability statistics
   */
  getReachabilityStats() {
    return this.reachabilityChecker.getStats();
  }

  /**
   * Adapt to false positive in reachability indexes
   * @param {string} sourceKey - Source node key
   * @param {string} targetKey - Target node key
   * @param {string} falsePositiveKey - False positive key
   * @param {string} conflictingKey - Conflicting key
   * @returns {boolean} True if adaptation was successful
   */
  adaptToFalsePositive(sourceKey, targetKey, falsePositiveKey, conflictingKey) {
    const sourceId = this.arbiter.nodeIdByKey.get(sourceKey);
    const targetId = this.arbiter.nodeIdByKey.get(targetKey);
    
    if (sourceId === undefined || targetId === undefined) {
      return false;
    }
    
    return this.reachabilityChecker.adaptToFalsePositive(sourceId, targetId, falsePositiveKey, conflictingKey);
  }

  /**
   * Get adaptation statistics
   * @returns {object} Adaptation statistics
   */
  getAdaptationStats() {
    return this.reachabilityChecker.getAdaptationStats();
  }

  /**
   * Initialize reachability checker
   * @param {object} options - Initialization options
   * @returns {Object} Initialization stats
   */
  initializeReachabilityChecker(options = {}) {
    return this.reachabilityChecker.initialize(options);
  }
}
