# -*- coding: utf-8 -*-
"""
/***************************************************************************
 RasterValueVectorizer
                                 A QGIS plugin
 Vectoriza un raster generando polígonos por valor único o rango de valores, ideal para análisis temático y clasificación.
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2025-11-26
        git sha              : $Format:%H$
        copyright            : (C) 2025 by Diener Chang
        email                : dienerchang@aerometricsgt.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
"""
from qgis.PyQt.QtCore import QSettings, QTranslator, QCoreApplication
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QAction

# Initialize Qt resources from file resources.py
from .resources import *
# Import the code for the dialog
from .raster_value_vectorizer_dc_dialog import RasterValueVectorizerDialog
import os.path


class RasterValueVectorizer:
    """QGIS Plugin Implementation."""

    def __init__(self, iface):
        """Constructor.

        :param iface: An interface instance that will be passed to this class
            which provides the hook by which you can manipulate the QGIS
            application at run time.
        :type iface: QgsInterface
        """
        # Save reference to the QGIS interface
        self.iface = iface
        # initialize plugin directory
        self.plugin_dir = os.path.dirname(__file__)
        # initialize locale
        locale = QSettings().value('locale/userLocale')[0:2]
        locale_path = os.path.join(
            self.plugin_dir,
            'i18n',
            'RasterValueVectorizer_{}.qm'.format(locale))

        if os.path.exists(locale_path):
            self.translator = QTranslator()
            self.translator.load(locale_path)
            QCoreApplication.installTranslator(self.translator)

        # Declare instance attributes
        self.actions = []
        self.menu = self.tr(u'&RasterValueVectorizer DC')

        # Check if plugin was started the first time in current QGIS session
        # Must be set in initGui() to survive plugin reloads
        self.first_start = None

    # noinspection PyMethodMayBeStatic
    def tr(self, message):
        """Get the translation for a string using Qt translation API.

        We implement this ourselves since we do not inherit QObject.

        :param message: String for translation.
        :type message: str, QString

        :returns: Translated version of message.
        :rtype: QString
        """
        # noinspection PyTypeChecker,PyArgumentList,PyCallByClass
        return QCoreApplication.translate('RasterValueVectorizer', message)


    def add_action(
        self,
        icon_path,
        text,
        callback,
        enabled_flag=True,
        add_to_menu=True,
        add_to_toolbar=True,
        status_tip=None,
        whats_this=None,
        parent=None):
        """Add a toolbar icon to the toolbar.

        :param icon_path: Path to the icon for this action. Can be a resource
            path (e.g. ':/plugins/foo/bar.png') or a normal file system path.
        :type icon_path: str

        :param text: Text that should be shown in menu items for this action.
        :type text: str

        :param callback: Function to be called when the action is triggered.
        :type callback: function

        :param enabled_flag: A flag indicating if the action should be enabled
            by default. Defaults to True.
        :type enabled_flag: bool

        :param add_to_menu: Flag indicating whether the action should also
            be added to the menu. Defaults to True.
        :type add_to_menu: bool

        :param add_to_toolbar: Flag indicating whether the action should also
            be added to the toolbar. Defaults to True.
        :type add_to_toolbar: bool

        :param status_tip: Optional text to show in a popup when mouse pointer
            hovers over the action.
        :type status_tip: str

        :param parent: Parent widget for the new action. Defaults None.
        :type parent: QWidget

        :param whats_this: Optional text to show in the status bar when the
            mouse pointer hovers over the action.

        :returns: The action that was created. Note that the action is also
            added to self.actions list.
        :rtype: QAction
        """

        icon = QIcon(icon_path)
        action = QAction(icon, text, parent)
        action.triggered.connect(callback)
        action.setEnabled(enabled_flag)

        if status_tip is not None:
            action.setStatusTip(status_tip)

        if whats_this is not None:
            action.setWhatsThis(whats_this)

        if add_to_toolbar:
            # Adds plugin icon to Plugins toolbar
            self.iface.addToolBarIcon(action)

        if add_to_menu:
            self.iface.addPluginToMenu(
                self.menu,
                action)

        self.actions.append(action)

        return action

    def initGui(self):
        """Create the menu entries and toolbar icons inside the QGIS GUI."""

        icon_path = os.path.join(self.plugin_dir, 'assets', 'Icono.png')
        if not os.path.exists(icon_path):
            # Fallback if asset missing
            icon_path = ':/plugins/raster_value_vectorizer_dc/icon.png'
            
        self.add_action(
            icon_path,
            text=self.tr(u'Raster Value Vectorizer DC'),
            callback=self.run,
            parent=self.iface.mainWindow(),
            add_to_menu=False) # Don't add to default Plugins menu automatically

        # Add to Raster Menu
        self.iface.addPluginToRasterMenu(self.tr(u'&Raster Value Vectorizer DC'), self.actions[0])

        # will be set False in run()
        self.first_start = True


    def unload(self):
        """Removes the plugin menu item and icon from QGIS GUI."""
        for action in self.actions:
            self.iface.removePluginRasterMenu(
                self.tr(u'&Raster Value Vectorizer DC'),
                action)
            self.iface.removeToolBarIcon(action)


    def run(self):
        """Run method that performs all the real work"""
        
        # Import processing and UI elements here to avoid circular dependencies or loading issues
        import tempfile
        import shutil
        import uuid
        from qgis import processing
        from qgis.core import (QgsProject, Qgis, QgsVectorLayer, QgsField, QgsFeature, 
                             QgsTask, QgsApplication, QgsProcessingFeedback, QgsProcessingContext, 
                             QgsMessageLog, QgsProcessingUtils, QgsCoordinateTransform)
        from qgis.PyQt.QtCore import QVariant, Qt
        from qgis.PyQt.QtWidgets import QMessageBox

        # Compatibilidad PyQt5 / PyQt6 para enums de Qt
        Qt_Checked = getattr(Qt, 'Checked', None)
        if Qt_Checked is None:
            Qt_Checked = Qt.CheckState.Checked

        Qt_UserRole = getattr(Qt, 'UserRole', None)
        if Qt_UserRole is None:
            Qt_UserRole = Qt.ItemDataRole.UserRole


        # Create the dialog with elements (after translation) and keep reference
        # Only create GUI ONCE in callback, so that it will only load when the plugin is started
        if self.first_start == True:
            self.first_start = False
            self.dlg = RasterValueVectorizerDialog()

        # Refresh layers every time the dialog is shown
        self.dlg.populate_layers()

        # show the dialog
        self.dlg.show()
        # Run the dialog event loop
        result = self.dlg.exec()
        # See if OK was pressed
        if result:
            # Performance Warning
            QMessageBox.information(self.dlg, "Información de Rendimiento", 
                                    "Tenga en cuenta que la velocidad de procesamiento puede estar limitada por el hardware de su PC (RAM, CPU).\n\n"
                                    "- Rasters de alta resolución o con muchos polígonos pueden tardar más tiempo.\n"
                                    "- El recorte con máscara y el cálculo de áreas incrementan el tiempo de proceso.\n"
                                    "- Si la barra de progreso parece detenerse, el proceso sigue activo (especialmente en 'Recortando vectores').")

            # 1. Get Inputs
            layer = self.dlg.mMapLayerComboBox.currentData()
            is_temp = self.dlg.mCheckBoxTemp.isChecked()
            output_file_base = self.dlg.mLineEditOutput.text()
            method_idx = self.dlg.mComboBoxMethod.currentIndex() # 0 = Unique, 1 = Ranges
            
            use_mask = self.dlg.mGroupBoxMask.isChecked()
            mask_layer = self.dlg.mMapLayerComboBoxMask.currentData()
            
            calc_area_m2 = self.dlg.mCheckBoxAreaM2.isChecked()
            calc_area_ha = self.dlg.mCheckBoxAreaHa.isChecked()

            # Capture selected attributes
            selected_attributes = []
            if self.dlg.mCheckBoxCopyAttributes.isChecked():
                for i in range(self.dlg.mListWidgetAttributes.count()):
                    item = self.dlg.mListWidgetAttributes.item(i)
                    if item.checkState() == Qt_Checked:
                        selected_attributes.append(item.text())

            # Validation
            if not layer:
                self.iface.messageBar().pushMessage("Error", "Por favor seleccione una capa raster.", level=Qgis.Critical)
                return
            
            if not is_temp and not output_file_base:
                self.iface.messageBar().pushMessage("Error", "Por favor seleccione un archivo de salida o marque 'Capa temporal'.", level=Qgis.Critical)
                return
                
            if use_mask and not mask_layer:
                self.iface.messageBar().pushMessage("Error", "Seleccionó usar máscara pero no eligió ninguna capa.", level=Qgis.Critical)
                return

            # Create a dedicated temp directory for this run to avoid path issues
            # We use mkdtemp to ensure it exists and is writable
            current_temp_dir = tempfile.mkdtemp(prefix="raster_vectorizer_")

            # Prepare inputs for thread (ensure they are files)
            layer_source = layer.source()
            if not os.path.isfile(layer_source):
                self.iface.messageBar().pushMessage("Info", "Preparando capa raster para procesamiento...", level=Qgis.Info)
                temp_raster = os.path.join(current_temp_dir, 'input_raster.tif')
                processing.run("gdal:translate", {
                    'INPUT': layer,
                    'OUTPUT': temp_raster
                })
                layer_source = temp_raster

            mask_source = None
            mask_extent = None
            if use_mask and mask_layer:
                mask_source = mask_layer.source()
                # Get extent for raster clipping optimization
                # Transform extent to Raster CRS if needed
                rect = mask_layer.extent()
                mask_crs = mask_layer.crs()
                raster_crs = layer.crs()
                
                if mask_crs != raster_crs:
                    try:
                        tr = QgsCoordinateTransform(mask_crs, raster_crs, QgsProject.instance())
                        rect = tr.transformBoundingBox(rect)
                    except Exception as e:
                        self.iface.messageBar().pushMessage("Warning", f"No se pudo transformar la extensión de la máscara: {str(e)}. Se usará la extensión original.", level=Qgis.Warning)

                mask_extent = f"{rect.xMinimum()},{rect.xMaximum()},{rect.yMinimum()},{rect.yMaximum()}"
                
                if not os.path.isfile(mask_source):
                    self.iface.messageBar().pushMessage("Info", "Preparando máscara para procesamiento...", level=Qgis.Info)
                    temp_mask = os.path.join(current_temp_dir, 'mask.gpkg')
                    processing.run("native:savefeatures", {
                        'INPUT': mask_layer,
                        'OUTPUT': temp_mask
                    })
                    mask_source = temp_mask

            # Gather parameters for background task
            params = {
                'layer_source': layer_source, 
                'layer_crs_authid': layer.crs().authid(),
                'layer_source': layer_source, 
                'layer_crs_authid': layer.crs().authid(),
                'mask_source': mask_source,
                'mask_extent': mask_extent,
                'is_temp': is_temp,
                'output_file_base': output_file_base,
                'method_idx': method_idx,
                'unique_values': [],
                'ranges_table': [],
                'id_to_label_map': {},
                'id_to_min_map': {},
                'id_to_max_map': {},
                'calc_area_m2': calc_area_m2,
                'calc_area_ha': calc_area_ha,
                'selected_attributes': selected_attributes,
                'temp_dir': current_temp_dir
            }

            if method_idx == 0: # Unique Values
                # Get values from list widget
                count = self.dlg.mListWidgetUnique.count()
                if count == 0:
                    self.iface.messageBar().pushMessage("Error", "Agregue al menos un valor único a la lista.", level=Qgis.Critical)
                    return
                for i in range(count):
                    item = self.dlg.mListWidgetUnique.item(i)
                    data = item.data(Qt_UserRole)
                    
                    if data and isinstance(data, dict):
                        params['unique_values'].append(data)
                    else:
                        # Fallback for items added before this change (if any) or plain text
                        try:
                            # Try to parse from text "val (label)" or just "val"
                            txt = item.text()
                            if "(" in txt and txt.endswith(")"):
                                val_str = txt.split("(")[0].strip()
                                label = txt.split("(")[1].strip(")")
                                val = float(val_str)
                                params['unique_values'].append({'val': val, 'label': label, 'tol': 0.0})
                            else:
                                val = float(txt)
                                params['unique_values'].append({'val': val, 'label': 'Valor Unico', 'tol': 0.0})
                        except ValueError:
                            pass
            
            else: # Ranges
                rows = self.dlg.mTableWidgetRanges.rowCount()
                if rows == 0:
                    self.iface.messageBar().pushMessage("Error", "Defina al menos un rango.", level=Qgis.Critical)
                    return

                current_id = 1
                for r in range(rows):
                    try:
                        min_val = float(self.dlg.mTableWidgetRanges.item(r, 0).text())
                        max_val = float(self.dlg.mTableWidgetRanges.item(r, 1).text())
                        raw_cls_val = self.dlg.mTableWidgetRanges.item(r, 2).text()
                        
                        internal_id = current_id
                        params['id_to_label_map'][internal_id] = raw_cls_val
                        params['id_to_min_map'][internal_id] = min_val
                        params['id_to_max_map'][internal_id] = max_val
                        current_id += 1
                        
                        params['ranges_table'].extend([min_val, max_val, internal_id])
                    except (ValueError, AttributeError):
                            self.iface.messageBar().pushMessage("Error", f"Valores numéricos inválidos en Min/Max en la fila {r+1}", level=Qgis.Critical)
                            return

            # Adjust ranges to close gaps and ensure continuity
            if method_idx == 1 and params['ranges_table']:
                # Reconstruct list of dicts for easier manipulation
                # ranges_table is [min, max, id, min, max, id...]
                parsed_ranges = []
                for i in range(0, len(params['ranges_table']), 3):
                    parsed_ranges.append({
                        'min': params['ranges_table'][i],
                        'max': params['ranges_table'][i+1],
                        'id': params['ranges_table'][i+2]
                    })
                
                # Sort by min value
                parsed_ranges.sort(key=lambda x: x['min'])
                
                # Close gaps
                for i in range(len(parsed_ranges) - 1):
                    current_range = parsed_ranges[i]
                    next_range = parsed_ranges[i+1]
                    
                    # If there is a gap (current_max < next_min), close it by extending current_max
                    if current_range['max'] < next_range['min']:
                        # We extend the current range up to the start of the next one
                        # This ensures values like 0.40999 are captured in the first range
                        current_range['max'] = next_range['min']
                        
                        # Update the map as well
                        params['id_to_max_map'][current_range['id']] = next_range['min']

                # Ensure last range includes its max value (since logic is min <= x < max)
                parsed_ranges[-1]['max'] += 0.00001
                params['id_to_max_map'][parsed_ranges[-1]['id']] = parsed_ranges[-1]['max']

                # Rebuild flat list
                new_ranges_table = []
                for r in parsed_ranges:
                    new_ranges_table.extend([r['min'], r['max'], r['id']])
                params['ranges_table'] = new_ranges_table

            # Define the task function
            def run_vectorization(task, params):
                from qgis import processing
                from qgis.core import QgsCoordinateReferenceSystem, QgsProcessingContext, QgsProcessingFeedback, QgsFields, QgsField
                import csv
                import time
                
                start_time = time.time()
                
                # Custom feedback class to update task progress with scaling
                class ScaledFeedback(QgsProcessingFeedback):
                    def __init__(self, task, start_percent, end_percent):
                        super().__init__()
                        self.task = task
                        self.start_percent = start_percent
                        self.end_percent = end_percent
                        self.last_reported = -1
                    
                    def setProgress(self, progress):
                        # Scale progress from 0-100 to start-end
                        scaled = self.start_percent + (progress / 100.0) * (self.end_percent - self.start_percent)
                        
                        # Throttle updates to avoid UI glitches (update only every 1% or if finished)
                        if int(scaled) > self.last_reported or progress >= 100:
                            self.task.setProgress(int(scaled))
                            self.last_reported = int(scaled)
                        
                    def reportError(self, error, fatalError=False):
                        if fatalError:
                            QgsMessageLog.logMessage(f"Processing Error: {error}", "RasterValueVectorizer", Qgis.Critical)
                        else:
                            QgsMessageLog.logMessage(f"Processing Warning: {error}", "RasterValueVectorizer", Qgis.Warning)

                try:
                    # Initial feedback for setup (0-5%)
                    feedback = ScaledFeedback(task, 0, 5)
                    context = QgsProcessingContext()
                    # Set project instance to ensure ellipsoidal area calculation if available
                    context.setProject(QgsProject.instance())
                    
                    task.setProgress(0)
                    QgsMessageLog.logMessage("Iniciando proceso de vectorización...", "RasterValueVectorizer", Qgis.Info)
                    
                    # Step 0: Clip Raster to Extent (Optimization)
                    input_raster_for_calc = params['layer_source']
                    crs = QgsCoordinateReferenceSystem(params['layer_crs_authid'])
                    
                    if params['mask_source'] and params['mask_extent']:
                        QgsMessageLog.logMessage("Recortando raster a la extensión de la máscara (Optimización)...", "RasterValueVectorizer", Qgis.Info)
                        temp_clip_extent = os.path.join(params['temp_dir'], f'clipped_extent_{uuid.uuid4().hex}.tif')
                        
                        # Use gdal:cliprasterbyextent
                        processing.run("gdal:cliprasterbyextent", {
                            'INPUT': params['layer_source'],
                            'PROJWIN': params['mask_extent'],
                            'NODATA': None,
                            'OPTIONS': '',
                            'DATA_TYPE': 0,
                            'OUTPUT': temp_clip_extent
                        }, context=context, feedback=feedback)
                        
                        if not os.path.exists(temp_clip_extent):
                            raise Exception("Falló el recorte del raster por extensión. Verifique que la máscara y el raster se superponen.")
                            
                        input_raster_for_calc = temp_clip_extent
                    
                    if task.isCanceled(): return None

                    output_layers = []

                    # Helper to process one item
                    def process_one(target_val=None, target_label="Valor Unico", target_tol=0.0, ranges_table=None, suffix="", start_global=0, end_global=100, desc_prefix=""):
                        temp_processed = os.path.join(params['temp_dir'], f'processed_{uuid.uuid4().hex}.tif')
                        
                        # Weights
                        w_reclass = 10
                        weights = {
                            'polygonize': 25,
                            'spatial_index': 5,
                            'clip_vector': 35,
                            'join_attributes': 10, 
                            'calc_areas': 15 
                        }

                        # Use reclassifybytable
                        table_to_use = []
                        if target_val is not None:
                            if target_tol > 0:
                                table_to_use = [target_val, target_val + target_tol, 1]
                            else:
                                epsilon = 1e-5
                                table_to_use = [target_val - epsilon, target_val + epsilon, 1]
                        else:
                            table_to_use = ranges_table
                        
                        reclass_end = start_global + (w_reclass / 100.0) * (end_global - start_global)
                        reclass_feedback = ScaledFeedback(task, start_global, reclass_end)
                        
                        task.setDescription(f"{desc_prefix}Clasificando raster...")

                        reclass_params = {
                            'INPUT_RASTER': input_raster_for_calc,
                            'RASTER_BAND': 1,
                            'TABLE': table_to_use,
                            'NO_DATA': 0,
                            'RANGE_BOUNDARIES': 2, 
                            'NODATA_FOR_MISSING': True,
                            'DATA_TYPE': 5, 
                            'OUTPUT': temp_processed
                        }
                        processing.run("native:reclassifybytable", reclass_params, context=context, feedback=reclass_feedback)

                        # Determine the final destination
                        final_output_path = ""
                        if params['is_temp']:
                             final_output_path = os.path.join(params['temp_dir'], f'vectorized_{suffix}_{uuid.uuid4().hex}.gpkg')
                        else:
                             base, ext = os.path.splitext(params['output_file_base'])
                             if suffix:
                                 final_output_path = f"{base}{suffix}.gpkg"
                             else:
                                 final_output_path = f"{base}.gpkg"
                             final_output_path = os.path.normpath(final_output_path)

                        # Determine intermediate steps
                        steps = ['polygonize', 'spatial_index']
                        
                        if params['mask_source']:
                            steps.append('clip_vector')
                            
                        steps.append('join_attributes')
                        
                        if params['calc_area_m2'] or params['calc_area_ha']:
                            steps.append('calc_areas')
                        
                        current_input_vector = None
                        
                        total_step_weight = sum(weights.get(s, 5) for s in steps)
                        progress_remaining_start = reclass_end
                        progress_remaining_end = end_global
                        
                        current_accumulated_weight = 0

                        for i, step in enumerate(steps):
                            is_last_step = (i == len(steps) - 1)
                            
                            step_w = weights.get(step, 5)
                            s_start = progress_remaining_start + (current_accumulated_weight / total_step_weight) * (progress_remaining_end - progress_remaining_start)
                            current_accumulated_weight += step_w
                            s_end = progress_remaining_start + (current_accumulated_weight / total_step_weight) * (progress_remaining_end - progress_remaining_start)
                            
                            step_feedback = ScaledFeedback(task, s_start, s_end)

                            # Output for this step
                            if is_last_step:
                                step_output = final_output_path
                            else:
                                step_output = os.path.join(params['temp_dir'], f'step_{step}_{uuid.uuid4().hex}.gpkg')
                            
                            if os.path.exists(step_output):
                                try: os.remove(step_output)
                                except: pass
                                
                            previous_input = current_input_vector

                            if step == 'polygonize':
                                task.setDescription(f"{desc_prefix}Vectorizando polígonos...")
                                QgsMessageLog.logMessage(f"Vectorizando a: {step_output}", "RasterValueVectorizer", Qgis.Info)
                                poly_params = {
                                    'INPUT': temp_processed,
                                    'BAND': 1,
                                    'FIELD': 'class_id',
                                    'EIGHT_CONNECTEDNESS': False,
                                    'OUTPUT': step_output
                                }
                                processing.run("gdal:polygonize", poly_params, context=context, feedback=step_feedback)
                                current_input_vector = step_output

                            elif step == 'spatial_index':
                                task.setDescription(f"{desc_prefix}Creando índice espacial...")
                                QgsMessageLog.logMessage(f"Creando índice espacial...", "RasterValueVectorizer", Qgis.Info)
                                processing.run("native:createspatialindex", {
                                    'INPUT': current_input_vector
                                }, context=context, feedback=step_feedback)
                                step_output = current_input_vector
                                current_input_vector = step_output

                            elif step == 'clip_vector':
                                task.setDescription(f"{desc_prefix}Recortando con máscara...")
                                QgsMessageLog.logMessage(f"Recortando vectores...", "RasterValueVectorizer", Qgis.Info)
                                
                                if params['selected_attributes']:
                                    processing.run("native:intersection", {
                                        'INPUT': current_input_vector,
                                        'OVERLAY': params['mask_source'],
                                        'INPUT_FIELDS': ['class_id'], 
                                        'OVERLAY_FIELDS': params['selected_attributes'],
                                        'OVERLAY_FIELDS_PREFIX': '',
                                        'OUTPUT': step_output
                                    }, context=context, feedback=step_feedback)
                                else:
                                    processing.run("native:clip", {
                                        'INPUT': current_input_vector,
                                        'OVERLAY': params['mask_source'],
                                        'OUTPUT': step_output
                                    }, context=context, feedback=step_feedback)
                                
                                current_input_vector = step_output

                            elif step == 'join_attributes':
                                task.setDescription(f"{desc_prefix}Uniendo atributos...")
                                QgsMessageLog.logMessage(f"Uniendo atributos...", "RasterValueVectorizer", Qgis.Info)
                                csv_path = os.path.join(params['temp_dir'], f'attributes_{uuid.uuid4().hex}.csv')
                                csvt_path = csv_path + "t"
                                with open(csvt_path, 'w') as f:
                                    f.write('"Integer","String","Real","Real","String","Real"')

                                with open(csv_path, 'w', newline='', encoding='utf-8') as f:
                                    writer = csv.writer(f)
                                    writer.writerow(['class_id', 'etiqueta', 'min_value', 'max_value', 'rango', 'valor'])
                                    
                                    if params['method_idx'] == 0: # Unique
                                        safe_label = target_label
                                        writer.writerow([1, safe_label, target_val, target_val, f"{target_val}", target_val])
                                    else: # Ranges
                                        for c_id, label in params['id_to_label_map'].items():
                                            min_v = params['id_to_min_map'].get(c_id, 0)
                                            max_v = params['id_to_max_map'].get(c_id, 0)
                                            rango_str = f"{min_v} to {max_v}"
                                            writer.writerow([c_id, label, min_v, max_v, rango_str, ''])

                                processing.run("native:joinattributestable", {
                                    'INPUT': current_input_vector,
                                    'FIELD': 'class_id',
                                    'INPUT_2': csv_path,
                                    'FIELD_2': 'class_id',
                                    'FIELDS_TO_COPY': [], 
                                    'METHOD': 1, 
                                    'DISCARD_NONMATCHING': False,
                                    'PREFIX': '',
                                    'OUTPUT': step_output
                                }, context=context, feedback=step_feedback)
                                current_input_vector = step_output

                            elif step == 'calc_areas':
                                task.setDescription(f"{desc_prefix}Calculando áreas...")
                                QgsMessageLog.logMessage(f"Calculando áreas...", "RasterValueVectorizer", Qgis.Info)
                                
                                calc_steps = []
                                if params['calc_area_m2']: calc_steps.append(('area_m2', '$area'))
                                if params['calc_area_ha']: calc_steps.append(('area_ha', '$area / 10000'))
                                
                                temp_in = current_input_vector
                                for idx, (fname, formula) in enumerate(calc_steps):
                                    is_last_sub = (idx == len(calc_steps) - 1)
                                    out_sub = step_output if is_last_sub else os.path.join(params['temp_dir'], f'area_tmp_{uuid.uuid4().hex}.gpkg')
                                    
                                    processing.run("native:fieldcalculator", {
                                        'INPUT': temp_in,
                                        'FIELD_NAME': fname,
                                        'FIELD_TYPE': 0, 
                                        'FIELD_LENGTH': 20,
                                        'FIELD_PRECISION': 6, 
                                        'FORMULA': formula,
                                        'OUTPUT': out_sub
                                    }, context=context, feedback=step_feedback)
                                    
                                    if temp_in != current_input_vector and os.path.exists(temp_in):
                                        try: os.remove(temp_in)
                                        except: pass
                                    temp_in = out_sub
                                
                                current_input_vector = step_output

                            # Cleanup previous step file
                            if previous_input and 'step_' in os.path.basename(previous_input) and previous_input != current_input_vector:
                                try: 
                                    if os.path.exists(previous_input):
                                        os.remove(previous_input)
                                except: pass

                        return final_output_path

                    # Execution Loop
                    if params['method_idx'] == 0: # Unique Values
                        total_items = len(params['unique_values'])
                        for idx, item_data in enumerate(params['unique_values']):
                            if task.isCanceled(): return None
                            val = item_data['val']
                            lbl = item_data.get('label', 'Valor Unico')
                            tol = item_data.get('tol', 0.0)
                            
                            QgsMessageLog.logMessage(f"Procesando valor: {val} ({lbl})...", "RasterValueVectorizer", Qgis.Info)
                            suffix = f"_{str(val).replace('.', '_')}"
                            
                            start_p = 5 + (idx / total_items) * 95
                            end_p = 5 + ((idx + 1) / total_items) * 95
                            
                            prefix = f"[{idx+1}/{total_items}] "
                            
                            out = process_one(target_val=val, target_label=lbl, target_tol=tol, suffix=suffix, start_global=start_p, end_global=end_p, desc_prefix=prefix)
                            output_layers.append({'path': out, 'name': f"{lbl} ({val})"})
                    else: # Ranges
                        if task.isCanceled(): return None
                        QgsMessageLog.logMessage("Procesando rangos...", "RasterValueVectorizer", Qgis.Info)
                        out = process_one(ranges_table=params['ranges_table'], start_global=5, end_global=100, desc_prefix="[Rangos] ")
                        output_layers.append({'path': out, 'name': "Rangos"})

                    end_time = time.time()
                    duration_min = (end_time - start_time) / 60.0
                    return {'layers': output_layers, 'duration': duration_min}

                except Exception as e:
                    QgsMessageLog.logMessage(f"Excepción en worker: {str(e)}", "RasterValueVectorizer", Qgis.Critical)
                    return Exception(str(e))

            # Callback when task finishes
            def on_finished(exception, result=None):
                if exception:
                    QMessageBox.critical(self.iface.mainWindow(), "Error", f"Ocurrió un error crítico: {str(exception)}")
                    return
                    
                if isinstance(result, Exception):
                    QMessageBox.critical(self.iface.mainWindow(), "Error", f"Error en el procesamiento: {str(result)}")
                    return
                    
                if result:
                    # Load Results
                    loaded_count = 0
                    layers_data = result.get('layers', [])
                    duration = result.get('duration', 0)
                    
                    # Reverse order to add them to top correctly if inserting at 0
                    for res in reversed(layers_data):
                        try:
                            path = res['path']
                            name = res['name']
                            if params['is_temp']:
                                name += " (Temp)"
                            
                            if os.path.exists(path):
                                # Load layer but don't add to legend yet
                                final_layer = QgsVectorLayer(path, name, "ogr")
                                if final_layer.isValid():
                                    QgsProject.instance().addMapLayer(final_layer, False)
                                    QgsProject.instance().layerTreeRoot().insertLayer(0, final_layer)
                                    loaded_count += 1
                        except Exception as e:
                             QgsMessageLog.logMessage(f"Error cargando capa {res['name']}: {str(e)}", "RasterValueVectorizer", Qgis.Warning)

                    if loaded_count > 0:
                        QMessageBox.information(self.iface.mainWindow(), "Éxito", f"Proceso completado.\nSe cargaron {loaded_count} capas.\nDuración: {duration:.2f} minutos.")
                    else:
                        QMessageBox.warning(self.iface.mainWindow(), "Advertencia", "El proceso terminó pero no se cargaron capas.")
                else:
                    QMessageBox.warning(self.iface.mainWindow(), "Aviso", "El proceso terminó sin resultados.")
                
                # Cleanup Logic
                if os.path.exists(params['temp_dir']):
                    if not params['is_temp']:
                        # If user chose output file, we can delete the whole temp dir
                        try:
                            shutil.rmtree(params['temp_dir'])
                        except:
                            pass 
                    else:
                        # If temp layer, we must KEEP the result files, but we can clean everything else
                        # Identify result files
                        layers_data = result.get('layers', []) if result else []
                        result_paths = [os.path.normpath(r['path']) for r in layers_data]
                        for root, dirs, files in os.walk(params['temp_dir']):
                            for f in files:
                                full_path = os.path.normpath(os.path.join(root, f))
                                if full_path not in result_paths:
                                    try:
                                        os.remove(full_path)
                                    except:
                                        pass

            # Create and start the task
            try:
                task = QgsTask.fromFunction("Vectorizando Raster", run_vectorization, on_finished=on_finished, params=params)
                QgsApplication.taskManager().addTask(task)
                self.iface.messageBar().pushMessage("Info", "Procesamiento iniciado en segundo plano. Espere...", level=Qgis.Info)
            except Exception as e:
                QMessageBox.critical(self.iface.mainWindow(), "Error", f"No se pudo iniciar la tarea: {str(e)}")
