using System;
using System.Collections.Generic;
using System.Linq;
namespace StardewModdingAPI.Framework
{
    /// Manages deprecation warnings.
    internal class DeprecationManager
    {
        /*********
        ** Fields
        *********/
        /// The deprecations which have already been logged (as 'mod name::noun phrase::version').
        private readonly HashSet LoggedDeprecations = new(StringComparer.OrdinalIgnoreCase);
        /// Encapsulates monitoring and logging for a given module.
        private readonly IMonitor Monitor;
        /// Tracks the installed mods.
        private readonly ModRegistry ModRegistry;
        /// The queued deprecation warnings to display.
        private readonly IList QueuedWarnings = new List();
        /*********
        ** Public methods
        *********/
        /// Construct an instance.
        /// Encapsulates monitoring and logging for a given module.
        /// Tracks the installed mods.
        public DeprecationManager(IMonitor monitor, ModRegistry modRegistry)
        {
            this.Monitor = monitor;
            this.ModRegistry = modRegistry;
        }
        /// Get the source name for a mod from its unique ID.
        public string? GetSourceNameFromStack()
        {
            return this.ModRegistry.GetFromStack()?.DisplayName;
        }
        /// Get the source name for a mod from its unique ID.
        /// The mod's unique ID.
        public string? GetSourceName(string modId)
        {
            return this.ModRegistry.Get(modId)?.DisplayName;
        }
        /// Log a deprecation warning.
        /// The friendly mod name which used the deprecated code.
        /// A noun phrase describing what is deprecated.
        /// The SMAPI version which deprecated it.
        /// How deprecated the code is.
        public void Warn(string? source, string nounPhrase, string version, DeprecationLevel severity)
        {
            source ??= this.GetSourceNameFromStack() ?? "";
            // ignore if already warned
            if (!this.MarkWarned(source, nounPhrase, version))
                return;
            // queue warning
            this.QueuedWarnings.Add(new DeprecationWarning(source, nounPhrase, version, severity, Environment.StackTrace));
        }
        /// A placeholder method used to track deprecated code for which a separate warning will be shown.
        /// The SMAPI version which deprecated it.
        /// How deprecated the code is.
        public void PlaceholderWarn(string version, DeprecationLevel severity) { }
        /// Print any queued messages.
        public void PrintQueued()
        {
            foreach (DeprecationWarning warning in this.QueuedWarnings.OrderBy(p => p.ModName).ThenBy(p => p.NounPhrase))
            {
                // build message
                string message = $"{warning.ModName} uses deprecated code ({warning.NounPhrase} is deprecated since SMAPI {warning.Version}).";
                // get log level
                LogLevel level;
                switch (warning.Level)
                {
                    case DeprecationLevel.Notice:
                        level = LogLevel.Trace;
                        break;
                    case DeprecationLevel.Info:
                        level = LogLevel.Debug;
                        break;
                    case DeprecationLevel.PendingRemoval:
                        level = LogLevel.Warn;
                        break;
                    default:
                        throw new NotSupportedException($"Unknown deprecation level '{warning.Level}'.");
                }
                // log message
                if (level == LogLevel.Trace)
                    this.Monitor.Log($"{message}\n{warning.StackTrace}", level);
                else
                {
                    this.Monitor.Log(message, level);
                    this.Monitor.Log(warning.StackTrace, LogLevel.Debug);
                }
            }
            this.QueuedWarnings.Clear();
        }
        /*********
        ** Private methods
        *********/
        /// Mark a deprecation warning as already logged.
        /// The friendly name of the assembly which used the deprecated code.
        /// A noun phrase describing what is deprecated (e.g. "the Extensions.AsInt32 method").
        /// The SMAPI version which deprecated it.
        /// Returns whether the deprecation was successfully marked as warned. Returns false if it was already marked.
        private bool MarkWarned(string source, string nounPhrase, string version)
        {
            if (string.IsNullOrWhiteSpace(source))
                throw new InvalidOperationException("The deprecation source cannot be empty.");
            string key = $"{source}::{nounPhrase}::{version}";
            if (this.LoggedDeprecations.Contains(key))
                return false;
            this.LoggedDeprecations.Add(key);
            return true;
        }
    }
}