using System.ComponentModel; namespace Greencore.platform.validators; public enum FormValidationState { Idle, Pending, Valid, Invalid, Failed, Canceled } public sealed record FormValidationSnapshot(long Version, FormValidationState State, ValidationReport? Report = null, Exception? Error = null); /// Resultado de una evaluación de formulario sin exponer el vocabulario interno de datos. public sealed record FormValidationOperationResult(long Version, FormValidationState State, ValidationReport? Report, Exception? Error, bool IsCurrentAtCompletion); /// Coordina estados y respuestas del formulario sin publicar resultados de ediciones anteriores. public sealed class FormValidationSession : IDisposable { private readonly ValidationPlan _plan; private readonly AsyncDataSession _session; public TimeSpan Debounce { get; } public FormValidationSnapshot Snapshot => Map(_session.Snapshot); public event Action? Changed; public FormValidationSession(ValidationPlan plan, Action? dispatch = null) : this(plan, TimeSpan.Zero, dispatch) { } public FormValidationSession(ValidationPlan plan, TimeSpan debounce, Action? dispatch = null) { ArgumentNullException.ThrowIfNull(plan); if (debounce < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(debounce)); Debounce = debounce; _plan = plan; _session = new(dispatch); _session.Changed += snapshot => Changed?.Invoke(Map(snapshot)); } /// Recibe una instantánea del formulario y un contexto por ejecución; el retraso es cancelable. /// Para código nuevo, usar UpdateWithResultAsync para recibir FormValidationOperationResult. [EditorBrowsable(EditorBrowsableState.Never)] public ValueTask> UpdateAsync(T candidate, C context, CancellationToken cancellationToken = default) => ValidateWithResultAsync(candidate, context, Debounce, cancellationToken); /// Actualiza el formulario y devuelve un resultado con estados de validación. public ValueTask UpdateWithResultAsync(T candidate, C context, CancellationToken cancellationToken = default) => EvaluateAsync(candidate, context, Debounce, cancellationToken); /// Evalúa inmediatamente salvo que se indique un retraso explícito. /// Para código nuevo que necesita el resultado, usar EvaluateAsync. [EditorBrowsable(EditorBrowsableState.Never)] public ValueTask ValidateAsync(T candidate, C context, TimeSpan delay = default, CancellationToken cancellationToken = default) { if (delay < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(delay)); return _session.RunAsync(async token => { if (delay > TimeSpan.Zero) await Task.Delay(delay, token).ConfigureAwait(false); return await _plan.ValidateAsync(candidate, context, token).ConfigureAwait(false); }, cancellationToken); } /// Devuelve el resultado calculado sin esperar al dispatcher de la UI. /// Para código nuevo, usar EvaluateAsync para recibir estados de formulario. [EditorBrowsable(EditorBrowsableState.Never)] public ValueTask> ValidateWithResultAsync(T candidate, C context, TimeSpan delay = default, CancellationToken cancellationToken = default) { if (delay < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(delay)); return _session.RunWithResultAsync(async token => { if (delay > TimeSpan.Zero) await Task.Delay(delay, token).ConfigureAwait(false); return await _plan.ValidateAsync(candidate, context, token).ConfigureAwait(false); }, cancellationToken); } /// Evalúa y devuelve el informe con el mismo vocabulario que usa el formulario. public async ValueTask EvaluateAsync(T candidate, C context, TimeSpan delay = default, CancellationToken cancellationToken = default) { var operation = await ValidateWithResultAsync(candidate, context, delay, cancellationToken).ConfigureAwait(false); return Map(operation); } public void Dispose() { _session.Dispose(); Changed = null; } private static FormValidationSnapshot Map(DataSnapshot snapshot) => new(snapshot.Version, snapshot.State switch { DataState.Idle => FormValidationState.Idle, DataState.Pending => FormValidationState.Pending, DataState.Ready => snapshot.Value!.IsValid ? FormValidationState.Valid : FormValidationState.Invalid, DataState.Failed => FormValidationState.Failed, _ => FormValidationState.Canceled }, snapshot.Value, snapshot.Error); private static FormValidationOperationResult Map(DataOperationResult operation) { var snapshot = Map(operation.Snapshot); return new(snapshot.Version, snapshot.State, snapshot.Report, snapshot.Error, operation.IsCurrentAtCompletion); } }