| 1 | using AgentForge.Abstractions; |
| 2 | using AgentForge.Plugin.Grouping.Core; |
| 3 | using AgentForge.Plugin.Grouping.Repositories.Axes; |
| 4 | using AgentForge.Plugin.Grouping.Repositories.Enrichers; |
| 5 | using AgentForge.Plugin.View; |
| 6 | using Microsoft.Extensions.DependencyInjection; |
| 7 | |
| 8 | namespace AgentForge.Plugin.Grouping.Repositories; |
| 9 | |
| 10 | public sealed class ForgeOrgCatalogGroupingService( |
| 11 | ForgeGroupingAxisCatalog<IForgeCatalogGroupingAxis, ForgeCatalogGroupingRepoRow> axes, |
| 12 | IEnumerable<IForgeCatalogGroupingRepoEnricher> enrichers) : IForgeOrgCatalogGrouping |
| 13 | { |
| 14 | public IReadOnlyList<string> ParseGroupStack(string? groupStackQuery) => |
| 15 | axes.ParseStack(groupStackQuery); |
| 16 | |
| 17 | public int ParseEditorRowCount(string? groupStackRowsQuery, int stackCount) => |
| 18 | ForgeGroupingStackOptions.ParseEditorRowCount(groupStackRowsQuery, stackCount); |
| 19 | |
| 20 | public string BuildCatalogUrl( |
| 21 | in ForgeOrgCatalogViewContext context, |
| 22 | string? groupPath = null, |
| 23 | IReadOnlyList<string>? groupStack = null, |
| 24 | int? editorRowCount = null) |
| 25 | { |
| 26 | var parts = new List<string>(); |
| 27 | var activeGroupPath = groupPath ?? context.ActiveGroupPath; |
| 28 | if (!string.IsNullOrWhiteSpace(activeGroupPath)) |
| 29 | parts.Add($"group={Uri.EscapeDataString(ForgeRepoGroupPath.ToUrlPath(activeGroupPath))}"); |
| 30 | |
| 31 | var stack = groupStack ?? context.GroupStack; |
| 32 | if (stack.Count > 0) |
| 33 | parts.Add($"groupStack={Uri.EscapeDataString(string.Join(',', stack))}"); |
| 34 | |
| 35 | var rows = editorRowCount ?? context.EditorRowCount; |
| 36 | if (rows > 0 && ForgeGroupingStackOptions.ShouldEmitEditorRowCountQuery(rows, stack.Count)) |
| 37 | parts.Add($"groupStackRows={rows}"); |
| 38 | |
| 39 | return parts.Count == 0 |
| 40 | ? context.CatalogBasePath |
| 41 | : $"{context.CatalogBasePath}?{string.Join('&', parts)}"; |
| 42 | } |
| 43 | |
| 44 | public string RenderToolbar(in ForgeOrgCatalogViewContext context) => |
| 45 | ForgeViewCatalogGroupingToolbar.Render(this, axes, context); |
| 46 | |
| 47 | public void AppendRepoNodes( |
| 48 | ICollection<string> childNodes, |
| 49 | IReadOnlyList<ForgeOrgCatalogRepoItem> repos, |
| 50 | in ForgeOrgCatalogViewContext context) |
| 51 | { |
| 52 | if (repos.Count == 0) |
| 53 | return; |
| 54 | |
| 55 | if (context.GroupStack.Count == 0) |
| 56 | { |
| 57 | foreach (var repo in repos) |
| 58 | childNodes.Add(ForgeViewCatalogRepoNode.Render(repo)); |
| 59 | return; |
| 60 | } |
| 61 | |
| 62 | var body = RenderRepoBody(repos, context); |
| 63 | if (!string.IsNullOrEmpty(body)) |
| 64 | childNodes.Add(ForgeHtml.Li("org-repo-grouped", body)); |
| 65 | } |
| 66 | |
| 67 | public string RenderRepoBody( |
| 68 | IReadOnlyList<ForgeOrgCatalogRepoItem> repos, |
| 69 | in ForgeOrgCatalogViewContext context) |
| 70 | { |
| 71 | if (repos.Count == 0) |
| 72 | return ""; |
| 73 | |
| 74 | if (context.GroupStack.Count == 0) |
| 75 | return ForgeHtml.Ul("plain org-repo-tree-children", [.. repos.Select(ForgeViewCatalogRepoNode.Render)]); |
| 76 | |
| 77 | var rows = BuildRows(repos); |
| 78 | var nested = ForgeGroupingNestedRenderer.Render( |
| 79 | axes, |
| 80 | rows, |
| 81 | context.GroupStack, |
| 82 | static row => ForgeViewCatalogRepoNode.Render(row.Item), |
| 83 | listClass: "plain org-repo-tree-children"); |
| 84 | |
| 85 | return string.IsNullOrEmpty(nested) |
| 86 | ? ForgeHtml.Ul("plain org-repo-tree-children", [.. repos.Select(ForgeViewCatalogRepoNode.Render)]) |
| 87 | : nested; |
| 88 | } |
| 89 | |
| 90 | private List<ForgeCatalogGroupingRepoRow> BuildRows(IReadOnlyList<ForgeOrgCatalogRepoItem> repos) |
| 91 | { |
| 92 | var rows = repos |
| 93 | .Select(repo => new ForgeCatalogGroupingRepoRow { Item = repo }) |
| 94 | .ToList(); |
| 95 | |
| 96 | foreach (var enricher in enrichers) |
| 97 | enricher.Enrich(repos, rows); |
| 98 | |
| 99 | return rows; |
| 100 | } |
| 101 | |
| 102 | internal static void RegisterAxes(IServiceCollection services) |
| 103 | { |
| 104 | services.AddSingleton<IForgeCatalogGroupingAxis, VisibilityCatalogGroupingAxis>(); |
| 105 | services.AddSingleton<IForgeCatalogGroupingAxis, DriveModeCatalogGroupingAxis>(); |
| 106 | services.AddSingleton<IForgeCatalogGroupingAxis, LastCommitCatalogGroupingAxis>(); |
| 107 | services.AddSingleton<IForgeCatalogGroupingAxis, CreatedAtCatalogGroupingAxis>(); |
| 108 | services.AddSingleton<IForgeCatalogGroupingAxis, OrgCatalogGroupingAxis>(); |
| 109 | services.AddSingleton<IForgeCatalogGroupingAxis, GroupCatalogGroupingAxis>(); |
| 110 | services.AddSingleton<IForgeCatalogGroupingRepoEnricher, LastCommitRepoEnricher>(); |
| 111 | services.AddSingleton<ForgeGroupingAxisCatalog<IForgeCatalogGroupingAxis, ForgeCatalogGroupingRepoRow>>(sp => |
| 112 | new(sp.GetServices<IForgeCatalogGroupingAxis>(), "repository")); |
| 113 | } |
| 114 | } |
| 115 | |