using System.Collections.Concurrent;
using System.Runtime.Versioning;

namespace Kompas.Mcp.Core.Threading;

/// <summary>
/// Выделенный STA-поток, который владеет всеми COM-вызовами к КОМПАС.
/// <para>
/// КОМПАС — однопоточный STA COM-сервер в единственном экземпляре, поэтому весь доступ
/// к нему сериализуется через этот поток. Асинхронные обработчики MCP-инструментов
/// маршалят работу сюда через <see cref="InvokeAsync{T}"/>.
/// </para>
/// <remarks>
/// v1: простая очередь задач без явного насоса сообщений Windows. Для исходящих
/// синхронных вызовов из out-of-process клиента этого достаточно (COM сам обслуживает
/// STA на время вызова). Если при интеграции появятся зависания на колбэках/диалогах —
/// заменить цикл на насос сообщений (WPF Dispatcher / нативный GetMessage). См. docs/OPEN_QUESTIONS.md.
/// </remarks>
/// </summary>
[SupportedOSPlatform("windows")]
public sealed class KompasDispatcher : IDisposable
{
    private readonly Thread _thread;
    private readonly BlockingCollection<Action> _queue = new();
    private readonly ManualResetEventSlim _ready = new(false);
    private volatile bool _disposed;

    public KompasDispatcher()
    {
        _thread = new Thread(ThreadProc)
        {
            IsBackground = true,
            Name = "KompasSTA",
        };
        _thread.SetApartmentState(ApartmentState.STA);
        _thread.Start();
        _ready.Wait();
    }

    /// <summary>Выполняется ли вызывающий код уже на STA-потоке диспетчера.</summary>
    public bool IsOnDispatcherThread => Thread.CurrentThread == _thread;

    /// <summary>Поставить функцию в очередь STA-потока и дождаться результата.</summary>
    public Task<T> InvokeAsync<T>(Func<T> func, CancellationToken ct = default)
    {
        ArgumentNullException.ThrowIfNull(func);
        var tcs = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);

        if (_disposed)
        {
            tcs.SetException(new ObjectDisposedException(nameof(KompasDispatcher)));
            return tcs.Task;
        }

        // Уже на STA-потоке — выполняем синхронно, иначе будет дедлок (ждать себя же).
        if (IsOnDispatcherThread)
        {
            try { tcs.SetResult(func()); }
            catch (Exception ex) { tcs.SetException(ex); }
            return tcs.Task;
        }

        try
        {
            _queue.Add(() =>
            {
                if (ct.IsCancellationRequested) { tcs.TrySetCanceled(ct); return; }
                try { tcs.TrySetResult(func()); }
                catch (Exception ex) { tcs.TrySetException(ex); }
            });
        }
        catch (InvalidOperationException)
        {
            tcs.SetException(new ObjectDisposedException(nameof(KompasDispatcher)));
        }

        return tcs.Task;
    }

    /// <summary>Поставить действие в очередь STA-потока и дождаться завершения.</summary>
    public Task InvokeAsync(Action action, CancellationToken ct = default)
    {
        ArgumentNullException.ThrowIfNull(action);
        return InvokeAsync(() => { action(); return true; }, ct);
    }

    private void ThreadProc()
    {
        _ready.Set();
        foreach (var work in _queue.GetConsumingEnumerable())
        {
            // Исключения каждой задачи уже перехвачены и переданы в её TaskCompletionSource.
            work();
        }
    }

    public void Dispose()
    {
        if (_disposed) return;
        _disposed = true;

        _queue.CompleteAdding();
        if (!_thread.Join(TimeSpan.FromSeconds(10)))
        {
            // STA-поток не завершился (возможно, висит COM-вызов) — оставляем как фон.
        }

        _queue.Dispose();
        _ready.Dispose();
    }
}
