import { OWAFusion, getOWAWeightsFromRule } from '../utils/OWAFusion.js';
import { buildRemediation, extractRemediation, mergeRemediationOptions } from './remediation.js';

export class CompiledEvaluator {
  constructor(arbiter, ruleEvaluator, logicalOperators) {
    this.arbiter = arbiter;
    this.ruleEvaluator = ruleEvaluator;
    this.logicalOperators = logicalOperators;
  }

  evaluate(compiled, userId, userKey, objectId, objectKey, visited, currentRelation, options = {}) {
    if (!compiled || typeof compiled !== 'object') {
      return { possibility: 0, reason: 'missing_compiled_rule' };
    }

    switch (compiled.type) {
      case 'direct':
        return this._evaluateDirect(compiled, userId, userKey, objectId, objectKey, options, currentRelation);
      case 'computed':
        return this._evaluateComputed(compiled, userKey, objectKey, options, visited, currentRelation);
      case 'tuple_to_userset':
        return this._evaluateTupleToUserset(compiled, userId, userKey, objectId, objectKey, visited, currentRelation, options);
      case 'chain':
        return this._evaluateChain(compiled, userId, userKey, objectId, objectKey, visited, currentRelation, options);
      case 'multi_hop':
        return this._evaluateMultiHop(compiled, userId, userKey, objectId, objectKey, visited, currentRelation, options);
      case 'logical':
        return this._evaluateLogical(compiled, userId, userKey, objectId, objectKey, visited, currentRelation, options);
      case 'defeasible':
        return this._evaluateDefeasible(compiled, userId, userKey, objectId, objectKey, visited, currentRelation, options);
      case 'relational_comparator':
        return this._evaluateRelationalComparator(compiled, userId, userKey, objectId, objectKey, visited, currentRelation, options);
      case 'challenge':
        return this.ruleEvaluator.evaluateRule(
          userId,
          userKey,
          objectId,
          objectKey,
          compiled,
          visited,
          currentRelation,
          { ...options, useCompiled: false }
        );
      default:
        if (compiled.source) {
          return this.ruleEvaluator.evaluateRule(
            userId,
            userKey,
            objectId,
            objectKey,
            compiled.source,
            visited,
            currentRelation,
            { ...options, useCompiled: false }
          );
        }
        return { possibility: 0, reason: 'unsupported_compiled_rule' };
    }
  }

  _evaluateRelationalComparator(compiled, userId, userKey, objectId, objectKey, visited, currentRelation, options) {
    if (compiled.source) {
      return this.ruleEvaluator.evaluateRule(
        userId,
        userKey,
        objectId,
        objectKey,
        compiled.source,
        visited,
        currentRelation,
        { ...options, useCompiled: false }
      );
    }

    const toOperandConfig = (operand) => {
      if (!operand) return null;
      return {
        rule: operand.rule || null,
        extractValue: operand.extractValue !== false,
        valueRelation: operand.valueRelationResolved || operand.valueRelation || null,
        evaluateFrom: operand.evaluateFrom || 'auto',
        aggregator: operand.aggregator,
        owaWeights: operand.owaWeights,
        _owaSparseCount: operand._owaSparseCount,
        minOperandPossibility: operand.minOperandPossibility
      };
    };

    const fallbackConfig = {
      type: 'relational_comparator',
      comparator: compiled.comparator,
      marginOfSafety: compiled.marginOfSafety,
      fallbackBehavior: compiled.fallbackBehavior,
      minRulePossibility: compiled.minRulePossibility,
      epsilon: compiled.epsilon,
      left: toOperandConfig(compiled.left),
      right: toOperandConfig(compiled.right)
    };

    return this.ruleEvaluator.evaluateRule(
      userId,
      userKey,
      objectId,
      objectKey,
      fallbackConfig,
      visited,
      currentRelation,
      { ...options, useCompiled: false }
    );
  }

