/// <summary>
 /// Compares this <see cref="IgnoreReason"/> with <paramref name="other"/> for equality.
 /// </summary>
 /// <param name="other">the reason to compare to</param>
 /// <returns><see langword="true"/> if the two reasons compare equal, <see langword="false"/> otherwise</returns>
 public bool Equals(IgnoreReason other)
 => Reason == other.Reason && ReasonText == other.ReasonText &&
 Error == other.Error && RelatedTo == other.RelatedTo;
        internal static void Resolve()
        { // resolves duplicates and conflicts, etc
            PluginsMetadata.Sort((a, b) => b.Version.CompareTo(a.Version));

            var ids      = new HashSet <string>();
            var ignore   = new Dictionary <PluginMetadata, IgnoreReason>();
            var resolved = new List <PluginMetadata>(PluginsMetadata.Count);

            foreach (var meta in PluginsMetadata)
            {
                if (meta.Id != null)
                {
                    if (ids.Contains(meta.Id))
                    {
                        Logger.loader.Warn($"Found duplicates of {meta.Id}, using newest");
                        var ireason = new IgnoreReason(Reason.Duplicate)
                        {
                            ReasonText = $"Duplicate entry of same ID ({meta.Id})",
                            RelatedTo  = resolved.First(p => p.Id == meta.Id)
                        };
                        ignore.Add(meta, ireason);
                        ignoredPlugins.Add(meta, ireason);
                        continue; // because of sorted order, hightest order will always be the first one
                    }

                    bool processedLater = false;
                    foreach (var meta2 in PluginsMetadata)
                    {
                        if (ignore.ContainsKey(meta2))
                        {
                            continue;
                        }
                        if (meta == meta2)
                        {
                            processedLater = true;
                            continue;
                        }

                        if (!meta2.Manifest.Conflicts.ContainsKey(meta.Id))
                        {
                            continue;
                        }

                        var range = meta2.Manifest.Conflicts[meta.Id];
                        if (!range.IsSatisfied(meta.Version))
                        {
                            continue;
                        }

                        Logger.loader.Warn($"{meta.Id}@{meta.Version} conflicts with {meta2.Id}");

                        if (processedLater)
                        {
                            Logger.loader.Warn($"Ignoring {meta2.Name}");
                            ignore.Add(meta2, new IgnoreReason(Reason.Conflict)
                            {
                                ReasonText = $"{meta.Id}@{meta.Version} conflicts with {meta2.Id}",
                                RelatedTo  = meta
                            });
                        }
                        else
                        {
                            Logger.loader.Warn($"Ignoring {meta.Name}");
                            ignore.Add(meta, new IgnoreReason(Reason.Conflict)
                            {
                                ReasonText = $"{meta2.Id}@{meta2.Version} conflicts with {meta.Id}",
                                RelatedTo  = meta2
                            });
                            break;
                        }
                    }
                }

                if (ignore.TryGetValue(meta, out var reason))
                {
                    ignoredPlugins.Add(meta, reason);
                    continue;
                }
                if (meta.Id != null)
                {
                    ids.Add(meta.Id);
                }

                resolved.Add(meta);
            }

            PluginsMetadata = resolved;
        }