using Autofac;

using Myriad.Rest.Types;
using Myriad.Rest.Types.Requests;
using Myriad.Types;

using NodaTime;

namespace PluralKit.Bot.Interactive;

public abstract class BaseInteractive
{
    protected readonly List<Button> _buttons = new();
    protected readonly Context _ctx;
    protected readonly TaskCompletionSource _tcs = new();
    protected bool _running;

    protected BaseInteractive(Context ctx)
    {
        _ctx = ctx;
    }

    protected Message _message { get; private set; }

    public Duration Timeout { get; set; } = Duration.FromMinutes(5);

    protected Button AddButton(Func<InteractionContext, Task> handler, string? label = null,
                               ButtonStyle style = ButtonStyle.Secondary, bool disabled = false)
    {
        var dispatch = _ctx.Services.Resolve<InteractionDispatchService>();
        var customId = dispatch.Register(handler, Timeout);

        var button = new Button
        {
            Label = label,
            Style = style,
            Disabled = disabled,
            CustomId = customId,
        };
        _buttons.Add(button);
        return button;
    }

    protected async Task Update(InteractionContext ctx)
    {
        await ctx.Respond(InteractionResponse.ResponseType.UpdateMessage,
            new InteractionApplicationCommandCallbackData { Components = GetComponents() });
    }

    protected async Task Finish(InteractionContext? ctx = null)
    {
        foreach (var button in _buttons)
            button.Disabled = true;

        if (ctx != null)
            await Update(ctx);
        else
            await _ctx.Rest.EditMessage(_message.ChannelId, _message.Id,
                new MessageEditRequest { Components = GetComponents() });

        _tcs.TrySetResult();
    }

    protected async Task Send(string? content = null, Embed? embed = null, AllowedMentions? mentions = null)
    {
        _message = await _ctx.Rest.CreateMessage(_ctx.Channel.Id,
            new MessageRequest
            {
                Content = content,
                Embeds = embed != null ? new[] { embed } : null,
                AllowedMentions = mentions,
                Components = GetComponents()
            });
    }

    public MessageComponent[] GetComponents()
    {
        return new MessageComponent[]
        {
            new()
            {
                Type = ComponentType.ActionRow,
                Components = _buttons.Select(b => b.ToMessageComponent()).ToArray()
            }
        };
    }

    public void Setup(Context ctx)
    {
        var dispatch = ctx.Services.Resolve<InteractionDispatchService>();
        foreach (var button in _buttons)
            button.CustomId = dispatch.Register(button.Handler, Timeout);
    }

    public abstract Task Start();

    public async Task Run()
    {
        if (_running)
            throw new InvalidOperationException("Action is already running");
        _running = true;

        await Start();

        var cts = new CancellationTokenSource(Timeout.ToTimeSpan());
        cts.Token.Register(() => _tcs.TrySetException(new TimeoutException("Action timed out")));

        try
        {
            await _tcs.Task;
        }
        finally
        {
            Cleanup();
        }
    }

    protected void Cleanup()
    {
        var dispatch = _ctx.Services.Resolve<InteractionDispatchService>();
        foreach (var button in _buttons)
            dispatch.Unregister(button.CustomId!);
    }
}