using ScadaLink.Commons.Entities.Templates;
namespace ScadaLink.TemplateEngine;
///
/// Detects naming collisions across composed module members using canonical (path-qualified) names.
/// Two members from different composed modules collide if they produce the same canonical name.
/// Members from different module instance names cannot collide because the prefix differentiates them.
///
public static class CollisionDetector
{
///
/// Represents a resolved member with its canonical name and origin.
///
public sealed record ResolvedMember(
string CanonicalName,
string MemberType, // "Attribute", "Alarm", "Script"
string OriginDescription);
///
/// Detects naming collisions among all members (direct + composed) of a template.
///
/// The template to check.
/// All templates in the system (for resolving composed templates).
/// List of collision descriptions. Empty if no collisions.
public static IReadOnlyList DetectCollisions(
Template template,
IReadOnlyList allTemplates)
{
// Duplicate-tolerant lookup (see CycleDetector.BuildLookup): a plain
// ToDictionary(t => t.Id) throws if two templates share an Id (e.g.
// not-yet-saved templates carrying Id 0).
var lookup = CycleDetector.BuildLookup(allTemplates);
var allMembers = new List();
// Collect direct (top-level) members
CollectDirectMembers(template, prefix: null, originPrefix: template.Name, allMembers);
// Collect members from composed modules recursively
foreach (var composition in template.Compositions)
{
if (lookup.TryGetValue(composition.ComposedTemplateId, out var composedTemplate))
{
CollectComposedMembers(
composedTemplate,
prefix: composition.InstanceName,
lookup,
allMembers,
visited: new HashSet());
}
}
// Collect inherited members (walk parent chain)
CollectInheritedMembers(template, lookup, allMembers, new HashSet { template.Id });
// Detect duplicates by canonical name
var collisions = new List();
var grouped = allMembers.GroupBy(m => m.CanonicalName, StringComparer.Ordinal);
foreach (var group in grouped)
{
var members = group.ToList();
if (members.Count > 1)
{
// Only report collision if members come from different origins
var distinctOrigins = members.Select(m => m.OriginDescription).Distinct().ToList();
if (distinctOrigins.Count > 1)
{
var origins = string.Join(", ", members.Select(m => $"{m.MemberType} from {m.OriginDescription}"));
collisions.Add($"Naming collision on '{group.Key}': {origins}.");
}
}
}
return collisions;
}
private static void CollectDirectMembers(
Template template,
string? prefix,
string originPrefix,
List members)
{
foreach (var attr in template.Attributes)
{
var canonicalName = prefix == null ? attr.Name : $"{prefix}.{attr.Name}";
members.Add(new ResolvedMember(canonicalName, "Attribute", originPrefix));
}
foreach (var alarm in template.Alarms)
{
var canonicalName = prefix == null ? alarm.Name : $"{prefix}.{alarm.Name}";
members.Add(new ResolvedMember(canonicalName, "Alarm", originPrefix));
}
foreach (var script in template.Scripts)
{
var canonicalName = prefix == null ? script.Name : $"{prefix}.{script.Name}";
members.Add(new ResolvedMember(canonicalName, "Script", originPrefix));
}
}
private static void CollectComposedMembers(
Template template,
string prefix,
Dictionary lookup,
List members,
HashSet visited)
{
if (!visited.Add(template.Id))
return;
// Add direct members of this composed template with the prefix
CollectDirectMembers(template, prefix, $"module '{prefix}'", members);
// Recurse into nested compositions
foreach (var composition in template.Compositions)
{
if (lookup.TryGetValue(composition.ComposedTemplateId, out var nested))
{
var nestedPrefix = $"{prefix}.{composition.InstanceName}";
CollectComposedMembers(nested, nestedPrefix, lookup, members, visited);
}
}
}
private static void CollectInheritedMembers(
Template template,
Dictionary lookup,
List members,
HashSet visited)
{
if (!template.ParentTemplateId.HasValue)
return;
if (!lookup.TryGetValue(template.ParentTemplateId.Value, out var parent))
return;
if (!visited.Add(parent.Id))
return;
// Inherited direct members (no prefix)
CollectDirectMembers(parent, prefix: null, $"parent '{parent.Name}'", members);
// Inherited composed modules
foreach (var composition in parent.Compositions)
{
if (lookup.TryGetValue(composition.ComposedTemplateId, out var composedTemplate))
{
CollectComposedMembers(
composedTemplate,
composition.InstanceName,
lookup,
members,
new HashSet());
}
}
// Continue up the inheritance chain
CollectInheritedMembers(parent, lookup, members, visited);
}
}