using CtrEditor;
using System.ComponentModel;
using System.Windows.Controls;
using System.Windows.Input;
using Ookii.Dialogs.Wpf;
using System.Collections.ObjectModel;
using System.Windows.Threading;
using CtrEditor.ObjetosSim;
using LibS7Adv;
using System.IO;
using Newtonsoft.Json;
using System.Windows;
using CtrEditor.Simulacion;
using System.Diagnostics;
using System.Reflection;
using CommunityToolkit.Mvvm.ComponentModel;
using Xceed.Wpf.Toolkit.PropertyGrid;
using CtrEditor.ObjetosSim.Extraccion_Datos;
using ClosedXML.Excel;
using CtrEditor.PopUps;
using System.Windows.Data;
using CommunityToolkit.Mvvm.Input;
using System.Text.RegularExpressions;
using System.Collections.Specialized;
using CtrEditor.Serialization;  // Add this line
using CtrEditor.Controls;  // Add this using directive
using CtrEditor.PopUps;  // Add this using directive

namespace CtrEditor
{

    public partial class MainViewModel : ObservableObject
    {
        private readonly StateSerializer _stateSerializer;
        public Stopwatch stopwatch_Sim;

        private double stopwatch_SimPLC_last;
        private double stopwatch_SimModel_last;
        private double accumulatedSimTime;
        private double accumulatedPlcTime;
        private int simSampleCount;
        private int plcSampleCount;

        private float TiempoDesdeStartSimulacion;
        private bool Debug_SimulacionCreado = false;

        public SimulationManagerFP simulationManager = new SimulationManagerFP();

        private readonly DispatcherTimer _timerSimulacion;
        private readonly DispatcherTimer _timerPLCUpdate;
        private readonly DispatcherTimer _timerDisplayUpdate;

        public Canvas MainCanvas;

        [ObservableProperty]
        private bool isConnected;

        partial void OnPLCViewModelChanged(PLCViewModel value)
        {
            if (value != null)
            {
                value.PropertyChanged += PLCViewModel_PropertyChanged;
                IsConnected = value.IsConnected;
            }
        }

