import { BaseRule } from './BaseRule.js';
import { OWAFusion, getOWAWeightsFromRule } from '../../utils/OWAFusion.js';
import { buildValidity } from '../../core/validity.js';
import { Arbiter } from '../../core/Arbiter.js';
import { BilatticeOrderings } from '../../qualitative/BilatticeOrderings.js';
import { QualitativeCapacity } from '../../qualitative/QualitativeCapacity.js';
import { QualitativeScale } from '../../qualitative/QualitativeScale.js';
import { buildRemediation, extractRemediation, mergeRemediationOptions } from '../remediation.js';

/**
 * LogicalOperators - Handles logical combinations of rules and defeasible logic with sophisticated evidential fusion
 * 
 * This rule implements both traditional logical operations and defeasible logic patterns with full OWA (Ordered Weighted Averaging) support
 * for nuanced evidential reasoning beyond simple max/min operations.
 * 
 * === EVIDENTIAL FUSION WITH OWA ===
 * Every logical operation supports sophisticated aggregation through OWAFusion:
 * 
 * Basic Aggregators:
 * - max: Maximum value (optimistic OR)
 * - min: Minimum value (pessimistic AND) 
 * - average: Equal weight average
 * - sum: Additive evidence (each contributes full weight)
 * 
 * Advanced Aggregators:
 * - majority: Focus on consensus (median or top 60% for larger sets)
 * - median: Pure median value
 * - optimistic: Exponential decay favoring higher values
 * - pessimistic: Exponential decay favoring lower values
 * - top2/top3: Equal weight on top N values
 * - priority: Weight by rule priority values
 * - custom: User-defined OWA weights
 * 
 * Example sophisticated union with evidential fusion:
 * {
 *   type: 'logical',
 *   union: {
 *     rules: [rule1, rule2, rule3],
 *     aggregator: 'majority',              // Use consensus-based fusion
 *     reliabilityWeighting: true,          // Weight by reliability scores
 *     owaWeights: [0.5, 0.3, 0.2]         // Custom evidential weights (optional)
 *   }
 * }
 * 
 * === DEFEASIBLE LOGIC STRUCTURE ===
 * {
 *   type: 'logical',
 *   never: {                         // ABSOLUTE DENIAL - If true, overrides everything
 *     union: {
 *       rules: Array<Rule>,
 *       aggregator?: string,         // OWA aggregation for absolute denials
 *       owaWeights?: Array<number>,
 *       reliabilityWeighting?: boolean
 *     }
 *   },
 *   unless: {                        // DEFEATERS - If true, defeats the rule
 *     union: {
 *       rules: Array<Rule>,
 *       aggregator?: string,         // OWA aggregation for defeaters
 *       owaWeights?: Array<number>,
 *       reliabilityWeighting?: boolean
 *     }
 *   },
 *   always: {                        // STRICT RULES - If true, wins against all (unless NEVER is true)
 *     direct: Rule,                  // Single rule that is strict
 *     aggregator?: string,
 *     owaWeights?: Array<number>,
 *     reliabilityWeighting?: boolean
 *   },
 *   when: {                          // DEFEASIBLE RULES - Potentially defeasible
 *     intersection: {
 *       rules: Array<Rule>,
 *       aggregator?: string,         // OWA aggregation for defeasible conditions
 *       owaWeights?: Array<number>,
 *       reliabilityWeighting?: boolean
 *     }
 *   },
 *   requires: {                      // INVERSE DEFEATERS - If not true, defeats
 *     union: {
 *       rules: Array<Rule>,
 *       aggregator?: string,         // OWA aggregation for requirements
 *       owaWeights?: Array<number>,
 *       reliabilityWeighting?: boolean
 *     }
 *   },
 *   
 *   // Mode control
 *   mode?: 'binary' | 'normal' | 'threshold',  // Evaluation mode
 *   priority?: number,                          // For tie-breaking in binary mode
 * }
 * 
 * === TRADITIONAL LOGICAL OPERATIONS WITH OWA ===
 * {
 *   type: 'logical',
 *   union: {
 *     rules: Array<Rule>,           // OR operation on child rules
 *     aggregator?: string,          // OWA aggregation method: max, average, majority, optimistic, etc.
 *     owaWeights?: Array<number>,   // Custom OWA weights for sophisticated evidential fusion
 *     reliabilityWeighting?: boolean // Weight results by reliability for this union
 *   },
 *   intersection: {
 *     rules: Array<Rule>,           // AND operation on child rules  
 *     aggregator?: string,          // OWA aggregation method: min, average, pessimistic, etc.
 *     owaWeights?: Array<number>,   // Custom OWA weights for sophisticated evidential fusion
 *     reliabilityWeighting?: boolean // Weight results by reliability for this intersection
 *   },
 *   exclusion: {
 *     rules: [Rule, Rule],          // A AND NOT B operation
 *     aggregator?: string,          // OWA aggregation method for exclusion evidence
 *     owaWeights?: Array<number>,   // Custom OWA weights for exclusion fusion
 *     reliabilityWeighting?: boolean // Weight results by reliability for this exclusion
 *   }
 * }
 * 
 * === EVALUATION MODES ===
 * - Binary mode: Classic defeasible logic with tie-breakers going to priority
 * - Normal mode: Possibilistic space where defeaters raise possibility of denial,
 *                Always sets minimum possible, When provides positive possibility that defeaters erode
 * - Threshold mode: Bail out to binary-like operation with fastPath early exits
 * 
 * === EVIDENTIAL REASONING EXAMPLES ===
 * 
 * Consensus-based authorization (majority rule):
 * {
 *   type: 'logical',
 *   union: {
 *     rules: [managerApproval, peerReview, systemCheck],
 *     aggregator: 'majority'  // Requires consensus, not just one approval
 *   }
 * }
 * 
 * Priority-weighted evidence:
 * {
 *   type: 'logical', 
 *   union: {
  *     rules: [
  *       { type: 'direct', priority: 10 },
  *       { type: 'inferred', priority: 5 },
  *       { type: 'computed', priority: 1 }
  *     ],
 *     aggregator: 'priority'  // Weight by rule priority values
 *   }
 * }
 * 
 * Custom evidential fusion:
 * {
 *   type: 'logical',
 *   intersection: {
 *     rules: [securityCheck, businessLogic, complianceRule],
 *     aggregator: 'custom',
 *     owaWeights: [0.6, 0.3, 0.1],  // Security most important, compliance least
 *     reliabilityWeighting: true     // Also consider reliability of each check
 *   }
 * }
 */
export class LogicalOperators extends BaseRule {
  constructor(arbiter, ruleEvaluator) {
    super(arbiter);
    this.ruleEvaluator = ruleEvaluator;
  }

  /**
   * Determine if this is a defeasible logic rule or traditional logical operation
   * @private
   */
  _isDefeasibleLogic(rule) {
    return !!(rule.never || rule.unless || rule.always || rule.when || rule.requires);
  }

