using System.Collections.ObjectModel; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using ClaudeDo.Data.Models; using ClaudeDo.Data.Repositories; using ClaudeDo.Ui.Services; using ClaudeDo.Ui.Views; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using TaskStatus = ClaudeDo.Data.Models.TaskStatus; namespace ClaudeDo.Ui.ViewModels; public partial class TaskListViewModel : ViewModelBase { private readonly TaskRepository _taskRepo; private readonly TagRepository _tagRepo; private readonly ListRepository _listRepo; private readonly WorkerClient _worker; private readonly Func _editorFactory; private readonly Action _showMessage; public ObservableCollection Tasks { get; } = new(); [ObservableProperty] private TaskItemViewModel? _selectedTask; [ObservableProperty] private string? _currentListId; public event Action? SelectedTaskChanged; partial void OnSelectedTaskChanged(TaskItemViewModel? value) => SelectedTaskChanged?.Invoke(value); public TaskListViewModel(TaskRepository taskRepo, TagRepository tagRepo, ListRepository listRepo, WorkerClient worker, Func editorFactory, Action showMessage) { _taskRepo = taskRepo; _tagRepo = tagRepo; _listRepo = listRepo; _worker = worker; _editorFactory = editorFactory; _showMessage = showMessage; worker.TaskUpdatedEvent += OnTaskUpdated; worker.TaskFinishedEvent += (_, taskId, _, _) => OnTaskUpdated(taskId); } public async Task LoadAsync(string? listId) { CurrentListId = listId; Tasks.Clear(); SelectedTask = null; if (listId is null) return; try { var entities = await _taskRepo.GetByListAsync(listId); foreach (var e in entities) { var tags = await _taskRepo.GetEffectiveTagsAsync(e.Id); Tasks.Add(new TaskItemViewModel(e, tags, RunNowAsync, () => _worker.IsConnected)); } } catch (Exception ex) { _showMessage($"Error loading tasks: {ex.Message}"); } } [RelayCommand] private async Task AddTask() { if (CurrentListId is null) return; // Get list default commit type var list = await _listRepo.GetByIdAsync(CurrentListId); var defaultCommitType = list?.DefaultCommitType ?? "chore"; var editor = _editorFactory(); editor.InitForCreate(CurrentListId, defaultCommitType); var window = new TaskEditorView { DataContext = editor }; editor.RequestClose += () => window.Close(); _ = ShowDialogAsync(window); var saved = await editor.ShowAndWaitAsync(); if (saved is null) return; try { await _taskRepo.AddAsync(saved); foreach (var tagName in editor.SelectedTagNames) { var tagId = await _tagRepo.GetOrCreateAsync(tagName); await _taskRepo.AddTagAsync(saved.Id, tagId); } var tags = await _taskRepo.GetEffectiveTagsAsync(saved.Id); Tasks.Add(new TaskItemViewModel(saved, tags, RunNowAsync, () => _worker.IsConnected)); // Auto wake-queue if agent+queued if (saved.Status == TaskStatus.Queued && tags.Any(t => t.Name == "agent")) { try { await _worker.WakeQueueAsync(); } catch { /* worker offline is fine */ } } } catch (Exception ex) { _showMessage($"Error creating task: {ex.Message}"); } } [RelayCommand] private async Task EditTask() { if (SelectedTask is null || CurrentListId is null) return; var entity = await _taskRepo.GetByIdAsync(SelectedTask.Id); if (entity is null) return; var taskTags = await _taskRepo.GetTagsAsync(entity.Id); var editor = _editorFactory(); editor.InitForEdit(entity, taskTags); var window = new TaskEditorView { DataContext = editor }; editor.RequestClose += () => window.Close(); _ = ShowDialogAsync(window); var saved = await editor.ShowAndWaitAsync(); if (saved is null) return; try { await _taskRepo.UpdateAsync(saved); var existingTags = await _taskRepo.GetTagsAsync(saved.Id); foreach (var old in existingTags) await _taskRepo.RemoveTagAsync(saved.Id, old.Id); foreach (var tagName in editor.SelectedTagNames) { var tagId = await _tagRepo.GetOrCreateAsync(tagName); await _taskRepo.AddTagAsync(saved.Id, tagId); } var newTags = await _taskRepo.GetEffectiveTagsAsync(saved.Id); SelectedTask.Refresh(saved, newTags); } catch (Exception ex) { _showMessage($"Error updating task: {ex.Message}"); } } [RelayCommand] private async Task DeleteTask() { if (SelectedTask is null) return; try { await _taskRepo.DeleteAsync(SelectedTask.Id); Tasks.Remove(SelectedTask); SelectedTask = null; } catch (Exception ex) { _showMessage($"Error deleting task: {ex.Message}"); } } public async Task RefreshSingleAsync(string taskId) { var entity = await _taskRepo.GetByIdAsync(taskId); var existing = Tasks.FirstOrDefault(t => t.Id == taskId); if (entity is null) { if (existing is not null) Tasks.Remove(existing); return; } var tags = await _taskRepo.GetEffectiveTagsAsync(taskId); if (existing is not null) existing.Refresh(entity, tags); } private async Task RunNowAsync(string taskId) { try { await _worker.RunNowAsync(taskId); } catch (Exception ex) { _showMessage($"RunNow failed: {ex.Message}"); } } private async void OnTaskUpdated(string taskId) { if (CurrentListId is null) return; await RefreshSingleAsync(taskId); } private static async Task ShowDialogAsync(Window dialog) { if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop && desktop.MainWindow is not null) { await dialog.ShowDialog(desktop.MainWindow); } else { dialog.Show(); } } }