using SharepointToolbox.Core.Models;

namespace SharepointToolbox.Services;

public static class BulkOperationRunner
{
    /// <summary>
    /// Runs a bulk operation with continue-on-error semantics, per-item result tracking,
    /// and cancellation support. OperationCanceledException propagates immediately.
    ///
    /// Progress is reported AFTER each item completes (success or failure), so the bar
    /// reflects actual work done rather than work queued. A final "Complete" report
    /// guarantees 100% when the total was determinate.
    ///
    /// Set <paramref name="maxConcurrency"/> > 1 to run items in parallel. Callers must
    /// ensure processItem is safe to invoke concurrently (e.g. each invocation uses its
    /// own CSOM ClientContext — a shared CSOM context is NOT thread-safe).
    /// </summary>
    public static async Task<BulkOperationSummary<TItem>> RunAsync<TItem>(
        IReadOnlyList<TItem> items,
        Func<TItem, int, CancellationToken, Task> processItem,
        IProgress<OperationProgress> progress,
        CancellationToken ct,
        int maxConcurrency = 1)
    {
        if (items.Count == 0)
        {
            progress.Report(new OperationProgress(0, 0, "Nothing to do."));
            return new BulkOperationSummary<TItem>(Array.Empty<BulkItemResult<TItem>>());
        }

        progress.Report(new OperationProgress(0, items.Count, $"Processing 1/{items.Count}..."));

        var results = new BulkItemResult<TItem>[items.Count];
        int completed = 0;

        async Task RunOne(int i, CancellationToken token)
        {
            try
            {
                await processItem(items[i], i, token);
                results[i] = BulkItemResult<TItem>.Success(items[i]);
            }
            catch (OperationCanceledException) { throw; }
            catch (Exception ex)
            {
                results[i] = BulkItemResult<TItem>.Failed(items[i], ex.Message);
            }
            finally
            {
                int done = Interlocked.Increment(ref completed);
                progress.Report(new OperationProgress(done, items.Count,
                    $"Processed {done}/{items.Count}"));
            }
        }

        if (maxConcurrency <= 1)
        {
            for (int i = 0; i < items.Count; i++)
            {
                ct.ThrowIfCancellationRequested();
                await RunOne(i, ct);
            }
        }
        else
        {
            var options = new ParallelOptions
            {
                MaxDegreeOfParallelism = maxConcurrency,
                CancellationToken = ct
            };
            await Parallel.ForEachAsync(Enumerable.Range(0, items.Count), options,
                async (i, token) => await RunOne(i, token));
        }

        progress.Report(new OperationProgress(items.Count, items.Count, "Complete."));
        return new BulkOperationSummary<TItem>(results);
    }
}