  /**
   * Normalize rule configuration to support both new per-operation format and legacy format
   * @private
   */
  _normalizeRule(rule) {
    const normalized = { ...rule };
    
    // Handle defeasible logic structure
    if (this._isDefeasibleLogic(rule)) {
      return this._normalizeDefeasibleRule(normalized);
    }
    
    // Legacy logical operations normalization
    // Convert legacy array format to new object format with backward compatibility
    if (rule.union && Array.isArray(rule.union)) {
      normalized.union = {
        rules: rule.union,
        aggregator: rule.aggregator,
        owaWeights: rule.owaWeights,
        reliabilityWeighting: rule.reliabilityWeighting
      };
    } else if (rule.union && typeof rule.union === 'object' && rule.union.rules) {
      // New format, apply top-level defaults if not specified
      normalized.union = {
        ...rule.union,
        aggregator: rule.union.aggregator || rule.aggregator,
        owaWeights: rule.union.owaWeights || rule.owaWeights,
        reliabilityWeighting: rule.union.reliabilityWeighting !== undefined ? 
          rule.union.reliabilityWeighting : rule.reliabilityWeighting
      };
    } else if (rule.union && typeof rule.union === 'object') {
      // Handle case where union is an object but missing rules property - treat as legacy
      normalized.union = {
        rules: [],
        ...rule.union,
        // Apply top-level defaults if not specified
        aggregator: rule.union.aggregator || rule.aggregator,
        owaWeights: rule.union.owaWeights || rule.owaWeights,
        reliabilityWeighting: rule.union.reliabilityWeighting !== undefined ? 
          rule.union.reliabilityWeighting : rule.reliabilityWeighting
      };
    }
    
    if (rule.intersection && Array.isArray(rule.intersection)) {
      normalized.intersection = {
        rules: rule.intersection,
        aggregator: rule.aggregator,
        owaWeights: rule.owaWeights,
        reliabilityWeighting: rule.reliabilityWeighting
      };
    } else if (rule.intersection && typeof rule.intersection === 'object' && rule.intersection.rules) {
      normalized.intersection = {
        ...rule.intersection,
        aggregator: rule.intersection.aggregator || rule.aggregator,
        owaWeights: rule.intersection.owaWeights || rule.owaWeights,
        reliabilityWeighting: rule.intersection.reliabilityWeighting !== undefined ? 
          rule.intersection.reliabilityWeighting : rule.reliabilityWeighting
      };
    } else if (rule.intersection && typeof rule.intersection === 'object') {
      normalized.intersection = {
        rules: [],
        ...rule.intersection,
        aggregator: rule.intersection.aggregator || rule.aggregator,
        owaWeights: rule.intersection.owaWeights || rule.owaWeights,
        reliabilityWeighting: rule.intersection.reliabilityWeighting !== undefined ? 
          rule.intersection.reliabilityWeighting : rule.reliabilityWeighting
      };
    }
    
    if (rule.exclusion && Array.isArray(rule.exclusion)) {
      normalized.exclusion = {
        rules: rule.exclusion,
        aggregator: rule.aggregator,
        owaWeights: rule.owaWeights,
        reliabilityWeighting: rule.reliabilityWeighting
      };
    } else if (rule.exclusion && typeof rule.exclusion === 'object' && rule.exclusion.rules) {
      normalized.exclusion = {
        ...rule.exclusion,
        aggregator: rule.exclusion.aggregator || rule.aggregator,
        owaWeights: rule.exclusion.owaWeights || rule.owaWeights,
        reliabilityWeighting: rule.exclusion.reliabilityWeighting !== undefined ? 
          rule.exclusion.reliabilityWeighting : rule.reliabilityWeighting
      };
    } else if (rule.exclusion && typeof rule.exclusion === 'object') {
      normalized.exclusion = {
        rules: [],
        ...rule.exclusion,
        aggregator: rule.exclusion.aggregator || rule.aggregator,
        owaWeights: rule.exclusion.owaWeights || rule.owaWeights,
        reliabilityWeighting: rule.exclusion.reliabilityWeighting !== undefined ? 
          rule.exclusion.reliabilityWeighting : rule.reliabilityWeighting
      };
    }
    
    return normalized;
  }

