#nullable enable
using System.Collections.Generic;
using AdvChkSys.Interfaces;

namespace AdvChkSys.Dependencies
{
    /// <summary>
    /// Tracks dependencies between chunks.
    /// </summary>
    public class ChunkDependencyTracker
    {
        /// <summary>
        /// Defines the type of dependency between chunks.
        /// </summary>
        public enum DependencyType
        {
            /// <summary>
            /// Indicates a neighboring chunk relationship.
            /// </summary>
            Neighbor,

            /// <summary>
            /// Indicates a reference relationship between chunks.
            /// </summary>
            Reference,

            /// <summary>
            /// Indicates an update relationship between chunks.
            /// </summary>
            Update,

            /// <summary>
            /// Indicates a custom dependency relationship.
            /// </summary>
            Custom
        }

        // Dictionary to track dependencies: source chunk -> (target chunk, dependency type)
        private readonly Dictionary<IChunk, HashSet<(IChunk Target, DependencyType Type)>> _dependencies = new();

        // Dictionary to track dependents: target chunk -> (source chunk, dependency type)
        private readonly Dictionary<IChunk, HashSet<(IChunk Source, DependencyType Type)>> _dependents = new();

        // Lock object for thread safety
        private readonly object _lock = new();

        /// <summary>
        /// Registers a dependency between two chunks.
        /// </summary>
        public void RegisterDependency(IChunk source, IChunk target, DependencyType type)
        {
            if (source == null || target == null)
                return;

            lock (_lock)
            {
                // Add to dependencies
                if (!_dependencies.TryGetValue(source, out var targetSet))
                {
                    targetSet = new HashSet<(IChunk, DependencyType)>();
                    _dependencies[source] = targetSet;
                }
                targetSet.Add((target, type));

                // Add to dependents
                if (!_dependents.TryGetValue(target, out var sourceSet))
                {
                    sourceSet = new HashSet<(IChunk, DependencyType)>();
                    _dependents[target] = sourceSet;
                }
                sourceSet.Add((source, type));
            }
        }

        /// <summary>
        /// Removes a dependency between two chunks.
        /// </summary>
        public void RemoveDependency(IChunk source, IChunk target)
        {
            if (source == null || target == null)
                return;

            lock (_lock)
            {
                // Remove from dependencies
                if (_dependencies.TryGetValue(source, out var targetSet))
                {
                    targetSet.RemoveWhere(t => t.Target.Equals(target));
                    if (targetSet.Count == 0)
                    {
                        _dependencies.Remove(source);
                    }
                }

                // Remove from dependents
                if (_dependents.TryGetValue(target, out var sourceSet))
                {
                    sourceSet.RemoveWhere(s => s.Source.Equals(source));
                    if (sourceSet.Count == 0)
                    {
                        _dependents.Remove(target);
                    }
                }
            }
        }

        /// <summary>
        /// Gets all chunks that depend on the specified chunk.
        /// </summary>
        public IEnumerable<IChunk> GetDependents(IChunk chunk)
        {
            if (chunk == null)
                return new List<IChunk>();

            lock (_lock)
            {
                if (!_dependents.TryGetValue(chunk, out var sourceSet))
                {
                    return new List<IChunk>();
                }

                var result = new List<IChunk>(sourceSet.Count);
                foreach (var (source, _) in sourceSet)
                {
                    result.Add(source);
                }
                return result;
            }
        }

        /// <summary>
        /// Gets all chunks that the specified chunk depends on.
        /// </summary>
        public IEnumerable<IChunk> GetDependencies(IChunk chunk)
        {
            if (chunk == null)
                return new List<IChunk>();

            lock (_lock)
            {
                if (!_dependencies.TryGetValue(chunk, out var targetSet))
                {
                    return new List<IChunk>();
                }

                var result = new List<IChunk>(targetSet.Count);
                foreach (var (target, _) in targetSet)
                {
                    result.Add(target);
                }
                return result;
            }
        }

