using CommunityToolkit.Mvvm.ComponentModel;
using CtrEditor.FuncionesBase;
using CtrEditor.Serialization;
using CtrEditor.Services;
using CtrEditor.Simulacion;
using LibS7Adv;
using System.Numerics;
using PaddleOCRSharp;
using Siemens.Simatic.Simulation.Runtime;
using System.ComponentModel;  // Para poder usar [property: Category ...
using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using Application = System.Windows.Application;
using ItemCollection = Xceed.Wpf.Toolkit.PropertyGrid.Attributes.ItemCollection;
using JsonIgnoreAttribute = Newtonsoft.Json.JsonIgnoreAttribute;
using UserControl = System.Windows.Controls.UserControl;

namespace CtrEditor.ObjetosSim
{

    public interface IosBase
    {
        static abstract string NombreClase();
        static abstract string NombreCategoria();
    }

    public interface IDataContainer
    {
        [JsonIgnore]
        osBase? Datos { get; set; }
        int zIndex_fromFrames { get; set; }
        void Highlight(bool State);
        ZIndexEnum ZIndex_Base();
    }

    public class DataSaveToSerialize
    {
        private MainViewModel? _mainViewModel;
        private UserControl? VisualRepresentation;
        private SimulationManagerBEPU? simulationManager;

        public DataSaveToSerialize(MainViewModel a, UserControl b, SimulationManagerBEPU c)
        {
            _mainViewModel = a;
            VisualRepresentation = b;
            simulationManager = c;
        }

        public void DataRestoreAfterSerialize(out MainViewModel a, out UserControl b, out SimulationManagerBEPU c)
        {
            a = _mainViewModel;
            b = VisualRepresentation;
            c = simulationManager;
        }

    }

    /// <summary>
    /// Estructura para almacenar las dimensiones de los objetos de simulación
    /// </summary>
    public struct SimObjectDimensions
    {
        public float Width { get; set; }
        public float Height { get; set; }
        public float Radius { get; set; }
        public int ObjectType { get; set; } // Tipo de objeto: 1=Transport, 2=Barrier, 3=Guide, etc.

        public bool Equals(SimObjectDimensions other)
        {
            return Math.Abs(Width - other.Width) < 0.001f &&
                   Math.Abs(Height - other.Height) < 0.001f &&
                   Math.Abs(Radius - other.Radius) < 0.001f &&
                   ObjectType == other.ObjectType;
        }
    }

    public abstract partial class osBase : ObservableObject
    {
        public virtual string Nombre { get; set; } = "osBase";

        [JsonIgnore]
        private Storyboard _storyboard;
        [JsonIgnore]
        private System.Threading.Timer timer = null;

        // ✅ SISTEMA DE DIMENSIONES: Para evitar recreación innecesaria de objetos físicos
        [JsonIgnore]
        private static Dictionary<simBase, SimObjectDimensions> _lastKnownDimensions = new Dictionary<simBase, SimObjectDimensions>();


        [ObservableProperty]
        [property: JsonIgnore]
        [property: Hidden]
        public bool isVisFilter;

        public UniqueId Id { get; set; }


        [ObservableProperty]
        [property: Category("Configuración")]
        [property: Description("Bloquear movimiento del objeto")]
        [property: Name("Bloquear Movimiento")]
        private bool lock_movement;

        [ObservableProperty]
        [property: Category("Configuración")]
        [property: Description("Coordenada X del objeto")]
        [property: Name("Izquierda")]
        private float left;

        public void CheckData()
        {
            if (Id is null)
                Id = new UniqueId().ObtenerNuevaID();
        }

        partial void OnLeftChanged(float value)
        {
            CanvasSetLeftinMeter(value);
            LeftChanged(value);
        }

        partial void OnLeftChanging(float oldValue, float newValue)
        {
            LeftChanging(oldValue, newValue);
        }

        public virtual void LeftChanged(float value)
        {
            // Actualizar posición relativa si el movimiento no viene del FramePlate
            UpdateFramePlateRelativePosition();
        }

        public virtual void LeftChanging(float oldValue, float newValue) { }

        [ObservableProperty]
        [property: Category("Configuración")]
        [property: Description("Coordenada Y del objeto")]
        [property: Name("Superior")]
        private float top;

        partial void OnTopChanged(float value)
        {
            CanvasSetTopinMeter(value);
            TopChanged(value);
        }
        partial void OnTopChanging(float oldValue, float newValue)
        {
            TopChanging(oldValue, newValue);
        }

        public virtual void TopChanged(float value)
        {
            // Actualizar posición relativa si el movimiento no viene del FramePlate
            UpdateFramePlateRelativePosition();
        }
        public virtual void TopChanging(float oldValue, float newValue) { }


        [ObservableProperty]
        [property: Category("Configuración")]
        [property: Description("Ancho del objeto")]
        [property: Name("Ancho")]
        private float ancho;

        partial void OnAnchoChanged(float value)
        {
            AnchoChanged(value);
        }
        public virtual void AnchoChanged(float value) { }

        [ObservableProperty]
        [property: Category("Configuración")]
        [property: Description("Alto del objeto")]
        [property: Name("Alto")]
        private float alto;

        partial void OnAltoChanged(float value)
        {
            AltoChanged(value);
        }
        public virtual void AltoChanged(float value) { }


        [ObservableProperty]
        [property: Category("Configuración")]
        [property: Description("Ángulo de rotación del objeto")]
        [property: Name("Ángulo")]
        private float angulo;

        partial void OnAnguloChanged(float value)
        {
            AnguloChanged(value);
        }
        partial void OnAnguloChanging(float oldValue, float newValue)
        {
            AnguloChanging(oldValue, newValue);
        }

        public virtual void AnguloChanged(float value) { }
        public virtual void AnguloChanging(float oldValue, float newValue) { }

        [ObservableProperty]
        [property: Category("Identificación")]
        [property: Description("Etiquetas para clasificar el objeto. Use # antes de cada etiqueta (ej: #motor #bomba #critico)")]
        [property: Name("Etiquetas")]
        [property: Editor(typeof(TagPropertyEditor), typeof(TagPropertyEditor))]
        [property: TagEditor]
        private string etiquetas = "";

        partial void OnEtiquetasChanged(string value)
        {
            EtiquetasChanged(value);
            // Update the visibility filters when tags change
            _mainViewModel?.MainWindow?.VisFilter?.UpdateAvailableTags(_mainViewModel.ObjetosSimulables);
        }

        public virtual void EtiquetasChanged(string value) { }

        /// <summary>
        /// Obtiene la lista de etiquetas parseadas desde el string
        /// </summary>
        [JsonIgnore]
        public List<string> ListaEtiquetas
        {
            get
            {
                if (string.IsNullOrWhiteSpace(Etiquetas))
                    return new List<string>();

                return Etiquetas
                    .Split(' ', StringSplitOptions.RemoveEmptyEntries)
                    .Where(tag => tag.StartsWith("#") && tag.Length > 1)
                    .Select(tag => tag.Substring(1).ToLowerInvariant())
                    .Distinct()
                    .ToList();
            }
        }

        /// <summary>
        /// Verifica si el objeto tiene una etiqueta específica
        /// </summary>
        /// <param name="etiqueta">La etiqueta a buscar (sin el #)</param>
        /// <returns>True si el objeto tiene la etiqueta</returns>
        public bool TieneEtiqueta(string etiqueta)
        {
            return ListaEtiquetas.Contains(etiqueta.ToLowerInvariant());
        }


        public void Resize(double width, double height)
        {
            Resize((float)width, (float)height);
        }

        public void Resize(float Delta_W_pixels, float Delta_H_pixels)
        {
            var Delta_W = PixelToMeter.Instance.calc.PixelsToMeters(Delta_W_pixels);
            var Delta_H = PixelToMeter.Instance.calc.PixelsToMeters(Delta_H_pixels);

            OnResize(Delta_W, Delta_H);
            OnMoveResizeRotate();

            if ((Angulo >= 45 && Angulo <= 135) || (Angulo >= 225 && Angulo <= 315))
            {
                Ancho += Delta_H;
                Alto += Delta_W;
            }
            else
            {
                Ancho += Delta_W;
                Alto += Delta_H;
            }
            if (Ancho < 0.01f) Ancho = 0.01f;
            if (Alto < 0.01f) Alto = 0.01f;
        }
        public virtual void OnResize(float Delta_Width, float Delta_Height) { }

        public void Move(double LeftPixels, double TopPixels)
        {
            Move((float)LeftPixels, (float)TopPixels);
        }
        public void Move(float LeftPixels, float TopPixels)
        {
            Left = PixelToMeter.Instance.calc.PixelsToMeters(LeftPixels);
            Top = PixelToMeter.Instance.calc.PixelsToMeters(TopPixels);
            OnMove(LeftPixels, TopPixels);
            OnMoveResizeRotate();
        }
        public virtual void OnMove(float LeftPixels, float TopPixels) { }

