using System.Collections.Concurrent;
using System.Text;
namespace Wp2GhostUI;
///
/// Verbindet den Importer mit der Oberfläche. Der Importer schreibt mit Console.Write und fragt mit
/// Console.ReadLine — beides wird hierher umgeleitet: Ausgaben landen im Protokoll, und bei einer
/// Rückfrage wartet der Ablauf, bis die Oberfläche eine Antwort liefert. So laufen Konsole und
/// Oberfläche garantiert durch dieselbe Logik, ohne dass Abfragen doppelt programmiert werden.
///
public class ImporterSession
{
private const int MaxLines = 3000;
private readonly object _gate = new();
private readonly List _lines = new();
private readonly StringBuilder _current = new(); // angefangene Zeile (z. B. eine Frage ohne Zeilenende)
private readonly BlockingCollection _answers = new();
private CancellationTokenSource _cancel = new();
private Timer? _notifyTimer;
private bool _dirty;
public string DataDir { get; private set; } = "";
public bool IsBusy { get; private set; }
public string? CurrentTask { get; private set; }
/// Text der offenen Rückfrage; null, solange keine Antwort erwartet wird.
public string? Prompt { get; private set; }
/// Zählt abgeschlossene Abläufe — die Oberfläche lädt danach ihre Listen neu.
public int CompletedRuns { get; private set; }
/// Wird ausgelöst, wenn sich Protokoll, Rückfrage oder Status geändert haben (gebündelt).
public event Action? Changed;
public void Start(string dataDir)
{
DataDir = dataDir;
Console.SetOut(new SessionWriter(this));
Console.SetIn(new SessionReader(this));
Importer.Initialize(dataDir);
_notifyTimer = new Timer(_ =>
{
if (!_dirty) return;
_dirty = false;
Changed?.Invoke();
}, null, 150, 150);
}
/// Protokollzeilen plus die gerade angefangene Zeile.
public List GetLines(int last = 400)
{
lock (_gate)
{
var result = _lines.Skip(Math.Max(0, _lines.Count - last)).ToList();
if (_current.Length > 0 && Prompt == null) result.Add(_current.ToString());
return result;
}
}
public void ClearLog()
{
lock (_gate) { _lines.Clear(); _current.Clear(); }
_dirty = true;
}
/// Startet einen Ablauf im Hintergrund. Es läuft immer nur einer gleichzeitig.
public bool Run(string name, Func work)
{
lock (_gate)
{
if (IsBusy) return false;
IsBusy = true;
CurrentTask = name;
_cancel = new CancellationTokenSource();
while (_answers.TryTake(out _)) { }
}
Append($"\n▶ {name} ({DateTime.Now:HH:mm:ss})\n");
_ = Task.Run(async () =>
{
try
{
await work();
Append($"■ {name} beendet\n");
}
catch (OperationCanceledException)
{
Append($"\n■ {name} abgebrochen\n");
}
catch (Exception ex)
{
Append($"\n[Fehler] {ex.Message}\n■ {name} mit Fehler beendet\n");
}
finally
{
lock (_gate)
{
IsBusy = false;
CurrentTask = null;
Prompt = null;
CompletedRuns++;
wp2ghost.LinkQuestion.Current = null;
}
_dirty = true;
}
});
return true;
}
/// Beantwortet die offene Rückfrage.
public void Answer(string text)
{
if (Prompt == null) return;
_answers.Add(text);
}
///
/// Beantwortet die offene Rückfrage und gleich die darauf folgenden — z. B. erst "e" (bearbeiten)
/// und dann die neue Adresse.
///
public void AnswerMany(params string[] answers)
{
if (Prompt == null) return;
foreach (string answer in answers) _answers.Add(answer);
}
/// Bricht den laufenden Ablauf bei der nächsten Rückfrage ab (mitten in einem Upload nicht möglich).
public void Cancel()
{
if (!IsBusy) return;
_cancel.Cancel();
}
internal void Append(string text)
{
lock (_gate)
{
foreach (char c in text)
{
if (c == '\r') continue;
if (c != '\n') { _current.Append(c); continue; }
_lines.Add(_current.ToString());
_current.Clear();
}
if (_lines.Count > MaxLines) _lines.RemoveRange(0, _lines.Count - MaxLines);
}
_dirty = true;
}
internal string? WaitForAnswer()
{
// Außerhalb eines Ablaufs fragt niemand — wie eine geschlossene Eingabe behandeln
if (!IsBusy) return null;
lock (_gate)
{
// Die angefangene Zeile ist die Frage; fehlt sie, die letzte vollständige Zeile
Prompt = _current.Length > 0 ? _current.ToString() : _lines.LastOrDefault() ?? "";
}
_dirty = true;
string answer;
try
{
answer = _answers.Take(_cancel.Token);
}
finally
{
lock (_gate) { Prompt = null; }
}
// Antwort wie in der Konsole hinter die Frage schreiben
Append(answer + "\n");
return answer;
}
private sealed class SessionWriter(ImporterSession session) : TextWriter
{
public override Encoding Encoding => Encoding.UTF8;
public override void Write(char value) => session.Append(value.ToString());
public override void Write(string? value) { if (value != null) session.Append(value); }
public override void WriteLine(string? value) => session.Append((value ?? "") + "\n");
public override void WriteLine() => session.Append("\n");
}
private sealed class SessionReader(ImporterSession session) : TextReader
{
public override string? ReadLine() => session.WaitForAnswer();
}
}