  /**
   * Normalize defeasible logic rule structure
   * @private
   */
  _normalizeDefeasibleRule(rule) {
    const normalized = { ...rule };

    if (rule.union && Array.isArray(rule.union)) {
      normalized.union = {
        rules: rule.union,
        aggregator: rule.aggregator,
        owaWeights: rule.owaWeights,
        reliabilityWeighting: rule.reliabilityWeighting
      };
    } else if (rule.union && rule.union.rules) {
      normalized.union = {
        ...rule.union,
        aggregator: rule.union.aggregator || rule.aggregator,
        owaWeights: rule.union.owaWeights || rule.owaWeights,
        reliabilityWeighting: rule.union.reliabilityWeighting !== undefined ?
          rule.union.reliabilityWeighting : rule.reliabilityWeighting
      };
    }
    
    // Normalize never (absolute denials)
    if (rule.never) {
      if (rule.never.union && Array.isArray(rule.never.union)) {
        normalized.never = {
          union: {
            rules: rule.never.union,
            aggregator: rule.never.aggregator || rule.aggregator,
            owaWeights: rule.never.owaWeights || rule.owaWeights,
            reliabilityWeighting: rule.never.reliabilityWeighting !== undefined ? 
              rule.never.reliabilityWeighting : rule.reliabilityWeighting
          }
        };
      } else if (rule.never.union) {
        normalized.never = {
          union: {
            ...rule.never.union,
            aggregator: rule.never.union.aggregator || rule.never.aggregator || rule.aggregator,
            owaWeights: rule.never.union.owaWeights || rule.never.owaWeights || rule.owaWeights,
            reliabilityWeighting: rule.never.union.reliabilityWeighting !== undefined ? 
              rule.never.union.reliabilityWeighting : 
              (rule.never.reliabilityWeighting !== undefined ? rule.never.reliabilityWeighting : rule.reliabilityWeighting)
          }
        };
      }
    }
    
    // Normalize unless (defeaters)
    if (rule.unless) {
      if (rule.unless.union && Array.isArray(rule.unless.union)) {
        normalized.unless = {
          union: {
            rules: rule.unless.union,
            aggregator: rule.unless.aggregator || rule.aggregator,
            owaWeights: rule.unless.owaWeights || rule.owaWeights,
            reliabilityWeighting: rule.unless.reliabilityWeighting !== undefined ? 
              rule.unless.reliabilityWeighting : rule.reliabilityWeighting
          }
        };
      } else if (rule.unless.union) {
        normalized.unless = {
          union: {
            ...rule.unless.union,
            aggregator: rule.unless.union.aggregator || rule.unless.aggregator || rule.aggregator,
            owaWeights: rule.unless.union.owaWeights || rule.unless.owaWeights || rule.owaWeights,
            reliabilityWeighting: rule.unless.union.reliabilityWeighting !== undefined ? 
              rule.unless.union.reliabilityWeighting : 
              (rule.unless.reliabilityWeighting !== undefined ? rule.unless.reliabilityWeighting : rule.reliabilityWeighting)
          }
        };
      }
    }
    
    // Normalize always (strict rules)
    if (rule.always) {
      if (rule.always.direct) {
        normalized.always = {
          ...rule.always,
          aggregator: rule.always.aggregator || rule.aggregator,
          owaWeights: rule.always.owaWeights || rule.owaWeights,
          reliabilityWeighting: rule.always.reliabilityWeighting !== undefined ? 
            rule.always.reliabilityWeighting : rule.reliabilityWeighting
        };
      } else {
        // Treat as direct rule
        normalized.always = {
          direct: rule.always,
          aggregator: rule.aggregator,
          owaWeights: rule.owaWeights,
          reliabilityWeighting: rule.reliabilityWeighting
        };
      }
    }
    
    // Normalize when (defeasible rules)
    if (rule.when) {
      if (rule.when.intersection && Array.isArray(rule.when.intersection)) {
        normalized.when = {
          intersection: {
            rules: rule.when.intersection,
            aggregator: rule.when.aggregator || rule.aggregator,
            owaWeights: rule.when.owaWeights || rule.owaWeights,
            reliabilityWeighting: rule.when.reliabilityWeighting !== undefined ? 
              rule.when.reliabilityWeighting : rule.reliabilityWeighting
          }
        };
      } else if (rule.when.intersection) {
        normalized.when = {
          intersection: {
            ...rule.when.intersection,
            aggregator: rule.when.intersection.aggregator || rule.when.aggregator || rule.aggregator,
            owaWeights: rule.when.intersection.owaWeights || rule.when.owaWeights || rule.owaWeights,
            reliabilityWeighting: rule.when.intersection.reliabilityWeighting !== undefined ? 
              rule.when.intersection.reliabilityWeighting : 
              (rule.when.reliabilityWeighting !== undefined ? rule.when.reliabilityWeighting : rule.reliabilityWeighting)
          }
        };
      }
    }
    
    // Normalize requires (inverse defeaters)
    if (rule.requires) {
      if (rule.requires.union && Array.isArray(rule.requires.union)) {
        normalized.requires = {
          union: {
            rules: rule.requires.union,
            aggregator: rule.requires.aggregator || rule.aggregator,
            owaWeights: rule.requires.owaWeights || rule.owaWeights,
            reliabilityWeighting: rule.requires.reliabilityWeighting !== undefined ? 
              rule.requires.reliabilityWeighting : rule.reliabilityWeighting
          }
        };
      } else if (rule.requires.union) {
        normalized.requires = {
          union: {
            ...rule.requires.union,
            aggregator: rule.requires.union.aggregator || rule.requires.aggregator || rule.aggregator,
            owaWeights: rule.requires.union.owaWeights || rule.requires.owaWeights || rule.owaWeights,
            reliabilityWeighting: rule.requires.union.reliabilityWeighting !== undefined ? 
              rule.requires.union.reliabilityWeighting : 
              (rule.requires.reliabilityWeighting !== undefined ? rule.requires.reliabilityWeighting : rule.reliabilityWeighting)
          }
        };
      }
    }
    
    return normalized;
  }

  /**
   * Evaluate logical operations on child rules
   * @protected
   */
  _evaluateRule(userId, userKey, objectId, objectKey, rule, visited, currentRelation, options) {
    const normalizedRule = this._normalizeRule(rule);
    
    // Check if this is defeasible logic
    if (this._isDefeasibleLogic(normalizedRule)) {
      return this.evaluateDefeasible(userId, userKey, objectId, objectKey, normalizedRule, visited, currentRelation, options);
    }
    
    // Traditional logical operations
    let result;
    if (normalizedRule.union) {
      result = this.evaluateUnion(userId, userKey, objectId, objectKey, normalizedRule, visited, currentRelation, options);
    } else if (normalizedRule.intersection) {
      result = this.evaluateIntersection(userId, userKey, objectId, objectKey, normalizedRule, visited, currentRelation, options);
    } else if (normalizedRule.exclusion) {
      result = this.evaluateExclusion(userId, userKey, objectId, objectKey, normalizedRule, visited, currentRelation, options);
    } else {
      result = {
        possibility: 0,
        meta: { reason: 'no_logical_operation_specified' }
      };
    }

    // Apply negation if the rule specifies negate: true (used by NOT operator)
    if (normalizedRule.intersection?.negate || normalizedRule.union?.negate || normalizedRule.exclusion?.negate) {
      const originalPossibility = result.possibility || 0;
      result = {
        ...result,
        possibility: Math.max(0, 1 - originalPossibility),
        reason: 'negated'
      };
    }

    return result;
  }

  /**
   * Evaluate defeasible logic structure
   * @protected
   */
  evaluateDefeasible(userId, userKey, objectId, objectKey, rule, visited, currentRelation, options = {}) {
    const { binary = false, fastPath = false, minPossibility = 0.0, valueContext = null } = options;
    const mode = rule.mode || (binary ? 'binary' : (fastPath ? 'threshold' : 'normal'));
    
    Arbiter.DEBUG && Arbiter.log('evaluateDefeasible called', {
      mode,
      hasNever: !!rule.never,
      hasUnless: !!rule.unless,
      hasAlways: !!rule.always,
      hasWhen: !!rule.when,
      hasRequires: !!rule.requires,
      priority: rule.priority
    });
    
    const allCollectedValues = [];
    let neverResult = null, defeatersResult = null, strictResult = null, defeasibleResult = null, requiresResult = null;
    const timingLevels = { never: 0, always: 0, requires: 0, when: 0, unless: 0, ordinary: 0 };
    
    // Evaluate each component
    
    // 1. Evaluate absolute denials (never) - highest precedence
    if (rule.never) {
      const levelStart = Date.now();
      const neverRule = { union: rule.never.union };
      neverResult = this.evaluateUnion(userId, userKey, objectId, objectKey, neverRule, visited, currentRelation, options);
      timingLevels.never = Math.max(0, Date.now() - levelStart);
      if (neverResult.collectedValues) {
        allCollectedValues.push(...neverResult.collectedValues);
      }
    }
    
    // 2. Evaluate strict rules (always)
    if (rule.always) {
      const levelStart = Date.now();
      strictResult = this.ruleEvaluator.evaluateRule(userId, userKey, objectId, objectKey, rule.always.direct, visited, currentRelation, options);
      timingLevels.always = Math.max(0, Date.now() - levelStart);
      if (strictResult.collectedValues) {
        allCollectedValues.push(...strictResult.collectedValues);
      }
    }
    
    // 3. Evaluate security requirements (requires)
    if (rule.requires) {
      const levelStart = Date.now();
      const requiresRule = { union: rule.requires.union };
      requiresResult = this.evaluateUnion(userId, userKey, objectId, objectKey, requiresRule, visited, currentRelation, options);
      timingLevels.requires = Math.max(0, Date.now() - levelStart);
      if (requiresResult.collectedValues) {
        allCollectedValues.push(...requiresResult.collectedValues);
      }
    }
    
    // 4. Evaluate defeaters (unless) - invert minPossibility for defeater evaluation
    if (rule.unless) {
      const levelStart = Date.now();
      const unlessRule = { union: rule.unless.union };
      defeatersResult = this.evaluateUnion(userId, userKey, objectId, objectKey, unlessRule, visited, currentRelation, options);
      timingLevels.unless = Math.max(0, Date.now() - levelStart);
      if (defeatersResult.collectedValues) {
        allCollectedValues.push(...defeatersResult.collectedValues);
      }
    }
    
    // 5. Evaluate defeasible rules (when or base union)
    if (rule.when) {
      const levelStart = Date.now();
      const whenRule = { intersection: rule.when.intersection };
      defeasibleResult = this.evaluateIntersection(userId, userKey, objectId, objectKey, whenRule, visited, currentRelation, options);
      timingLevels.when = Math.max(0, Date.now() - levelStart);
      if (defeasibleResult.collectedValues) {
        allCollectedValues.push(...defeasibleResult.collectedValues);
      }
    } else if (rule.union) {
      const levelStart = Date.now();
      const unionRule = { union: rule.union };
      defeasibleResult = this.evaluateUnion(userId, userKey, objectId, objectKey, unionRule, visited, currentRelation, options);
      timingLevels.when = Math.max(0, Date.now() - levelStart);
      if (defeasibleResult.collectedValues) {
        allCollectedValues.push(...defeasibleResult.collectedValues);
      }
    }
    
    // Now apply defeasible logic based on mode
    const finalResult = this._applyDefeasibleLogic(mode, rule, neverResult, defeatersResult, strictResult, defeasibleResult, requiresResult, allCollectedValues);
    if (finalResult && finalResult.meta) {
      finalResult.meta.timingLevels = timingLevels;
    }
    return finalResult;
  }

