using System.Windows; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Microsoft.Extensions.Logging; using Microsoft.Win32; using Serilog; using SharepointToolbox.Core.Models; using SharepointToolbox.Services; using SharepointToolbox.Services.Export; namespace SharepointToolbox.ViewModels.Tabs; public partial class TransferViewModel : FeatureViewModelBase { private readonly IFileTransferService _transferService; private readonly ISessionManager _sessionManager; private readonly BulkResultCsvExportService _exportService; private readonly ILogger _logger; private TenantProfile? _currentProfile; // Source selection [ObservableProperty] private string _sourceSiteUrl = string.Empty; [ObservableProperty] private string _sourceLibrary = string.Empty; [ObservableProperty] private string _sourceFolderPath = string.Empty; // Destination selection [ObservableProperty] private string _destSiteUrl = string.Empty; [ObservableProperty] private string _destLibrary = string.Empty; [ObservableProperty] private string _destFolderPath = string.Empty; // Transfer options [ObservableProperty] private TransferMode _transferMode = TransferMode.Copy; [ObservableProperty] private ConflictPolicy _conflictPolicy = ConflictPolicy.Skip; // Results [ObservableProperty] private string _resultSummary = string.Empty; [ObservableProperty] private bool _hasFailures; private BulkOperationSummary? _lastResult; public IAsyncRelayCommand ExportFailedCommand { get; } // Expose current profile for View code-behind (site picker, folder browser) public TenantProfile? CurrentProfile => _currentProfile; // Dialog factories — set by View code-behind public Func? ShowConfirmDialog { get; set; } public TransferViewModel( IFileTransferService transferService, ISessionManager sessionManager, BulkResultCsvExportService exportService, ILogger logger) : base(logger) { _transferService = transferService; _sessionManager = sessionManager; _exportService = exportService; _logger = logger; ExportFailedCommand = new AsyncRelayCommand(ExportFailedAsync, () => HasFailures); } protected override async Task RunOperationAsync(CancellationToken ct, IProgress progress) { if (_currentProfile == null) throw new InvalidOperationException("No tenant connected."); if (string.IsNullOrWhiteSpace(SourceSiteUrl) || string.IsNullOrWhiteSpace(SourceLibrary)) throw new InvalidOperationException("Source site and library must be selected."); if (string.IsNullOrWhiteSpace(DestSiteUrl) || string.IsNullOrWhiteSpace(DestLibrary)) throw new InvalidOperationException("Destination site and library must be selected."); // Confirmation dialog var message = $"{TransferMode} files from {SourceLibrary} to {DestLibrary} ({ConflictPolicy} on conflict)"; if (ShowConfirmDialog != null && !ShowConfirmDialog(message)) return; var job = new TransferJob { SourceSiteUrl = SourceSiteUrl, SourceLibrary = SourceLibrary, SourceFolderPath = SourceFolderPath, DestinationSiteUrl = DestSiteUrl, DestinationLibrary = DestLibrary, DestinationFolderPath = DestFolderPath, Mode = TransferMode, ConflictPolicy = ConflictPolicy, }; // Build per-site profiles so SessionManager can resolve contexts var srcProfile = new TenantProfile { Name = _currentProfile.Name, TenantUrl = SourceSiteUrl, ClientId = _currentProfile.ClientId, }; var dstProfile = new TenantProfile { Name = _currentProfile.Name, TenantUrl = DestSiteUrl, ClientId = _currentProfile.ClientId, }; var srcCtx = await _sessionManager.GetOrCreateContextAsync(srcProfile, ct); var dstCtx = await _sessionManager.GetOrCreateContextAsync(dstProfile, ct); _lastResult = await _transferService.TransferAsync(srcCtx, dstCtx, job, progress, ct); // Update UI on dispatcher await Application.Current.Dispatcher.InvokeAsync(() => { HasFailures = _lastResult.HasFailures; ExportFailedCommand.NotifyCanExecuteChanged(); if (_lastResult.HasFailures) { ResultSummary = string.Format( Localization.TranslationSource.Instance["bulk.result.success"], _lastResult.SuccessCount, _lastResult.FailedCount); } else { ResultSummary = string.Format( Localization.TranslationSource.Instance["bulk.result.allsuccess"], _lastResult.TotalCount); } }); } private async Task ExportFailedAsync() { if (_lastResult == null || !_lastResult.HasFailures) return; var dlg = new SaveFileDialog { Filter = "CSV Files (*.csv)|*.csv", FileName = "transfer_failed_items.csv", }; if (dlg.ShowDialog() == true) { await _exportService.WriteFailedItemsCsvAsync( _lastResult.FailedItems.ToList(), dlg.FileName, CancellationToken.None); Log.Information("Exported failed transfer items to {Path}", dlg.FileName); } } protected override void OnTenantSwitched(TenantProfile profile) { _currentProfile = profile; SourceSiteUrl = string.Empty; SourceLibrary = string.Empty; SourceFolderPath = string.Empty; DestSiteUrl = string.Empty; DestLibrary = string.Empty; DestFolderPath = string.Empty; ResultSummary = string.Empty; HasFailures = false; _lastResult = null; } }