using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using CtrEditor.ObjetosSim; using CtrEditor.ObjetosSim.Extraccion_Datos; using System.Collections.ObjectModel; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Threading; // Add this line using System.Linq; using ClosedXML.Excel; using System.Diagnostics; namespace CtrEditor.PopUps { public class MatrixItem : ObservableObject { public string TagName { get; set; } private string columnName; public string ColumnName { get => columnName; set => SetProperty(ref columnName, value); } private int columnNumber; public int ColumnNumber { get => columnNumber; set => SetProperty(ref columnNumber, value); } public string Value { get; set; } public string Type { get; set; } public bool IsCloned { get; set; } public UniqueId Id { get; set; } public string Source_Image { get; set; } public int Copy_Number { get; set; } // Add this property } public partial class MatrixPreviewViewModel : ObservableObject { private Window _window; private MainViewModel _mainViewModel; private DataGrid _dataGrid; private readonly Services.LLMService _llmService; public MatrixPreviewViewModel() { _llmService = new Services.LLMService(); } [ObservableProperty] private ObservableCollection matrixItems; [ObservableProperty] private ObservableCollection> matrixRows; [ObservableProperty] private GridLength detailsHeight = new GridLength(150); [ObservableProperty] private double detailsHeightValue = 150; private ObservableCollection selectedImages; partial void OnDetailsHeightValueChanged(double value) { DetailsHeight = new GridLength(value); } public void Initialize(MainViewModel mainViewModel, Window window, ObservableCollection images = null) { _mainViewModel = mainViewModel; _window = window; _dataGrid = (_window as MatrixPreviewWindow)?.MatrixPreview; selectedImages = images ?? new ObservableCollection { mainViewModel.SelectedImage }; MatrixItems = new ObservableCollection(); MatrixRows = new ObservableCollection>(); AnalyzeMatrixMultiPage(); } private void UpdateMatrixPreview() { if (_dataGrid == null) return; if (MatrixRows == null) MatrixRows = new ObservableCollection>(); MatrixRows.Clear(); _dataGrid.Columns.Clear(); if (!MatrixItems.Any()) return; // Group items by source image and then by column number var itemsByImage = MatrixItems.GroupBy(x => x.Source_Image); // Ordenar items por número de columna var orderedItems = MatrixItems .GroupBy(x => x.ColumnNumber) .OrderBy(g => g.Key) .Select(g => g.First()) .ToList(); // Crear columnas foreach (var item in orderedItems) { var column = new DataGridTextColumn { Header = $"{item.ColumnNumber}:{item.ColumnName}", Binding = new Binding($"[{item.ColumnNumber}]"), Width = DataGridLength.Auto }; _dataGrid.Columns.Add(column); } // Process each image's data foreach (var imageGroup in itemsByImage) { // Calculate max rows needed for this image int maxRows = 1; var clonedItems = imageGroup.Where(x => x.IsCloned).ToList(); if (clonedItems.Any()) { maxRows = clonedItems.Max(x => x.Copy_Number) + 1; } // Create rows for this image for (int row = 0; row < maxRows; row++) { var rowData = new Dictionary(); // Add image identifier rowData["Image"] = imageGroup.Key; // Add fixed (non-cloned) values in all rows foreach (var item in imageGroup.Where(x => !x.IsCloned)) { if (item.ColumnNumber > 0) { rowData[item.ColumnNumber.ToString()] = item.Value ?? string.Empty; } } // Add cloned values only in their corresponding row foreach (var item in imageGroup.Where(x => x.IsCloned)) { if (item.ColumnNumber > 0 && item.Copy_Number == row) { rowData[item.ColumnNumber.ToString()] = item.Value ?? string.Empty; } } MatrixRows.Add(rowData); } } } private void UpdateColumnHeaders() { if (_dataGrid == null) return; foreach (var col in _dataGrid.Columns.Cast()) { if (col is DataGridTextColumn textCol) { var item = MatrixItems.FirstOrDefault(x => x.ColumnName == (textCol.Header?.ToString() ?? "").Split(':').Last()); if (item != null) { textCol.Header = $"{item.ColumnNumber}:{item.ColumnName}"; } } } } private async void AnalyzeMatrixMultiPage() { MatrixItems.Clear(); bool originalHasUnsavedChanges = _mainViewModel.HasUnsavedChanges; _mainViewModel.HasUnsavedChanges = false; try { foreach (var imageName in selectedImages) { // Store current image var currentImage = _mainViewModel.SelectedImage; // Change to target image and wait for UI update _mainViewModel.SelectedImage = imageName; await _mainViewModel.WaitForUIUpdateAsync(); // Analyze current page var items = AnalyzePage(); foreach (var item in items) { item.Source_Image = imageName; MatrixItems.Add(item); } // Restore original image if needed if (currentImage != imageName) { _mainViewModel.SelectedImage = currentImage; await _mainViewModel.WaitForUIUpdateAsync(); } } } finally { _mainViewModel.HasUnsavedChanges = originalHasUnsavedChanges; } ResolveColumnConflicts(); UpdateMatrixPreview(); } private List AnalyzePage() { var items = new List(); var osBuscarCoincidencias_List = _mainViewModel.ObjetosSimulables .OfType() .Where(tag => tag.Show_On_This_Page) .ToList(); var osExtraccionTagBaseGrouped_List = _mainViewModel.ObjetosSimulables .OfType() .Where(tag => tag.Show_On_This_Page && !tag.Cloned && tag.Id_Search_Templates != null && tag.Id_Search_Templates != "") .ToList(); var osExtraccionTagBaseFix_List = _mainViewModel.ObjetosSimulables .OfType() .Where(tag => tag.Show_On_This_Page && !tag.Cloned && (tag.Id_Search_Templates == null || tag.Id_Search_Templates == "")) .ToList(); var osExtraccionTagCloned_List = _mainViewModel.ObjetosSimulables .OfType() .Where(tag => tag.Show_On_This_Page && tag.Cloned) .ToList(); // Add fixed tags foreach (var tag in osExtraccionTagBaseFix_List) { tag.CaptureImageAreaAndDoOCR(); items.Add(new MatrixItem { TagName = tag.Nombre, ColumnName = tag.Collumn_name, ColumnNumber = tag.Collumn_number, Value = tag.Tag_extract, Type = "Fixed", IsCloned = false, Id = tag.Id }); } // Add grouped tags foreach (var tag in osExtraccionTagBaseGrouped_List) { tag.CaptureImageAreaAndDoOCR(); items.Add(new MatrixItem { TagName = tag.Nombre, ColumnName = tag.Collumn_name, ColumnNumber = tag.Collumn_number, Value = tag.Tag_extract, Type = $"Grouped ({tag.Id_Search_Templates})", IsCloned = false, Id = tag.Id }); } // Add cloned tags foreach (var tag in osExtraccionTagCloned_List) { tag.CaptureImageAreaAndDoOCR(); items.Add(new MatrixItem { TagName = tag.Nombre, ColumnName = tag.Collumn_name, ColumnNumber = tag.Collumn_number, Value = tag.Tag_extract, Type = "Cloned", IsCloned = true, Id = tag.Id, Copy_Number = tag.Copy_Number // Add this line }); } return items; } partial void OnMatrixItemsChanged(ObservableCollection value) { if (value != null) { foreach (var item in value) { item.PropertyChanged += (s, e) => { if (e.PropertyName == nameof(MatrixItem.ColumnNumber) || e.PropertyName == nameof(MatrixItem.ColumnName)) { UpdateColumnHeaders(); } }; } } UpdateMatrixPreview(); } [RelayCommand] private async void ApplyChanges() { // Resolver solo los conflictos reales, mantener el orden existente var conflicts = MatrixItems .GroupBy(x => x.ColumnNumber) .Where(g => g.Select(x => x.ColumnName).Distinct().Count() > 1) .Any(); if (conflicts) { ResolveColumnConflicts(); } // Validar números de columna var columnNumbers = MatrixItems.Select(x => x.ColumnNumber).OrderBy(x => x).ToList(); for (int i = 0; i < columnNumbers.Count; i++) { if (columnNumbers[i] <= 0) { MessageBox.Show("Column numbers must be greater than 0", "Error", MessageBoxButton.OK, MessageBoxImage.Error); return; } } bool originalHasUnsavedChanges = _mainViewModel.HasUnsavedChanges; _mainViewModel.HasUnsavedChanges = false; try { foreach (var imageName in selectedImages) { // Store current image var currentImage = _mainViewModel.SelectedImage; // Change to target image and wait for UI update _mainViewModel.SelectedImage = imageName; await Task.Yield(); Application.Current.Dispatcher.Invoke(() => { }, DispatcherPriority.ApplicationIdle); // Apply changes for current page var pageItems = MatrixItems.Where(x => x.Source_Image == imageName); foreach (var item in pageItems) { // Find the tag by Id instead of direct reference var tag = _mainViewModel.ObjetosSimulables .OfType() .FirstOrDefault(t => t.Id == item.Id); if (tag != null) { tag.Collumn_name = item.ColumnName; tag.Collumn_number = item.ColumnNumber; } } // Restore original image if needed if (currentImage != imageName) { _mainViewModel.SelectedImage = currentImage; await Task.Yield(); Application.Current.Dispatcher.Invoke(() => { }, DispatcherPriority.ApplicationIdle); } _mainViewModel.SaveStateObjetosSimulables(); } } finally { _mainViewModel.HasUnsavedChanges = originalHasUnsavedChanges; } _window.Close(); } [RelayCommand] private void Close() { _window.Close(); } [RelayCommand] private void RegenerateMatrix() { ResolveColumnConflicts(); UpdateMatrixPreview(); } private void ResolveColumnConflicts() { // Paso 1: Mantener el orden actual y resolver solo los conflictos reales var columnGroups = MatrixItems .GroupBy(x => x.ColumnNumber) .Where(g => g.Select(x => x.ColumnName).Distinct().Count() > 1) .ToList(); foreach (var group in columnGroups) { var nameGroups = group .GroupBy(x => x.ColumnName) .OrderByDescending(g => g.Count()) .ToList(); // El grupo más grande mantiene su número var largestGroup = nameGroups.First(); // Solo reasignar números para grupos que tengan conflicto for (int i = 1; i < nameGroups.Count; i++) { var newColumnNumber = GetNextAvailableColumnNumber(); foreach (var item in nameGroups[i]) { item.ColumnNumber = newColumnNumber; } } } // Paso 2: Asegurarse de que los números son consecutivos sin alterar el orden relativo var orderedItems = MatrixItems .OrderBy(x => x.ColumnNumber) .GroupBy(x => x.ColumnName) .ToList(); int currentNumber = 1; foreach (var group in orderedItems) { foreach (var item in group) { item.ColumnNumber = currentNumber; } currentNumber++; } } private int GetNextAvailableColumnNumber() { var usedNumbers = MatrixItems.Select(x => x.ColumnNumber).Distinct().OrderBy(x => x).ToList(); int nextNumber = 1; foreach (var number in usedNumbers) { if (number > nextNumber) return nextNumber; nextNumber = number + 1; } return nextNumber; } [RelayCommand] private void ExportToExcel() { var saveDialog = new Microsoft.Win32.SaveFileDialog { DefaultExt = ".xlsx", Filter = "Excel files (*.xlsx)|*.xlsx", InitialDirectory = EstadoPersistente.Instance.directorio, FileName = "MatrixExport.xlsx" }; if (saveDialog.ShowDialog() == true) { try { using (var workbook = new XLWorkbook()) { var worksheet = workbook.Worksheets.Add("Matrix"); // Write headers int colIndex = 1; worksheet.Cell(1, colIndex++).Value = "Image"; var columns = MatrixItems .GroupBy(x => x.ColumnNumber) .OrderBy(g => g.Key) .Select(g => g.First()) .ToList(); foreach (var col in columns) { worksheet.Cell(1, colIndex++).Value = col.ColumnName; } // Write data int rowIndex = 2; foreach (var row in MatrixRows) { colIndex = 1; worksheet.Cell(rowIndex, colIndex++).Value = row["Image"]; foreach (var col in columns) { var value = row.ContainsKey(col.ColumnNumber.ToString()) ? row[col.ColumnNumber.ToString()] : string.Empty; worksheet.Cell(rowIndex, colIndex++).Value = value; } rowIndex++; } // Format headers var headerRow = worksheet.Row(1); headerRow.Style.Font.Bold = true; headerRow.Style.Fill.BackgroundColor = XLColor.LightGray; headerRow.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center; // Auto-fit columns worksheet.Columns().AdjustToContents(); workbook.SaveAs(saveDialog.FileName); } MessageBox.Show("Export completed successfully", "Success", MessageBoxButton.OK, MessageBoxImage.Information); } catch (Exception ex) { MessageBox.Show($"Error exporting to Excel: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); } } } [RelayCommand] private async Task AICorrectMatrix() { try { if (_llmService == null) { MessageBox.Show("LLM Service not initialized", "Error", MessageBoxButton.OK, MessageBoxImage.Error); return; } var columnNames = MatrixItems .Select(x => x.ColumnName) .Distinct() .ToList(); var dialog = new ColumnSelectionDialog(columnNames); if (dialog.ShowDialog() != true) return; var sourceColumn = dialog.SelectedSourceColumn; var targetColumn = dialog.SelectedTargetColumn; var sourceLanguage = dialog.SelectedSourceLanguage; var targetLanguage = dialog.SelectedTargetLanguage; // Group items by image and process each row var itemsByImage = MatrixItems.GroupBy(x => x.Source_Image); int batchSize = 10; int processedCount = 0; foreach (var imageGroup in itemsByImage) { var textPairs = new List<(string Source, string Target)>(); var itemsToUpdate = new List<(MatrixItem SourceItem, MatrixItem TargetItem)>(); // Create pairs for each row var sourceItems = imageGroup .Where(x => x.ColumnName == sourceColumn) .OrderBy(x => x.Copy_Number); var targetItems = imageGroup .Where(x => x.ColumnName == targetColumn) .OrderBy(x => x.Copy_Number); foreach (var (source, target) in sourceItems.Zip(targetItems, (s, t) => (s, t))) { if (!string.IsNullOrWhiteSpace(source.Value) && !string.IsNullOrWhiteSpace(target.Value)) { textPairs.Add((source.Value, target.Value)); itemsToUpdate.Add((source, target)); } } // Process in batches for (int i = 0; i < textPairs.Count; i += batchSize) { var batchPairs = textPairs.Skip(i).Take(batchSize).ToList(); var batchItems = itemsToUpdate.Skip(i).Take(batchSize).ToList(); var correctedPairs = await _llmService.ProcessTextBatch(batchPairs, sourceLanguage, targetLanguage); // Update values for (int j = 0; j < correctedPairs.Count; j++) { batchItems[j].SourceItem.Value = correctedPairs[j].Source; batchItems[j].TargetItem.Value = correctedPairs[j].Target; processedCount += 2; } } } UpdateMatrixPreview(); MessageBox.Show($"AI Correction completed. Processed {processedCount} items.", "Success", MessageBoxButton.OK, MessageBoxImage.Information); } catch (Exception ex) { MessageBox.Show($"Error during AI correction: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); } } ~MatrixPreviewViewModel() { _llmService?.Dispose(); } } }