  /**
   * Apply defeasible logic based on evaluation mode
   * @private
   */
  _applyDefeasibleLogic(mode, rule, neverResult, defeatersResult, strictResult, defeasibleResult, requiresResult, allCollectedValues) {
    switch (mode) {
      case 'binary':
        return this._applyBinaryDefeasibleLogic(rule, neverResult, defeatersResult, strictResult, defeasibleResult, requiresResult, allCollectedValues);
      case 'threshold':
        return this._applyThresholdDefeasibleLogic(rule, neverResult, defeatersResult, strictResult, defeasibleResult, requiresResult, allCollectedValues);
      case 'normal':
      default:
        return this._applyNormalDefeasibleLogic(rule, neverResult, defeatersResult, strictResult, defeasibleResult, requiresResult, allCollectedValues);
    }
  }

  /**
   * Apply binary defeasible logic (classic defeasible logic with priority tie-breaking)
   * @private
   */
  _applyBinaryDefeasibleLogic(rule, neverResult, defeatersResult, strictResult, defeasibleResult, requiresResult, allCollectedValues) {
    
    const minPossibility = rule.minPossibility < 0.5 ? 0.5 : (rule.minPossibility || 0.5);
    // Convert to binary decisions
    const isNever = (neverResult && neverResult.possibility > minPossibility);
    const isDefeated = (defeatersResult && defeatersResult.possibility > minPossibility);
    const isStrict = (strictResult && strictResult.possibility > minPossibility);
    const isDefeasible = (defeasibleResult && defeasibleResult.possibility > minPossibility);
    const isRequired = !requiresResult || (requiresResult.possibility > minPossibility);
    
    // NEVER rules override everything - highest precedence
    if (isNever) {
      return {
        possibility: 0,
        collectedValues: allCollectedValues,
        meta: {
          ...neverResult.meta,
          mode: 'binary',
          never: isNever,
          reason: 'never_rule_triggered',
          defeats: [{ defeater: 'never', target: 'when', evidence: 'never_rule', possibility: neverResult.possibility }]
        }
      };
    }
    
    // Handle cases where we only have strict or defeasible rules
    const hasStrictRules = !!strictResult;
    const hasDefeasibleRules = !!defeasibleResult;
    
    // If we only have strict rules, ignore defeasible logic
    if (hasStrictRules && !hasDefeasibleRules) {
      return {
        possibility: isStrict ? 1 : 0,
        collectedValues: allCollectedValues,
        meta: {
          ...strictResult.meta,
          mode: 'binary',
          reason: isStrict ? 'strict_rule_passed' : 'strict_rule_failed',
          never: isNever,
          strict: isStrict,
          strictOnly: true
        }
      };
    }
    
    // If we only have defeasible rules, ignore strict logic
    if (!hasStrictRules && hasDefeasibleRules) {
      return {
        possibility: isDefeasible ? 1 : 0,
        collectedValues: allCollectedValues,
        meta: {
          ...defeasibleResult.meta,
          mode: 'binary',
          never: isNever,
          defeasible: isDefeasible,
          defeasibleOnly: true
        }
      };
    }

    
    // Strict rules always win (unless NEVER is true)
    if (isStrict) {
      return {
        possibility: 1,
        collectedValues: allCollectedValues,
        meta: {
          ...strictResult.meta,
          mode: 'binary',
          never: isNever,
          defeaters: isDefeated,
          strict: isStrict,
          defeasible: isDefeasible,
          required: isRequired
        }
      };
    }
    // Check if requirements not met (security requirements take precedence over defeaters)
    else if (!isRequired) {
      return {
        possibility: 0,
        collectedValues: allCollectedValues,
        meta: {
          ...requiresResult.meta,
          mode: 'binary',
          never: isNever,
          defeaters: isDefeated,
          strict: isStrict,
          defeasible: isDefeasible,
          required: isRequired,
          reason: 'requirements_not_met',
          defeats: [{ defeater: 'requires', target: 'when', evidence: 'requirements_not_met', possibility: requiresResult.possibility }]
        }
      };
    }
    // Check if defeated
    else if (isDefeated) {
      return {
        possibility: 0,
        collectedValues: allCollectedValues,
        meta: {
          ...defeatersResult.meta,
          mode: 'binary',
          never: isNever,
          defeaters: isDefeated,
          strict: isStrict,
          defeasible: isDefeasible,
          required: isRequired,
          reason: 'defeated_by_unless',
          defeats: [{ defeater: 'unless', target: 'when', evidence: 'defeated_by_unless', possibility: defeatersResult.possibility }]
        }
      };
    }
    // Defeasible rules apply if not defeated
    else if (isDefeasible) {
      return {
        possibility: 1,
        collectedValues: allCollectedValues,
        meta: {
          ...defeasibleResult.meta,
          mode: 'binary',
          never: isNever,
          defeaters: isDefeated,
          strict: isStrict,
          defeasible: isDefeasible,
          required: isRequired
        }
      };
    }
    
    // Default case - no rules matched
    return {
      possibility: 0,
      collectedValues: allCollectedValues,
      meta: {
        mode: 'binary',
        never: isNever,
        defeaters: isDefeated,
        strict: isStrict,
        defeasible: isDefeasible,
        required: isRequired
      }
    };
  }

