// Copyright (c) 2019 Blazored (https://github.com/Blazored)
// Copyright (c) 2020 Jonny Larsson (https://github.com/MudBlazor/MudBlazor)
// Copyright (c) 2021 improvements by Meinrad Recheis
// See https://github.com/Blazored
// License: MIT

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

namespace Connected
{
   public class DialogService : IDialogService
   {
      /// <summary>
      /// This internal wrapper components prevents overwriting parameters of once
      /// instanciated dialog instances
      /// </summary>
      private class DialogHelperComponent : IComponent
      {
         const string ChildContent = nameof(ChildContent);
         RenderFragment _renderFragment;
         RenderHandle _renderHandle;
         void IComponent.Attach(RenderHandle renderHandle) => _renderHandle = renderHandle;
         Task IComponent.SetParametersAsync(ParameterView parameters)
         {
            if (_renderFragment == null)
            {
               if (parameters.TryGetValue(ChildContent, out _renderFragment))
               {
                  _renderHandle.Render(_renderFragment);
               }
            }
            return Task.CompletedTask;
         }
         public static RenderFragment Wrap(RenderFragment renderFragment)
            => new RenderFragment(builder =>
            {
               builder.OpenComponent<DialogHelperComponent>(1);
               builder.AddAttribute(2, ChildContent, renderFragment);
               builder.CloseComponent();
            });
      }

      public event Action<IDialogReference> OnDialogInstanceAdded;
      public event Action<IDialogReference, DialogResult> OnDialogCloseRequested;

      public IDialogReference Show<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>() where T : ComponentBase
      {
         return Show<T>(string.Empty, new DialogParameters(), new DialogOptions());
      }

      public IDialogReference Show<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(string title) where T : ComponentBase
      {
         return Show<T>(title, new DialogParameters(), new DialogOptions());
      }

      public IDialogReference Show<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(string title, DialogOptions options) where T : ComponentBase
      {
         return Show<T>(title, new DialogParameters(), options);
      }

      public IDialogReference Show<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(string title, DialogParameters parameters) where T : ComponentBase
      {
         return Show<T>(title, parameters, new DialogOptions());
      }

      public IDialogReference Show<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(string title, DialogParameters parameters, DialogOptions options) where T : ComponentBase
      {
         return Show(typeof(T), title, parameters, options);
      }

      public IDialogReference Show([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type contentComponent)
      {
         return Show(contentComponent, string.Empty, new DialogParameters(), new DialogOptions());
      }

      public IDialogReference Show([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type contentComponent, string title)
      {
         return Show(contentComponent, title, new DialogParameters(), new DialogOptions());
      }

      public IDialogReference Show([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type contentComponent, string title, DialogOptions options)
      {
         return Show(contentComponent, title, new DialogParameters(), options);
      }

      public IDialogReference Show([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type contentComponent, string title, DialogParameters parameters)
      {
         return Show(contentComponent, title, parameters, new DialogOptions());
      }

      public IDialogReference Show([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type contentComponent, string title, DialogParameters parameters, DialogOptions options)
      {
         if (!typeof(ComponentBase).IsAssignableFrom(contentComponent))
         {
            throw new ArgumentException($"{contentComponent?.FullName} must be a Blazor Component");
         }
         var dialogReference = CreateReference();

         var dialogContent = DialogHelperComponent.Wrap(new RenderFragment(builder =>
         {
            var i = 0;
            builder.OpenComponent(i++, contentComponent);
            foreach (var parameter in parameters)
            {
               builder.AddAttribute(i++, parameter.Key, parameter.Value);
            }
            builder.AddComponentReferenceCapture(i++, inst => { dialogReference.InjectDialog(inst); });
            builder.CloseComponent();
         }));
         var dialogInstance = new RenderFragment(builder =>
         {
            builder.OpenComponent<DialogInstance>(0);
            builder.SetKey(dialogReference.Id);
            builder.AddAttribute(1, "Options", options);
            builder.AddAttribute(2, "Title", title);
            builder.AddAttribute(3, "Content", dialogContent);
            builder.AddAttribute(4, "Id", dialogReference.Id);
            builder.CloseComponent();
         });
         dialogReference.InjectRenderFragment(dialogInstance);
         OnDialogInstanceAdded?.Invoke(dialogReference);

         return dialogReference;
      }

      public Task<IDialogReference> ShowAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>() where T : ComponentBase
      {
         return ShowAsync<T>(string.Empty, new DialogParameters(), new DialogOptions());
      }

      public Task<IDialogReference> ShowAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(string title) where T : ComponentBase
      {
         return ShowAsync<T>(title, new DialogParameters(), new DialogOptions());
      }

      public Task<IDialogReference> ShowAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(string title, DialogOptions options) where T : ComponentBase
      {
         return ShowAsync<T>(title, new DialogParameters(), options);
      }

      public Task<IDialogReference> ShowAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(string title, DialogParameters parameters) where T : ComponentBase
      {
         return ShowAsync<T>(title, parameters, new DialogOptions());
      }

      public Task<IDialogReference> ShowAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(string title, DialogParameters parameters, DialogOptions options) where T : ComponentBase
      {
         return ShowAsync(typeof(T), title, parameters, options);
      }

      public Task<IDialogReference> ShowAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type contentComponent)
      {
         return ShowAsync(contentComponent, string.Empty, new DialogParameters(), new DialogOptions());
      }

      public Task<IDialogReference> ShowAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type contentComponent, string title)
      {
         return ShowAsync(contentComponent, title, new DialogParameters(), new DialogOptions());
      }

      public Task<IDialogReference> ShowAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type contentComponent, string title, DialogOptions options)
      {
         return ShowAsync(contentComponent, title, new DialogParameters(), options);
      }

      public Task<IDialogReference> ShowAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type contentComponent, string title, DialogParameters parameters)
      {
         return ShowAsync(contentComponent, title, parameters, new DialogOptions());
      }

      public async Task<IDialogReference> ShowAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type contentComponent, string title, DialogParameters parameters, DialogOptions options)
      {
         var dialogReference = Show(contentComponent, title, parameters, options);

         //Do not wait forever, what if render fails because of some internal exception and we will never release the method.
         var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(5));
         var token = cancellationTokenSource.Token;
         await using (token.Register(() => dialogReference.RenderCompleteTaskCompletionSource.TrySetResult(false)))
         {
            await dialogReference.RenderCompleteTaskCompletionSource.Task;

            return dialogReference;
         }
      }

