в моей программе есть всё
но я хочу поменять только mainwindow, любые вызовы других окон и функций не надо изменять
они уже существуют, поэтому изменять нужно ТОЛЬКО mainwindow и то что участвует в моем запросе

сейчас мой код при нажатии "сформировать файл", создает либо .txt либо .magnete объединенный файл и заносит его в таблицу объединенные файлы
однако если заново нажать кнопку сформировать файл, то таблица перезапишется

я хочу реализовать так:
я могу при нажатии "сформировать файл" выбрать из каких файлов либо txt либо magnete формировать объединенный файл (имеется ввиду что если загружено несколько txt файлов, я могу выбрать из каких именно формировать файл, аналогично для файлов magnete)
нужно сделать так чтобы каждый новый объединенный файл также записывался в таблицу, НО чтобы старый файл остался тоже, то есть можно сделать вкладки для всех новых сформированных файлов
также при нажатии "ГРафики" нужно реализовать окно выбора объединенного файла который будет использоваться для построения графиков, нужно всегда выбирать два файла, один из txt один из magnete

<!-- MainWindow.xaml -->
<Window x:Class="TxtProcessorApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Txt Processor" Height="900" Width="1400">
    <Window.Resources>
        <!-- Общий стиль для DataGrid -->
        <Style TargetType="DataGrid" x:Key="CommonDataGridStyle">
            <Setter Property="FontFamily" Value="Consolas"/>
            <Setter Property="FontSize" Value="12"/>
            <Setter Property="Margin" Value="5"/>
            <Setter Property="IsReadOnly" Value="True"/>
        </Style>
    </Window.Resources>
    <DockPanel Margin="10">
        <!-- Верхнее меню -->
        <Menu DockPanel.Dock="Top">
            <!-- Пункт меню "Проект" -->
            <MenuItem Header="_Проект">
                <MenuItem Header="_Новый проект" Click="NewProject_Click" />
                <Separator />
                <MenuItem Header="_Сохранить проект" Click="SaveProject_Click" />
                <MenuItem Header="_Загрузить проект" Click="LoadProject_Click" />
            </MenuItem>

            <!-- Пункт меню "Загрузить" -->
            <MenuItem Header="_Загрузить">
                <MenuItem Header="Загрузить .txt файлы" Click="LoadFiles_Click" Tag="txt" />
                <MenuItem Header="Загрузить .magnete файлы" Click="LoadFiles_Click" Tag="magnete" />
            </MenuItem>

            <!-- Пункт меню "Операции с файлами" -->
            <MenuItem Header="_Операции с файлами">
                <MenuItem Header="Сформировать файл" Click="CombineFiles_Click" />
                <MenuItem Header="Редактировать и Экспортировать" Click="EditAndExport_Click" IsEnabled="False" x:Name="EditExportButton" />
            </MenuItem>

            <!-- Пункт меню "Графики" -->
            <MenuItem Header="_Графики" Click="ShowGraphs_Click" />
        </Menu>

        <!-- Строка состояния -->
        <StatusBar DockPanel.Dock="Bottom">
            <StatusBarItem>
                <TextBlock x:Name="StatusTextBlock" Text="Готово"/>
            </StatusBarItem>
        </StatusBar>

        <!-- Основная область с данными -->
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="3*"/>
                <RowDefinition Height="2*"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="5"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>

            <!-- Левая область для файлов -->
            <GroupBox Header=".txt Файлы" Grid.Row="0" Grid.Column="0" Margin="0,10,5,10">
                <TabControl x:Name="FilesTabControl"/>
            </GroupBox>

            <!-- Разделитель -->
            <GridSplitter Grid.Row="0" Grid.Column="1" Width="5" HorizontalAlignment="Stretch" Background="Gray"/>

            <!-- Правая область для файлов -->
            <GroupBox Header=".magnete Файлы" Grid.Row="0" Grid.Column="2" Margin="5,10,0,10">
                <TabControl x:Name="MagneteFilesTabControl"/>
            </GroupBox>

            <!-- Нижняя область для объединённых данных -->
            <Grid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="5"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>

                <!-- Объединённые .txt данные -->
                <GroupBox Header="Объединённые .txt Файлы" Grid.Column="0" Margin="0,10,5,10">
                    <DataGrid x:Name="CombinedTxtDataGrid" 
                              AutoGenerateColumns="True" 
                              ItemsSource="{Binding CombinedTxtDataTable.DefaultView}" 
                              Style="{StaticResource CommonDataGridStyle}"/>
                </GroupBox>

                <!-- Разделитель -->
                <GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" Background="Gray"/>

                <!-- Объединённые .magnete данные -->
                <GroupBox Header="Объединённые .magnete Файлы" Grid.Column="2" Margin="5,10,0,10">
                    <DataGrid x:Name="CombinedMagneteDataGrid" 
                              AutoGenerateColumns="True" 
                              ItemsSource="{Binding CombinedMagneteDataTable.DefaultView}" 
                              Style="{StaticResource CommonDataGridStyle}"/>
                </GroupBox>
            </Grid>
        </Grid>
    </DockPanel>
</Window>


// MainWindow.xaml.cs
using Microsoft.Win32;
using Newtonsoft.Json;
using ProjNet.CoordinateSystems;
using ProjNet.CoordinateSystems.Transformations;
using System;
using System.Collections.Generic;
using System.ComponentModel; // For INotifyPropertyChanged
using System.Data;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;

namespace TxtProcessorApp
{
    /// <summary>
    /// Класс MainWindow.xaml.cs для приложения TxtProcessorApp
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        // Реализация интерфейса INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string name)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }

        // Словари для хранения данных из файлов
        private Dictionary<string, List<string[]>> txtFileData = new Dictionary<string, List<string[]>>();
        private Dictionary<string, List<string[]>> magneteFileData = new Dictionary<string, List<string[]>>();

        // Поля для объединенных DataTable
        private DataTable combinedTxtDataTable = new DataTable();
        private DataTable combinedMagneteDataTable = new DataTable();

        // Свойства для привязки
        public DataTable CombinedTxtDataTable
        {
            get => combinedTxtDataTable;
            set
            {
                combinedTxtDataTable = value;
                OnPropertyChanged(nameof(CombinedTxtDataTable));
            }
        }

        public DataTable CombinedMagneteDataTable
        {
            get => combinedMagneteDataTable;
            set
            {
                combinedMagneteDataTable = value;
                OnPropertyChanged(nameof(CombinedMagneteDataTable));
            }
        }

        /// <summary>
        /// Конструктор MainWindow
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = this; // Установка контекста данных

            // Инициализация столбцов для объединенных .txt данных
            InitializeCombinedTxtDataTable();

