using Greencore.platform.rules;
namespace Greencore.platform.validators;
/// Mensaje asociado a una aparición; se resuelve utilizando evidencia ya capturada.
public sealed class RuleDiagnosticDefinition
{
private readonly Func _message;
public string Code { get; }
public RuleDiagnosticDefinition(string code, Func message)
{
ArgumentException.ThrowIfNullOrWhiteSpace(code);
ArgumentNullException.ThrowIfNull(message);
Code = code;
_message = message;
}
public RuleDiagnosticDefinition(string code, string message) : this(code, _ => message)
{
ArgumentException.ThrowIfNullOrWhiteSpace(message);
}
public string Format(RuleEvaluation evaluation)
{
var message = _message(evaluation);
ArgumentException.ThrowIfNullOrWhiteSpace(message);
return message;
}
}
public sealed record RuleDiagnostic(RuleEvaluation Evaluation, string Code, string Message, IReadOnlyList Children)
{
public string? Path => Evaluation.Path;
public bool IsGroup => Evaluation.Kind != RuleNodeKind.Leaf;
}
/// Proyección neutral; las alternativas exitosas nunca publican sus fallos parciales.
public sealed class RuleDiagnostics
{
private readonly IReadOnlyDictionary _definitions;
internal RuleDiagnosticDefinition? Find(string nodeId) => _definitions.GetValueOrDefault(nodeId);
public RuleDiagnostics(IEnumerable> definitions)
{
ArgumentNullException.ThrowIfNull(definitions);
var copy = new Dictionary(StringComparer.Ordinal);
foreach (var pair in definitions)
{
ArgumentException.ThrowIfNullOrWhiteSpace(pair.Key);
ArgumentNullException.ThrowIfNull(pair.Value);
copy.Add(pair.Key, pair.Value);
}
_definitions = copy;
}
public IReadOnlyList Project(RuleEvaluation evaluation)
{
ArgumentNullException.ThrowIfNull(evaluation);
if (evaluation.Status != RuleStatus.Unsatisfied) return [];
var children = evaluation.Kind == RuleNodeKind.Not ? [] : evaluation.Children.SelectMany(Project).ToArray();
if (_definitions.TryGetValue(evaluation.NodeId, out var definition))
return [new(evaluation, definition.Code, definition.Format(evaluation), Array.AsReadOnly(children))];
if (evaluation.Kind is RuleNodeKind.Leaf or RuleNodeKind.AnyOf or RuleNodeKind.Not)
throw new InvalidOperationException($"Missing diagnostic for node '{evaluation.NodeId}'.");
return Array.AsReadOnly(children);
}
/// Rechaza antes de evaluar las ramas que pueden fallar sin diagnóstico.
public void Validate(RuleNode tree)
{
ArgumentNullException.ThrowIfNull(tree);
if (tree.Kind is RuleNodeKind.Leaf or RuleNodeKind.AnyOf or RuleNodeKind.Not && !_definitions.ContainsKey(tree.NodeId))
throw new ArgumentException($"Missing diagnostic for node '{tree.NodeId}'.", nameof(tree));
if (tree.Kind == RuleNodeKind.Not) return;
var children = tree.Kind == RuleNodeKind.When ? tree.Children.Skip(1) : tree.Children;
foreach (var child in children) Validate(child);
}
}