using System.Linq; using ClaudeDo.Data; using ClaudeDo.Localization; using ClaudeDo.Ui.Localization; using ClaudeDo.Ui.Services; using ClaudeDo.Ui.ViewModels.Modals.Settings; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; namespace ClaudeDo.Ui.ViewModels.Modals; public sealed partial class SettingsModalViewModel : ViewModelBase { private readonly IWorkerClient _worker; public GeneralSettingsTabViewModel General { get; } public WorktreesSettingsTabViewModel Worktrees { get; } public FilesSettingsTabViewModel Files { get; } public PrimeClaudeTabViewModel Prime { get; } public OnlineInboxSettingsViewModel OnlineInbox { get; } [ObservableProperty] private string _validationError = ""; [ObservableProperty] private bool _isBusy; [ObservableProperty] private string _statusMessage = ""; public Action? CloseAction { get; set; } public SettingsModalViewModel(IWorkerClient worker, PrimeClaudeTabViewModel prime, IOnlineLoginService onlineLoginService, ILocalizer localizer, AppSettings appSettings) { _worker = worker; General = new GeneralSettingsTabViewModel(localizer, code => { appSettings.Language = code; appSettings.Save(); }); Worktrees = new WorktreesSettingsTabViewModel(worker); Files = new FilesSettingsTabViewModel(worker); Prime = prime; OnlineInbox = new OnlineInboxSettingsViewModel(worker, onlineLoginService); } public async Task LoadAsync() { IsBusy = true; try { var dto = await _worker.GetAppSettingsAsync(); if (dto is not null) { General.DefaultClaudeInstructions = dto.DefaultClaudeInstructions ?? ""; General.DefaultModel = dto.DefaultModel ?? "sonnet"; General.DefaultMaxTurns = dto.DefaultMaxTurns; General.DefaultPermissionMode = dto.DefaultPermissionMode ?? "auto"; General.MaxParallelExecutions = dto.MaxParallelExecutions; Worktrees.WorktreeStrategy = dto.WorktreeStrategy ?? "sibling"; Worktrees.CentralWorktreeRoot = dto.CentralWorktreeRoot; Worktrees.WorktreeAutoCleanupEnabled = dto.WorktreeAutoCleanupEnabled; Worktrees.WorktreeAutoCleanupDays = dto.WorktreeAutoCleanupDays; General.ReportExcludedPaths = string.IsNullOrWhiteSpace(dto.ReportExcludedPaths) ? @"C:\Private" : string.Join(Environment.NewLine, System.Text.Json.JsonSerializer.Deserialize>(dto.ReportExcludedPaths) ?? new()); General.StandupWeekday = dto.StandupWeekday is >= 0 and <= 6 ? dto.StandupWeekday : (int)DayOfWeek.Wednesday; Prime.DailyPrepMaxTasks = dto.DailyPrepMaxTasks < 1 ? 5 : dto.DailyPrepMaxTasks; } else StatusMessage = Loc.T("vm.settingsModal.workerOffline"); await Prime.LoadAsync(); await OnlineInbox.LoadAsync(); } finally { IsBusy = false; } } [RelayCommand] private async Task Save() { var err = General.Validate() ?? Worktrees.Validate() ?? Prime.Validate(); if (err is not null) { ValidationError = err; return; } ValidationError = ""; IsBusy = true; try { var dto = new AppSettingsDto( General.DefaultClaudeInstructions ?? "", General.DefaultModel ?? "sonnet", General.DefaultMaxTurns, General.DefaultPermissionMode ?? "auto", General.MaxParallelExecutions, Worktrees.WorktreeStrategy ?? "sibling", string.IsNullOrWhiteSpace(Worktrees.CentralWorktreeRoot) ? null : Worktrees.CentralWorktreeRoot, Worktrees.WorktreeAutoCleanupEnabled, Worktrees.WorktreeAutoCleanupDays, System.Text.Json.JsonSerializer.Serialize( General.ReportExcludedPaths .Split('\n').Select(l => l.Trim().TrimEnd('\r')).Where(l => l.Length > 0).ToList()), General.StandupWeekday, Prime.DailyPrepMaxTasks); await _worker.UpdateAppSettingsAsync(dto); await Prime.SaveAsync(); CloseAction?.Invoke(); } catch (Exception ex) { StatusMessage = Loc.T("vm.settingsModal.saveFailed", ex.Message); } finally { IsBusy = false; } } [RelayCommand] private void Cancel() => CloseAction?.Invoke(); }