using ScadaLink.Commons.Entities.Templates;

namespace ScadaLink.TemplateEngine;

/// <summary>
/// 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.
/// </summary>
public static class CollisionDetector
{
    /// <summary>
    /// Represents a resolved member with its canonical name and origin.
    /// </summary>
    public sealed record ResolvedMember(
        string CanonicalName,
        string MemberType, // "Attribute", "Alarm", "Script"
        string OriginDescription);

    /// <summary>
    /// Detects naming collisions among all members (direct + composed) of a template.
    /// </summary>
    /// <param name="template">The template to check.</param>
    /// <param name="allTemplates">All templates in the system (for resolving composed templates).</param>
    /// <returns>List of collision descriptions. Empty if no collisions.</returns>
    public static IReadOnlyList<string> DetectCollisions(
        Template template,
        IReadOnlyList<Template> allTemplates)
    {
        var lookup = allTemplates.ToDictionary(t => t.Id);
        var allMembers = new List<ResolvedMember>();

        // 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<int>());
            }
        }

        // Collect inherited members (walk parent chain)
        CollectInheritedMembers(template, lookup, allMembers, new HashSet<int> { template.Id });

        // Detect duplicates by canonical name
        var collisions = new List<string>();
        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<ResolvedMember> 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<int, Template> lookup,
        List<ResolvedMember> members,
        HashSet<int> 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<int, Template> lookup,
        List<ResolvedMember> members,
        HashSet<int> 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<int>());
            }
        }

        // Continue up the inheritance chain
        CollectInheritedMembers(parent, lookup, members, visited);
    }
}