  /**
   * Apply threshold defeasible logic (early exit to binary-like operation)
   * @private
   */
  _applyThresholdDefeasibleLogic(rule, neverResult, defeatersResult, strictResult, defeasibleResult, requiresResult, allCollectedValues) {
    
    // Use threshold logic similar to binary but with early exits
    // NEVER rules override everything - highest precedence
    if (neverResult && neverResult.possibility >= 0.8) {
      return {
        possibility: 0,
        collectedValues: allCollectedValues,
        meta: { 
          ...neverResult.meta,
          mode: 'threshold', 
          earlyExit: 'never_rule',
          never: true,
          defeats: [{ defeater: 'never', target: 'when', evidence: 'never_rule', possibility: neverResult.possibility }]
        }
      };
    }
    
    // Strict rules override everything except NEVER
    if (strictResult && strictResult.possibility >= 0.8) {
      return {
        possibility: strictResult.possibility,
        collectedValues: allCollectedValues,
        meta: { 
          ...strictResult.meta,
          mode: 'threshold', 
          earlyExit: 'strict_rule',
          never: neverResult?.possibility >= 0.8
        }
      };
    }
    
    // Requirements not met (security requirements take precedence over defeaters)
    if (requiresResult && requiresResult.possibility < 0.2) {
      return {
        possibility: requiresResult.possibility,
        collectedValues: allCollectedValues,
        meta: { 
          ...requiresResult.meta,
          mode: 'threshold', 
          earlyExit: 'requirements_not_met',
          defeats: [{ defeater: 'requires', target: 'when', evidence: 'requirements_not_met', possibility: requiresResult.possibility }]
        }
      };
    }
    
    // Strong defeaters override everything except strict and requirements
    if (defeatersResult && defeatersResult.possibility >= 0.8) {
      return {
        possibility: 1 - defeatersResult.possibility, // Invert defeater strength
        collectedValues: allCollectedValues,
        meta: { 
          ...defeatersResult.meta,
          mode: 'threshold', 
          earlyExit: 'defeater',
          defeats: [{ defeater: 'unless', target: 'when', evidence: 'defeater', possibility: defeatersResult.possibility }]
        }
      };
    }
    
    // Fall back to normal logic
    return this._applyNormalDefeasibleLogic(rule, neverResult, defeatersResult, strictResult, defeasibleResult, requiresResult, allCollectedValues);
  }

  /**
   * Apply normal defeasible logic (possibilistic space)
   * @private
   */
  _applyNormalDefeasibleLogic(rule, neverResult, defeatersResult, strictResult, defeasibleResult, requiresResult, allCollectedValues) {
    
    let possibility = 0;
    let resultMeta = {};
    // Reliability mirrors the possibility combination: the base leg's
    // reliability is eroded by requirements and defeaters (both legs of
    // each combination matter), consistent with TTU path semantics.
    let reliability = 1.0;
    
    // NEVER rules override everything when their evidence possibility exceeds threshold (0.5 default)
    if (neverResult && neverResult.possibility >= 0.5) {
      possibility = 0;
      reliability = neverResult.reliability !== undefined ? neverResult.reliability : 1.0;
      resultMeta = { ...neverResult.meta, never: true };
      return {
        possibility: 0,
        reliability,
        reason: 'never_rule_triggered',
        collectedValues: allCollectedValues,
        meta: {
          ...resultMeta,
          mode: 'normal',
          components: {
            never: neverResult.possibility,
            defeaters: defeatersResult?.possibility || 0,
            strict: strictResult?.possibility || 0,
            defeasible: defeasibleResult?.possibility || 0,
            requires: requiresResult?.possibility || 1
          },
          defeats: [{ defeater: 'never', target: 'when', evidence: 'never_rule', possibility: neverResult.possibility }]
        }
      };
    }
    
    // Start with defeasible rules as base
    if (defeasibleResult) {
      possibility = defeasibleResult.possibility;
      reliability = defeasibleResult.reliability !== undefined ? defeasibleResult.reliability : 1.0;
      resultMeta = { ...defeasibleResult.meta };
    }
    
    // Strict rules set minimum allow possibility
    if (strictResult) {
      possibility = Math.max(possibility, strictResult.possibility);
      if (strictResult.possibility > (defeasibleResult?.possibility || 0)) {
        reliability = strictResult.reliability !== undefined ? strictResult.reliability : 1.0;
        resultMeta = { ...strictResult.meta };
      }
    }
    
    // Requirements not met reduce possibility (security requirements take precedence)
    if (requiresResult) {
      const reqStrength = requiresResult.possibility;
      possibility = possibility * reqStrength; // Requirements failure reduces possibility
      reliability = reliability * (requiresResult.reliability !== undefined ? requiresResult.reliability : 1.0);
      if (reqStrength < 0.5) {
        resultMeta = { ...requiresResult.meta, requirementsFailed: requiresResult.meta };
      }
    }
    
    // Defeaters reduce possibility (after requirements are checked)
    if (defeatersResult) {
      const defeatStrength = defeatersResult.possibility;
      possibility = possibility * (1 - defeatStrength); // Defeaters erode possibility
      reliability = reliability * (defeatersResult.reliability !== undefined ? defeatersResult.reliability : 1.0);
      if (defeatStrength > 0.5) {
        resultMeta = { ...defeatersResult.meta, defeatedBy: defeatersResult.meta };
      }
    }

    const finalPossibility = Math.max(0, Math.min(1, possibility));

    // Surface a top-level reason so the AuthorizationChecker reports why a
    // defeasible rule resolved to 0 instead of a generic 'no_matching_rule'.
    // Ordering mirrors precedence: requirements beat defeaters; a defeater
    // reason is only claimed when the base (when/strict) leg was actually
    // active — otherwise the rule simply did not match.
    let reason;
    if (finalPossibility === 0) {
      const baseWasActive = (defeasibleResult?.possibility || 0) > 0 || (strictResult?.possibility || 0) > 0;
      if (requiresResult && requiresResult.possibility < 0.5) {
        reason = 'requirements_not_met';
      } else if (defeatersResult && defeatersResult.possibility > 0.5 && baseWasActive) {
        reason = 'defeated_by_unless';
      }
    }

    return {
      possibility: finalPossibility,
      reliability,
      reason,
      validity: buildValidity('product', [], [], 2, finalPossibility),
      collectedValues: allCollectedValues,
      meta: {
        ...resultMeta,
        mode: 'normal',
        components: {
          never: neverResult?.possibility || 0,
          defeaters: defeatersResult?.possibility || 0,
          strict: strictResult?.possibility || 0,
          defeasible: defeasibleResult?.possibility || 0,
          requires: requiresResult?.possibility || 1
        },
        defeats: [
          ...(requiresResult && requiresResult.possibility < 0.5 ? [{ defeater: 'requires', target: 'when', evidence: 'requirements_not_met', possibility: requiresResult.possibility }] : []),
          ...(defeatersResult ? [{ defeater: 'unless', target: 'when', evidence: 'defeaters_applied', possibility: defeatersResult.possibility }] : [])
        ]
      }
    };
  }

