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);
}
}