/**
 * Graph analysis operations including access path analysis and rule evaluation
 */
export class GraphAnalysis {
  constructor(arbiter, authChecker) {
    this.arbiter = arbiter;
    this.authChecker = authChecker;
    this.relationConfigs = new Map();
  }

  /**
   * Set relation configuration
   * @param {string} relation - Relation name
   * @param {object} config - Configuration object
   */
  setRelationConfig(relation, config) {
    this.relationConfigs.set(relation, config);
  }

  /**
   * Analyze all possible paths that would grant access
   * @param {string} userKey - User key
   * @param {string} relation - Relation to check
   * @param {string} objectKey - Object key
   * @returns {Array} Array of access hints
   */
  analyzeAccessPaths(userKey, relation, objectKey) {
    const hints = [];
    const config = this.relationConfigs.get(relation);
    
    if (!config) {
      return hints;
    }
    
    // Helper to check if a relation exists
    const hasRelation = (src, rel, dst) => {
      const srcId = this.arbiter.nodeIdByKey.get(src);
      const dstId = dst ? this.arbiter.nodeIdByKey.get(dst) : null;
      
      return this.arbiter.relations.some(r => 
        r.src === srcId && 
        r.rel === rel && 
        (dstId === null || r.dst === dstId)
      );
    };
    
    // Analyze based on rule type
    if (config.type) {
      hints.push(...this._analyzeRule(config, userKey, objectKey, hasRelation));
    }
    
    // Analyze union rules (any would work)
    if (config.union) {
      for (const rule of config.union) {
        const ruleHints = this._analyzeRule(rule, userKey, objectKey, hasRelation);
        hints.push(...ruleHints);
      }
    }
    
    // Analyze intersection rules (all are needed)
    if (config.intersection) {
      const missingParts = [];
      for (const rule of config.intersection) {
        const partResult = this._checkRule(rule, userKey, objectKey);
        if (partResult.possibility === 0) {
          const ruleHints = this._analyzeRule(rule, userKey, objectKey, hasRelation);
          if (ruleHints.length > 0) {
            missingParts.push({
              rule: rule,
              alternatives: ruleHints
            });
          }
        }
      }
      
      if (missingParts.length > 0) {
        hints.push({
          type: 'intersection_requirements',
          description: 'All of the following are required',
          requirements: missingParts,
          priority: config.priority || 5
        });
      }
    }
    
    // Analyze exclusion rules (need base without exclusion)
    if (config.exclusion) {
      const [baseRule, excludeRule] = config.exclusion;
      const baseResult = this._checkRule(baseRule, userKey, objectKey);
      const excludeResult = this._checkRule(excludeRule, userKey, objectKey);
      
      if (baseResult.possibility === 0) {
        // Need to satisfy base rule
        const baseHints = this._analyzeRule(baseRule, userKey, objectKey, hasRelation);
        hints.push(...baseHints);
      } else if (excludeResult.possibility > 0) {
        // Base is satisfied but excluded - need to remove exclusion
        hints.push({
          type: 'remove_exclusion',
          description: 'Access is blocked by an exclusion rule',
          exclusion: excludeRule,
          suggestion: 'Remove or resolve the blocking condition',
          priority: 10
        });
      }
    }
    
    // Sort by priority (higher priority first)
    return hints.sort((a, b) => (b.priority || 5) - (a.priority || 5));
  }