  evaluateUnion(userId, userKey, objectId, objectKey, rule, visited, currentRelation, options = {}) {
    // Normalize the rule to handle both legacy and new formats
    const normalizedRule = this._normalizeRule(rule);
    
    const unionConfig = normalizedRule.union;
    const childRules = unionConfig.rules || [];
    
    let possibilities = [], reliabilities = [], metas = [], reasons = [];
    const resChildrenValidity = [];
    const remediationOptions = [];
    const { fastPath = false, minPossibility = null, valueContext = null, collectValues = false, includeMeta = true, trackEvaluation = false } = options;
    const allCollectedValues = collectValues ? [] : null; // Track collected values from all child rules
    const includeOwaTrace = trackEvaluation && includeMeta;
    const owaTraceOptions = includeOwaTrace ? { includeTrace: true } : null;
    
    
    for (const child of childRules) {
      
      let res = this.ruleEvaluator.evaluateRule(userId, userKey, objectId, objectKey, child, visited, currentRelation, options);
      if (!res || typeof res.possibility !== 'number') {
        res = { possibility: 0, meta: { reason: 'missing_rule' }, collectedValues: [] };
      }
      if (res.validity) resChildrenValidity.push(res.validity);
      
      if (res.reason === 'cycle') reasons.push('cycle');
      mergeRemediationOptions(remediationOptions, extractRemediation(res));
      possibilities.push(res.possibility);
      reliabilities.push(res.reliability !== undefined ? res.reliability : 1.0);
      metas.push(includeMeta ? res.meta : null);
      
      // Collect values from child rule results
      if (collectValues && res.collectedValues && Array.isArray(res.collectedValues)) {
        allCollectedValues.push(...res.collectedValues);
        
        // Add to shared value context if available
        if (valueContext) {
          valueContext.addCollectedValues(res.collectedValues, child.type, child);
        }
      }
      
      // Fast path for union: if ANY rule gives strong possibility, we can stop (OR semantics)
      if (fastPath && minPossibility !== null && res.possibility >= minPossibility) {
        Arbiter.DEBUG && Arbiter.log('union early exit: threshold met', { 
          threshold: minPossibility, 
          actual: res.possibility,
          rulesEvaluated: possibilities.length,
          totalRules: childRules.length
        });
        
        const remediation = buildRemediation(extractRemediation(res));
        return {
          possibility: res.possibility,
          reliability: res.reliability !== undefined ? res.reliability : 1.0,
          ...(collectValues && { collectedValues: allCollectedValues }), // Include all collected values so far
          ...(includeMeta && { meta: res.meta }),
          ...(remediation ? { remediation } : {}),
          reason: reasons.includes('cycle') ? 'cycle' : undefined
        };
      }
    }
    
    
    if (!possibilities.length) {
      return {
        possibility: 0,
        reliability: 1.0,
        ...(collectValues && { collectedValues: allCollectedValues }),
        ...(includeMeta && { meta: { operation: 'union', childCount: 0 } }),
        reason: reasons.includes('cycle') ? 'cycle' : undefined
      };
    }
    
    // Apply OWA fusion with per-operation aggregator support and optional bilattice reasoning
    const unionOWAWeights = getOWAWeightsFromRule(unionConfig, possibilities.length, metas);
    const useBilattice = unionConfig.useBilattice || false;
    const epistemicMode = unionConfig.epistemicMode || 'hybrid';
    const capacityType = unionConfig.capacityType || 'simple_support';
    
    let result;
    let epistemicAnalysis = null;
    
    // Use bilattice-enhanced evidence combination if enabled
    if (useBilattice && collectValues && allCollectedValues.length > 0) {
      const scale = QualitativeScale.fivePoint(); // Default scale for bilattice analysis
      const capacity = this._createCapacityFromValues(allCollectedValues, scale, capacityType);
      
      const bilatticeResult = this._combineEvidenceWithBilattice(allCollectedValues, {
        method: unionConfig.aggregator || 'max',
        useBilattice: true,
        capacity: capacity,
        scale: scale,
        epistemicMode: epistemicMode
      });
      
      result = {
        value: bilatticeResult.value,
        meta: bilatticeResult.aggregationMeta?.selectedSource || metas[0]
      };
      epistemicAnalysis = bilatticeResult.epistemicAnalysis;
      
      Arbiter.DEBUG && Arbiter.log('union using bilattice evidential fusion', { 
        aggregator: unionConfig.aggregator || 'max',
        epistemicMode: epistemicMode,
        capacityType: capacityType,
        inputCount: possibilities.length,
        bilatticeAnalysis: epistemicAnalysis
      });
    } else if (unionConfig.reliabilityWeighting) {
      const weighted = possibilities.map((poss, i) => poss * (reliabilities[i] ?? 1.0));
      result = OWAFusion.fuseWithMeta(weighted, metas, unionOWAWeights, unionConfig.aggregator || 'max', true, owaTraceOptions);
    } else if (unionOWAWeights.some(w => w > 0)) {
      Arbiter.DEBUG && Arbiter.log('union using OWA evidential fusion', { 
        aggregator: unionConfig.aggregator || 'max', 
        weights: unionOWAWeights,
        inputPossibilities: possibilities,
        evidentialFusion: {
          mode: unionConfig.aggregator || 'max',
          customWeights: !!unionConfig.owaWeights,
          sophisticatedAggregation: unionConfig.aggregator !== 'max'
        }
      });
      
      result = OWAFusion.fuseWithMeta(possibilities, metas, unionOWAWeights, unionConfig.aggregator || 'max', true, owaTraceOptions);
    } else {
      result = OWAFusion.fuseWithMeta(possibilities, metas, null, 'max', true, owaTraceOptions);
    }
    
    Arbiter.DEBUG && Arbiter.log('union evidential fusion results:', { 
      result,
      fusedPossibility: result.value,
      selectedMeta: result.meta,
      evidentialSummary: {
        inputCount: possibilities.length,
        fusionMethod: unionConfig.aggregator || 'max',
        resultPossibility: result.value,
        sourceRule: result.meta?.ruleType || result.meta?.operation || 'unknown'
      }
    });
    
    const unionRemediation = result.value === 0
      ? buildRemediation(remediationOptions.length > 0 ? { status: 'required', options: remediationOptions } : null, { status: 'required' })
      : null;
    // Reliability of the child driving the fused value: the OWA selected
    // source when traced, else the max/min possibility child (max is the
    // default union aggregator).
    let unionReliability = 1.0;
    if (includeOwaTrace && result.trace && typeof result.trace.selectedIndex === 'number') {
      unionReliability = reliabilities[result.trace.selectedIndex] ?? 1.0;
    } else if ((unionConfig.aggregator || 'max') === 'min') {
      const minP = Math.min(...possibilities);
      unionReliability = reliabilities[possibilities.indexOf(minP)] ?? 1.0;
    } else {
      const maxP = Math.max(...possibilities);
      unionReliability = reliabilities[possibilities.indexOf(maxP)] ?? 1.0;
    }
    const unionOperator = (unionConfig.aggregator || 'max') === 'max' && (!unionConfig.owaWeights || (unionConfig.owaWeights.length <= 1 || (unionConfig.owaWeights[0] === 1 && unionConfig.owaWeights.slice(1).every(w => w === 0))))
      ? 'max' : 'owa';
    const unionSources = [];
    for (const v of resChildrenValidity) for (const s of v.sources || []) if (!unionSources.includes(s)) unionSources.push(s);
    return {
      possibility: result.value,
      reliability: unionReliability,
      validity: buildValidity(unionOperator, unionSources, resChildrenValidity.map(v => v.label).filter(Boolean), possibilities.length, result.value),
      ...(collectValues && { collectedValues: allCollectedValues }), // Include all collected values from child rules
      ...(includeMeta && {
          meta: {
            ...result.meta,
            operation: 'union',
            childCount: possibilities.length,
            aggregator: unionConfig.aggregator || 'max',
            useBilattice: useBilattice,
            epistemicMode: useBilattice ? epistemicMode : undefined,
            epistemicAnalysis: epistemicAnalysis,
            ...(includeOwaTrace && result.trace ? {
              owa: {
                level: null,
                aggregator: unionConfig.aggregator || 'max',
                weights: result.trace.weights,
                sortedValues: result.trace.sortedValues,
                contributions: result.trace.contributions,
                selectedIndex: result.trace.selectedIndex
              }
            } : {}),
            ...(unionRemediation ? { remediation: unionRemediation } : {})
          }
        }),
      ...(unionRemediation ? { remediation: unionRemediation } : {}),
      reason: reasons.includes('cycle') ? 'cycle' : undefined
    };
  }

