import { BaseRule } from './BaseRule.js';
import { Arbiter } from '../../core/Arbiter.js';
import { OWAFusion } from '../../utils/OWAFusion.js';

/**
 * ParentRule - Evaluates access through parent-child relationships (hierarchical inheritance)
 * 
 * This rule implements hierarchical access where access is granted if:
 * 1. Object has a parent relationship to another entity (e.g., file -> folder)
 * 2. A direct edge exists from the user to that parent entity for the target relation
 * 
 * Access is checked via direct edge lookup on the parent (not recursive authorization).
 * This prevents infinite recursion when parent entities also use ParentRule definitions.
 * Full transitive parent evaluation requires multi-hop configurations (MultiHopRule).
 * 
 * Returns single possibility value representing the likelihood that user has access
 * through the parent hierarchy. Uses minPossibility as a cutoff threshold.
 * 
 * Configuration:
 * {
 *   type: 'parent',
 *   parentRelation: string,      // Relation defining parent-child (default: 'parent')
 *   relation: string,            // Relation to check on parent (uses currentRelation if not specified)
 *   reverse: boolean,            // Check in reverse direction (default: false)
 *   aggregator?: string,         // OWA aggregation method for multiple parents
 *   owaWeights?: Array<number>,  // Custom OWA weights for fusion
 *   reliabilityWeighting?: boolean // Weight by reliability
 * }
 */
export class ParentRule extends BaseRule {
  constructor(arbiter) {
    super(arbiter);
  }

  /**
   * Evaluate parent relationship with reachability optimization
   * @protected
   */
  _evaluateRule(userId, userKey, objectId, objectKey, rule, visited, currentRelation, options) {
    const { minPossibility = 0.0, includeMeta = true, trackEvaluation = false } = options;
    const includeOwaTrace = trackEvaluation && includeMeta;
    const owaTraceOptions = includeOwaTrace ? { includeTrace: true } : null;
    
    const reverse = rule.reverse || false;
    const parentRelName = rule.parentRelation || 'parent';
    
    // Get parent relationships
    let parentRels;
    if (reverse) {
      parentRels = this.arbiter.relationManager.getRelationsFromSrc(objectId, parentRelName, options);
    } else {
      parentRels = this.arbiter.relationManager.getRelationsToDst(objectId, parentRelName, options);
    }
    
    // Filter parent relationships by reachability
    const reachableParents = [];
    for (const rel of parentRels) {
      const parentId = reverse ? rel.dst : rel.src;
      const parentKey = this.arbiter.resolveKey(parentId, options);
      
      if (!parentKey) continue;
      
      // Quick reachability check for parent
      const isReachable = this._quickReachabilityCheck(userKey, parentKey);
      if (isReachable !== false) { // Include reachable or unknown
        reachableParents.push(rel);
      }
    }
    
    // Use reachable parents for evaluation
    parentRels = reachableParents;
    
    
    let possibilities = [], metas = [], reasons = [];
    
    // Track if there were any direct parent relationships (regardless of threshold)
    let anyDirectParent = parentRels.length > 0;
    
    // Check for circular dependencies first
    for (const rel of parentRels) {
      const parentId = reverse ? rel.dst : rel.src;
      const parentKey = this.arbiter.resolveKey(parentId, options);
      
      if (!parentKey) continue;
      
      // Check if this would create a cycle: if parentKey is the same as userKey
      if (parentKey === userKey) {
          return {
            possibility: 0,
            ...(includeMeta && {
              meta: {
                parentRule: {
                  type: 'cycle_detected',
                  parentRelation: parentRelName,
                  cyclePath: [userKey, objectKey, userKey]
                }
              }
            }),
            reason: 'cycle'
          };
        }
    }
    
    // Process direct parent relationships
    const targetRelation = rule.relation || currentRelation;
    for (const rel of parentRels) {
      const parentId = reverse ? rel.dst : rel.src;
      const parentKey = this.arbiter.resolveKey(parentId, options);
      
      if (!parentKey) continue;
      
      if (parentKey === userKey) continue;
      
      // Check direct edges from user to parent entity, bypassing the relation config
      // (which would re-enter the parent rule). This implements the contract:
      // "User has access to that parent entity" via direct relation check.
      const parentResult = this.arbiter.indices.getDirectRelation(userId, targetRelation, parentId);
      const directRelFromPartial = options.partialGraphContext
        ? options.partialGraphContext.getDirectRelation(userId, targetRelation, parentId)
        : null;
      const directRel = parentResult || directRelFromPartial;
      const parentPossibility = directRel ? (directRel.possibility ?? 1.0) : 0;
      
      // Apply cutoff threshold
      const finalPossibility = parentPossibility >= minPossibility ? parentPossibility : 0;
      
      if (finalPossibility > 0) {
        possibilities.push(finalPossibility);
        metas.push(includeMeta ? {
          parentRule: {
            type: 'parent_access_checked',
            parentKey,
            parentRelation: parentRelName,
            targetRelation,
            parentAccessPossibility: parentPossibility
          }
        } : null);
      }
    }
    
    if (!possibilities.length) {
      // If there were any direct parents, but all were below threshold, cutoff was applied
      const cutoffApplied = anyDirectParent;
      return {
        possibility: 0,
        ...(includeMeta && {
          meta: {
            parentRule: {
              type: 'no_parent_relationship_found',
              parentRelation: parentRelName,
              directParentsSearched: parentRels.length,
              threshold: minPossibility,
              cutoffAppliedPostFusion: cutoffApplied
            }
          }
        }),
        reason: 'no_parent_relationship_path_above_threshold'
      };
    }
    
    // Apply OWA fusion to combine possibilities of multiple parent relationships existing
    let result;
    const aggregator = rule.aggregator || 'max'; // Default to max: if any parent relationship exists strongly, that's enough
    
    if (rule.aggregator || rule.owaWeights) {
      const weights = rule.owaWeights || OWAFusion.generateOWAWeights(possibilities.length, aggregator);
      result = OWAFusion.fuseWithMeta(possibilities, metas, weights, aggregator, true, owaTraceOptions);
    } else {
      result = OWAFusion.fuseWithMeta(possibilities, metas, null, aggregator, true, owaTraceOptions);
    }
    
    // Final cutoff check after fusion
    const finalFusedPossibility = result.value >= minPossibility ? result.value : 0;

    // Always set cutoffAppliedPostFusion in meta
    const cutoffApplied = result.value !== finalFusedPossibility;
    const parentRuleMeta = includeMeta ? {
      ...(finalFusedPossibility > 0 ? result.meta?.parentRule : {}),
      finalOutcomeType: finalFusedPossibility > 0 ? 'parent_relationship_possible' : 'no_strong_parent_relationship',
      fusionMethod: aggregator,
      pathsConsidered: possibilities.length,
      cutoffAppliedPostFusion: cutoffApplied,
      threshold: minPossibility,
      ...(includeOwaTrace && result.trace ? {
        owa: {
          level: null,
          aggregator,
          weights: result.trace.weights,
          sortedValues: result.trace.sortedValues,
          contributions: result.trace.contributions,
          selectedIndex: result.trace.selectedIndex
        }
      } : {})
    } : null;

    return {
      possibility: finalFusedPossibility,
      ...(includeMeta && {
        meta: {
          ...(finalFusedPossibility > 0 ? result.meta : {}),
          parentRule: parentRuleMeta
        }
      }),
      reason: finalFusedPossibility > 0 ? 'parent_relationship_path_found' : 'no_parent_relationship_path_above_threshold'
    };
  }
}
