namespace Greencore.platform.rules;
/// Una aparición de una regla o un operador dentro de un árbol.
public sealed class RuleNode
{
public string NodeId { get; }
public string? Path { get; }
public RuleNodeKind Kind { get; }
public RuleDefinition? Definition { get; }
public IReadOnlyList> Children { get; }
public bool IsAsync => Definition?.IsAsync == true || Children.Any(child => child.IsAsync);
public RuleNode(string nodeId, RuleNodeKind kind, IEnumerable> children, RuleDefinition? definition = null, string? path = null)
{
ArgumentException.ThrowIfNullOrWhiteSpace(nodeId);
ArgumentNullException.ThrowIfNull(children);
if (!Enum.IsDefined(kind)) throw new ArgumentOutOfRangeException(nameof(kind));
var copy = children.ToArray();
foreach (var child in copy) ArgumentNullException.ThrowIfNull(child);
var valid = kind switch
{
RuleNodeKind.Leaf => definition is not null && copy.Length == 0,
RuleNodeKind.Not => definition is null && copy.Length == 1,
RuleNodeKind.When or RuleNodeKind.Prerequisite => definition is null && copy.Length == 2,
_ => definition is null && copy.Length > 0
};
if (!valid) throw new ArgumentException("Invalid children or definition for this operator.");
NodeId = nodeId;
Kind = kind;
Definition = definition;
Path = path;
Children = Array.AsReadOnly(copy);
var ids = new HashSet(StringComparer.Ordinal);
ValidateIds(this, ids);
}
private static void ValidateIds(RuleNode node, HashSet ids)
{
if (!ids.Add(node.NodeId)) throw new ArgumentException($"Duplicate node id: {node.NodeId}.");
foreach (var child in node.Children) ValidateIds(child, ids);
}
}
/// Construcción explícita de operadores sin precedencia implícita.
public static class RuleTree
{
public static RuleNode AllOf(string id, params RuleNode[] children) => new(id, RuleNodeKind.AllOf, children);
public static RuleNode AnyOf(string id, params RuleNode[] children) => new(id, RuleNodeKind.AnyOf, children);
public static RuleNode Not(string id, RuleNode child) => new(id, RuleNodeKind.Not, [child]);
public static RuleNode When(string id, RuleNode condition, RuleNode child) => new(id, RuleNodeKind.When, [condition, child]);
public static RuleNode Require(string id, RuleNode condition, RuleNode child) => new(id, RuleNodeKind.Prerequisite, [condition, child]);
}
public static class Rules
{
public static RuleTreeBuilder For() => new();
public static RuleTreeBuilder For() => new();
}
/// Constructor fluido; cada operación devuelve una configuración independiente.
public sealed class RuleTreeBuilder
{
private readonly RuleNode? _root;
public RuleTreeBuilder() { }
private RuleTreeBuilder(RuleNode root) => _root = root;
public RuleTreeBuilder Add(RuleNode node)
{
ArgumentNullException.ThrowIfNull(node);
if (_root is not null) throw new InvalidOperationException("Use an explicit group to combine nodes.");
return new(node);
}
public RuleTreeBuilder Require(RuleNode node) => Add(node);
public RuleTreeBuilder Then(string id, RuleNode node) => new(RuleTree.Require(id, Build(), node));
public RuleTreeBuilder AllOf(string id, params RuleNode[] nodes) => Add(RuleTree.AllOf(id, nodes));
public RuleTreeBuilder AnyOf(string id, params RuleNode[] nodes) => Add(RuleTree.AnyOf(id, nodes));
public RuleTreeBuilder When(string id, RuleNode condition) => new(RuleTree.When(id, condition, Build()));
public RuleTreeBuilder Unless(string id, RuleNode condition) => When(id, RuleTree.Not(id + ".condition", condition));
public RuleNode Build() => _root ?? throw new InvalidOperationException("An empty tree cannot be built.");
}