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

/**
 * DirectRule - Checks for direct relationships between user and object
 * 
 * This rule evaluates direct relations in the graph, optionally in reverse direction.
 * It supports efficient batch processing and early exit optimizations.
 * 
 * Returns standardized results with raw possibility values:
 * - possibility_allow: The strength/possibility of the relation (0.0 to 1.0)
 * - possibility_deny: Always 0 (DirectRule only reports relation strength, not polarity)
 * - Collected Values: Values from relations with full path metadata
 * 
 * Note: This rule returns raw relation strength. The logical context (defeater, strict, 
 * defeasible, etc.) determines how this strength is interpreted as positive or negative evidence.
 * 
 * Configuration:
 * {
 *   type: 'direct',
 *   relation: string,     // Optional: relation to check (uses currentRelation if not specified)
 *   reverse: boolean,     // Optional: check in reverse direction (default: false)
 *   collectValues: boolean // Optional: collect values from relations (default: true if relation has values)
 * }
 */
export class DirectRule extends BaseRule {
  constructor(arbiter) {
    super(arbiter);
  }

  /**
   * Evaluate method returns raw relation strength
   */
  evaluate(userId, userKey, objectId, objectKey, rule, visited, currentRelation, options = {}) {
    const { fastPath = false, minPossibility = null, collectValues: collectValuesOption, includeMeta = true } = options;
    
    const relName = rule.relation || rule.rel || rule.label || rule.name || currentRelation;
    const reverse = rule.reverse;
    const collectValues = collectValuesOption !== undefined ? collectValuesOption : rule.collectValues !== false;
    
    let directRel;
    if (reverse) {
      directRel = this.arbiter.relationManager.getDirectRelation(objectId, relName, userId, options);
    } else {
      directRel = this.arbiter.relationManager.getDirectRelation(userId, relName, objectId, options);
    }
    
    if (!directRel) {
        return this._createStandardResult({
          possibility: 0,
          ...(includeMeta && {
            meta: {
              ruleType: 'direct',
              reason: 'no_relation'
            }
          })
        }, []);
    }

    // Value gate: a rule carrying expectedValue (from a DSL literal like
    // `balance(user, 5)`, or a per-check value-object override like
    // `check(user, can_withdraw, 5)`) only grants when the matched edge carries
    // exactly that value. Without it a value-carrying fact matches ANY edge of
    // the relation, silently over-granting past the declared amount.
    const expectedValue = rule.expectedValue !== undefined ? rule.expectedValue : options.expectedValue;
    if (expectedValue !== undefined && directRel.value !== expectedValue) {
        return this._createStandardResult({
          possibility: 0,
          ...(includeMeta && {
            meta: {
              ruleType: 'direct',
              reason: 'value_mismatch',
              expectedValue,
              actualValue: directRel.value
            }
          })
        }, []);
    }
    
    const relationStrength = directRel.possibility;
    const _source = directRel.source || 'persistent';
    const _allowMeta = {
      ruleType: 'direct',
      reason: 'direct',
      source: _source,
      layer_name: directRel.layer_name || null,
      source_class: directRel.source_class || null,
      reducer_applied: directRel.reducer_applied || null
    };
    
    const authResult = {
      possibility: relationStrength,
      reliability: directRel.reliability !== undefined ? directRel.reliability : 1.0,
      validity: this._validity('identity', [relName], [this._relationValidity(directRel)], 1, relationStrength),
      possibility_allow: relationStrength,  // For binary mode
      possibility_deny: 0,                 // DirectRule doesn't deny
      ...(includeMeta && {
        meta: { 
          ruleType: 'direct', 
          reason: 'relation_exists', 
          rule,
          relation: relName,
          reverse: reverse || false,
          strength: relationStrength,
          reliability: directRel.reliability !== undefined ? directRel.reliability : 1.0,
          source: _source,
          allow: _allowMeta
        },
        meta_allow: _allowMeta
      }),
      reason: 'exists'
    };
    
    // Collect values if relation has them and collection is enabled
    let collectedValues = [];
    if (collectValues && directRel.value !== undefined) {
      const sourceEntity = reverse ? objectKey : userKey;
      const targetEntity = reverse ? userKey : objectKey;
      const path = [sourceEntity, targetEntity];
      
      collectedValues.push(this._createCollectedValue(
        directRel.value,
        directRel.possibility,
        path,
        {
          entityKey: sourceEntity,
          relation: relName,
          step: 0
        },
        {
          timestamp: directRel.changed_last_at || directRel.updated_last_at ||
            (options && options.now !== undefined && options.now !== null ? options.now : Date.now()),
          reliability: 1.0,
          source: directRel.source || 'persistent'
        }
      ));
    }
    
    // Apply early exit logic if thresholds are enabled
    if (fastPath) {
      // Early exit based on relation strength threshold
      if (minPossibility !== null && authResult.possibility >= minPossibility) {
        
        if (authResult.meta) {
          authResult.meta.earlyExit = true;
          authResult.meta.earlyExitReason = 'strength_threshold_met';
        }
      }
    }
    
    return this._createStandardResult(authResult, collectedValues);
  }
} 