  evaluateIntersection(userId, userKey, objectId, objectKey, rule, visited, currentRelation, options = {}) {
    // Normalize the rule to handle both legacy and new formats
    const normalizedRule = this._normalizeRule(rule);
    
    const intersectionConfig = normalizedRule.intersection;
    const childRules = intersectionConfig.rules || [];
    
    let possibilities = [], reliabilities = [], metas = [], reasons = [];
    const resChildrenValidity = [];
    const remediationOptions = [];
    const { fastPath = false, minPossibility = null, valueContext = null, collectValues = false, includeMeta = true, trackEvaluation = false } = options;
    const allCollectedValues = collectValues ? [] : null; // Track collected values from all child rules
    const includeOwaTrace = trackEvaluation && includeMeta;
    const owaTraceOptions = includeOwaTrace ? { includeTrace: true } : null;
    
    Arbiter.DEBUG && Arbiter.log('evaluateIntersection called', { 
      fastPath, 
      minPossibility, 
      numRules: childRules.length,
      intersectionAggregator: intersectionConfig.aggregator,
      hasCustomWeights: !!intersectionConfig.owaWeights,
      hasValueContext: !!valueContext
    });
    
    for (const child of childRules) {
      const res = this.ruleEvaluator.evaluateRule(userId, userKey, objectId, objectKey, child, visited, currentRelation, options);
      if (res.validity) resChildrenValidity.push(res.validity);
      if (res.reason === 'cycle') reasons.push('cycle');
      possibilities.push(res.possibility);
      reliabilities.push(res.reliability !== undefined ? res.reliability : 1.0);
      metas.push(includeMeta ? res.meta : null);
      mergeRemediationOptions(remediationOptions, extractRemediation(res));
      
      // Collect values from child rule results
      if (collectValues && res.collectedValues && Array.isArray(res.collectedValues)) {
        allCollectedValues.push(...res.collectedValues);
        
        // Add to shared value context if available
        if (valueContext) {
          valueContext.addCollectedValues(res.collectedValues, child.type, child);
        }
      }
      
      // Fast path for intersection: if ANY rule falls below the allow
      // threshold, the conjunction is denied — stop (AND semantics).
      // NOTE: must compare against minPossibility itself, not (1 - minPossibility):
      // for t < 0.5 a child in [t, 1-t) triggered the old exit with a value
      // >= t, producing a false allow while the true min was below t.
      if (fastPath && minPossibility !== null && res.possibility < minPossibility) {
        Arbiter.DEBUG && Arbiter.log('intersection early exit: low possibility threshold met', { 
          threshold: minPossibility, 
          actual: res.possibility,
          rulesEvaluated: possibilities.length,
          totalRules: childRules.length
        });
        
        return {
          possibility: res.possibility,
          ...(collectValues && { collectedValues: allCollectedValues }), // Include all collected values so far
          ...(includeMeta && { meta: res.meta }),
          reason: reasons.includes('cycle') ? 'cycle' : undefined
        };
      }
    }
    
    if (!possibilities.length) {
      return {
        possibility: 0,
        ...(collectValues && { collectedValues: allCollectedValues }),
        ...(includeMeta && { meta: { operation: 'intersection', childCount: 0 } }),
        reason: reasons.includes('cycle') ? 'cycle' : undefined
      };
    }
    
    // Use intersection-specific aggregator configuration
    const intersectionOWAWeights = getOWAWeightsFromRule(intersectionConfig, possibilities.length, metas);
    
    // For intersection (AND), default to min semantics if no aggregator specified
    const defaultMode = intersectionConfig.aggregator || 'min';
    const finalWeights = intersectionConfig.aggregator || intersectionConfig.owaWeights ? 
                        intersectionOWAWeights : 
                        [...Array(Math.max(0, possibilities.length - 1)).fill(0), 1]; // min weights
    
    Arbiter.DEBUG && Arbiter.log('intersection using aggregation', { 
      aggregator: defaultMode, 
      weights: finalWeights,
    });
    
    let result;
    if (intersectionConfig.reliabilityWeighting) {
      const reliabilityWeightedPossibilities = possibilities.map((poss, i) => poss * (reliabilities[i] ?? 1.0));
      result = OWAFusion.fuseWithMeta(reliabilityWeightedPossibilities, metas, finalWeights, defaultMode, true, owaTraceOptions);
    } else {
      result = OWAFusion.fuseWithMeta(possibilities, metas, finalWeights, defaultMode, true, owaTraceOptions);
    }
    
    const intersectionRemediation = result.value === 0
      ? buildRemediation(remediationOptions.length > 0 ? { status: 'required', options: remediationOptions } : null, { status: 'required' })
      : null;
    // Reliability of the child driving the fused value (min is the
    // default intersection aggregator).
    let intersectionReliability = 1.0;
    if (includeOwaTrace && result.trace && typeof result.trace.selectedIndex === 'number') {
      intersectionReliability = reliabilities[result.trace.selectedIndex] ?? 1.0;
    } else if ((intersectionConfig.aggregator || 'min') === 'max') {
      const maxP = Math.max(...possibilities);
      intersectionReliability = reliabilities[possibilities.indexOf(maxP)] ?? 1.0;
    } else {
      const minP = Math.min(...possibilities);
      intersectionReliability = reliabilities[possibilities.indexOf(minP)] ?? 1.0;
    }
    const interSources = [];
    for (const v of resChildrenValidity) for (const s of v.sources || []) if (!interSources.includes(s)) interSources.push(s);
    return {
      possibility: result.value,
      reliability: intersectionReliability,
      validity: buildValidity('min', interSources, resChildrenValidity.map(v => v.label).filter(Boolean), possibilities.length, result.value),
      ...(collectValues && { collectedValues: allCollectedValues }), // Include all collected values from child rules
      ...(includeMeta && {
          meta: {
            ...result.meta,
            operation: 'intersection',
            childCount: possibilities.length,
            aggregator: defaultMode,
            ...(includeOwaTrace && result.trace ? {
              owa: {
                level: null,
                aggregator: defaultMode,
                weights: result.trace.weights,
                sortedValues: result.trace.sortedValues,
                contributions: result.trace.contributions,
                selectedIndex: result.trace.selectedIndex
              }
            } : {}),
            ...(intersectionRemediation ? { remediation: intersectionRemediation } : {})
          }
        }),
      ...(intersectionRemediation ? { remediation: intersectionRemediation } : {}),
      reason: reasons.includes('cycle') ? 'cycle' : undefined
    };
  }

