# -*- coding: utf-8 -*-
"""
/***************************************************************************
 FARM tools
                                 A QGIS plugin
 DEM
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2026-04-01
        git sha              : $Format:%H$
        copyright            : (C) 2026 by FARM Analytica
        email                : livia.scopel@farmanalytica.com.br
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/
"""

import logging
import os.path

from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QAction
from qgis.PyQt.QtCore import QCoreApplication, QTranslator
from qgis.core import QgsSettings

from .farm_tools_dialog import FarmToolsDialog
from .managers.settings_manager import SettingsManager

logger = logging.getLogger(__name__)


class FarmTools:
    """
    Main plugin class for FARM tools QGIS plugin.

    Manages plugin initialization, GUI setup, and authentication workflow.
    """

    def __init__(self, interface):

        self.interface = interface
        self.plugin_dir = os.path.dirname(__file__)
        self.actions = []
        # Flavor-specific menu so each single-module plugin (RAVI, EasyDEM, …)
        # gets its own QGIS Plugins submenu instead of all sharing "FARM tools".
        from .view.module_prefs import flavor_label

        self.menu = "&" + flavor_label()

        self.first_start = None
        self._waiting_for_extlibs = False
        self._services_ready = False

        locale = QgsSettings().value("locale/userLocale", "en_US")
        language = locale[:2]
        locale_map = {"pt": "pt_BR", "zh": "zh_CN"}
        qm_language = locale_map.get(language, language)
        self._translator = QTranslator()
        qm_path = os.path.join(self.plugin_dir, "i18n", f"farm_tools_{qm_language}.qm")
        if os.path.exists(qm_path):
            self._translator.load(qm_path)
            QCoreApplication.installTranslator(self._translator)

    def tr(self, message):
        """
        Translate a message string.

        Args:
            message: Message to translate.

        Returns:
            Translated message string.
        """
        return QCoreApplication.translate("RAVI", 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 an action to the toolbar and/or menu.

        Args:
            icon_path: Path to the action icon.
            text: Action display text.
            callback: Function to call when action is triggered.
            enabled_flag: Whether action is enabled initially.
            add_to_menu: Whether to add action to plugin menu.
            add_to_toolbar: Whether to add action to toolbar.
            status_tip: Tooltip text.
            whats_this: Help text.
            parent: Parent widget.

        Returns:
            The created QAction object.
        """

        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:
            self.interface.addToolBarIcon(action)

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

        self.actions.append(action)

        return action

    def initGui(self):
        """Initialize the plugin GUI."""
        icon_path = os.path.join(self.plugin_dir, "icon.png")
        self.add_action(
            icon_path,
            text=self.tr(""),
            callback=self.run,
            parent=self.interface.mainWindow(),
        )

        self.first_start = True

    def unload(self):
        """Remove the plugin from QGIS."""
        if getattr(self, "fieldguide_ctrl", None) is not None:
            # Release the capture tool and remove canvas markers/labels.
            try:
                self.fieldguide_ctrl.cleanup()
            except Exception:
                logger.debug("Field guide controller cleanup failed", exc_info=True)
        if getattr(self, "climaplots_ctrl", None) is not None:
            # Release the pick tool, markers, temp charts, and any worker.
            try:
                self.climaplots_ctrl.cleanup()
            except Exception:
                logger.debug("ClimaPlots controller cleanup failed", exc_info=True)
        if getattr(self, "mapbiomas_ctrl", None) is not None:
            # Release the draw tool, temp chart, and any worker.
            try:
                self.mapbiomas_ctrl.cleanup()
            except Exception:
                logger.debug("MapBiomas controller cleanup failed", exc_info=True)
        if getattr(self, "mzones_ctrl", None) is not None:
            # Disconnect QgsProject layer signals.
            try:
                self.mzones_ctrl.cleanup()
            except Exception:
                logger.debug(
                    "Management zones controller cleanup failed", exc_info=True
                )
        QCoreApplication.removeTranslator(self._translator)
        for action in self.actions:
            self.interface.removePluginMenu(self.menu, action)
            self.interface.removeToolBarIcon(action)

    def _finish_init(self):
        from .services.gee_service import GEEService
        from .controllers.dem_ctrl import DEMCtrl
        from .controllers.auth_ctrl import AuthCtrl
        from .controllers.car_ctrl import CarCtrl
        from .controllers.sar_ctrl import SARCtrl
        from .controllers.optical_ctrl import OpticalCtrl
        from .controllers.landsat_ctrl import LandsatCtrl
        from .controllers.sysi_ctrl import SYSICtrl
        from .controllers.fieldguide_ctrl import FieldGuideCtrl
        from .controllers.climaplots_ctrl import ClimaPlotsCtrl
        from .controllers.mapbiomas_ctrl import MapBiomasCtrl
        from .controllers.mzones_ctrl import MZonesCtrl

        self._services_ready = True
        self.gee_service = GEEService()
        self.dem_ctrl = DEMCtrl(self.dialog, self.gee_service, self.interface)
        self.auth_ctrl = AuthCtrl(self.dialog, self.gee_service)
        self.car_ctrl = CarCtrl(self.dialog, self.interface)
        self.sar_ctrl = SARCtrl(self.dialog, self.interface, self.gee_service)
        self.optical_ctrl = OpticalCtrl(self.dialog, self.interface, self.gee_service)
        self.landsat_ctrl = LandsatCtrl(self.dialog, self.interface, self.gee_service)
        self.sysi_ctrl = SYSICtrl(self.dialog, self.interface, self.gee_service)
        self.fieldguide_ctrl = FieldGuideCtrl(self.dialog, self.interface)
        self.climaplots_ctrl = ClimaPlotsCtrl(self.dialog, self.interface)
        self.mapbiomas_ctrl = MapBiomasCtrl(
            self.dialog, self.interface, self.gee_service
        )
        self.mzones_ctrl = MZonesCtrl(self.dialog, self.interface)

        saved_project_id = self.gee_service.get_saved_project_id()
        if saved_project_id:
            self.dialog.project_id_input.setText(saved_project_id)

        saved_sa_key = self.gee_service.get_saved_sa_key_path()
        if saved_sa_key:
            self.dialog.sa_key_input.setText(saved_sa_key)
        # Restore the sign-in mode so the card opens in the user's last context;
        # refresh_auth_status() below then auto-authenticates silently.
        self.dialog.set_auth_mode(self.gee_service.get_saved_auth_mode())

        saved_folder = SettingsManager.load_download_folder()
        if saved_folder:
            self.dialog.folder_input.setText(saved_folder)

        self.dialog.project_id_input.textChanged.connect(
            self.gee_service.save_project_id
        )
        self.dialog.project_id_input.textChanged.connect(
            self.auth_ctrl.on_project_id_changed
        )
        self.dialog.btn_mode_personal.clicked.connect(
            lambda: self.auth_ctrl.handle_auth_mode_changed("personal")
        )
        self.dialog.btn_mode_service.clicked.connect(
            lambda: self.auth_ctrl.handle_auth_mode_changed("service")
        )
        self.dialog.btn_browse_key.clicked.connect(self.auth_ctrl.handle_browse_key)
        self.dialog.btn_authenticate.clicked.connect(
            self.auth_ctrl.handle_authentication
        )
        self.dialog.btn_reset_auth.clicked.connect(
            self.auth_ctrl.handle_reset_authentication
        )
        self.dialog.auth_status_badge.clicked.connect(
            self.auth_ctrl.refresh_auth_status
        )
        self.dialog.welcome_auth_badge.clicked.connect(self.dialog.show_auth_page)
        self.dialog.btn_browse_folder.clicked.connect(
            self.auth_ctrl.handle_folder_selection
        )
        self.dialog.btn_clear_folder.clicked.connect(self.auth_ctrl.handle_clear_folder)
        self.dialog.btn_go_to_aoi.clicked.connect(self.dem_ctrl.load_available_datasets)

        self.dialog.btn_fetch_car.clicked.connect(self.car_ctrl.handle_fetch_car)
        self.dialog.car_input.returnPressed.connect(self.car_ctrl.handle_fetch_car)

        self.dialog.layer_combo.layerChanged.connect(self.dem_ctrl.handle_layer_changed)
        self.dialog.dem_combo.currentIndexChanged.connect(
            self.dem_ctrl.on_dataset_changed
        )
        self.dialog.btn_download_dem.clicked.connect(
            lambda: self.dem_ctrl.handle_dem_service(self.interface)
        )
        self.dialog.btn_hybrid_layer.clicked.connect(self.dem_ctrl.handle_hybrid_layer)
        self.dialog.btn_draw_aoi.clicked.connect(self.dem_ctrl.handle_draw_aoi)

        self.dialog.s2_btn_hybrid_layer.clicked.connect(
            self.dem_ctrl.handle_hybrid_layer
        )
        self.dialog.s2_btn_draw_aoi.clicked.connect(self.optical_ctrl.handle_draw_aoi)
        self.dialog.s2_btn_run.clicked.connect(self.optical_ctrl.handle_optical_run)
        self.dialog.s2_layer_combo.layerChanged.connect(
            self.optical_ctrl.handle_layer_changed
        )
        self.dialog.s2_btn_filter_dates.clicked.connect(
            self.optical_ctrl.handle_filter_dates
        )
        self.dialog.s2_date_range_slider.low_changed.connect(
            self.optical_ctrl.handle_date_range_changed
        )
        self.dialog.s2_date_range_slider.high_changed.connect(
            self.optical_ctrl.handle_date_range_changed
        )
        self.dialog.s2_chk_smoothing.toggled.connect(
            self.optical_ctrl.handle_smoothing_changed
        )
        self.dialog.s2_smooth_window.valueChanged.connect(
            self.optical_ctrl.handle_smoothing_changed
        )
        self.dialog.s2_smooth_polyorder.valueChanged.connect(
            self.optical_ctrl.handle_smoothing_changed
        )
        self.dialog.s2_btn_open_browser.clicked.connect(
            self.optical_ctrl.handle_open_browser
        )
        self.dialog.s2_btn_download_csv.clicked.connect(
            self.optical_ctrl.handle_export_csv
        )
        self.dialog.s2_btn_batch_download.clicked.connect(
            self.optical_ctrl.handle_batch_download
        )
        self.dialog.s2_btn_rgb_preview.clicked.connect(
            self.optical_ctrl.handle_rgb_preview
        )
        self.dialog.s2_btn_rgb_download.clicked.connect(
            self.optical_ctrl.handle_rgb_download
        )
        self.dialog.s2_btn_vi_preview.clicked.connect(
            self.optical_ctrl.handle_vi_preview
        )
        self.dialog.s2_btn_vi_download.clicked.connect(
            self.optical_ctrl.handle_vi_download
        )
        self.dialog.s2_btn_custom_save.clicked.connect(
            self.optical_ctrl.handle_custom_index_save
        )

        self.dialog.s2_btn_composite_preview.clicked.connect(
            self.optical_ctrl.handle_composite_preview
        )
        self.dialog.s2_btn_composite_download.clicked.connect(
            self.optical_ctrl.handle_composite_download
        )
        self.dialog.s2_btn_climate_overlay.clicked.connect(
            self.optical_ctrl.handle_climate_overlay
        )
        self.dialog.s2_btn_climate_save.clicked.connect(
            self.optical_ctrl.handle_climate_export
        )
        self.dialog.s2_btn_climate_clear.clicked.connect(
            self.optical_ctrl.handle_climate_clear
        )
        self.dialog.s2_btn_capture_points.clicked.connect(
            self.optical_ctrl.handle_toggle_point_capture
        )
        self.dialog.s2_btn_clear_points.clicked.connect(
            self.optical_ctrl.handle_clear_points
        )
        self.dialog.s2_btn_plot_features.clicked.connect(
            self.optical_ctrl.handle_plot_features
        )
        self.dialog.s2_plot_view_aoi.clicked.connect(
            lambda _checked=False: self.optical_ctrl.handle_plot_view("aoi")
        )
        self.dialog.s2_plot_view_points.clicked.connect(
            lambda _checked=False: self.optical_ctrl.handle_plot_view("points")
        )
        self.dialog.s2_plot_view_features.clicked.connect(
            lambda _checked=False: self.optical_ctrl.handle_plot_view("features")
        )
        self.dialog.s2_btn_custom_delete.clicked.connect(
            self.optical_ctrl.handle_delete_custom
        )
        self.dialog.sar_btn_hybrid_layer.clicked.connect(
            self.dem_ctrl.handle_hybrid_layer
        )
        self.dialog.sar_btn_draw_aoi.clicked.connect(self.sar_ctrl.handle_draw_aoi)
        self.dialog.sar_btn_next.clicked.connect(self.sar_ctrl.handle_sar_run)
        self.dialog.sar_btn_preview.clicked.connect(self.sar_ctrl.handle_preview_image)
        self.dialog.sar_btn_download_preview.clicked.connect(
            self.sar_ctrl.handle_download_preview
        )
        self.dialog.sar_btn_open_browser.clicked.connect(
            self.sar_ctrl.handle_open_browser
        )
        self.dialog.sar_btn_download_csv.clicked.connect(
            self.sar_ctrl.handle_export_csv
        )
        self.dialog.sar_btn_batch_download.clicked.connect(
            self.sar_ctrl.handle_batch_download
        )
        self.dialog.sar_btn_filter_dates.clicked.connect(
            self.sar_ctrl.handle_filter_dates
        )
        self.dialog.sar_btn_composite_preview.clicked.connect(
            self.sar_ctrl.handle_composite_preview
        )
        self.dialog.sar_btn_composite_download.clicked.connect(
            self.sar_ctrl.handle_composite_download
        )
        self.dialog.sar_layer_combo.layerChanged.connect(
            self.sar_ctrl.handle_layer_changed
        )

        self.dialog.ls_btn_hybrid_layer.clicked.connect(
            self.dem_ctrl.handle_hybrid_layer
        )
        self.dialog.ls_btn_draw_aoi.clicked.connect(self.landsat_ctrl.handle_draw_aoi)
        self.dialog.ls_layer_combo.layerChanged.connect(
            self.landsat_ctrl.handle_layer_changed
        )
        self.dialog.ls_btn_run.clicked.connect(self.landsat_ctrl.handle_landsat_run)
        self.dialog.ls_date_combo.currentIndexChanged.connect(
            self.landsat_ctrl.handle_date_changed
        )
        for _ls_chk in self.dialog.ls_sensor_checks.values():
            _ls_chk.toggled.connect(self.landsat_ctrl.handle_sensors_changed)
        self.dialog.ls_btn_sr_preview.clicked.connect(
            self.landsat_ctrl.handle_sr_preview
        )
        self.dialog.ls_btn_sr_download.clicked.connect(
            self.landsat_ctrl.handle_sr_download
        )
        self.dialog.ls_btn_sr_batch.clicked.connect(
            self.landsat_ctrl.handle_batch_download
        )
        self.dialog.ls_btn_index_preview.clicked.connect(
            self.landsat_ctrl.handle_index_preview
        )
        self.dialog.ls_btn_index_download.clicked.connect(
            self.landsat_ctrl.handle_index_download
        )
        self.dialog.ls_btn_ms_preview.clicked.connect(
            self.landsat_ctrl.handle_ms_preview
        )
        self.dialog.ls_btn_ms_download.clicked.connect(
            self.landsat_ctrl.handle_ms_download
        )
        self.dialog.ls_btn_ts_browser.clicked.connect(
            self.landsat_ctrl.handle_open_browser
        )
        self.dialog.ls_btn_ts_csv.clicked.connect(self.landsat_ctrl.handle_export_csv)

        self.dialog.sysi_btn_draw_aoi.clicked.connect(self.sysi_ctrl.handle_draw_aoi)
        self.dialog.sysi_btn_hybrid_layer.clicked.connect(
            self.dem_ctrl.handle_hybrid_layer
        )
        self.dialog.sysi_btn_generate.clicked.connect(
            self.sysi_ctrl.handle_generate_sysi
        )
        self.dialog.sysi_layer_combo.layerChanged.connect(
            self.sysi_ctrl.handle_layer_changed
        )

        self.dialog.fg_btn_capture.toggled.connect(
            self.fieldguide_ctrl.handle_capture_toggled
        )
        self.dialog.fg_btn_hybrid_layer.clicked.connect(
            self.dem_ctrl.handle_hybrid_layer
        )
        self.dialog.fg_btn_mark_samples.clicked.connect(
            self.fieldguide_ctrl.handle_mark_samples
        )
        self.dialog.fg_layer_combo.layerChanged.connect(
            self.fieldguide_ctrl.handle_polygon_layer_changed
        )
        self.dialog.fg_raster_layer_combo.layerChanged.connect(
            self.fieldguide_ctrl.handle_raster_layer_changed
        )
        self.dialog.fg_raster_band_selector.valueChanged.connect(
            self.fieldguide_ctrl.handle_raster_band_changed
        )
        self.dialog.fg_use_raster_selection_checkbox.toggled.connect(
            self.fieldguide_ctrl.handle_use_raster_selection_toggled
        )
        self.dialog.fg_btn_remove_last.clicked.connect(
            self.fieldguide_ctrl.handle_remove_last
        )
        self.dialog.fg_btn_delete_selected.clicked.connect(
            self.fieldguide_ctrl.handle_delete_selected
        )
        self.dialog.fg_btn_clear.clicked.connect(
            self.fieldguide_ctrl.handle_clear_marks
        )
        self.dialog.fg_btn_add_manual.clicked.connect(
            self.fieldguide_ctrl.handle_add_manual
        )
        self.dialog.fg_lon_input.returnPressed.connect(
            self.fieldguide_ctrl.handle_add_manual
        )
        self.dialog.fg_btn_route.clicked.connect(self.fieldguide_ctrl.handle_route)
        self.dialog.fg_btn_import_csv.clicked.connect(
            self.fieldguide_ctrl.handle_import_csv
        )
        self.dialog.fg_btn_export_csv.clicked.connect(
            self.fieldguide_ctrl.handle_export_csv
        )
        self.dialog.fg_btn_export_gpx.clicked.connect(
            self.fieldguide_ctrl.handle_export_gpx
        )
        self.dialog.fg_btn_temp_layer.clicked.connect(
            self.fieldguide_ctrl.handle_temp_layer
        )
        self.dialog.fg_btn_pdf.clicked.connect(self.fieldguide_ctrl.handle_generate_pdf)
        self.dialog.fg_points_list.itemSelectionChanged.connect(
            self.fieldguide_ctrl.handle_selection_changed
        )

        self.dialog.cp_btn_pick_a.toggled.connect(
            self.climaplots_ctrl.handle_pick_a_toggled
        )
        self.dialog.cp_btn_pick_b.toggled.connect(
            self.climaplots_ctrl.handle_pick_b_toggled
        )
        self.dialog.cp_btn_copy_a_to_b.clicked.connect(
            self.climaplots_ctrl.handle_copy_a_to_b
        )
        self.dialog.cp_btn_clear_marker.clicked.connect(
            self.climaplots_ctrl.handle_clear_marker
        )
        self.dialog.cp_btn_hybrid_layer.clicked.connect(
            self.dem_ctrl.handle_hybrid_layer
        )
        self.dialog.cp_btn_run.clicked.connect(self.climaplots_ctrl.handle_run)
        self.dialog.cp_source_combo_a.currentIndexChanged.connect(
            self.climaplots_ctrl.handle_sync_year_range
        )
        self.dialog.cp_source_combo_b.currentIndexChanged.connect(
            self.climaplots_ctrl.handle_sync_year_range
        )
        self.dialog.cp_var_combo.currentTextChanged.connect(self.climaplots_ctrl.plots1)
        self.dialog.cp_var_combo.currentTextChanged.connect(
            self.climaplots_ctrl.handle_update_var_desc
        )
        self.dialog.cp_index_combo.currentTextChanged.connect(
            self.climaplots_ctrl.plots3
        )
        self.dialog.cp_index_combo.currentTextChanged.connect(
            self.climaplots_ctrl.handle_update_index_desc
        )
        self.dialog.cp_btn_browser_trends.clicked.connect(
            lambda: self.climaplots_ctrl.handle_open_in_browser(1)
        )
        self.dialog.cp_btn_browser_thermo.clicked.connect(
            lambda: self.climaplots_ctrl.handle_open_in_browser(2)
        )
        self.dialog.cp_btn_browser_indices.clicked.connect(
            lambda: self.climaplots_ctrl.handle_open_in_browser(3)
        )
        self.dialog.cp_btn_csv_trends.clicked.connect(
            self.climaplots_ctrl.handle_save_csv_trends
        )
        self.dialog.cp_btn_csv_thermo.clicked.connect(
            self.climaplots_ctrl.handle_save_csv_thermo
        )
        self.dialog.cp_btn_csv_indices.clicked.connect(
            self.climaplots_ctrl.handle_save_csv_indices
        )
        self.dialog.cp_btn_csv_raw.clicked.connect(
            self.climaplots_ctrl.handle_save_csv_raw
        )
        self.dialog.cp_btn_png_trends.clicked.connect(
            lambda: self.climaplots_ctrl.handle_save_png(1)
        )
        self.dialog.cp_btn_png_thermo.clicked.connect(
            lambda: self.climaplots_ctrl.handle_save_png(2)
        )
        self.dialog.cp_btn_png_indices.clicked.connect(
            lambda: self.climaplots_ctrl.handle_save_png(3)
        )
        self.dialog.cp_btn_export_all.clicked.connect(
            self.climaplots_ctrl.handle_export_all
        )

        self.dialog.mb_btn_draw_aoi.clicked.connect(self.mapbiomas_ctrl.handle_draw_aoi)
        self.dialog.mb_btn_hybrid_layer.clicked.connect(
            self.dem_ctrl.handle_hybrid_layer
        )
        self.dialog.mb_layer_combo.layerChanged.connect(
            self.mapbiomas_ctrl.handle_layer_changed
        )
        self.dialog.mb_btn_load_coverage.clicked.connect(
            self.mapbiomas_ctrl.handle_load_coverage
        )
        self.dialog.mb_btn_load_transition.clicked.connect(
            self.mapbiomas_ctrl.handle_load_transition
        )
        self.dialog.mb_btn_browser_transition.clicked.connect(
            self.mapbiomas_ctrl.handle_open_in_browser
        )
        self.dialog.mb_btn_download_qgis.clicked.connect(
            self.mapbiomas_ctrl.handle_download_qgis
        )
        self.dialog.mb_btn_download_year.clicked.connect(
            self.mapbiomas_ctrl.handle_download_year
        )
        self.dialog.mb_tx_preset_combo.currentIndexChanged.connect(
            self.mapbiomas_ctrl.handle_preset_changed
        )
        self.dialog.mb_btn_download_tx_qgis.clicked.connect(
            self.mapbiomas_ctrl.handle_download_transition_qgis
        )
        self.dialog.mb_tx_range.low_changed.connect(
            self.mapbiomas_ctrl.handle_tx_range_changed
        )
        self.dialog.mb_tx_range.high_changed.connect(
            self.mapbiomas_ctrl.handle_tx_range_changed
        )

        # Management Zones (local pipeline; deps/tab wiring lives in the view)
        self.dialog.mz_btn_deps_install.clicked.connect(self.mzones_ctrl.deps.install)
        self.dialog.mz_btn_deps_recheck.clicked.connect(self.mzones_ctrl.deps.refresh)
        self.dialog.mz_btn_resample.clicked.connect(self.mzones_ctrl.resample.run)
        self.dialog.mz_btn_run_pca.clicked.connect(self.mzones_ctrl.pca.run_pca)
        self.dialog.mz_btn_export_report.clicked.connect(
            self.mzones_ctrl.pca.export_report
        )
        self.dialog.mz_btn_export_pc.clicked.connect(
            self.mzones_ctrl.pca.export_selected_pc
        )
        self.dialog.mz_btn_export_all_pcs.clicked.connect(
            self.mzones_ctrl.pca.export_all_pcs
        )
        self.dialog.mz_btn_run_elbow.clicked.connect(self.mzones_ctrl.zones.run_elbow)
        self.dialog.mz_btn_export_elbow_png.clicked.connect(
            self.mzones_ctrl.zones.export_elbow_png
        )
        self.dialog.mz_btn_export_elbow_csv.clicked.connect(
            self.mzones_ctrl.zones.export_elbow_csv
        )
        self.dialog.mz_btn_generate_zones.clicked.connect(
            self.mzones_ctrl.zones.generate_zones
        )
        self.dialog.mz_btn_run_filter.clicked.connect(self.mzones_ctrl.filter.apply)
        self.dialog.mz_btn_load_csv.clicked.connect(self.mzones_ctrl.analysis.load_csv)
        self.dialog.mz_btn_run_analysis.clicked.connect(
            self.mzones_ctrl.analysis.variance_reduction
        )
        self.dialog.mz_btn_export_boxplots.clicked.connect(
            self.mzones_ctrl.analysis.export_boxplots
        )

        self.auth_ctrl.refresh_auth_status()

    def _on_extlibs_ready(self, success, error_msg):
        self._waiting_for_extlibs = False
        if success:
            from . import extlibs_manager

            extlibs_manager.ensure_on_path()
            self._finish_init()
            self.dialog.show_welcome_page()
        else:
            self.dialog.pop_message(
                self.tr("Failed to download dependencies: %s") % error_msg,
                "critical",
            )

    def run(self):
        """Display the plugin dialog and handle user interaction."""
        from . import extlibs_manager

        if self.first_start or not hasattr(self, "dialog"):
            self.first_start = False
            self.dialog = FarmToolsDialog(self.interface.mainWindow())

        if not self._services_ready:
            if extlibs_manager.is_ready():
                extlibs_manager.ensure_on_path()
                self._finish_init()
            else:
                self.dialog.show_loading_page()
                downloader = extlibs_manager.get_downloader()
                if downloader and not self._waiting_for_extlibs:
                    self._waiting_for_extlibs = True
                    downloader.download_done.connect(self._on_extlibs_ready)

        self.dialog.show()
        self.dialog.raise_()
        self.dialog.activateWindow()
