using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using System.Collections.ObjectModel; using ClaudeDo.Ui.Services; using ClaudeDo.Ui.ViewModels.Modals; using Microsoft.Extensions.DependencyInjection; namespace ClaudeDo.Ui.ViewModels.Islands; public sealed partial class MergeSectionViewModel : ViewModelBase { private readonly IWorkerClient _worker; private readonly IServiceProvider _services; // Context mirrored from parent, updated via Sync* methods internal string? TaskId { get; private set; } internal string? TaskTitle { get; private set; } private string? _worktreePath; private string? _worktreeBaseCommit; private string? _worktreeHeadCommit; private string? _worktreeStateLabel; private string? _listWorkingDir; private bool _isPlanningParent; private int _subtaskCount; private bool _hasChildOutcomes; [ObservableProperty] private ObservableCollection _mergeTargetBranches = new(); [ObservableProperty] private string? _selectedMergeTarget; [ObservableProperty] [NotifyPropertyChangedFor(nameof(ShowMergePreviewMuted))] private string _mergePreviewText = ""; [ObservableProperty] [NotifyPropertyChangedFor(nameof(ShowMergePreviewMuted))] private bool _mergeIsClean; [ObservableProperty] [NotifyPropertyChangedFor(nameof(ShowMergePreviewMuted))] private bool _mergeIsConflict; public bool ShowMergePreviewMuted => !MergeIsClean && !MergeIsConflict && !string.IsNullOrEmpty(MergePreviewText); public bool ShowMergeSection => _worktreePath != null || _isPlanningParent || _hasChildOutcomes; public Func? ShowDiffViewer { get; set; } public Func? ShowMergeModal { get; set; } public MergeSectionViewModel(IWorkerClient worker, IServiceProvider services) { _worker = worker; _services = services; } partial void OnSelectedMergeTargetChanged(string? value) => _ = RefreshMergePreviewAsync(); internal void SyncWorktree( string? worktreePath, string? worktreeBase, string? worktreeHead, string? worktreeState, string? listWorkDir) { _worktreePath = worktreePath; _worktreeBaseCommit = worktreeBase; _worktreeHeadCommit = worktreeHead; _worktreeStateLabel = worktreeState; _listWorkingDir = listWorkDir; OnPropertyChanged(nameof(ShowMergeSection)); OpenDiffCommand.NotifyCanExecuteChanged(); OpenWorktreeCommand.NotifyCanExecuteChanged(); } internal void SyncTaskContext(string? taskId, string? taskTitle, bool isPlanningParent) { TaskId = taskId; TaskTitle = taskTitle; _isPlanningParent = isPlanningParent; OnPropertyChanged(nameof(ShowMergeSection)); } internal void SyncChildOutcomes(bool hasChildOutcomes, int subtaskCount) { _hasChildOutcomes = hasChildOutcomes; _subtaskCount = subtaskCount; OnPropertyChanged(nameof(ShowMergeSection)); ReviewCombinedDiffCommand.NotifyCanExecuteChanged(); } internal async System.Threading.Tasks.Task RefreshMergePreviewAsync() { if (TaskId is null || _worktreePath is null) { MergePreviewText = ""; MergeIsClean = false; MergeIsConflict = false; return; } if (_worktreeStateLabel is { } label && label != "Active") { MergePreviewText = label; MergeIsClean = false; MergeIsConflict = false; return; } var capturedTaskId = TaskId; var capturedTarget = SelectedMergeTarget; var dto = await _worker.PreviewMergeAsync(capturedTaskId, capturedTarget ?? ""); if (TaskId != capturedTaskId || SelectedMergeTarget != capturedTarget) return; var (text, clean, conflict) = MergePreviewPresenter.Describe(dto); MergePreviewText = text; MergeIsClean = clean; MergeIsConflict = conflict; } internal void Clear() { MergeTargetBranches.Clear(); SelectedMergeTarget = null; MergePreviewText = ""; MergeIsClean = false; MergeIsConflict = false; SyncWorktree(null, null, null, null, null); SyncTaskContext(null, null, false); SyncChildOutcomes(false, 0); } [RelayCommand(CanExecute = nameof(CanReviewDiff))] private async System.Threading.Tasks.Task ReviewCombinedDiffAsync() { if (TaskId is null || ShowDiffViewer is null) return; var vm = _services.GetRequiredService(); vm.ConfigurePlanning(TaskId, SelectedMergeTarget ?? "main"); await vm.LoadAsync(); await ShowDiffViewer(vm); } private bool CanReviewDiff() => (_isPlanningParent && _subtaskCount > 0) || _hasChildOutcomes; [RelayCommand(CanExecute = nameof(CanOpenDiff))] private async System.Threading.Tasks.Task OpenDiffAsync() { if (ShowDiffViewer is null) return; var hasLiveWorktree = _worktreePath != null && _worktreeStateLabel == "Active" && System.IO.Directory.Exists(_worktreePath); var vm = _services.GetRequiredService(); if (hasLiveWorktree) { vm.ConfigureWorktree(_worktreePath!, _worktreeBaseCommit, TaskId, TaskTitle ?? ""); vm.ShowMergeModal = ShowMergeModal; vm.ResolveMergeVm = () => _services.GetRequiredService(); } else if (CanDiffMergedRange) { vm.ConfigureCommitRange(_listWorkingDir!, _worktreeBaseCommit, _worktreeHeadCommit, TaskId, TaskTitle ?? ""); } else return; await vm.LoadAsync(); await ShowDiffViewer(vm); } private bool CanDiffMergedRange => _worktreeBaseCommit != null && _worktreeHeadCommit != null && _listWorkingDir != null; private bool CanOpenDiff() => _worktreePath != null || CanDiffMergedRange; [RelayCommand(CanExecute = nameof(CanOpenWorktree))] private void OpenWorktree() { if (_worktreePath is null) return; try { System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo { FileName = _worktreePath, UseShellExecute = true, }); } catch { } } private bool CanOpenWorktree() => _worktreePath != null; }