using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using System.IO;
using System.Windows.Media.Imaging;
using Emgu.CV.CvEnum;
using Emgu.CV;
using System.Drawing;
using Image = System.Windows.Controls.Image;
using Rectangle = System.Windows.Shapes.Rectangle;
using Size = System.Drawing.Size;
using Ookii.Dialogs.Wpf;
using Rect = System.Windows.Rect;
using System.ComponentModel;
using Newtonsoft.Json;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using System.ComponentModel;
using ClosedXML.Excel;
using Colors = System.Windows.Media.Colors;
using CtrEditor.FuncionesBase;
using System.Drawing;
using System.Windows.Shapes;
using System.Drawing.Imaging;
using Emgu.CV.Structure;

namespace CtrEditor.ObjetosSim.Extraccion_Datos
{
    /// <summary>
    /// Represents a template search control that identifies similar patterns in images and creates tag extraction clones.
    /// This class is designed to work with OCR extraction by finding visual patterns and creating copies of extraction tags
    /// at each matching location.
    /// </summary>
    /// <remarks>
    /// Key functionalities:
    /// - Template matching using OpenCV
    /// - Automatic tag cloning at found locations
    /// - OCR text extraction from matched regions
    /// - Export capabilities to Excel
    /// 
    /// Workflow:
    /// 1. User creates a search template by positioning and sizing the control over a pattern
    /// 2. Links extraction tags to this template using Id_Search_Templates
    /// 3. Activates search_templates to find similar patterns
    /// 4. The system automatically:
    ///    - Searches for visual matches in the image
    ///    - Creates clones of linked extraction tags at each match
    ///    - Assigns incremental copy_Number to organize rows in exports
    ///    - Performs OCR on each cloned tag location
    /// 
    /// Properties:
    /// - search_templates: Triggers the pattern search process
    /// - threshold: Minimum similarity threshold for pattern matching
    /// - coincidencias: Number of matches found (readonly)
    /// - show_debug_ocr: Shows debug windows during OCR process
    /// - export_ocr: Triggers OCR text export for all matches
    /// 
    /// Usage example:
    /// 1. Position the search template over a repeating pattern
    /// 2. Create extraction tags and link them to this template
    /// 3. Set threshold value (default usually works well)
    /// 4. Activate search_templates to find matches and create clones
    /// </remarks>

    public partial class osBuscarCoincidencias : osBase, IosBase
    {
        public static string NombreCategoria() => "Extraccion Datos";
        [JsonIgnore]
        public float offsetY;
        [JsonIgnore]
        public float offsetX;

        [ObservableProperty]
        List<Rect> search_rectangles;

        public static string NombreClase()
        {
            return "Buscador de Plantillas";
        }
        private string nombre = NombreClase();

        [property: Category("Identificación")]
        [property: Description("Nombre identificativo del objeto")]
        [property: Name("Nombre")]
        public override string Nombre
        {
            get => nombre;
            set => SetProperty(ref nombre, value);
        }

        [ObservableProperty]
        [property: Category("Configuración")]
        [property: Description("Ejecutar búsqueda de plantillas")]
        [property: Name("Buscar Plantillas")]
        bool search_templates;

        partial void OnSearch_templatesChanged(bool oldValue, bool newValue)
        {
            if (Search_templates)
                BuscarCoincidencias();
            Search_templates = false;
        }

        [ObservableProperty]
        [property: Category("Configuración")]
        [property: Description("Capturar imagen de referencia")]
        [property: Name("Tomar Clip")]
        bool tomarClip;

        // En lugar de almacenar Mat directamente, guardaremos una representación serializable
        [ObservableProperty]
        [property: Category("Información")]
        [property: Description("Datos de región capturada")]
        [property: Name("Datos Región")]
        byte[] capturedRegionData;

        [ObservableProperty]
        [property: Category("Información")]
        [property: Description("Indica si la región fue capturada")]
        [property: Name("Región Capturada")]
        [property: ReadOnly(true)]
        bool regionCapturada;