  /**
   * Analyze a single rule to find what would satisfy it
   * @param {object} rule - Rule to analyze
   * @param {string} userKey - User key
   * @param {string} objectKey - Object key
   * @param {Function} hasRelation - Function to check if relation exists
   * @returns {Array} Array of hints
   */
  _analyzeRule(rule, userKey, objectKey, hasRelation) {
    const hints = [];
    
    switch (rule.type) {
      case 'direct':
        const relationToCheck = rule.relation || 'can_read'; // Default fallback
        if (!hasRelation(userKey, relationToCheck, objectKey)) {
          hints.push({
            type: 'add_direct_relation',
            description: `Need direct ${relationToCheck} permission`,
            relation: relationToCheck,
            from: userKey,
            to: objectKey,
            priority: rule.priority || 8
          });
        }
        break;
        
      case 'tuple_to_userset':
        // Find what groups/roles would grant access
        const potentialGroups = this._findGroupsWithAccess(rule, objectKey);
        if (potentialGroups.length > 0) {
          hints.push({
            type: 'join_group',
            description: `Join a group that has ${rule.tuplesetRelation} permission`,
            relation: rule.computedRelation,
            groups: potentialGroups,
            priority: rule.priority || 6
          });
        }
        break;
        
      case 'parent':
        // Find parent objects that would grant access
        const parents = this._findAccessibleParents(rule, userKey);
        if (parents.length > 0) {
          hints.push({
            type: 'get_parent_access',
            description: 'Get access to a parent object',
            parentRelation: rule.parentRelation,
            accessibleParents: parents,
            priority: rule.priority || 5
          });
        }
        break;
        
      case 'computed':
        // Recursively analyze the computed relation
        const computedHints = this.analyzeAccessPaths(userKey, rule.relation, objectKey);
        hints.push(...computedHints.map(h => ({
          ...h,
          via: 'computed',
          computedRelation: rule.relation
        })));
        break;
        
      case 'similar_to':
        hints.push({
          type: 'similarity_based',
          description: 'Access could be granted based on similarity to other users',
          relation: rule.relation,
          threshold: rule.similarityThreshold,
          suggestion: 'Build more similar access patterns',
          priority: rule.priority || 3
        });
        break;
        
    }
    
    return hints;
  }

  /**
   * Find groups that have access to an object
   * @param {object} rule - Rule configuration
   * @param {string} objectKey - Object key
   * @returns {Array} Array of groups with access
   */
  _findGroupsWithAccess(rule, objectKey) {
    const groups = [];
    const objectId = this.arbiter.nodeIdByKey.get(objectKey);
    
    // Find all relations that grant access to the object
    const accessRelations = this.arbiter.relations.filter(r => 
      r.dst === objectId && 
      r.rel === rule.tuplesetRelation
    );
    
    // Get the group keys - include any entity that has access, not just groups
    for (const rel of accessRelations) {
      const entityKey = this.arbiter.keyByNodeId.get(rel.src);
      if (entityKey) {
        groups.push({
          key: entityKey,
          confidence: rel.possibility || 1.0
        });
      }
    }
    
    return groups;
  }

  /**
   * Find parent objects the user has access to
   * @param {object} rule - Rule configuration
   * @param {string} userKey - User key
   * @returns {Array} Array of accessible parents
   */
  _findAccessibleParents(rule, userKey) {
    const parents = [];
    const userId = this.arbiter.nodeIdByKey.get(userKey);
    
    // Find objects the user has access to with the same relation we're checking
    const accessRelation = rule.relation || 'can_read'; // Use rule's relation or default
    const userAccess = this.arbiter.relations.filter(r => 
      r.src === userId && 
      r.rel === accessRelation
    );
    
    // Check which ones are parents
    for (const access of userAccess) {
      const objectKey = this.arbiter.keyByNodeId.get(access.dst);
      if (objectKey) {
        // Check if this object is a parent of something
        const childRelations = this.arbiter.relations.filter(r => 
          r.dst === access.dst && 
          r.rel === rule.parentRelation
        );
        
        if (childRelations.length > 0) {
          parents.push({
            key: objectKey,
            childCount: childRelations.length
          });
        }
      }
    }
    
    return parents;
  }

  /**
   * Helper to check a single rule
   * @param {object} rule - Rule to check
   * @param {string} userKey - User key
   * @param {string} objectKey - Object key
   * @returns {object} Check result
   */
  _checkRule(rule, userKey, objectKey) {
    // Temporarily set up the rule as a relation config
    const tempRelation = `_temp_${Date.now()}`;
    this.relationConfigs.set(tempRelation, rule);
    
    try {
      return this.authChecker.check(userKey, tempRelation, objectKey);
    } finally {
      this.relationConfigs.delete(tempRelation);
    }
  }
}
