using ZB.MOM.WW.GalaxyRepository;
using ZB.MOM.WW.GalaxyRepository.Grpc;
namespace ZB.MOM.WW.MxGateway.Server.Dashboard;
///
/// Projects a shared-library into the
/// dashboard's host-side .
///
///
/// The shared ZB.MOM.WW.GalaxyRepository cache entry does not compute a
/// dashboard summary; this projector relocates the summary logic that previously
/// lived inside the gateway's inline Galaxy hierarchy cache. It groups the entry's
/// objects into template usage and category counts, maps the cache status to the
/// dashboard status, and copies the timestamps and counts the entry already carries.
///
public static class DashboardGalaxySummaryProjector
{
private const int MaxTopTemplates = 10;
///
/// Builds a from a shared-library Galaxy
/// hierarchy cache entry.
///
/// The shared-library cache entry to project.
/// The dashboard summary derived from .
public static DashboardGalaxySummary Project(GalaxyHierarchyCacheEntry entry)
{
ArgumentNullException.ThrowIfNull(entry);
return BuildSummary(entry, ComputeBreakdown(entry));
}
///
/// Computes the O(N) template-usage and category breakdown from the entry's objects.
/// This is the only part of the summary that changes with the cache
/// — the shared library replaces the object
/// set only on a heavy refresh that bumps the sequence — so callers may memoize the result
/// on the sequence. The cheap, per-tick-volatile fields are re-read fresh via
/// and must never be memoized.
///
/// The shared-library cache entry to project.
/// The template/category breakdown of 's objects.
public static GalaxyObjectBreakdown ComputeBreakdown(GalaxyHierarchyCacheEntry entry)
{
ArgumentNullException.ThrowIfNull(entry);
if (entry.Objects.Count == 0)
{
return GalaxyObjectBreakdown.Empty;
}
Dictionary objectsByCategory = new();
Dictionary templateUsage = new(StringComparer.OrdinalIgnoreCase);
foreach (GalaxyObject obj in entry.Objects)
{
objectsByCategory.TryGetValue(obj.CategoryId, out int categoryCount);
objectsByCategory[obj.CategoryId] = categoryCount + 1;
if (obj.TemplateChain.Count > 0)
{
string immediate = obj.TemplateChain[0];
if (!string.IsNullOrWhiteSpace(immediate))
{
templateUsage.TryGetValue(immediate, out int templateCount);
templateUsage[immediate] = templateCount + 1;
}
}
}
IReadOnlyList topTemplates = templateUsage
.OrderByDescending(usage => usage.Value)
.ThenBy(usage => usage.Key, StringComparer.OrdinalIgnoreCase)
.Take(MaxTopTemplates)
.Select(usage => new DashboardGalaxyTemplateUsage(usage.Key, usage.Value))
.ToArray();
IReadOnlyList objectCategories = objectsByCategory
.OrderByDescending(category => category.Value)
.ThenBy(category => category.Key)
.Select(category => new DashboardGalaxyCategoryCount(
category.Key,
ResolveCategoryName(category.Key),
category.Value))
.ToArray();
return new GalaxyObjectBreakdown(topTemplates, objectCategories);
}
///
/// Assembles a from an entry's cheap, per-tick-volatile
/// fields (status, timestamps, last error, counts) and a precomputed
/// . The volatile fields are copied fresh on every call so a
/// memoized breakdown never freezes the dashboard's health or timestamps between heavy
/// refreshes — the library mutates those fields in place at the same sequence on steady-state
/// ticks and on refresh failure.
///
/// The cache entry whose volatile fields to copy.
/// The precomputed template/category breakdown.
/// The assembled dashboard summary.
public static DashboardGalaxySummary BuildSummary(GalaxyHierarchyCacheEntry entry, GalaxyObjectBreakdown breakdown)
{
ArgumentNullException.ThrowIfNull(entry);
ArgumentNullException.ThrowIfNull(breakdown);
return new DashboardGalaxySummary(
Status: MapDashboardStatus(entry.Status),
LastQueriedAt: entry.LastQueriedAt,
LastSuccessAt: entry.LastSuccessAt,
LastDeployTime: entry.LastDeployTime,
LastError: entry.LastError,
ObjectCount: entry.ObjectCount,
AreaCount: entry.AreaCount,
AttributeCount: entry.AttributeCount,
HistorizedAttributeCount: entry.HistorizedAttributeCount,
AlarmAttributeCount: entry.AlarmAttributeCount,
TopTemplates: breakdown.TopTemplates,
ObjectCategories: breakdown.ObjectCategories);
}
private static DashboardGalaxyStatus MapDashboardStatus(GalaxyCacheStatus status) => status switch
{
GalaxyCacheStatus.Healthy => DashboardGalaxyStatus.Healthy,
GalaxyCacheStatus.Stale => DashboardGalaxyStatus.Stale,
GalaxyCacheStatus.Unavailable => DashboardGalaxyStatus.Unavailable,
_ => DashboardGalaxyStatus.Unknown,
};
private static string ResolveCategoryName(int categoryId) => categoryId switch
{
1 => "WinPlatform",
3 => "AppEngine",
4 => "InTouchViewApp",
10 => "UserDefined",
11 => "FieldReference",
13 => "Area",
17 => "DIObject",
24 => "DDESuiteLinkClient",
26 => "OPCClient",
_ => $"Category {categoryId}",
};
}