        public void Rotate(double Delta_Angle)
        {
            Rotate((float)Delta_Angle);
        }
        public void Rotate(float Delta_Angle)
        {
            Angulo += Delta_Angle;
            if (Angulo <= -360)
                Angulo += 360;
            if (Angulo >= 360)
                Angulo -= 360;

            OnRotate(Delta_Angle);
            OnMoveResizeRotate();
        }
        public virtual void OnRotate(float Delta_Angle) { }

        public virtual void OnMoveResizeRotate() { }

        public void InicializaNuevoObjeto()
        {
            Id = new UniqueId().ObtenerNuevaID();
        }

        // Group as FacePlate
        [ObservableProperty]
        [property: Category("Agrupación")]
        [property: Description("Enlace a una placa de texto como ancla")]
        [property: Name("Panel Grupo")]
        [property: ItemsSource(typeof(osBaseItemsSource<osTextPlate>))]
        private string group_Panel;

        [JsonIgnore]
        private osTextPlate TextPlate;
        [JsonIgnore]
        private bool isUpdatingFromTextPlate = false;

        partial void OnGroup_PanelChanged(string value)
        {
            if (TextPlate != null)
                TextPlate.PropertyChanged -= OnTextPlatePropertyChanged;
            if (_mainViewModel != null && value != null && value.Length > 0)
            {
                TextPlate = (osTextPlate)_mainViewModel.ObjetosSimulables.FirstOrDefault(s => (s is osTextPlate && s.Nombre == value), null);
                if (TextPlate != null)
                    TextPlate.PropertyChanged += OnTextPlatePropertyChanged;
            }
        }

        private void OnTextPlatePropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (!isUpdatingFromTextPlate)
            {
                isUpdatingFromTextPlate = true;

                if (e.PropertyName == nameof(osTextPlate.Nombre))
                    Group_Panel = ((osTextPlate)sender).Nombre;

                if (e.PropertyName == nameof(osTextPlate.Top))
                    Top += ((osTextPlate)sender).offsetY;

                if (e.PropertyName == nameof(osTextPlate.Left))
                    Left += ((osTextPlate)sender).offsetX;

                isUpdatingFromTextPlate = false;
            }
        }

        // Group as FacePlate
        [ObservableProperty]
        [property: Category("Agrupación")]
        [property: Description("Enlace a un marco que se mueve automáticamente como ancla")]
        [property: Name("Panel Marco")]
        [property: ItemsSource(typeof(osBaseItemsSource<osFramePlate>))]
        private string group_FramePanel;

        [JsonIgnore]
        private osFramePlate FramePlate;
        [JsonIgnore]
        private bool isUpdatingFromFramePlate = false;

        // Variables para rotación orbital alrededor del FramePlate
        [ObservableProperty]
        [property: JsonIgnore]
        [property: Hidden]
        private float framePlate_RelativeX;

        [ObservableProperty]
        [property: JsonIgnore]
        [property: Hidden]
        private float framePlate_RelativeY;

        [ObservableProperty]
        [property: JsonIgnore]
        [property: Hidden]
        private float framePlate_InitialAngle;

        [ObservableProperty]
        [property: JsonIgnore]
        [property: Hidden]
        private float framePlate_PivotX;

        [ObservableProperty]
        [property: JsonIgnore]
        [property: Hidden]
        private float framePlate_PivotY;

        partial void OnGroup_FramePanelChanged(string value)
        {
            if (FramePlate != null)
                FramePlate.PropertyChanged -= OnFramePlatePropertyChanged;
            if (_mainViewModel != null && value != null && value.Length > 0)
            {
                FramePlate = (osFramePlate)_mainViewModel.ObjetosSimulables.FirstOrDefault(s => (s is osFramePlate && s.Nombre == value), null);
                if (FramePlate != null)
                {
                    FramePlate.PropertyChanged += OnFramePlatePropertyChanged;
                    UpdateZIndex(FramePlate.Zindex_FramePlate);

                    // Calcular punto de pivot y posición relativa inicial
                    var pivotPoint = FramePlate.GetPivotPoint();
                    FramePlate_PivotX = pivotPoint.X;
                    FramePlate_PivotY = pivotPoint.Y;
                    FramePlate_RelativeX = Left - FramePlate_PivotX;
                    FramePlate_RelativeY = Top - FramePlate_PivotY;
                    FramePlate_InitialAngle = FramePlate.Angulo;
                }
            }
            else
            {
                // Reset relative position when disconnecting
                FramePlate_RelativeX = 0;
                FramePlate_RelativeY = 0;
                FramePlate_InitialAngle = 0;
                FramePlate_PivotX = 0;
                FramePlate_PivotY = 0;
            }
        }

        protected void UpdateZIndex(int zIndex)
        {
            if (_visualRepresentation is IDataContainer dataContainer)
            {
                dataContainer.zIndex_fromFrames = zIndex;
                Canvas.SetZIndex(_visualRepresentation, zIndex + ((int)dataContainer.ZIndex_Base()));
            }
        }

