using System.ComponentModel;
using Greencore.platform.rules;
namespace Greencore.platform.validators;
/// Selecciona resúmenes por campo, detalles terminales o el árbol completo.
public enum DiagnosticView { Summary, Details, All }
public sealed record ValidationReport(RuleEvaluation Evaluation, IReadOnlyList Diagnostics)
{
public bool IsValid => Evaluation.Status != RuleStatus.Unsatisfied;
/// Recorre el árbol sin perder la distinción entre grupos y detalles.
public IEnumerable EnumerateDiagnostics(bool includeGroups = true)
{
var pending = new Stack(Diagnostics.Reverse());
while (pending.TryPop(out var diagnostic))
{
if (includeGroups || diagnostic.Children.Count == 0) yield return diagnostic;
foreach (var child in diagnostic.Children.Reverse()) pending.Push(child);
}
}
public IReadOnlyList ForField(string path, bool includeGroups = false)
{
ArgumentException.ThrowIfNullOrWhiteSpace(path);
return EnumerateDiagnostics(includeGroups).Where(item => item.Path == path).ToArray();
}
public IReadOnlyList ForField(System.Linq.Expressions.Expression> property, bool includeGroups = false) => ForField(ValidationPath.Get(property), includeGroups);
/// Devuelve mensajes del campo sin repetir resúmenes y detalles de la misma rama.
public IReadOnlyList ForField(string path, DiagnosticView view)
{
ArgumentException.ThrowIfNullOrWhiteSpace(path);
if (!Enum.IsDefined(view)) throw new ArgumentOutOfRangeException(nameof(view));
if (view != DiagnosticView.Summary) return ForField(path, view == DiagnosticView.All);
var result = new List();
var pending = new Stack(Diagnostics.Reverse());
while (pending.TryPop(out var item))
{
if (item.Path == path) result.Add(item);
else foreach (var child in item.Children.Reverse()) pending.Push(child);
}
return result.AsReadOnly();
}
public IReadOnlyList ForField(System.Linq.Expressions.Expression> property, DiagnosticView view) => ForField(ValidationPath.Get(property), view);
}
/// Plan listo para evaluar, con árbol y diagnósticos comprobados al construir.
public sealed class ValidationPlan
{
public RuleNode Tree { get; }
public RuleDiagnostics Diagnostics { get; }
public FormValidationSession CreateSession(Action? dispatch = null) => new(this, dispatch);
public FormValidationSession CreateSession(TimeSpan debounce, Action? dispatch = null) => new(this, debounce, dispatch);
public ValidationPlan(RuleNode tree, RuleDiagnostics diagnostics)
{
ArgumentNullException.ThrowIfNull(tree);
ArgumentNullException.ThrowIfNull(diagnostics);
diagnostics.Validate(tree);
Tree = tree;
Diagnostics = diagnostics;
}
public ValidationReport Validate(T value, C context, RuleEvaluationOptions? options = null, CancellationToken cancellationToken = default)
{
var result = RuleEvaluator.Evaluate(Tree, value, context, options, cancellationToken);
return new(result, Diagnostics.Project(result));
}
public async ValueTask ValidateAsync(T value, C context, CancellationToken cancellationToken = default, RuleEvaluationOptions? options = null)
{
var result = await RuleEvaluator.EvaluateAsync(Tree, value, context, options, cancellationToken).ConfigureAwait(false);
return new(result, Diagnostics.Project(result));
}
}
public static class LocalValidationExtensions
{
[EditorBrowsable(EditorBrowsableState.Never)]
public static ValueTask> UpdateAsync(this FormValidationSession session, T candidate, CancellationToken cancellationToken = default) => session.UpdateAsync(candidate, default, cancellationToken);
public static ValueTask UpdateWithResultAsync(this FormValidationSession session, T candidate, CancellationToken cancellationToken = default) => session.UpdateWithResultAsync(candidate, default, cancellationToken);
public static ValueTask EvaluateAsync(this FormValidationSession session, T candidate, TimeSpan delay = default, CancellationToken cancellationToken = default) => session.EvaluateAsync(candidate, default, delay, cancellationToken);
[EditorBrowsable(EditorBrowsableState.Never)]
public static ValueTask> ValidateWithResultAsync(this FormValidationSession session, T candidate, TimeSpan delay = default, CancellationToken cancellationToken = default) => session.ValidateWithResultAsync(candidate, default, delay, cancellationToken);
public static ValidationBuilder Include(this ValidationBuilder builder, ValidationPlan plan)
{
ArgumentNullException.ThrowIfNull(plan);
RuleNode Lift(RuleNode node) => new(node.NodeId, node.Kind, node.Children.Select(Lift), node.Definition?.WithContext(), node.Path);
return builder.Include(new ValidationPlan(Lift(plan.Tree), plan.Diagnostics));
}
[EditorBrowsable(EditorBrowsableState.Never)]
public static ValueTask ValidateAsync(this FormValidationSession session, T candidate, TimeSpan delay = default, CancellationToken cancellationToken = default) => session.ValidateAsync(candidate, default, delay, cancellationToken);
public static ValidationReport Validate(this ValidationPlan plan, T value, RuleEvaluationOptions? options = null, CancellationToken cancellationToken = default) => plan.Validate(value, default, options, cancellationToken);
public static ValueTask ValidateAsync(this ValidationPlan plan, T value, CancellationToken cancellationToken = default) => plan.ValidateAsync(value, default, cancellationToken);
public static ValueTask ValidateAsync(this ValidationPlan plan, T value, RuleEvaluationOptions options, CancellationToken cancellationToken = default) => plan.ValidateAsync(value, default, cancellationToken, options);
}