using Greencore.platform.rules; namespace Greencore.platform.validators; /// Motor secuencial común para árboles locales y contextuales. public static class RuleEvaluator { public static RuleEvaluation Evaluate(RuleNode tree, T value, RuleEvaluationOptions? options = null, CancellationToken cancellationToken = default) => Evaluate(tree, value, default(NoRuleContext), options, cancellationToken); public static ValueTask EvaluateAsync(RuleNode tree, T value, RuleEvaluationOptions? options = null, CancellationToken cancellationToken = default) => EvaluateAsync(tree, value, default(NoRuleContext), options, cancellationToken); public static RuleEvaluation Evaluate(RuleNode tree, T value, C context, RuleEvaluationOptions? options = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(tree); if (tree.IsAsync) throw new InvalidOperationException("This tree requires asynchronous evaluation."); // Solo las hojas síncronas pueden llegar aquí; no se bloquea ninguna tarea externa. return EvaluateAsync(tree, value, context, options, cancellationToken).GetAwaiter().GetResult(); } public static ValueTask EvaluateAsync(RuleNode tree, T value, C context, RuleEvaluationOptions? options = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(tree); var settings = options ?? new(); if (!Enum.IsDefined(settings.Execution) || !Enum.IsDefined(settings.Detail)) throw new ArgumentOutOfRangeException(nameof(options)); return Visit(tree, value, context, settings, cancellationToken); } private static async ValueTask Visit(RuleNode node, T value, C context, RuleEvaluationOptions options, CancellationToken token) { token.ThrowIfCancellationRequested(); if (node.Definition is { } definition) { var result = await definition.AsyncCondition(value, context, token).ConfigureAwait(false); token.ThrowIfCancellationRequested(); ArgumentNullException.ThrowIfNull(result); return Result(node, result.IsSatisfied ? RuleStatus.Satisfied : RuleStatus.Unsatisfied, [], result.Evidence); } var children = new List(); if (node.Kind is RuleNodeKind.When or RuleNodeKind.Prerequisite) { var gate = await Visit(node.Children[0], value, context, options, token).ConfigureAwait(false); children.Add(gate); if (gate.Status != RuleStatus.Satisfied) { children.Add(Skip(node.Children[1], node.Kind == RuleNodeKind.When ? RuleSkipReason.NotApplicable : RuleSkipReason.Prerequisite)); var status = node.Kind == RuleNodeKind.When || gate.Status == RuleStatus.Skipped ? RuleStatus.Skipped : RuleStatus.Unsatisfied; return Finish(node, status, children, options); } var dependent = await Visit(node.Children[1], value, context, options, token).ConfigureAwait(false); children.Add(dependent); return Finish(node, dependent.Status, children, options); } if (node.Kind == RuleNodeKind.Not) { var child = await Visit(node.Children[0], value, context, options, token).ConfigureAwait(false); var status = child.Status switch { RuleStatus.Satisfied => RuleStatus.Unsatisfied, RuleStatus.Unsatisfied => RuleStatus.Satisfied, _ => RuleStatus.Skipped }; return Finish(node, status, [child], options); } var stopped = false; foreach (var child in node.Children) { if (stopped) { children.Add(Skip(child, RuleSkipReason.ShortCircuit)); continue; } var result = await Visit(child, value, context, options, token).ConfigureAwait(false); children.Add(result); stopped = node.Kind == RuleNodeKind.AnyOf && result.Status == RuleStatus.Satisfied || options.Execution == RuleExecution.ShortCircuit && node.Kind == RuleNodeKind.AllOf && result.Status == RuleStatus.Unsatisfied; } var applicable = children.Where(child => child.Status != RuleStatus.Skipped).ToArray(); var final = applicable.Length == 0 ? RuleStatus.Skipped : node.Kind == RuleNodeKind.AllOf ? applicable.All(child => child.Status == RuleStatus.Satisfied) ? RuleStatus.Satisfied : RuleStatus.Unsatisfied : applicable.Any(child => child.Status == RuleStatus.Satisfied) ? RuleStatus.Satisfied : RuleStatus.Unsatisfied; return Finish(node, final, children, options); } private static RuleEvaluation Finish(RuleNode node, RuleStatus status, IEnumerable children, RuleEvaluationOptions options) { var retained = options.Detail == RuleDetail.Trace ? children : status == RuleStatus.Unsatisfied ? children.Where(child => child.Status == RuleStatus.Unsatisfied || node.Kind == RuleNodeKind.Not) : []; return Result(node, status, retained); } private static RuleEvaluation Result(RuleNode node, RuleStatus status, IEnumerable children, object? evidence = null) => new(node.NodeId, node.Definition?.Id, node.Path, node.Kind, status, status == RuleStatus.Skipped ? RuleSkipReason.NotApplicable : RuleSkipReason.None, evidence, children); private static RuleEvaluation Skip(RuleNode node, RuleSkipReason reason) => new(node.NodeId, node.Definition?.Id, node.Path, node.Kind, RuleStatus.Skipped, reason, null, []); }