        // Para uso interno (no serializado)
        [JsonIgnore]
        private Mat _capturedRegion;

        // Propiedades para almacenar las dimensiones de la captura
        [ObservableProperty]
        [property: Category("Información")]
        [property: Description("Ancho de región capturada")]
        [property: Name("Ancho Capturado")]
        int capturedWidth;

        [ObservableProperty]
        [property: Category("Información")]
        [property: Description("Alto de región capturada")]
        [property: Name("Alto Capturado")]
        int capturedHeight;

        [ObservableProperty]
        [property: Category("Datos")]
        [property: Description("Exportar datos OCR")]
        [property: Name("Exportar OCR")]
        bool export_ocr;

        [ObservableProperty]
        [property: Category("Datos")]
        [property: Description("Texto exportado OCR")]
        [property: Name("Texto Export OCR")]
        string text_export_ocr;


        // Esta propiedad manejará la conversión entre Mat y datos serializables
        [JsonIgnore]
        public Mat CapturedRegion
        {
            get
            {
                if (_capturedRegion == null && CapturedRegionData != null && CapturedRegionData.Length > 0)
                {
                    // Recrear Mat desde los datos almacenados
                    _capturedRegion = BytesToMat(CapturedRegionData, CapturedWidth, CapturedHeight);
                }
                return _capturedRegion;
            }
            set
            {
                if (value != null)
                {
                    // Convertir Mat a bytes para serialización
                    CapturedRegionData = MatToBytes(value);
                    CapturedWidth = value.Width;
                    CapturedHeight = value.Height;
                    _capturedRegion = value;
                }
                else
                {
                    CapturedRegionData = null;
                    CapturedWidth = 0;
                    CapturedHeight = 0;
                    _capturedRegion = null;
                }
            }
        }


        partial void OnTomarClipChanged(bool oldValue, bool newValue)
        {
            if (tomarClip)
            {
                CapturarRegionActual();
                TomarClip = false; // Resetear el flag después de la captura
            }
        }

        // Método para capturar la región actual
        private void CapturarRegionActual()
        {
            Application.Current.Dispatcher.Invoke(() =>
            {
                if (_mainViewModel?.MainCanvas.Children[0] is Image imagenDeFondo)
                {
                    if (imagenDeFondo.Source is BitmapSource bitmapSource)
                    {
                        // Obtener los DPI de la imagen original
                        float originalDpiX = (float)bitmapSource.DpiX;
                        float originalDpiY = (float)bitmapSource.DpiY;

                        // Estándar DPI en el que el Canvas renderiza la imagen
                        float canvasDpiX = 96;
                        float canvasDpiY = 96;

                        // Calcular el ratio de escala entre el Canvas y la imagen original
                        float scaleFactorX = originalDpiX / canvasDpiX;
                        float scaleFactorY = originalDpiY / canvasDpiY;

                        // Ajustar las coordenadas de recorte en función del ratio de escala
                        int x = (int)MeterToPixels(Left * scaleFactorX);
                        int y = (int)MeterToPixels(Top * scaleFactorY);
                        int width = (int)MeterToPixels(Ancho * scaleFactorX);
                        int height = (int)MeterToPixels(Alto * scaleFactorY);

                        // Validar y ajustar el tamaño del recorte para que se mantenga dentro de los límites de la imagen
                        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;

                        // Recortar el área deseada utilizando las coordenadas ajustadas
                        CroppedBitmap croppedBitmap = new CroppedBitmap(bitmapSource, new Int32Rect(x, y, width, height));

                        // Capturar la región y almacenarla
                        if (_capturedRegion != null)
                        {
                            _capturedRegion.Dispose(); // Liberar recursos previos
                        }

                        // Usar la propiedad que maneja la serialización
                        CapturedRegion = BitmapSourceToMat(croppedBitmap);

                        // Actualizar el estado
                        RegionCapturada = true;
                        MessageBox.Show("Región capturada correctamente.", "Información", MessageBoxButton.OK, MessageBoxImage.Information);
                    }
                }
            });
        }

