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