  _evaluateDirect(compiled, userId, userKey, objectId, objectKey, options, currentRelation) {
    const { fastPath = false, minPossibility = null, includeMeta = true } = options;
    const relName = compiled.relation || currentRelation;
    const reverse = compiled.reverse;
    const collectValues = options.collectValues !== undefined ? options.collectValues : compiled.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 {
        possibility: 0,
        ...(includeMeta && { meta: { ruleType: 'direct', reason: 'no_relation' } }),
        reason: 'no_relation'
      };
    }

    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 result = {
      possibility: relationStrength,
      possibility_allow: relationStrength,
      possibility_deny: 0,
      ...(includeMeta && {
        meta: {
          ruleType: 'direct',
          reason: 'relation_exists',
          relation: relName,
          reverse: reverse || false,
          strength: relationStrength,
          source: _source,
          allow: _allowMeta
        },
        meta_allow: _allowMeta
      }),
      reason: 'exists'
    };

    if (fastPath && minPossibility !== null && result.possibility >= minPossibility) {
      if (result.meta) {
        result.meta.earlyExit = true;
        result.meta.earlyExitReason = 'strength_threshold_met';
      }
    }

    if (collectValues && directRel.value !== undefined) {
      const sourceEntity = reverse ? objectKey : userKey;
      const targetEntity = reverse ? userKey : objectKey;
      const path = [sourceEntity, targetEntity];
      result.collectedValues = [
        this.ruleEvaluator.ruleHandlers.direct._createCollectedValue(
          directRel.value,
          directRel.possibility,
          path,
          {
            entityKey: sourceEntity,
            relation: relName,
            step: 0
          },
          {
            timestamp: directRel.changed_last_at || directRel.updated_last_at || Date.now(),
            reliability: 1.0,
            source: directRel.source || 'persistent'
          }
        )
      ];
    }

