using System.Collections.ObjectModel;
using ClawdDotNet.App.Models;
using ClawdDotNet.App.Services;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using ClawdDotNet.Core.Instances;
namespace ClawdDotNet.Desktop.ViewModels;
///
/// Eine Instanz in der Auswahl.
///
/// Das Monogramm und der Punkt stammen aus dem Entwurf. Der Punkt zeigt bewusst
/// Bereitschaft und nicht „läuft": Ob eine andere Instanz gerade läuft, weiß
/// diese Anwendung nicht — jede Instanz ist ein eigener Prozess, und einen
/// instanzübergreifenden Status gibt es noch nicht. Ein grüner Punkt für „läuft" wäre
/// hier schlicht geraten.
///
public sealed class InstanceChoiceViewModel(InstanceInfo info, bool isCurrent)
{
public InstanceInfo Info { get; } = info;
public string InstanceName => Info.InstanceName;
public string FolderPath => Info.FolderPath;
/// Erste zwei Buchstaben, gross — wie im Entwurf.
public string Monogram => Info.InstanceName.Length >= 2
? Info.InstanceName[..2].ToUpperInvariant()
: Info.InstanceName.ToUpperInvariant();
public string Summary
{
get
{
var keyStatus = IsReady
? LocalizationManager.Instance["desktop.instancePicker.apiKeyConfigured"]
: LocalizationManager.Instance["desktop.instancePicker.apiKeyMissing"];
return string.Format(
LocalizationManager.Instance["desktop.instancePicker.agentsCount"],
Info.AgentCount,
keyStatus);
}
}
/// Ohne API-Key kann die Instanz keine Agenten starten.
public bool IsReady => Info.ApiKeyStatus.Equals("Konfiguriert", StringComparison.OrdinalIgnoreCase)
|| Info.ApiKeyStatus.Equals("Configured", StringComparison.OrdinalIgnoreCase);
/// Die gerade laufende Instanz — traegt im Entwurf das Etikett „Aktiv".
public bool IsCurrent { get; } = isCurrent;
}
///
/// Auswahl der Instanz — beim Start und beim Wechseln aus der laufenden Anwendung.
///
/// Die WinForms-Fassung mischte Datenzugriff, Spaltenformatierung und Meldungsfenster
/// in einem Formular. Hier steht nur, was geschieht; das wie es aussieht
/// liegt in der Ansicht, und Rückfragen laufen über statt über
/// einen direkten Dialogaufruf — so bleibt das Ansichtsmodell ohne Fensterbezug prüfbar.
///
public sealed partial class InstancePickerViewModel : ViewModelBase
{
private readonly InstanceDirectoryManager _directories;
private readonly TaskCompletionSource _completion = new();
private readonly string? _currentInstancePath;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(StartCommand))]
private InstanceChoiceViewModel? _selected;
[ObservableProperty]
private string _newInstanceName = "";
/// Fehlertext für die Ansicht; leer heißt: alles in Ordnung.
[ObservableProperty]
private string _errorText = "";
public ObservableCollection Instances { get; } = [];
/// Der gewählte Pfad, oder null bei Abbruch.
public Task Result => _completion.Task;
public string InstancesDirectory => _directories.InstancesDirectory;
///
/// Beim Start heisst die Schaltflaeche „Starten", aus der laufenden Anwendung heraus
/// „Wechseln" — und dann steht der Hinweis dabei, dass dafuer neu gestartet wird.
///
public bool IsSwitchMode => _currentInstancePath is not null;
public string ConfirmLabel => IsSwitchMode
? LocalizationManager.Instance["desktop.instancePicker.confirmSwitch"]
: LocalizationManager.Instance["desktop.instancePicker.confirmStart"];
/// Ueberschrift des Dialogs — nicht aus ConfirmLabel zusammengesetzt,
/// sonst stuende dort „Instanz Starten".
public string Title => IsSwitchMode
? LocalizationManager.Instance["desktop.instancePicker.titleSwitch"]
: LocalizationManager.Instance["desktop.instancePicker.titleSelect"];
public string Hint => IsSwitchMode
? LocalizationManager.Instance["desktop.instancePicker.hintSwitch"]
: LocalizationManager.Instance["desktop.instancePicker.hintSelect"];
///
/// Pfad der laufenden Instanz, oder null beim Start. Steuert Beschriftung und
/// das Etikett „Aktiv" in der Liste.
///
public InstancePickerViewModel(InstanceDirectoryManager directories, string? currentInstancePath = null)
{
_directories = directories;
_currentInstancePath = currentInstancePath;
Refresh();
}
[RelayCommand]
private void Refresh()
{
Instances.Clear();
foreach (var instance in _directories.ListInstances())
{
var isCurrent = _currentInstancePath is not null
&& string.Equals(instance.FolderPath, _currentInstancePath,
StringComparison.OrdinalIgnoreCase);
Instances.Add(new InstanceChoiceViewModel(instance, isCurrent));
}
// Eine einzelne Instanz gleich vorwählen — der häufigste Fall, und ein Klick
// weniger bei jedem Start.
Selected ??= Instances.FirstOrDefault(i => i.IsCurrent) ?? Instances.FirstOrDefault();
}
private bool CanStart => Selected is not null;
[RelayCommand(CanExecute = nameof(CanStart))]
private void Start() => _completion.TrySetResult(Selected!.FolderPath);
/// Waehlt eine Zeile und bestaetigt in einem Zug — die Schaltflaeche je Zeile.
[RelayCommand]
private void Activate(InstanceChoiceViewModel? choice)
{
if (choice is null) return;
Selected = choice;
_completion.TrySetResult(choice.FolderPath);
}
/// Abbruch — der Aufrufer beendet die Anwendung oder schliesst den Dialog.
[RelayCommand]
private void Cancel() => _completion.TrySetResult(null);
[RelayCommand]
private void Create()
{
var name = NewInstanceName.Trim();
if (string.IsNullOrWhiteSpace(name))
{
ErrorText = LocalizationManager.Instance["desktop.instancePicker.nameRequired"];
return;
}
try
{
_directories.CreateInstance(name);
NewInstanceName = "";
ErrorText = "";
Refresh();
// Die frisch angelegte Instanz vorwählen — wer sie erstellt, will sie starten.
Selected = Instances.FirstOrDefault(i =>
string.Equals(i.InstanceName, name, StringComparison.Ordinal));
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException
or ArgumentException)
{
ErrorText = string.Format(
LocalizationManager.Instance["desktop.instancePicker.createFailed"],
ex.Message);
}
}
///
/// Wird gesetzt, wenn ein Fehler von außen kommt (etwa eine unlesbare
/// Instanzkonfiguration), damit das Fenster offen bleiben und eine andere Wahl
/// zulassen kann.
///
public void ConfirmError(string message) => ErrorText = message;
}