//Copyright (c) 2019 Alessandro Ghidini.All rights reserved.
//Copyright (c) 2020 Jonny Larson and Meinrad Recheis

using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Routing;

namespace Connected.Components;

/// <inheritdoc />
public class SnackbarService : ISnackbar, IDisposable
{
   public SnackbarConfiguration Configuration { get; }
   public event Action OnSnackbarsUpdated;

   private NavigationManager _navigationManager;
   private ReaderWriterLockSlim SnackBarLock { get; }
   private IList<Snackbar> SnackBarList { get; }

   public SnackbarService(NavigationManager navigationManager, SnackbarConfiguration configuration = null)
   {
      _navigationManager = navigationManager;
      configuration ??= new SnackbarConfiguration();

      Configuration = configuration;
      Configuration.OnUpdate += ConfigurationUpdated;
      navigationManager.LocationChanged += NavigationManager_LocationChanged;

      SnackBarLock = new ReaderWriterLockSlim();
      SnackBarList = new List<Snackbar>();
   }

   public IEnumerable<Snackbar> ShownSnackbars
   {
      get
      {
         SnackBarLock.EnterReadLock();
         try
         {
            return SnackBarList.Take(Configuration.MaxDisplayedSnackbars);
         }
         finally
         {
            SnackBarLock.ExitReadLock();
         }
      }
   }

   private Snackbar Add(SnackbarMessage message, Severity severity = Severity.Normal, Action<SnackbarOptions> configure = null)
   {
      var options = new SnackbarOptions(severity, Configuration);
      configure?.Invoke(options);

      var snackbar = new Snackbar(message, options);

      SnackBarLock.EnterWriteLock();
      try
      {
         if (ResolvePreventDuplicates(options) && SnackbarAlreadyPresent(snackbar)) return null;
         snackbar.OnClose += Remove;
         SnackBarList.Add(snackbar);
      }
      finally
      {
         SnackBarLock.ExitWriteLock();
      }

      OnSnackbarsUpdated?.Invoke();

      return snackbar;
   }

   /// <summary>
   /// Displays a snackbar containing a custom component specified by T.
   /// </summary>
   /// <typeparam name="T">The type of the custom component that specifies the content of the snackbar.</typeparam>
   /// <param name="componentParameters">Any additional parameters needed by the custom component to display the message.</param>
   /// <param name="severity">The severity of the snackbar. Dictates the color and default icon of the notification.</param>
   /// <param name="configure">Additional configuration for the snackbar.</param>
   /// <param name="key">If a key is provided, this message will not be shown while any other message with the same key is being shown.</param>
   /// <returns>The snackbar created by the parameters.</returns>
   public Snackbar Add<T>(Dictionary<string, object> componentParameters = null, Severity severity = Severity.Normal, Action<SnackbarOptions> configure = null, string key = "") where T : IComponent
   {
      var type = typeof(T);
      var message = new SnackbarMessage(type, componentParameters, key);

      return Add(message, severity, configure);
   }

   /// <summary>
   /// Displays a snackbar containing the RenderFragment.
   /// </summary>
   /// <param name="message">The RenderFragment which specifies the content of the snackbar.</param>
   /// <param name="severity">The severity of the snackbar. Dictates the color and default icon of the notification.</param>
   /// <param name="configure">Additional configuration for the snackbar.</param>
   /// <param name="key">If a key is provided, this message will not be shown while any other message with the same key is being shown.</param>
   /// <returns>The snackbar created by the parameters.</returns>
   public Snackbar Add(RenderFragment message, Severity severity = Severity.Normal, Action<SnackbarOptions> configure = null, string key = "")
   {
      if (message == null) return null;

      var componentParams = new Dictionary<string, object>()
         {
             { "Message", message as object }
         };

      return Add
      (
          new SnackbarMessage(typeof(SnackbarMessageRenderFragment), componentParams, key),
          severity,
          configure
      );
   }

   /// <summary>
   /// Displays a snackbar containing the text.
   /// </summary>
   /// <param name="message">The string which specifies the content of the snackbar.</param>
   /// <param name="severity">The severity of the snackbar. Dictates the color and default icon of the notification.</param>
   /// <param name="configure">Additional configuration for the snackbar.</param>
   /// <param name="key">If no key is passed, defaults to the content of the message. This message will not be shown while any other message with the same key is being shown.</param>
   /// <returns>The snackbar created by the parameters.</returns>
   public Snackbar Add(string message, Severity severity = Severity.Normal, Action<SnackbarOptions> configure = null, string key = "")
   {
      if (message.IsEmpty()) return null;
      message = message.Trimmed();

      var componentParams = new Dictionary<string, object>() { { "Message", new MarkupString(message) } };

      return Add
      (
          new SnackbarMessage(typeof(SnackbarMessageText), componentParams, string.IsNullOrEmpty(key) ? message : key) { Text = message },
          severity,
          configure
      );
   }

   [Obsolete("Use Add instead.", true)]
   [ExcludeFromCodeCoverage]
   public Snackbar AddNew(Severity severity, string message, Action<SnackbarOptions> configure)
   {
      return Add(message, severity, configure);
   }

   public void Clear()
   {
      SnackBarLock.EnterWriteLock();
      try
      {
         RemoveAllSnackbars(SnackBarList);
      }
      finally
      {
         SnackBarLock.ExitWriteLock();
      }

      OnSnackbarsUpdated?.Invoke();
   }

   public void Remove(Snackbar snackbar)
   {
      snackbar.OnClose -= Remove;
      snackbar.Dispose();

      SnackBarLock.EnterWriteLock();
      try
      {
         var index = SnackBarList.IndexOf(snackbar);
         if (index < 0) return;
         SnackBarList.RemoveAt(index);
      }
      finally
      {
         SnackBarLock.ExitWriteLock();
      }

      OnSnackbarsUpdated?.Invoke();
   }

   private bool ResolvePreventDuplicates(SnackbarOptions options)
   {
      return options.DuplicatesBehavior == SnackbarDuplicatesBehavior.Prevent
              || (options.DuplicatesBehavior == SnackbarDuplicatesBehavior.GlobalDefault && Configuration.PreventDuplicates);
   }

   private bool SnackbarAlreadyPresent(Snackbar newSnackbar)
   {
      return !string.IsNullOrEmpty(newSnackbar.SnackbarMessage.Key) && SnackBarList.Any(snackbar => newSnackbar.SnackbarMessage.Key == snackbar.SnackbarMessage.Key);
   }

   private void ConfigurationUpdated()
   {
      OnSnackbarsUpdated?.Invoke();
   }

   private void NavigationManager_LocationChanged(object sender, LocationChangedEventArgs e)
   {
      if (Configuration.ClearAfterNavigation)
      {
         Clear();
      }
      else
      {
         ShownSnackbars.Where(s => s.State.Options.CloseAfterNavigation).ToList().ForEach(s => Remove(s));
      }
   }

   public void Dispose()
   {
      Configuration.OnUpdate -= ConfigurationUpdated;
      _navigationManager.LocationChanged -= NavigationManager_LocationChanged;
      RemoveAllSnackbars(SnackBarList);
   }

   private void RemoveAllSnackbars(IEnumerable<Snackbar> snackbars)
   {
      if (SnackBarList.Count == 0) return;

      foreach (var snackbar in snackbars)
      {
         snackbar.OnClose -= Remove;
         snackbar.Dispose();
      }

      SnackBarList.Clear();
   }
}