        /// <summary>
        /// Gets all chunks that depend on the specified chunk with their dependency types.
        /// </summary>
        public IEnumerable<(IChunk Chunk, DependencyType Type)> GetDependentsWithTypes(IChunk chunk)
        {
            if (chunk == null)
                return new List<(IChunk, DependencyType)>();

            lock (_lock)
            {
                if (!_dependents.TryGetValue(chunk, out var sourceDependents))
                {
                    return new List<(IChunk, DependencyType)>();
                }

                var result = new List<(IChunk, DependencyType)>(sourceDependents.Count);
                foreach (var (source, type) in sourceDependents)
                {
                    result.Add((source, type));
                }
                return result;
            }
        }

        /// <summary>
        /// Gets all chunks that the specified chunk depends on with their dependency types.
        /// </summary>
        public IEnumerable<(IChunk Chunk, DependencyType Type)> GetDependenciesWithTypes(IChunk chunk)
        {
            if (chunk == null)
                return new List<(IChunk, DependencyType)>();

            lock (_lock)
            {
                if (!_dependencies.TryGetValue(chunk, out var targetSet))
                {
                    return new List<(IChunk, DependencyType)>();
                }

                var result = new List<(IChunk, DependencyType)>(targetSet.Count);
                foreach (var (target, type) in targetSet)
                {
                    result.Add((target, type));
                }
                return result;
            }
        }

        /// <summary>
        /// Checks if a dependency exists between source and target chunks.
        /// </summary>
        public bool HasDependency(IChunk source, IChunk target)
        {
            if (source == null || target == null)
                return false;

            lock (_lock)
            {
                if (!_dependencies.TryGetValue(source, out var targetSet))
                {
                    return false;
                }

                foreach (var (t, _) in targetSet)
                {
                    if (t.Equals(target))
                    {
                        return true;
                    }
                }

                return false;
            }
        }

        /// <summary>
        /// Gets the dependency type between source and target chunks, or null if no dependency exists.
        /// </summary>
        public DependencyType? GetDependencyType(IChunk source, IChunk target)
        {
            if (source == null || target == null)
                return null;

            lock (_lock)
            {
                if (!_dependencies.TryGetValue(source, out var targetSet))
                {
                    return null;
                }

                foreach (var (t, type) in targetSet)
                {
                    if (t.Equals(target))
                    {
                        return type;
                    }
                }

                return null;
            }
        }

        /// <summary>
        /// Clears all dependencies for a specific chunk.
        /// </summary>
        public void ClearDependencies(IChunk chunk)
        {
            if (chunk == null)
                return;

            lock (_lock)
            {
                // Remove all dependencies where this chunk is the source
                if (_dependencies.TryGetValue(chunk, out var targetSet))
                {
                    foreach (var (target, _) in targetSet)
                    {
                        if (_dependents.TryGetValue(target, out var depSources))
                        {
                            depSources.RemoveWhere(s => s.Source.Equals(chunk));
                            if (depSources.Count == 0)
                            {
                                _dependents.Remove(target);
                            }
                        }
                    }
                    _dependencies.Remove(chunk);
                }

                // Remove all dependencies where this chunk is the target
                if (_dependents.TryGetValue(chunk, out var sourceSets))
                {
                    foreach (var (source, _) in sourceSets)
                    {
                        if (_dependencies.TryGetValue(source, out var depTargets))
                        {
                            depTargets.RemoveWhere(t => t.Target.Equals(chunk));
                            if (depTargets.Count == 0)
                            {
                                _dependencies.Remove(source);
                            }
                        }
                    }
                    _dependents.Remove(chunk);
                }
            }
        }

        /// <summary>
        /// Gets all chunks that have any dependencies.
        /// </summary>
        public IEnumerable<IChunk> GetAllChunksWithDependencies()
        {
            lock (_lock)
            {
                return new HashSet<IChunk>(_dependencies.Keys);
            }
        }

        /// <summary>
        /// Gets all chunks that have any dependents.
        /// </summary>
        public IEnumerable<IChunk> GetAllChunksWithDependents()
        {
            lock (_lock)
            {
                return new HashSet<IChunk>(_dependents.Keys);
            }
        }
    }
}