    return result;
  }

  _evaluateComputed(compiled, userKey, objectKey, options, visited, currentRelation) {
    return this.arbiter.authChecker.check(userKey, compiled.relation, objectKey, {
      ...options,
      _visited: visited,
      _currentRelation: currentRelation
    });
  }

  _evaluateTupleToUserset(compiled, userId, userKey, objectId, objectKey, visited, currentRelation, options) {
    if (compiled._optimized?.kind === 'direct_join' && !options.collectValues && !options.trackEvaluation) {
      const optimized = compiled._optimized;
      const resolvePossibility = (value) => value !== undefined ? value : 1.0;
      const resolveReliability = (value) => value !== undefined ? value : 1.0;
      const tuplesetDirection = optimized.tuplesetDirection || 'out';
      let tuples;
      if (optimized.reverse) {
        tuples = tuplesetDirection === 'in'
          ? this.arbiter.relationManager.getRelationsToDst(userId, optimized.tuplesetRelation, options)
          : this.arbiter.relationManager.getRelationsFromSrc(userId, optimized.tuplesetRelation, options);
      } else {
        tuples = tuplesetDirection === 'in'
          ? this.arbiter.relationManager.getRelationsToDst(objectId, optimized.tuplesetRelation, options)
          : this.arbiter.relationManager.getRelationsFromSrc(objectId, optimized.tuplesetRelation, options);
      }

      const maxIntermediates = optimized.maxIntermediates !== undefined ? optimized.maxIntermediates : 20;
      if (tuples.length > maxIntermediates * 3) {
        tuples.sort((a, b) => resolvePossibility(b.possibility) - resolvePossibility(a.possibility));
        tuples = tuples.slice(0, maxIntermediates);
      }

      const checkingId = optimized.reverse ? objectId : userId;
      const computedEdges = this.arbiter.relationManager.getRelationsFromSrc(checkingId, optimized.computedRelation, options);
      const computedByIntermediate = new Map(computedEdges.map(edge => [edge.dst, edge]));

      let processed = 0;
      let bestPossibility = 0;
      let bestReliability = 1.0;
      const earlyExitThreshold = optimized.earlyExitThreshold !== undefined ? optimized.earlyExitThreshold : 0.95;
      const minPossibility = optimized.minPossibility !== undefined ? optimized.minPossibility : 0;

      for (const t of tuples) {
        if (processed >= maxIntermediates) break;
        processed++;
        const intermediateId = tuplesetDirection === 'in' ? t.src : t.dst;
        const directRel = computedByIntermediate.get(intermediateId);
        if (!directRel) continue;
        const combinedPossibility = Math.min(resolvePossibility(t.possibility), resolvePossibility(directRel.possibility));
        const finalPossibility = combinedPossibility >= minPossibility ? combinedPossibility : 0;
        if (finalPossibility > bestPossibility) {
          bestPossibility = finalPossibility;
          bestReliability = resolveReliability(t.reliability) * (directRel.reliability !== undefined ? directRel.reliability : 1.0);
          if (bestPossibility >= earlyExitThreshold) break;
        }
      }

      return {
        possibility: bestPossibility,
        reliability: bestReliability,
        reason: bestPossibility > 0 ? 'direct_join' : 'no_match'
      };
    }

    if (compiled.source) {
      return this.ruleEvaluator.evaluateRule(
        userId,
        userKey,
        objectId,
        objectKey,
        compiled.source,
        visited,
        currentRelation,
        { ...options, useCompiled: false }
      );
    }
    const fallbackConfig = {
      type: 'tuple_to_userset',
      tuplesetRelation: compiled.tuplesetRelation,
      tuplesetDirection: compiled.tuplesetDirection,
      computedRelation: compiled.computedRelation
    };
    return this.ruleEvaluator.evaluateRule(
      userId,
      userKey,
      objectId,
      objectKey,
      fallbackConfig,
      visited,
      currentRelation,
      { ...options, useCompiled: false }
    );
  }

  _evaluateChain(compiled, userId, userKey, objectId, objectKey, visited, currentRelation, options) {
    if (compiled._optimized?.kind === 'direct' && !options.collectValues && !options.trackEvaluation) {
      const direct = {
        type: 'direct',
        relation: compiled._optimized.relation,
        reverse: compiled._optimized.reverse,
        collectValues: false
      };
      return this._evaluateDirect(direct, userId, userKey, objectId, objectKey, options, currentRelation);
    }
    if (compiled.source) {
      return this.ruleEvaluator.evaluateRule(
        userId,
        userKey,
        objectId,
        objectKey,
        compiled.source,
        visited,
        currentRelation,
        { ...options, useCompiled: false }
      );
    }
    const fallbackConfig = {
      type: 'chain',
      steps: compiled.steps,
      reverse: compiled.reverse,
      collectValues: compiled.collectValues,
      valueFilters: compiled.valueFilters,
      valueAggregation: compiled.valueAggregation
    };
    return this.ruleEvaluator.evaluateRule(
      userId,
      userKey,
      objectId,
      objectKey,
      fallbackConfig,
      visited,
      currentRelation,
      { ...options, useCompiled: false }
    );
  }

  _evaluateMultiHop(compiled, userId, userKey, objectId, objectKey, visited, currentRelation, options) {
    if (compiled._optimized?.kind === 'direct' && !options.collectValues && !options.trackEvaluation) {
      const direct = {
        type: 'direct',
        relation: compiled._optimized.relation,
        reverse: compiled._optimized.reverse,
        collectValues: false
      };
      return this._evaluateDirect(direct, userId, userKey, objectId, objectKey, options, currentRelation);
    }
    if (compiled.source) {
      return this.ruleEvaluator.evaluateRule(
        userId,
        userKey,
        objectId,
        objectKey,
        compiled.source,
        visited,
        currentRelation,
        { ...options, useCompiled: false }
      );
    }
    return { possibility: 0, reason: 'unsupported_multi_hop' };
  }

  _evaluateLogical(compiled, userId, userKey, objectId, objectKey, visited, currentRelation, options) {
    if (compiled.source?._subjectAsObject) { objectId = userId; objectKey = userKey; }
    let result;
    switch (compiled.op) {
      case 'union':
        result = this._evaluateUnion(compiled, userId, userKey, objectId, objectKey, visited, currentRelation, options);
        break;
      case 'intersection':
        result = this._evaluateIntersection(compiled, userId, userKey, objectId, objectKey, visited, currentRelation, options);
        break;
      case 'exclusion':
        result = this._evaluateExclusion(compiled, userId, userKey, objectId, objectKey, visited, currentRelation, options);
        break;
      default:
        result = { possibility: 0, reason: 'unknown_logical_op' };
    }

    if (compiled.negate) {
      result = {
        ...result,
        possibility: Math.max(0, 1 - (result.possibility || 0)),
        reason: result.possibility === 0 ? result.reason : 'negated'
      };
    }

    return result;
  }

  _evaluateUnion(compiled, userId, userKey, objectId, objectKey, visited, currentRelation, options) {
    const { fastPath = false, minPossibility = null, valueContext = null, collectValues = false, includeMeta = true, trackEvaluation = false } = options;
    const includeOwaTrace = trackEvaluation && includeMeta;
    const owaTraceOptions = includeOwaTrace ? { includeTrace: true } : null;

    if (compiled.useBilattice && compiled.source) {
      return this.ruleEvaluator.evaluateRule(
        userId,
        userKey,
        objectId,
        objectKey,
        compiled.source,
        visited,
        currentRelation,
        { ...options, useCompiled: false }
      );
    }

    if (compiled._optimized?.kind === 'direct_list') {
      return this._evaluateDirectList(compiled, userId, userKey, objectId, objectKey, visited, currentRelation, options, 'union');
    }

    const children = compiled.children || [compiled];
    const possibilities = [];
    const metas = [];
    const remediationOptions = [];
    const allCollectedValues = collectValues ? [] : null;

    for (const child of children) {
      const childVisited = new Set(visited);
      let res = this.evaluate(child, userId, userKey, objectId, objectKey, childVisited, currentRelation, options);
      if (!res || typeof res.possibility !== 'number') {
        res = { possibility: 0, meta: { reason: 'missing_rule' }, collectedValues: [] };
      }

      mergeRemediationOptions(remediationOptions, extractRemediation(res));

      possibilities.push(res.possibility);
      metas.push(includeMeta ? res.meta : null);

      if (collectValues && res.collectedValues && Array.isArray(res.collectedValues)) {
        allCollectedValues.push(...res.collectedValues);
        if (valueContext) {
          valueContext.addCollectedValues(res.collectedValues, child.type, child);
        }
      }

      if (fastPath && minPossibility !== null && res.possibility >= minPossibility) {
        const remediation = buildRemediation(extractRemediation(res));
        return {
          possibility: res.possibility,
          ...(collectValues && { collectedValues: allCollectedValues }),
          ...(includeMeta && { meta: res.meta }),
          ...(remediation ? { remediation } : {})
        };
      }
    }

    if (!possibilities.length) {
      return {
        possibility: 0,
        ...(collectValues && { collectedValues: allCollectedValues }),
        ...(includeMeta && { meta: { operation: 'union', childCount: 0 } })
      };
    }

    const unionOWAWeights = compiled._precomputedWeights && compiled._precomputedWeights.length === possibilities.length
      ? compiled._precomputedWeights
      : getOWAWeightsFromRule(compiled, possibilities.length, metas);
    let result;
    if (unionOWAWeights.some(w => w > 0)) {
      result = OWAFusion.fuseWithMeta(possibilities, metas, unionOWAWeights, compiled.aggregator || 'max', true, owaTraceOptions);
    } else {
      result = OWAFusion.fuseWithMeta(possibilities, metas, null, 'max', true, owaTraceOptions);
    }

    const remediation = result.value === 0
      ? buildRemediation(remediationOptions.length > 0 ? { status: 'required', options: remediationOptions } : null, { status: 'required' })
      : null;
    return {
      possibility: result.value,
      ...(collectValues && { collectedValues: allCollectedValues }),
      ...(includeMeta && {
        meta: {
          ...result.meta,
          operation: 'union',
          childCount: possibilities.length,
          aggregator: compiled.aggregator || 'max',
          ...(includeOwaTrace && result.trace ? {
            owa: {
              level: null,
              aggregator: compiled.aggregator || 'max',
              weights: result.trace.weights,
              sortedValues: result.trace.sortedValues,
              contributions: result.trace.contributions,
              selectedIndex: result.trace.selectedIndex
            }
          } : {}),
          ...(remediation ? { remediation } : {})
        }
      }),
      ...(remediation ? { remediation } : {})
    };
  }

  _evaluateIntersection(compiled, userId, userKey, objectId, objectKey, visited, currentRelation, options) {
    const { fastPath = false, minPossibility = null, valueContext = null, collectValues = false, includeMeta = true, trackEvaluation = false } = options;
    const includeOwaTrace = trackEvaluation && includeMeta;
    const owaTraceOptions = includeOwaTrace ? { includeTrace: true } : null;
    const effectiveObjectId = compiled.source?._subjectAsObject ? userId : objectId;
    const effectiveObjectKey = compiled.source?._subjectAsObject ? userKey : objectKey;
    if (compiled._optimized?.kind === 'direct_list') {
      return this._evaluateDirectList(compiled, userId, userKey, effectiveObjectId, effectiveObjectKey, visited, currentRelation, options, 'intersection');
    }
    const possibilities = [];
    const metas = [];
    const remediationOptions = [];
    const allCollectedValues = collectValues ? [] : null;

    // A defeasible `when` component may compile to a single direct node
    // (no children); treat it as a one-element conjunction, matching the
    // union path's `compiled.children || [compiled]` fallback.
    const children = compiled.children || [compiled];

    for (const child of children) {
      const childVisited = new Set(visited);
      const res = this.evaluate(child, userId, userKey, objectId, objectKey, childVisited, currentRelation, options);
      possibilities.push(res.possibility);
      metas.push(includeMeta ? res.meta : null);

      mergeRemediationOptions(remediationOptions, extractRemediation(res));

      if (collectValues && res.collectedValues && Array.isArray(res.collectedValues)) {
        allCollectedValues.push(...res.collectedValues);
        if (valueContext) {
          valueContext.addCollectedValues(res.collectedValues, child.type, child);
        }
      }

      if (fastPath && minPossibility !== null && res.possibility < minPossibility) {
        return {
          possibility: res.possibility,
          ...(collectValues && { collectedValues: allCollectedValues }),
          ...(includeMeta && { meta: res.meta })
        };
      }
    }

    if (!possibilities.length) {
      return {
        possibility: 0,
        ...(collectValues && { collectedValues: allCollectedValues }),
        ...(includeMeta && { meta: { operation: 'intersection', childCount: 0 } })
      };
    }

    const intersectionOWAWeights = compiled._precomputedWeights && compiled._precomputedWeights.length === possibilities.length
      ? compiled._precomputedWeights
      : getOWAWeightsFromRule(compiled, possibilities.length, metas);
    const defaultMode = compiled.aggregator || 'min';
    const finalWeights = compiled.aggregator || compiled.owaWeights ? intersectionOWAWeights :
      [...Array(Math.max(0, possibilities.length - 1)).fill(0), 1];

    let result;
    if (compiled.reliabilityWeighting) {
      const reliabilityWeightedPossibilities = possibilities.map((poss, i) => poss * (metas[i]?.reliability || 1.0));
      result = OWAFusion.fuseWithMeta(reliabilityWeightedPossibilities, metas, finalWeights, defaultMode, true, owaTraceOptions);
    } else {
      result = OWAFusion.fuseWithMeta(possibilities, metas, finalWeights, defaultMode, true, owaTraceOptions);
    }

    const remediation = result.value === 0
      ? buildRemediation(remediationOptions.length > 0 ? { status: 'required', options: remediationOptions } : null, { status: 'required' })
      : null;
    return {
      possibility: result.value,
      ...(collectValues && { collectedValues: allCollectedValues }),
      ...(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
            }
          } : {}),
          ...(remediation ? { remediation } : {})
        }
      }),
      ...(remediation ? { remediation } : {})
    };
  }

  _evaluateExclusion(compiled, userId, userKey, objectId, objectKey, visited, currentRelation, options) {
    const { valueContext = null, collectValues = false, includeMeta = true, trackEvaluation = false } = options;
    const includeOwaTrace = trackEvaluation && includeMeta;
    const owaTraceOptions = includeOwaTrace ? { includeTrace: true } : null;
    if (compiled.children.length !== 2) {
      return {
        possibility: 0,
        ...(collectValues && { collectedValues: [] }),
        ...(includeMeta && { meta: { operation: 'exclusion', error: 'exclusion_requires_exactly_two_rules' } }),
        reason: 'exclusion_requires_exactly_two_rules'
      };
    }

    const visitedA = new Set(visited);
    const visitedB = new Set(visited);
    const a = this.evaluate(compiled.children[0], userId, userKey, objectId, objectKey, visitedA, currentRelation, options);
    const b = this.evaluate(compiled.children[1], userId, userKey, objectId, objectKey, visitedB, currentRelation, options);

    const allCollectedValues = collectValues ? [] : null;
    if (collectValues && a.collectedValues && Array.isArray(a.collectedValues)) {
      allCollectedValues.push(...a.collectedValues);
      if (valueContext) valueContext.addCollectedValues(a.collectedValues, compiled.children[0].type, compiled.children[0]);
    }
    if (collectValues && b.collectedValues && Array.isArray(b.collectedValues)) {
      allCollectedValues.push(...b.collectedValues);
      if (valueContext) valueContext.addCollectedValues(b.collectedValues, compiled.children[1].type, compiled.children[1]);
    }

    let possibility;
    let result;
    if (compiled.aggregator || compiled.owaWeights) {
      const possibilities = [a.possibility, 1 - b.possibility];
      const metas = [a.meta, { exclusion_complement: b.meta }];
      const exclusionOWAWeights = compiled._precomputedWeights && compiled._precomputedWeights.length === 2
        ? compiled._precomputedWeights
        : getOWAWeightsFromRule(compiled, 2, metas);
      result = OWAFusion.fuseWithMeta(possibilities, metas, exclusionOWAWeights, compiled.aggregator || 'min', true, owaTraceOptions);
      possibility = result.value;
    } else {
      possibility = a.possibility * (1 - b.possibility);
    }

    return {
      possibility,
      ...(collectValues && { collectedValues: allCollectedValues }),
      ...(includeMeta && {
        meta: {
          operation: 'exclusion',
          childA: a.meta,
          childB: b.meta,
          aggregator: compiled.aggregator || 'standard',
          ...(includeOwaTrace && result?.trace ? {
            owa: {
              level: null,
              aggregator: compiled.aggregator || 'standard',
              weights: result.trace.weights,
              sortedValues: result.trace.sortedValues,
              contributions: result.trace.contributions,
              selectedIndex: result.trace.selectedIndex
            }
          } : {})
        }
      })
    };
  }

  _evaluateDirectList(compiled, userId, userKey, objectId, objectKey, visited, currentRelation, options, op) {
    const { fastPath = false, minPossibility = null, valueContext = null, collectValues = false, includeMeta = true, trackEvaluation = false } = options;
    const includeOwaTrace = trackEvaluation && includeMeta;
    const owaTraceOptions = includeOwaTrace ? { includeTrace: true } : null;

    const possibilities = [];
    const metas = [];
    const allCollectedValues = collectValues ? [] : null;
    const directRules = compiled._optimized.direct;

    for (const direct of directRules) {
      let directRel;
      if (direct.reverse) {
        directRel = this.arbiter.relationManager.getDirectRelation(objectId, direct.relation, userId, options);
      } else {
        directRel = this.arbiter.relationManager.getDirectRelation(userId, direct.relation, objectId, options);
      }

      const possibility = directRel ? directRel.possibility : 0;
      possibilities.push(possibility);
      if (includeMeta) {
        const _src = directRel ? (directRel.source || 'persistent') : null;
        const _allowMeta = directRel ? {
          ruleType: 'direct',
          reason: 'direct',
          source: _src,
          layer_name: directRel.layer_name || null,
          source_class: directRel.source_class || null,
          reducer_applied: directRel.reducer_applied || null
        } : null;
        metas.push(directRel ? {
          ruleType: 'direct',
          relation: direct.relation,
          reverse: direct.reverse || false,
          strength: possibility,
          source: _src,
          allow: _allowMeta
        } : { ruleType: 'direct', relation: direct.relation, reason: 'no_relation' });
      } else {
        metas.push(null);
      }

      if (collectValues && direct.collectValues !== false && directRel && directRel.value !== undefined) {
        const sourceEntity = direct.reverse ? objectKey : userKey;
        const targetEntity = direct.reverse ? userKey : objectKey;
        const path = [sourceEntity, targetEntity];
        const collected = this.ruleEvaluator.ruleHandlers.direct._createCollectedValue(
          directRel.value,
          directRel.possibility,
          path,
          {
            entityKey: sourceEntity,
            relation: direct.relation,
            step: 0
          },
          {
            timestamp: directRel.changed_last_at || directRel.updated_last_at || Date.now(),
            reliability: 1.0,
            source: directRel.source || 'persistent'
          }
        );
        allCollectedValues.push(collected);
        if (valueContext) {
          valueContext.addCollectedValues([collected], 'direct', direct);
        }
      }

      if (fastPath && minPossibility !== null) {
        if (op === 'union' && possibility >= minPossibility) {
          return {
            possibility,
            ...(collectValues && { collectedValues: allCollectedValues }),
            ...(includeMeta && { meta: metas[metas.length - 1] })
          };
        }
        if (op === 'intersection' && possibility < minPossibility) {
          return {
            possibility,
            ...(collectValues && { collectedValues: allCollectedValues }),
            ...(includeMeta && { meta: metas[metas.length - 1] })
          };
        }
      }
    }

    if (!possibilities.length) {
      return {
        possibility: 0,
        ...(collectValues && { collectedValues: allCollectedValues }),
        ...(includeMeta && { meta: { operation: op, childCount: 0 } })
      };
    }

    const weights = compiled._precomputedWeights && compiled._precomputedWeights.length === possibilities.length
      ? compiled._precomputedWeights
      : getOWAWeightsFromRule(compiled, possibilities.length, metas);
    const aggregator = op === 'intersection' ? (compiled.aggregator || 'min') : (compiled.aggregator || 'max');
    const result = OWAFusion.fuseWithMeta(possibilities, metas, weights, aggregator, true, owaTraceOptions);

    return {
      possibility: result.value,
      ...(collectValues && { collectedValues: allCollectedValues }),
      ...(includeMeta && {
        meta: {
          ...result.meta,
          operation: op,
          childCount: possibilities.length,
          aggregator,
          ...(includeOwaTrace && result.trace ? {
            owa: {
              level: null,
              aggregator,
              weights: result.trace.weights,
              sortedValues: result.trace.sortedValues,
              contributions: result.trace.contributions,
              selectedIndex: result.trace.selectedIndex
            }
          } : {})
        }
      })
    };
  }

  _evaluateDefeasible(compiled, userId, userKey, objectId, objectKey, visited, currentRelation, options) {
    const { binary = false, fastPath = false, minPossibility = 0.0 } = options;
    const mode = compiled.mode || (binary ? 'binary' : (fastPath ? 'threshold' : 'normal'));
    const allCollectedValues = [];
    let neverResult = null, defeatersResult = null, strictResult = null, defeasibleResult = null, requiresResult = null;

    if (compiled.never) {
      neverResult = this._evaluateUnion(compiled.never, userId, userKey, objectId, objectKey, visited, currentRelation, options);
      if (neverResult.collectedValues) allCollectedValues.push(...neverResult.collectedValues);
    }
    if (compiled.always) {
      strictResult = this.evaluate(compiled.always, userId, userKey, objectId, objectKey, visited, currentRelation, options);
      if (strictResult.collectedValues) allCollectedValues.push(...strictResult.collectedValues);
    }
    if (compiled.requires) {
      requiresResult = this._evaluateUnion(compiled.requires, userId, userKey, objectId, objectKey, visited, currentRelation, options);
      if (requiresResult.collectedValues) allCollectedValues.push(...requiresResult.collectedValues);
    }
    if (compiled.unless) {
      defeatersResult = this._evaluateUnion(compiled.unless, userId, userKey, objectId, objectKey, visited, currentRelation, options);
      if (defeatersResult.collectedValues) allCollectedValues.push(...defeatersResult.collectedValues);
    }
    if (compiled.when) {
      defeasibleResult = this._evaluateIntersection(compiled.when, userId, userKey, objectId, objectKey, visited, currentRelation, options);
      if (defeasibleResult.collectedValues) allCollectedValues.push(...defeasibleResult.collectedValues);
    }

    const ruleMeta = {
      minPossibility: compiled.minPossibility,
      priority: compiled.priority,
      mode
    };

    const finalResult = this.logicalOperators._applyDefeasibleLogic(mode, ruleMeta, neverResult, defeatersResult, strictResult, defeasibleResult, requiresResult, allCollectedValues);
    return finalResult;
  }
}
