using ScadaLink.Commons.Entities.Templates;

namespace ScadaLink.TemplateEngine;

/// <summary>
/// Walks inheritance and composition chains to resolve effective template members.
/// Produces canonical (path-qualified) names for composed module members.
/// </summary>
public static class TemplateResolver
{
    /// <summary>
    /// Represents a resolved member from any point in the inheritance/composition hierarchy.
    /// </summary>
    public sealed record ResolvedTemplateMember
    {
        public string CanonicalName { get; init; }
        public string MemberType { get; init; } // "Attribute", "Alarm", "Script"
        public int SourceTemplateId { get; init; }
        public int MemberId { get; init; }
        public bool IsLocked { get; init; }
        public string? ModulePath { get; init; }

        public ResolvedTemplateMember(string canonicalName, string memberType, int sourceTemplateId, int memberId, bool isLocked, string? modulePath = null)
        {
            CanonicalName = canonicalName;
            MemberType = memberType;
            SourceTemplateId = sourceTemplateId;
            MemberId = memberId;
            IsLocked = isLocked;
            ModulePath = modulePath;
        }
    }

    /// <summary>
    /// Resolves all effective members for a template, walking inheritance and composition chains.
    /// Child members override parent members of the same canonical name (unless locked).
    /// </summary>
    public static IReadOnlyList<ResolvedTemplateMember> ResolveAllMembers(
        int templateId,
        IReadOnlyList<Template> allTemplates)
    {
        var lookup = allTemplates.ToDictionary(t => t.Id);
        if (!lookup.TryGetValue(templateId, out var template))
            return Array.Empty<ResolvedTemplateMember>();

        // Build inheritance chain from root to leaf (root first, child last)
        var chain = BuildInheritanceChain(templateId, lookup);

        // Start with root members, apply overrides from each child
        var effectiveMembers = new Dictionary<string, ResolvedTemplateMember>(StringComparer.Ordinal);

        foreach (var tmpl in chain)
        {
            // Direct members
            AddDirectMembers(tmpl, prefix: null, effectiveMembers);

            // Composed module members
            foreach (var composition in tmpl.Compositions)
            {
                if (lookup.TryGetValue(composition.ComposedTemplateId, out var composedTemplate))
                {
                    AddComposedMembers(composedTemplate, composition.InstanceName, lookup, effectiveMembers, new HashSet<int>());
                }
            }
        }

        return effectiveMembers.Values.ToList();
    }

    /// <summary>
    /// Gets the inheritance chain from root ancestor to the specified template.
    /// </summary>
    public static IReadOnlyList<Template> BuildInheritanceChain(
        int templateId,
        IReadOnlyDictionary<int, Template> lookup)
    {
        var chain = new List<Template>();
        var currentId = templateId;
        var visited = new HashSet<int>();

        while (currentId != 0 && lookup.TryGetValue(currentId, out var current))
        {
            if (!visited.Add(currentId))
                break; // Safety: cycle detected

            chain.Add(current);
            currentId = current.ParentTemplateId ?? 0;
        }

        chain.Reverse(); // Root first
        return chain;
    }

    /// <summary>
    /// Finds a member by canonical name in the resolved member set.
    /// Used to check override/lock constraints.
    /// </summary>
    public static ResolvedTemplateMember? FindMemberByCanonicalName(
        string canonicalName,
        int parentTemplateId,
        IReadOnlyList<Template> allTemplates)
    {
        var members = ResolveAllMembers(parentTemplateId, allTemplates);
        return members.FirstOrDefault(m => m.CanonicalName == canonicalName);
    }

    private static void AddDirectMembers(
        Template template,
        string? prefix,
        Dictionary<string, ResolvedTemplateMember> effectiveMembers)
    {
        foreach (var attr in template.Attributes)
        {
            var canonicalName = prefix == null ? attr.Name : $"{prefix}.{attr.Name}";
            effectiveMembers[canonicalName] = new ResolvedTemplateMember(
                canonicalName, "Attribute", template.Id, attr.Id, attr.IsLocked, prefix);
        }

        foreach (var alarm in template.Alarms)
        {
            var canonicalName = prefix == null ? alarm.Name : $"{prefix}.{alarm.Name}";
            effectiveMembers[canonicalName] = new ResolvedTemplateMember(
                canonicalName, "Alarm", template.Id, alarm.Id, alarm.IsLocked, prefix);
        }

        foreach (var script in template.Scripts)
        {
            var canonicalName = prefix == null ? script.Name : $"{prefix}.{script.Name}";
            effectiveMembers[canonicalName] = new ResolvedTemplateMember(
                canonicalName, "Script", template.Id, script.Id, script.IsLocked, prefix);
        }
    }

    private static void AddComposedMembers(
        Template template,
        string prefix,
        Dictionary<int, Template> lookup,
        Dictionary<string, ResolvedTemplateMember> effectiveMembers,
        HashSet<int> visited)
    {
        if (!visited.Add(template.Id))
            return;

        AddDirectMembers(template, prefix, effectiveMembers);

        foreach (var composition in template.Compositions)
        {
            if (lookup.TryGetValue(composition.ComposedTemplateId, out var nested))
            {
                var nestedPrefix = $"{prefix}.{composition.InstanceName}";
                AddComposedMembers(nested, nestedPrefix, lookup, effectiveMembers, visited);
            }
        }
    }
}