  evaluateExclusion(userId, userKey, objectId, objectKey, rule, visited, currentRelation, options = {}) {
    // Normalize the rule to handle both legacy and new formats
    const normalizedRule = this._normalizeRule(rule);
    
    const exclusionConfig = normalizedRule.exclusion;
    const childRules = exclusionConfig.rules || [];
    
    const { valueContext = null, collectValues = false, includeMeta = true, trackEvaluation = false } = options;
    const includeOwaTrace = trackEvaluation && includeMeta;
    const owaTraceOptions = includeOwaTrace ? { includeTrace: true } : null;
    if (childRules.length !== 2) {
      return {
        possibility: 0,
        ...(collectValues && { collectedValues: [] }),
        ...(includeMeta && {
          meta: { 
            operation: 'exclusion',
            error: 'exclusion_requires_exactly_two_rules' 
          }
        }),
        reason: 'exclusion_requires_exactly_two_rules'
      };
    }
    
    Arbiter.DEBUG && Arbiter.log('evaluateExclusion called', { 
      numRules: childRules.length,
      exclusionAggregator: exclusionConfig.aggregator,
      hasCustomWeights: !!exclusionConfig.owaWeights,
      hasValueContext: !!valueContext
    });
    
    const a = this.ruleEvaluator.evaluateRule(userId, userKey, objectId, objectKey, childRules[0], visited, currentRelation, options);
    const b = this.ruleEvaluator.evaluateRule(userId, userKey, objectId, objectKey, childRules[1], visited, currentRelation, options);
    
    // Collect values from both child rules
    const allCollectedValues = collectValues ? [] : null;
    if (collectValues && a.collectedValues && Array.isArray(a.collectedValues)) {
      allCollectedValues.push(...a.collectedValues);
      if (valueContext) {
        valueContext.addCollectedValues(a.collectedValues, childRules[0].type, childRules[0]);
      }
    }
    if (collectValues && b.collectedValues && Array.isArray(b.collectedValues)) {
      allCollectedValues.push(...b.collectedValues);
      if (valueContext) {
        valueContext.addCollectedValues(b.collectedValues, childRules[1].type, childRules[1]);
      }
    }
    
    if (a.reason === 'cycle' || b.reason === 'cycle') {
      return {
        possibility: 0,
        ...(collectValues && { collectedValues: allCollectedValues }),
        ...(includeMeta && {
          meta: { 
            operation: 'exclusion',
            childA: a.meta,
            childB: b.meta 
          }
        }),
        reason: 'cycle'
      };
    }
    
    // Exclusion logic: A AND NOT B
    // For possibility theory: possibility = P(A) * (1 - P(B))
    let possibility;
    let result = null;
    
    if (exclusionConfig.aggregator || exclusionConfig.owaWeights) {
      // Use custom aggregation for exclusion if specified
      const possibilities = [a.possibility, 1 - b.possibility];
      const metas = [a.meta, { exclusion_complement: b.meta }];
      
      const exclusionOWAWeights = getOWAWeightsFromRule(exclusionConfig, 2, metas);
      
      Arbiter.DEBUG && Arbiter.log('exclusion using custom aggregation', { 
        aggregator: exclusionConfig.aggregator, 
        weights: exclusionOWAWeights,
      });
      
      if (exclusionConfig.reliabilityWeighting) {
        const legReliabilities = [
          a.reliability !== undefined ? a.reliability : 1.0,
          b.reliability !== undefined ? b.reliability : 1.0
        ];
        const reliabilityWeightedPossibilities = possibilities.map((poss, i) => poss * (legReliabilities[i] ?? 1.0));
        result = OWAFusion.fuseWithMeta(reliabilityWeightedPossibilities, metas, exclusionOWAWeights, exclusionConfig.aggregator || 'min', true, owaTraceOptions);
      } else {
        result = OWAFusion.fuseWithMeta(possibilities, metas, exclusionOWAWeights, exclusionConfig.aggregator || 'min', true, owaTraceOptions);
      }
      
      possibility = result.value;
    } else {
      // Standard exclusion logic: A AND NOT B
      possibility = a.possibility * (1 - b.possibility);
    }
    
    return {
      possibility,
      // Both legs contribute to the decision, so their reliabilities
      // multiply (consistent with TTU/chain path semantics).
      reliability: (a.reliability !== undefined ? a.reliability : 1.0) * (b.reliability !== undefined ? b.reliability : 1.0),
      validity: buildValidity('product', [a.validity?.sources?.[0], b.validity?.sources?.[0]].filter(Boolean), [a.validity?.label, b.validity?.label].filter(Boolean), 2, possibility),
      // In binary mode the negated child's strength is the deny side of the
      // dual-threshold contract: deny fires when P(B) >= maxDenyPossibility.
      ...(options.binary && { possibility_deny: b.possibility ?? 0 }),
      ...(collectValues && { collectedValues: allCollectedValues }), // Include all collected values from both child rules
      ...(includeMeta && {
        meta: { 
          operation: 'exclusion',
          childA: a.meta,
          childB: b.meta,
          aggregator: exclusionConfig.aggregator || 'standard',
          ...(includeOwaTrace && result?.trace ? {
            owa: {
              level: null,
              aggregator: exclusionConfig.aggregator || 'standard',
              weights: result.trace.weights,
              sortedValues: result.trace.sortedValues,
              contributions: result.trace.contributions,
              selectedIndex: result.trace.selectedIndex
            }
          } : {})
        }
      })
    };
  }

}