        // Métodos para convertir entre Mat y bytes
        private byte[] MatToBytes(Mat mat)
        {
            if (mat == null)
                return null;

            // Asegurar que tenemos un formato consistente para serialización
            Mat bgr = new Mat();
            if (mat.NumberOfChannels != 3)
            {
                CvInvoke.CvtColor(mat, bgr, mat.NumberOfChannels == 1 ?
                    ColorConversion.Gray2Bgr : ColorConversion.Bgra2Bgr);
            }
            else
            {
                bgr = mat.Clone();
            }

            // Convertir a un formato que pueda ser serializado
            using (MemoryStream ms = new MemoryStream())
            {
                // Convertir Mat a Bitmap
                Bitmap bitmap = bgr.ToBitmap();

                // Guardar como PNG (sin pérdida de calidad)
                bitmap.Save(ms, ImageFormat.Png);

                // Liberar recursos
                bitmap.Dispose();
                if (bgr != mat)
                    bgr.Dispose();

                return ms.ToArray();
            }
        }

        private Mat BytesToMat(byte[] bytes, int width, int height)
        {
            if (bytes == null || bytes.Length == 0)
                return null;

            try
            {
                using (MemoryStream ms = new MemoryStream(bytes))
                {
                    // Cargar imagen desde bytes
                    Bitmap bitmap = (Bitmap)System.Drawing.Image.FromStream(ms);

                    // Convertir Bitmap a Mat
                    Image<Bgr, byte> img = bitmap.ToImage<Bgr, byte>();

                    // Liberar recursos
                    bitmap.Dispose();

                    // Si las dimensiones no coinciden, redimensionar
                    if (img.Width != width || img.Height != height)
                    {
                        CvInvoke.Resize(img.Mat, img.Mat, new Size(width, height));
                    }

                    return img.Mat;
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine($"Error al reconstruir Mat: {ex.Message}");
                return null;
            }
        }

        // Sobrescribir los métodos de cambio de tamaño para limpiar la región capturada
        public override void AnchoChanged(float newValue)
        {
            base.AnchoChanged(newValue);
            //LimpiarRegionCapturada();
        }

        public override void AltoChanged(float newValue)
        {
            base.AnchoChanged(newValue);
            // LimpiarRegionCapturada();
        }

        private void LimpiarRegionCapturada()
        {
            if (_capturedRegion != null)
            {
                _capturedRegion.Dispose();
                _capturedRegion = null;
            }

            // Usar la propiedad para manejar la serialización
            CapturedRegion = null;
            RegionCapturada = false;
        }


        partial void OnExport_ocrChanged(bool value)
        {
            if (Export_ocr)
            {
                Text_export_ocr = "";
                if (!string.IsNullOrEmpty(Nombre) && _mainViewModel != null)
                {
                    foreach (var objetoSimulable in _mainViewModel.ObjetosSimulables)
                    {
                        if (objetoSimulable != this && objetoSimulable.Group_Panel == Nombre)
                        {
                            if (objetoSimulable is osExtraccionTag osExtraccionTag)
                            {
                                osExtraccionTag.CaptureImageAreaAndDoOCR();
                                Text_export_ocr += osExtraccionTag.Tag_extract;
                            }
                        }
                    }
                }
            }
            Export_ocr = false;
        }

        public override void TopChanging(float oldValue, float newValue)
        {
            offsetY = newValue - oldValue;
        }
        public override void LeftChanging(float oldValue, float newValue)
        {
            offsetX = newValue - oldValue;
        }

        [ObservableProperty]
        [property: Category("Tag Extraction:")]
        string tag_extract;

        [ObservableProperty]
        [property: Category("Tag Extraction:")]
        string clase;

        [ObservableProperty]
        [property: Category("Tag Extraction:")]
        string tag_name;

        [ObservableProperty]
        float opacity_oculto;

        [ObservableProperty]
        [property: Category("Tag Extraction:")]
        bool show_debug_ocr;

        [ObservableProperty]
        [property: Category("Tag Extraction:")]
        float threshold;

        [ObservableProperty]
        [property: Category("Tag Extraction:")]
        [property: ReadOnly(true)]
        int coincidencias;

        public osBuscarCoincidencias()
        {
            Ancho = 1;
            Alto = 1;
            Angulo = 0;
            Opacity_oculto = 0.1f;
            Threshold = 0.6f;
        }

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

        public async void BuscarCoincidencias()
        {
            var progressDialog = new ProgressDialog
            {
                WindowTitle = "Procesando",
                Text = "Buscando coincidencias...",
                ShowTimeRemaining = true,
                ShowCancelButton = false
            };
            progressDialog.DoWork += (sender, e) => BuscarCoincidenciasAsync(progressDialog);
            progressDialog.RunWorkerCompleted += (sender, e) =>
            {
                if (e.Error != null)
                {
                    MessageBox.Show(e.Error.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            };

            progressDialog.Show();
        }

        // Modificar el método BuscarCoincidenciasAsync para usar la región capturada si está disponible
        private void BuscarCoincidenciasAsync(ProgressDialog progressDialog)
        {
            // Reset the Canvas children
            Application.Current.Dispatcher.Invoke(() =>
            {
                var clearShapes = _mainViewModel.MainCanvas.Children.OfType<System.Windows.Shapes.Shape>().Where(s => s.Tag as string == "BuscarCoincidencias").ToList();
                foreach (var shape in clearShapes)
                {
                    _mainViewModel.MainCanvas.Children.Remove(shape);
                }

                if (_mainViewModel?.MainCanvas.Children[0] is Image imagenDeFondo)
                {
                    // Asegurarse de que la imagen origen está disponible
                    if (imagenDeFondo.Source is BitmapSource bitmapSource)
                    {
                        progressDialog.ReportProgress(10);

                        Mat templateMat;
                        int width, height;
                        float scaleFactorX, scaleFactorY;
                        bool deleteTemplateMat = false;

                        // Usar la región capturada si existe, de lo contrario capturar la región actual
                        if (CapturedRegion != null && RegionCapturada)
                        {
                            // Usar la región almacenada (ya deserializada)
                            templateMat = CapturedRegion.Clone();
                            deleteTemplateMat = true;
                            width = templateMat.Width;
                            height = templateMat.Height;

                            // Para mantener la relación con la imagen original
                            float originalDpiX = (float)bitmapSource.DpiX;
                            float originalDpiY = (float)bitmapSource.DpiY;
                            float canvasDpiX = 96;
                            float canvasDpiY = 96;
                            scaleFactorX = originalDpiX / canvasDpiX;
                            scaleFactorY = originalDpiY / canvasDpiY;
                        }
                        else
                        {
                            // Obtener los DPI de la imagen original
                            float originalDpiX = (float)bitmapSource.DpiX;
                            float originalDpiY = (float)bitmapSource.DpiY;

                            // Estándar DPI en el que el Canvas renderiza la imagen
                            float canvasDpiX = 96;
                            float canvasDpiY = 96;

                            // Calcular el ratio de escala entre el Canvas y la imagen original
                            scaleFactorX = originalDpiX / canvasDpiX;
                            scaleFactorY = originalDpiY / canvasDpiY;

                            // Ajustar las coordenadas de recorte en función del ratio de escala
                            int x = (int)MeterToPixels(Left * scaleFactorX);
                            int y = (int)MeterToPixels(Top * scaleFactorY);
                            width = (int)MeterToPixels(Ancho * scaleFactorX);
                            height = (int)MeterToPixels(Alto * scaleFactorY);

                            // Validar y ajustar el tamaño del recorte para que se mantenga dentro de los límites de la imagen
                            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;

                            // Recortar el área deseada utilizando las coordenadas ajustadas
                            CroppedBitmap croppedBitmap = new CroppedBitmap(bitmapSource, new Int32Rect(x, y, width, height));

                            // Convertir CroppedBitmap a Mat directamente
                            templateMat = BitmapSourceToMat(croppedBitmap);
                            deleteTemplateMat = true;
                        }

                        int scale = 4;

                        // Convertir la plantilla a escala de grises y redimensionarla
                        Mat templateGray = new Mat();
                        CvInvoke.CvtColor(templateMat, templateGray, ColorConversion.Bgr2Gray);
                        Mat templateGrayResized = new Mat();
                        CvInvoke.Resize(templateGray, templateGrayResized, new Size(templateGray.Width / scale, templateGray.Height / scale), 0, 0, Inter.Linear);
                        progressDialog.ReportProgress(20);

                        // El resto del código permanece igual...

                        // Cargar la imagen principal completa en un Mat
                        Mat mainImageMat = BitmapSourceToMat(bitmapSource);

                        // Convertir la imagen principal a escala de grises y redimensionarla
                        Mat mainImageGray = new Mat();
                        CvInvoke.CvtColor(mainImageMat, mainImageGray, ColorConversion.Bgr2Gray);
                        Mat mainImageGrayResized = new Mat();
                        CvInvoke.Resize(mainImageGray, mainImageGrayResized, new Size(mainImageGray.Width / scale, mainImageGray.Height / scale), 0, 0, Inter.Linear);

                        progressDialog.ReportProgress(50);

                        // Realizar la coincidencia de plantillas
                        Mat result = new Mat();
                        CvInvoke.MatchTemplate(mainImageGray, templateGray, result, TemplateMatchingType.CcoeffNormed);

                        // Establecer un umbral de coincidencia
                        if (Threshold < 0.4)
                            Threshold = 0.4f;
                        double threshold = Threshold;

                        int ConteoPositivos = 0;

                        // Lista para mantener áreas ya aceptadas
                        if (search_rectangles != null)
                            search_rectangles.Clear();
                        else
                            search_rectangles = new List<Rect>();

                        // Obtener los puntos que superan el umbral
                        float[] resultArray = result.GetData(false) as float[];
                        if (resultArray != null)
                        {
                            for (int i = 0; i < resultArray.Length; i++)
                            {
                                if (resultArray[i] >= threshold)
                                {
                                    int row = i / result.Cols;
                                    int col = i % result.Cols;

                                    Rect newRect = new Rect();
                                    newRect.X = col / scaleFactorX;
                                    newRect.Y = row / scaleFactorY;
                                    newRect.Width = width / scaleFactorX;
                                    newRect.Height = height / scaleFactorY;

                                    // Crear un rectángulo para la coincidencia actual
                                    Rectangle matchRect = new Rectangle
                                    {
                                        Stroke = new SolidColorBrush(Colors.Red),
                                        StrokeThickness = 2,
                                        Width = newRect.Width,
                                        Height = newRect.Height,
                                        Tag = "BuscarCoincidencias"
                                    };

                                    Canvas.SetLeft(matchRect, newRect.X);
                                    Canvas.SetTop(matchRect, newRect.Y);

                                    // Verificar si la coincidencia actual está dentro de algún rectángulo aceptado
                                    bool isOverlap = search_rectangles.Any(r =>
                                        r.IntersectsWith(newRect)
                                    );

                                    // Si no hay superposición, agregar el rectángulo al Canvas y a la lista de aceptados
                                    if (!isOverlap)
                                    {
                                        Canvas.SetZIndex(matchRect, 40);
                                        _mainViewModel.MainCanvas.Children.Add(matchRect);
                                        search_rectangles.Add(newRect);

                                        ConteoPositivos++;
                                        Coincidencias = ConteoPositivos;
                                        progressDialog.ReportProgress(90);
                                        if (ConteoPositivos > 20)
                                        {
                                            // Liberar recursos antes de salir
                                            if (deleteTemplateMat) templateMat.Dispose();
                                            templateGray.Dispose();
                                            templateGrayResized.Dispose();
                                            mainImageMat.Dispose();
                                            mainImageGray.Dispose();
                                            mainImageGrayResized.Dispose();
                                            result.Dispose();
                                            return;
                                        }
                                    }
                                }
                            }
                            PopularTagExtraction();
                        }

                        // Limpiar recursos
                        if (deleteTemplateMat) templateMat.Dispose();
                        templateGray.Dispose();
                        templateGrayResized.Dispose();
                        mainImageMat.Dispose();
                        mainImageGray.Dispose();
                        mainImageGrayResized.Dispose();
                        result.Dispose();
                    }
                }
            });
        }

        public void PopularTagExtraction()
        {
            var objetosSimulablesCopy = new List<osBase>(_mainViewModel.ObjetosSimulables);
            foreach (var obj in objetosSimulablesCopy)
                if (obj is osExtraccionTag objExtraccionTag)
                    if (objExtraccionTag.Id_Search_Templates == this.Nombre && objExtraccionTag.Cloned)
                        _mainViewModel.RemoverObjetoSimulable(objExtraccionTag);

            var objetosSimulables2Copy = new List<osBase>(_mainViewModel.ObjetosSimulables);
            // Saltar el primer rectángulo en el foreach

            int Row = 0;
            foreach (var rectangle in search_rectangles) //.Skip(1))
            {
                float offsetX = PixelsToMeters((float)rectangle.X) - Left;
                float offsetY = PixelsToMeters((float)rectangle.Y) - Top;

                osExtraccionTag newObj = null;

                foreach (var eTag in objetosSimulables2Copy)
                {
                    if (eTag is osExtraccionTag objExtraccionTag)
                    {
                        if (objExtraccionTag.Id_Search_Templates == this.Nombre)
                        {
                            newObj = (osExtraccionTag)_mainViewModel.DuplicarObjeto(objExtraccionTag, offsetX, offsetY);
                            if (newObj != null)
                            {
                                newObj.Cloned = true;
                                newObj.Cloned_from = objExtraccionTag.Id;
                                newObj.Copy_Number = Row;
                                newObj.Enable_On_All_Pages = false;
                                if (newObj.Extraer)
                                    objExtraccionTag.CaptureImageAreaAndDoOCR();
                            }
                        }
                    }
                }
                Row++;
            }
        }

        public static int FindFirstEmptyRow(IXLWorksheet worksheet)
        {
            var lastRowUsed = worksheet.LastRowUsed();
            return lastRowUsed == null ? 1 : lastRowUsed.RowNumber() + 1;
        }

        // Método para convertir BitmapSource a Mat
        private Mat BitmapSourceToMat(BitmapSource bitmapSource)
        {
            if (bitmapSource == null)
                throw new ArgumentNullException(nameof(bitmapSource));

            // Convierte BitmapSource a Bitmap
            Bitmap bitmap;
            using (MemoryStream outStream = new MemoryStream())
            {
                BitmapEncoder enc = new BmpBitmapEncoder();
                enc.Frames.Add(BitmapFrame.Create(bitmapSource));
                enc.Save(outStream);
                bitmap = new Bitmap(outStream);
            }

            // Convierte directamente a Mat usando Image<Bgr, byte>
            Image<Bgr, byte> image = bitmap.ToImage<Bgr, byte>();
            return image.Mat;
        }

        public override void ucLoaded()
        {
            // El UserControl ya se ha cargado y podemos obtener las coordenadas para 
            // crear el objeto de simulacion
            base.ucLoaded();

        }

    }

    public partial class ucBuscarCoincidencias : UserControl, IDataContainer
    {
        public osBase? Datos { get; set; }
        public int zIndex_fromFrames { get; set; }

        public ucBuscarCoincidencias()
        {
            InitializeComponent();
            this.Loaded += OnLoaded;
            this.Unloaded += OnUnloaded;
        }
        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            Datos?.ucLoaded();
        }
        private void OnUnloaded(object sender, RoutedEventArgs e)
        {
            Datos?.ucUnLoaded();
        }
        public void Highlight(bool State) { }
        public ZIndexEnum ZIndex_Base()
        {
            return ZIndexEnum.Estaticos;
        }

    }
}



