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 stub implementation
using System.IO;
using Newtonsoft.Json;
using System.Windows;
using CtrEditor.Simulacion;
using CtrEditor.HydraulicSimulator; // Nuevo using para el simulador hidráulico
using System.Diagnostics;
using System.Reflection;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using Xceed.Wpf.Toolkit.PropertyGrid;
using CtrEditor.ObjetosSim.Extraccion_Datos;
using ClosedXML.Excel;
using System.Timers; // Para el nuevo timer de simulación más preciso
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 System.Linq;
using System.Windows.Media;


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;

        // Sistema adaptativo para timing de PLC
        private double lastPlcExecutionTime = 0;
        private double maxPlcExecutionTime = 0;
        private int plcTimingAdaptationCounter = 0;
        private const int PLC_ADAPTATION_SAMPLES = 10; // Evaluar cada 10 muestras
        private const double MIN_PLC_INTERVAL = 10; // Mínimo intervalo PLC (ms)
        private const double MAX_PLC_INTERVAL = 100; // Máximo intervalo PLC (ms)
        private const double SIM_PRIORITY_TIME = 10; // Tiempo reservado para simulación (ms)

        private float TiempoDesdeStartSimulacion;
        private bool Debug_SimulacionCreado = false;

        public SimulationManagerBEPU simulationManager = new SimulationManagerBEPU();
        public HydraulicSimulationManager hydraulicSimulationManager = new HydraulicSimulationManager();

        private readonly System.Timers.Timer _timerSimulacion; // Cambiado a System.Timers.Timer para mejor precisión
        private readonly System.Timers.Timer _timerPLCUpdate; // Cambiado a System.Timers.Timer para mejor precisión
        private readonly DispatcherTimer _timerDisplayUpdate;
        private readonly DispatcherTimer _timer3DUpdate; // Nuevo timer para actualización 3D cuando simulación está detenida

        public Canvas MainCanvas;

        // Manager para la visualización 3D
        public BEPUVisualization3DManager Visualization3DManager { get; set; }

        [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;

        [ObservableProperty]
        public ObservableCollection<CategoriaNode> categoriasOsBase;

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

        // Comandos para vista 3D
        public ICommand TB3DViewTopCommand { get; }
        public ICommand TB3DViewSideCommand { get; }
        public ICommand TB3DViewFrontCommand { get; }
        public ICommand TB3DViewIsometricCommand { 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

            // Controlar el timer de actualización 3D
            if (value)
            {
                // Simulación iniciada - detener timer 3D (la sincronización se hace en Step())
                _timer3DUpdate.Stop();
            }
            else
            {
                // Simulación detenida - iniciar timer 3D para mantener sincronización
                _timer3DUpdate.Start();
            }
        }

        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 Models.WorkspaceConfiguration workspaceConfig;

        [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>();
            CategoriasOsBase = new ObservableCollection<CategoriaNode>();

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

            _timerPLCUpdate = new System.Timers.Timer();
            _timerPLCUpdate.Interval = 10; // 10ms - más preciso que DispatcherTimer
            _timerPLCUpdate.Elapsed += OnRefreshEvent;
            _timerPLCUpdate.AutoReset = true; // Reinicio automático
            _timerPLCUpdate.SynchronizingObject = null; // No sincronizar automáticamente con UI

            InitializeTipoSimulableList();

            ItemDoubleClickCommand = new ParameterizedRelayCommand(ExecuteDoubleClick);

            _timerSimulacion = new System.Timers.Timer();
            _timerSimulacion.Interval = 10; // 10ms - más preciso que DispatcherTimer
            _timerSimulacion.Elapsed += OnTickSimulacion;
            _timerSimulacion.AutoReset = true; // Reinicio automático
            _timerSimulacion.SynchronizingObject = null; // No sincronizar automáticamente con UI

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

            _timer3DUpdate = new DispatcherTimer();
            _timer3DUpdate.Interval = TimeSpan.FromMilliseconds(20);
            _timer3DUpdate.Tick += OnTick3DUpdate;
            _timer3DUpdate.Start(); // Iniciar porque la simulación empieza detenida

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

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


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

            // Comandos para vista 3D
            TB3DViewTopCommand = new RelayCommand(() => Visualization3DManager?.SetCameraView(CameraView.Top));
            TB3DViewSideCommand = new RelayCommand(() => Visualization3DManager?.SetCameraView(CameraView.Side));
            TB3DViewFrontCommand = new RelayCommand(() => Visualization3DManager?.SetCameraView(CameraView.Front));
            TB3DViewIsometricCommand = new RelayCommand(() => Visualization3DManager?.SetCameraView(CameraView.Isometric));
            TBToggle3DUpdateCommand = new RelayCommand(() => Is3DUpdateEnabled = !Is3DUpdateEnabled);
            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);

            // NOTA: La conexión del manager 3D se hace en MainWindow_Loaded
            // después de que se cree la instancia de BEPUVisualization3DManager

            // Inicializar configuración del workspace
            WorkspaceConfig = new Models.WorkspaceConfiguration();
        }

        #region Workspace Configuration Management

        /// <summary>
        /// Captura el estado actual del workspace desde la UI
        /// </summary>
        public void CaptureWorkspaceState()
        {
            if (MainWindow == null) return;

            try
            {
                // Capturar posición del GridSplitter (altura de la fila 3 que contiene la vista 3D)
                var middleGrid = MainWindow.FindName("MiddleColumnGrid") as Grid;
                if (middleGrid != null && middleGrid.RowDefinitions.Count > 3)
                {
                    // Capturar la altura actual de la fila 3 (vista 3D) en píxeles
                    var row3ActualHeight = middleGrid.RowDefinitions[3].ActualHeight;
                    WorkspaceConfig.GridSplitterPosition = row3ActualHeight;
                }

                // Capturar estado del canvas y scroll
                var scrollViewer = MainWindow.FindName("ImagenEnTrabajoScrollViewer") as ScrollViewer;
                if (scrollViewer != null)
                {
                    WorkspaceConfig.HorizontalScrollOffset = scrollViewer.HorizontalOffset;
                    WorkspaceConfig.VerticalScrollOffset = scrollViewer.VerticalOffset;
                }

                // Capturar transformaciones del canvas
                var canvas = MainWindow.FindName("ImagenEnTrabajoCanvas") as Canvas;
                if (canvas?.RenderTransform is TransformGroup transformGroup)
                {
                    var scaleTransform = transformGroup.Children.OfType<ScaleTransform>().FirstOrDefault();
                    var translateTransform = transformGroup.Children.OfType<TranslateTransform>().FirstOrDefault();

                    if (scaleTransform != null)
                    {
                        WorkspaceConfig.ZoomLevel = scaleTransform.ScaleX;
                    }

                    if (translateTransform != null)
                    {
                        WorkspaceConfig.CanvasOffsetX = translateTransform.X;
                        WorkspaceConfig.CanvasOffsetY = translateTransform.Y;
                    }
                }

                // Capturar centro del canvas
                var centerPixels = MainWindow.ObtenerCentroCanvasPixels();
                WorkspaceConfig.CanvasCenterX = centerPixels.X;
                WorkspaceConfig.CanvasCenterY = centerPixels.Y;

                // Capturar estado de Is3DUpdateEnabled
                WorkspaceConfig.Is3DUpdateEnabled = Is3DUpdateEnabled;
            }
            catch (Exception ex)
            {
                // Log error but don't throw to avoid interrupting save process
                System.Diagnostics.Debug.WriteLine($"Error capturing workspace state: {ex.Message}");
            }
        }

        /// <summary>
        /// Restaura el estado del workspace a la UI
        /// </summary>
        public void RestoreWorkspaceState()
        {
            if (MainWindow == null || WorkspaceConfig == null) return;

            try
            {
                // Restaurar posición del GridSplitter (altura de la fila 3 que contiene la vista 3D)
                var middleGrid = MainWindow.FindName("MiddleColumnGrid") as Grid;
                if (middleGrid != null && middleGrid.RowDefinitions.Count > 3)
                {
                    // Establecer la altura de la vista 3D en píxeles absolutos
                    middleGrid.RowDefinitions[3].Height = new GridLength(WorkspaceConfig.GridSplitterPosition, GridUnitType.Pixel);
                }

                // Restaurar estado del canvas y scroll
                var scrollViewer = MainWindow.FindName("ImagenEnTrabajoScrollViewer") as ScrollViewer;
                if (scrollViewer != null)
                {
                    scrollViewer.ScrollToHorizontalOffset(WorkspaceConfig.HorizontalScrollOffset);
                    scrollViewer.ScrollToVerticalOffset(WorkspaceConfig.VerticalScrollOffset);
                }

                // Restaurar transformaciones del canvas
                var canvas = MainWindow.FindName("ImagenEnTrabajoCanvas") as Canvas;
                if (canvas?.RenderTransform is TransformGroup transformGroup)
                {
                    var scaleTransform = transformGroup.Children.OfType<ScaleTransform>().FirstOrDefault();
                    var translateTransform = transformGroup.Children.OfType<TranslateTransform>().FirstOrDefault();

                    if (scaleTransform != null)
                    {
                        scaleTransform.ScaleX = WorkspaceConfig.ZoomLevel;
                        scaleTransform.ScaleY = WorkspaceConfig.ZoomLevel;
                    }

                    if (translateTransform != null)
                    {
                        translateTransform.X = WorkspaceConfig.CanvasOffsetX;
                        translateTransform.Y = WorkspaceConfig.CanvasOffsetY;
                    }
                }

                // Restaurar estado de Is3DUpdateEnabled
                Is3DUpdateEnabled = WorkspaceConfig.Is3DUpdateEnabled;
            }
            catch (Exception ex)
            {
                // Log error but don't throw to avoid interrupting load process
                System.Diagnostics.Debug.WriteLine($"Error restoring workspace state: {ex.Message}");
            }
        }

        /// <summary>
        /// Inicializa la configuración del workspace con valores por defecto
        /// </summary>
        public void InitializeDefaultWorkspaceConfig()
        {
            WorkspaceConfig = new Models.WorkspaceConfiguration();
        }

        #endregion

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

                // Registrar objetos hidráulicos cargados
                RegisterLoadedHydraulicObjects();

                // 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);
                    
                    // Registrar en el simulador hidráulico si tiene componentes hidráulicos
                    RegisterHydraulicObjectIfNeeded(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))
            {
                // Desregistrar del simulador hidráulico si estaba registrado
                UnregisterHydraulicObjectIfNeeded(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);
                    
                    // Registrar en el simulador hidráulico si tiene componentes hidráulicos
                    RegisterHydraulicObjectIfNeeded(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));

            var categorias = new Dictionary<string, CategoriaNode>();

            foreach (var type in types)
            {
                var methodInfoNombre = type.GetMethod("NombreClase", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
                var methodInfoCategoria = type.GetMethod("NombreCategoria", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
                
                string nombre = methodInfoNombre != null ? methodInfoNombre.Invoke(null, null)?.ToString() : "Nombre no encontrado";
                string categoria = methodInfoCategoria != null ? methodInfoCategoria.Invoke(null, null)?.ToString() : "Sin categoría";

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

                // Crear o obtener el nodo de categoría
                if (!categorias.ContainsKey(categoria))
                {
                    categorias[categoria] = new CategoriaNode(categoria);
                }
                
                // Agregar el tipo simulable a la categoría correspondiente
                categorias[categoria].Elementos.Add(tipoSimulable);
            }

            // Ordenar categorías y sus elementos
            foreach (var categoria in categorias.Values.OrderBy(c => c.Nombre))
            {
                // Ordenar elementos dentro de cada categoría por nombre
                var elementosOrdenados = categoria.Elementos.OrderBy(e => e.Nombre).ToList();
                categoria.Elementos.Clear();
                foreach (var elemento in elementosOrdenados)
                {
                    categoria.Elementos.Add(elemento);
                }
                
                CategoriasOsBase.Add(categoria);
            }
        }


        private void StartSimulation()
        {

            IsSimulationRunning = true;

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

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


            TiempoDesdeStartSimulacion = 0;
            Debug_SimulacionCreado = true;

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

        public void StopSimulation()
        {
            IsSimulationRunning = false;

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

            // Reiniciar simulador hidráulico al detener la simulación
            ResetHydraulicSimulation();

            if (Debug_SimulacionCreado)
            {
                Debug_SimulacionCreado = false;
            }
            _timerSimulacion.Stop();
            _timerPLCUpdate.Stop(); // También detener el timer PLC

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

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



        private void OnTickSimulacion(object sender, System.Timers.ElapsedEventArgs e)
        {
            // Verificar si la aplicación todavía está disponible
            if (Application.Current == null) return;
            
            // Ejecutar en el thread de UI para acceso a controles
            Application.Current.Dispatcher.Invoke(() =>
            {
                var executionStopwatch = Stopwatch.StartNew(); // ✅ NUEVO: Medir tiempo de ejecución del método

                // ✅ MANTENER: Tiempo entre llamadas del timer para lógica interna
                var timeBetweenCalls = stopwatch_Sim.Elapsed.TotalMilliseconds - stopwatch_SimModel_last;
                stopwatch_SimModel_last = stopwatch_Sim.Elapsed.TotalMilliseconds;

                // Acumular tiempo para el promedio (usando tiempo real del timer)
                accumulatedSimTime += timeBetweenCalls;
                simSampleCount++;

                // Contador de tiempo desde el inicio de la simulación
                if (TiempoDesdeStartSimulacion <= 1200)
                    TiempoDesdeStartSimulacion += (float)timeBetweenCalls;

                // ✅ NUEVO: Optimización temprana para escenarios sin objetos
                if (ObjetosSimulables?.Count > 0)
                {
                    foreach (var objetoSimulable in ObjetosSimulables)
                        objetoSimulable.UpdateGeometryStep();
                }

                // Ejecutar simulación física BEPU
                simulationManager.Step();
                
                // Ejecutar simulación hidráulica
                hydraulicSimulationManager.Step((float)(timeBetweenCalls / 1000.0)); // Convertir ms a segundos

                // ✅ NUEVO: Solo crear la copia si hay objetos para procesar
                if (ObjetosSimulables?.Count > 0)
                {
                    var objetosSimulablesCopy = new List<osBase>(ObjetosSimulables);

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

                executionStopwatch.Stop(); // ✅ CORREGIDO: Detener cronómetro de ejecución
                //Debug.WriteLine($"OnTickSimulacion execution time: {stopwatch.ElapsedMilliseconds} ms");
                //Debug.WriteLine($"OnTickSimulacion execution time: {executionStopwatch.Elapsed.TotalMilliseconds:F2}ms | Timer interval: {timeBetweenCalls:F2}ms | Objects: {ObjetosSimulables?.Count ?? 0}");
            });
        }

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

        /// <summary>
        /// Limpia todos los recursos y detiene todos los timers antes del cierre de la aplicación
        /// </summary>
        public void CleanupResources()
        {
            // Detener simulación si está corriendo
            if (IsSimulationRunning)
            {
                StopSimulation();
            }

            // Detener todos los timers
            _timerSimulacion?.Stop();
            _timerPLCUpdate?.Stop();
            _timer3DUpdate?.Stop();

            // Desconectar PLC si está conectado
            if (PLCViewModel?.IsConnected == true)
            {
                PLCViewModel.Disconnect();
            }
        }

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

        private void OnRefreshEvent(object sender, System.Timers.ElapsedEventArgs e)
        {
            // Verificar si la aplicación todavía está disponible
            if (Application.Current == null) return;
            
            // Ejecutar en el thread de UI para acceso a controles
            Application.Current.Dispatcher.Invoke(() =>
            {
                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
                
                // Sistema adaptativo de timing PLC
                AdaptPlcTiming(stopwatch.Elapsed.TotalMilliseconds);
                
                // Debug.WriteLine($"OnRefreshEvent: {stopwatch.Elapsed.TotalMilliseconds} ms, Interval: {_timerPLCUpdate.Interval}ms");
            });
        }

        private void AdaptPlcTiming(double executionTime)
        {
            lastPlcExecutionTime = executionTime;
            maxPlcExecutionTime = Math.Max(maxPlcExecutionTime, executionTime);
            plcTimingAdaptationCounter++;

            // Evaluar cada PLC_ADAPTATION_SAMPLES ejecuciones
            if (plcTimingAdaptationCounter >= PLC_ADAPTATION_SAMPLES)
            {
                double currentInterval = _timerPLCUpdate.Interval;
                double newInterval = currentInterval;

                // Si el tiempo máximo de ejecución + tiempo para simulación > intervalo actual
                if (maxPlcExecutionTime + SIM_PRIORITY_TIME > currentInterval)
                {
                    // Incrementar intervalo para dar más tiempo
                    newInterval = Math.Min(maxPlcExecutionTime + SIM_PRIORITY_TIME + 5, MAX_PLC_INTERVAL);
                    Debug.WriteLine($"PLC Timer: Aumentando intervalo a {newInterval:F1}ms (ejecución máxima: {maxPlcExecutionTime:F1}ms)");
                }
                // Si hemos estado por debajo del umbral, podemos reducir el intervalo
                else if (maxPlcExecutionTime + SIM_PRIORITY_TIME < currentInterval * 0.7)
                {
                    newInterval = Math.Max(maxPlcExecutionTime + SIM_PRIORITY_TIME + 2, MIN_PLC_INTERVAL);
                    Debug.WriteLine($"PLC Timer: Reduciendo intervalo a {newInterval:F1}ms (ejecución máxima: {maxPlcExecutionTime:F1}ms)");
                }

                // Aplicar el nuevo intervalo si cambió
                if (Math.Abs(newInterval - currentInterval) > 1)
                {
                    _timerPLCUpdate.Interval = newInterval;
                }

                // Reset para el próximo período de evaluación
                maxPlcExecutionTime = 0;
                plcTimingAdaptationCounter = 0;
            }
        }

        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;

        [ObservableProperty]
        private bool is3DUpdateEnabled = true;

        public ICommand TBToggle3DUpdateCommand { get; }

        partial void OnIs3DUpdateEnabledChanged(bool value)
        {
            // Sincronizar la propiedad con el simulationManager
            if (simulationManager != null)
            {
                simulationManager.Is3DUpdateEnabled = value;
            }
        }

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

        private void OnTick3DUpdate(object? sender, EventArgs e)
        {
            // Solo actualizar Helix3D si la simulación está detenida y la actualización 3D está habilitada
            // Cuando la simulación está corriendo, la sincronización se hace en simulationManager.Step()
            if (!IsSimulationRunning && Is3DUpdateEnabled && Visualization3DManager != null)
            {
                Visualization3DManager.SynchronizeWorld();
            }
        }

        #region Hydraulic Simulation Management
        
        /// <summary>
        /// Registra un objeto en el simulador hidráulico si tiene componentes hidráulicos
        /// </summary>
        private void RegisterHydraulicObjectIfNeeded(osBase obj)
        {
            if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents)
            {
                hydraulicSimulationManager.RegisterHydraulicObject(obj);
                Debug.WriteLine($"Objeto hidráulico registrado: {obj.Nombre}");
            }
        }
        
        /// <summary>
        /// Desregistra un objeto del simulador hidráulico si estaba registrado
        /// </summary>
        private void UnregisterHydraulicObjectIfNeeded(osBase obj)
        {
            // Desregistrar independientemente de si implementa la interfaz actualmente
            // (podría haber cambiado desde que se registró)
            hydraulicSimulationManager.UnregisterHydraulicObject(obj);
            Debug.WriteLine($"Objeto desregistrado del simulador hidráulico: {obj.Nombre}");
        }
        
        /// <summary>
        /// Reinicia el simulador hidráulico
        /// </summary>
        public void ResetHydraulicSimulation()
        {
            hydraulicSimulationManager.Reset();
            Debug.WriteLine("Simulador hidráulico reiniciado");
        }
        
        /// <summary>
        /// Obtiene estadísticas del simulador hidráulico
        /// </summary>
        public string GetHydraulicSimulationStats()
        {
            return hydraulicSimulationManager.GetSimulationStats();
        }
        
        /// <summary>
        /// Habilita/deshabilita la simulación hidráulica
        /// </summary>
        public void SetHydraulicSimulationEnabled(bool enabled)
        {
            hydraulicSimulationManager.IsHydraulicSimulationEnabled = enabled;
            Debug.WriteLine($"Simulación hidráulica {(enabled ? "habilitada" : "deshabilitada")}");
        }
        
        /// <summary>
        /// Registra todos los objetos hidráulicos existentes después de cargar un proyecto
        /// </summary>
        private void RegisterLoadedHydraulicObjects()
        {
            // Limpiar registros previos
            hydraulicSimulationManager.ClearHydraulicObjects();
            
            // Registrar todos los objetos cargados que tengan componentes hidráulicos
            foreach (var obj in ObjetosSimulables)
            {
                RegisterHydraulicObjectIfNeeded(obj);
            }
            
            Debug.WriteLine($"Registrados {hydraulicSimulationManager.HydraulicObjects.Count} objetos hidráulicos tras cargar proyecto");
        }
        
        #endregion

    }

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

        // Configuración del entorno de trabajo para cada imagen
        public Models.WorkspaceConfiguration? WorkspaceConfig { 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 string? Categoria { get; set; }
    }

    public partial class CategoriaNode : ObservableObject
    {
        [ObservableProperty]
        private string nombre;

        [ObservableProperty]
        private ObservableCollection<TipoSimulable> elementos;

        [ObservableProperty]
        private bool isExpanded = true;

        public CategoriaNode(string nombre)
        {
            this.nombre = nombre;
            elementos = new ObservableCollection<TipoSimulable>();
        }
    }

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

}