        private void OnFramePlatePropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (!isUpdatingFromFramePlate)
            {
                isUpdatingFromFramePlate = true;

                if (e.PropertyName == nameof(osFramePlate.Nombre))
                    Group_Panel = ((osFramePlate)sender).Nombre;

                if (e.PropertyName == nameof(osFramePlate.Top) ||
                    e.PropertyName == nameof(osFramePlate.Left))
                {
                    UpdateOrbitalPosition();
                }

                if (e.PropertyName == nameof(osFramePlate.Angulo))
                {
                    // Primero actualizar el ángulo del objeto
                    Angulo += ((osFramePlate)sender).offsetAngulo;
                    // Luego actualizar la posición orbital con el ángulo ya actualizado
                    UpdateOrbitalPosition();
                }

                if (e.PropertyName == nameof(osFramePlate.PivotCenterX) ||
                    e.PropertyName == nameof(osFramePlate.PivotCenterY))
                {
                    // Cuando cambia el pivot, recalcular posición relativa
                    RecalculateRelativePositionForNewPivot();
                }

                if (e.PropertyName == nameof(osFramePlate.Zindex_FramePlate))
                    UpdateZIndex(((osFramePlate)sender).Zindex_FramePlate);

                isUpdatingFromFramePlate = false;
            }
        }

        private void UpdateOrbitalPosition()
        {
            if (FramePlate == null) return;

            // Actualizar punto de pivot actual (puede haber cambiado por los checkboxes)
            var currentPivot = FramePlate.GetPivotPoint();

            // Calcular el ángulo de rotación total desde la posición inicial
            float deltaAngle = FramePlate.Angulo - FramePlate_InitialAngle;

            // Convertir ángulo a radianes
            float angleRad = deltaAngle * (float)Math.PI / 180.0f;

            // Calcular la nueva posición orbital usando rotación de matriz
            float cosAngle = (float)Math.Cos(angleRad);
            float sinAngle = (float)Math.Sin(angleRad);

            // Rotar la posición relativa
            float rotatedX = cosAngle * FramePlate_RelativeX - sinAngle * FramePlate_RelativeY;
            float rotatedY = sinAngle * FramePlate_RelativeX + cosAngle * FramePlate_RelativeY;

            // Calcular nueva posición absoluta usando el punto de pivot actual
            float newLeft = currentPivot.X + rotatedX;
            float newTop = currentPivot.Y + rotatedY;

            // Actualizar directamente los campos sin disparar eventos de cambio
            SetProperty(ref left, newLeft);
            SetProperty(ref top, newTop);

            // Forzar actualización de la posición visual
            ActualizarLeftTop();

            // Llamar a OnMoveResizeRotate para actualizar la física
            OnMoveResizeRotate();
        }

        private void UpdateFramePlateRelativePosition()
        {
            // Solo actualizar si está conectado a un FramePlate y el movimiento NO viene del FramePlate
            if (FramePlate != null && !isUpdatingFromFramePlate)
            {
                // Obtener punto de pivot actual
                var currentPivot = FramePlate.GetPivotPoint();

                // Recalcular posición relativa considerando la rotación actual del FramePlate
                float deltaAngle = FramePlate.Angulo - FramePlate_InitialAngle;
                float angleRad = deltaAngle * (float)Math.PI / 180.0f;

                // Calcular la posición relativa actual respecto al pivot del FramePlate
                float currentRelativeX = Left - currentPivot.X;
                float currentRelativeY = Top - currentPivot.Y;

                // Si el FramePlate está rotado, necesitamos "desrotar" la posición para obtener 
                // la posición relativa en el sistema de coordenadas original
                float cosAngle = (float)Math.Cos(-angleRad);
                float sinAngle = (float)Math.Sin(-angleRad);

                FramePlate_RelativeX = cosAngle * currentRelativeX - sinAngle * currentRelativeY;
                FramePlate_RelativeY = sinAngle * currentRelativeX + cosAngle * currentRelativeY;
            }
        }

        private void RecalculateRelativePositionForNewPivot()
        {
            if (FramePlate == null) return;

            // Obtener posición absoluta actual del objeto (antes del cambio de pivot)
            float currentLeft = Left;
            float currentTop = Top;

            // Obtener nuevo punto de pivot
            var newPivot = FramePlate.GetPivotPoint();

            // Calcular nueva posición relativa respecto al nuevo pivot
            // Considerar la rotación actual para obtener posición relativa en sistema original
            float deltaAngle = FramePlate.Angulo - FramePlate_InitialAngle;
            float angleRad = deltaAngle * (float)Math.PI / 180.0f;

            // Posición relativa actual respecto al nuevo pivot
            float currentRelativeX = currentLeft - newPivot.X;
            float currentRelativeY = currentTop - newPivot.Y;

            // "Desrotar" para obtener coordenadas en el sistema original
            float cosAngle = (float)Math.Cos(-angleRad);
            float sinAngle = (float)Math.Sin(-angleRad);

            FramePlate_RelativeX = cosAngle * currentRelativeX - sinAngle * currentRelativeY;
            FramePlate_RelativeY = sinAngle * currentRelativeX + cosAngle * currentRelativeY;
        }


        private void ShowPreviewWindow(Stream imageStream)
        {
            // Create a new window for preview
            Window previewWindow = new Window
            {
                Title = "Preview Captured Image",
                Width = 500,
                Height = 500,
                Content = new Image
                {
                    Source = BitmapFrame.Create(imageStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad),
                    Stretch = Stretch.Uniform
                }
            };

            previewWindow.ShowDialog();
        }


        // Reemplaza el método existente CaptureImageAreaAndDoOCR con esta implementación
        public string CaptureImageAreaAndDoOCRPPaddle(float Left, float Top, float Ancho, float Alto, float Angulo = 0, bool ShowPreview = false)
        {
            if (_mainViewModel?.MainCanvas.Children[0] is System.Windows.Controls.Image imagenDeFondo)
            {
                if (imagenDeFondo.Source is BitmapSource bitmapSource)
                {
                    float originalDpiX = (float)bitmapSource.DpiX;
                    float originalDpiY = (float)bitmapSource.DpiY;

                    float canvasDpiX = 96;
                    float canvasDpiY = 96;

                    float scaleFactorX = originalDpiX / canvasDpiX;
                    float scaleFactorY = originalDpiY / canvasDpiY;

                    int x = (int)MeterToPixels(Left * scaleFactorX);
                    int y = (int)MeterToPixels(Top * scaleFactorY);
                    int width = (int)MeterToPixels(Ancho * scaleFactorX);
                    int height = (int)MeterToPixels(Alto * scaleFactorY);

                    if (x < 0) x = 0;
                    if (y < 0) y = 0;
                    if (x + width > bitmapSource.PixelWidth) width = bitmapSource.PixelWidth - x;
                    if (y + height > bitmapSource.PixelHeight) height = bitmapSource.PixelHeight - y;

                    CroppedBitmap croppedBitmap = new CroppedBitmap(bitmapSource, new Int32Rect(x, y, width, height));

                    TransformedBitmap transformedBitmap = new TransformedBitmap();
                    transformedBitmap.BeginInit();
                    transformedBitmap.Source = croppedBitmap;

                    if (Angulo != 0)
                    {
                        // TransformedBitmap only accepts rotations in 90-degree increments
                        // Round to nearest 90 degrees: 0, 90, 180, or 270
                        double normalizedAngle = Math.Round(Angulo / 90.0) * 90.0;
                        RotateTransform rotateTransform = new RotateTransform(-normalizedAngle);
                        transformedBitmap.Transform = rotateTransform;
                    }

                    transformedBitmap.EndInit();

                    PngBitmapEncoder encoder = new PngBitmapEncoder();
                    encoder.Frames.Add(BitmapFrame.Create(transformedBitmap));

                    using (MemoryStream memoryStream = new MemoryStream())
                    {
                        encoder.Save(memoryStream);
                        memoryStream.Seek(0, SeekOrigin.Begin);

                        if (ShowPreview) ShowPreviewWindow(memoryStream);

                        try
                        {
                            // Primero convertimos a un array de bytes
                            byte[] imageBytes = memoryStream.ToArray();

                            // Obtener el motor PaddleOCR
                            var ocrEngine = PaddleOCRManager.GetEngine();

                            // Usar la sobrecarga que acepta un array de bytes en lugar de Bitmap
                            OCRResult result = ocrEngine.DetectText(imageBytes);

                            if (result != null && result.TextBlocks != null && result.TextBlocks.Count > 0)
                            {
                                // Lista de caracteres permitidos (similar a la whitelist de Tesseract)
                                string allowedChars = " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-./";

                                // Filtrar texto por lista de caracteres permitidos
                                var filteredBlocks = result.TextBlocks
                                    .Where(block => block.Score > 0.5) // Solo bloques con confianza > 50%
                                    .Select(block => new
                                    {
                                        Text = new string(block.Text.Where(c => allowedChars.Contains(c)).ToArray()),
                                        Score = block.Score
                                    })
                                    .OrderByDescending(block => block.Score);

                                string recognizedText = string.Join(" ", filteredBlocks.Select(b => b.Text));
                                return recognizedText.Trim();
                            }
                            return "";
                        }
                        catch (Exception ex)
                        {
                            System.Diagnostics.Debug.WriteLine($"Error en OCR: {ex.Message}");
                            return "";
                        }
                    }
                }
            }
            return "";
        }

        // Método para obtener un color más claro y saturado
        public static Color ObtenerColorMasClaroYSaturado(Color colorOriginal, double incrementoL, double incrementoS)
        {
            return ColorUtils.ObtenerColorMasClaroYSaturado(colorOriginal, incrementoL, incrementoS);
        }

        // Método para obtener un color más claro
        public Color ObtenerColorMasClaro(Color colorOriginal, double incremento)
        {
            incremento = Math.Clamp(incremento, 0, 1); // Asegurarse de que el incremento esté entre 0 y 1
            byte r = (byte)(colorOriginal.R + (255 - colorOriginal.R) * incremento);
            byte g = (byte)(colorOriginal.G + (255 - colorOriginal.G) * incremento);
            byte b = (byte)(colorOriginal.B + (255 - colorOriginal.B) * incremento);
            return Color.FromArgb(colorOriginal.A, r, g, b);
        }

        // All Pages Objects
        [NotifyPropertyChangedFor(nameof(Show_On_This_Page))]
        [ObservableProperty]
        [property: Category("Configuración")]
        [property: Description("Habilitar este objeto para ser usado en todas las páginas")]
        [property: Name("Habilitado en Todas Páginas")]
        private bool enable_On_All_Pages;

        partial void OnEnable_On_All_PagesChanged(bool value)
        {
            // Si se está desactivando el modo global
            if (!value)
            {
                // Limpiar los datos locales
                _datosLocales = null;
                _snapshotGlobal = null;

                // Desactivar datos locales
                Enable_Local_Data = false;

                Show_On_This_Page = true;
            }
        }

        // Local Data for Global Objects
        [NotifyPropertyChangedFor(nameof(Show_On_This_Page))]
        [ObservableProperty]
        [property: Category("Configuración")]
        [property: Description("Habilitar datos locales de este objeto global en todas las páginas")]
        [property: Name("Datos Locales para Todas")]
        private bool enable_Local_Data_for_All;

        partial void OnEnable_Local_Data_for_AllChanged(bool value)
        {
            if (value)
            {
                // Activar datos locales
                Enable_Local_Data = true;
            }
        }

        // Local Data for Global Objects
        [NotifyPropertyChangedFor(nameof(Show_On_This_Page))]
        [ObservableProperty]
        [property: Category("Configuración")]
        [property: Description("Habilitar datos locales de este objeto global en esta página")]
        [property: Name("Datos Locales")]
        [property: JsonIgnore]
        private bool enable_Local_Data;

        // Añadir estas propiedades y métodos a la clase osBase

        [JsonIgnore]
        private DatosLocales _snapshotGlobal;

        [JsonIgnore]
        private DatosLocales _datosLocales;

        /// <summary>
        /// Crea un snapshot de los datos globales actuales
        /// </summary>
        public void CrearSnapshotGlobal()
        {
            if (Enable_On_All_Pages) // No verificamos Enable_Local_Data aquí
            {
                _snapshotGlobal = new DatosLocales(Id);
                _snapshotGlobal.CopiarDesdeObjeto(this);
            }
        }

        /// <summary>
        /// Restaura los datos globales desde el snapshot
        /// </summary>
        public void RestaurarDesdeSnapshotGlobal()
        {
            if (Enable_On_All_Pages && Enable_Local_Data && _snapshotGlobal != null)
            {
                // Guarda los datos locales actuales antes de restaurar
                if (_datosLocales == null)
                {
                    _datosLocales = new DatosLocales(Id);
                }
                _datosLocales.CopiarDesdeObjeto(this);

                // Restaura desde el snapshot
                _snapshotGlobal.AplicarAObjeto(this);
            }
        }

        /// <summary>
        /// Restaura los datos locales después de guardar
        /// </summary>
        public void RestaurarDatosLocales()
        {
            if (Enable_On_All_Pages && Enable_Local_Data && _datosLocales != null)
            {
                _datosLocales.AplicarAObjeto(this);
            }
        }

        /// <summary>
        /// Obtiene los datos locales del objeto
        /// </summary>
        public DatosLocales ObtenerDatosLocales()
        {
            if (!Enable_On_All_Pages)
                return null;

            DatosLocales datos = new DatosLocales(Id);
            datos.CopiarDesdeObjeto(this);
            return datos;
        }

        /// <summary>
        /// Aplica datos locales al objeto
        /// </summary>
        public void AplicarDatosLocales(DatosLocales datos)
        {
            if (Enable_On_All_Pages && datos != null && datos.Id_GlobalObject.Value == Id.Value)
            {
                Enable_Local_Data = true; // Activar datos locales si se encuentran

                datos.AplicarAObjeto(this);

                // Actualiza la visualización
                ActualizarLeftTop();
                OnAnchoChanged(Ancho);
                OnAltoChanged(Alto);
            }
        }

        partial void OnEnable_Local_DataChanged(bool value)
        {
            // Si se está desactivando los datos locales y teníamos un snapshot
            if (!value && _snapshotGlobal != null && Enable_On_All_Pages)
            {
                // Restaurar los valores globales
                _snapshotGlobal.AplicarAObjeto(this);

                // Limpiar los datos locales
                _datosLocales = null;
                _snapshotGlobal = null;

                // Actualizar la visualización
                ActualizarLeftTop();
                OnAnchoChanged(Ancho);
                OnAltoChanged(Alto);
            }
            // Si se está activando, crear el snapshot
            else if (value && Enable_On_All_Pages)
            {
                CrearSnapshotGlobal();
            }
        }

        [ObservableProperty]
        [property: Hidden]
        private List<string> showOnThisPagesList;

        private bool show_On_This_Page;
        [property: Category("Información")]
        [property: Description("Mostrar en esta página")]
        [property: Name("Mostrar en Esta Página")]
        public bool Show_On_This_Page
        {
            get
            {
                if (!Enable_On_All_Pages)
                {
                    showOnThisPagesList?.Clear();
                    showOnThisPagesList = null;
                    show_On_This_Page = true;
                }
                else
                {
                    if (showOnThisPagesList == null)
                    {
                        showOnThisPagesList = new List<string> { _mainViewModel?.SelectedImage };
                    }
                    show_On_This_Page = _mainViewModel?.SelectedImage != null && showOnThisPagesList.Contains(_mainViewModel.SelectedImage);
                }
                return show_On_This_Page;
            }
            set
            {
                if (Enable_On_All_Pages)
                {
                    if (showOnThisPagesList == null)
                    {
                        showOnThisPagesList = new List<string>();
                    }
                    if (_mainViewModel?.SelectedImage == null) return;

                    if (value)
                    {
                        if (!showOnThisPagesList.Contains(_mainViewModel.SelectedImage))
                        {
                            showOnThisPagesList.Add(_mainViewModel.SelectedImage);
                        }
                    }
                    else
                    {
                        showOnThisPagesList.Remove(_mainViewModel.SelectedImage);
                    }
                }
                SetProperty(ref show_On_This_Page, value);
            }
        }

        [ObservableProperty]
        [property: Category("Datos")]
        [property: Description("Creado automáticamente y clonado con plantillas de búsqueda")]
        [property: Name("Clonado")]
        bool cloned;

        [ObservableProperty]
        [property: Category("Datos")]
        [property: Description("ID del objeto del cual fue clonado")]
        [property: Name("Clonado Desde")]
        [property: Hidden]
        UniqueId cloned_from;

        [ObservableProperty]
        [JsonIgnore]
        [property: Hidden]
        private bool isSelected;

        private async void TimerCallback(object state)
        {
            await Task.Delay(500); // Esperar 0.5 segundos antes de ejecutar la función
            Application.Current.Dispatcher.Invoke(() =>
            {
                // Realiza tus cambios en la interfaz de usuario aquí
                OnTimerAfterMovement();
            });
        }
        /// <summary>
        /// Este timer se usa para llamar a OnTimerAfterMovement luego de que han pasado 500ms sin que se
        /// llame a ResetTimer.
        /// Esto es util para no actualizar las simulaciones o controles mientras se esta moviendo aun el control.
        /// Se debe hacer overrride de OnTimerAfterMovement y dentro de esta funcion llamar a la actualizacion que se 
        /// requiere hacer una vez terminado el movimiento del control por parte del usuario.
        /// </summary>
        public void ResetTimer()
        {
            if (timer == null)
                timer = new System.Threading.Timer(TimerCallback, null, Timeout.Infinite, Timeout.Infinite);

            timer.Change(500, Timeout.Infinite);
        }

        public virtual void OnTimerAfterMovement() { }

        /// <summary>
        /// Usado para saber cuando un objeto fue creado manualmente o por algun generador
        /// Mas adelante la idea es poder eliminar o tratar de manera diferente a estos objetos
        /// </summary>
        public bool AutoCreated = false;

        /// <summary>
        /// Son los objetos marcados para ser eliminados luego de se detectados por un sensor del mundo fisico
        /// Se deben eliminar fuera de la simulacion
        /// </summary>
        public bool RemoverDesdeSimulacion = false; // La simulacion indica que se debe remover

        [JsonIgnore]
        private DataSaveToSerialize DataSave;

        /// <summary>
        /// Link al UserControl. Inicializado en el evento OnLoaded
        /// </summary>
        [JsonIgnore]
        protected UserControl? _visualRepresentation = null;

        /// <summary>
        /// Link al control PLC. 
        /// Inicializado en el metodo SetPLC
        /// </summary>
        [JsonIgnore]
        protected PLCViewModel? _plc = null;

        /// <summary>
        /// Se llama luego de cada Step. Esto permite actualziar las posiciones graficas de cada objeto que se esta dibujando
        /// con las nuevas posiciones calculadas luego de la simulacion fisica.
        /// </summary>
        /// <param name="elapsedMilliseconds"></param>
        public virtual void UpdateControl(int elapsedMilliseconds) { }


        /// <summary>
        /// Se llama antes de comenzar la simulacion con el boton de Iniciar simulacion.
        /// La idea es actualizar los objetos en el motor fisico antes de comenzar la simulacion fisica.
        /// </summary>
        public virtual void UpdateGeometryStart() { }

        /// <summary>
        /// Se llama al terminar la simulacion con el boton de Detener simulacion.
        /// Se puede utilizar para detener los storyboard o alguna otra simulacion independiente
        /// </summary>
        public virtual void SimulationStop() { }

        /// <summary>
        /// Se llama antes de cada Step al World de la simulacion fisica.
        /// Permite actualizar el estado de los objetos de la simulacion fisica
        /// </summary>
        public virtual void UpdateGeometryStep() { }

        /// <summary>
        /// Es llamada en cada Tick del reloj establecido del PLC
        /// cuando la conexion con el PLC esta establecida.
        /// </summary>
        /// <param name="plc"></param>
        /// <param name="elapsedMilliseconds"></param>
        public virtual void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds) { }

        /// <summary>
        /// El UserControl ya se ha cargado y podemos obtener las coordenadas para 
        /// crear el objeto de simulacion
        /// </summary>
        public virtual void ucLoaded()
        {
            ActualizarLeftTop();
            OnGroup_PanelChanged(Group_Panel); // Establece el link y se suscribe a los eventos
            OnGroup_FramePanelChanged(Group_FramePanel); // Establece el link y se suscribe a los eventos
            Show_On_This_Page = Show_On_This_Page; // Update data
        }

        /// <summary>
        ///  El UserControl se esta eliminando
        ///  eliminar el objeto de simulacion
        /// </summary>
        public virtual void ucUnLoaded() { }

        [JsonIgnore]
        public MainViewModel _mainViewModel;

        /// <summary>
        /// Link al UserControl. Inicializado en el evento OnLoaded
        /// </summary>
        [JsonIgnore]
        public UserControl? VisualRepresentation
        {
            get => _visualRepresentation;
            set => _visualRepresentation = value;
        }

        /// <summary>
        /// Link al Simualdor fisico.
        /// </summary>
        [JsonIgnore]
        public SimulationManagerBEPU simulationManager;

        /// <summary>
        /// Prepara la clase para ser serializable poniendo a null los objetos que tienen referencias circulares
        /// Luego se debe llamar a RestaurarDatosNoSerializables para restaurar el estado original
        /// </summary>
        public void SalvarDatosNoSerializables()
        {
            DataSave = new DataSaveToSerialize(_mainViewModel, _visualRepresentation, simulationManager);
            _mainViewModel = null;
            _visualRepresentation = null;
            simulationManager = null;
        }

        /// <summary>
        /// Restaura los links de los objetos que se eliminaron temporalmente para serializar.
        /// </summary>
        public void RestaurarDatosNoSerializables()
        {
            if (DataSave == null) return;
            DataSave.DataRestoreAfterSerialize(out _mainViewModel, out _visualRepresentation, out simulationManager);
        }
        /// <summary>
        /// Se llama una unica vez al conectar con el PLC.
        /// </summary>
        /// <param name="plc"></param>
        public void SetPLC(PLCViewModel plc)
        {
            _plc = plc;
            UpdatePLCPrimerCiclo();
        }

        /// <summary>
        /// Se llama una unica vez al conectar con el PLC
        /// </summary>
        public virtual void UpdatePLCPrimerCiclo() { }

        protected Storyboard CrearAnimacionMultiStoryBoardTrasnporte(Rectangle transporte, bool invertirDireccion)
        {
            if (_visualRepresentation == null) return null;
            if (transporte == null) return null;

            Storyboard storyboard = new Storyboard();
            var animation = new DoubleAnimation
            {
                From = invertirDireccion ? 20 : 0,
                To = invertirDireccion ? 0 : 20, // Total Pixels Brush
                Duration = TimeSpan.FromSeconds(PixelToMeter.Instance.calc.PixelsToMeters(20) * 60),
                RepeatBehavior = RepeatBehavior.Forever
            };
            Storyboard.SetTarget(animation, transporte);
            Storyboard.SetTargetProperty(animation, new PropertyPath("(Rectangle.Fill).(VisualBrush.Transform).(TransformGroup.Children)[0].(TranslateTransform.X)"));
            storyboard.Children.Add(animation);
            storyboard.Begin();
            storyboard.SetSpeedRatio(0);
            return storyboard;
        }

        protected Storyboard CrearAnimacionMultiStoryBoardTrasnporteCircular(System.Windows.Shapes.Path transporte, bool invertirDireccion)
        {
            if (_visualRepresentation == null) return null;
            if (transporte == null) return null;

            Storyboard storyboard = new Storyboard();
            var animation = new DoubleAnimation
            {
                From = invertirDireccion ? 20 : 0,
                To = invertirDireccion ? 0 : 20, // Total Pixels Brush
                Duration = TimeSpan.FromSeconds(PixelToMeter.Instance.calc.PixelsToMeters(20) * 60),
                RepeatBehavior = RepeatBehavior.Forever
            };
            Storyboard.SetTarget(animation, transporte);
            Storyboard.SetTargetProperty(animation, new PropertyPath("(Path.Fill).(VisualBrush.Transform).(TransformGroup.Children)[0].(TranslateTransform.Y)"));
            storyboard.Children.Add(animation);
            storyboard.Begin();
            storyboard.SetSpeedRatio(0);
            return storyboard;
        }

        protected Storyboard CrearAnimacionMultiStoryBoardTrasnporte(Storyboard storyboard, Rectangle transporte, bool invertirDireccion)
        {
            // Detener y eliminar el storyboard existente si hay uno
            if (storyboard != null)
            {
                storyboard.Stop();
                storyboard.Children.Clear();
            }

            return CrearAnimacionMultiStoryBoardTrasnporte(transporte, invertirDireccion);
        }

        protected Storyboard CrearAnimacionMultiStoryBoardTrasnporteCircular(Storyboard storyboard, System.Windows.Shapes.Path transporte, bool invertirDireccion)
        {
            // Detener y eliminar el storyboard existente si hay uno
            if (storyboard != null)
            {
                storyboard.Stop();
                storyboard.Children.Clear();
            }

            return CrearAnimacionMultiStoryBoardTrasnporteCircular(transporte, invertirDireccion);
        }

        protected void ActualizarAnimacionMultiStoryBoardTransporte(Storyboard storyboard, float velocidadActual)
        {
            if (_visualRepresentation == null) return;
            if (storyboard == null) return;

            if (!_mainViewModel.IsSimulationRunning)
                storyboard.SetSpeedRatio(0);
            else
                storyboard.SetSpeedRatio(Math.Abs(velocidadActual));
        }

        protected void CrearAnimacionStoryBoardTrasnporte(Rectangle transporte, bool invertirDireccion)
        {
            _storyboard = CrearAnimacionMultiStoryBoardTrasnporte(_storyboard, transporte, invertirDireccion);
        }

        protected void CrearAnimacionStoryBoardTrasnporteCircular(System.Windows.Shapes.Path transporte, bool invertirDireccion, float angulo)
        {
            if (angulo > 45 && angulo < 225)
                _storyboard = CrearAnimacionMultiStoryBoardTrasnporteCircular(_storyboard, transporte, !invertirDireccion);
            else
                _storyboard = CrearAnimacionMultiStoryBoardTrasnporteCircular(_storyboard, transporte, invertirDireccion);
        }

        protected void ActualizarAnimacionStoryBoardTransporte(float velocidadActual)
        {
            ActualizarAnimacionMultiStoryBoardTransporte(_storyboard, velocidadActual);
        }

        /// <summary>
        /// Actualiza Left Top del objeto respecto al Canvas padre.
        /// </summary>
        public void ActualizarLeftTop()
        {
            CanvasSetLeftinMeter(Left);
            CanvasSetTopinMeter(Top);
        }

        /// <summary>
        /// Fuerza la actualización de todos los bindings de posición y tamaño
        /// disparando PropertyChanged para Left, Top, Ancho, Alto
        /// </summary>
        public void ForceUpdatePositionBindings()
        {
            // Para Left y Top, actualizar directamente el Canvas ya que tienen lógica especial en OnChanged
            CanvasSetLeftinMeter(Left);
            CanvasSetTopinMeter(Top);

            // Para Ancho y Alto, disparar PropertyChanged para que los bindings se actualicen
            OnPropertyChanged(nameof(Ancho));
            OnPropertyChanged(nameof(Alto));
        }



        public bool LeerBitTag(string Tag)
        {
            if (this._plc == null) return false;
            if (!string.IsNullOrEmpty(Tag))
            {
                if (Tag == "1") return true;
                else if (Tag == "0") return false;
                if (_plc != null)
                    return _plc.LeerTagBool(Tag);
            }
            return false;
        }

        /// <summary>
        /// Si la conexion con el plc esta activa y el Tag no es nulo escribe el bit en el PLC
        /// </summary>
        /// <param name="Tag"></param>
        /// <param name="Value"></param>
        public void EscribirBitTag(string Tag, bool Value)
        {
            if (_plc == null) return;
            if (!string.IsNullOrEmpty(Tag))
                if (_plc != null)
                    _plc.EscribirBool(Tag, Value);
        }

        public void EscribirDINTTag(string Tag, float Value)
        {
            if (_plc == null) return;
            if (!string.IsNullOrEmpty(Tag))
            {
                SDataValue plcData = new SDataValue();
                plcData.Int32 = (int)Value;
                _plc.EscribirTag(Tag, plcData);
            }
        }

        public int? LeerDINTTag(string Tag)
        {
            if (_plc == null) return null;
            if (!string.IsNullOrEmpty(Tag))
            {
                return _plc.LeerTagDInt(Tag);
            }
            return null;
        }

        public void EscribirWordTagScaled(string Tag, float Value, float IN_scale_Min, float IN_scale_Max, float OUT_scale_Min, float OUT_scale_Max)
        {
            if (_plc == null) return;
            if (!string.IsNullOrEmpty(Tag))
            {
                SDataValue plcData = new SDataValue();
                plcData.UInt16 = (ushort)((Value - IN_scale_Min) / (IN_scale_Max - IN_scale_Min) * (OUT_scale_Max - OUT_scale_Min) + OUT_scale_Min);
                _plc.EscribirTag(Tag, plcData);
            }
        }

        public float LeerWordTagScaled(string Tag, float IN_scale_Min = 0, float IN_scale_Max = 27648, float OUT_scale_Min = 0, float OUT_scale_Max = 100)
        {
            if (_plc == null) return 0;
            if (!string.IsNullOrEmpty(Tag))
            {
                if (float.TryParse(Tag, out float v))
                    return v;
                if (_plc != null)
                {
                    SDataValue plcData = _plc.LeerTag(Tag);
                    float Value = plcData.UInt16; // WORD
                    return (Value - IN_scale_Min) / (IN_scale_Max - IN_scale_Min) * (OUT_scale_Max - OUT_scale_Min) + OUT_scale_Min;
                }
            }
            return 0;
        }
        public float LeerWordTag(string Tag)
        {
            if (_plc == null) return 0;
            if (!string.IsNullOrEmpty(Tag))
            {
                if (float.TryParse(Tag, out float v))
                    return v;
                if (_plc != null)
                {
                    SDataValue plcData = _plc.LeerTag(Tag);
                    float Value = plcData.UInt16; // WORD
                    return Value;
                }
            }
            return 0;
        }

        public void CanvasSetLeftinMeter(float left)
        {
            if (_visualRepresentation != null)
                Canvas.SetLeft(_visualRepresentation, PixelToMeter.Instance.calc.MetersToPixels(left));
        }
        public float CanvasGetLeftinMeter()
        {
            if (_visualRepresentation != null)
                return PixelToMeter.Instance.calc.PixelsToMeters((float)Canvas.GetLeft(_visualRepresentation));
            else return 0f;
        }

        public void CanvasSetTopinMeter(float top)
        {
            if (_visualRepresentation != null)
                Canvas.SetTop(_visualRepresentation, PixelToMeter.Instance.calc.MetersToPixels(top));
        }
        public float CanvasGetTopinMeter()
        {
            if (_visualRepresentation != null)
                return PixelToMeter.Instance.calc.PixelsToMeters((float)System.Windows.Controls.Canvas.GetTop(_visualRepresentation));
            else return 0f;
        }

        public float PixelsToMeters(float pixel)
        {
            return PixelToMeter.Instance.calc.PixelsToMeters(pixel);
        }

        public Vector2 PixelsToMeters(Vector2 pixel)
        {
            return new Vector2(PixelsToMeters(pixel.X), PixelsToMeters(pixel.Y));
        }

        public float MeterToPixels(float meter)
        {
            return PixelToMeter.Instance.calc.MetersToPixels(meter);
        }

        public Vector2 MeterToPixels(Vector2 meter)
        {
            return new Vector2(MeterToPixels(meter.X), MeterToPixels(meter.Y));
        }


        private static GeneralTransform GetTransformToRoot(Visual visual)
        {
            // Crear una transformación acumulativa inicial
            GeneralTransformGroup transformGroup = new GeneralTransformGroup();

            // Recorrer hacia arriba el árbol visual, acumulando las transformaciones
            while (visual != null)
            {
                GeneralTransform transform = visual.TransformToAncestor(visual);
                transformGroup.Children.Insert(0, transform);
                visual = VisualTreeHelper.GetParent(visual) as Visual;
            }

            return transformGroup;
        }

        private static bool IsVisualChild(Visual child, Visual parent)
        {
            if (child == null || parent == null)
                return false;

            try
            {
                // Intentar obtener el ancestro común
                var transform = child.TransformToAncestor(parent);
                return transform != null;
            }
            catch (InvalidOperationException)
            {
                return false;
            }
        }


        public (Vector2 TopLeft, Vector2 BottomRight) GetRectangleCoordinatesInMeter(Rectangle rect)
        {
            if (rect == null)
                return (new Vector2(0, 0), new Vector2(0, 0));

            var _canvasLeft = CanvasGetLeftinMeter();
            var _canvasTop = CanvasGetTopinMeter();

            try
            {
                // Verificar que tanto el rectángulo como la representación visual estén cargados
                if (!rect.IsLoaded || _visualRepresentation == null || !_visualRepresentation.IsLoaded)
                {
                    // Fallback: usar las propiedades básicas de posición si la transformación no está disponible
                    float rectWidth = (float)rect.Width;
                    float rectHeight = (float)rect.Height;

                    if (double.IsNaN(rectWidth) || rectWidth == 0)
                        rectWidth = Ancho * PixelToMeter.Instance.calc.MetersToPixels(1);
                    if (double.IsNaN(rectHeight) || rectHeight == 0)
                        rectHeight = Alto * PixelToMeter.Instance.calc.MetersToPixels(1);

                    return (new Vector2(_canvasLeft, _canvasTop),
                            new Vector2(_canvasLeft + PixelToMeter.Instance.calc.PixelsToMeters(rectWidth),
                                       _canvasTop + PixelToMeter.Instance.calc.PixelsToMeters(rectHeight)));
                }

                // Forzar actualización del layout
                _visualRepresentation.UpdateLayout();

                // Verificar que el rectángulo sea un descendiente visual de _visualRepresentation
                if (!IsVisualChild(rect, _visualRepresentation))
                {
                    // Fallback: usar las dimensiones básicas
                    float rectWidth = (float)rect.ActualWidth;
                    float rectHeight = (float)rect.ActualHeight;

                    return (new Vector2(_canvasLeft, _canvasTop),
                            new Vector2(_canvasLeft + PixelToMeter.Instance.calc.PixelsToMeters(rectWidth),
                                       _canvasTop + PixelToMeter.Instance.calc.PixelsToMeters(rectHeight)));
                }

                // Obtiene la transformada del objeto visual
                GeneralTransform transform = rect.TransformToAncestor(_visualRepresentation);

                // Obtiene la posición absoluta
                Point topLeft = transform.Transform(new Point(0, 0));
                Point bottomRight = transform.Transform(new Point(rect.ActualWidth, rect.ActualHeight));

                return (new Vector2(PixelToMeter.Instance.calc.PixelsToMeters((float)topLeft.X) + _canvasLeft,
                                   PixelToMeter.Instance.calc.PixelsToMeters((float)topLeft.Y) + _canvasTop),
                        new Vector2(PixelToMeter.Instance.calc.PixelsToMeters((float)bottomRight.X) + _canvasLeft,
                                   PixelToMeter.Instance.calc.PixelsToMeters((float)bottomRight.Y) + _canvasTop));
            }
            catch (InvalidOperationException)
            {
                // Fallback en caso de error de transformación
                float rectWidth = (float)rect.ActualWidth;
                float rectHeight = (float)rect.ActualHeight;

                if (double.IsNaN(rectWidth) || rectWidth == 0)
                    rectWidth = Ancho * PixelToMeter.Instance.calc.MetersToPixels(1);
                if (double.IsNaN(rectHeight) || rectHeight == 0)
                    rectHeight = Alto * PixelToMeter.Instance.calc.MetersToPixels(1);

                return (new Vector2(_canvasLeft, _canvasTop),
                        new Vector2(_canvasLeft + PixelToMeter.Instance.calc.PixelsToMeters(rectWidth),
                                   _canvasTop + PixelToMeter.Instance.calc.PixelsToMeters(rectHeight)));
            }
        }

        public (Vector2 Start, Vector2 End) GetCenterLineVectors(Rectangle rect)
        {
            if (rect == null)
                return (new Vector2(0, 0), new Vector2(0, 0));

            Vector2 start = new Vector2(0, 0);
            Vector2 end = new Vector2(0, 0);

            // Usar Dispatcher para asegurar la ejecución en el hilo correcto
            _visualRepresentation?.Dispatcher.Invoke(() =>
            {
                try
                {
                    // Asegúrate de que el control está en el árbol visual y actualizado
                    if (_visualRepresentation.IsLoaded && rect.IsLoaded && IsVisualChild(rect, _visualRepresentation))
                    {
                        _visualRepresentation.UpdateLayout();

                        var _canvasLeft = CanvasGetLeftinMeter();
                        var _canvasTop = CanvasGetTopinMeter();

                        var transform = rect.TransformToAncestor(_visualRepresentation);

                        // Puntos en coordenadas locales del rectángulo no rotado
                        Point startLocal = new Point(0, rect.ActualHeight / 2);
                        Point endLocal = new Point(rect.ActualWidth, rect.ActualHeight / 2);

                        // Transformar estos puntos al sistema de coordenadas del ancestro
                        Point transformedStart = transform.Transform(startLocal);
                        Point transformedEnd = transform.Transform(endLocal);

                        // Convierte a unidades de Farseer (metros en este caso)
                        start = new Vector2(PixelToMeter.Instance.calc.PixelsToMeters((float)transformedStart.X) + _canvasLeft, PixelToMeter.Instance.calc.PixelsToMeters((float)transformedStart.Y) + _canvasTop);
                        end = new Vector2(PixelToMeter.Instance.calc.PixelsToMeters((float)transformedEnd.X) + _canvasLeft, PixelToMeter.Instance.calc.PixelsToMeters((float)transformedEnd.Y) + _canvasTop);
                    }
                    else
                    {
                        // Fallback: usar coordenadas básicas
                        var _canvasLeft = CanvasGetLeftinMeter();
                        var _canvasTop = CanvasGetTopinMeter();

                        float rectWidth = (float)rect.ActualWidth;
                        if (double.IsNaN(rectWidth) || rectWidth == 0)
                            rectWidth = Ancho * PixelToMeter.Instance.calc.MetersToPixels(1);

                        start = new Vector2(_canvasLeft, _canvasTop + PixelToMeter.Instance.calc.PixelsToMeters(rectWidth / 2));
                        end = new Vector2(_canvasLeft + PixelToMeter.Instance.calc.PixelsToMeters(rectWidth), _canvasTop + PixelToMeter.Instance.calc.PixelsToMeters(rectWidth / 2));
                    }
                }
                catch (InvalidOperationException)
                {
                    // Fallback en caso de error de transformación
                    var _canvasLeft = CanvasGetLeftinMeter();
                    var _canvasTop = CanvasGetTopinMeter();

                    float rectWidth = (float)rect.ActualWidth;
                    if (double.IsNaN(rectWidth) || rectWidth == 0)
                        rectWidth = Ancho * PixelToMeter.Instance.calc.MetersToPixels(1);

                    start = new Vector2(_canvasLeft, _canvasTop + PixelToMeter.Instance.calc.PixelsToMeters(rectWidth / 2));
                    end = new Vector2(_canvasLeft + PixelToMeter.Instance.calc.PixelsToMeters(rectWidth), _canvasTop + PixelToMeter.Instance.calc.PixelsToMeters(rectWidth / 2));
                }
            });

            return (start, end);
        }


        public Vector2 GetCurveCenterInMeter(float RadioExterno)
        {
            var _canvasLeft = CanvasGetLeftinMeter();
            var _canvasTop = CanvasGetTopinMeter();

            // El centro del Canvas
            double centerX = RadioExterno + _canvasLeft;
            double centerY = RadioExterno + _canvasTop;

            // Convertir a Vector2
            return new Vector2((float)centerX, (float)centerY);
        }

        public Vector2 GetRectangleCenter(Rectangle wpfRect)
        {
            var coords = GetRectangleCoordinatesInMeter(wpfRect);

            Vector2 topLeft = coords.TopLeft;
            Vector2 bottomRight = coords.BottomRight;

            // Calcular el centro, ancho y alto en metros
            return new Vector2((topLeft.X + bottomRight.X) / 2, (topLeft.Y + bottomRight.Y) / 2);
        }

        public Vector2 GetRectangleTopLeft(Rectangle wpfRect)
        {
            var coords = GetRectangleCoordinatesInMeter(wpfRect);
            return coords.TopLeft;
        }


        public void UpdateRectangle(simTransporte simRect, Rectangle wpfRect, float Alto, float Ancho, float Angulo)
        {
            if (simRect != null)
            {
                var topLeft2D = GetRectangleTopLeft(wpfRect);
                
                // ✅ SISTEMA INTELIGENTE: Solo recrear si las dimensiones han cambiado
                bool dimensionsChanged = HasTransportDimensionsChanged(simRect, Ancho, Alto);
                
                if (dimensionsChanged)
                {
                    // Las dimensiones cambiaron, recrear el objeto físico
                    simRect.Create(Ancho, Alto, topLeft2D, Angulo);
                    simRect.SetDimensions(Ancho, Alto); // Actualizar dimensiones internas
                }
                else
                {
                    // ✅ CORREGIDO: Usar UpdateFromWpfParameters para manejar correctamente Top-Left + Ángulo
                    simRect.UpdateFromWpfParameters(topLeft2D, Angulo);
                }
                
                // Actualizar propiedades cacheadas
                simRect.UpdateCachedProperties();
            }
        }

        public void UpdateRectangle(simBarrera barrera, Rectangle wpfRect, float Alto, float Ancho, float Angulo)
        {
            if (barrera != null)
            {
                var topLeft2D = GetRectangleTopLeft(wpfRect);
                simulationManager.UpdateBarrera(barrera, Ancho, Alto, topLeft2D, Angulo);
            }
        }

        public simTransporte AddRectangle(SimulationManagerBEPU simulationManager, Rectangle wpfRect, float Alto, float Ancho, float Angulo)
        {
            var topLeft2D = GetRectangleTopLeft(wpfRect);
            return simulationManager.AddRectangle(Ancho, Alto, topLeft2D, Angulo);
        }

        public simBarrera AddBarrera(SimulationManagerBEPU simulationManager, Rectangle wpfRect, float Alto, float Ancho, float Angulo, bool detectarCuello)
        {
            var topLeft2D = GetRectangleTopLeft(wpfRect);
            // Convertir Vector2 a Vector3 para BEPU (Z = 0 para objetos 2D)
            return simulationManager.CreateBarrera(Ancho, Alto, topLeft2D, Angulo, detectarCuello);
        }

        public void UpdateOrCreateLine(simGuia simGuia, Rectangle wpfRect)
        {
            if (simGuia != null)
            {
                // Usar el mismo sistema que transportes: Top-Left + Ángulo
                var topLeft2D = GetRectangleTopLeft(wpfRect);

                // Actualizar las propiedades desde el objeto osGuia
                if (this is osGuia guiaObj)
                {
                    // ✅ SISTEMA INTELIGENTE: Solo recrear si las dimensiones han cambiado
                    bool dimensionsChanged = HasGuideDimensionsChanged(simGuia, guiaObj.Ancho, guiaObj.AltoGuia);
                    
                    if (dimensionsChanged)
                    {
                        // Las dimensiones cambiaron, recrear el objeto físico
                        simGuia.Create(guiaObj.Ancho, guiaObj.AltoGuia, topLeft2D, guiaObj.Angulo);
                        simGuia.SetDimensions(guiaObj.Ancho, guiaObj.AltoGuia); // Actualizar dimensiones internas
                    }
                    else
                    {
                        // Solo actualizar posición y rotación
                        simGuia.SetPosition(topLeft2D, guiaObj.Angulo);
                    }
                    
                    // Actualizar propiedades internas
                    simGuia.UpdateProperties(guiaObj.Ancho, guiaObj.AltoGuia, guiaObj.Angulo);
                }
            }
        }

        public simGuia AddLine(SimulationManagerBEPU simulationManager, Rectangle wpfRect)
        {
            // Usar el mismo sistema que transportes: Top-Left + Ángulo
            var topLeft2D = GetRectangleTopLeft(wpfRect);

            // Obtener propiedades desde el objeto osGuia
            float ancho = 1.0f;
            float altoGuia = 0.05f;
            float angulo = 0f;

            if (this is osGuia guiaObj)
            {
                ancho = guiaObj.Ancho;
                altoGuia = guiaObj.AltoGuia;
                angulo = guiaObj.Angulo;
            }

            var simGuia = simulationManager.AddLine(ancho, altoGuia, topLeft2D, angulo);

            return simGuia;
        }

        public ImageSource ImageFromPath(string value)
        {
            if (value is string stringValue)
            {
                try
                {
                    var bitmapImage = new BitmapImage();
                    bitmapImage.BeginInit();
                    bitmapImage.UriSource = new Uri(stringValue, UriKind.RelativeOrAbsolute);
                    bitmapImage.EndInit();
                    return bitmapImage;
                }
                catch
                {
                    // Si falla la carga de la imagen, usar imagen por defecto como fallback
                    try
                    {
                        var defaultImage = new BitmapImage();
                        defaultImage.BeginInit();
                        defaultImage.UriSource = new Uri("/Icons/unselect.png", UriKind.RelativeOrAbsolute);
                        defaultImage.EndInit();
                        return defaultImage;
                    }
                    catch
                    {
                        // Si incluso la imagen por defecto falla, devolver null
                        return null;
                    }
                }
            }
            return null;
        }

        static protected T GetLastElement<T>(List<T> list) where T : class
        {
            if (list.Count > 0)
            {
                return list[list.Count - 1];
            }
            else
            {
                return null;
            }
        }

        protected override void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            base.OnPropertyChanged(e);

            // Don't mark changes for certain properties
            if (e.PropertyName != nameof(Top) &&
                e.PropertyName != nameof(Left) &&
                e.PropertyName != nameof(Ancho) &&
                e.PropertyName != nameof(Angulo) &&
                Cloned == false &&
                e.PropertyName != nameof(Show_On_This_Page) &&
                _mainViewModel != null)
            {
                _mainViewModel.HasUnsavedChanges = true;
            }
        }

        // ✅ MÉTODOS PARA GESTIÓN INTELIGENTE DE DIMENSIONES

        /// <summary>
        /// Verifica si las dimensiones de un transporte han cambiado
        /// </summary>
        protected bool HasTransportDimensionsChanged(simTransporte transport, float newWidth, float newHeight)
        {
            if (transport == null) return true;

            var newDimensions = new SimObjectDimensions
            {
                Width = newWidth,
                Height = newHeight,
                ObjectType = 1 // Transport
            };

            if (_lastKnownDimensions.TryGetValue(transport, out var lastDimensions))
            {
                bool changed = !newDimensions.Equals(lastDimensions);
                if (changed)
                {
                    _lastKnownDimensions[transport] = newDimensions;
                    System.Diagnostics.Debug.WriteLine($"[Dimensions] Transport dimensions CHANGED: {newWidth}x{newHeight}");
                }
                return changed;
            }
            else
            {
                // Primera vez - consideramos como cambio
                _lastKnownDimensions[transport] = newDimensions;
                System.Diagnostics.Debug.WriteLine($"[Dimensions] Transport first time: {newWidth}x{newHeight}");
                return true;
            }
        }

        /// <summary>
        /// Verifica si las dimensiones de una guía han cambiado
        /// </summary>
        protected bool HasGuideDimensionsChanged(simGuia guide, float newWidth, float newHeight)
        {
            if (guide == null) return true;

            var newDimensions = new SimObjectDimensions
            {
                Width = newWidth,
                Height = newHeight,
                ObjectType = 3 // Guide
            };

            if (_lastKnownDimensions.TryGetValue(guide, out var lastDimensions))
            {
                bool changed = !newDimensions.Equals(lastDimensions);
                if (changed)
                {
                    _lastKnownDimensions[guide] = newDimensions;
                    System.Diagnostics.Debug.WriteLine($"[Dimensions] Guide dimensions CHANGED: {newWidth}x{newHeight}");
                }
                return changed;
            }
            else
            {
                // Primera vez - consideramos como cambio
                _lastKnownDimensions[guide] = newDimensions;
                System.Diagnostics.Debug.WriteLine($"[Dimensions] Guide first time: {newWidth}x{newHeight}");
                return true;
            }
        }

        /// <summary>
        /// ✅ NUEVO - Sistema inteligente de verificación de dimensiones de descartes
        /// Verifica si el diámetro de un descarte ha cambiado
        /// </summary>
        protected bool HasDiscardDimensionsChanged(simDescarte discard, float newDiameter)
        {
            if (discard == null) return true;

            var newDimensions = new SimObjectDimensions
            {
                Width = newDiameter,
                Height = newDiameter,
                Radius = newDiameter / 2f,
                ObjectType = 4 // Tipo 4 = Descarte
            };

            if (_lastKnownDimensions.TryGetValue(discard, out var lastDimensions))
            {
                bool changed = !newDimensions.Equals(lastDimensions);
                if (changed)
                {
                    _lastKnownDimensions[discard] = newDimensions;
                    System.Diagnostics.Debug.WriteLine($"[Dimensions] Discard dimensions CHANGED: Ø{newDiameter}");
                }
                return changed;
            }
            else
            {
                // Primera vez - consideramos como cambio
                _lastKnownDimensions[discard] = newDimensions;
                System.Diagnostics.Debug.WriteLine($"[Dimensions] Discard first time: Ø{newDiameter}");
                return true;
            }
        }

        /// <summary>
        /// Limpia las dimensiones almacenadas para un objeto específico
        /// </summary>
        public static void ClearStoredDimensions(simBase simObj)
        {
            if (simObj != null && _lastKnownDimensions.ContainsKey(simObj))
            {
                _lastKnownDimensions.Remove(simObj);
                System.Diagnostics.Debug.WriteLine($"[Dimensions] Cleared stored dimensions for {simObj.GetType().Name}");
            }
        }

        /// <summary>
        /// Limpia todas las dimensiones almacenadas (útil al cerrar simulación)
        /// </summary>
        public static void ClearAllStoredDimensions()
        {
            _lastKnownDimensions.Clear();
            System.Diagnostics.Debug.WriteLine($"[Dimensions] Cleared ALL stored dimensions");
        }

    }
    public class UniqueId
    {
        public int Value { get; set; }

        public UniqueId ObtenerNuevaID()
        {
            Value = EstadoPersistente.Instance.newid;
            if (Value == 0)
                Value++;
            return this;
        }

        public void NoId()
        {
            // No ID == NULL
            Value = 0;
        }

        // Sobrecarga del operador ++
        public static UniqueId operator ++(UniqueId id)
        {
            if (id == null)
            {
                id = new UniqueId();
            }

            id.Value = EstadoPersistente.Instance.newid;
            if (id.Value == 0)
                id.Value++;

            return id;
        }

        // Sobrecarga del operador ==
        public static bool operator ==(UniqueId id1, UniqueId id2)
        {
            if (ReferenceEquals(id1, null) || ReferenceEquals(id2, null))
                return false;

            return id1.Value == id2.Value;
        }

        // Sobrecarga del operador !=
        public static bool operator !=(UniqueId id1, UniqueId id2)
        {
            if (ReferenceEquals(id1, null) || ReferenceEquals(id2, null))
                return true;

            return id1.Value != id2.Value;
        }

        // Sobrescribir Equals y GetHashCode también es una buena práctica
        public override bool Equals(object obj)
        {
            if (obj is UniqueId id)
            {
                return Value == id.Value;
            }
            return false;
        }

        public override int GetHashCode()
        {
            return Value.GetHashCode();
        }
    }



    public class osBaseItemsSource<T> : IItemsSource
    {
        public ItemCollection GetValues()
        {
            ItemCollection items = new ItemCollection();

            var viewModel = (MainViewModel)App.Current.MainWindow.DataContext;
            var objetosSimulables = viewModel.ObjetosSimulables;

            items.Add("");
            foreach (var obj in objetosSimulables)
            {
                if (obj.GetType() == typeof(T))
                {
                    items.Add(obj.Nombre);
                }
            }

            return items;
        }
    }

    public class TimerTON_TOFF
    {
        Stopwatch _stopwatch_ON = new Stopwatch();
        Stopwatch _stopwatch_OFF = new Stopwatch();
        private bool _senalFiltrada;

        public float Tiempo_ON_s { get; set; }
        public float Tiempo_OFF_s { get; set; }

        private bool senal;
        public bool Senal
        {
            get => senal;
            set
            {
                senal = value;
                if (value)
                {
                    ReiniciarTimer(_stopwatch_ON);
                    StopTimer(_stopwatch_OFF);
                }
                else
                {
                    ReiniciarTimer(_stopwatch_OFF);
                    StopTimer(_stopwatch_ON);
                }
            }
        }

        public TimerTON_TOFF()
        {
            Senal = false;
            Tiempo_ON_s = 1;
            Tiempo_OFF_s = 1;
        }

        public bool SenalFiltrada()
        {
            if (_stopwatch_ON.Elapsed.TotalMilliseconds > (Tiempo_ON_s * 1000))
                _senalFiltrada = true;
            if (_stopwatch_OFF.Elapsed.TotalMilliseconds > (Tiempo_OFF_s * 1000))
                _senalFiltrada = false;

            return _senalFiltrada;
        }

        void ReiniciarTimer(Stopwatch timer)
        {
            timer.Start();
        }
        void StopTimer(Stopwatch timer)
        {
            timer.Reset();
            timer.Stop();
        }

    }

    [AttributeUsage(AttributeTargets.Property)]
    public class HiddenAttribute : Attribute
    {
    }

    public class ColorUtils
    {
        // Convertir RGB a HSL
        public static void RgbToHsl(Color color, out double h, out double s, out double l)
        {
            double r = color.R / 255.0;
            double g = color.G / 255.0;
            double b = color.B / 255.0;

            double max = Math.Max(r, Math.Max(g, b));
            double min = Math.Min(r, Math.Min(g, b));

            h = s = l = (max + min) / 2.0;

            if (max == min)
            {
                h = s = 0.0; // Gris
            }
            else
            {
                double d = max - min;
                s = l > 0.5 ? d / (2.0 - max - min) : d / (max + min);

                if (max == r)
                    h = (g - b) / d + (g < b ? 6.0 : 0.0);
                else if (max == g)
                    h = (b - r) / d + 2.0;
                else
                    h = (r - g) / d + 4.0;

                h /= 6.0;
            }
        }

        // Convertir HSL a RGB
        public static Color HslToRgb(double h, double s, double l)
        {
            double r, g, b;

            if (s == 0.0)
            {
                r = g = b = l; // Gris
            }
            else
            {
                Func<double, double, double, double> hue2rgb = (p, q, t) =>
                {
                    if (t < 0.0) t += 1.0;
                    if (t > 1.0) t -= 1.0;
                    if (t < 1.0 / 6.0) return p + (q - p) * 6.0 * t;
                    if (t < 1.0 / 2.0) return q;
                    if (t < 2.0 / 3.0) return p + (q - p) * (2.0 / 3.0 - t) * 6.0;
                    return p;
                };

                double q = l < 0.5 ? l * (1.0 + s) : l + s - l * s;
                double p = 2.0 * l - q;

                r = hue2rgb(p, q, h + 1.0 / 3.0);
                g = hue2rgb(p, q, h);
                b = hue2rgb(p, q, h - 1.0 / 3.0);
            }

            return Color.FromArgb(255, (byte)(r * 255.0), (byte)(g * 255.0), (byte)(b * 255.0));
        }

        // Método para obtener un color más claro y saturado
        public static Color ObtenerColorMasClaroYSaturado(Color colorOriginal, double incrementoL, double incrementoS)
        {
            RgbToHsl(colorOriginal, out double h, out double s, out double l);

            l = Math.Clamp(l + incrementoL, 0.0, 1.0);
            s = Math.Clamp(s + incrementoS, 0.0, 1.0);

            return HslToRgb(h, s, l);
        }
    }

}
