using System.Threading.Channels; namespace OpenQuery.Services; public class StatusReporter : IDisposable { private readonly bool _verbose; private readonly char[] _spinnerChars = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; private string? _currentMessage; private CancellationTokenSource? _spinnerCts; private Task? _spinnerTask; private readonly Channel _statusChannel; private readonly Task _statusProcessor; public StatusReporter(bool verbose) { _verbose = verbose; _statusChannel = Channel.CreateUnbounded(); _statusProcessor = ProcessStatusUpdatesAsync(); } private async Task ProcessStatusUpdatesAsync() { await foreach (var message in _statusChannel.Reader.ReadAllAsync()) { if (_verbose) { Console.WriteLine(message); continue; } // Clear current line using ANSI escape code Console.Write("\r\x1b[K"); // Write new status with spinner (use first spinner char for static updates) Console.Write($"{_spinnerChars[0]} {message}"); _currentMessage = message; } } public void UpdateStatus(string message) { _statusChannel.Writer.TryWrite(message); } public void ClearStatus() { if (_verbose) return; Console.Write("\r\x1b[K"); _currentMessage = null; } public void WriteFinal(string text) { if (_verbose) { Console.WriteLine(text); return; } StopSpinner(); Console.Write("\r\x1b[K"); Console.Write(text); Console.WriteLine(); } public void StartSpinner() { if (_verbose || _spinnerCts != null) return; _spinnerCts = new CancellationTokenSource(); _spinnerTask = Task.Run(async () => { var spinner = _spinnerChars; var index = 0; while (_spinnerCts is { Token.IsCancellationRequested: false }) { if (_currentMessage != null) { Console.Write("\r\x1b[K"); var charIndex = index++ % spinner.Length; Console.Write($"{spinner[charIndex]} {_currentMessage}"); } try { await Task.Delay(100, _spinnerCts.Token); } catch (TaskCanceledException) { break; } } }, _spinnerCts.Token); } public void StopSpinner() { if (_spinnerCts == null) return; _spinnerCts.Cancel(); _spinnerTask?.GetAwaiter().GetResult(); _spinnerCts = null; _spinnerTask = null; } public void WriteLine(string text) { if (_verbose) { Console.WriteLine(text); return; } StopSpinner(); ClearStatus(); Console.WriteLine(text); } public void Dispose() { _statusChannel.Writer.Complete(); _statusProcessor.GetAwaiter().GetResult(); StopSpinner(); } }