      public Task<bool?> ShowMessageBox(string title, string message, string yesText = "OK",
         string noText = null, string cancelText = null, DialogOptions options = null)
      {
         return this.ShowMessageBox(new MessageBoxOptions
         {
            Title = title,
            Message = message,
            YesText = yesText,
            NoText = noText,
            CancelText = cancelText,
         }, options);
      }

      public Task<bool?> ShowMessageBox(string title, MarkupString markupMessage, string yesText = "OK",
         string noText = null, string cancelText = null, DialogOptions options = null)
      {
         return this.ShowMessageBox(new MessageBoxOptions
         {
            Title = title,
            MarkupMessage = markupMessage,
            YesText = yesText,
            NoText = noText,
            CancelText = cancelText,
         }, options);
      }

      public async Task<bool?> ShowMessageBox(MessageBoxOptions messageBoxOptions, DialogOptions options = null)
      {
         var parameters = new DialogParameters()
         {
            [nameof(MessageBoxOptions.Title)] = messageBoxOptions.Title,
            [nameof(MessageBoxOptions.Message)] = messageBoxOptions.Message,
            [nameof(MessageBoxOptions.MarkupMessage)] = messageBoxOptions.MarkupMessage,
            [nameof(MessageBoxOptions.CancelText)] = messageBoxOptions.CancelText,
            [nameof(MessageBoxOptions.NoText)] = messageBoxOptions.NoText,
            [nameof(MessageBoxOptions.YesText)] = messageBoxOptions.YesText,
         };
         var reference = await ShowAsync<MessageBox>(parameters: parameters, options: options, title: messageBoxOptions.Title);
         var result = await reference.Result;
         if (result.Cancelled || result.Data is not bool data)
            return null;
         return data;
      }

      public void Close(DialogReference dialog)
      {
         Close(dialog, DialogResult.Ok<object>(null));
      }

      public virtual void Close(DialogReference dialog, DialogResult result)
      {
         OnDialogCloseRequested?.Invoke(dialog, result);
      }

      public virtual IDialogReference CreateReference()
      {
         return new DialogReference(Guid.NewGuid(), this);
      }
   }

   [ExcludeFromCodeCoverage]
   public class MessageBoxOptions
   {
      public string Title { get; set; }
      public string Message { get; set; }
      public MarkupString MarkupMessage { get; set; }
      public string YesText { get; set; } = "OK";
      public string NoText { get; set; }
      public string CancelText { get; set; }
   }
}
