| 1 | using AgentForge.Abstractions; |
| 2 | |
| 3 | namespace AgentForge.Plugin.Grouping.Core; |
| 4 | |
| 5 | public sealed class ForgeGroupingAxisCatalog<TAxis, TItem> |
| 6 | where TAxis : IForgeGroupingAxis<TItem> |
| 7 | { |
| 8 | private readonly IReadOnlyList<TAxis> _axes; |
| 9 | private readonly Dictionary<string, TAxis> _byId; |
| 10 | |
| 11 | public ForgeGroupingAxisCatalog(IEnumerable<TAxis> axes, string domainLabel) |
| 12 | { |
| 13 | var list = axes |
| 14 | .OrderBy(axis => axis.Order) |
| 15 | .ThenBy(axis => axis.Id, StringComparer.OrdinalIgnoreCase) |
| 16 | .ToList(); |
| 17 | |
| 18 | _byId = new Dictionary<string, TAxis>(StringComparer.OrdinalIgnoreCase); |
| 19 | foreach (var axis in list) |
| 20 | { |
| 21 | if (string.IsNullOrWhiteSpace(axis.Id)) |
| 22 | throw new InvalidOperationException($"{domainLabel} grouping axis Id is required."); |
| 23 | |
| 24 | if (!_byId.TryAdd(axis.Id.Trim(), axis)) |
| 25 | { |
| 26 | throw new InvalidOperationException( |
| 27 | $"Duplicate {domainLabel} grouping axis id '{axis.Id}'."); |
| 28 | } |
| 29 | } |
| 30 | |
| 31 | _axes = list; |
| 32 | } |
| 33 | |
| 34 | public IReadOnlyList<TAxis> Axes => _axes; |
| 35 | |
| 36 | public bool TryGetAxis(string axisId, out TAxis? axis) => |
| 37 | _byId.TryGetValue(axisId, out axis); |
| 38 | |
| 39 | public IReadOnlyList<string> ParseStack(string? groupStackQuery) |
| 40 | { |
| 41 | if (string.IsNullOrWhiteSpace(groupStackQuery)) |
| 42 | return []; |
| 43 | |
| 44 | var result = new List<string>(); |
| 45 | foreach (var part in groupStackQuery.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) |
| 46 | { |
| 47 | if (!_byId.ContainsKey(part)) |
| 48 | continue; |
| 49 | if (result.Contains(part, StringComparer.OrdinalIgnoreCase)) |
| 50 | continue; |
| 51 | if (result.Count >= ForgeGroupingStackOptions.MaxStackDepth) |
| 52 | break; |
| 53 | result.Add(part); |
| 54 | } |
| 55 | |
| 56 | return result; |
| 57 | } |
| 58 | } |
| 59 | |