        private void PLCViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == nameof(PLCViewModel.IsConnected))
            {
                IsConnected = PLCViewModel.IsConnected;
            }
        }

        [ObservableProperty]
        private DatosDeTrabajo datosDeTrabajo;

        [ObservableProperty]
        private ObservableCollection<string> listaImagenes;  // Publicación de las claves del diccionario        

        [ObservableProperty]
        public ObservableCollection<TipoSimulable> listaOsBase;

        // Diccionario para almacenar datos expandidos de imágenes
        public Dictionary<string, Models.ImageData> _imageDataDictionary = new Dictionary<string, Models.ImageData>();

        // Comando para renombrar imagen
        public ICommand RenameImageCommand { get; private set; }

        public ICommand StartSimulationCommand { get; }
        public ICommand StopSimulationCommand { get; }
        public ICommand ItemDoubleClickCommand { get; private set; }

        public SimulationFluidsViewModel FluidSimulation { get; private set; }

        public ICommand TBStartSimulationCommand { get; }
        public ICommand TBStopSimulationCommand { get; }
        public ICommand TBStartFluidSimulationCommand { get; }
        public ICommand TBStopFluidSimulationCommand { get; }
        public ICommand TBSaveCommand { get; }
        public ICommand TBExtractTagsCommand { get; }

        public ICommand TBEliminarUserControlCommand { get; }
        public ICommand TBDuplicarUserControlCommand { get; }

        public ICommand OpenWorkDirectoryCommand { get; }

        public ICommand TBEliminarTodosCommand { get; }
        public ICommand TBEliminarAutoCreatedCommand { get; }
        public ICommand TBEliminarClonedCommand { get; }
        public ICommand TBAssingPagesCommand { get; }
        public ICommand TBMultiPageExtractTagsCommand { get; }
        public ICommand TBMultiPageAnalizeCommand { get; }
        public ICommand TBMultiPageMatrixCommand { get; }
        public ICommand TBLibraryManagerCommand { get; }


        public ICommand TBTogglePLCConnectionCommand => new RelayCommand(() =>
        {
            if (IsConnected)
                DisconnectPLC();
            else
                ConnectPLC();
        });

        // Evento que se dispara cuando se selecciona una nueva imagen
        public event EventHandler<string> ImageSelected;
        public event EventHandler<TickSimulacionEventArgs> TickSimulacion;

        // Propiedades

        private bool habilitarEliminarUserControl;

        private MainWindow mainWindow;
        private ObjectManipulationManager _objectManager;  // Add this line

        public MainWindow MainWindow
        {
            get => mainWindow;
            set
            {
                mainWindow = value;
                _objectManager = mainWindow._objectManager;  // Initialize _objectManager
                // Add the filter changed event handler here instead of in constructor
                if (mainWindow.VisFilter != null)
                {
                    mainWindow.VisFilter.FilterChanged += (s, e) => OnVisFilterChanged(e.FilterViewModel);
                }
            }
        }

        [ObservableProperty]
        private float canvasLeft;

        [ObservableProperty]
        private float canvasTop;

        [ObservableProperty]
        private bool isSimulationRunning;

        [ObservableProperty]
        private bool hasUnsavedChanges;

        [ObservableProperty]
        private ObservableCollection<string> recentDirectories;

        private bool inhibitSaveChangesControl;

        public ICommand OpenRecentDirectoryCommand { get; private set; }

        partial void OnIsSimulationRunningChanged(bool value)
        {
            CommandManager.InvalidateRequerySuggested(); // Notificar que el estado de los comandos ha cambiado
        }

        partial void OnHasUnsavedChangesChanged(bool value)
        {
            // Notificar el cambio del título indirectamente a través de directorioTrabajo
            OnPropertyChanged(nameof(directorioTrabajo));
        }

        public string directorioTrabajo
        {
            get => EstadoPersistente.Instance.directorio;
            set
            {
                if (value != null)
                {
                    EstadoPersistente.Instance.directorio = value; // Actualizar el estado persistente
                    EstadoPersistente.Instance.GuardarEstado();  // Guardar el estado actualizado
                    DatosDeTrabajo.CargarImagenes();
                    ListaImagenes = new ObservableCollection<string>(DatosDeTrabajo.Imagenes.Keys);  // Actualizar claves
                                                                                                     // Si no hay imágenes en el directorio, copiar base.png desde los recursos
                    if (!ListaImagenes.Any())
                    {
                        CopiarImagenBase(value);
                        // Recargar las imágenes después de copiar la imagen base
                        DatosDeTrabajo.CargarImagenes();
                        ListaImagenes = new ObservableCollection<string>(DatosDeTrabajo.Imagenes.Keys);  // Actualizar claves nuevamente
                    }

                    SelectedImage = null;
                    var x = ListaImagenes.FirstOrDefault(o => o == EstadoPersistente.Instance.imagen, null);
                    if (EstadoPersistente.Instance.imagen != null && EstadoPersistente.Instance.imagen.Length > 0 && x != null)
                        SelectedImage = EstadoPersistente.Instance.imagen;
                    else if (ListaImagenes.FirstOrDefault() != null)
                        SelectedImage = ListaImagenes.FirstOrDefault();

                    OnPropertyChanged(nameof(directorioTrabajo));  // Notificar el cambio de propiedad
                    OnPropertyChanged(nameof(ListaImagenes)); // Notificar que la lista de imágenes ha cambiado

                    AddToRecentDirectories(value);
                }
            }
        }

        // Función para copiar la imagen base desde los recursos
        private void CopiarImagenBase(string directorio)
        {
            try
            {
                // Obtener el path del archivo base.png desde los recursos (dentro del ensamblado)
                var assembly = Assembly.GetExecutingAssembly();
                var resourcePath = "CtrEditor.Images.base.png";  // Ajusta el namespace y el path según tu proyecto

                using (Stream resourceStream = assembly.GetManifestResourceStream(resourcePath))
                {
                    if (resourceStream != null)
                    {
                        string destino = Path.Combine(directorio, "base.png");

                        using (FileStream fileStream = new FileStream(destino, FileMode.Create, FileAccess.Write))
                        {
                            resourceStream.CopyTo(fileStream);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Error al copiar la imagen base: {ex.Message}");
            }
        }

        [RelayCommand]
        public void DebugWindow()
        {
            MainWindow.DebugWindow();
        }

        [ObservableProperty]
        private PLCViewModel pLCViewModel;

        [ObservableProperty]
        private string selectedImage;

        partial void OnSelectedImageChanging(string? oldValue, string newValue)
        {
            if (HasUnsavedChanges && !inhibitSaveChangesControl)
            {
                var result = MessageBox.Show("There are unsaved changes. Do you want to save them?",
                    "Save Changes",
                    MessageBoxButton.YesNo);

                if (result == MessageBoxResult.Yes)
                {
                    SaveStateObjetosSimulables();
                }
            }
        }
        partial void OnSelectedImageChanged(string value)
        {
            if (value != null)
            {
                StopSimulation();
                ImageSelected?.Invoke(this, datosDeTrabajo.Imagenes[value]);
                LoadStateObjetosSimulables();
                EstadoPersistente.Instance.imagen = value;
                EstadoPersistente.Instance.GuardarEstado();
                HasUnsavedChanges = false;
            }
        }

        [ObservableProperty]
        private osBase selectedItemOsList;

        partial void OnSelectedItemOsListChanged(osBase value)
        {
            // Enable delete and duplicate commands when either an individual item is selected
            // or when there are multiple objects selected
            habilitarEliminarUserControl = _objectManager.SelectedObjects.Count > 0;
        }


        [ObservableProperty]
        private TipoSimulable selectedItem;

        public ICollectionView ObjetosSimulablesAllPages { get; }

        [ObservableProperty]
        public ObservableCollection<osBase> objetosSimulables;

        [ObservableProperty]
        private bool isMultiSelectionActive;

        partial void OnIsMultiSelectionActiveChanged(bool value)
        {
            _objectManager?.OnMultiSelectionModeChanged(value);
        }

        partial void OnObjetosSimulablesChanged(ObservableCollection<osBase> value)
        {
            if (value != null)
            {
                value.CollectionChanged += ObjetosSimulables_CollectionChanged;
                UpdateVisFilterTypes();
            }
        }

        private void ObjetosSimulables_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
        {
            UpdateVisFilterTypes();
        }

        private void UpdateVisFilterTypes()
        {
            MainWindow?.VisFilter?.UpdateAvailableTypes(ObjetosSimulables.Select(o => o.GetType()).Distinct());
            MainWindow?.VisFilter?.UpdateAvailableTags(ObjetosSimulables);
        }

        //
        // Constructor
        //

        private StateManager _stateManager;

        public MainViewModel()
        {
            OpenWorkDirectoryCommand = new RelayCommand(OpenWorkDirectory);
            datosDeTrabajo = new DatosDeTrabajo();

            // Initialize ObjetosSimulables first
            ObjetosSimulables = new ObservableCollection<osBase>();

            ObjetosSimulables = new ObservableCollection<osBase>();

            ListaOsBase = new ObservableCollection<TipoSimulable>();

            // Inicializa el PLCViewModel
            PLCViewModel = new PLCViewModel();

            _timerPLCUpdate = new DispatcherTimer();
            _timerPLCUpdate.Interval = TimeSpan.FromMilliseconds(10); // Restaurado a 10ms
            _timerPLCUpdate.Tick += OnRefreshEvent;

            InitializeTipoSimulableList();

            ItemDoubleClickCommand = new ParameterizedRelayCommand(ExecuteDoubleClick);

            _timerSimulacion = new DispatcherTimer();
            _timerSimulacion.Interval = TimeSpan.FromMilliseconds(10); // Restaurado a 10ms
            _timerSimulacion.Tick += OnTickSimulacion;

            // Nuevo timer para actualización de display
            _timerDisplayUpdate = new DispatcherTimer();
            _timerDisplayUpdate.Interval = TimeSpan.FromMilliseconds(250);
            _timerDisplayUpdate.Tick += OnDisplayUpdate;
            _timerDisplayUpdate.Start();

            StartSimulationCommand = new RelayCommand(StartSimulation);
            StopSimulationCommand = new RelayCommand(StopSimulation);

            TBStartSimulationCommand = new RelayCommand(StartSimulation, () => !IsSimulationRunning);
            TBStopSimulationCommand = new RelayCommand(StopSimulation, () => IsSimulationRunning);

            // Inicializar simulación de fluidos
            FluidSimulation = new SimulationFluidsViewModel(this);
            TBStartFluidSimulationCommand = new RelayCommand(StartFluidSimulation, () => !FluidSimulation.IsFluidSimulationRunning);
            TBStopFluidSimulationCommand = new RelayCommand(StopFluidSimulation, () => FluidSimulation.IsFluidSimulationRunning);
            TBSaveCommand = new RelayCommand(Save);

            TBEliminarUserControlCommand = new RelayCommand(EliminarUserControl, () => habilitarEliminarUserControl);
            TBDuplicarUserControlCommand = new RelayCommand(DuplicarUserControl, () => habilitarEliminarUserControl);

            TBEliminarTodosCommand = new RelayCommand(EliminarTodosCommand);
            TBEliminarAutoCreatedCommand = new RelayCommand(EliminarAutoCreatedCommand);
            TBEliminarClonedCommand = new RelayCommand(EliminarClonedCommand);
            TBAssingPagesCommand = new RelayCommand(AssingPagesCommand);
            TBMultiPageAnalizeCommand = new RelayCommand(MultiPageAnalizeCommand);
            TBMultiPageMatrixCommand = new RelayCommand(MultiPageMatrixCommand);
            TBLibraryManagerCommand = new RelayCommand(ShowLibraryManager);
            RenameImageCommand = new RelayCommand<string>(RenameImage);

            stopwatch_Sim = new Stopwatch();
            stopwatch_Sim.Start();

            ObjetosSimulables.CollectionChanged += (s, e) =>
            {
                if (e.Action != NotifyCollectionChangedAction.Move)
                    HasUnsavedChanges = true;
            };

            recentDirectories = new ObservableCollection<string>(EstadoPersistente.Instance.RecentDirectories);
            OpenRecentDirectoryCommand = new RelayCommand<string>(OpenRecentDirectory);

            _stateManager = new StateManager(EstadoPersistente.Instance.directorio, this);
            _stateSerializer = new StateSerializer(this, datosDeTrabajo, simulationManager);

            // Conectar DatosDeTrabajo con este ViewModel para el escaneo de imágenes
            datosDeTrabajo.SetMainViewModel(this);
        }

        // Métodos para manejo de datos de imágenes
        private void RenameImage(string imageName)
        {
            if (string.IsNullOrEmpty(imageName)) return;

            var imageData = GetOrCreateImageData(imageName);

            var dialog = new PopUps.RenameImageWindow(imageName, imageData);
            dialog.Owner = MainWindow;

            if (dialog.ShowDialog() == true)
            {
                // El dialog ya ha modificado directamente el imageData
                // Solo necesitamos verificar si debemos remover la entrada si está vacía
                if (string.IsNullOrEmpty(imageData.CustomName) &&
                    imageData.Tags.Count == 0 &&
                    string.IsNullOrEmpty(imageData.Etiquetas))
                {
                    _imageDataDictionary.Remove(imageName);
                }

                // Forzar actualización del UI usando CollectionViewSource
                var collectionView = System.Windows.Data.CollectionViewSource.GetDefaultView(ListaImagenes);
                collectionView?.Refresh();

                HasUnsavedChanges = true;
            }
        }

        public string GetImageDisplayName(string imageName)
        {
            if (_imageDataDictionary.TryGetValue(imageName, out var imageData))
            {
                return imageData.DisplayName;
            }
            return imageName;
        }

        public string GetImageDisplayNameWithTags(string imageName)
        {
            if (_imageDataDictionary.TryGetValue(imageName, out var imageData))
            {
                var displayName = imageData.DisplayName;
                var tags = imageData.Etiquetas;

                if (!string.IsNullOrWhiteSpace(tags))
                {
                    return $"{displayName} {tags}";
                }
                return displayName;
            }
            return imageName;
        }

        public Models.ImageData GetOrCreateImageData(string imageName)
        {
            if (!_imageDataDictionary.TryGetValue(imageName, out var imageData))
            {
                imageData = new Models.ImageData(imageName);
                _imageDataDictionary[imageName] = imageData;
            }
            return imageData;
        }

        private void OnVisFilterChanged(osVisFilterViewModel filter)  // Changed signature to accept viewmodel directly
        {
            foreach (var obj in ObjetosSimulables)
            {
                bool isVisible = true;

                // Apply Show All filter
                if (!filter.ShowAll)
                {
                    // Check type filter
                    var typeFilter = filter.TypeFilters.FirstOrDefault(tf => tf.Type == obj.GetType());
                    if (typeFilter == null || !typeFilter.IsSelected)
                    {
                        isVisible = false;
                    }

                    // Check tag filters
                    if (filter.TagFilters.Any() && filter.TagFilters.Any(tf => tf.IsSelected))
                    {
                        var selectedTags = filter.TagFilters.Where(tf => tf.IsSelected).Select(tf => tf.TagName).ToList();
                        bool hasMatchingTag = obj.ListaEtiquetas.Any(tag => selectedTags.Contains(tag));
                        if (!hasMatchingTag)
                        {
                            isVisible = false;
                        }
                    }

                    // Check search tags
                    if (!string.IsNullOrWhiteSpace(filter.SearchTags))
                    {
                        var searchTags = filter.SearchTags
                            .Split(' ', StringSplitOptions.RemoveEmptyEntries)
                            .Where(tag => tag.StartsWith("#") && tag.Length > 1)
                            .Select(tag => tag.Substring(1).ToLowerInvariant())
                            .ToList();

                        if (searchTags.Any())
                        {
                            bool hasMatchingSearchTag = searchTags.Any(searchTag => obj.ListaEtiquetas.Contains(searchTag));
                            if (!hasMatchingSearchTag)
                            {
                                isVisible = false;
                            }
                        }
                    }

                    // Check other filters
                    if (filter.ShowCloned && !obj.Cloned)
                        isVisible = false;

                    if (filter.ShowAutoCreated && !obj.AutoCreated)
                        isVisible = false;

                    if (filter.ShowEnableOnAllPages && !obj.Enable_On_All_Pages)
                        isVisible = false;

                    if (filter.ShowOnThisPage && !obj.Show_On_This_Page)
                        isVisible = false;
                }

                obj.IsVisFilter = isVisible;

                // Update Canvas object visibility
                if (obj.VisualRepresentation != null)
                {
                    obj.VisualRepresentation.Visibility = isVisible ? Visibility.Visible : Visibility.Collapsed;
                }
            }
        }

        public void LoadStateObjetosSimulables()
        {
            if (SelectedImage != null)
            {
                _stateSerializer.LoadState(SelectedImage);

                // Aplicar los filtros actuales a los objetos recién cargados
                if (MainWindow?.VisFilter?.FilterViewModel != null)
                {
                    OnVisFilterChanged(MainWindow.VisFilter.FilterViewModel);
                }

                // Limpiar historial de undo al cargar un estado desde archivo
                MainWindow?.ClearUndoHistory();
            }
        }


        public void LoadInitialData()
        {
            // Suponiendo que "SelectedImage" es una propiedad que al establecerse dispara "ImageSelected"
            directorioTrabajo = EstadoPersistente.Instance.directorio;

            // Limpiar historial de undo al cargar datos iniciales
            MainWindow?.ClearUndoHistory();
        }

        // Crear un nuevo Objeto
        private void ExecuteDoubleClick(object parameter)
        {
            if (parameter is TipoSimulable tipoSimulable)
            {
                CrearObjetoSimulableEnCentroCanvas(tipoSimulable.Tipo);
            }
        }

        public void CrearObjetoSimulableEnCentroCanvas(Type tipoSimulable)
        {
            var CentroCanvas = MainWindow.ObtenerCentroCanvasMeters();
            CrearObjetoSimulable(tipoSimulable, CentroCanvas.X, CentroCanvas.Y);
        }

        public osBase CrearObjetoSimulable(Type tipoSimulable, float Left, float Top)
        {
            // Crear una nueva instancia del osBase correspondiente
            osBase? NuevoOsBase = UserControlFactory.GetInstanceForType(tipoSimulable);

            NuevoOsBase.Left = Left;
            NuevoOsBase.Top = Top;

            if (NuevoOsBase != null)
            {
                if (CrearUserControlDesdeObjetoSimulable(NuevoOsBase))
                {
                    // Añadir el nuevo osBase a la colección de objetos simulables
                    ObjetosSimulables.Add(NuevoOsBase);
                    HasUnsavedChanges = true;
                }
            }
            return NuevoOsBase;
        }

        // Crear UserControl desde osBase : Nuevo o desde Deserealizacion
        public bool CrearUserControlDesdeObjetoSimulable(osBase osObjeto)
        {
            Type tipoObjeto = osObjeto.GetType();

            // Obtén el UserControl correspondiente para el tipo de objeto
            UserControl? userControl = UserControlFactory.GetControlForType(tipoObjeto);

            if (userControl != null)
            {
                // Asignar los datos al UserControl
                UserControlFactory.AssignDatos(userControl, osObjeto, simulationManager);
                osObjeto._mainViewModel = this;
                if (osObjeto.Id == null) // Para los objetos salvados antes de usar UniqueID
                    osObjeto.Id = new UniqueId().ObtenerNuevaID();

                MainWindow.AgregarRegistrarUserControlCanvas(userControl);

                return true;
            }
            return false;
        }

        public void RemoverObjetoSimulable(osBase osObjeto)
        {
            if (osObjeto != null && ObjetosSimulables.Contains(osObjeto))
            {
                ObjetosSimulables.Remove(osObjeto);
                if (osObjeto.VisualRepresentation != null)
                    MainWindow.EliminarUserControlDelCanvas(osObjeto.VisualRepresentation);
                HasUnsavedChanges = true;
            }
        }

        private void DuplicarUserControl()
        {
            if (_objectManager.SelectedObjects.Count > 0)
            {
                // Create a copy of the selected objects to avoid issues during iteration
                var objectsToDuplicate = _objectManager.SelectedObjects.ToList();

                // Clear current selection before duplicating
                _objectManager.ClearSelection();

                // Track all newly created objects
                var newObjects = new List<osBase>();

                // Duplicate each object with a small offset
                float offsetX = 0.5f;
                float offsetY = 0.5f;

                foreach (var objToDuplicate in objectsToDuplicate)
                {
                    var newObj = DuplicarObjeto(objToDuplicate, offsetX, offsetY);
                    if (newObj != null)
                    {
                        newObjects.Add(newObj);
                    }
                }

                // Force a complete layout update to ensure all controls are positioned
                MainWindow.ImagenEnTrabajoCanvas.UpdateLayout();

                // Use a dispatcher to delay the selection until the UI has had time to fully render
                Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() =>
                {
                    // Select all newly created objects
                    foreach (var newObj in newObjects)
                    {
                        if (newObj.VisualRepresentation != null)
                        {
                            double left = Canvas.GetLeft(newObj.VisualRepresentation);
                            double top = Canvas.GetTop(newObj.VisualRepresentation);

                            // Only add to selection if the object has valid coordinates
                            if (!double.IsNaN(left) && !double.IsNaN(top) && !double.IsInfinity(left) && !double.IsInfinity(top))
                            {
                                _objectManager.SelectObject(newObj);
                            }
                        }
                    }

                    // Force another layout update before updating selection visuals
                    MainWindow.ImagenEnTrabajoCanvas.UpdateLayout();

                    // Update SelectedItemOsList if there are newly created objects
                    if (newObjects.Count > 0)
                    {
                        // Set to the last duplicated object so it's visible in the property panel
                        SelectedItemOsList = newObjects.LastOrDefault();
                    }

                    // Now update selection visuals
                    _objectManager.UpdateSelectionVisuals();
                }));
            }
        }

        public osBase DuplicarObjeto(osBase objDuplicar, float OffsetX, float OffsetY)
        {
            StopSimulation();
            DisconnectPLC();

            osBase? NuevoObjetoDuplicado = null;

            objDuplicar.SalvarDatosNoSerializables();

            var settings = new JsonSerializerSettings
            {
                Formatting = Formatting.Indented,
                NullValueHandling = NullValueHandling.Ignore,
                TypeNameHandling = TypeNameHandling.All
            };

            try
            {
                // Serializar
                var serializedData = JsonConvert.SerializeObject(objDuplicar, settings);
                // Duplicar
                NuevoObjetoDuplicado = JsonConvert.DeserializeObject<osBase>(serializedData, settings);
                if (NuevoObjetoDuplicado != null)
                {
                    NuevoObjetoDuplicado.Id.ObtenerNuevaID();
                    string nombre = Regex.IsMatch(NuevoObjetoDuplicado.Nombre, @"_\d+$")
                        ? Regex.Replace(NuevoObjetoDuplicado.Nombre, @"_\d+$", $"_{NuevoObjetoDuplicado.Id.Value}")
                        : NuevoObjetoDuplicado.Nombre + "_" + NuevoObjetoDuplicado.Id.Value;

                    NuevoObjetoDuplicado.Nombre = nombre;
                    NuevoObjetoDuplicado.Left += OffsetX;
                    NuevoObjetoDuplicado.Top += OffsetY;
                    ObjetosSimulables.Add(NuevoObjetoDuplicado);
                    CrearUserControlDesdeObjetoSimulable(NuevoObjetoDuplicado);
                    HasUnsavedChanges = true;
                }
            }
            catch
            {
                // Log error or handle it accordingly
            }
            finally
            {
                objDuplicar.RestaurarDatosNoSerializables();
            }
            return NuevoObjetoDuplicado;
        }


        private void EliminarUserControl()
        {
            _objectManager.EliminarObjetosSeleccionados();
            SelectedItemOsList = null;
        }


        private void EliminarTodosCommand()
        {
            var objetosSimulablesCopy = new List<osBase>(ObjetosSimulables);
            foreach (var obj in objetosSimulablesCopy)
                RemoverObjetoSimulable(obj);
        }

        private void EliminarAutoCreatedCommand()
        {
            var osAutoCreated_List = ObjetosSimulables
                                            .Where(o => o.Show_On_This_Page && o.AutoCreated)
                                            .ToList();
            foreach (var obj in osAutoCreated_List)
                RemoverObjetoSimulable(obj);
        }

        private void EliminarClonedCommand()
        {
            var osCloned_List = ObjetosSimulables
                                                .Where(o => o.Show_On_This_Page && o.Cloned)
                                                .ToList();
            foreach (var obj in osCloned_List)
                RemoverObjetoSimulable(obj);
        }

        private void AssingPagesCommand()
        {
            var assignImagesWindow = new AssignImagesWindow();
            var assignImagesViewModel = new AssignImagesViewModel();
            assignImagesViewModel.Initialize(this, assignImagesWindow);
            assignImagesWindow.DataContext = assignImagesViewModel;
            assignImagesWindow.ShowDialog();
            if (assignImagesWindow.DataContext is AssignImagesViewModel dialog && dialog.CloseOK)
                SaveStateObjetosSimulables();
        }

        public async Task WaitForUIUpdateAsync()
        {
            await Task.Yield();
            Application.Current.Dispatcher.Invoke(() => { }, DispatcherPriority.ApplicationIdle);
        }

        private async void MultiPageAnalizeCommand()
        {
            var ImagenesSeleccionadas = new ObservableCollection<string>
            {
                SelectedImage
            };

            StopSimulation();

            var selectPagesWindow = new SelectPages();
            var selectPagesViewModel = new SelectPagesViewModel();
            selectPagesViewModel.Initialize(this, selectPagesWindow, ref ImagenesSeleccionadas);
            selectPagesWindow.DataContext = selectPagesViewModel;
            selectPagesWindow.ShowDialog();

            bool originalHasUnsavedChanges = HasUnsavedChanges;
            HasUnsavedChanges = false;

            try
            {
                if (selectPagesWindow.DataContext is SelectPagesViewModel dialog && dialog.CloseOK)
                {
                    SaveStateObjetosSimulables();  // Guarda el estado antes de cambiar la imagen

                    foreach (var page in ImagenesSeleccionadas)
                    {
                        SelectedImage = page;
                        await WaitForUIUpdateAsync(); // Espera a que la UI se actualice
                        AnalizePageCommand();
                        await WaitForUIUpdateAsync(); // Espera a que la UI se actualice

                        SaveStateObjetosSimulables();  // Guarda el estado antes de cambiar la imagen
                    }
                }
            }
            finally
            {
                HasUnsavedChanges = originalHasUnsavedChanges;
            }

        }

        private void AnalizePageCommand()
        {
            foreach (var obj in ObjetosSimulables)
                if (obj is osBuscarCoincidencias objBC)
                    if (objBC.Show_On_This_Page)
                        objBC.BuscarCoincidencias();

        }


        private void InitializeTipoSimulableList()
        {
            var baseType = typeof(osBase);
            var types = AppDomain.CurrentDomain.GetAssemblies()
                .SelectMany(assembly => assembly.GetTypes())
                .Where(type => type.IsSubclassOf(baseType) && !type.IsAbstract && typeof(IosBase).IsAssignableFrom(type));

            foreach (var type in types)
            {
                var methodInfo = type.GetMethod("NombreClase", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
                string nombre = methodInfo != null ? methodInfo.Invoke(null, null)?.ToString() : "Nombre no encontrado";

                ListaOsBase.Add(new TipoSimulable { Nombre = nombre, Tipo = type });
            }
        }


        private void StartSimulation()
        {
            // Detener simulación de fluidos si está ejecutándose
            StopFluidSimulation();

            IsSimulationRunning = true;

            // Ocultar rectángulos de selección antes de iniciar la simulación
            _objectManager.UpdateSelectionVisuals();

            foreach (var objetoSimulable in ObjetosSimulables)
                objetoSimulable.UpdateGeometryStart();

            simulationManager.Debug_DrawInitialBodies();
            TiempoDesdeStartSimulacion = 0;
            Debug_SimulacionCreado = true;

            _timerSimulacion.Start();
            simulationManager.Start();
        }

        public void StopSimulation()
        {
            IsSimulationRunning = false;

            foreach (var objetoSimulable in ObjetosSimulables)
                objetoSimulable.SimulationStop();

            if (Debug_SimulacionCreado)
            {
                simulationManager.Debug_ClearSimulationShapes();
                Debug_SimulacionCreado = false;
            }
            _timerSimulacion.Stop();

            // Restaurar los rectángulos de selección si hay objetos seleccionados
            _objectManager.UpdateSelectionVisuals();

            // Limpiar historial de undo al detener la simulación
            MainWindow?.ClearUndoHistory();
        }

        /// <summary>
        /// Inicia la simulación de fluidos independiente
        /// </summary>
        public void StartFluidSimulation()
        {
            // Detener simulación física si está ejecutándose
            StopSimulation();

            FluidSimulation.StartFluidSimulation();
            CommandManager.InvalidateRequerySuggested();
        }

        /// <summary>
        /// Detiene la simulación de fluidos independiente
        /// </summary>
        public void StopFluidSimulation()
        {
            FluidSimulation.StopFluidSimulation();
            CommandManager.InvalidateRequerySuggested();
        }

        private void OnTickSimulacion(object sender, EventArgs e)
        {
            var stopwatch = Stopwatch.StartNew(); // Start measuring time

            // Detener el cronómetro y obtener el tiempo transcurrido en milisegundos
            var elapsedMilliseconds = stopwatch_Sim.Elapsed.TotalMilliseconds - stopwatch_SimModel_last;
            stopwatch_SimModel_last = stopwatch_Sim.Elapsed.TotalMilliseconds;

            // Acumular tiempo para el promedio
            accumulatedSimTime += elapsedMilliseconds;
            simSampleCount++;

            // Eliminar el diseño de Debug luego de 2 segundos
            if (TiempoDesdeStartSimulacion > 12000)
                simulationManager.Debug_ClearSimulationShapes();
            else
                TiempoDesdeStartSimulacion += (float)elapsedMilliseconds;

            foreach (var objetoSimulable in ObjetosSimulables)
                objetoSimulable.UpdateGeometryStep();

            simulationManager.Step();

            var objetosSimulablesCopy = new List<osBase>(ObjetosSimulables);

            foreach (var objetoSimulable in objetosSimulablesCopy)
            {
                if (!objetoSimulable.RemoverDesdeSimulacion)
                    objetoSimulable.UpdateControl((int)elapsedMilliseconds);
                else
                    RemoverObjetoSimulable(objetoSimulable);
            }

            stopwatch.Stop(); // Stop measuring time
            //Debug.WriteLine($"OnTickSimulacion execution time: {stopwatch.TotalMilliseconds} ms");
        }

        private void ConnectPLC()
        {
            _timerPLCUpdate.Start();
            PLCViewModel.Connect();
            foreach (var objetoSimulable in ObjetosSimulables)
                objetoSimulable.SetPLC(PLCViewModel);
        }

        public void DisconnectPLC()
        {
            PLCViewModel.Disconnect();
            _timerPLCUpdate.Stop();
            foreach (var objetoSimulable in ObjetosSimulables)
                objetoSimulable.SetPLC(null);

            // Limpiar historial de undo al desconectar del PLC
            MainWindow?.ClearUndoHistory();
        }

        private List<osBase> objetosSimulablesLlamados = new List<osBase>();

        private void OnRefreshEvent(object sender, EventArgs e)
        {
            var stopwatch = Stopwatch.StartNew(); // Start measuring time

            if (PLCViewModel.IsConnected)
            {
                // Detener el cronómetro y obtener el tiempo transcurrido en milisegundos
                var elapsedMilliseconds = stopwatch_Sim.Elapsed.TotalMilliseconds - stopwatch_SimPLC_last;
                stopwatch_SimPLC_last = stopwatch_Sim.Elapsed.TotalMilliseconds;

                // Acumular tiempo para el promedio
                accumulatedPlcTime += elapsedMilliseconds;
                plcSampleCount++;

                // Reiniciar el cronómetro para la próxima medición
                var remainingObjetosSimulables = ObjetosSimulables.Except(objetosSimulablesLlamados).ToList();

                foreach (var objetoSimulable in remainingObjetosSimulables)
                {
                    var objStopwatch = Stopwatch.StartNew();
                    objetoSimulable.UpdatePLC(PLCViewModel, (int)elapsedMilliseconds);
                    objStopwatch.Stop();

                    objetosSimulablesLlamados.Add(objetoSimulable);

                    if (stopwatch.Elapsed.TotalMilliseconds >= 10)
                        break;
                }

                if (remainingObjetosSimulables.Count == 0)
                {
                    objetosSimulablesLlamados.Clear();
                }
            }

            stopwatch.Stop(); // Stop measuring time
                              // Debug.WriteLine($"OnRefreshEvent: {stopwatch.Elapsed.TotalMilliseconds} ms");
        }

        private void OpenWorkDirectory()
        {
            var dialog = new VistaFolderBrowserDialog();
            if (dialog.ShowDialog() == true) // Mostrar el diálogo y comprobar si el resultado es positivo
            {
                directorioTrabajo = dialog.SelectedPath;  // Actualizar la propiedad que también actualiza el estado persistente
            }
        }

        private void OpenRecentDirectory(string path)
        {
            if (Directory.Exists(path))
            {
                directorioTrabajo = path;
            }
            else
            {
                MessageBox.Show($"Directory not found: {path}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                RecentDirectories.Remove(path);
                UpdateRecentDirectories();
            }
        }

        private void UpdateRecentDirectories()
        {
            EstadoPersistente.Instance.RecentDirectories = RecentDirectories.ToList();
            EstadoPersistente.Instance.GuardarEstado();
        }

        private void AddToRecentDirectories(string path)
        {
            // Remove the path if it already exists
            RecentDirectories.Remove(path);

            // Add the new path at the beginning
            RecentDirectories.Insert(0, path);

            // Keep only the last 10 entries
            while (RecentDirectories.Count > 10)
            {
                RecentDirectories.RemoveAt(RecentDirectories.Count - 1);
            }

            UpdateRecentDirectories();
        }

        // 
        //  Lista de osBase
        //

        public void Save()
        {
            SaveStateObjetosSimulables();
        }

        public void SaveStateObjetosSimulables()
        {
            if (SelectedImage != null)
            {
                _stateSerializer.SaveState(SelectedImage);
                HasUnsavedChanges = false;
            }
        }

        // Se cargan los datos de cada UserControl en el StackPanel
        public void CargarPropiedadesosDatos(osBase selectedObject, Controls.PanelEdicionControl PanelEdicion, ResourceDictionary Resources)
        {
            if (selectedObject == null)
            {
                // Clear the property panel when no object is selected
                PanelEdicion.ClearProperties();
            }
            else
            {
                PanelEdicion.CargarPropiedades(selectedObject);
            }
        }

        private RelayCommand saveCommand;
        public ICommand SaveCommand => saveCommand ??= new RelayCommand(Save);

        private void Save(object commandParameter)
        {
        }

        private RelayCommand exitCommand;
        public ICommand ExitCommand => exitCommand ??= new RelayCommand(Exit);

        private void Exit()
        {
            if (HasUnsavedChanges)
            {
                var result = MessageBox.Show("There are unsaved changes. Do you want to save them?",
                    "Save Changes",
                    MessageBoxButton.YesNoCancel);

                if (result == MessageBoxResult.Cancel)
                    return;
                else if (result == MessageBoxResult.Yes)
                    SaveStateObjetosSimulables();
            }
            Application.Current.Shutdown();
        }

        private async void MultiPageMatrixCommand()
        {
            if (HasUnsavedChanges)
            {
                var result = MessageBox.Show("There are unsaved changes. Do you want to save them?",
                    "Save Changes",
                    MessageBoxButton.YesNoCancel);

                if (result == MessageBoxResult.Cancel)
                    return;
                else if (result == MessageBoxResult.Yes)
                    SaveStateObjetosSimulables();
            }

            var ImagenesSeleccionadas = new ObservableCollection<string>
            {
                SelectedImage
            };

            StopSimulation();

            var selectPagesWindow = new SelectPages();
            var selectPagesViewModel = new SelectPagesViewModel();
            selectPagesViewModel.Initialize(this, selectPagesWindow, ref ImagenesSeleccionadas);
            selectPagesWindow.DataContext = selectPagesViewModel;
            selectPagesWindow.ShowDialog();

            inhibitSaveChangesControl = true;
            try
            {
                if (selectPagesWindow.DataContext is SelectPagesViewModel dialog && dialog.CloseOK)
                {
                    var matrixPreviewWindow = new MatrixPreviewWindow();
                    var matrixPreviewViewModel = new MatrixPreviewViewModel();
                    matrixPreviewViewModel.Initialize(this, matrixPreviewWindow, ImagenesSeleccionadas);
                    matrixPreviewWindow.DataContext = matrixPreviewViewModel;
                    matrixPreviewWindow.ShowDialog();
                }
            }
            finally
            {
                inhibitSaveChangesControl = false;
            }
        }

        [ObservableProperty]
        private double simulationSpeed;

        [ObservableProperty]
        private double plcUpdateSpeed;

        private void OnDisplayUpdate(object? sender, EventArgs e)
        {
            if (simSampleCount > 0)
            {
                SimulationSpeed = accumulatedSimTime / simSampleCount;
                accumulatedSimTime = 0;
                simSampleCount = 0;
            }

            if (plcSampleCount > 0)
            {
                PlcUpdateSpeed = accumulatedPlcTime / plcSampleCount;
                accumulatedPlcTime = 0;
                plcSampleCount = 0;
            }
        }

        // Reemplazar la propiedad _multiPropertyEditorWindow por un diccionario
        private Dictionary<string, Windows.MultiPropertyEditorWindow> _propertyEditorWindows = new();

        public void ShowMultiPropertyEditor(IEnumerable<osBase> selectedObjects)
        {
            var objectsList = selectedObjects.ToList();
            // Crear una clave única basada en los IDs de los objetos seleccionados
            string key = string.Join("_", objectsList.Select(o => o.Id.Value).OrderBy(id => id));

            // Verificar si ya existe una ventana para esta selección
            if (_propertyEditorWindows.TryGetValue(key, out var existingWindow) &&
                existingWindow.IsVisible)
            {
                existingWindow.Activate();
                return;
            }

            // Crear nueva ventana
            var window = new Windows.MultiPropertyEditorWindow(objectsList, MainWindow);
            window.Closed += (s, e) => _propertyEditorWindows.Remove(key);
            _propertyEditorWindows[key] = window;

            window.Show();
            HasUnsavedChanges = true;
            _objectManager.UpdateSelectionVisuals();
        }

        // Add this method to MainViewModel class
        public void NotifySelectionChanged()
        {
            OnPropertyChanged(nameof(SelectedItemOsList));
        }

        // Diccionario para manejar la ventana de configuración de escala
        private PopUps.ScaleConfigWindow? _scaleConfigWindow;

        // Método para configurar la escala desde el menú contextual
        public void ConfigureScale()
        {
            // Verificar si ya existe una ventana de configuración de escala
            if (_scaleConfigWindow != null && _scaleConfigWindow.IsVisible)
            {
                _scaleConfigWindow.Activate();
                return;
            }

            var currentScale = PixelToMeter.Instance.calc.Scale;
            _scaleConfigWindow = new PopUps.ScaleConfigWindow(currentScale, this);
            _scaleConfigWindow.Owner = MainWindow;

            // Manejar el cierre de la ventana
            _scaleConfigWindow.Closed += (s, e) => _scaleConfigWindow = null;

            // Mostrar como modeless (no modal)
            _scaleConfigWindow.Show();
        }

        // Método público para aplicar la escala desde la ventana modeless
        public void ApplyScale(float newScale)
        {
            // Detener simulaciones antes de cambiar la escala
            StopSimulation();
            StopFluidSimulation();
            DisconnectPLC();

            // Actualizar la escala en el UnitConverter
            PixelToMeter.Instance.calc.SetScale(newScale);

            // Forzar redibujo completo del canvas y todos los objetos
            ForceCanvasRedraw();

            // Marcar como cambios no guardados ya que esto afecta la configuración de la imagen
            HasUnsavedChanges = true;

            // Limpiar historial de undo después de cambiar la escala
            MainWindow?.ClearUndoHistory();
        }

        // Método para forzar el redibujo completo del canvas
        private void ForceCanvasRedraw()
        {
            // Forzar actualización de todos los objetos simulables mediante PropertyChanged
            foreach (var obj in ObjetosSimulables)
            {
                // Forzar la notificación de PropertyChanged para las propiedades de posición y tamaño
                // Esto hará que los bindings y converters se recalculen automáticamente
                obj.ForceUpdatePositionBindings();
            }

            // Forzar actualización del layout del canvas principal
            MainWindow?.ImagenEnTrabajoCanvas?.InvalidateVisual();
            MainWindow?.ImagenEnTrabajoCanvas?.UpdateLayout();

            // Actualizar selecciones visuales si existen
            _objectManager?.UpdateSelectionVisuals();

            // Forzar actualización global del layout con prioridad de renderizado
            Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() =>
            {
                MainWindow?.ImagenEnTrabajoCanvas?.InvalidateVisual();
                MainWindow?.ImagenEnTrabajoCanvas?.UpdateLayout();
            }));
        }

        // Diccionario para manejar ventanas de biblioteca múltiples
        private Dictionary<string, PopUps.LibraryWindow> _libraryWindows = new();

        private void ShowLibraryManager()
        {
            const string libraryWindowKey = "LibraryManager";

            // Verificar si ya existe una ventana de biblioteca
            if (_libraryWindows.TryGetValue(libraryWindowKey, out var existingWindow) &&
                existingWindow.IsVisible)
            {
                existingWindow.Activate();
                return;
            }

            // Crear nueva ventana modeless
            var libraryWindow = new PopUps.LibraryWindow();
            var libraryViewModel = new PopUps.LibraryWindowViewModel(this);
            libraryWindow.DataContext = libraryViewModel;
            libraryWindow.Owner = MainWindow;

            // Manejar el cierre de la ventana
            libraryWindow.Closed += (s, e) => _libraryWindows.Remove(libraryWindowKey);

            // Registrar la ventana
            _libraryWindows[libraryWindowKey] = libraryWindow;

            // Mostrar como modeless (no modal)
            libraryWindow.Show();
        }
    }

    public class SimulationData
    {
        public ObservableCollection<osBase>? ObjetosSimulables { get; set; }
        public UnitConverter? UnitConverter { get; set; }
        public PLCViewModel? PLC_ConnectionData { get; set; }

        // Nueva propiedad para almacenar los datos locales de objetos globales
        public ObservableCollection<DatosLocales>? DatosLocalesObjetos { get; set; }

        // Diccionario para almacenar datos expandidos de imágenes
        public Dictionary<string, Models.ImageData>? ImageDataDictionary { get; set; }

        // Compatibilidad con versiones anteriores - OBSOLETO
        [System.Obsolete("Use ImageDataDictionary instead")]
        public Dictionary<string, string>? ImageCustomNames { get; set; }
    }

    public class TipoSimulable
    {
        public string? Nombre { get; set; }
        public Type? Tipo { get; set; }
    }

    public class TickSimulacionEventArgs : EventArgs
    {
        // Aquí puedes agregar propiedades o campos para pasar información adicional
        // en el evento TickSimulacion
    }

}
