using Insight.Remote.Shared.Abstractions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Windows.Threading;
using Application = System.Windows.Application;

namespace Insight.Remote.Windows.Services;

internal class WinDispatcher : IDispatcher
{
    private Application? _app;

    private readonly ManualResetEvent _appSignal = new(false);

    private readonly IHostApplicationLifetime _lifetime;
    private readonly ILogger<WinDispatcher> _logger;

    public WinDispatcher(IHostApplicationLifetime lifetime, ILogger<WinDispatcher> logger)
    {
        _lifetime = lifetime;
        _logger = logger;
    }

    public async Task<Thread> RunAsync(CancellationToken cancellationToken)
    {
        var startSignal = new SemaphoreSlim(0, 1);

        var thread = new Thread(() =>
        {
            _app = new Application();
            _app.Startup += (s, e) => startSignal.Release();
            _app.Run();
        });

        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();

        await startSignal.WaitAsync(cancellationToken).ConfigureAwait(false);
        _appSignal.Set();

        return thread;
    }

    public async Task ShutdownAsync()
    {
        _lifetime.StopApplication();

        if (_app is null) return;

        try
        {
            _logger.LogInformation("Shutting down WPF thread.");

            await InvokeAsync(_app.Shutdown);
        }
        catch (TaskCanceledException) { }
        catch (Exception ex)
        {
            _logger.LogError(ex.Message);
        }
    }

    public async ValueTask InvokeAsync(Action action, CancellationToken cancellationToken = default)
    {
        if (_app is null) return;
        if (_appSignal.WaitOne(5000) is false) return;

        await _app.Dispatcher.InvokeAsync(action, DispatcherPriority.Normal, cancellationToken);
    }

    public async ValueTask<T?> InvokeAsync<T>(Func<T> func, CancellationToken cancellationToken = default)
    {
        if (_app is null) return default;
        if (_appSignal.WaitOne(5000) is false) return default;

        return await _app.Dispatcher.InvokeAsync(func, DispatcherPriority.Normal, cancellationToken);
    }
}