using ScadaLink.Commons.Entities.Templates;

namespace ScadaLink.TemplateEngine;

/// <summary>
/// Detects cycles in template inheritance and composition graphs.
/// Covers: self-inheritance, circular inheritance chains, self-composition,
/// circular composition chains, and cross-graph (inheritance + composition) cycles.
/// </summary>
public static class CycleDetector
{
    /// <summary>
    /// Checks whether setting <paramref name="parentId"/> as the parent of template
    /// <paramref name="templateId"/> would introduce an inheritance cycle.
    /// </summary>
    /// <returns>A description of the cycle if one would be created, or null if safe.</returns>
    public static string? DetectInheritanceCycle(
        int templateId,
        int parentId,
        IReadOnlyList<Template> allTemplates)
    {
        if (templateId == parentId)
        {
            var selfName = allTemplates.FirstOrDefault(t => t.Id == templateId)?.Name ?? templateId.ToString();
            return $"Template '{selfName}' cannot inherit from itself.";
        }

        // Walk the inheritance chain from the proposed parent upward.
        // If we arrive back at templateId, there is a cycle.
        var lookup = allTemplates.ToDictionary(t => t.Id);
        var visited = new HashSet<int> { templateId };
        var chain = new List<string>();

        var templateName = lookup.TryGetValue(templateId, out var tmpl) ? tmpl.Name : templateId.ToString();
        chain.Add(templateName);

        var currentId = parentId;
        while (currentId != 0)
        {
            if (!lookup.TryGetValue(currentId, out var current))
                break;

            chain.Add(current.Name);

            if (visited.Contains(currentId))
            {
                return $"Inheritance cycle detected: {string.Join(" -> ", chain)}.";
            }

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

        return null;
    }

    /// <summary>
    /// Checks whether adding a composition of <paramref name="composedTemplateId"/> into
    /// <paramref name="templateId"/> would introduce a composition cycle.
    /// </summary>
    /// <returns>A description of the cycle if one would be created, or null if safe.</returns>
    public static string? DetectCompositionCycle(
        int templateId,
        int composedTemplateId,
        IReadOnlyList<Template> allTemplates)
    {
        if (templateId == composedTemplateId)
        {
            var selfName = allTemplates.FirstOrDefault(t => t.Id == templateId)?.Name ?? templateId.ToString();
            return $"Template '{selfName}' cannot compose itself.";
        }

        var lookup = allTemplates.ToDictionary(t => t.Id);

        // BFS/DFS from composedTemplateId through all its compositions.
        // If we reach templateId, that's a cycle.
        var visited = new HashSet<int>();
        var queue = new Queue<int>();
        queue.Enqueue(composedTemplateId);

        while (queue.Count > 0)
        {
            var currentId = queue.Dequeue();
            if (currentId == templateId)
            {
                var tmplName = lookup.TryGetValue(templateId, out var t1) ? t1.Name : templateId.ToString();
                var composedName = lookup.TryGetValue(composedTemplateId, out var t2) ? t2.Name : composedTemplateId.ToString();
                return $"Composition cycle detected: '{tmplName}' -> '{composedName}' -> ... -> '{tmplName}'.";
            }

            if (!visited.Add(currentId))
                continue;

            if (!lookup.TryGetValue(currentId, out var current))
                continue;

            foreach (var comp in current.Compositions)
            {
                queue.Enqueue(comp.ComposedTemplateId);
            }
        }

        return null;
    }

    /// <summary>
    /// Detects cross-graph cycles that span both inheritance and composition edges.
    /// A cross-graph cycle exists when following any combination of inheritance (parent)
    /// and composition edges from a template leads back to itself.
    /// </summary>
    /// <returns>A description of the cycle if found, or null if safe.</returns>
    public static string? DetectCrossGraphCycle(
        int templateId,
        int? proposedParentId,
        int? proposedComposedTemplateId,
        IReadOnlyList<Template> allTemplates)
    {
        var lookup = allTemplates.ToDictionary(t => t.Id);

        // Build adjacency: for each template, collect all reachable templates
        // via inheritance (parent) and composition edges.
        // We temporarily add the proposed edge and check for reachability back to templateId.

        var visited = new HashSet<int>();
        var queue = new Queue<int>();

        // Seed with proposed targets
        if (proposedParentId.HasValue && proposedParentId.Value != 0)
            queue.Enqueue(proposedParentId.Value);

        if (proposedComposedTemplateId.HasValue && proposedComposedTemplateId.Value != 0)
            queue.Enqueue(proposedComposedTemplateId.Value);

        while (queue.Count > 0)
        {
            var currentId = queue.Dequeue();
            if (currentId == templateId)
            {
                var tmplName = lookup.TryGetValue(templateId, out var t) ? t.Name : templateId.ToString();
                return $"Cross-graph cycle detected involving template '{tmplName}'.";
            }

            if (!visited.Add(currentId))
                continue;

            if (!lookup.TryGetValue(currentId, out var current))
                continue;

            // Follow inheritance edge
            if (current.ParentTemplateId.HasValue && current.ParentTemplateId.Value != 0)
                queue.Enqueue(current.ParentTemplateId.Value);

            // Follow composition edges
            foreach (var comp in current.Compositions)
            {
                queue.Enqueue(comp.ComposedTemplateId);
            }
        }

        return null;
    }
}
