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

namespace Unity.VisualScripting
{
    public static class GraphInstances
    {
        private static readonly object @lock = new object();

        private static readonly Dictionary<IGraph, HashSet<GraphReference>> byGraph = new Dictionary<IGraph, HashSet<GraphReference>>();

        private static readonly Dictionary<IGraphParent, HashSet<GraphReference>> byParent = new Dictionary<IGraphParent, HashSet<GraphReference>>();

        public static void Instantiate(GraphReference instance)
        {
            lock (@lock)
            {
                Ensure.That(nameof(instance)).IsNotNull(instance);

                instance.CreateGraphData();

                instance.graph.Instantiate(instance);

                if (!byGraph.TryGetValue(instance.graph, out var instancesWithGraph))
                {
                    instancesWithGraph = new HashSet<GraphReference>();
                    byGraph.Add(instance.graph, instancesWithGraph);
                }

                if (instancesWithGraph.Add(instance))
                {
                    // Debug.Log($"Added graph instance mapping:\n{instance.graph} => {instance}");
                }
                else
                {
                    Debug.LogWarning($"Attempting to add duplicate graph instance mapping:\n{instance.graph} => {instance}");
                }

                if (!byParent.TryGetValue(instance.parent, out var instancesWithParent))
                {
                    instancesWithParent = new HashSet<GraphReference>();
                    byParent.Add(instance.parent, instancesWithParent);
                }

                if (instancesWithParent.Add(instance))
                {
                    // Debug.Log($"Added parent instance mapping:\n{instance.parent.ToSafeString()} => {instance}");
                }
                else
                {
                    Debug.LogWarning($"Attempting to add duplicate parent instance mapping:\n{instance.parent.ToSafeString()} => {instance}");
                }
            }
        }

        public static void Uninstantiate(GraphReference instance)
        {
            lock (@lock)
            {
                instance.graph.Uninstantiate(instance);

                if (!byGraph.TryGetValue(instance.graph, out var instancesWithGraph))
                {
                    throw new InvalidOperationException("Graph instance not found via graph.");
                }

                if (instancesWithGraph.Remove(instance))
                {
                    // Debug.Log($"Removed graph instance mapping:\n{instance.graph} => {instance}");

                    // Free the key references for GC collection
                    if (instancesWithGraph.Count == 0)
                    {
                        byGraph.Remove(instance.graph);
                    }
                }
                else
                {
                    Debug.LogWarning($"Could not find graph instance mapping to remove:\n{instance.graph} => {instance}");
                }

                if (!byParent.TryGetValue(instance.parent, out var instancesWithParent))
                {
                    throw new InvalidOperationException("Graph instance not found via parent.");
                }

                if (instancesWithParent.Remove(instance))
                {
                    // Debug.Log($"Removed parent instance mapping:\n{instance.parent.ToSafeString()} => {instance}");

                    // Free the key references for GC collection
                    if (instancesWithParent.Count == 0)
                    {
                        byParent.Remove(instance.parent);
                    }
                }
                else
                {
                    Debug.LogWarning($"Could not find parent instance mapping to remove:\n{instance.parent.ToSafeString()} => {instance}");
                }

                // It's important to only free the graph data after
                // dissociating the instance mapping, because the data
                // is used as part of the equality comparison for pointers
                instance.FreeGraphData();
            }
        }

        public static HashSet<GraphReference> OfPooled(IGraph graph)
        {
            Ensure.That(nameof(graph)).IsNotNull(graph);

            lock (@lock)
            {
                if (byGraph.TryGetValue(graph, out var instances))
                {
                    // Debug.Log($"Found {instances.Count} instances of {graph}\n{instances.ToLineSeparatedString()}");

                    return instances.ToHashSetPooled();
                }
                else
                {
                    // Debug.Log($"Found no instances of {graph}.\n");

                    return HashSetPool<GraphReference>.New();
                }
            }
        }

        public static HashSet<GraphReference> ChildrenOfPooled(IGraphParent parent)
        {
            Ensure.That(nameof(parent)).IsNotNull(parent);

            lock (@lock)
            {
                if (byParent.TryGetValue(parent, out var instances))
                {
                    // Debug.Log($"Found {instances.Count} instances of {parent.ToSafeString()}\n{instances.ToLineSeparatedString()}");

                    return instances.ToHashSetPooled();
                }
                else
                {
                    // Debug.Log($"Found no instances of {parent.ToSafeString()}.\n");

                    return HashSetPool<GraphReference>.New();
                }
            }
        }
    }
}