using ImageCatalog_2.Commands; using ImageCatalog_2.Models; using ImageCatalog_2.Services; using ImageCatalog_2.ViewModels; using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; #if WINDOWS using System.Drawing.Text; #endif using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; #if WINDOWS using System.Windows.Forms; #endif using System.Windows.Input; using AutoMapper; using MaddoShared; using Microsoft.Extensions.Logging; namespace ImageCatalog_2 { public class DataModel : ViewModelBase { public ICommand TestCommand { get; } public ICommand AsyncTestCommand { get; } public ICommand AsyncCancelOperationCommand { get; } public ICommand ProcessImagesCommand { get; } public ICommand SelectSourceFolderCommand { get; } public ICommand SelectDestinationFolderCommand { get; } public ICommand SelectLogoFileCommand { get; } public ICommand SaveSettingsCommand { get; } public ICommand LoadSettingsCommand { get; } public ICommand SelectColorCommand { get; } public ICommand SelectTransparentColorCommand { get; } public ICommand SelectModelsFolderCommand { get; } public ICommand SelectCsvOutputCommand { get; } public ICommand StartAiCommand { get; } private readonly ITestService _service; private readonly ILogger _logger; private readonly ISettingsService _settingsService; private readonly ImageCreationService _imageCreationService; private readonly IAiExtractionService _aiExtractionService; private readonly IImageProcessingCoordinator _imageProcessingCoordinator; private readonly ProcessingStateViewModel _processing; private readonly PathSettingsViewModel _paths; private readonly AiSettingsViewModel _ai; private readonly RaceUploadSettingsViewModel _raceUpload; private readonly VisualSettingsViewModel _visual; private readonly PicSettings _picSettings; private readonly IMapper _mapper; // ComboBox collections public List AvailableFonts { get; } public List VerticalPositions { get; } = new() { "Alto", "Centro", "Basso" }; public List HorizontalAlignments { get; } = new() { "Sinistra", "Centro", "Destra" }; [CLSCompliant(false)] public DataModel(ITestService testService, ISettingsService settingsService, ImageCreationService imageCreationService, IAiExtractionService aiExtractionService, IImageProcessingCoordinator imageProcessingCoordinator, PicSettings picSettings, IMapper mapper, ILogger logger, MaddoShared.IVersionProvider? versionProvider = null) { _service = testService; _logger = logger; _settingsService = settingsService; _imageCreationService = imageCreationService; _aiExtractionService = aiExtractionService; _imageProcessingCoordinator = imageProcessingCoordinator; _processing = new ProcessingStateViewModel(); _processing.PropertyChanged += OnProcessingPropertyChanged; _paths = new PathSettingsViewModel(); _paths.PropertyChanged += OnPathsPropertyChanged; _ai = new AiSettingsViewModel(); _ai.PropertyChanged += OnAiPropertyChanged; _raceUpload = new RaceUploadSettingsViewModel(); _raceUpload.PropertyChanged += OnRaceUploadPropertyChanged; _visual = new VisualSettingsViewModel(); _visual.PropertyChanged += OnVisualPropertyChanged; _picSettings = picSettings; _mapper = mapper; // Populate AppVersion from version provider when available AppVersion = versionProvider?.GetVersionString() ?? string.Empty; TestCommand = new RelayCommand(Test); AsyncTestCommand = new AsyncCommand(TestAsync); AsyncCancelOperationCommand = new AsyncCommand(CancelOperation); ProcessImagesCommand = new AsyncCommand(ProcessImages); SelectModelsFolderCommand = new RelayCommand(SelectModelsFolder); SelectCsvOutputCommand = new RelayCommand(SelectCsvOutput); StartAiCommand = new AsyncCommand(StartAiAsync); SelectSourceFolderCommand = new RelayCommand(SelectSourceFolder); SelectDestinationFolderCommand = new RelayCommand(SelectDestinationFolder); SelectLogoFileCommand = new RelayCommand(SelectLogoFile); SaveSettingsCommand = new RelayCommand(SaveSettings); LoadSettingsCommand = new RelayCommand(LoadSettings); SelectColorCommand = new RelayCommand(SelectColor); SelectTransparentColorCommand = new RelayCommand(SelectTransparentColor); // Load available fonts AvailableFonts = LoadAvailableFonts(); } private async Task StartAiAsync() { MainToken = new CancellationTokenSource(); try { await RunAiExtractionCoreAsync(MainToken.Token, useDestination: true, recursive: true).ConfigureAwait(false); } catch (OperationCanceledException) { // user cancelled } finally { MainToken = null; } } private async Task RunAiExtractionCoreAsync(CancellationToken token, bool useDestination = false, bool recursive = false) { var searchRoot = useDestination ? DestinationPath : SourcePath; if (string.IsNullOrWhiteSpace(searchRoot) || !System.IO.Directory.Exists(searchRoot)) { _logger.LogWarning("AI extraction path invalid: {Path}", searchRoot); return; } await InvokeOnUiThreadAsync(() => { PreviewResults.Clear(); AiProgress = 0; }).ConfigureAwait(false); await _aiExtractionService.RunAsync( new AiExtractionRequest { SearchRoot = searchRoot, Recursive = recursive, CsvOutputPath = CsvOutputPath }, token, result => InvokeOnUiThreadAsync(() => PreviewResults.Add(result)), progress => InvokeOnUiThreadAsync(() => AiProgress = progress)).ConfigureAwait(false); } /// /// Optional UI-thread invoker set by the active UI layer (WPF, Avalonia, etc.). /// When set, uses this delegate instead of the WPF dispatcher. /// public Action? UiInvoker { get; set; } private Task InvokeOnUiThreadAsync(Action action) { return Task.Run(() => { if (UiInvoker != null) UiInvoker(action); else System.Windows.Application.Current?.Dispatcher.Invoke(action); }); } public AiSettingsViewModel Ai => _ai; public RaceUploadSettingsViewModel RaceUpload => _raceUpload; // AI properties public bool ExtractNumbers { get => _ai.ExtractNumbers; set => _ai.ExtractNumbers = value; } public string ModelsFolderPath { get => _ai.ModelsFolderPath; set => _ai.ModelsFolderPath = value; } public string CsvOutputPath { get => _ai.CsvOutputPath; set => _ai.CsvOutputPath = value; } public string FaceExecutablePath { get => _ai.FaceExecutablePath; set => _ai.FaceExecutablePath = value; } public string FaceOutputFolderPath { get => _ai.FaceOutputFolderPath; set => _ai.FaceOutputFolderPath = value; } // Race upload settings public string ApiLogin { get => _raceUpload.ApiLogin; set => _raceUpload.ApiLogin = value; } public string ApiPassword { get => _raceUpload.ApiPassword; set => _raceUpload.ApiPassword = value; } public string ApiRaceDescription { get => _raceUpload.ApiRaceDescription; set => _raceUpload.ApiRaceDescription = value; } public string ApiRaceTypeId { get => _raceUpload.ApiRaceTypeId; set => _raceUpload.ApiRaceTypeId = value; } public DateTime ApiRaceStartDate { get => _raceUpload.ApiRaceStartDate; set => _raceUpload.ApiRaceStartDate = value; } public DateTime ApiRaceEndDate { get => _raceUpload.ApiRaceEndDate; set => _raceUpload.ApiRaceEndDate = value; } public string ApiPathBase { get => _raceUpload.ApiPathBase; set => _raceUpload.ApiPathBase = value; } public string ApiLocalita { get => _raceUpload.ApiLocalita; set => _raceUpload.ApiLocalita = value; } public int ApiEventoInLineaIndex { get => _raceUpload.ApiEventoInLineaIndex; set => _raceUpload.ApiEventoInLineaIndex = value; } public int ApiTipoIndexValue { get => _raceUpload.ApiTipoIndexValue; set => _raceUpload.ApiTipoIndexValue = value; } public int ApiFreeEventIndex { get => _raceUpload.ApiFreeEventIndex; set => _raceUpload.ApiFreeEventIndex = value; } public string ApiRaceId { get => _raceUpload.ApiRaceId; set => _raceUpload.ApiRaceId = value; } public string ApiRemoteProcessedBasePath { get => _raceUpload.ApiRemoteProcessedBasePath; set => _raceUpload.ApiRemoteProcessedBasePath = value; } // Preview results for DataGrid public System.Collections.ObjectModel.ObservableCollection PreviewResults => _ai.PreviewResults; public double AiProgress { get => _ai.AiProgress; set => _ai.AiProgress = value; } private List LoadAvailableFonts() { #if WINDOWS var fonts = new List(); using (var installedFonts = new InstalledFontCollection()) { fonts.AddRange(installedFonts.Families.Select(f => f.Name)); } return fonts; #else return new List(); #endif } private CancellationTokenSource? _mainToken; public CancellationTokenSource? MainToken { get => _mainToken; set { _mainToken = value; NotifyPropertyChanged(); } } public string SourcePath { get => _paths.SourcePath; set => _paths.SourcePath = value; } public string DestinationPath { get => _paths.DestinationPath; set => _paths.DestinationPath = value; } public string HorizontalText { get => _visual.HorizontalText; set => _visual.HorizontalText = value; } public string VerticalText { get => _visual.VerticalText; set => _visual.VerticalText = value; } public bool OverwriteImages { get => _visual.OverwriteImages; set => _visual.OverwriteImages = value; } private bool _uiEnabled = true; public bool UiEnabled { get => _uiEnabled; set { _uiEnabled = value; NotifyPropertyChanged(); NotifyPropertyChanged(nameof(UiDisabled)); } } public bool UiDisabled => !_uiEnabled; public ProcessingStateViewModel Processing => _processing; public PathSettingsViewModel Paths => _paths; public VisualSettingsViewModel Visual => _visual; private void OnProcessingPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (string.IsNullOrWhiteSpace(e.PropertyName)) { return; } // Keep existing DataModel bindings working while state lives in child viewmodel. NotifyPropertyChanged(e.PropertyName); } private void OnPathsPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (string.IsNullOrWhiteSpace(e.PropertyName)) { return; } NotifyPropertyChanged(e.PropertyName); } private void OnAiPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (string.IsNullOrWhiteSpace(e.PropertyName)) { return; } NotifyPropertyChanged(e.PropertyName); } private void OnRaceUploadPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (string.IsNullOrWhiteSpace(e.PropertyName)) { return; } NotifyPropertyChanged(e.PropertyName); } private void OnVisualPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (string.IsNullOrWhiteSpace(e.PropertyName)) { return; } NotifyPropertyChanged(e.PropertyName); } public string SpeedCounter { get => _processing.SpeedCounter; set => _processing.SpeedCounter = value; } private int _chunkSize; public int ChunkSize { get => _chunkSize; set { _chunkSize = value; NotifyPropertyChanged(); } } private int _threadsCount; public int ThreadsCount { get => _threadsCount; set { _threadsCount = value; NotifyPropertyChanged(); } } // Thumbnail settings public string ThumbnailPrefix { get => _visual.ThumbnailPrefix; set => _visual.ThumbnailPrefix = value; } public int ThumbnailHeight { get => _visual.ThumbnailHeight; set => _visual.ThumbnailHeight = value; } public int ThumbnailWidth { get => _visual.ThumbnailWidth; set => _visual.ThumbnailWidth = value; } // Big photo settings public int PhotoBigHeight { get => _visual.PhotoBigHeight; set => _visual.PhotoBigHeight = value; } public int PhotoBigWidth { get => _visual.PhotoBigWidth; set => _visual.PhotoBigWidth = value; } // Font settings public int FontSize { get => _visual.FontSize; set => _visual.FontSize = value; } public int FontSizeThumbnail { get => _visual.FontSizeThumbnail; set => _visual.FontSizeThumbnail = value; } public string FontName { get => _visual.FontName; set => _visual.FontName = value; } public bool FontBold { get => _visual.FontBold; set => _visual.FontBold = value; } // Text settings public int TextTransparency { get => _visual.TextTransparency; set => _visual.TextTransparency = value; } public int TextMargin { get => _visual.TextMargin; set => _visual.TextMargin = value; } public string TextColorRGB { get => _visual.TextColorRGB; set => _visual.TextColorRGB = value; } public string TransparentColor { get => _visual.TransparentColor; set => _visual.TransparentColor = value; } public bool UseTransparentColor { get => _visual.UseTransparentColor; set => _visual.UseTransparentColor = value; } // Logo/Watermark settings public string LogoFile { get => _visual.LogoFile; set => _visual.LogoFile = value; } public int LogoHeight { get => _visual.LogoHeight; set => _visual.LogoHeight = value; } public int LogoWidth { get => _visual.LogoWidth; set => _visual.LogoWidth = value; } public int LogoMargin { get => _visual.LogoMargin; set => _visual.LogoMargin = value; } public int LogoTransparency { get => _visual.LogoTransparency; set => _visual.LogoTransparency = value; } // Image library selection (UI radio buttons bind to the boolean helpers) private string _imageLibrary = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "System.Graphics" : "ImageSharp"; /// /// Whether the application is running on Windows. Used by cross-platform UIs to show/hide Windows-only options. /// public bool IsRunningOnWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); /// /// The selected image processing library. Possible values: "System.Graphics" or "ImageSharp". /// This value is mirrored into PicSettings.ImageCreatorProvider so the runtime mapper picks the implementation. /// public string ImageLibrary { get => _imageLibrary; set { if (_imageLibrary == value) return; _imageLibrary = value; // Reflect selection into PicSettings so mapper can resolve at runtime _picSettings.ImageCreatorProvider = string.Equals(value, "ImageSharp", StringComparison.OrdinalIgnoreCase) ? "ALTERNATE" : "Sharp"; NotifyPropertyChanged(); NotifyPropertyChanged(nameof(UseSystemGraphics)); NotifyPropertyChanged(nameof(UseImageSharp)); NotifyPropertyChanged(nameof(IsRunningOnWindows)); } } public bool UseSystemGraphics { get => string.Equals(ImageLibrary, "System.Graphics", StringComparison.OrdinalIgnoreCase); set { if (value) ImageLibrary = "System.Graphics"; NotifyPropertyChanged(); } } public bool UseImageSharp { get => string.Equals(ImageLibrary, "ImageSharp", StringComparison.OrdinalIgnoreCase); set { if (value) ImageLibrary = "ImageSharp"; NotifyPropertyChanged(); } } // Folder division settings private int _filesPerFolder = 99; public int FilesPerFolder { get => _filesPerFolder; set { _filesPerFolder = value; NotifyPropertyChanged(); } } private string _folderSuffix = ""; public string FolderSuffix { get => _folderSuffix; set { _folderSuffix = value; NotifyPropertyChanged(); } } private int _counterDigits = 2; public int CounterDigits { get => _counterDigits; set { _counterDigits = value; NotifyPropertyChanged(); } } // Vertical text settings public int VerticalTextSize { get => _visual.VerticalTextSize; set => _visual.VerticalTextSize = value; } public int VerticalTextMargin { get => _visual.VerticalTextMargin; set => _visual.VerticalTextMargin = value; } // JPEG compression settings public int JpegQuality { get => _visual.JpegQuality; set => _visual.JpegQuality = value; } public int JpegQualityThumbnail { get => _visual.JpegQualityThumbnail; set => _visual.JpegQualityThumbnail = value; } // CheckBox settings private bool _createThumbnails = true; public bool CreateThumbnails { get => _createThumbnails; set { _createThumbnails = value; NotifyPropertyChanged(); } } private bool _automaticRotation; public bool AutomaticRotation { get => _automaticRotation; set { _automaticRotation = value; NotifyPropertyChanged(); } } private bool _forceJpeg; public bool ForceJpeg { get => _forceJpeg; set { _forceJpeg = value; NotifyPropertyChanged(); } } private bool _updateSubdirectories; public bool UpdateSubdirectories { get => _updateSubdirectories; set { _updateSubdirectories = value; NotifyPropertyChanged(); } } private bool _createSubfolders; public bool CreateSubfolders { get => _createSubfolders; set { _createSubfolders = value; NotifyPropertyChanged(); } } private bool _addTime; public bool AddTime { get => _addTime; set { _addTime = value; NotifyPropertyChanged(); } } private bool _addRaceTime; public bool AddRaceTime { get => _addRaceTime; set { _addRaceTime = value; NotifyPropertyChanged(); } } public bool AddLogo { get => _visual.AddLogo; set => _visual.AddLogo = value; } public bool KeepOriginalDimensions { get => _visual.KeepOriginalDimensions; set => _visual.KeepOriginalDimensions = value; } private bool _showDate; public bool ShowDate { get => _showDate; set { _showDate = value; NotifyPropertyChanged(); } } private bool _showPhotoNumber; public bool ShowPhotoNumber { get => _showPhotoNumber; set { if (_showPhotoNumber == value) return; _showPhotoNumber = value; if (value) { // ensure mutually exclusive choices _addTimeToThumbnails = false; _addTextToThumbnails = false; _addNumberAndTimeToThumbnails = false; _addRaceTimeToThumbnails = false; NotifyPropertyChanged(nameof(AddTimeToThumbnails)); NotifyPropertyChanged(nameof(AddTextToThumbnails)); NotifyPropertyChanged(nameof(AddNumberAndTimeToThumbnails)); NotifyPropertyChanged(nameof(AddRaceTimeToThumbnails)); } NotifyPropertyChanged(); NotifyPropertyChanged(nameof(ThumbnailMode)); } } private bool _shutdownSystem; public bool ShutdownSystem { get => _shutdownSystem; set { _shutdownSystem = value; NotifyPropertyChanged(); } } // ComboBox position/alignment settings private string _verticalPosition = "Basso"; public string VerticalPosition { get => _verticalPosition; set { _verticalPosition = value; NotifyPropertyChanged(); } } private string _horizontalAlignment = "Centro"; public string HorizontalAlignment { get => _horizontalAlignment; set { _horizontalAlignment = value; NotifyPropertyChanged(); } } private string _logoHorizontalPosition = "Destra"; public string LogoHorizontalPosition { get => _logoHorizontalPosition; set { _logoHorizontalPosition = value; NotifyPropertyChanged(); } } private string _logoVerticalPosition = "Basso"; public string LogoVerticalPosition { get => _logoVerticalPosition; set { _logoVerticalPosition = value; NotifyPropertyChanged(); } } // RadioButton settings private bool _useProgressiveNumbering = true; public bool UseProgressiveNumbering { get => _useProgressiveNumbering; set { _useProgressiveNumbering = value; NotifyPropertyChanged(); } } private bool _useFileNumbering; public bool UseFileNumbering { get => _useFileNumbering; set { _useFileNumbering = value; NotifyPropertyChanged(); } } private bool _useParallelProcessing = true; public bool UseParallelProcessing { get => _useParallelProcessing; set { _useParallelProcessing = value; NotifyPropertyChanged(); } } private bool _useSequentialProcessing; public bool UseSequentialProcessing { get => _useSequentialProcessing; set { _useSequentialProcessing = value; NotifyPropertyChanged(); } } // Additional settings that were missing private bool _addTimeToThumbnails; public bool AddTimeToThumbnails { get => _thumbnailOption == ThumbnailOptionEnum.Time; set { if (value) { ThumbnailOption = ThumbnailOptionEnum.Time; } else if (_thumbnailOption == ThumbnailOptionEnum.Time) { ThumbnailOption = ThumbnailOptionEnum.None; } NotifyPropertyChanged(); } } private bool _showFileNameOnThumbnails; public bool ShowFileNameOnThumbnails { get => _thumbnailOption == ThumbnailOptionEnum.FileName; set { if (value) ThumbnailOption = ThumbnailOptionEnum.FileName; else if (_thumbnailOption == ThumbnailOptionEnum.FileName) ThumbnailOption = ThumbnailOptionEnum.None; NotifyPropertyChanged(); } } private DateTime _raceStartDate = DateTime.Now; public DateTime RaceStartDate { get => _raceStartDate; set { _raceStartDate = value; NotifyPropertyChanged(); } } private string _timeLabel = ""; public string TimeLabel { get => _timeLabel; set { _timeLabel = value; NotifyPropertyChanged(); } } private string _bigPhotoSuffix = ""; public string BigPhotoSuffix { get => _bigPhotoSuffix; set { _bigPhotoSuffix = value; NotifyPropertyChanged(); } } private bool _addTextToThumbnails; public bool AddTextToThumbnails { get => _thumbnailOption == ThumbnailOptionEnum.Text; set { if (value) ThumbnailOption = ThumbnailOptionEnum.Text; else if (_thumbnailOption == ThumbnailOptionEnum.Text) ThumbnailOption = ThumbnailOptionEnum.None; NotifyPropertyChanged(); } } private bool _addRaceTimeToThumbnails; public bool AddRaceTimeToThumbnails { get => _thumbnailOption == ThumbnailOptionEnum.RaceTime; set { if (value) ThumbnailOption = ThumbnailOptionEnum.RaceTime; else if (_thumbnailOption == ThumbnailOptionEnum.RaceTime) ThumbnailOption = ThumbnailOptionEnum.None; NotifyPropertyChanged(); } } private bool _addNumberAndTimeToThumbnails; public bool AddNumberAndTimeToThumbnails { get => _thumbnailOption == ThumbnailOptionEnum.FileNameAndTime; set { if (value) ThumbnailOption = ThumbnailOptionEnum.FileNameAndTime; else if (_thumbnailOption == ThumbnailOptionEnum.FileNameAndTime) ThumbnailOption = ThumbnailOptionEnum.None; NotifyPropertyChanged(); } } // New enum and authoritative property for thumbnail selection public enum ThumbnailOptionEnum { None = 0, Text = 1, FileName = 2, Time = 3, FileNameAndTime = 4, RaceTime = 5 } private ThumbnailOptionEnum _thumbnailOption = ThumbnailOptionEnum.None; // Name matches DTO property so SettingsService mapping works public ThumbnailOptionEnum ThumbnailOption { get => _thumbnailOption; set { if (_thumbnailOption == value) return; _thumbnailOption = value; // Notify all dependent properties so UI updates NotifyPropertyChanged(); NotifyPropertyChanged(nameof(AddTextToThumbnails)); NotifyPropertyChanged(nameof(AddTimeToThumbnails)); NotifyPropertyChanged(nameof(ShowPhotoNumber)); NotifyPropertyChanged(nameof(AddNumberAndTimeToThumbnails)); NotifyPropertyChanged(nameof(AddRaceTimeToThumbnails)); NotifyPropertyChanged(nameof(ShowFileNameOnThumbnails)); NotifyPropertyChanged(nameof(ThumbnailMode)); } } // Helper int property to bind ComboBox SelectedIndex in the WinForms designer public int ThumbnailOptionIndex { get => (int)ThumbnailOption; set { var opt = (ThumbnailOptionEnum)value; if (opt == ThumbnailOption) return; ThumbnailOption = opt; NotifyPropertyChanged(); } } // Single authoritative thumbnail mode string to avoid conflicting bindings // Possible values: "None", "Text", "Time", "Number", "NumberAndTime", "RaceTime" public string ThumbnailMode { get { return _thumbnailOption switch { ThumbnailOptionEnum.Text => "Text", ThumbnailOptionEnum.Time => "Time", ThumbnailOptionEnum.FileName => "Number", ThumbnailOptionEnum.FileNameAndTime => "NumberAndTime", ThumbnailOptionEnum.RaceTime => "RaceTime", _ => "None", }; } set { // Map incoming string to enum and set the authoritative property switch ((value ?? string.Empty).ToLowerInvariant()) { case "text": ThumbnailOption = ThumbnailOptionEnum.Text; break; case "time": ThumbnailOption = ThumbnailOptionEnum.Time; break; case "number": ThumbnailOption = ThumbnailOptionEnum.FileName; break; case "numberandtime": ThumbnailOption = ThumbnailOptionEnum.FileNameAndTime; break; case "racetime": ThumbnailOption = ThumbnailOptionEnum.RaceTime; break; default: ThumbnailOption = ThumbnailOptionEnum.None; break; } } } // Image processing progress and status public string ProcessingStatus { get => _processing.ProcessingStatus; set => _processing.ProcessingStatus = value; } public int ProcessedImagesCount { get => _processing.ProcessedImagesCount; set => _processing.ProcessedImagesCount = value; } public int TotalImagesCount { get => _processing.TotalImagesCount; set => _processing.TotalImagesCount = value; } public int ProgressBarValue { get => _processing.ProgressBarValue; set => _processing.ProgressBarValue = value; } public int ProgressBarMaximum { get => _processing.ProgressBarMaximum; set => _processing.ProgressBarMaximum = value; } private void Test(object parameter) { Debug.WriteLine("Yep"); this.UiEnabled = !this.UiEnabled; } private async Task TestAsync() { Debug.WriteLine("Yep c"); } private async Task ProcessImages() { _logger.LogInformation("Avvio elaborazione..."); UiEnabled = false; MainToken?.Dispose(); MainToken = new CancellationTokenSource(); var token = MainToken.Token; // Normalize paths _paths.NormalizePaths(); // Reset counters _processing.ResetForRun(); // Update PicSettings from DataModel using AutoMapper _mapper.Map(this, _picSettings); // Explicitly ensure thumbnail-related flags are applied to PicSettings // because AutoMapper may not map differently-named properties. try { _picSettings.AggiungiScritteMiniature = this.AddTextToThumbnails; _picSettings.UsaOrarioMiniatura = this.AddTimeToThumbnails; _picSettings.AggNumTempMin = this.AddNumberAndTimeToThumbnails; _picSettings.AggTempoGaraMin = this.AddRaceTimeToThumbnails; _picSettings.CreaMiniature = this.CreateThumbnails; _picSettings.LarghezzaSmall = this.ThumbnailWidth; _picSettings.AltezzaSmall = this.ThumbnailHeight; _picSettings.DimMin = this.FontSizeThumbnail; _picSettings.JpegQualityMin = this.JpegQualityThumbnail; } catch { // Best-effort; do not fail processing on mapping issues } var imageCreationOptions = new ImageCreationService.Options { AggiornaSottodirectory = UpdateSubdirectories, CreaSottocartelle = CreateSubfolders, FilePerCartella = FilesPerFolder, SuffissoCartelle = FolderSuffix, CifreContatore = CounterDigits, NumerazioneType = UseProgressiveNumbering ? NumerazioneType.Progressiva : NumerazioneType.Files, SourcePath = SourcePath, DestinationPath = DestinationPath, MaxThreads = ThreadsCount, ChunksSize = ChunkSize, LinearExecution = UseSequentialProcessing }; try { var runResult = await _imageProcessingCoordinator.RunAsync( new ImageProcessingRunRequest { Options = imageCreationOptions }, token, update => { ProcessedImagesCount = update.Processed; TotalImagesCount = update.Total; ProgressBarMaximum = update.Total; ProgressBarValue = update.Processed; ProcessingStatus = update.Status; }, speed => { SpeedCounter = speed; }).ConfigureAwait(false); // AI integration stub: if ExtractNumbers is enabled, simulate or invoke OCR processing if (ExtractNumbers) { try { await RunAiExtractionCoreAsync(token); } catch (OperationCanceledException) { _logger.LogInformation("AI extraction canceled"); } catch (Exception ex) { _logger.LogError(ex, "AI extraction failed"); } } SpeedCounter = runResult.FinalSpeedCounter; } catch (OperationCanceledException) { _logger.LogInformation("Operazione Cancellata"); } catch (Exception ex) { _logger.LogError(ex, "Errore durante l'elaborazione delle immagini"); ProcessingStatus = $"Errore: {ex.Message}"; } finally { MainToken?.Dispose(); MainToken = null; } ProcessingStatus = "Finito"; UiEnabled = true; } private async Task CancelOperation() { try { var tokenSource = MainToken; if (tokenSource is not null) { // Cancel synchronously and return to caller. Some CTSource implementations // may provide async helpers but cancelling is immediate. try { tokenSource.Cancel(); } catch (Exception ex) { _logger.LogWarning(ex, "Exception while cancelling token"); } } UiEnabled = true; } catch (Exception e) { _logger.LogError(e, "Error canceling the token"); _logger.LogInformation("Ignora questo errore"); } } // Note: These commands will trigger events that the View will handle to show dialogs // since dialogs require UI context public event EventHandler? SelectSourceFolderRequested; public event EventHandler? SelectDestinationFolderRequested; public event EventHandler? SelectLogoFileRequested; public event EventHandler? SelectModelsFolderRequested; public event EventHandler? SelectCsvOutputRequested; public event EventHandler? SaveSettingsRequested; public event EventHandler? LoadSettingsRequested; public event EventHandler? SelectColorRequested; // Request that the View shows a message to the user (message, caption, icon) #if WINDOWS public event EventHandler>? ShowMessageRequested; #else public event EventHandler>? ShowMessageRequested; #endif public event EventHandler? SelectTransparentColorRequested; private void SelectSourceFolder(object parameter) { SelectSourceFolderRequested?.Invoke(this, EventArgs.Empty); } private void SelectDestinationFolder(object parameter) { SelectDestinationFolderRequested?.Invoke(this, EventArgs.Empty); } private void SelectLogoFile(object parameter) { SelectLogoFileRequested?.Invoke(this, EventArgs.Empty); } private void SelectModelsFolder(object parameter) { SelectModelsFolderRequested?.Invoke(this, EventArgs.Empty); } private void SelectCsvOutput(object parameter) { SelectCsvOutputRequested?.Invoke(this, EventArgs.Empty); } private void SaveSettings(object parameter) { SaveSettingsRequested?.Invoke(this, string.Empty); } private void LoadSettings(object parameter) { LoadSettingsRequested?.Invoke(this, string.Empty); } private void SelectColor(object parameter) { SelectColorRequested?.Invoke(this, EventArgs.Empty); } private void SelectTransparentColor(object parameter) { SelectTransparentColorRequested?.Invoke(this, EventArgs.Empty); } public async Task SaveSettingsToFileAsync(string filePath) { await _settingsService.SaveSettingsAsync(filePath, this); } public async Task LoadSettingsFromFileAsync(string filePath) { await _settingsService.LoadSettingsAsync(filePath, this); } private string _appVersion = string.Empty; public string AppVersion { get => _appVersion; set { _appVersion = value; NotifyPropertyChanged(); } } } }