# -*- coding: utf-8 -*-
"""📦 Analysis Aggregation Dialog module.

This module contains functionality for analysis aggregation dialog.
"""

from urllib.parse import unquote

from qgis.core import Qgis, QgsMapLayerProxyModel, QgsProject
from qgis.gui import QgsMapLayerComboBox
from qgis.PyQt.QtCore import QByteArray, QSettings, Qt, QUrl
from qgis.PyQt.QtGui import QDesktopServices, QPixmap
from qgis.PyQt.QtWidgets import (
    QAbstractScrollArea,
    QApplication,
    QCheckBox,
    QDialogButtonBox,
    QDoubleSpinBox,
    QFileDialog,
    QHBoxLayout,
    QHeaderView,
    QLabel,
    QLineEdit,
    QPushButton,
    QSizePolicy,
    QSpacerItem,
    QTableWidget,
    QTableWidgetItem,
    QWidget,
)

from geest.core.settings import setting
from geest.gui.widgets import CustomBannerLabel
from geest.utilities import (
    get_ui_class,
    is_qgis_dark_theme_active,
    log_message,
    log_window_geometry,
    resources_path,
)

from .custom_base_dialog import CustomBaseDialog

FORM_CLASS = get_ui_class("analysis_dialog_base.ui")


class AnalysisAggregationDialog(FORM_CLASS, CustomBaseDialog):
    """🎯 Analysis Aggregation Dialog.

    Attributes:
        analysis_data: Analysis data.
        analysis_name: Analysis name.
        custom_label: Custom label.
        guid_column_visible: Guid column visible.
        guids: Guids.
    """

    def __init__(self, analysis_item, parent=None):
        """🏗️ Initialize the instance.

        Args:
            analysis_item: Analysis item.
            parent: Parent.
        """
        super().__init__(parent)
        # Dynamically load the .ui file
        self.setupUi(self)

        self.analysis_name = analysis_item.attribute("analysis_name")
        self.analysis_data = analysis_item.attributes()
        self.tree_item = analysis_item  # Reference to the QTreeView item to update

        self.setWindowTitle(f"Edit weightings for analysis: {self.analysis_name}")
        # Need to be redimensioned...
        self.guids = self.tree_item.getAnalysisDimensionGuids()
        self.weightings = {}  # To store the temporary weightings
        # Banner label
        self.custom_label = CustomBannerLabel(
            "The Geospatial Enabling Environments for Employment Tool",
            resources_path("resources", "geoe3-banner.png"),
        )
        parent_layout = self.banner_label.parent().layout()
        parent_layout.replaceWidget(self.banner_label, self.custom_label)
        self.banner_label.deleteLater()
        parent_layout.update()

        self.setup_table()

        self.aggregation_lineedit.hide()
        self.population_lineedit.hide()
        self.point_lineedit.hide()
        self.polygon_lineedit.hide()
        self.raster_lineedit.hide()

        # Set up the aggregation layer widgets
        self.aggregation_combo.setAllowEmptyLayer(True)
        self.aggregation_combo.setCurrentIndex(-1)
        self.aggregation_combo.setFilters(QgsMapLayerProxyModel.Filter.PolygonLayer)
        self.aggregation_combo.currentIndexChanged.connect(self.aggregation_selected)
        self.aggregation_toolbutton.clicked.connect(self.aggregation_toolbutton_clicked)
        self.aggregation_lineedit.textChanged.connect(self.aggregation_lineedit_text_changed)
        self.load_combo_from_model(self.aggregation_combo, self.aggregation_lineedit, "aggregation")

        # Set up the population raster widgets
        self.population_combo.setAllowEmptyLayer(True)
        self.population_combo.setCurrentIndex(-1)
        self.population_combo.setFilters(QgsMapLayerProxyModel.Filter.RasterLayer)
        self.population_combo.currentIndexChanged.connect(self.population_selected)
        self.population_toolbutton.clicked.connect(self.population_toolbutton_clicked)
        self.population_lineedit.textChanged.connect(self.population_lineedit_text_changed)
        self.load_combo_from_model(self.population_combo, self.population_lineedit, "population")

        # Set up the point layer widgets
        self.point_combo.setAllowEmptyLayer(True)
        self.point_combo.setCurrentIndex(-1)
        self.point_combo.setFilters(QgsMapLayerProxyModel.Filter.PointLayer)
        self.point_combo.currentIndexChanged.connect(self.point_selected)
        self.population_toolbutton.clicked.connect(self.point_toolbutton_clicked)
        self.point_lineedit.textChanged.connect(self.point_lineedit_text_changed)
        self.load_combo_from_model(self.point_combo, self.point_lineedit, "point_mask")

        # set up the polygon layer widgets
        self.polygon_combo.setAllowEmptyLayer(True)
        self.polygon_combo.setCurrentIndex(-1)
        self.polygon_combo.setFilters(QgsMapLayerProxyModel.Filter.PolygonLayer)
        self.polygon_combo.currentIndexChanged.connect(self.polygon_selected)
        self.polygon_toolbutton.clicked.connect(self.polygon_toolbutton_clicked)
        self.polygon_lineedit.textChanged.connect(self.polygon_lineedit_text_changed)
        self.load_combo_from_model(self.polygon_combo, self.polygon_lineedit, "polygon_mask")

        # Set up the raster layer widgets
        self.raster_combo.setAllowEmptyLayer(True)
        self.raster_combo.setCurrentIndex(-1)
        self.raster_combo.setFilters(QgsMapLayerProxyModel.Filter.RasterLayer)
        self.raster_combo.currentIndexChanged.connect(self.raster_selected)
        self.raster_toolbutton.clicked.connect(self.raster_toolbutton_clicked)
        self.raster_lineedit.textChanged.connect(self.raster_lineedit_text_changed)
        self.load_combo_from_model(self.raster_combo, self.raster_lineedit, "raster_mask")

        help_layout = QHBoxLayout()
        help_layout.addItem(QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
        self.help_icon = QPixmap(resources_path("resources", "images", "help.png"))
        self.help_icon = self.help_icon.scaledToWidth(20)
        self.help_label_icon = QLabel()
        self.help_label_icon.setPixmap(self.help_icon)
        self.help_label_icon.setScaledContents(True)
        self.help_label_icon.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
        self.help_label_icon.setMaximumWidth(20)
        self.help_label_icon.setAlignment(Qt.AlignmentFlag.AlignRight)
        help_layout.addWidget(self.help_label_icon)

        self.help_label = QLabel(
            "For detailed instructions on how to use this tool, please refer to the <a href='https://worldbank.github.io/GEOE3/docs/user_guide.html'>GeoE3 User Guide</a>."
        )
        self.help_label.setOpenExternalLinks(True)
        self.help_label.setAlignment(Qt.AlignmentFlag.AlignCenter)

        self.help_label.linkActivated.connect(self.open_link_in_browser)
        help_layout.addWidget(self.help_label)
        help_layout.addItem(QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
        self.help_widget.setLayout(help_layout)

        auto_calculate_button = QPushButton("Balance Weights")
        self.button_box.addButton(auto_calculate_button, QDialogButtonBox.ButtonRole.ActionRole)
        self.button_box.accepted.connect(self.accept_changes)
        self.button_box.rejected.connect(self.reject)
        auto_calculate_button.clicked.connect(self.auto_calculate_weightings)

        toggle_guid_button = QPushButton("Show GUIDs")
        self.button_box.addButton(auto_calculate_button, QDialogButtonBox.ButtonRole.ActionRole)
        verbose_mode = setting(key="verbose_mode", default=0)
        if verbose_mode:
            self.button_box.addButton(toggle_guid_button, QDialogButtonBox.ButtonRole.ActionRole)
        toggle_guid_button.clicked.connect(self.toggle_guid_column)
        self.guid_column_visible = False  # Track GUID column visibility
        self.table.setColumnHidden(4, not self.guid_column_visible)  # Hide GUID column by default

        # Initial validation check
        self.validate_weightings()

        # Restore the radio button state
        mask_mode = self.tree_item.attribute("mask_mode", "")
        if mask_mode == "point":
            self.point_radio_button.setChecked(True)
        elif mask_mode == "polygon":
            self.polygon_radio_button.setChecked(True)
        elif mask_mode == "raster":
            self.raster_radio_button.setChecked(True)
        else:  # ghsl - use the global human settlements layer configured during project setup
            self.ghsl_radio_button.setChecked(True)

        buffer_distance = self.tree_item.attribute("buffer_distance_m", 0)
        self.buffer_distance_m.setValue(int(buffer_distance))

        # Restore the dialog geometry
        self.restore_dialog_geometry()

    def restore_dialog_geometry(self):
        """
        Restore the dialog geometry from QSettings.
        """
        settings = QSettings()
        geometry = settings.value("AnalysisAggregationDialog/geometry_v2", QByteArray())
        log_window_geometry(geometry)
        if geometry:
            log_message("Restoring dialog geometry")
            try:
                self.restoreGeometry(geometry)
                # Sanity-check: if the restored size is still oversized, discard it
                screen = QApplication.primaryScreen().geometry()
                if self.width() > int(screen.width() * 0.85) or self.height() > int(screen.height() * 0.85):
                    log_message("Restored geometry too large, resetting to default", tag="GeoE3", level=Qgis.Warning)
                    settings.remove("AnalysisAggregationDialog/geometry_v2")
                else:
                    return
            except Exception:
                log_message("Restoring geometry failed", tag="GeoE3", level=Qgis.Warning)
        log_message("No saved geometry found, resizing dialog")
        # Sensible default: cap at 900px wide, 80% screen height
        screen = QApplication.primaryScreen().geometry()
        width = min(900, int(screen.width() * 0.65))
        height = min(int(screen.height() * 0.80), 750)
        self.resize(width, height)

    def save_geometry(self):
        """
        Save the dialog geometry to QSettings.
        """
        log_message("Saving dialog geometry")
        settings = QSettings()
        settings.setValue("AnalysisAggregationDialog/geometry_v2", self.saveGeometry())

    def closeEvent(self, event):
        """⚙️ Closeevent.

        Args:
            event: Event.
        """
        self.save_geometry()
        super().closeEvent(event)

    def setup_table(self):
        """
        Set up the QTableWidget to display the analysis dimensions.

        """
        # Table setup
        self.table = QTableWidget(self)
        self.table.setRowCount(len(self.guids))
        self.table.setColumnCount(5)
        self.table.setHorizontalHeaderLabels(["Dimension", "Weight 0-1", "Use", "", "Guid"])
        self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)

        # Adjust column widths
        self.table.horizontalHeader().setSectionResizeMode(
            0, QHeaderView.ResizeMode.Stretch
        )  # dimension column expands
        self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)  # Weight 0-1 column fixed
        self.table.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeMode.Fixed)  # Use column fixed
        self.table.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeMode.Fixed)  # Reset column fixed
        self.table.horizontalHeader().setSectionResizeMode(4, QHeaderView.ResizeMode.Fixed)  # Guid column fixed

        # Set fixed widths for the last three columns
        self.table.setColumnWidth(1, 100)  # Weight 0-1 column width
        self.table.setColumnWidth(2, 50)  # Use column (checkbox) width
        self.table.setColumnWidth(3, 75)  # Reset button column width
        self.table.setColumnWidth(3, 100)  # GUID button column width

        # Populate the table
        for row, guid in enumerate(self.guids):
            item = self.tree_item.getItemByGuid(guid)
            attributes = item.attributes()
            dimension_id = attributes.get("name")
            analysis_weighting = float(attributes.get("analysis_weighting", 0.0))
            default_analysis_weighting = attributes.get("default_analysis_weighting", 0)
            is_included_in_analysis = item.getStatus() != "Excluded from analysis" and analysis_weighting > 0

            name_item = QTableWidgetItem(dimension_id)
            name_item.setFlags(Qt.ItemFlag.ItemIsEnabled)
            self.table.setItem(row, 0, name_item)

            # weightings
            weighting_item = QDoubleSpinBox(self)
            weighting_item.setRange(0.0, 1.0)
            weighting_item.setDecimals(4)
            weighting_item.setValue(analysis_weighting)
            weighting_item.setSingleStep(0.01)
            weighting_item.valueChanged.connect(self.validate_weightings)
            self.table.setCellWidget(row, 1, weighting_item)
            self.weightings[guid] = weighting_item

            # Use checkboxes
            checkbox_widget = self.create_checkbox_widget(row, is_included_in_analysis)
            self.table.setCellWidget(row, 2, checkbox_widget)

            # Reset button
            reset_button = QPushButton("Reset")
            reset_button.clicked.connect(lambda checked, item=weighting_item: item.setValue(default_analysis_weighting))
            self.table.setCellWidget(row, 3, reset_button)

            # Guid column
            guid_item = QTableWidgetItem(guid)
            guid_item.setFlags(Qt.ItemFlag.ItemIsEnabled)
            self.table.setItem(row, 4, guid_item)
            guid_item.setToolTip(str(item.attributes()))

            # Ensure row widgets are in sync with initial checkbox state.
            initial_state = Qt.CheckState.Checked if self.is_checkbox_checked(row) else Qt.CheckState.Unchecked
            self.toggle_row_widgets(row, initial_state)
        # Set the table widget height to be no taller than its content
        self.table.setSizeAdjustPolicy(QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents)
        self.table.resizeColumnsToContents()
        self.table.resizeRowsToContents()
        self.table.setMaximumHeight(
            self.table.verticalHeader().length()  # noqa W503
            + self.table.horizontalHeader().height()  # noqa W503
            + 4  # noqa W503
        )  # Add 2 pixels to prevent scrollbar showing
        self.table.setFrameStyle(QTableWidget.Shape.NoFrame)
        parent_layout = self.geoe3_container.parent().layout()
        parent_layout.replaceWidget(self.geoe3_container, self.table)
        self.geoe3_container.deleteLater()
        parent_layout.update()

    def aggregation_selected(self):
        """Handle combo selection change"""
        self.aggregation_lineedit.hide()

    def population_selected(self):
        """Handle combo selection change"""
        self.population_lineedit.hide()

    def point_selected(self):
        """Handle combo selection change"""
        self.point_lineedit.hide()

    def polygon_selected(self):
        """Handle combo selection change"""
        self.polygon_lineedit.hide()

    def raster_selected(self):
        """Handle combo selection change"""
        self.raster_lineedit.hide()

    def aggregation_toolbutton_clicked(self):
        """⚙️ Aggregation toolbutton clicked."""
        # Show a file dialog to select a raster file
        file_dialog = QFileDialog()
        file_dialog.setFileMode(QFileDialog.FileMode.ExistingFile)
        file_dialog.setNameFilter("Vector files (*.shp *.gpkg)")
        if file_dialog.exec():
            self.aggregation_combo.setCurrentIndex(0)
            file_path = file_dialog.selectedFiles()[0]
            self.aggregation_lineedit.setText(file_path)
            self.aggregation_lineedit.show()

    def population_toolbutton_clicked(self):
        """⚙️ Population toolbutton clicked."""
        # Show a file dialog to select a raster file
        file_dialog = QFileDialog()
        file_dialog.setFileMode(QFileDialog.FileMode.ExistingFile)
        file_dialog.setNameFilter("Raster files (*.tif *.tiff *.asc)")
        if file_dialog.exec():
            self.population_combo.setCurrentIndex(0)
            file_path = file_dialog.selectedFiles()[0]
            self.population_lineedit.setText(file_path)
            self.population_lineedit.show()

    def point_toolbutton_clicked(self):
        """⚙️ Point toolbutton clicked."""
        # Show a file dialog to select a raster file
        file_dialog = QFileDialog()
        file_dialog.setFileMode(QFileDialog.FileMode.ExistingFile)
        file_dialog.setNameFilter("Vector files (*.shp *.gpkg)")
        if file_dialog.exec():
            self.point_combo.setCurrentIndex(0)
            file_path = file_dialog.selectedFiles()[0]
            self.point_lineedit.setText(file_path)
            self.point_lineedit.show()

    def polygon_toolbutton_clicked(self):
        """⚙️ Polygon toolbutton clicked."""
        # Show a file dialog to select a raster file
        file_dialog = QFileDialog()
        file_dialog.setFileMode(QFileDialog.FileMode.ExistingFile)
        file_dialog.setNameFilter("Vector files (*.shp *.gpkg)")
        if file_dialog.exec():
            self.polygon_combo.setCurrentIndex(0)
            file_path = file_dialog.selectedFiles()[0]
            self.polygon_lineedit.setText(file_path)
            self.polygon_lineedit.show()

    def raster_toolbutton_clicked(self):
        """⚙️ Raster toolbutton clicked."""
        # Show a file dialog to select a raster file
        file_dialog = QFileDialog()
        file_dialog.setFileMode(QFileDialog.FileMode.ExistingFile)
        file_dialog.setNameFilter("Raster files (*.tif *.tiff *.asc)")
        if file_dialog.exec():
            self.raster_combo.setCurrentIndex(0)
            file_path = file_dialog.selectedFiles()[0]
            self.raster_lineedit.setText(file_path)
            self.raster_lineedit.show()

    def aggregation_lineedit_text_changed(self):
        """Handle text change in the line edit"""
        self.aggregation_combo.setCurrentIndex(0)

    def population_lineedit_text_changed(self):
        """Handle text change in the line edit"""
        self.population_combo.setCurrentIndex(0)

    def point_lineedit_text_changed(self):
        """Handle text change in the line edit"""
        self.point_combo.setCurrentIndex(0)

    def polygon_lineedit_text_changed(self):
        """Handle text change in the line edit"""
        self.polygon_combo.setCurrentIndex(0)

    def raster_lineedit_text_changed(self):
        """Handle text change in the line edit"""
        self.raster_combo.setCurrentIndex(0)

    def open_link_in_browser(self, url: str):
        """Open the given URL in the user's default web browser.

        Args:
            url: The URL to open in the browser.
        """
        QDesktopServices.openUrl(QUrl(url))

    def toggle_guid_column(self):
        """Toggle the visibility of the GUID column."""
        log_message("Toggling GUID column visibility")
        self.guid_column_visible = not self.guid_column_visible
        self.table.setColumnHidden(4, not self.guid_column_visible)

    def create_checkbox_widget(self, row: int, is_checked: bool) -> QWidget:
        """Create a QWidget containing a QCheckBox for a specific row and center it.

        Args:
            row: The row index for which to create the checkbox.
            is_checked: Initial checked state for the row.

        Returns:
            A QWidget containing a centered QCheckBox.
        """
        checkbox = QCheckBox()
        checkbox.setChecked(is_checked)
        checkbox.stateChanged.connect(lambda state, r=row: self.toggle_row_widgets(r, state))
        checkbox.setEnabled(True)  # Enable by default
        # Create a container widget with a centered layout
        container = QWidget()
        layout = QHBoxLayout()
        layout.addWidget(checkbox)
        layout.setAlignment(Qt.AlignmentFlag.AlignCenter)  # Center the checkbox
        layout.setContentsMargins(0, 0, 0, 0)  # Remove margins
        container.setLayout(layout)

        return container

    def toggle_row_widgets(self, row: int, state: int):
        """Enable or disable widgets in the row based on checkbox state.

        Args:
            row: The row index containing the widgets to toggle.
            state: The checkbox state (Qt.CheckState.Checked or Qt.CheckState.Unchecked).
        """
        is_enabled = state == Qt.CheckState.Checked
        for col in range(self.table.columnCount()):
            # Skip the column containing the checkbox (assumed to be column 2)
            if col == 2:
                continue
            # Disable QTableWidgetItems
            item = self.table.item(row, col)
            if item:
                item.setFlags(Qt.ItemFlag.ItemIsEnabled if is_enabled else Qt.ItemFlag.NoItemFlags)

            # Disable widgets inside cells
            widget = self.table.cellWidget(row, col)
            if widget:
                if isinstance(widget, QDoubleSpinBox) and not is_enabled:
                    widget.setValue(0)  # Reset weightings to zero
                widget.setEnabled(is_enabled)
        self.validate_weightings()

    def auto_calculate_weightings(self):
        """Calculate and set equal weighting for each enabled indicator."""
        log_message("Auto-calculating weightings")
        # Filter rows where the checkbox is checked
        enabled_rows = [row for row in range(self.table.rowCount()) if self.is_checkbox_checked(row)]
        disabled_rows = [row for row in range(self.table.rowCount()) if not self.is_checkbox_checked(row)]
        if not enabled_rows:
            log_message("No enabled rows found, skipping auto-calculation")
            return  # No enabled rows, avoid division by zero

        if len(enabled_rows) == 0:
            equal_weighting = 0.0
        else:
            equal_weighting = 1.0 / len(enabled_rows)  # Divide equally among enabled rows

        # Set the weighting for each enabled row
        for row in enabled_rows:
            log_message(f"Setting equal weighting for row: {row}")
            widget = self.table.cellWidget(row, 1)  # Assuming weight is in column 1
            widget.setValue(equal_weighting)
        # Rest of the rows get assigned zero
        for row in disabled_rows:
            log_message(f"Setting zero weighting for row: {row}")
            widget = self.table.cellWidget(row, 1)  # Assuming weight is in column 1
            widget.setValue(0)
        self.validate_weightings()

    def is_checkbox_checked(self, row: int) -> bool:
        """Check if the checkbox in the specified row is checked.

        Args:
            row: The row index to check.

        Returns:
            True if the checkbox is checked, False otherwise.
        """
        log_message(f"Checking checkbox state for row: {row}")
        checkbox = self.get_checkbox_in_row(row)  # Assuming the checkbox is in column 2
        return checkbox.isChecked() if checkbox else False

    def get_checkbox_in_row(self, row: int) -> QCheckBox:
        """Retrieve the checkbox widget in the specified row.

        Args:
            row: The row index to retrieve the checkbox from.

        Returns:
            The QCheckBox widget, or None if not found.
        """
        container = self.table.cellWidget(row, 2)  # Assuming the checkbox is in column 2
        if container and isinstance(container, QWidget):
            layout = container.layout()
            if layout and layout.count() > 0:
                checkbox = layout.itemAt(0).widget()
                if isinstance(checkbox, QCheckBox):
                    return checkbox
        return None

    def saveWeightingsToModel(self):
        """Assign new weightings to the analysiss's dimensions."""
        for dimension_guid, spin_box in self.weightings.items():
            try:
                new_weighting = spin_box.value()
                self.tree_item.updateDimensionWeighting(dimension_guid, new_weighting)
            except ValueError:
                log_message(
                    f"Invalid weighting input for GUID: {dimension_guid}",
                    tag="GeoE3",
                    level=Qgis.Warning,
                )

    def validate_weightings(self):
        """Validate weightings to ensure they sum to 1 and are within range."""
        try:
            total_weighting = sum(float(spin_box.value() or 0) for spin_box in self.weightings.values())
            valid_sum = abs(total_weighting - 1.0) < 0.001  # Allow slight floating-point tolerance
        except ValueError:
            valid_sum = False

        # In the case that all rows are disabled, the sum is valid
        enabled_rows = [row for row in range(self.table.rowCount()) if self.is_checkbox_checked(row)]
        enabled_rows_count = len(enabled_rows)
        if enabled_rows_count == 0:
            valid_sum = True

        if is_qgis_dark_theme_active():
            normal_color = "color: white;"
        else:
            normal_color = "color: black;"
        # Update button state and cell highlighting
        for spin_box in self.weightings.values():
            if valid_sum:
                spin_box.setStyleSheet(normal_color)  # Reset font color to black if valid
            else:
                spin_box.setStyleSheet("color: red;")  # Set font color to red if invalid

        # Enable or disable the OK button based on validation result
        if hasattr(self, "button_box"):
            ok_button = self.button_box.button(QDialogButtonBox.StandardButton.Ok)
            if ok_button:
                ok_button.setEnabled(valid_sum)

    def accept_changes(self):
        """Handle the OK button by applying changes and closing the dialog."""
        self.save_use_state_to_model()
        self.saveWeightingsToModel()  # Assign weightings when changes are accepted
        self.save_combo_to_model(self.aggregation_combo, self.aggregation_lineedit, "aggregation")
        self.save_combo_to_model(self.population_combo, self.population_lineedit, "population")
        self.save_combo_to_model(self.point_combo, self.point_lineedit, "point_mask")
        self.save_combo_to_model(self.polygon_combo, self.polygon_lineedit, "polygon_mask")
        self.save_combo_to_model(self.raster_combo, self.raster_lineedit, "raster_mask")
        # Save the radio button state
        if self.point_radio_button.isChecked():
            self.tree_item.setAttribute("mask_mode", "point")
        elif self.polygon_radio_button.isChecked():
            self.tree_item.setAttribute("mask_mode", "polygon")
        elif self.raster_radio_button.isChecked():
            self.tree_item.setAttribute("mask_mode", "raster")
        elif self.ghsl_radio_button.isChecked():
            self.tree_item.setAttribute("mask_mode", "ghsl")  # gloabl human settlements layer

        self.tree_item.setAttribute("buffer_distance_m", self.buffer_distance_m.value())
        # Save the dialog geometry
        self.save_geometry()
        self.accept()

    def save_use_state_to_model(self) -> None:
        """Persist the Use checkbox state to dimension analysis_weighting values."""
        for row, dimension_guid in enumerate(self.guids):
            dimension_item = self.tree_item.getItemByGuid(dimension_guid)
            if dimension_item is None:
                continue

            checkbox_checked = self.is_checkbox_checked(row)
            spin_box = self.weightings.get(dimension_guid)
            if checkbox_checked:
                if spin_box and float(spin_box.value() or 0.0) == 0.0:
                    default_weighting = float(dimension_item.attribute("default_analysis_weighting", 1.0) or 1.0)
                    spin_box.setValue(default_weighting)
                    dimension_item.setAttribute("analysis_weighting", default_weighting)
            else:
                if spin_box:
                    spin_box.setValue(0.0)
                dimension_item.setAttribute("analysis_weighting", 0.0)

    def save_combo_to_model(self, combo: QgsMapLayerComboBox, lineedit: QLineEdit, prefix: str):
        """Save the state of a QgsMapLayerComboBox to the json tree item.

        Args:
            combo: The QgsMapLayerComboBox containing the selected layer.
            lineedit: The QLineEdit containing the layer path.
            prefix: The prefix for attribute keys in the tree item.
        """
        item = self.tree_item
        layer = combo.currentLayer()
        if not layer:
            item.setAttribute(f"{prefix}_layer", None)
        else:
            item.setAttribute(f"{prefix}_layer_name", layer.name())
            item.setAttribute(f"{prefix}_layer_source", layer.source())
            item.setAttribute(f"{prefix}_layer_provider_type", layer.providerType())
            item.setAttribute(f"{prefix}_layer_crs", layer.crs().authid())
            if layer.providerType() != "gdal":
                item.setAttribute(f"{prefix}_layer_wkb_type", layer.wkbType())
            item.setAttribute(f"{prefix}_layer_id", layer.id())
        if lineedit.objectName() == "raster_lineedit":
            item.setAttribute(f"{prefix}_raster", lineedit.text())
        else:
            item.setAttribute(f"{prefix}_shapefile", lineedit.text())

    def load_combo_from_model(self, combo: QgsMapLayerComboBox, lineedit: QLineEdit, prefix: str):
        """Load the state of a QgsMapLayerComboBox from the json tree item.

        Args:
            combo: The QgsMapLayerComboBox to populate with the layer.
            lineedit: The QLineEdit to populate with the layer path.
            prefix: The prefix for attribute keys in the tree item.
        """
        item = self.tree_item
        layer_id = item.attribute(f"{prefix}_layer_id", None)
        if layer_id:
            layer = QgsProject.instance().mapLayer(layer_id)
            if layer:
                combo.setLayer(layer)
        if item.attribute(f"{prefix}_shapefile", False):
            lineedit.setText(unquote(item.attribute(f"{prefix}_shapefile")))
            lineedit.setVisible(True)
        if item.attribute(f"{prefix}_raster", False):
            lineedit.setText(unquote(item.attribute(f"{prefix}_raster")))
            lineedit.setVisible(True)
