using System.Collections.ObjectModel; using System.IO; using System.Reflection; 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.Localization; using SharepointToolbox.Services; using SharepointToolbox.Services.Export; namespace SharepointToolbox.ViewModels.Tabs; public partial class FolderStructureViewModel : FeatureViewModelBase { private readonly IFolderStructureService _folderService; private readonly ICsvValidationService _csvService; private readonly ISessionManager _sessionManager; private readonly BulkResultCsvExportService _exportService; private readonly ILogger _logger; private TenantProfile? _currentProfile; private List? _validRows; private BulkOperationSummary? _lastResult; [ObservableProperty] private string _siteUrl = string.Empty; [ObservableProperty] private string _libraryTitle = string.Empty; [ObservableProperty] private string _previewSummary = string.Empty; [ObservableProperty] private string _resultSummary = string.Empty; [ObservableProperty] private bool _hasFailures; [ObservableProperty] private bool _hasPreview; private ObservableCollection> _previewRows = new(); public ObservableCollection> PreviewRows { get => _previewRows; private set { _previewRows = value; OnPropertyChanged(); } } public IRelayCommand ImportCsvCommand { get; } public IRelayCommand LoadExampleCommand { get; } public IAsyncRelayCommand ExportFailedCommand { get; } public Func? ShowConfirmDialog { get; set; } public TenantProfile? CurrentProfile => _currentProfile; public FolderStructureViewModel( IFolderStructureService folderService, ICsvValidationService csvService, ISessionManager sessionManager, BulkResultCsvExportService exportService, ILogger logger) : base(logger) { _folderService = folderService; _csvService = csvService; _sessionManager = sessionManager; _exportService = exportService; _logger = logger; ImportCsvCommand = new RelayCommand(ImportCsv); LoadExampleCommand = new RelayCommand(LoadExample); ExportFailedCommand = new AsyncRelayCommand(ExportFailedAsync, () => HasFailures); } private void ImportCsv() { var dlg = new OpenFileDialog { Title = TranslationSource.Instance["bulk.csvimport.title"], Filter = TranslationSource.Instance["bulk.csvimport.filter"], }; if (dlg.ShowDialog() != true) return; using var stream = File.OpenRead(dlg.FileName); LoadAndPreview(stream); } private void LoadExample() { var assembly = Assembly.GetExecutingAssembly(); var resourceName = assembly.GetManifestResourceNames() .FirstOrDefault(n => n.EndsWith("folder_structure.csv", StringComparison.OrdinalIgnoreCase)); if (resourceName == null) return; using var stream = assembly.GetManifestResourceStream(resourceName); if (stream != null) LoadAndPreview(stream); } private void LoadAndPreview(Stream stream) { var rows = _csvService.ParseAndValidateFolders(stream); PreviewRows = new ObservableCollection>(rows); _validRows = rows.Where(r => r.IsValid && r.Record != null).Select(r => r.Record!).ToList(); var uniquePaths = FolderStructureService.BuildUniquePaths(_validRows); PreviewSummary = string.Format(TranslationSource.Instance["folderstruct.preview"], uniquePaths.Count); HasPreview = true; ResultSummary = string.Empty; HasFailures = false; } protected override async Task RunOperationAsync(CancellationToken ct, IProgress progress) { if (_currentProfile == null) throw new InvalidOperationException("No tenant connected."); if (_validRows == null || _validRows.Count == 0) throw new InvalidOperationException("No valid rows. Import a CSV first."); if (string.IsNullOrWhiteSpace(SiteUrl)) throw new InvalidOperationException("Site URL is required."); if (string.IsNullOrWhiteSpace(LibraryTitle)) throw new InvalidOperationException("Library title is required."); var uniquePaths = FolderStructureService.BuildUniquePaths(_validRows); var message = string.Format(TranslationSource.Instance["bulk.confirm.message"], $"{uniquePaths.Count} folders will be created in {LibraryTitle}"); if (ShowConfirmDialog != null && !ShowConfirmDialog(message)) return; var profile = new TenantProfile { Name = _currentProfile.Name, TenantUrl = SiteUrl, ClientId = _currentProfile.ClientId, }; var ctx = await _sessionManager.GetOrCreateContextAsync(profile, ct); _lastResult = await _folderService.CreateFoldersAsync(ctx, LibraryTitle, _validRows, progress, ct); await Application.Current.Dispatcher.InvokeAsync(() => { HasFailures = _lastResult.HasFailures; ExportFailedCommand.NotifyCanExecuteChanged(); ResultSummary = _lastResult.HasFailures ? string.Format(TranslationSource.Instance["bulk.result.success"], _lastResult.SuccessCount, _lastResult.FailedCount) : string.Format(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 = "failed_folders.csv" }; if (dlg.ShowDialog() == true) { await _exportService.WriteFailedItemsCsvAsync(_lastResult.FailedItems.ToList(), dlg.FileName, CancellationToken.None); } } protected override void OnTenantSwitched(TenantProfile profile) { _currentProfile = profile; SiteUrl = string.Empty; LibraryTitle = string.Empty; PreviewRows = new(); _validRows = null; PreviewSummary = string.Empty; ResultSummary = string.Empty; HasFailures = false; HasPreview = false; } }