using Bit.Seeder.Options;
using Bit.Seeder.Pipeline;
using Spectre.Console;
namespace Bit.SeederUtility.Helpers;
///
/// Renders into a Spectre.Console .
/// One per phase name.
///
///
/// Thread-safe: the task dictionary is guarded by a lock because events
/// can arrive from worker threads.
/// itself is safe to call concurrently per Spectre.Console's documentation.
///
internal sealed class ConsoleProgressReporter(ProgressContext ctx) : IProgress
{
private readonly Dictionary _tasks = new();
private readonly object _lock = new();
public void Report(SeederProgressEvent value)
{
switch (value)
{
case PhaseStarted started:
lock (_lock)
{
if (_tasks.ContainsKey(started.Phase))
{
return;
}
var task = ctx.AddTask(started.Phase, maxValue: started.Total ?? 1d);
if (started.Total is null)
{
task.IsIndeterminate = true;
}
_tasks[started.Phase] = task;
}
break;
case PhaseAdvanced advanced:
ProgressTask? advanceTask;
lock (_lock)
{
_tasks.TryGetValue(advanced.Phase, out advanceTask);
}
advanceTask?.Increment(advanced.Delta);
break;
case PhaseCompleted completed:
ProgressTask? completeTask;
lock (_lock)
{
_tasks.TryGetValue(completed.Phase, out completeTask);
}
if (completeTask is not null)
{
completeTask.IsIndeterminate = false;
completeTask.Value = completeTask.MaxValue;
completeTask.StopTask();
}
break;
}
}
///
/// Runs inside a Spectre progress context, wiring a reporter
/// into . The seeder's emitted events drive the live bars.
///
///
/// Progress output is written to stderr so stdout remains clean for downstream consumers
/// that pipe the final summary rows (org ID, counts, etc.) into other tools.
///
internal static TResult RunWithProgress(
SeederDependencies deps,
Func seed)
{
var console = AnsiConsole.Create(new AnsiConsoleSettings
{
Out = new AnsiConsoleOutput(Console.Error),
});
TResult result = default!;
console.Progress()
.Columns(
new TaskDescriptionColumn(),
new ProgressBarColumn(),
new PercentageColumn(),
new RemainingTimeColumn(),
new SpinnerColumn())
.Start(ctx =>
{
var reporter = new ConsoleProgressReporter(ctx);
result = seed(deps with { Progress = reporter });
});
return result;
}
}