using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace AIRuntime.Editor
{
    /// <summary>Deterministic spring/repulsion layout in two dimensions.</summary>
    internal static class AIRuntimeKanbanForceLayout
    {
        internal static Dictionary<string, Vector2> Calculate(IReadOnlyList<AIRuntimeKanbanGraphNode> input)
        {
            var nodes = (input ?? Array.Empty<AIRuntimeKanbanGraphNode>())
                .Where(n => n != null && !string.IsNullOrWhiteSpace(n.Id))
                .GroupBy(n => n.Id, StringComparer.Ordinal).Select(g => g.First())
                .OrderBy(n => n.Id, StringComparer.Ordinal).ToArray();
            int count = nodes.Length;
            var result = new Dictionary<string, Vector2>(StringComparer.Ordinal);
            if (count == 0) return result;
            var indices = nodes.Select((n, i) => new { n.Id, Index = i }).ToDictionary(n => n.Id, n => n.Index);
            var links = new HashSet<(int, int)>();
            for (int i = 0; i < count; i++)
            {
                var n = nodes[i];
                var neighbours = (n.DependsOnTaskIds ?? new List<string>())
                    .Concat(n.AssociatedTaskIds ?? new List<string>()).Concat(new[] { n.ParentTaskId, n.BlockedByTaskId });
                foreach (string id in neighbours)
                    if (id != null && indices.TryGetValue(id, out int j) && i != j)
                        links.Add((Math.Min(i, j), Math.Max(i, j)));
            }
            var edges = links.OrderBy(e => e.Item1).ThenBy(e => e.Item2).ToArray();
            var positions = new Vector2[count];
            var forces = new Vector2[count];
            // A spiral avoids collinear and symmetric starting states, including
            // chains/cycles. Stable ID order makes repeated layouts reproducible.
            for (int i = 0; i < count; i++)
            {
                float angle = i * 2.39996323f;
                positions[i] = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * (80f * Mathf.Sqrt(i + 1));
            }
            int iterations = Math.Max(12, Math.Min(260, (int)(4000000L / ((long)count * count))));
            const float length = 150f;
            for (int step = 0; step < iterations; step++)
            {
                for (int i = 0; i < count; i++) forces[i] = -positions[i] * .08f;
                for (int i = 0; i < count; i++)
                    for (int j = i + 1; j < count; j++)
                    {
                        var delta = positions[i] - positions[j];
                        float distance = Mathf.Max(delta.magnitude, 1f);
                        var push = delta / distance * (length * length / distance);
                        forces[i] += push;
                        forces[j] -= push;
                    }
                foreach (var edge in edges)
                {
                    var delta = positions[edge.Item2] - positions[edge.Item1];
                    var pull = delta * (delta.magnitude / length * .65f);
                    forces[edge.Item1] += pull;
                    forces[edge.Item2] -= pull;
                }
                float temperature = Mathf.Lerp(24f, .15f, step / (float)Math.Max(1, iterations - 1));
                for (int i = 0; i < count; i++) positions[i] += Vector2.ClampMagnitude(forces[i], temperature);
            }
            // Make room for labels, then separate their rectangular footprints.
            for (int i = 0; i < count; i++) positions[i].x *= 1.45f;
            for (int pass = 0; pass < 80; pass++)
            {
                bool overlap = false;
                for (int i = 0; i < count; i++)
                    for (int j = i + 1; j < count; j++)
                    {
                        var delta = positions[j] - positions[i];
                        float x = 204f - Mathf.Abs(delta.x), y = 100f - Mathf.Abs(delta.y);
                        if (x <= 0 || y <= 0) continue;
                        overlap = true;
                        var correction = x < y ? new Vector2((delta.x >= 0 ? 1 : -1) * (x + .1f) * .5f, 0) :
                            new Vector2(0, (delta.y >= 0 ? 1 : -1) * (y + .1f) * .5f);
                        positions[i] -= correction;
                        positions[j] += correction;
                    }
                if (!overlap) break;
            }
            var center = new Vector2(positions.Average(p => p.x), positions.Average(p => p.y));
            for (int i = 0; i < count; i++) result[nodes[i].Id] = positions[i] - center;
            return result;
        }
    }
}
