# -*- coding: utf-8 -*-
"""📦 Json Tree Item module.

This module contains functionality for json tree item.
"""

import traceback
import uuid
from contextlib import contextmanager
from typing import Optional

from qgis.core import Qgis
from qgis.PyQt.QtCore import Qt, QReadWriteLock, QReadLocker, QWriteLocker
from qgis.PyQt.QtGui import QColor, QFont, QIcon

from geest.core.settings import setting
from geest.utilities import is_qgis_dark_theme_active, log_message, resources_path


class JsonTreeItem:
    """A class representing a node in the tree.

    🚩  TAKE NOTE: 🚩

    This class MAY NOT inherit from QObject, as it has to remain
    thread safe and not be tied to the main thread. Items are passed to
    workflow threads and must be able to be manipulated in the background.

    """

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

        Args:
            data: Data.
            role: Role.
            guid: Guid.
            parent: Parent.
        """
        self.parentItem = parent
        self.itemData = data  # name, status, weighting, attributes(dict)
        self.childItems = []
        self.role = role  # Stores whether an item is a dimension, factor, or layer
        self.font_color = QColor(Qt.GlobalColor.black)  # Default font color
        # Add a unique guid for each item
        if guid:
            self.guid = guid
        else:
            self.guid = str(uuid.uuid4())  # Generate a unique identifier for this item

        # Thread safety: Read-write lock for protecting mutable state
        # Multiple threads can read simultaneously, but writes are exclusive
        self._lock = QReadWriteLock()

        if is_qgis_dark_theme_active():
            # Define icons for each role
            self.dimension_icon = QIcon(resources_path("resources", "icons", "dimension-light.svg"))
            self.factor_icon = QIcon(resources_path("resources", "icons", "factor-light.svg"))
            self.indicator_icon = QIcon(resources_path("resources", "icons", "indicator-light.svg"))
        else:
            # Define icons for each role
            self.dimension_icon = QIcon(resources_path("resources", "icons", "dimension.svg"))
            self.factor_icon = QIcon(resources_path("resources", "icons", "factor.svg"))
            self.indicator_icon = QIcon(resources_path("resources", "icons", "indicator.svg"))

        # Define fonts for each role
        self.dimension_font = QFont()
        self.dimension_font.setBold(True)

        self.factor_font = QFont()
        self.factor_font.setItalic(True)

        self._visible = True
        self._enabled = True

    @contextmanager
    def atomicAttributeUpdate(self):
        """
        Context manager for atomic attribute updates.

        Usage:
            with item.atomicAttributeUpdate() as attrs:
                attrs["key1"] = "value1"
                attrs["key2"] = "value2"

        This ensures all attribute updates happen atomically with a write lock.
        """
        self._lock.lockForWrite()
        try:
            yield self.itemData[3] if len(self.itemData) > 3 else {}
        finally:
            self._lock.unlock()

    def getAttribute(self, key, default=None):
        """
        Thread-safe getter for a single attribute.

        Args:
            key: The attribute key to retrieve
            default: Default value if key doesn't exist

        Returns:
            The attribute value or default
        """
        self._lock.lockForRead()
        try:
            if len(self.itemData) > 3:
                return self.itemData[3].get(key, default)
            return default
        finally:
            self._lock.unlock()

    def attributesSnapshot(self):
        """
        Thread-safe method to get a shallow copy of attributes.

        Returns:
            A dict copy of current attributes (snapshot at call time)
        """
        self._lock.lockForRead()
        try:
            if len(self.itemData) > 3:
                return dict(self.itemData[3])
            return {}
        finally:
            self._lock.unlock()

    def set_visibility(self, visible: bool):
        """Sets the visibility of this item."""
        self._visible = visible

    def is_visible(self) -> bool:
        """Returns the visibility status of this item."""
        return self._visible

    def set_enabled(self, enabled: bool):
        """Sets whether this item is enabled (not greyed out)."""
        self._enabled = enabled

    def is_enabled(self) -> bool:
        """Returns whether this item is enabled."""
        return self._enabled

    def is_only_child(self) -> bool:
        """Returns the only child status of this item."""
        siblings_count = len(self.parentItem.childItems)
        if siblings_count == 1:
            return True

    def internalPointer(self):
        """Returns a reference to itself, or any unique identifier for the item."""
        return self.guid

    def appendChild(self, item):
        """⚙️ Appendchild.

        Args:
            item: Item.
        """
        self._lock.lockForWrite()
        try:
            self.childItems.append(item)
        finally:
            self._lock.unlock()

    def child(self, row):
        """⚙️ Child.

        Args:
            row: Row.

        Returns:
            The result of the operation.
        """
        return self.childItems[row]

    def childCount(self, recursive=False):
        """Count the number of children of this item.

        If the recursive flag is set to True, count all descendants.

        Args:
            recursive (bool, optional): _description_. Defaults to False.

        Returns:
            _type_: _description_
        """
        if not recursive:
            return len(self.childItems)
        else:
            count = len(self.childItems)
            for child in self.childItems:
                count += child.childCount(recursive=True)
            return count

    def columnCount(self):
        """⚙️ Columncount.

        Returns:
            The result of the operation.
        """
        return len(self.itemData)

    def data(self, column):
        """⚙️ Data.

        Args:
            column: Column.

        Returns:
            The result of the operation.
        """
        if column < len(self.itemData):
            return self.itemData[column]
        return None

    def setData(self, column, value):
        """⚙️ Setdata.

        Args:
            column: Column.
            value: Value.

        Returns:
            The result of the operation.
        """
        self._lock.lockForWrite()
        try:
            if column < len(self.itemData):
                self.itemData[column] = value
                return True
            return False
        finally:
            self._lock.unlock()

    def parent(self):
        """⚙️ Parent.

        Returns:
            The result of the operation.
        """
        return self.parentItem

    def row(self):
        """⚙️ Row.

        Returns:
            The result of the operation.
        """
        if self.parentItem:
            return self.parentItem.childItems.index(self)
        return 0

    def name(self):
        """⚙️ Name.

        Returns:
            The result of the operation.
        """
        return self.data(0)

    def isIndicator(self):
        """⚙️ Isindicator.

        Returns:
            The result of the operation.
        """
        return self.role == "indicator"

    def isFactor(self):
        """⚙️ Isfactor.

        Returns:
            The result of the operation.
        """
        return self.role == "factor"

    def isDimension(self):
        """⚙️ Isdimension.

        Returns:
            The result of the operation.
        """
        return self.role == "dimension"

    def isAnalysis(self):
        """⚙️ Isanalysis.

        Returns:
            The result of the operation.
        """
        return self.role == "analysis"

    def clear(self, recursive=False):
        """
        Mark the item as not run, keeping any configurations made

        :param recursive: If True, clear all children as well
        """
        if recursive:
            for child in self.childItems:
                child.clear(recursive)
        data = self.attributes()
        data["result"] = "Not Run"
        data["result_file"] = ""
        data["error"] = ""
        data["error_file"] = ""
        data["execution_start_time"] = ""
        data["execution_end_time"] = ""

    def disable(self):
        """
        Mark the item as disabled, which is essentially just setting its weight to zero.
        """
        data = self.attributes()
        data["analysis_mode"] = "Do Not Use"

        if self.isDimension():
            data["analysis_weighting"] = 0.0
        if self.isFactor():
            data["dimension_weighting"] = 0.0
        if self.isIndicator():
            data["factor_weighting"] = 0.0

    def enable(self):
        """Enable the item by resetting weights to defaults."""
        data = self.attributes()
        data["analysis_mode"] = ""
        if self.isDimension():
            data["analysis_weighting"] = data.get("default_analysis_weighting", 1.0)
        if self.isFactor():
            data["dimension_weighting"] = data.get("default_dimension_weighting", 1.0)
            if self.parentItem and self.parentItem.getStatus() == "Excluded from analysis":  # noqa W503  # noqa W503
                self.parentItem.attributes()["analysis_weighting"] = self.parentItem.attribute(
                    "default_analysis_weighting"
                )
        if self.isIndicator():
            data["factor_weighting"] = data.get("default_factor_weighting", 1.0)
            if self.parentItem and self.parentItem.getStatus() == "Excluded from analysis":  # noqa W503  # noqa W503
                self.parentItem.attributes()["dimension_weighting"] = self.parentItem.attribute(
                    "default_dimension_weighting"
                )
                if (
                    self.parentItem.parentItem  # noqa W503
                    and self.parentItem.parentItem.getStatus() == "Excluded from analysis"  # noqa W503  # noqa W503
                ):
                    self.parentItem.parentItem.attributes()["analysis_weighting"] = (
                        self.parentItem.parentItem.attribute("default_analysis_weighting")
                    )

    def getIcon(self):
        """Retrieve the appropriate icon for the item based on its role."""
        if self.isDimension():
            return self.dimension_icon
        elif self.isFactor():
            return self.factor_icon
        elif self.isIndicator():
            return self.indicator_icon
        return None

    def getItemTooltip(self):
        """Retrieve the appropriate tooltip for the item based on its role."""
        data = self.attributes()
        if self.isDimension():
            description = data.get("description", "")
            if description:
                return f"{description}"
            else:
                return "Dimension"
        elif self.isFactor():
            description = data.get("description", "")
            if description:
                return f"{description}"
            else:
                return "Factor"
        elif self.isIndicator():
            error = data.get("error", None)
            if error:
                return f"Error: {data.get('error')}"
            description = data.get("description", "")
            if description:
                return f"{description}"
            else:
                return "Indicator"
        return ""

    def getStatusIcon(self):
        """Retrieve the appropriate icon for the item based on its role."""
        status = self.getStatus()

        if status == "Excluded from analysis":
            return QIcon(resources_path("resources", "icons", "excluded.svg"))
        if status == "Completed successfully":
            return QIcon(resources_path("resources", "icons", "completed-success.svg"))
        elif status == "Required and not configured":
            return QIcon(resources_path("resources", "icons", "required-not-configured.svg"))
        elif status == "Not configured (optional)":
            return QIcon(resources_path("resources", "icons", "not-configured.svg"))
        elif status == "Configured, not run":
            return QIcon(resources_path("resources", "icons", "not-run.svg"))
        elif status == "Workflow failed":
            return QIcon(resources_path("resources", "icons", "failed.svg"))
        elif status == "WRITE TOOL TIP":
            return QIcon(resources_path("resources", "icons", "unspecified.svg"))
        else:
            return QIcon(resources_path("resources", "icons", "unspecified.svg"))

    def getStatus(self):
        """Return the status of the item as single character."""
        try:
            if not isinstance(self.itemData, list):
                return ""
            if len(self.itemData) < 4:
                return ""
            data = self.attributes()
            analysis_mode = data.get("analysis_mode", "")
            qgis_layer_source_key = analysis_mode.replace("use_", "") + "_layer_source"
            qgis_layer_shapefile_key = analysis_mode.replace("use_", "") + "_shapefile"
            qgis_layer_raster_key = analysis_mode.replace("use_", "") + "_raster"

            if "Workflow Completed" in data.get("result", "") and data.get("result_file", ""):
                return "Completed successfully"

            # First check if the item weighting is 0, or its parent factor is zero
            # If so, return "Excluded from analysis"
            if self.isIndicator():
                required_by_parent = (
                    float(self.parentItem.attributes().get("dimension_weighting", 0.0)) if self.parentItem else 0.0
                )
                required_by_self = float(data.get("factor_weighting", 0.0))
                if not required_by_parent or not required_by_self:
                    return "Excluded from analysis"

                # Avoid infinite recursion by NOT using getStatus in the parent checks
                # If the parent's dimension weighting is zero, return "Excluded from analysis"
                if self.parentItem and not float(self.parentItem.attribute("dimension_weighting", 0.0)):
                    return "Excluded from analysis"
                # If the grandparent's analysis weighting is zero, return "Excluded from analysis"
                if (
                    self.parentItem  # noqa W503
                    and self.parentItem.parentItem  # noqa W503
                    and not float(  # noqa W503
                        self.parentItem.parentItem.attribute("analysis_weighting", 0.0)  # noqa W503
                    )
                ):
                    return "Excluded from analysis"

            if self.isFactor():
                # If the dimension weighting is zero, return "Excluded from analysis"
                if not float(data.get("dimension_weighting", 0.0)):
                    return "Excluded from analysis"

                # If the sum of the indicator weightings is zero, return "Excluded from analysis"
                weight_sum = 0
                unconfigured_child_count = 0
                for child in self.childItems:
                    weight_sum += float(child.attribute("factor_weighting", 0.0))
                    if child.getStatus() in [
                        "Not configured (optional)",
                        "Required and not configured",
                    ]:
                        unconfigured_child_count += 1
                if not weight_sum:
                    return "Excluded from analysis"
                if unconfigured_child_count:
                    return "Required and not configured"

                # If the parent's analysis weighting is zero, return "Excluded from analysis"
                if self.parentItem and not float(self.parentItem.attribute("analysis_weighting", 0.0)):
                    return "Excluded from analysis"

                # If any child indicator has a status of "Workflow failed", return "Workflow failed"
                for child in self.childItems:
                    child_status = child.getStatus()
                    # log_message(f"Child status: {child_status}")
                    if child_status == "Workflow failed":
                        return "Workflow failed"
                    if child_status == "Required and not configured":
                        return "Required and not configured"
                    if child_status == "Error":
                        return "Workflow failed"
                    if "Failed" in child_status:
                        return "Workflow failed"

            if self.isDimension():
                # If the analysis weighting is zero, return "Excluded from analysis"
                if not float(data.get("analysis_weighting", 0.0)):
                    return "Excluded from analysis"

                # If the sum of the factor weightings is zero, return "Excluded from analysis"
                weight_sum = sum(float(child.attribute("dimension_weighting", 0.0)) for child in self.childItems)
                if not weight_sum:
                    return "Excluded from analysis"

            if self.isAnalysis():
                # If the sum of the dimension weightings is zero, return "Excluded from analysis"
                weight_sum = sum(float(child.attribute("analysis_weighting", 0.0)) for child in self.childItems)
                if not weight_sum:
                    return "Excluded from analysis"

            # Check for workflow errors
            if "Error" in data.get("result", "") or "Failed" in data.get("result", ""):
                return "Workflow failed"

            # Check item configuration status
            if "Do Not Use" in analysis_mode and data.get("factor_weighting", 0.0) > 0:
                return "Required and not configured"
            if "Do Not Use" in analysis_mode:
                return "Not configured (optional)"
            if (
                self.isIndicator()  # noqa W503
                and analysis_mode == ""  # noqa W503
                and data.get("factor_weighting", 0.0) > 0  # noqa W503
            ):
                return "Required and not configured"
            if (
                self.isIndicator()  # noqa W503
                and analysis_mode == ""  # noqa W503
                and data.get("factor_weighting", 0.0) == 0.0  # noqa W503
            ):
                return "Not configured (optional)"

            # Test for algs requiring vector inputs
            if self.isIndicator() and analysis_mode not in [
                "use_index_score",
                "use_index_score_with_ookla",
                "use_environmental_hazards",
                "use_contextual_index_score",  # Contextual indicators don't use vector layers
                "use_eplex_score",  # EPLex indicators don't use vector layers
                "use_index_score_with_ghsl",  # GHSL indicators use raster, not vector
                "use_nighttime_lights",  # Nighttime lights use raster, not vector
            ]:
                if not data.get(qgis_layer_source_key, False) and not data.get(qgis_layer_shapefile_key, False):
                    return "Not configured (optional)"

            # Test for algs requiring raster inputs
            if self.isIndicator() and analysis_mode in ["use_environmental_hazards"]:
                if not data.get(qgis_layer_source_key, False) and not data.get(qgis_layer_raster_key, False):
                    return "Not configured (optional)"

            # Check if configured but not run
            if "Not Run" in data.get("result", "") and not data.get("result_file", ""):
                return "Configured, not run"
            if not data.get("result", False):
                return "Configured, not run"

            # Default fallback
            return "WRITE TOOL TIP"

        except Exception as e:
            verbose_mode = setting("verbose_mode", False)
            if verbose_mode:
                log_message(f"Error getting status: {e}", level=Qgis.Warning)
                log_message(traceback.format_exc(), level=Qgis.Warning)
            return f"Status Failed - {e}"

    def getFont(self):
        """Retrieve the appropriate font for the item based on its role."""
        if self.isDimension():
            return self.dimension_font
        elif self.isFactor():
            return self.factor_font
        return QFont()

    def getPaths(self) -> []:
        """Return the path of the item in the tree in the form dimension/factor/indicator.

        :return: A list of strings representing the path of the item in the tree.
        """
        path = []
        if self.isIndicator():
            path.append(self.parentItem.parentItem.attributes().get("id", "").lower().replace(" ", "_"))
            path.append(self.parentItem.attribute("id", "").lower().replace(" ", "_"))
            path.append(self.attribute("id", "").lower().replace(" ", "_"))
        elif self.isFactor():
            path.append(self.parentItem.attribute("id", "").lower().replace(" ", "_"))
            path.append(self.attribute("id", "").lower().replace(" ", "_"))
        if self.isDimension():
            path.append(self.attribute("id", "").lower().replace(" ", "_"))
        return path

    def attributes(self):
        """Return a reference to the dict of attributes for this item.

        🚨 Beware of Side Effects! Any changes you make to the dict will be propogated
           back to the tree model.🚨
        """
        if len(self.itemData) > 3:
            try:
                return self.itemData[3]
            except KeyError:
                log_message(
                    f"Error: {self.itemData} item 3 is not a dictionary",
                    level=Qgis.Warning,
                )
                return {}
        else:
            return {}

    def attribute(self, key, default=None):
        """Return the value of the attribute with the specified key."""
        return self.attributes().get(key, default)

    def setAttributes(self, attributes):
        """Set the attributes of the item."""
        self.itemData[3] = attributes

    def setAttribute(self, attribute_name, attribute_value):
        """Set the attribute of the item."""
        self._lock.lockForWrite()
        try:
            self.itemData[3][attribute_name] = attribute_value
        finally:
            self._lock.unlock()

    def attributesAsMarkdown(self):
        """Return the attributes as a markdown formatted string."""
        attributes = self.attributes()
        if not attributes:
            return "The dictionary is empty."

        # Extract keys and values
        headers = ["Key", "Value"]
        rows = [(str(key), str(value).replace("\n", "  \n")) for key, value in attributes.items()]

        # Calculate column widths
        col_widths = [
            max(len(headers[0]), max(len(row[0]) for row in rows)),
            max(len(headers[1]), max(len(row[1]) for row in rows)),
        ]

        # Construct the table
        table = []

        # Add header
        header_line = f"| {headers[0]:<{col_widths[0]}} | {headers[1]:<{col_widths[1]}} |"  # noqa E231
        table.append(header_line)
        table.append(f"|{'-' * (col_widths[0] + 2)}|{'-' * (col_widths[1] + 2)}|")

        # Add rows
        for key, value in rows:
            row_line = f"| {key:<{col_widths[0]}} | {value:<{col_widths[1]}} |"  # noqa E231
            table.append(row_line)

        return "\n" + "\n".join(table) + "\n"

    def setAnalysisMode(self, mode):
        """Set the analysis mode of the item."""
        self.attributes()["analysis_mode"] = mode

    def ensureValidAnalysisMode(self):
        """Ensure the analysis mode is valid for this item."""
        if self.isDimension():
            if self.attribute("analysis_mode", "") == "Do Not Use":
                self.attributes()["analysis_mode"] = "dimension_aggregation"
        if self.isFactor():
            if self.attribute("analysis_mode", "") == "Do Not Use":
                self.attributes()["analysis_mode"] = "factor_aggregation"
        if self.isIndicator():
            if self.attribute("analysis_mode", "") == "Do Not Use":
                log_message(f"Analysis mode for {self.attribute('id')} is set to Do Not Use")
                log_message("Updating it to the first valid analysis mode")
                # Set the analysis mode to the first matching key below that is not zero
                # Get a list of all attributes that start with 'use_'
                for key in self.attributes().keys():
                    if key.startswith("use_"):
                        log_message(f"Current key: {key} has value {self.attribute(key, 0)}")
                        if self.attribute(key, 0) == 1:
                            log_message(f"Setting analysis mode to {key}")
                            self.setAnalysisMode(key)
                            break

    def getDescendantIndicators(self, include_completed=True, include_disabled=False):
        """Return the list of indicators under this item.

        Recurses through the tree to find all indicators under this item.

        :param include_completed: If True, only return indicators that are completed.
        :param include_disabled: If True, also return indicators that are disabled.

        """
        indicators = []
        if self.isIndicator():
            if self.getStatus() != "Completed successfully" or include_completed:
                if self.getStatus() != "Excluded from analysis" or include_disabled:
                    indicators.append(self)
        for child in self.childItems:
            indicators.extend(child.getDescendantIndicators())
        return indicators

    def getDescendantFactors(self, include_completed=True, include_disabled=False):
        """Return the list of factors under this item.

        Recurses through the tree to find all factors under this item.

        :param include_completed: If True, also return factors that are completed.
        :param include_disabled: If True, also return factors that are disabled.
        """
        factors = []
        if self.isFactor():
            if self.getStatus() != "Completed successfully" or include_completed:
                if self.getStatus() != "Excluded from analysis" or include_disabled:
                    factors.append(self)
        for child in self.childItems:
            factors.extend(child.getDescendantFactors(include_completed, include_disabled))
        return factors

    def getDescendantDimensions(self, include_completed=True, include_disabled=False):
        """Return the list of dimensions under this item.

        Recurses through the tree to find all dimensions under this item.

        :param include_completed: If True, include dimensions that are completed.
        :param include_disabled: If True, include dimensions that are disabled.
        result_file
        """

        dimensions = []
        if self.isDimension():
            if self.getStatus() != "Completed successfully" or include_completed:
                if self.getStatus() != "Excluded from analysis" or include_disabled:
                    dimensions.append(self)
        for child in self.childItems:
            dimensions.extend(child.getDescendantDimensions(include_completed, include_disabled))
        return dimensions

    def getDescendantAnalyses(self, include_completed=True):
        """Return the top level analysis item or None if it is completed and include_completed is False.

        Will also return None if self is not the top level node

        :param include_completed: If True, include dimensions that are completed.
        :param include_disabled: If True, include dimensions that are disabled.
        result_file
        """

        analyses = []
        if self.isAnalysis():
            if self.getStatus() != "Completed successfully" or include_completed:
                analyses.append(self)
        for child in self.childItems:
            analyses.extend(child.getDescendantAnalyses(include_completed))
        return analyses

    def getFactorIndicatorGuids(self):
        """Return the list of indicators under this factor."""
        guids = []
        if self.isFactor():
            guids = [child.guid for i, child in enumerate(self.childItems)]
        return guids

    def getDimensionFactorGuids(self):
        """Return the list of factors under this dimension."""
        guids = []
        if self.isDimension():
            guids = [child.guid for i, child in enumerate(self.childItems)]
        return guids
        # attributes["analysis_mode"] = "dimension_aggregation"

    def getAnalysisDimensionGuids(self):
        """Return the list of factors under this dimension."""
        guids = []
        if self.isAnalysis():
            guids = [child.guid for i, child in enumerate(self.childItems)]
        return guids
        # attributes["analysis_mode"] = "dimension_aggregation"

    def getItemByGuid(self, guid) -> Optional["JsonTreeItem"]:
        """Return the item with the specified guid."""
        if self.guid == guid:
            return self
        for child in self.childItems:
            item = child.getItemByGuid(guid)
            if item:
                return item
        return None

    def updateIndicatorWeighting(self, indicator_guid, new_weighting):
        """Update the weighting of a specific indicator by its name."""
        try:
            # Search for the indicator by name
            indicator_item = self.getItemByGuid(indicator_guid)

            # If found, update the weighting
            if indicator_item:
                log_message(f"Updating weighting for {indicator_guid} to {new_weighting}")
                indicator_item.setData(2, f"{new_weighting:.2f}")  # noqa E231
                # weighting references the level above (i.e. factor)
                indicator_item.attributes()["factor_weighting"] = new_weighting
            else:
                # Log if the indicator name is not found
                log_message(
                    f"Indicator '{indicator_guid}' not found.",
                    tag="GeoE3",
                    level=Qgis.Warning,
                )

        except Exception as e:
            # Handle any exceptions and log the error
            log_message(f"Error updating weighting: {e}", level=Qgis.Warning)

    def updateFactorWeighting(self, factor_guid, new_weighting):
        """Update the weighting of a specific factor by its guid."""
        try:
            # Search for the factor by name
            factor_item = self.getItemByGuid(factor_guid)
            # If found, update the weighting
            if factor_item:
                factor_item.setData(2, f"{new_weighting:.2f}")  # noqa E231
                # weighting references the level above (i.e. dimension)
                factor_item.attributes()["dimension_weighting"] = new_weighting

            else:
                # Log if the factor name is not found
                log_message(
                    f"Factor '{factor_guid}' not found.",
                    tag="GeoE3",
                    level=Qgis.Warning,
                )

        except Exception as e:
            # Handle any exceptions and log the error
            log_message(f"Error updating weighting: {e}", level=Qgis.Warning)

    def updateDimensionWeighting(self, dimension_guid, new_weighting):
        """Update the weighting of a specific dimension by its guid."""
        try:
            # Search for the factor by name
            dimension_item = self.getItemByGuid(dimension_guid)
            # If found, update the weighting
            if dimension_item:
                dimension_item.setData(2, f"{new_weighting:.2f}")  # noqa E231
                # weighting references the level above (i.e. analysis)
                dimension_item.attributes()["analysis_weighting"] = new_weighting

            else:
                # Log if the factor name is not found
                log_message(
                    f"Factor '{dimension_guid}' not found.",
                    tag="GeoE3",
                    level=Qgis.Warning,
                )

        except Exception as e:
            # Handle any exceptions and log the error
            log_message(f"Error updating weighting: {e}", level=Qgis.Warning)
