нужно добавить еще один режим, режим выделения
то есть три режима
обычный где зум и передвижение графика и информация о точках
второй редактирование 
и третий новыый выделения

здесь нужно скопировать все методики из режима редактирования, однако те точки которые я выбрал нужно экспортировать в новую таблицу по нажатию кнопки экспорт там же
при этом нужно ввести имя файла
этот файл должен появится вкладкой в таблице в MainWondow рядом с тем файлом по которому строились графики

<!-- 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);
                var xAxis = plotView.Model.Axes.FirstOrDefault(a => a.IsHorizontal());
                var yAxis = plotView.Model.Axes.FirstOrDefault(a => a.IsVertical());

                if (xAxis == null || yAxis == null)
                    return;

                // Преобразуем экранные координаты в данные
                double xData = xAxis.InverseTransform(mousePos.X);
                double yData = yAxis.InverseTransform(mousePos.Y);
                selectionStartPoint = new ScreenPoint(xData, yData);

                selectionRectangle = new RectangleAnnotation
                {
                    Stroke = OxyColors.Red,
                    StrokeThickness = 2,
                    Fill = OxyColor.FromAColor(60, OxyColors.Orange),
                    Layer = AnnotationLayer.AboveSeries,
                    MinimumX = xData,
                    MaximumX = xData,
                    MinimumY = yData,
                    MaximumY = yData
                };
                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;

            if (EditModeToggleButton.IsChecked != true)
                return;

            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)
                return;

            // Преобразуем текущие экранные координаты в данные
            double currentX = xAxis.InverseTransform(mousePos.X);
            double currentY = yAxis.InverseTransform(mousePos.Y);

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

            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 xCurrent = xAxis.InverseTransform(mousePos.X);
                    double yCurrent = yAxis.InverseTransform(mousePos.Y);

                    double minX = Math.Min(selectionStartPoint.Value.X, xCurrent);
                    double maxX = Math.Max(selectionStartPoint.Value.X, xCurrent);
                    double minY = Math.Min(selectionStartPoint.Value.Y, yCurrent);
                    double maxY = Math.Max(selectionStartPoint.Value.Y, yCurrent);

                    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();

            // Обновляем оси для перерасчета их масштабов

        }

        private void ResetAxisLimits(ScatterSeries series)
        {
            var yAxis = series.PlotModel.Axes.FirstOrDefault(a => a.IsVertical());
            if (yAxis != null)
            {
                yAxis.Minimum = double.NaN;
                yAxis.Maximum = double.NaN;
            }

            series.PlotModel.InvalidatePlot(true);
        }
    }
}

// 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();
    }
}


<!-- 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
    }
}