            // Инициализация столбцов для объединенных .magnete данных
            InitializeCombinedMagneteDataTable();
        }

        /// <summary>
        /// Инициализирует столбцы для объединенных .txt данных
        /// </summary>
        private void InitializeCombinedTxtDataTable()
        {
            CombinedTxtDataTable.Columns.Add("№", typeof(int));
            CombinedTxtDataTable.Columns.Add("BASE-1", typeof(string));
            CombinedTxtDataTable.Columns.Add("Noise", typeof(string));
            CombinedTxtDataTable.Columns.Add("Time", typeof(string));
            CombinedTxtDataTable.Columns.Add("Time UTC", typeof(string));
        }

        /// <summary>
        /// Инициализирует столбцы для объединенных .magnete данных
        /// </summary>
        private void InitializeCombinedMagneteDataTable()
        {
            CombinedMagneteDataTable.Columns.Add("DATA", typeof(string));
            CombinedMagneteDataTable.Columns.Add("DATA UTC", typeof(string));
            CombinedMagneteDataTable.Columns.Add("TIME", typeof(string));
            CombinedMagneteDataTable.Columns.Add("TIME UTC", typeof(string));
            CombinedMagneteDataTable.Columns.Add("Долгота WGS", typeof(string));
            CombinedMagneteDataTable.Columns.Add("Широта WGS", typeof(string));
            CombinedMagneteDataTable.Columns.Add("Долгота UTM", typeof(double));
            CombinedMagneteDataTable.Columns.Add("Широта UTM", typeof(double));
            CombinedMagneteDataTable.Columns.Add("№1", typeof(int));
            CombinedMagneteDataTable.Columns.Add("Альтитуда", typeof(string));
            CombinedMagneteDataTable.Columns.Add("№2", typeof(int));
            CombinedMagneteDataTable.Columns.Add("Shark", typeof(string));
        }

        #region Обработчики Загрузки Файлов

        /// <summary>
        /// Обработчик для загрузки .txt и .magnete файлов через меню
        /// </summary>
        private async void LoadFiles_Click(object sender, RoutedEventArgs e)
        {
            if (sender is MenuItem menuItem && menuItem.Tag is string fileType)
            {
                await LoadFilesAsync(fileType);
            }
        }

        /// <summary>
        /// Универсальный метод для загрузки файлов
        /// </summary>
        /// <param name="fileType">Тип файла: "txt" или "magnete"</param>
        private async Task LoadFilesAsync(string fileType)
        {
            string filter = fileType == "txt" ? "Текстовые файлы (*.txt)|*.txt" : "Magnete файлы (*.magnete)|*.magnete";
            OpenFileDialog ofd = new OpenFileDialog
            {
                Multiselect = true,
                Filter = filter
            };

            if (ofd.ShowDialog() == true)
            {
                foreach (var file in ofd.FileNames)
                {
                    await ProcessFileAsync(file, fileType);
                }

                // Обновление соответствующего TabControl
                if (fileType == "txt")
                {
                    PopulateTabControl(FilesTabControl, txtFileData, CreateTxtDataGrid);
                    CombinedTxtDataTable.Clear(); // Сброс объединенных данных
                }
                else
                {
                    PopulateTabControl(MagneteFilesTabControl, magneteFileData, CreateMagneteDataGrid);
                    CombinedMagneteDataTable.Clear(); // Сброс объединенных данных
                }

                // Обновление состояния кнопки "Редактировать и Экспортировать"
                EditExportButton.IsEnabled = false;

                // Обновление строки состояния
                StatusTextBlock.Text = $"Загружено {(fileType == "txt" ? txtFileData.Count : magneteFileData.Count)} {(fileType == "txt" ? ".txt" : ".magnete")} файлов.";
            }
        }

        /// <summary>
        /// Асинхронный метод для обработки загруженного файла
        /// </summary>
        private Task ProcessFileAsync(string filePath, string fileType)
        {
            return Task.Run(() =>
            {
                List<string[]> processedLines = new List<string[]>();

                try
                {
                    var lines = File.ReadAllLines(filePath);
                    if (fileType == "txt")
                    {
                        lines = lines.Skip(11).ToArray(); // Пропустить первые 11 строк
                    }

                    foreach (var line in lines)
                    {
                        if (string.IsNullOrWhiteSpace(line))
                            continue;

                        var columns = line.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

                        if (columns.Length == 0)
                            continue;

                        // Проверка первого столбца
                        if (columns[0].ToLower() == "nan" || string.IsNullOrWhiteSpace(columns[0]))
                            continue;

                        // Добавить столбцы в зависимости от типа файла
                        if (fileType == "txt")
                        {
                            // Для .txt пропустить первый столбец
                            processedLines.Add(columns.Skip(1).ToArray());
                        }
                        else
                        {
                            // Для .magnete добавить все столбцы
                            processedLines.Add(columns);
                        }
                    }

                    // Добавить обработанные данные в словарь
                    string fileName = Path.GetFileName(filePath);
                    string uniqueFileName = GetUniqueFileName(fileName, fileType == "txt" ? txtFileData : magneteFileData);
                    if (fileType == "txt")
                    {
                        lock (txtFileData)
                        {
                            txtFileData[uniqueFileName] = processedLines;
                        }
                    }
                    else
                    {
                        lock (magneteFileData)
                        {
                            magneteFileData[uniqueFileName] = processedLines;
                        }
                    }
                }
                catch (Exception ex)
                {
                    Dispatcher.Invoke(() =>
                        MessageBox.Show($"Ошибка при обработке файла {filePath}:\n{ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error));
                }
            });
        }

        /// <summary>
        /// Генерация уникального имени файла
        /// </summary>
        private string GetUniqueFileName(string fileName, Dictionary<string, List<string[]>> existingData)
        {
            string originalFileName = fileName;
            int duplicateCount = 1;
            while (existingData.ContainsKey(fileName))
            {
                fileName = $"{Path.GetFileNameWithoutExtension(originalFileName)}_{duplicateCount}{Path.GetExtension(originalFileName)}";
                duplicateCount++;
            }
            return fileName;
        }

        /// <summary>
        /// Метод для заполнения TabControl данными файлов
        /// </summary>
        private void PopulateTabControl(TabControl tabControl, Dictionary<string, List<string[]>> fileData, Func<List<string[]>, DataGrid> createGrid)
        {
            Dispatcher.Invoke(() =>
            {
                tabControl.Items.Clear();
                foreach (var kvp in fileData)
                {
                    TabItem tabItem = new TabItem
                    {
                        Header = kvp.Key,
                        Content = createGrid(kvp.Value)
                    };
                    tabControl.Items.Add(tabItem);
                }
            });
        }

        /// <summary>
        /// Создание DataGrid для .txt файлов
        /// </summary>
        private DataGrid CreateTxtDataGrid(List<string[]> data)
        {
            DataGrid dataGrid = new DataGrid
            {
                AutoGenerateColumns = true,
                ItemsSource = ConvertToDataTable(data, isTxt: true).DefaultView,
                IsReadOnly = true,
                FontFamily = new System.Windows.Media.FontFamily("Consolas"),
                FontSize = 12,
                Margin = new Thickness(5)
            };
            return dataGrid;
        }

        /// <summary>
        /// Создание DataGrid для .magnete файлов
        /// </summary>
        private DataGrid CreateMagneteDataGrid(List<string[]> data)
        {
            DataGrid dataGrid = new DataGrid
            {
                AutoGenerateColumns = true,
                ItemsSource = ConvertToDataTable(data, isTxt: false).DefaultView,
                IsReadOnly = true,
                FontFamily = new System.Windows.Media.FontFamily("Consolas"),
                FontSize = 12,
                Margin = new Thickness(5)
            };
            return dataGrid;
        }

        /// <summary>
        /// Универсальный метод для преобразования данных в DataTable
        /// </summary>
        private DataTable ConvertToDataTable(List<string[]> data, bool isTxt)
        {
            DataTable dt = new DataTable();
            if (data == null || data.Count == 0)
                return dt;

            if (isTxt)
            {
                dt.Columns.Add("№", typeof(int));
                dt.Columns.Add("BASE-1", typeof(string));
                dt.Columns.Add("Noise", typeof(string));
                dt.Columns.Add("Time", typeof(string));
                dt.Columns.Add("Time UTC", typeof(string));
            }
            else
            {
                // Определение столбцов для .magnete, предполагая фиксированное количество столбцов
                dt.Columns.Add("Time", typeof(string));
                dt.Columns.Add("Широта WGS", typeof(string));
                dt.Columns.Add("Долгота WGS", typeof(string));
                dt.Columns.Add("Альтитуда", typeof(string));
                dt.Columns.Add("Shark", typeof(string));
                // Добавьте другие столбцы при необходимости
            }

            foreach (var row in data)
            {
                DataRow dr = dt.NewRow();
                if (isTxt)
                {
                    dr["№"] = int.TryParse(row[0], out int number) ? number : 0;
                    dr["BASE-1"] = row.Length > 1 ? row[1] : string.Empty;
                    dr["Noise"] = row.Length > 2 ? row[2] : string.Empty;
                    dr["Time"] = row.Length > 3 ? row[3] : string.Empty;
                    dr["Time UTC"] = row.Length > 4 ? row[4] : string.Empty;
                }
                else
                {
                    dr["Time"] = row.Length > 0 ? row[0] : string.Empty;
                    dr["Широта WGS"] = row.Length > 1 ? row[1] : string.Empty;
                    dr["Долгота WGS"] = row.Length > 2 ? row[2] : string.Empty;
                    dr["Альтитуда"] = row.Length > 3 ? row[3] : string.Empty;
                    dr["Shark"] = row.Length > 4 ? row[4] : string.Empty;
                    // Заполните другие столбцы при необходимости
                }
                dt.Rows.Add(dr);
            }

            return dt;
        }

        #endregion

        #region Обработчики Объединения Файлов

        /// <summary>
        /// Обработчик для комбинирования файлов через меню
        /// </summary>
        private async void CombineFiles_Click(object sender, RoutedEventArgs e)
        {
            if (!txtFileData.Any() && !magneteFileData.Any())
            {
                MessageBox.Show("Нет загруженных файлов для объединения.", "Информация", MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            // Диалог для выбора типа файлов для объединения
            SelectEditTypeWindow selectWindow = new SelectEditTypeWindow("Выберите тип файлов для объединения:");
            selectWindow.Owner = this;

            if (selectWindow.ShowDialog() == true)
            {
                string selectedOption = selectWindow.SelectedOption;

                if (selectedOption == ".txt файлы" && txtFileData.Any())
                {
                    await CombineTxtFilesAsync();
                }
                else if (selectedOption == ".magnete файлы" && magneteFileData.Any())
                {
                    await CombineMagneteFilesAsync();
                }
                else
                {
                    MessageBox.Show("Выбранная опция недоступна или нет загруженных файлов данного типа.", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
        }

        /// <summary>
        /// Асинхронный метод для объединения .txt файлов
        /// </summary>
        private Task CombineTxtFilesAsync()
        {
            return Task.Run(() =>
            {
                List<string[]> combinedData = new List<string[]>();
                int lineNumber = 1;

                foreach (var file in txtFileData.Values)
                {
                    foreach (var row in file)
                    {
                        if (row.Length < 3)
                            continue;

                        string secondColumn = row[1];
                        string thirdColumn = row.Length > 2 ? row[2] : string.Empty;
                        string fourthColumn = row.Length > 3 ? row[3] : "0";

                        string timeString = "InvalidNumber";
                        if (double.TryParse(fourthColumn, NumberStyles.Any, CultureInfo.InvariantCulture, out double number))
                        {
                            double divided = number / 24;
                            TimeSpan time = TimeSpan.FromDays(divided);
                            timeString = time.ToString(@"hh\:mm\:ss");
                        }

                        combinedData.Add(new string[]
                        {
                            lineNumber.ToString(),
                            secondColumn,
                            thirdColumn,
                            fourthColumn,
                            timeString
                        });

                        lineNumber++;
                    }
                }

                // Преобразование списка в существующую DataTable
                Dispatcher.Invoke(() =>
                {
                    CombinedTxtDataTable.Clear(); // Очистка существующих данных
                    foreach (var row in combinedData)
                    {
                        CombinedTxtDataTable.Rows.Add(
                            int.Parse(row[0]),
                            row[1],
                            row[2],
                            row[3],
                            row[4]
                        );
                    }

                    // Включение кнопки "Редактировать и Экспортировать" при наличии данных
                    EditExportButton.IsEnabled = CombinedTxtDataTable.Rows.Count > 0 || CombinedMagneteDataTable.Rows.Count > 0;

                    // Обновление строки состояния
                    StatusTextBlock.Text = $"Объединены .txt файлы. Строк: {CombinedTxtDataTable.Rows.Count}.";
                });
            });
        }

        /// <summary>
        /// Асинхронный метод для объединения .magnete файлов
        /// </summary>
        private Task CombineMagneteFilesAsync()
        {
            return Task.Run(() =>
            {
                if (!magneteFileData.Any())
                {
                    Dispatcher.Invoke(() =>
                        MessageBox.Show("Нет загруженных .magnete файлов для объединения.", "Информация", MessageBoxButton.OK, MessageBoxImage.Information));
                    return;
                }

                DateTime fixedDate = new DateTime(1980, 1, 6, 0, 0, 0, DateTimeKind.Utc);
                int sequentialNumber = 1;
                List<string> errorMessages = new List<string>();
                List<DataRow> newRows = new List<DataRow>();

                foreach (var file in magneteFileData.Values)
                {
                    foreach (var row in file)
                    {
                        DataRow newRow = CombinedMagneteDataTable.NewRow();
                        newRow["DATA UTC"] = fixedDate.ToString("dd.MM.yyyy");
                        newRow["TIME"] = row.Length > 0 ? row[0].Trim() : string.Empty;
                        newRow["Широта WGS"] = row.Length > 1 ? row[1].Trim() : string.Empty;
                        newRow["Долгота WGS"] = row.Length > 2 ? row[2].Trim() : string.Empty;
                        newRow["№1"] = sequentialNumber;
                        newRow["№2"] = sequentialNumber;
                        newRow["Альтитуда"] = row.Length > 3 ? row[3].Trim() : string.Empty;
                        newRow["Shark"] = row.Length > 4 ? row[4].Trim() : string.Empty;
                        newRow["Долгота UTM"] = 0.0;
                        newRow["Широта UTM"] = 0.0;

                        string stanValue = row.Length > 0 ? row[0].Trim() : string.Empty;
                        string zeitValue = row.Length > 1 ? row[1].Trim() : string.Empty;
                        string noiseValue = row.Length > 2 ? row[2].Trim() : string.Empty;

                        double longitude, latitude;
                        bool validCoordinates = true;

                        if (!double.TryParse(noiseValue, NumberStyles.Any, CultureInfo.InvariantCulture, out longitude))
                        {
                            string error = $"Не удалось распарсить Долгота WGS='{noiseValue}' в строке: {string.Join("\t", row)}.";
                            errorMessages.Add(error);
                            LogCalculation(error);
                            validCoordinates = false;
                        }

                        if (!double.TryParse(zeitValue, NumberStyles.Any, CultureInfo.InvariantCulture, out latitude))
                        {
                            string error = $"Не удалось распарсить Широта WGS='{zeitValue}' в строке: {string.Join("\t", row)}.";
                            errorMessages.Add(error);
                            LogCalculation(error);
                            validCoordinates = false;
                        }

                        if (validCoordinates)
                        {
                            try
                            {
                                int utmZone = GetUTMZone(longitude);
                                var utmCoordinates = ConvertWGS84ToUTM(longitude, latitude, utmZone);
                                newRow["Долгота UTM"] = utmCoordinates.Easting;
                                newRow["Широта UTM"] = utmCoordinates.Northing;
                            }
                            catch (Exception ex)
                            {
                                string error = $"Ошибка при преобразовании координат (Долгота WGS='{noiseValue}', Широта WGS='{latitude}') в строке: {string.Join("\t", row)}. Текст ошибки: {ex.Message}.";
                                errorMessages.Add(error);
                                LogCalculation(error);
                            }
                        }

                        // Обработка столбца "Stan"
                        if (double.TryParse(stanValue, NumberStyles.Any, CultureInfo.InvariantCulture, out double stanSeconds))
                        {
                            if (stanSeconds < 0)
                            {
                                string warning = $"Отрицательное значение TIME='{stanSeconds}' в строке: {string.Join("\t", row)}. Установка даты как фиксированной.";
                                errorMessages.Add(warning);
                                LogCalculation(warning);

                                newRow["DATA"] = fixedDate.ToString("dd.MM.yyyy");
                                newRow["TIME UTC"] = fixedDate.ToString("HH:mm:ss.f");
                            }
                            else
                            {
                                try
                                {
                                    DateTime computedDate = fixedDate.AddSeconds(stanSeconds);
                                    newRow["DATA"] = computedDate.ToString("dd.MM.yyyy");

                                    TimeSpan offsetTime = TimeSpan.FromSeconds(18);
                                    TimeSpan computedTime = (computedDate.TimeOfDay >= offsetTime) ? computedDate.TimeOfDay - offsetTime : TimeSpan.Zero;
                                    double roundedSeconds = Math.Round(computedTime.TotalSeconds, 1, MidpointRounding.AwayFromZero);
                                    TimeSpan roundedTime = TimeSpan.FromSeconds(roundedSeconds);
                                    newRow["TIME UTC"] = roundedTime.ToString(@"hh\:mm\:ss\.f");
                                }
                                catch (Exception ex)
                                {
                                    string error = $"Ошибка при расчёте даты для TIME='{stanSeconds}' в строке: {string.Join("\t", row)}. Текст ошибки: {ex.Message}. Установка даты как фиксированной.";
                                    errorMessages.Add(error);
                                    LogCalculation(error);

                                    newRow["DATA"] = fixedDate.ToString("dd.MM.yyyy");
                                    newRow["TIME UTC"] = fixedDate.ToString("HH:mm:ss.f");
                                }
                            }
                        }
                        else
                        {
                            string error = $"Не удалось распарсить TIME='{stanValue}' в строке: {string.Join("\t", row)}. Установка даты как фиксированной.";
                            errorMessages.Add(error);
                            LogCalculation(error);

                            newRow["DATA"] = fixedDate.ToString("dd.MM.yyyy");
                            newRow["TIME UTC"] = fixedDate.ToString("HH:mm:ss.f");
                        }

                        newRows.Add(newRow);
                        sequentialNumber++;

                        // Логирование расчетов для каждой строки
                        string logMessage = $"TIME: '{stanValue}', ComputedDate: '{newRow["DATA"]}', ComputedTime: '{newRow["TIME UTC"]}'";
                        LogCalculation(logMessage);
                    }
                }

                Dispatcher.Invoke(() =>
                {
                    // Добавляем все новые строки в DataTable на UI-потоке
                    foreach (var newRow in newRows)
                    {
                        CombinedMagneteDataTable.Rows.Add(newRow);
                    }

                    // Включение кнопки "Редактировать и Экспортировать" при наличии данных
                    EditExportButton.IsEnabled = CombinedTxtDataTable.Rows.Count > 0 || CombinedMagneteDataTable.Rows.Count > 0;

                    // Обновление строки состояния
                    StatusTextBlock.Text = $"Объединены .magnete файлы. Строк: {CombinedMagneteDataTable.Rows.Count}.";

                    if (errorMessages.Any())
                    {
                        string allErrors = string.Join("\n", errorMessages);
                        LogCalculation($"Обнаружены следующие проблемы:\n{allErrors}");
                        MessageBox.Show($"Файлы .magnete объединены с ошибками. Подробности в файле логов.", "Внимание", MessageBoxButton.OK, MessageBoxImage.Warning);
                    }
                    else
                    {
                    }
                });
            });
        }

        #endregion

        #region Обработчики Редактирования и Экспорта

        /// <summary>
        /// Обработчик для редактирования и экспорта через меню
        /// </summary>
        private void EditAndExport_Click(object sender, RoutedEventArgs e)
        {
            if ((CombinedTxtDataTable == null || CombinedTxtDataTable.Rows.Count == 0) &&
                (CombinedMagneteDataTable == null || CombinedMagneteDataTable.Rows.Count == 0))
            {
                MessageBox.Show("Нет объединённых данных для редактирования.", "Информация", MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            // Создание и открытие диалогового окна для выбора типа данных для редактирования
            SelectEditTypeWindow selectWindow = new SelectEditTypeWindow("Выберите данные для редактирования:");
            selectWindow.Owner = this;

            if (selectWindow.ShowDialog() == true)
            {
                string selectedOption = selectWindow.SelectedOption;

                if (selectedOption == ".txt файлы" && CombinedTxtDataTable != null && CombinedTxtDataTable.Rows.Count > 0)
                {
                    OpenEditWindow(CombinedTxtDataTable, "Редактирование .txt данных");
                }
                else if (selectedOption == ".magnete файлы" && CombinedMagneteDataTable != null && CombinedMagneteDataTable.Rows.Count > 0)
                {
                    OpenEditWindow(CombinedMagneteDataTable, "Редактирование .magnete данных");
                }
                else
                {
                    MessageBox.Show("Выбранная опция недоступна или данные отсутствуют.", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
        }

        /// <summary>
        /// Метод для открытия окна редактирования
        /// </summary>
        private void OpenEditWindow(DataTable dataTable, string windowTitle)
        {
            EditWindow editWindow = new EditWindow(dataTable)
            {
                Title = windowTitle,
                Owner = this
            };
            editWindow.ShowDialog();

            // После редактирования данные уже изменены в DataTable и отобразятся в DataGrid благодаря привязке
        }

        #endregion

        #region Обработчики Сохранения и Загрузки Проекта

        /// <summary>
        /// Обработчик для сохранения проекта через меню
        /// </summary>
        private async void SaveProject_Click(object sender, RoutedEventArgs e)
        {
            SaveFileDialog saveFileDialog = new SaveFileDialog
            {
                Filter = "Magnte Project Files (*.magn)|*.magn",
                DefaultExt = "magn",
                FileName = "project.magn"
            };

            if (saveFileDialog.ShowDialog() == true)
            {
                ProjectData project = new ProjectData
                {
                    TxtFileData = this.txtFileData,
                    MagneteFileData = this.magneteFileData,
                    CombinedTxtDataTable = this.CombinedTxtDataTable,
                    CombinedMagneteDataTable = this.CombinedMagneteDataTable
                };

                try
                {
                    // Асинхронная сериализация объекта в JSON
                    string json = await Task.Run(() => JsonConvert.SerializeObject(project, Formatting.Indented, new JsonSerializerSettings
                    {
                        PreserveReferencesHandling = PreserveReferencesHandling.Objects,
                        ReferenceLoopHandling = ReferenceLoopHandling.Ignore
                    }));
                    await File.WriteAllTextAsync(saveFileDialog.FileName, json, System.Text.Encoding.UTF8);
                    StatusTextBlock.Text = $"Проект сохранён: {saveFileDialog.FileName}";
                    MessageBox.Show("Проект успешно сохранён.", "Успех", MessageBoxButton.OK, MessageBoxImage.Information);
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"Ошибка при сохранении проекта:\n{ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
        }

        /// <summary>
        /// Обработчик для загрузки проекта через меню
        /// </summary>
        private async void LoadProject_Click(object sender, RoutedEventArgs e)
        {
            OpenFileDialog openFileDialog = new OpenFileDialog
            {
                Filter = "Magnte Project Files (*.magn)|*.magn",
                DefaultExt = "magn"
            };

            if (openFileDialog.ShowDialog() == true)
            {
                try
                {
                    string json = await File.ReadAllTextAsync(openFileDialog.FileName, System.Text.Encoding.UTF8);
                    ProjectData project = JsonConvert.DeserializeObject<ProjectData>(json);

                    if (project != null)
                    {
                        // Загрузка данных .txt
                        this.txtFileData = project.TxtFileData ?? new Dictionary<string, List<string[]>>();
                        PopulateTabControl(FilesTabControl, txtFileData, CreateTxtDataGrid);

                        // Загрузка данных .magnete
                        this.magneteFileData = project.MagneteFileData ?? new Dictionary<string, List<string[]>>();
                        PopulateTabControl(MagneteFilesTabControl, magneteFileData, CreateMagneteDataGrid);

                        // Загрузка объединенных DataTable
                        CombinedTxtDataTable.Clear();
                        foreach (DataRow row in project.CombinedTxtDataTable.Rows)
                        {
                            CombinedTxtDataTable.ImportRow(row);
                        }

                        CombinedMagneteDataTable.Clear();
                        foreach (DataRow row in project.CombinedMagneteDataTable.Rows)
                        {
                            CombinedMagneteDataTable.ImportRow(row);
                        }

                        // Применяем AcceptChanges, чтобы очистить статус изменений
                        CombinedTxtDataTable.AcceptChanges();
                        CombinedMagneteDataTable.AcceptChanges();

                        // Обновляем состояние кнопки "Редактировать и Экспортировать"
                        EditExportButton.IsEnabled = CombinedTxtDataTable.Rows.Count > 0 || CombinedMagneteDataTable.Rows.Count > 0;

                        // Обновление строки состояния
                        StatusTextBlock.Text = $"Проект загружен: {openFileDialog.FileName}";
                        MessageBox.Show("Проект успешно загружен.", "Успех", MessageBoxButton.OK, MessageBoxImage.Information);
                    }
                    else
                    {
                        MessageBox.Show("Не удалось загрузить данные проекта.", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"Ошибка при загрузке проекта:\n{ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
        }

        #endregion

        #region Обработчики Для Графиков

        /// <summary>
        /// Обработчик для отображения графиков через меню
        /// </summary>
        private void ShowGraphs_Click(object sender, RoutedEventArgs e)
        {
            if ((CombinedTxtDataTable == null || CombinedTxtDataTable.Rows.Count == 0) &&
                (CombinedMagneteDataTable == null || CombinedMagneteDataTable.Rows.Count == 0))
            {
                MessageBox.Show("Нет объединённых данных для построения графиков.", "Информация", MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            // Создаем копии DataTable для передачи в GraphWindow
            DataTable txtDataCopy = CombinedTxtDataTable.Copy();
            DataTable magneteDataCopy = CombinedMagneteDataTable.Copy();

            GraphWindow graphWindow = new GraphWindow(txtDataCopy, magneteDataCopy)
            {
                Owner = this
            };
            bool? dialogResult = graphWindow.ShowDialog();

            if (dialogResult == true)
            {
                // Пользователь нажал "Сохранить", применяем изменения
                // Обновляем Combined DataTables с копиями из GraphWindow
                CombinedTxtDataTable = graphWindow.TxtDataCopy;
                CombinedMagneteDataTable = graphWindow.MagneteDataCopy;

                // Применяем изменения
                CombinedTxtDataTable.AcceptChanges();
                CombinedMagneteDataTable.AcceptChanges();

                // Обновляем привязки
                OnPropertyChanged(nameof(CombinedTxtDataTable));
                OnPropertyChanged(nameof(CombinedMagneteDataTable));

                // Обновление строки состояния
                StatusTextBlock.Text = $"Данные графиков обновлены после сохранения изменений.";
            }
            else
            {
                // Пользователь отменил изменения
                StatusTextBlock.Text = $"Изменения на графиках были отменены.";
            }
        }

        #endregion

        #region Обработчики Меню

        /// <summary>
        /// Обработчик для выхода из приложения через меню
        /// </summary>
        private void Exit_Click(object sender, RoutedEventArgs e)
        {
            this.Close();
        }

        /// <summary>
        /// Обработчик для пункта "О программе" через меню
        /// </summary>
        private void About_Click(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("Txt Processor App\nВерсия 1.0\n© 2023 Ваша Компания", "О программе", MessageBoxButton.OK, MessageBoxImage.Information);
        }

        #endregion

        #region Модели Данных

        /// <summary>
        /// Класс для хранения данных проекта
        /// </summary>
        public class ProjectData
        {
            public Dictionary<string, List<string[]>> TxtFileData { get; set; }
            public Dictionary<string, List<string[]>> MagneteFileData { get; set; }
            public DataTable CombinedTxtDataTable { get; set; }
            public DataTable CombinedMagneteDataTable { get; set; }
        }

        #endregion

        #region Helper Methods для Преобразований Координат

        /// <summary>
        /// Метод для логирования расчетов и ошибок
        /// </summary>
        private void LogCalculation(string message, string logFileName = "calculation_log.txt")
        {
            string logFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, logFileName);
            try
            {
                File.AppendAllText(logFilePath, $"{DateTime.Now}: {message}{Environment.NewLine}");
            }
            catch (Exception ex)
            {
                Dispatcher.Invoke(() =>
                    MessageBox.Show($"Не удалось записать лог: {ex.Message}", "Ошибка логирования", MessageBoxButton.OK, MessageBoxImage.Error));
            }
        }

        /// <summary>
        /// Метод для определения зоны UTM на основе долготы
        /// </summary>
        private int GetUTMZone(double longitude)
        {
            if (longitude < -180 || longitude > 180)
                throw new ArgumentOutOfRangeException("Долгота должна быть в диапазоне от -180 до 180 градусов.");

            int zone = (int)Math.Floor((longitude + 180) / 6) + 1;
            return zone;
        }

        /// <summary>
        /// Метод для преобразования координат WGS84 в UTM
        /// </summary>
        private (double Easting, double Northing) ConvertWGS84ToUTM(double longitude, double latitude, int utmZone)
        {
            var wgs84 = GeographicCoordinateSystem.WGS84;
            var utmCS = ProjectedCoordinateSystem.WGS84_UTM(utmZone, latitude >= 0);
            var transformFactory = new CoordinateTransformationFactory();
            var transformation = transformFactory.CreateFromCoordinateSystems(wgs84, utmCS);
            double[] fromPoint = new double[] { longitude, latitude };
            double[] toPoint = transformation.MathTransform.Transform(fromPoint);
            return (toPoint[0], toPoint[1]);
        }

        #endregion

        #region Новый Обработчик для "Новый проект"

        /// <summary>
        /// Обработчик для создания нового пустого проекта
        /// </summary>
        private void NewProject_Click(object sender, RoutedEventArgs e)
        {
            var result = MessageBox.Show("Вы уверены, что хотите создать новый проект? Все несохраненные данные будут потеряны.", "Новый проект", MessageBoxButton.YesNo, MessageBoxImage.Question);
            if (result == MessageBoxResult.Yes)
            {
                // Очистка словарей с данными файлов
                txtFileData.Clear();
                magneteFileData.Clear();

                // Очистка TabControl для .txt файлов
                FilesTabControl.Items.Clear();

                // Очистка TabControl для .magnete файлов
                MagneteFilesTabControl.Items.Clear();

                // Очистка объединенных DataTable
                CombinedTxtDataTable.Clear();
                CombinedMagneteDataTable.Clear();

                // Сброс AcceptChanges
                CombinedTxtDataTable.AcceptChanges();
                CombinedMagneteDataTable.AcceptChanges();

                // Отключение кнопки "Редактировать и Экспортировать"
                EditExportButton.IsEnabled = false;

                // Обновление строки состояния
                StatusTextBlock.Text = "Создан новый проект.";
            }
        }

        #endregion
    }
}


<!-- GraphWindow.xaml -->
<Window x:Class="TxtProcessorApp.GraphWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:oxy="http://oxyplot.org/wpf"
        Title="GraphWindow" Height="800" Width="1000">
    <DockPanel>
        <!-- Верхнее меню -->
        <Menu DockPanel.Dock="Top">
            <MenuItem Header="Действия">
                <MenuItem Header="Сбросить масштаб" Click="ResetZoomMenuItem_Click"/>
                <MenuItem Header="Сбросить масштаб по Y" Click="ResetYZoomMenuItem_Click"/>
                <MenuItem Header="Сбросить масштаб по X" Click="ResetXZoomMenuItem_Click"/>
            </MenuItem>
        </Menu>

        <!-- Панель управления -->
        <StackPanel Orientation="Horizontal" DockPanel.Dock="Top" Margin="10">
            <Label Content="Выберите графики:" VerticalAlignment="Center" Margin="0,0,5,0"/>
            <!-- Используем ListBox с возможностью множественного выбора -->
            <ListBox x:Name="ChartSelector" Width="300" SelectionMode="Multiple" SelectionChanged="ChartSelector_SelectionChanged">
                <ListBoxItem Content=".txt Time UTC vs BASE-1"/>
                <ListBoxItem Content=".txt Time UTC vs Noise"/>
                <ListBoxItem Content=".magnete Долгота UTM vs Широта UTM (Альтитуда)"/>
                <ListBoxItem Content=".magnete Долгота UTM vs Широта UTM (Shark)"/>
                <ListBoxItem Content=".magnete Time UTC vs Shark"/>
            </ListBox>
            <Button x:Name="UndoButton" Content="Отменить" Width="100" Click="UndoButton_Click" IsEnabled="False" Margin="10,0,0,0"/>
            <Button x:Name="RedoButton" Content="Повторить" Width="100" Click="RedoButton_Click" IsEnabled="False" Margin="5,0,0,0"/>
            <Button Content="Сохранить" Width="100" Click="SaveButton_Click" Margin="10,0,0,0"/>
            <Button Content="Отмена" Width="100" Click="CancelButton_Click" Margin="5,0,0,0"/>
            <!-- Добавляем ToggleButton для переключения режима -->
            <ToggleButton x:Name="EditModeToggleButton" Content="Режим редактирования" Width="150" Margin="10,0,0,0" Checked="EditModeToggleButton_Checked" Unchecked="EditModeToggleButton_Unchecked"/>
        </StackPanel>

        <!-- Область для отображения графиков -->
        <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
            <!-- Используем UniformGrid для размещения графиков -->
            <UniformGrid x:Name="PlotGrid" Margin="10"/>
        </ScrollViewer>
    </DockPanel>
</Window>

// GraphWindow.xaml.cs
using OxyPlot;
using OxyPlot.Axes;
using OxyPlot.Series;
using OxyPlot.Annotations;
using OxyPlot.Wpf;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace TxtProcessorApp
{
    /// <summary>
    /// Окно для отображения и взаимодействия с графиками.
    /// </summary>
    public partial class GraphWindow : Window
    {
        // Словарь для хранения фабрик PlotInfo
        private readonly Dictionary<string, Func<PlotInfo>> plotModelFactories = new();

        // Хранит текущие отображаемые графики
        private List<PlotInfo> currentPlotInfos = new();

        private readonly Stack<IUndoRedoAction> undoStack = new();
        private readonly Stack<IUndoRedoAction> redoStack = new();

        private ScreenPoint? selectionStartPoint;
        private RectangleAnnotation selectionRectangle;

        private readonly List<(ScatterSeries Series, ScatterPoint Point, int Index, DataRow DataRow, List<DataRow> DataRows)> selectedPoints = new();

        private readonly Dictionary<PlotModel, List<PointAnnotation>> selectedPointAnnotations = new();

        private readonly DataTable txtDataCopy;
        private readonly DataTable magneteDataCopy;

        public DataTable TxtDataCopy => txtDataCopy;
        public DataTable MagneteDataCopy => magneteDataCopy;

        public GraphWindow(DataTable txtData, DataTable magneteData)
        {
            InitializeComponent();

            txtDataCopy = txtData.Copy();
            magneteDataCopy = magneteData.Copy();

            InitializePlotModelFactories();

            ChartSelector.SelectionChanged -= ChartSelector_SelectionChanged;
            ChartSelector.SelectedItems.Add(ChartSelector.Items[0]);
            ChartSelector.SelectedItems.Add(ChartSelector.Items[1]);
            ChartSelector.SelectionChanged += ChartSelector_SelectionChanged;

            UpdatePlots();
            UpdateUndoRedoButtons();

            Loaded += (s, e) => Keyboard.Focus(this);
        }

        /// <summary>
        /// Инициализирует фабрики для создания графиков.
        /// </summary>
        private void InitializePlotModelFactories()
        {
            plotModelFactories[".txt Time UTC vs BASE-1"] = () => CreateTxtPlotModel("BASE-1");
            plotModelFactories[".txt Time UTC vs Noise"] = () => CreateTxtPlotModel("Noise");
            plotModelFactories[".magnete Долгота UTM vs Широта UTM (Альтитуда)"] = () => CreateMagnetePlotModel("Альтитуда");
            plotModelFactories[".magnete Долгота UTM vs Широта UTM (Shark)"] = () => CreateMagnetePlotModel("Shark");
            plotModelFactories[".magnete Time UTC vs Shark"] = CreateMagneteTimeSharkPlotModel;
        }

        /// <summary>
        /// Класс для хранения информации о графике.
        /// </summary>
        private class PlotInfo
        {
            public PlotModel Model { get; set; }
            public Dictionary<ScatterSeries, List<DataRow>> SeriesDataRows { get; set; } = new();
        }

        private PlotInfo CreateTxtPlotModel(string valueColumn)
        {
            string title = $".txt Time UTC vs {valueColumn}";
            var model = new PlotModel { Title = title };
            var xAxis = new DateTimeAxis
            {
                Position = AxisPosition.Bottom,
                StringFormat = "HH:mm:ss",
                Title = "Time UTC",
                IntervalType = DateTimeIntervalType.Hours,
                MinorIntervalType = DateTimeIntervalType.Minutes
            };
            var yAxis = new LinearAxis
            {
                Position = AxisPosition.Left,
                Title = valueColumn
            };
            model.Axes.Add(xAxis);
            model.Axes.Add(yAxis);

            var series = new ScatterSeries
            {
                Title = valueColumn,
                MarkerType = MarkerType.Circle,
                MarkerSize = 5,
                TrackerFormatString = $"Time UTC: {{2:HH:mm:ss}}\n{valueColumn}: {{4}}"
            };

            var dataRows = new List<DataRow>();

            foreach (DataRow row in txtDataCopy.Rows)
            {
                if (row.RowState == DataRowState.Deleted)
                    continue;

                if (DateTime.TryParse(row["Time UTC"].ToString(), out DateTime timeUtc) &&
                    double.TryParse(row[valueColumn].ToString(), out double value))
                {
                    double timeInDouble = DateTimeAxis.ToDouble(timeUtc);
                    series.Points.Add(new ScatterPoint(timeInDouble, value));
                    dataRows.Add(row);
                }
            }

            model.Series.Add(series);

            var plotInfo = new PlotInfo { Model = model };
            plotInfo.SeriesDataRows[series] = dataRows;
            return plotInfo;
        }

        private PlotInfo CreateMagnetePlotModel(string valueColumn)
        {
            string title = $".magnete Долгота UTM vs Широта UTM ({valueColumn})";
            var model = new PlotModel { Title = title };
            var xAxis = new LinearAxis
            {
                Position = AxisPosition.Bottom,
                Title = "Долгота UTM"
            };
            var yAxis = new LinearAxis
            {
                Position = AxisPosition.Left,
                Title = "Широта UTM"
            };
            model.Axes.Add(xAxis);
            model.Axes.Add(yAxis);

            var colorAxis = new LinearColorAxis
            {
                Position = AxisPosition.Right,
                Palette = OxyPalettes.Jet(500),
                Title = valueColumn
            };
            model.Axes.Add(colorAxis);

            var series = new ScatterSeries
            {
                Title = valueColumn,
                MarkerType = MarkerType.Circle,
                MarkerSize = 5,
                TrackerFormatString = $"Долгота UTM: {{2}}\nШирота UTM: {{4}}\n{valueColumn}: {{6}}"
            };

            var dataRows = new List<DataRow>();

            foreach (DataRow row in magneteDataCopy.Rows)
            {
                if (row.RowState == DataRowState.Deleted)
                    continue;

                if (double.TryParse(row["Долгота UTM"].ToString(), out double longitudeUTM) &&
                    double.TryParse(row["Широта UTM"].ToString(), out double latitudeUTM) &&
                    double.TryParse(row[valueColumn].ToString(), out double value))
                {
                    series.Points.Add(new ScatterPoint(longitudeUTM, latitudeUTM, 5, value));
                    dataRows.Add(row);
                }
            }

            model.Series.Add(series);

            var plotInfo = new PlotInfo { Model = model };
            plotInfo.SeriesDataRows[series] = dataRows;

            SetEqualAxisScaling(xAxis, yAxis, dataRows, "Долгота UTM", "Широта UTM");

            return plotInfo;
        }

        private PlotInfo CreateMagneteTimeSharkPlotModel()
        {
            string title = ".magnete Time UTC vs Shark";
            var model = new PlotModel { Title = title };
            var xAxis = new DateTimeAxis
            {
                Position = AxisPosition.Bottom,
                StringFormat = "HH:mm:ss",
                Title = "Time UTC",
                IntervalType = DateTimeIntervalType.Hours,
                MinorIntervalType = DateTimeIntervalType.Minutes
            };
            var yAxis = new LinearAxis
            {
                Position = AxisPosition.Left,
                Title = "Shark"
            };
            model.Axes.Add(xAxis);
            model.Axes.Add(yAxis);

            var series = new ScatterSeries
            {
                Title = "Shark",
                MarkerType = MarkerType.Circle,
                MarkerSize = 5,
                TrackerFormatString = $"Time UTC: {{2:HH:mm:ss}}\nShark: {{4}}"
            };

            var dataRows = new List<DataRow>();

            foreach (DataRow row in magneteDataCopy.Rows)
            {
                if (row.RowState == DataRowState.Deleted)
                    continue;

                if (DateTime.TryParse(row["TIME UTC"].ToString(), out DateTime timeUtc) &&
                    double.TryParse(row["Shark"].ToString(), out double shark))
                {
                    double timeInDouble = DateTimeAxis.ToDouble(timeUtc);
                    series.Points.Add(new ScatterPoint(timeInDouble, shark));
                    dataRows.Add(row);
                }
            }

            model.Series.Add(series);

            var plotInfo = new PlotInfo { Model = model };
            plotInfo.SeriesDataRows[series] = dataRows;
            return plotInfo;
        }

        private void SetEqualAxisScaling(LinearAxis xAxis, LinearAxis yAxis, IEnumerable<DataRow> dataRows, string xColumn, string yColumn)
        {
            try
            {
                double minX = dataRows.Min(row => Convert.ToDouble(row[xColumn]));
                double maxX = dataRows.Max(row => Convert.ToDouble(row[xColumn]));
                double minY = dataRows.Min(row => Convert.ToDouble(row[yColumn]));
                double maxY = dataRows.Max(row => Convert.ToDouble(row[yColumn]));

                double maxRange = Math.Max(maxX - minX, maxY - minY);
                double centerX = (minX + maxX) / 2;
                double centerY = (minY + maxY) / 2;

                xAxis.Minimum = centerX - maxRange / 2;
                xAxis.Maximum = centerX + maxRange / 2;
                yAxis.Minimum = centerY - maxRange / 2;
                yAxis.Maximum = centerY + maxRange / 2;

                xAxis.MajorStep = yAxis.MajorStep = maxRange / 10;
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Ошибка при установке масштаба осей: {ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        private void ChartSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            UpdatePlots();
        }

        private void UpdatePlots()
        {
            PlotGrid.Children.Clear();
            currentPlotInfos.Clear();
            selectedPointAnnotations.Clear();
            selectedPoints.Clear();

            var selectedTitles = ChartSelector.SelectedItems.Cast<ListBoxItem>().Select(item => item.Content.ToString()).ToList();

            if (selectedTitles.Count == 0)
                return;

            int columns = (int)Math.Ceiling(Math.Sqrt(selectedTitles.Count));
            int rows = (int)Math.Ceiling((double)selectedTitles.Count / columns);

            PlotGrid.Rows = rows;
            PlotGrid.Columns = columns;

            foreach (var title in selectedTitles)
            {
                if (plotModelFactories.TryGetValue(title, out var factory))
                {
                    var plotInfo = factory();
                    currentPlotInfos.Add(plotInfo);

                    var plotView = new PlotView
                    {
                        Model = plotInfo.Model,
                        Margin = new Thickness(5),
                        Focusable = true
                    };

                    var controller = new PlotController();

                    if (EditModeToggleButton.IsChecked == true)
                    {
                        controller.UnbindAll();
                    }

                    plotView.Controller = controller;

                    plotView.MouseDown += PlotView_MouseDown;
                    plotView.MouseMove += PlotView_MouseMove;
                    plotView.MouseUp += PlotView_MouseUp;
                    plotView.KeyDown += PlotView_KeyDown;

                    selectedPointAnnotations[plotInfo.Model] = new List<PointAnnotation>();

                    PlotGrid.Children.Add(plotView);
                }
            }
        }

        private void PlotView_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (sender is not PlotView plotView)
                return;

            plotView.Focus();

            if (EditModeToggleButton.IsChecked == true && e.LeftButton == MouseButtonState.Pressed)
            {
                var mousePos = e.GetPosition(plotView);
                selectionStartPoint = new ScreenPoint(mousePos.X, mousePos.Y);

                selectionRectangle = new RectangleAnnotation
                {
                    Stroke = OxyColors.Blue,
                    StrokeThickness = 1,
                    Fill = OxyColor.FromAColor(40, OxyColors.SkyBlue),
                    Layer = AnnotationLayer.AboveSeries
                };
                plotView.Model.Annotations.Add(selectionRectangle);

                plotView.CaptureMouse();
                e.Handled = true;
            }
        }

        private void PlotView_MouseMove(object sender, MouseEventArgs e)
        {
            if (sender is not PlotView plotView || selectionStartPoint == null || selectionRectangle == null)
                return;

            var mousePos = e.GetPosition(plotView);
            var currentPoint = new ScreenPoint(mousePos.X, mousePos.Y);

            selectionRectangle.MinimumX = Math.Min(selectionStartPoint.Value.X, currentPoint.X);
            selectionRectangle.MaximumX = Math.Max(selectionStartPoint.Value.X, currentPoint.X);
            selectionRectangle.MinimumY = Math.Min(selectionStartPoint.Value.Y, currentPoint.Y);
            selectionRectangle.MaximumY = Math.Max(selectionStartPoint.Value.Y, currentPoint.Y);

            plotView.Model.InvalidatePlot(false);
            e.Handled = true;
        }

        private void PlotView_MouseUp(object sender, MouseButtonEventArgs e)
        {
            if (sender is not PlotView plotView || selectionStartPoint == null || selectionRectangle == null)
                return;

            if (EditModeToggleButton.IsChecked == true && e.LeftButton == MouseButtonState.Released)
            {
                var mousePos = e.GetPosition(plotView);

                var xAxis = plotView.Model.Axes.FirstOrDefault(a => a.IsHorizontal());
                var yAxis = plotView.Model.Axes.FirstOrDefault(a => a.IsVertical());

                if (xAxis != null && yAxis != null)
                {
                    double x0 = xAxis.InverseTransform(selectionRectangle.MinimumX);
                    double x1 = xAxis.InverseTransform(selectionRectangle.MaximumX);
                    double y0 = yAxis.InverseTransform(selectionRectangle.MinimumY);
                    double y1 = yAxis.InverseTransform(selectionRectangle.MaximumY);

                    double minX = Math.Min(x0, x1);
                    double maxX = Math.Max(x0, x1);
                    double minY = Math.Min(y0, y1);
                    double maxY = Math.Max(y0, y1);

                    selectedPoints.Clear();

                    var annotations = selectedPointAnnotations[plotView.Model];
                    foreach (var annotation in annotations)
                    {
                        plotView.Model.Annotations.Remove(annotation);
                    }
                    annotations.Clear();

                    var plotInfo = currentPlotInfos.FirstOrDefault(pi => pi.Model == plotView.Model);
                    if (plotInfo != null)
                    {
                        foreach (var series in plotView.Model.Series.OfType<ScatterSeries>())
                        {
                            var dataRows = plotInfo.SeriesDataRows[series];

                            for (int i = 0; i < series.Points.Count; i++)
                            {
                                var point = series.Points[i];
                                if (point.X >= minX && point.X <= maxX && point.Y >= minY && point.Y <= maxY)
                                {
                                    selectedPoints.Add((series, point, i, dataRows[i], dataRows));

                                    var pointAnnotation = new PointAnnotation
                                    {
                                        X = point.X,
                                        Y = point.Y,
                                        Shape = MarkerType.Circle,
                                        Fill = OxyColors.Red,
                                        Stroke = OxyColors.Transparent,
                                        Size = 7
                                    };
                                    plotView.Model.Annotations.Add(pointAnnotation);
                                    annotations.Add(pointAnnotation);
                                }
                            }
                        }
                    }

                    plotView.Model.Annotations.Remove(selectionRectangle);
                    selectionRectangle = null;
                    selectionStartPoint = null;

                    plotView.ReleaseMouseCapture();
                    plotView.Model.InvalidatePlot(false);
                    e.Handled = true;
                }
            }
        }

        private void PlotView_KeyDown(object sender, KeyEventArgs e)
        {
            if (EditModeToggleButton.IsChecked == true && e.Key == Key.Delete && selectedPoints.Any())
            {
                DeleteSelectedPoints();
                e.Handled = true;
            }
        }

        private void DeleteSelectedPoints()
        {
            if (selectedPoints.Any())
            {
                var result = MessageBox.Show($"Вы уверены, что хотите удалить выбранные точки ({selectedPoints.Count})?", "Подтверждение удаления", MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (result == MessageBoxResult.Yes)
                {
                    var seriesGroups = selectedPoints.GroupBy(sp => sp.Series);

                    foreach (var group in seriesGroups)
                    {
                        var scatterSeries = group.Key;
                        var pointsToRemove = group.OrderByDescending(sp => sp.Index).ToList();

                        foreach (var sp in pointsToRemove)
                        {
                            sp.DataRow.Delete();
                            scatterSeries.Points.RemoveAt(sp.Index);
                            sp.DataRows.RemoveAt(sp.Index);
                        }

                        var yAxis = scatterSeries.PlotModel.Axes.FirstOrDefault(a => a.IsVertical());
                        if (yAxis != null)
                        {
                            yAxis.Minimum = Double.NaN;
                            yAxis.Maximum = Double.NaN;
                        }

                        undoStack.Push(new MultiPointGraphAction(pointsToRemove));
                        redoStack.Clear();

                        scatterSeries.PlotModel.InvalidatePlot(true);
                    }

                    foreach (var kvp in selectedPointAnnotations)
                    {
                        var model = kvp.Key;
                        var annotations = kvp.Value;
                        foreach (var annotation in annotations)
                        {
                            model.Annotations.Remove(annotation);
                        }
                        annotations.Clear();
                    }

                    UpdateUndoRedoButtons();
                }

                selectedPoints.Clear();
            }
        }

        private void UndoButton_Click(object sender, RoutedEventArgs e)
        {
            if (undoStack.Any())
            {
                var action = undoStack.Pop();
                action.Undo();

                redoStack.Push(action);

                if (action is GraphAction graphAction)
                {
                    graphAction.Series.PlotModel.InvalidatePlot(true);
                }
                else if (action is MultiPointGraphAction multiPointAction)
                {
                    multiPointAction.InvalidatePlots();
                }

                UpdateUndoRedoButtons();
            }
        }

        private void RedoButton_Click(object sender, RoutedEventArgs e)
        {
            if (redoStack.Any())
            {
                var action = redoStack.Pop();
                action.Redo();

                undoStack.Push(action);

                if (action is GraphAction graphAction)
                {
                    graphAction.Series.PlotModel.InvalidatePlot(true);
                }
                else if (action is MultiPointGraphAction multiPointAction)
                {
                    multiPointAction.InvalidatePlots();
                }

                UpdateUndoRedoButtons();
            }
        }

        private void SaveButton_Click(object sender, RoutedEventArgs e)
        {
            var result = MessageBox.Show("Вы уверены, что хотите сохранить изменения?", "Подтверждение сохранения", MessageBoxButton.YesNo, MessageBoxImage.Question);
            if (result == MessageBoxResult.Yes)
            {
                txtDataCopy.AcceptChanges();
                magneteDataCopy.AcceptChanges();
                DialogResult = true;
            }
        }

        private void CancelButton_Click(object sender, RoutedEventArgs e)
        {
            var result = MessageBox.Show("Вы уверены, что хотите отменить все изменения?", "Подтверждение отмены", MessageBoxButton.YesNo, MessageBoxImage.Question);
            if (result == MessageBoxResult.Yes)
            {
                txtDataCopy.RejectChanges();
                magneteDataCopy.RejectChanges();
                DialogResult = false;
            }
        }

        private void ResetZoomMenuItem_Click(object sender, RoutedEventArgs e)
        {
            foreach (var plotView in PlotGrid.Children.OfType<PlotView>())
            {
                if (plotView.Model != null)
                {
                    foreach (var axis in plotView.Model.Axes)
                    {
                        axis.Reset();
                    }
                    plotView.Model.InvalidatePlot(false);
                }
            }
        }

        private void ResetYZoomMenuItem_Click(object sender, RoutedEventArgs e)
        {
            foreach (var plotView in PlotGrid.Children.OfType<PlotView>())
            {
                if (plotView.Model != null)
                {
                    foreach (var axis in plotView.Model.Axes.Where(a => a.IsVertical()))
                    {
                        axis.Reset();
                    }
                    plotView.Model.InvalidatePlot(false);
                }
            }
        }

        private void ResetXZoomMenuItem_Click(object sender, RoutedEventArgs e)
        {
            foreach (var plotView in PlotGrid.Children.OfType<PlotView>())
            {
                if (plotView.Model != null)
                {
                    foreach (var axis in plotView.Model.Axes.Where(a => a.IsHorizontal()))
                    {
                        axis.Reset();
                    }
                    plotView.Model.InvalidatePlot(false);
                }
            }
        }

        private void UpdateUndoRedoButtons()
        {
            UndoButton.IsEnabled = undoStack.Any();
            RedoButton.IsEnabled = redoStack.Any();
        }

        private void EditModeToggleButton_Checked(object sender, RoutedEventArgs e)
        {
            UpdatePlots();
        }

        private void EditModeToggleButton_Unchecked(object sender, RoutedEventArgs e)
        {
            UpdatePlots();
        }

        // Интерфейс для действий Undo/Redo
        private interface IUndoRedoAction
        {
            void Undo();
            void Redo();
        }

        // Класс для действий с множественными точками
        private class MultiPointGraphAction : IUndoRedoAction
        {
            private readonly List<(ScatterSeries Series, ScatterPoint Point, int Index, DataRow DataRow, List<DataRow> DataRows)> points;

            public MultiPointGraphAction(List<(ScatterSeries Series, ScatterPoint Point, int Index, DataRow DataRow, List<DataRow> DataRows)> points)
            {
                this.points = points;
            }

            public void Undo()
            {
                var grouped = points.GroupBy(p => p.Series);
                foreach (var group in grouped)
                {
                    var series = group.Key;

                    foreach (var sp in group.OrderBy(p => p.Index))
                    {
                        if (sp.DataRow.RowState == DataRowState.Deleted)
                            sp.DataRow.RejectChanges();

                        series.Points.Insert(sp.Index, sp.Point);
                        sp.DataRows.Insert(sp.Index, sp.DataRow);
                    }

                    var yAxis = series.PlotModel.Axes.FirstOrDefault(a => a.IsVertical());
                    if (yAxis != null)
                    {
                        yAxis.Minimum = Double.NaN;
                        yAxis.Maximum = Double.NaN;
                    }

                    series.PlotModel.InvalidatePlot(true);
                }
            }

            public void Redo()
            {
                var grouped = points.GroupBy(p => p.Series);
                foreach (var group in grouped)
                {
                    var series = group.Key;

                    foreach (var sp in group.OrderByDescending(p => p.Index))
                    {
                        sp.DataRow.Delete();
                        series.Points.RemoveAt(sp.Index);
                        sp.DataRows.RemoveAt(sp.Index);
                    }

                    var yAxis = series.PlotModel.Axes.FirstOrDefault(a => a.IsVertical());
                    if (yAxis != null)
                    {
                        yAxis.Minimum = Double.NaN;
                        yAxis.Maximum = Double.NaN;
                    }

                    series.PlotModel.InvalidatePlot(true);
                }
            }

            public void InvalidatePlots()
            {
                var seriesSet = points.Select(p => p.Series).Distinct();
                foreach (var series in seriesSet)
                {
                    series.PlotModel.InvalidatePlot(true);
                }
            }
        }
    }
}

// GraphAction.cs
using OxyPlot.Series;
using System.Data;

namespace TxtProcessorApp
{
    /// <summary>
    /// Класс, представляющий действие на графике для реализации Undo и Redo.
    /// </summary>
    public class GraphAction : IUndoRedoAction
    {
        public ScatterSeries Series { get; }
        public ScatterPoint Point { get; }
        public int PointIndex { get; }
        public DataRow DataRow { get; }

        public GraphAction(ScatterSeries series, ScatterPoint point, int pointIndex, DataRow dataRow)
        {
            Series = series;
            Point = point;
            PointIndex = pointIndex;
            DataRow = dataRow;
        }

        public void Undo()
        {
            if (DataRow.RowState == DataRowState.Deleted)
                DataRow.RejectChanges();

            Series.Points.Insert(PointIndex, Point);
        }

        public void Redo()
        {
            Series.Points.RemoveAt(PointIndex);

            if (DataRow.RowState != DataRowState.Deleted)
                DataRow.Delete();
        }
    }
}

// UndoRedoManager.cs
using System.Collections.Generic;

namespace TxtProcessorApp
{
    /// <summary>
    /// Класс для управления операциями Undo и Redo.
    /// </summary>
    public class UndoRedoManager
    {
        private readonly Stack<IUndoRedoAction> undoStack = new();
        private readonly Stack<IUndoRedoAction> redoStack = new();
        private readonly int maxStackSize;

        public UndoRedoManager(int maxStackSize = 100)
        {
            this.maxStackSize = maxStackSize;
        }

        public void ExecuteAction(IUndoRedoAction action)
        {
            undoStack.Push(action);
            if (undoStack.Count > maxStackSize)
            {
                var tempStack = new Stack<IUndoRedoAction>(undoStack.Reverse());
                tempStack.Pop(); // Удаляем самое старое действие
                undoStack.Clear();
                foreach (var act in tempStack)
                    undoStack.Push(act);
            }
            redoStack.Clear();
        }

        public void Undo()
        {
            if (CanUndo)
            {
                var action = undoStack.Pop();
                action.Undo();
                redoStack.Push(action);
            }
        }

        public void Redo()
        {
            if (CanRedo)
            {
                var action = redoStack.Pop();
                action.Redo();
                undoStack.Push(action);
            }
        }

        public bool CanUndo => undoStack.Count > 0;
        public bool CanRedo => redoStack.Count > 0;

        public void Clear()
        {
            undoStack.Clear();
            redoStack.Clear();
        }
    }

    /// <summary>
    /// Интерфейс для действий, поддерживающих Undo и Redo.
    /// </summary>
    public interface IUndoRedoAction
    {
        void Undo();
        void Redo();
    }
}


// ProjectData.cs
using System.Collections.Generic;
using System.Data;

namespace TxtProcessorApp
{
    /// <summary>
    /// Класс для хранения данных проекта.
    /// </summary>
    public class ProjectData
    {
        public Dictionary<string, List<string[]>> TxtFileData { get; set; } = new();
        public Dictionary<string, List<string[]>> MagneteFileData { get; set; } = new();
        public DataTable CombinedTxtDataTable { get; set; } = new();
        public DataTable CombinedMagneteDataTable { get; set; } = new();
    }
}



<!-- InputDialog.xaml -->
<Window x:Class="TxtProcessorApp.InputDialog"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Ввод" Height="150" Width="400" WindowStartupLocation="CenterOwner" ResizeMode="NoResize">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <TextBlock x:Name="PromptTextBlock" Grid.Row="0" Text="Prompt:" Margin="0,0,0,10" TextWrapping="Wrap"/>

        <TextBox x:Name="ResponseTextBox" Grid.Row="1" Height="25"/>

        <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,0">
            <Button Content="OK" Width="75" Margin="0,0,10,0" IsDefault="True" Click="OK_Click"/>
            <Button Content="Отмена" Width="75" IsCancel="True"/>
        </StackPanel>
    </Grid>
</Window>

// InputDialog.xaml.cs
using System.Windows;

namespace TxtProcessorApp
{
    /// <summary>
    /// Логика взаимодействия для InputDialog.xaml
    /// </summary>
    public partial class InputDialog : Window
    {
        public string ResponseText { get; private set; }

        public InputDialog(string question, string defaultAnswer = "")
        {
            InitializeComponent();
            PromptTextBlock.Text = question;
            ResponseTextBox.Text = defaultAnswer;
            ResponseTextBox.SelectAll();
            ResponseTextBox.Focus();
        }

        private void OK_Click(object sender, RoutedEventArgs e)
        {
            ResponseText = ResponseTextBox.Text;
            DialogResult = true;
        }
    }
}

<!-- SelectEditTypeWindow.xaml -->
<Window x:Class="TxtProcessorApp.SelectEditTypeWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Выбор данных" Height="200" Width="400" WindowStartupLocation="CenterOwner" ResizeMode="NoResize">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <TextBlock Text="Выберите данные:" Grid.Row="0" Margin="0,0,0,10" FontWeight="Bold" />

        <ListBox x:Name="OptionsListBox" Grid.Row="1">
            <ListBoxItem Content=".txt файлы" />
            <ListBoxItem Content=".magnete файлы" />
        </ListBox>

        <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,0">
            <Button Content="OK" Width="75" Margin="0,0,10,0" IsDefault="True" Click="OK_Click"/>
            <Button Content="Отмена" Width="75" IsCancel="True"/>
        </StackPanel>
    </Grid>
</Window>

// SelectEditTypeWindow.xaml.cs
using System.Windows;
using System.Windows.Controls;

namespace TxtProcessorApp
{
    public partial class SelectEditTypeWindow : Window
    {
        public string SelectedOption { get; private set; }

        public SelectEditTypeWindow(string prompt)
        {
            InitializeComponent();
            // Можно добавить дополнительную настройку окна при необходимости
        }

        private void OK_Click(object sender, RoutedEventArgs e)
        {
            if (OptionsListBox.SelectedItem is ListBoxItem selectedItem)
            {
                SelectedOption = selectedItem.Content.ToString();
                DialogResult = true;
            }
            else
            {
                MessageBox.Show("Пожалуйста, выберите опцию.", "Внимание", MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }
    }
}

<!-- EditWindow.xaml -->
<Window x:Class="TxtProcessorApp.EditWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Редактирование данных" Height="600" Width="900">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <!-- DataGrid для отображения и редактирования данных -->
        <DataGrid x:Name="EditDataGrid" Grid.Row="0" AutoGenerateColumns="True" 
                  CanUserAddRows="True" CanUserDeleteRows="True" 
                  IsReadOnly="False" 
                  SelectionUnit="CellOrRowHeader" 
                  SelectedCellsChanged="EditDataGrid_SelectedCellsChanged"
                  FontFamily="Consolas" FontSize="12">
            <!-- Контекстное меню для вставки строк и столбцов -->
            <DataGrid.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="Вставить строку выше" Click="InsertRowAbove_Click"/>
                    <MenuItem Header="Вставить строку ниже" Click="InsertRowBelow_Click"/>
                    <Separator/>
                    <MenuItem Header="Вставить столбец слева" Click="InsertColumnLeft_Click"/>
                    <MenuItem Header="Вставить столбец справа" Click="InsertColumnRight_Click"/>
                </ContextMenu>
            </DataGrid.ContextMenu>
        </DataGrid>

        <!-- Панель кнопок ниже DataGrid -->
        <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,10,0,0">
            <!-- Кнопки Undo и Redo -->
            <Button x:Name="UndoButton" Content="Отменить" Width="100" Height="30" Margin="0,0,10,0" Click="UndoButton_Click" IsEnabled="False"/>
            <Button x:Name="RedoButton" Content="Повторить" Width="100" Height="30" Margin="0,0,10,0" Click="RedoButton_Click" IsEnabled="False"/>

            <!-- Кнопка Применить -->
            <Button x:Name="ApplyButton" Content="Применить" Width="100" Height="30" Margin="0,0,10,0" Click="ApplyButton_Click"/>
            <Button x:Name="CancelButton" Content="Отмена" Width="100" Height="30" Margin="0,0,10,0" Click="CancelButton_Click"/>

            <!-- Элемент управления для выбора разделителя -->
            <ComboBox x:Name="DelimiterComboBox" Width="100" Height="30" SelectedIndex="0">
                <ComboBoxItem Content="Пробел"/>
                <ComboBoxItem Content="Табуляция"/>
                <ComboBoxItem Content="Запятая"/>
            </ComboBox>
            <Button Content="Экспортировать файл" Width="150" Height="30" Margin="10,0,0,0" Click="ExportButton_Click"/>
        </StackPanel>
    </Grid>
</Window>

// EditWindow.xaml.cs
using Microsoft.Win32;
using System;
using System.Data;
using System.Linq;
using System.IO;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Collections.Generic;
using System.Windows.Data;
using System.Threading.Tasks;

namespace TxtProcessorApp
{
    public partial class EditWindow : Window
    {
        private DataTable _originalDataTable;
        private DataTable _dataTableCopy;

        // Стек для операций Undo/Redo
        private readonly Stack<IUndoRedoAction> undoStack = new();
        private readonly Stack<IUndoRedoAction> redoStack = new();

        public EditWindow(DataTable dataTable)
        {
            InitializeComponent();

            // Создаём копию исходного DataTable
            _originalDataTable = dataTable;
            _dataTableCopy = dataTable.Copy();

            EditDataGrid.ItemsSource = _dataTableCopy.DefaultView;

            // Разрешаем добавление и удаление строк через интерфейс DataGrid
            EditDataGrid.CanUserAddRows = true;
            EditDataGrid.CanUserDeleteRows = true;

            // Подписываемся на события изменений данных
            _dataTableCopy.RowChanged += DataTable_RowChanged;
            _dataTableCopy.RowDeleting += DataTable_RowDeleting; // Используем RowDeleting вместо RowDeleted
            _dataTableCopy.ColumnChanged += DataTable_ColumnChanged;

            UpdateUndoRedoButtons();
        }

        #region Обработчики изменений данных

        private void DataTable_RowChanged(object sender, DataRowChangeEventArgs e)
        {
            if (e.Action == DataRowAction.Add)
            {
                var action = new DataRowUndoRedoAction(e.Row, DataRowAction.Add, _dataTableCopy);
                undoStack.Push(action);
                redoStack.Clear();
                UpdateUndoRedoButtons();
            }
        }

        private void DataTable_RowDeleting(object sender, DataRowChangeEventArgs e)
        {
            var action = new DataRowUndoRedoAction(e.Row, DataRowAction.Delete, _dataTableCopy);
            undoStack.Push(action);
            redoStack.Clear();
            UpdateUndoRedoButtons();
        }

        private void DataTable_ColumnChanged(object sender, DataColumnChangeEventArgs e)
        {
            object oldValue;

            if (e.Row.HasVersion(DataRowVersion.Original))
            {
                oldValue = e.Row[e.Column, DataRowVersion.Original];
            }
            else if (e.Row.HasVersion(DataRowVersion.Current))
            {
                oldValue = e.Row[e.Column, DataRowVersion.Current];
            }
            else
            {
                oldValue = null; // Or set a default value if appropriate
            }

            var action = new DataCellChangeAction(e.Row, e.Column, oldValue, e.ProposedValue);
            undoStack.Push(action);
            redoStack.Clear();
            UpdateUndoRedoButtons();
        }


        #endregion

        #region Вставка строк и столбцов

        // Вставка строки выше выбранной
        private void InsertRowAbove_Click(object sender, RoutedEventArgs e)
        {
            int rowIndex = GetSelectedRowIndex();
            if (rowIndex != -1)
            {
                var newRow = _dataTableCopy.NewRow();
                _dataTableCopy.Rows.InsertAt(newRow, rowIndex);
                var action = new DataRowUndoRedoAction(newRow, DataRowAction.Add, _dataTableCopy);
                undoStack.Push(action);
                redoStack.Clear();
                UpdateUndoRedoButtons();
            }
            else
            {
                var newRow = _dataTableCopy.NewRow();
                _dataTableCopy.Rows.Add(newRow);
                var action = new DataRowUndoRedoAction(newRow, DataRowAction.Add, _dataTableCopy);
                undoStack.Push(action);
                redoStack.Clear();
                UpdateUndoRedoButtons();
            }
            RefreshDataGrid();
        }

        // Вставка строки ниже выбранной
        private void InsertRowBelow_Click(object sender, RoutedEventArgs e)
        {
            int rowIndex = GetSelectedRowIndex();
            if (rowIndex != -1)
            {
                var newRow = _dataTableCopy.NewRow();
                _dataTableCopy.Rows.InsertAt(newRow, rowIndex + 1);
                var action = new DataRowUndoRedoAction(newRow, DataRowAction.Add, _dataTableCopy);
                undoStack.Push(action);
                redoStack.Clear();
                UpdateUndoRedoButtons();
            }
            else
            {
                var newRow = _dataTableCopy.NewRow();
                _dataTableCopy.Rows.Add(newRow);
                var action = new DataRowUndoRedoAction(newRow, DataRowAction.Add, _dataTableCopy);
                undoStack.Push(action);
                redoStack.Clear();
                UpdateUndoRedoButtons();
            }
            RefreshDataGrid();
        }

        // Вставка столбца слева от выбранного
        private void InsertColumnLeft_Click(object sender, RoutedEventArgs e)
        {
            int columnIndex = GetSelectedColumnIndex();
            InsertNewColumn(columnIndex);
            RefreshDataGrid();
        }

        // Вставка столбца справа от выбранного
        private void InsertColumnRight_Click(object sender, RoutedEventArgs e)
        {
            int columnIndex = GetSelectedColumnIndex();
            InsertNewColumn(columnIndex + 1);
            RefreshDataGrid();
        }

        private void InsertNewColumn(int index)
        {
            string columnName = GenerateUniqueColumnName("NewColumn");
            DataColumn newColumn = new DataColumn(columnName, typeof(string));
            _dataTableCopy.Columns.Add(newColumn);
            newColumn.SetOrdinal(Math.Min(index, _dataTableCopy.Columns.Count - 1));

            foreach (DataRow row in _dataTableCopy.Rows)
            {
                row[newColumn] = string.Empty;
            }

            var action = new DataColumnAction(newColumn, DataColumnActionType.Add, _dataTableCopy);
            undoStack.Push(action);
            redoStack.Clear();
            UpdateUndoRedoButtons();
        }

        private string GenerateUniqueColumnName(string baseName)
        {
            int suffix = 1;
            string newName = baseName;
            while (_dataTableCopy.Columns.Contains(newName))
            {
                newName = $"{baseName}_{suffix}";
                suffix++;
            }
            return newName;
        }

        private int GetSelectedRowIndex()
        {
            if (EditDataGrid.SelectedItem is DataRowView selectedRow)
            {
                return _dataTableCopy.Rows.IndexOf(selectedRow.Row);
            }
            return -1;
        }

        private int GetSelectedColumnIndex()
        {
            if (EditDataGrid.SelectedCells.Count > 0)
            {
                var selectedCell = EditDataGrid.SelectedCells[0];
                var column = selectedCell.Column;
                if (column != null)
                {
                    return column.DisplayIndex;
                }
            }
            return -1;
        }

        #endregion

        private void RefreshDataGrid()
        {
            var view = CollectionViewSource.GetDefaultView(EditDataGrid.ItemsSource);
            if (view != null)
            {
                view.Refresh();
            }
        }

        private void EditDataGrid_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
        {
            // Дополнительная логика при изменении выбора (если требуется)
        }

        #region Экспорт данных

        private async void ExportButton_Click(object sender, RoutedEventArgs e)
        {
            string delimiter = GetSelectedDelimiter();

            SaveFileDialog sfd = new SaveFileDialog
            {
                Filter = delimiter == "," ? "CSV файлы (*.csv)|*.csv" : "Текстовые файлы (*.txt)|*.txt",
                FileName = delimiter == "," ? "EditedCombinedFile.csv" : "EditedCombinedFile.txt"
            };

            if (sfd.ShowDialog() == true)
            {
                try
                {
                    using (StreamWriter sw = new StreamWriter(sfd.FileName, false, Encoding.UTF8))
                    {
                        await ExportWithDelimiterAsync(sw, delimiter);
                    }

                    MessageBox.Show($"Файл успешно экспортирован по адресу:\n{sfd.FileName}", "Успех", MessageBoxButton.OK, MessageBoxImage.Information);
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"Ошибка при сохранении файла:\n{ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
        }

        private async Task ExportWithDelimiterAsync(StreamWriter sw, string delimiter)
        {
            string[] headers = _dataTableCopy.Columns
                .Cast<DataColumn>()
                .Select(column => $"\"{column.ColumnName}\"")
                .ToArray();
            await sw.WriteLineAsync(string.Join(delimiter, headers));

            foreach (DataRow row in _dataTableCopy.Rows)
            {
                string[] fields = row.ItemArray.Select(field =>
                {
                    string fieldStr = field?.ToString() ?? string.Empty;
                    if (fieldStr.Contains(delimiter) || fieldStr.Contains("\""))
                    {
                        fieldStr = $"\"{fieldStr.Replace("\"", "\"\"")}\"";
                    }
                    return fieldStr;
                }).ToArray();
                await sw.WriteLineAsync(string.Join(delimiter, fields));
            }
        }

        private string GetSelectedDelimiter()
        {
            switch (DelimiterComboBox.SelectedIndex)
            {
                case 0:
                    return " "; // Пробел
                case 1:
                    return "\t"; // Табуляция
                case 2:
                    return ","; // Запятая
                default:
                    return "\t"; // По умолчанию табуляция
            }
        }

        #endregion

        #region Undo/Redo Functionality

        private void UndoButton_Click(object sender, RoutedEventArgs e)
        {
            if (undoStack.Any())
            {
                var action = undoStack.Pop();
                action.Undo();
                redoStack.Push(action);
                UpdateUndoRedoButtons();
                RefreshDataGrid();
            }
        }


        private void RedoButton_Click(object sender, RoutedEventArgs e)
        {
            if (redoStack.Any())
            {
                var action = redoStack.Pop();
                action.Redo();
                undoStack.Push(action);
                UpdateUndoRedoButtons();
            }
        }

        private void UpdateUndoRedoButtons()
        {
            UndoButton.IsEnabled = undoStack.Any();
            RedoButton.IsEnabled = redoStack.Any();
        }

        #endregion

        #region Кнопки Применить и Отмена

        private void ApplyButton_Click(object sender, RoutedEventArgs e)
        {
            // Применяем изменения к исходному DataTable
            _originalDataTable.Clear();
            foreach (DataRow row in _dataTableCopy.Rows)
            {
                _originalDataTable.ImportRow(row);
            }
            _originalDataTable.AcceptChanges();

            // Закрываем окно после применения изменений
            this.DialogResult = true;
            this.Close();
        }

        private void CancelButton_Click(object sender, RoutedEventArgs e)
        {
            // Закрываем окно без сохранения изменений
            this.DialogResult = false;
            this.Close();
        }

        #endregion

        #region Undo/Redo Actions

        private interface IUndoRedoAction
        {
            void Undo();
            void Redo();
        }

        private class DataRowUndoRedoAction : IUndoRedoAction
        {
            private readonly DataTable _table;
            private readonly DataRowAction _actionType;
            private readonly object[] _data;
            private readonly int _rowIndex;

            public DataRowUndoRedoAction(DataRow row, DataRowAction actionType, DataTable table)
            {
                _table = table;
                _actionType = actionType;
                _data = row.ItemArray.Clone() as object[];
                _rowIndex = _table.Rows.IndexOf(row);
            }

            public void Undo()
            {
                if (_actionType == DataRowAction.Add)
                {
                    // Undo adding the row: remove it
                    _table.Rows.RemoveAt(_rowIndex);
                }
                else if (_actionType == DataRowAction.Delete)
                {
                    // Undo deleting the row: insert it back
                    DataRow newRow = _table.NewRow();
                    newRow.ItemArray = _data;
                    _table.Rows.InsertAt(newRow, _rowIndex);
                }
            }

            public void Redo()
            {
                if (_actionType == DataRowAction.Add)
                {
                    // Redo adding the row: insert it back
                    DataRow newRow = _table.NewRow();
                    newRow.ItemArray = _data;
                    _table.Rows.InsertAt(newRow, _rowIndex);
                }
                else if (_actionType == DataRowAction.Delete)
                {
                    // Redo deleting the row: remove it
                    _table.Rows.RemoveAt(_rowIndex);
                }
            }
        }

        private class DataCellChangeAction : IUndoRedoAction
        {
            private readonly DataRow _row;
            private readonly DataColumn _column;
            private readonly object _oldValue;
            private readonly object _newValue;

            public DataCellChangeAction(DataRow row, DataColumn column, object oldValue, object newValue)
            {
                _row = row;
                _column = column;
                _oldValue = oldValue;
                _newValue = newValue;
            }

            public void Undo()
            {
                _row[_column] = _oldValue ?? DBNull.Value;
            }

            public void Redo()
            {
                _row[_column] = _newValue ?? DBNull.Value;
            }
        }

        private enum DataColumnActionType
        {
            Add,
            Remove
        }

        private class DataColumnAction : IUndoRedoAction
        {
            private readonly DataColumn _column;
            private readonly DataColumnActionType _actionType;
            private readonly DataTable _table;

            public DataColumnAction(DataColumn column, DataColumnActionType actionType, DataTable table)
            {
                _column = column;
                _actionType = actionType;
                _table = table;
            }

            public void Undo()
            {
                if (_actionType == DataColumnActionType.Add)
                {
                    _table.Columns.Remove(_column);
                }
                else if (_actionType == DataColumnActionType.Remove)
                {
                    _table.Columns.Add(_column);
                }
            }

            public void Redo()
            {
                if (_actionType == DataColumnActionType.Add)
                {
                    _table.Columns.Add(_column);
                }
                else if (_actionType == DataColumnActionType.Remove)
                {
                    _table.Columns.Remove(_column);
                }
            }
        }

        #endregion
    }
}