# -*- coding: utf-8 -*-
"""
/***************************************************************************
 Mapbiomasfogo
                                 A QGIS plugin
 Este plugin permite acesso aos produtos de fogo do Mapbiomas diretamente no Qgis
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2025-06-05
        git sha              : $Format:%H$
        copyright            : (C) 2025 by IPAM Amazonia
        email                : contato@mapbiomas.org
 ***************************************************************************/
            Desenvolvimento do plugin: @IPAM - Brasília, DF - BR
  Newton Monteiro, Wallace Silva, Felipe Martenexen, Vera Laísa, João Ribeiro
                   contato:newton.monteiro@ipam.org.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 html
import os
import re
import shutil
import tempfile
import unicodedata
import zipfile
import json

from qgis.PyQt import uic
from qgis.PyQt import QtWidgets, QtCore
from qgis.PyQt.QtCore import QVariant
from qgis.core import (
    QgsProject,
    QgsVectorFileWriter,
    QgsVectorLayer,
    QgsWkbTypes,
    QgsMessageLog,
    # Qgis,  # ← Importado do compat.py para compatibilidade QGIS 3/4
    QgsCoordinateReferenceSystem,
    QgsGeometry,
    QgsField,
    QgsFeature,
    QgsProcessingContext,
    QgsProcessingFeedback,
)
# from qgis.core import QgsMapLayerProxyModel  # ← Importado do compat.py
from qgis import processing
from .parametros import ano_maximo, ano_minimo, collection, base_url, upload_base_url
from .mapbiomas_api import MapBiomasApiClient
from .compat import (
    Qt, 
    QMessageBox,
    QgisMessageLevel,
    QgsVectorFileWriterError,
    QgsMapLayerProxyModel,  # Import from compat.py for QGIS 3/4 compatibility
    Qgis,  # Import from compat.py for QGIS 3/4 compatibility
)  # Qt5/Qt6 and QGIS 3/4 enum compatibility

# Detect QGIS version and load correct .ui file
def _get_ui_file_path(base_name):
    """Load .ui file compatible with current QGIS version (3 or 4)"""
    try:
        from qgis.core import Qgis as _Qgis
        qgis_version = int(_Qgis.QGIS_VERSION.split('.')[0])
        if qgis_version >= 4:
            ui_file = f'{base_name}_QGS4.ui'
        else:
            ui_file = f'{base_name}_QGS3.ui'
    except (AttributeError, ValueError):
        # Fallback to QGS3 if version detection fails
        ui_file = f'{base_name}_QGS3.ui'
    
    ui_path = os.path.join(os.path.dirname(__file__), ui_file)
    if not os.path.exists(ui_path):
        # Last resort: try without version suffix
        ui_path = os.path.join(os.path.dirname(__file__), f'{base_name}.ui')
    
    return ui_path

# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer
FORM_CLASS, _ = uic.loadUiType(_get_ui_file_path('MapBiomas_fogo_dialog_base'))

class MapbiomasfogoDialog(QtWidgets.QDialog, FORM_CLASS):
    NODE_KIND_ROLE = Qt.ItemDataRole.UserRole
    TERRITORY_ID_ROLE = Qt.ItemDataRole.UserRole + 1
    UC_TYPE_ROLE = Qt.ItemDataRole.UserRole + 2

    UC_CATEGORY_IDS = [153, 154, 155, 156, 157, 158, 159, 160, 161]
    UC_SUBCATEGORY_DEFINITIONS = [
        ("Área de Relevante Interesse Ecológico", ["AREA DE RELEVANTE INTERESSE ECOLOGICO", "ARIE"]),
        ("Área de Proteção Ambiental", ["AREA DE PROTECAO AMBIENTAL", "APA"]),
        ("Reserva de Desenvolvimento Sustentável", ["RESERVA DE DESENVOLVIMENTO SUSTENTAVEL", "RDS"]),
        ("Reserva Particular do Patrimônio Natural", ["RESERVA PARTICULAR DO PATRIMONIO NATURAL", "RPPN"]),
        ("Reserva Extrativista", ["RESERVA EXTRATIVISTA", "RESEX"]),
        ("Reserva Biológica", ["RESERVA BIOLOGICA", "REBIO"]),
        ("Refúgio de Vida Silvestre", ["REFUGIO DE VIDA SILVESTRE", "REVIS", "RVS"]),
        ("PARNA", ["PARQUE", "PARNA"]),
        ("Monumento Natural", ["MONUMENTO NATURAL", "MONA"]),
        ("FLONA", ["FLORESTA", "FLONA"]),
        ("Estação Ecológica", ["ESTACAO ECOLOGICA", "ESEC"]),
    ]

    def __init__(self, parent=None):
        super(MapbiomasfogoDialog, self).__init__(parent)
        self.setupUi(self)
        self.api_client = MapBiomasApiClient(base_url=base_url, upload_base_url=upload_base_url)
        self._territory_tree_updating = False
        self._user_geometry_syncing = False
        self._uc_territories_cache = None

        # Define o valor máximo como o ano atual - 1
        self.FireAnualYear.setMaximum(ano_maximo)
        self.FireAcumulatedT1.setMaximum(ano_maximo)
        self.FireFrequencyYearT1.setMaximum(ano_maximo)
        self.FireMonthlyYear.setMaximum(ano_maximo)
        self.FireLastYear.setMaximum(ano_maximo)
        self.FireSizeYear.setMaximum(ano_maximo)
        self.FireReturnIntervalYear.setMaximum(ano_maximo)
        self.FireSeverityYear.setMaximum(ano_maximo)
        self.FireMeanReturnIntervalYear.setMaximum(ano_maximo)
        self.collection_version.setText(f"COLEÇÃO {collection}")
        self.collection_version.setStyleSheet("color: white;")
        self.Descriotion_download_3.setText(f"<html><head/><body><p><span style=\" font-weight:600;\">Selecione os anos desejados para carregar as cicatrizes de fogo da Coleção {collection}:</span></p></body></html>")
        # Atualiza Descriotion_download_2 com o valor dinâmico da collection
        self._update_download_description_with_collection()
        self._setup_disclaimer_text()
        self._setup_user_geometry_layer_combo()
        self._setup_territory_tree()
        self._setup_download_years_combo()

        # Lista de todas as QCheckBox relevantes
        self.checkboxes = [
            self.check_fire_annual_by_total_burned,
            self.check_fire_annual_by_class,
            self.check_fire_annual_by_natural_anthropic,
            self.check_fire_accumulated_by_total_burned,
            self.check_fire_accumulated_by_class,
            self.check_fire_accumulated_by_natural_anthropic,
            self.check_fire_frequency,
            self.check_fire_Monthly,
            self.check_fire_Last,
            self.check_fire_size,
            self.check_fire_return_interval,
            self.check_fire_severity,
            self.check_FireMeanReturnIntervalYear
        ]

        # Conecta o evento de mudança de estado de cada checkbox ao método de validação
        for cb in self.checkboxes:
            cb.stateChanged.connect(self._validate_ok_button)

        # Valida o botão OK no início
        self._validate_ok_button()

    @staticmethod
    def _to_html_paragraph(text):
        escaped = html.escape(str(text or ""))
        return escaped.replace("\n", "<br>")

    def _build_disclaimer_html(self, informative_note):
        title_block = informative_note.get("title", {}) if isinstance(informative_note, dict) else {}
        description_block = informative_note.get("description", {}) if isinstance(informative_note, dict) else {}

        title_pt = 'Mapbiomas Fogo Coleção ' + str(collection)
        description_pt = description_block.get("pt-BR", "")
        title_en = 'Mapbiomas Fire Collection ' + str(collection)
        description_en = description_block.get("en-US", "")

        title_style = "color:#b6b6b6; font-family:'Montserrat','sans-serif'; font-size:14pt; font-weight:bold;"
        desc_style = "color:#b6b6b6; font-family:'Montserrat','sans-serif'; font-size:9pt;"

        return (
            "<html><head/><body>"
            f"<p align=\"center\" style=\"{title_style}\">{html.escape(title_pt)}</p>"
            f"<p align=\"justify\" style=\"{desc_style}\">{self._to_html_paragraph(description_pt)}</p>"
            f"<p align=\"center\" style=\"{title_style}\">{html.escape(title_en)}</p>"
            f"<p align=\"justify\" style=\"{desc_style}\">{self._to_html_paragraph(description_en)}</p>"
            "</body></html>"
        )

    def _setup_disclaimer_text(self):
        try:
            informative_note = self.api_client.get_fire_informative_note(region_name="brazil")
            disclaimer_html = self._build_disclaimer_html(informative_note)
        except Exception as exc:
            print(f"Falha ao carregar texto informativo: {exc}")
            return

        self.Text_disclaimer.setStyleSheet("color: #b6b6b6;")
        self.Text_disclaimer.setText(disclaimer_html)

    def _update_download_description_with_collection(self):
        """Atualiza o texto de Descriotion_download_2 substituindo 'Coleção N' por 'Coleção {collection}'"""
        if not hasattr(self, "Descriotion_download_2"):
            return
        
        current_text = self.Descriotion_download_2.text()
        # Substitui "Coleção N" por "Coleção {collection}"
        updated_text = current_text.replace("Coleção N", f"Coleção {collection}")
        self.Descriotion_download_2.setText(updated_text)

    @staticmethod
    def _normalize_label(value):
        text = str(value or "")
        text = unicodedata.normalize("NFKD", text)
        text = "".join(ch for ch in text if not unicodedata.combining(ch))
        return text.upper().strip()

    def _setup_user_geometry_layer_combo(self):
        """Permite no combo apenas camadas vetoriais de polígono."""
        if not hasattr(self, "mMapLayerComboBox_usergeometry"):
            return

        self.mMapLayerComboBox_usergeometry.setFilters(QgsMapLayerProxyModel.Filter.PolygonLayer)
        if hasattr(self.mMapLayerComboBox_usergeometry, "setAllowEmptyLayer"):
            self.mMapLayerComboBox_usergeometry.setAllowEmptyLayer(True, "")
        if hasattr(self.mMapLayerComboBox_usergeometry, "setCurrentLayer"):
            self.mMapLayerComboBox_usergeometry.setCurrentLayer(None)
        self.mMapLayerComboBox_usergeometry.layerChanged.connect(self._on_user_geometry_layer_changed)

    def _clear_user_geometry_selection(self):
        if not hasattr(self, "mMapLayerComboBox_usergeometry"):
            return
        self._user_geometry_syncing = True
        try:
            if hasattr(self.mMapLayerComboBox_usergeometry, "setCurrentLayer"):
                self.mMapLayerComboBox_usergeometry.setCurrentLayer(None)
            else:
                self.mMapLayerComboBox_usergeometry.setCurrentIndex(-1)
        finally:
            self._user_geometry_syncing = False

    def _has_user_geometry_selected(self):
        if not hasattr(self, "mMapLayerComboBox_usergeometry"):
            return False
        return self.mMapLayerComboBox_usergeometry.currentLayer() is not None

    def _on_user_geometry_layer_changed(self, layer):
        if self._user_geometry_syncing:
            return
        if layer is None:
            self._update_brazil_visual_state()
            return
        self._reset_territory_tree_checks()

    def upload_selected_user_geometry_territory(self, api_client, region_name="brazil"):
        """Exporta a camada poligonal selecionada em ZIP e envia para /territories/upload.

        Retorna o territoryId (UUID) criado pela API ou None quando nao ha
        camada de geometria selecionada no combo.
        """
        if not hasattr(self, "mMapLayerComboBox_usergeometry"):
            return None

        layer = self.mMapLayerComboBox_usergeometry.currentLayer()
        if layer is None:
            return None
        if not isinstance(layer, QgsVectorLayer):
            return None
        if layer.geometryType() != Qgis.GeometryType.Polygon:
            return None
        if layer.featureCount() == 0:
            return None

        temp_dir = tempfile.mkdtemp(prefix="mapbiomas_fogo_geometry_")
        try:
            shp_path = os.path.join(temp_dir, "my_geometry.shp")
            
            selected_only_checkbox = False
            if hasattr(self, "SelectedGeometry"):
                selected_only_checkbox = self.SelectedGeometry.isChecked()

            if selected_only_checkbox:
                # Condição 3: SelectedGeometry marcado -> Aplica Dissolve apenas nas geometrias selecionadas pelo usuário
                if layer.selectedFeatureCount() == 0:
                    raise RuntimeError("Nenhuma geometria selecionada na camada para exportar.")
                
                temp_input_shp = os.path.join(temp_dir, "temp_input.shp")
                write_result = QgsVectorFileWriter.writeAsVectorFormat(
                    layer,
                    temp_input_shp,
                    "UTF-8",
                    layer.crs(),
                    "ESRI Shapefile",
                    True,  # only_selected = True
                )

                if isinstance(write_result, tuple):
                    error_code = write_result[0]
                else:
                    error_code = write_result

                if error_code != QgsVectorFileWriterError.NoError:
                    raise RuntimeError(f"Falha ao exportar geometrias selecionadas para shapefile temporario. Codigo: {error_code}")

                import processing
                dissolve_params = {
                    'INPUT': temp_input_shp,
                    'FIELD': [],
                    'OUTPUT': shp_path
                }
                processing.run("native:dissolve", dissolve_params)

            else:
                # SelectedGeometry desmarcado
                feature_count = layer.featureCount()

                if feature_count > 1:
                    # Condição 2: Mais de uma feição na camada -> Dissolve toda a camada e segue processo normal
                    temp_input_shp = os.path.join(temp_dir, "temp_input.shp")
                    write_result = QgsVectorFileWriter.writeAsVectorFormat(
                        layer,
                        temp_input_shp,
                        "UTF-8",
                        layer.crs(),
                        "ESRI Shapefile",
                        False,  # only_selected = False
                    )

                    if isinstance(write_result, tuple):
                        error_code = write_result[0]
                    else:
                        error_code = write_result

                    if error_code != QgsVectorFileWriterError.NoError:
                        raise RuntimeError(f"Falha ao exportar geometrias da camada para shapefile temporario. Codigo: {error_code}")

                    import processing
                    dissolve_params = {
                        'INPUT': temp_input_shp,
                        'FIELD': [],
                        'OUTPUT': shp_path
                    }
                    processing.run("native:dissolve", dissolve_params)

                else:
                    # Condição 1: Camada com uma única feição -> Processo normal (exporta sem dissolve)
                    write_result = QgsVectorFileWriter.writeAsVectorFormat(
                        layer,
                        shp_path,
                        "UTF-8",
                        layer.crs(),
                        "ESRI Shapefile",
                        False,  # only_selected = False
                    )

                    if isinstance(write_result, tuple):
                        error_code = write_result[0]
                    else:
                        error_code = write_result

                    if error_code != QgsVectorFileWriterError.NoError:
                        raise RuntimeError(f"Falha ao exportar geometria unica para shapefile. Codigo: {error_code}")

            base_name = os.path.splitext(shp_path)[0]
            zip_path = os.path.join(temp_dir, "my_geometry.zip")
            has_file = False
            with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zip_file:
                for extension in (".shp", ".shx", ".dbf", ".prj", ".cpg"):
                    part_path = base_name + extension
                    if not os.path.exists(part_path):
                        continue
                    zip_file.write(part_path, arcname=os.path.basename(part_path))
                    has_file = True

            if not has_file:
                raise RuntimeError("Arquivo ZIP da geometria foi criado vazio.")

            territory_id = api_client.upload_territory_zip(zip_path, region_name=region_name)
            print(f"Geometria customizada enviada com sucesso. territoryId: {territory_id}")
            return territory_id
        finally:
            shutil.rmtree(temp_dir, ignore_errors=True)

    def _create_tree_item(self, text, kind, territory_id=None):
        item = QtWidgets.QTreeWidgetItem([text])
        item.setData(0, self.NODE_KIND_ROLE, kind)
        if territory_id:
            item.setData(0, self.TERRITORY_ID_ROLE, territory_id)
        item.setFlags(item.flags() | Qt.ItemFlag.ItemIsUserCheckable)
        item.setCheckState(0, Qt.CheckState.Unchecked)
        return item

    def _add_placeholder(self, parent_item):
        placeholder = QtWidgets.QTreeWidgetItem(["(carregar)"])
        placeholder.setData(0, self.NODE_KIND_ROLE, "placeholder")
        placeholder.setFlags(Qt.ItemFlag.ItemIsEnabled)
        parent_item.addChild(placeholder)

    def _has_placeholder(self, item):
        for index in range(item.childCount()):
            if item.child(index).data(0, self.NODE_KIND_ROLE) == "placeholder":
                return True
        return False

    def _remove_placeholders(self, item):
        index = item.childCount() - 1
        while index >= 0:
            child = item.child(index)
            if child.data(0, self.NODE_KIND_ROLE) == "placeholder":
                item.removeChild(child)
            index -= 1

    def _setup_territory_tree(self):
        tree = self.Territorial_TreeWidget
        tree.clear()
        tree.setColumnCount(1)
        tree.setHeaderLabel("Selecione as categorias e níveis territoriais abaixo para refinar a sua análise:")

        self._Brasil_root = self._create_tree_item("Brasil", "brazil_root")
        self._biomes_root = self._create_tree_item("Biomas", "biomes_root")
        self._states_root = self._create_tree_item("Unidades Federativas", "states_root")
        self._uc_root = self._create_tree_item("Unidades de Conservação", "uc_root")
        self._ti_root = self._create_tree_item("Terras Indígenas", "ti_root")
        self._quilombolas_root = self._create_tree_item("Territórios Quilombolas", "quilombolas_root")
        self._assentamentos_root = self._create_tree_item("Assentamentos", "assentamentos_root")

        for root_item in (self._Brasil_root, self._biomes_root, self._states_root, self._uc_root, self._ti_root, self._quilombolas_root, self._assentamentos_root ):
            tree.addTopLevelItem(root_item)

        self._Brasil_root.setCheckState(0, Qt.CheckState.Checked)
        self._add_placeholder(self._biomes_root)
        self._add_placeholder(self._states_root)
        self._add_placeholder(self._uc_root)
        self._add_placeholder(self._ti_root)
        self._add_placeholder(self._quilombolas_root)
        self._add_placeholder(self._assentamentos_root)

        tree.itemExpanded.connect(self._on_territory_item_expanded)
        tree.itemChanged.connect(self._on_territory_item_changed)

        # Deixa biomas e estados prontos para exploracao assim que a janela abre.
        self._territory_tree_updating = True
        try:
            self._ensure_item_loaded(self._biomes_root)
            self._ensure_item_loaded(self._states_root)
            self._update_brazil_visual_state()
        finally:
            self._territory_tree_updating = False

    def _has_non_brazil_selection(self):
        tree = self.Territorial_TreeWidget
        for index in range(tree.topLevelItemCount()):
            item = tree.topLevelItem(index)
            if item is self._Brasil_root:
                continue
            if item.checkState(0) != Qt.CheckState.Unchecked:
                return True
        return False

    def _update_brazil_visual_state(self):
        if self._has_user_geometry_selected():
            desired_state = Qt.CheckState.Unchecked
        else:
            desired_state = (
                Qt.CheckState.PartiallyChecked
                if self._has_non_brazil_selection()
                else Qt.CheckState.Checked
            )

        if self._Brasil_root.checkState(0) != desired_state:
            self._Brasil_root.setCheckState(0, desired_state)

    def _clear_non_brazil_checks(self):
        tree = self.Territorial_TreeWidget
        for index in range(tree.topLevelItemCount()):
            item = tree.topLevelItem(index)
            if item is self._Brasil_root:
                continue
            item.setCheckState(0, Qt.CheckState.Unchecked)
            self._set_children_check_state(item, Qt.CheckState.Unchecked)

    def _list_territories_sorted(self, category_id, parent_id=None):
        territories = self.api_client.list_territories(
            region_name="brazil",
            category_id=category_id,
            parent_id=parent_id,
        )
        return sorted(territories, key=lambda item: self._normalize_label(item.get("name", "")))

    def _ensure_item_loaded(self, item):
        if not self._has_placeholder(item):
            return

        self._remove_placeholders(item)
        kind = item.data(0, self.NODE_KIND_ROLE)

        try:
            if kind == "biomes_root":
                self._populate_biomes(item)
            elif kind == "states_root":
                self._populate_states(item)
            elif kind == "state":
                self._populate_state_municipalities(item)
            elif kind == "uc_root":
                self._populate_uc_types(item)
            elif kind == "uc_type":
                self._populate_uc_type_territories(item)
            elif kind == "ti_root":
                self._populate_indigenous_territories(item)
            elif kind == "quilombolas_root":
                self._populate_quilombola_territories(item)
            elif kind == "assentamentos_root":
                self._populate_settlement_territories(item)
        except Exception as exc:
            print(f"Falha ao carregar territorios: {exc}")

    def _populate_biomes(self, root_item):
        for territory in self._list_territories_sorted(category_id=4):
            child = self._create_tree_item(
                territory.get("name", "Sem nome"),
                "territory",
                territory_id=territory.get("id"),
            )
            root_item.addChild(child)

    def _populate_states(self, root_item):
        for territory in self._list_territories_sorted(category_id=3):
            child = self._create_tree_item(
                territory.get("name", "Sem nome"),
                "state",
                territory_id=territory.get("id"),
            )
            self._add_placeholder(child)
            root_item.addChild(child)

    def _populate_state_municipalities(self, state_item):
        state_id = state_item.data(0, self.TERRITORY_ID_ROLE)
        if not state_id:
            return
        for territory in self._list_territories_sorted(category_id=95, parent_id=state_id):
            child = self._create_tree_item(
                territory.get("name", "Sem nome"),
                "territory",
                territory_id=territory.get("id"),
            )
            state_item.addChild(child)

    def _populate_uc_types(self, root_item):
        sorted_subcategories = sorted(
            self.UC_SUBCATEGORY_DEFINITIONS,
            key=lambda item: self._normalize_label(item[0]),
        )
        for type_label, match_terms in sorted_subcategories:
            type_item = self._create_tree_item(type_label, "uc_type")
            type_item.setData(0, self.UC_TYPE_ROLE, list(match_terms))
            self._add_placeholder(type_item)
            root_item.addChild(type_item)

    def _list_uc_territories(self):
        if self._uc_territories_cache is not None:
            return self._uc_territories_cache

        dedup = {}
        for category_id in self.UC_CATEGORY_IDS:
            for territory in self._list_territories_sorted(category_id=category_id):
                territory_id = territory.get("id")
                if territory_id:
                    dedup[territory_id] = territory

        self._uc_territories_cache = sorted(
            dedup.values(),
            key=lambda item: self._normalize_label(item.get("name", "")),
        )
        return self._uc_territories_cache

    @staticmethod
    def _tokenize_text(value):
        return [token for token in re.sub(r"[^A-Z0-9]+", " ", value).split() if token]

    def _is_uc_type_match(self, territory_name, match_terms):
        normalized = self._normalize_label(territory_name)
        normalized_tokens = set(self._tokenize_text(normalized))

        for term in match_terms or []:
            normalized_term = self._normalize_label(term)
            if " " in normalized_term:
                if normalized_term in normalized:
                    return True
                continue

            if normalized_term in normalized_tokens:
                return True

        return False

    def _populate_uc_type_territories(self, type_item):
        match_terms = type_item.data(0, self.UC_TYPE_ROLE) or []
        for territory in self._list_uc_territories():
            territory_name = territory.get("name", "Sem nome")
            if not self._is_uc_type_match(territory_name, match_terms):
                continue

            child = self._create_tree_item(
                territory_name,
                "territory",
                territory_id=territory.get("id"),
            )
            type_item.addChild(child)

    def _populate_indigenous_territories(self, root_item):
        for territory in self._list_territories_sorted(category_id=61):
            child = self._create_tree_item(
                territory.get("name", "Sem nome"),
                "territory",
                territory_id=territory.get("id"),
            )
            root_item.addChild(child)

    def _populate_quilombola_territories(self, root_item):
        for territory in self._list_territories_sorted(category_id=62):
            child = self._create_tree_item(
                territory.get("name", "Sem nome"),
                "territory",
                territory_id=territory.get("id"),
            )
            root_item.addChild(child)

    def _populate_settlement_territories(self, root_item):
        for territory in self._list_territories_sorted(category_id=63):
            child = self._create_tree_item(
                territory.get("name", "Sem nome"),
                "territory",
                territory_id=territory.get("id"),
            )
            root_item.addChild(child)

    def _set_children_check_state(self, item, check_state):
        for index in range(item.childCount()):
            child = item.child(index)
            if child.data(0, self.NODE_KIND_ROLE) == "placeholder":
                continue
            child.setCheckState(0, check_state)
            self._set_children_check_state(child, check_state)

    def _update_parent_check_state(self, item):
        if item is None:
            return

        child_states = []
        for index in range(item.childCount()):
            child = item.child(index)
            if child.data(0, self.NODE_KIND_ROLE) == "placeholder":
                continue
            child_states.append(child.checkState(0))

        if not child_states:
            self._update_parent_check_state(item.parent())
            return

        if all(state == Qt.CheckState.Checked for state in child_states):
            item.setCheckState(0, Qt.CheckState.Checked)
        elif all(state == Qt.CheckState.Unchecked for state in child_states):
            item.setCheckState(0, Qt.CheckState.Unchecked)
        else:
            item.setCheckState(0, Qt.CheckState.PartiallyChecked)

        self._update_parent_check_state(item.parent())

    def _on_territory_item_expanded(self, item):
        self._ensure_item_loaded(item)

    def _on_territory_item_changed(self, item, _column):
        if self._territory_tree_updating:
            return

        self._territory_tree_updating = True
        try:
            if item.checkState(0) != Qt.CheckState.Unchecked:
                self._clear_user_geometry_selection()

            current_state = item.checkState(0)
            if item is self._Brasil_root and current_state != Qt.CheckState.Unchecked:
                self._clear_non_brazil_checks()

            if current_state in (Qt.CheckState.Checked, Qt.CheckState.Unchecked):
                self._ensure_item_loaded(item)
                self._set_children_check_state(item, current_state)
            self._update_parent_check_state(item.parent())
            self._update_brazil_visual_state()
        finally:
            self._territory_tree_updating = False

    def _collect_checked_territory_ids(self, item, selected, seen):
        state = item.checkState(0)
        if state == Qt.CheckState.Unchecked:
            return

        if state == Qt.CheckState.Checked and self._has_placeholder(item):
            self._ensure_item_loaded(item)

        territory_id = item.data(0, self.TERRITORY_ID_ROLE)
        if territory_id and state == Qt.CheckState.Checked and territory_id not in seen:
            seen.add(territory_id)
            selected.append(str(territory_id))

            # Se um territorio pai ja esta totalmente marcado, nao envia os
            # filhos para evitar listas gigantes/redundantes no territoryId.
            has_non_placeholder_child = any(
                item.child(index).data(0, self.NODE_KIND_ROLE) != "placeholder"
                for index in range(item.childCount())
            )
            if has_non_placeholder_child:
                return

        for index in range(item.childCount()):
            self._collect_checked_territory_ids(item.child(index), selected, seen)

    def get_selected_territory_ids(self):
        selected = []
        seen = set()
        tree = self.Territorial_TreeWidget
        for index in range(tree.topLevelItemCount()):
            self._collect_checked_territory_ids(tree.topLevelItem(index), selected, seen)

        return selected if selected else None

    def _reset_territory_tree_checks(self):
        self._territory_tree_updating = True
        try:
            tree = self.Territorial_TreeWidget
            for index in range(tree.topLevelItemCount()):
                item = tree.topLevelItem(index)
                item.setCheckState(0, Qt.CheckState.Unchecked)
                self._set_children_check_state(item, Qt.CheckState.Unchecked)
            self._update_brazil_visual_state()
        finally:
            self._territory_tree_updating = False

    def _validate_ok_button(self):
        """Valida se pelo menos uma checkbox está selecionada ou anos no download tab, habilita/desabilita o botão OK"""
        # Verifica se pelo menos uma checkbox está marcada
        any_checked = any(cb.isChecked() for cb in self.checkboxes)
        
        # Verifica se há anos selecionados no combobox do Download tab
        any_years_selected = False
        if hasattr(self, "mComboBox"):
            any_years_selected = len(self.mComboBox.checkedItems()) > 0
        
        # O botão OK é habilitado se há checkboxes OU anos selecionados
        ok_button = self.button_box.button(QtWidgets.QDialogButtonBox.StandardButton.Ok)
        if ok_button:
            ok_button.setEnabled(any_checked or any_years_selected)

    def _setup_download_years_combo(self):
        """Popula o QgsCheckableComboBox com os anos disponíveis"""
        if not hasattr(self, "mComboBox"):
            return
        
        # Limpa os itens padrão
        self.mComboBox.clear()
        
        # Popula com os anos de ano_minimo até ano_maximo
        years = list(range(ano_minimo, ano_maximo + 1))
        for year in years:
            self.mComboBox.addItem(str(year))
        
        # Conecta o sinal de mudança de seleção para validar o botão OK
        self.mComboBox.checkedItemsChanged.connect(self._validate_ok_button)

    def _load_selected_fgb_layers(self, api_client=None):
        """Carrega as camadas FlatGeobuf para todos os anos selecionados no combobox
        
        Aplica intersect com geometria de território se não for Brasil ou geometria customizada.
        """
        if not hasattr(self, "mComboBox"):
            return
        
        # Obtém os anos selecionados
        selected_items = self.mComboBox.checkedItems()
        
        if not selected_items:
            return
        
        # Obtém os territórios selecionados
        territory_ids = self.get_selected_territory_ids() or []
        
        # Obtém a camada de geometria personalizada selecionada (se houver)
        user_geometry_layer = None
        if hasattr(self, "mMapLayerComboBox_usergeometry"):
            user_geometry_layer = self.mMapLayerComboBox_usergeometry.currentLayer()
            if user_geometry_layer is not None and not isinstance(user_geometry_layer, QgsVectorLayer):
                user_geometry_layer = None
        
        # Para cada ano selecionado, carrega a camada FlatGeobuf
        for item_text in selected_items:
            try:
                year = int(item_text)
                self._load_fgb_layer(
                    year, 
                    territory_ids=territory_ids if territory_ids else None,
                    user_geometry_layer=user_geometry_layer,
                    api_client=api_client
                )
            except ValueError:
                QgsMessageLog.logMessage(
                    f"Erro ao converter '{item_text}' para ano",
                    "MapBiomas Fogo",
                    QgisMessageLevel.Warning
                )

    def _load_fgb_layer(self, year, territory_ids=None, user_geometry_layer=None, api_client=None):
        """Carrega a camada FlatGeobuf para um ano específico
        
        Se territory_ids for fornecido (e não for Brasil) ou user_geometry_layer for fornecido,
        realiza intersect com a geometria do território ou geometria customizada.
        
        Args:
            year: Ano das feições de fogo
            territory_ids: Lista de IDs de territórios selecionados
            user_geometry_layer: Camada QgsVectorLayer com geometria customizada
            api_client: Cliente API para obter geometria dos territórios
        """
        try:
            # URL pública do arquivo FlatGeobuf no Google Cloud Storage
            url_fgb = f"https://storage.googleapis.com/mapbiomas-public/initiatives/brasil/collection_10/fire-col5/annual_burned_vectors_v1/FlatGeobuf/mapbiomas_fire_collection5_burned_area_{year}.fgb"
            
            # O prefixo /vsicurl/ instrui o GDAL a ler o arquivo remoto sob demanda (via HTTP Range Requests)
            path_vsicurl = f"/vsicurl/{url_fgb}"
            
            # Instancia a camada usando o provedor OGR (que dá suporte a FlatGeobuf)
            layer = QgsVectorLayer(path_vsicurl, f"MapBiomas Fogo - {year}", "ogr")
            
            if not layer.isValid():
                QgsMessageLog.logMessage(
                    f"Erro ao carregar a camada FlatGeobuf para {year}. Verifique a URL ou conexão de rede.",
                    "MapBiomas Fogo",
                    QgisMessageLevel.Critical
                )
                return
            
            # Aplica o filtro de atributos ANTES de adicionar à tela para evitar requisições desnecessárias
            layer.setSubsetString(f"year = {year}")
            
            # Verifica se é Brasil (sem territórios ou territoryId = "1-1-1") ou se há geometria customizada
            is_brasil_only = (
                not territory_ids or 
                (isinstance(territory_ids, list) and all(tid == "1-1-1" for tid in territory_ids))
            )
            
            # Se é Brasil e não há geometria customizada, carrega normalmente
            if is_brasil_only and user_geometry_layer is None:
                QgsProject.instance().addMapLayer(layer)
                QgsMessageLog.logMessage(
                    f"Camada FlatGeobuf para {year} carregada e filtrada com sucesso!",
                    "MapBiomas Fogo",
                    QgisMessageLevel.Info
                )
                return
            
            # Se há geometria customizada, faz intersect com ela
            if user_geometry_layer is not None:
                self._clip_with_user_geometry(layer, user_geometry_layer, year)
                return
            
            # Se há territórios selecionados (não Brasil), faz intersect com geometria da API
            if territory_ids and api_client is not None:
                self._clip_with_territory_geometry(layer, territory_ids, api_client, year)
                return
            
            # Fallback: carrega normalmente
            QgsProject.instance().addMapLayer(layer)
            QgsMessageLog.logMessage(
                f"Camada FlatGeobuf para {year} carregada com sucesso!",
                "MapBiomas Fogo",
                QgisMessageLevel.Info
            )
            
        except Exception as exc:
            QgsMessageLog.logMessage(
                f"Erro ao carregar camada para {year}: {str(exc)}",
                "MapBiomas Fogo",
                QgisMessageLevel.Critical
            )

    @staticmethod
    def _build_qgs_geometry_from_geojson(geometry_payload):
        """Converte GeoJSON em QgsGeometry com fallback para versoes antigas do QGIS."""
        if not isinstance(geometry_payload, dict):
            return None

        geometry_json = json.dumps(geometry_payload, ensure_ascii=False)

        # QGIS mais novo pode ter QgsGeometry.fromGeoJson.
        from_geojson = getattr(QgsGeometry, "fromGeoJson", None)
        if callable(from_geojson):
            for candidate in (geometry_json.encode("utf-8"), geometry_json):
                try:
                    geometry = from_geojson(candidate)
                    if geometry is not None and not geometry.isEmpty():
                        return geometry
                except (TypeError, ValueError, AttributeError):
                    pass  # Try next encoding format

        # Fallback compativel com QGIS antigos via OGR -> WKT -> QgsGeometry.
        try:
            from osgeo import ogr

            ogr_geometry = ogr.CreateGeometryFromJson(geometry_json)
            if ogr_geometry is None:
                print("[MapBiomas Fogo] Falha ao converter GeoJSON para OGR geometry.")
                return None

            geometry_wkt = ogr_geometry.ExportToWkt()
            if not geometry_wkt:
                print("[MapBiomas Fogo] OGR geometry sem WKT para conversao.")
                return None

            geometry = QgsGeometry.fromWkt(geometry_wkt)
            if geometry is None or geometry.isEmpty():
                print("[MapBiomas Fogo] Falha ao converter WKT em QgsGeometry.")
                return None

            return geometry
        except Exception as exc:
            print(f"[MapBiomas Fogo] Falha no fallback OGR para QgsGeometry: {exc}")
            return None

    def _clip_with_territory_geometry(self, fgb_layer, territory_ids, api_client, year):
        """Realiza intersect entre FGB e geometria de territórios da API
        
        Args:
            fgb_layer: Camada QgsVectorLayer com feições de fogo (FGB)
            territory_ids: Lista de IDs de territórios
            api_client: Cliente MapBiomasApiClient para obter geometria
            year: Ano das feições para nome da camada
        """
        try:
            print(f"[MapBiomas Fogo] Iniciando intersect com territórios: {territory_ids}")
            
            # Obtém a geometria dos territórios da API
            territory_geometry = api_client.get_territory_geometry(
                region_name="brazil",
                territoryIds=territory_ids,
            )
            
            if not territory_geometry:
                QgsMessageLog.logMessage(
                    f"Não foi possível obter geometria dos territórios: {territory_ids}",
                    "MapBiomas Fogo",
                    QgisMessageLevel.Warning
                )
                # Fallback: carrega normalmente
                QgsProject.instance().addMapLayer(fgb_layer)
                return
            
            # Converte geometria GeoJSON da API para QgsGeometry
            territory_qgs_geom = self._build_qgs_geometry_from_geojson(territory_geometry)
            
            if territory_qgs_geom is None or territory_qgs_geom.isEmpty():
                QgsMessageLog.logMessage(
                    f"Geometria do território está vazia: {territory_ids}",
                    "MapBiomas Fogo",
                    QgisMessageLevel.Warning
                )
                # Fallback: carrega normalmente
                QgsProject.instance().addMapLayer(fgb_layer)
                return
            
            # Realiza o intersect
            self._perform_clip(fgb_layer, territory_qgs_geom, year, "Território")
            
        except Exception as exc:
            QgsMessageLog.logMessage(
                f"Erro ao fazer intersect com territórios: {str(exc)}",
                "MapBiomas Fogo",
                QgisMessageLevel.Critical
            )
            # Fallback: carrega normalmente
            QgsProject.instance().addMapLayer(fgb_layer)

    def _clip_with_user_geometry(self, fgb_layer, user_geometry_layer, year):
        """Realiza intersect entre FGB e geometria customizada do usuário
        
        Args:
            fgb_layer: Camada QgsVectorLayer com feições de fogo (FGB)
            user_geometry_layer: Camada QgsVectorLayer com geometria customizada
            year: Ano das feições para nome da camada
        """
        try:
            print(f"[MapBiomas Fogo] Iniciando intersect com geometria customizada")
            
            if user_geometry_layer.featureCount() == 0:
                QgsMessageLog.logMessage(
                    "Geometria customizada não possui feições",
                    "MapBiomas Fogo",
                    QgisMessageLevel.Warning
                )
                # Fallback: carrega normalmente
                QgsProject.instance().addMapLayer(fgb_layer)
                return
            
            # Obtém a geometria (dissolve se houver múltiplas feições)
            selected_only = False
            if hasattr(self, "SelectedGeometry"):
                selected_only = self.SelectedGeometry.isChecked()
            
            user_geom = self._get_dissolved_geometry(user_geometry_layer, selected_only)
            
            if user_geom is None or user_geom.isEmpty():
                QgsMessageLog.logMessage(
                    "Geometria customizada está vazia após processamento",
                    "MapBiomas Fogo",
                    QgisMessageLevel.Warning
                )
                # Fallback: carrega normalmente
                QgsProject.instance().addMapLayer(fgb_layer)
                return
            
            # Realiza o intersect
            self._perform_clip(fgb_layer, user_geom, year, "Geometria Customizada")
            
        except Exception as exc:
            QgsMessageLog.logMessage(
                f"Erro ao fazer intersect com geometria customizada: {str(exc)}",
                "MapBiomas Fogo",
                QgisMessageLevel.Critical
            )
            # Fallback: carrega normalmente
            QgsProject.instance().addMapLayer(fgb_layer)

    def _get_dissolved_geometry(self, vector_layer, selected_only=False):
        """Obtém a geometria dissolvida de uma camada vetorial
        
        Args:
            vector_layer: Camada QgsVectorLayer
            selected_only: Se True, usa apenas features selecionadas
            
        Returns:
            QgsGeometry com geometria dissolvida ou None
        """
        try:
            features = []
            
            if selected_only:
                features = vector_layer.selectedFeatures()
            else:
                features = vector_layer.getFeatures()
            
            if not features:
                return None
            
            # Combina todas as geometrias
            geometries = [f.geometry() for f in features if f.geometry() and not f.geometry().isEmpty()]
            
            if not geometries:
                return None
            
            if len(geometries) == 1:
                return geometries[0]
            
            # Faz a união de todas as geometrias
            combined_geom = geometries[0]
            for geom in geometries[1:]:
                combined_geom = combined_geom.combine(geom)
            
            return combined_geom
            
        except Exception as exc:
            print(f"Erro ao obter geometria dissolvida: {exc}")
            return None

    def _perform_clip(self, fgb_layer, clip_geometry, year, clip_source_name):
        """Realiza clip entre camada FGB e geometria de recorte usando algoritmo nativo do QGIS
        
        Args:
            fgb_layer: Camada QgsVectorLayer com feições de fogo
            clip_geometry: QgsGeometry para clipe
            year: Ano para nome da camada
            clip_source_name: Nome da fonte de clipe (para logging)
        """
        try:
            print(f"[MapBiomas Fogo] Realizando clip com {clip_source_name} ({year})")
            
            # Cria uma camada de memória temporária com a geometria de clipe
            clip_layer = QgsVectorLayer("Polygon", "temp_clip", "memory")
            clip_layer.dataProvider().addAttributes([QgsField("id", QVariant.Int)])
            clip_layer.updateFields()
            
            clip_feature = QgsFeature(clip_layer.fields())
            clip_feature.setGeometry(clip_geometry)
            clip_feature.setAttribute(0, 1)
            clip_layer.dataProvider().addFeatures([clip_feature])
            
            # Executa o algoritmo de clip nativo do QGIS (muito mais otimizado)
            try:
                result = processing.run(
                    "native:clip",
                    {
                        'INPUT': fgb_layer,
                        'OVERLAY': clip_layer,
                        'OUTPUT': 'memory:'
                    },
                    context=QgsProcessingContext(),
                    feedback=QgsProcessingFeedback(),
                    is_child_algorithm=False
                )
                
                clipped_layer = result['OUTPUT']
                
                if clipped_layer is None or not isinstance(clipped_layer, QgsVectorLayer):
                    QgsMessageLog.logMessage(
                        f"Falha ao executar algoritmo de clip com {clip_source_name}",
                        "MapBiomas Fogo",
                        QgisMessageLevel.Warning
                    )
                    QgsProject.instance().addMapLayer(fgb_layer)
                    return
                
                # Renomeia a camada
                clipped_layer.setName(f"MapBiomas Fogo - {year}")
                
                # Valida a camada antes de adicionar
                if not clipped_layer.isValid():
                    QgsMessageLog.logMessage(
                        f"Camada clipeada inválida para {year}",
                        "MapBiomas Fogo",
                        QgisMessageLevel.Warning
                    )
                    QgsProject.instance().addMapLayer(fgb_layer)
                    return
                
                # Adiciona a camada ao projeto
                QgsProject.instance().addMapLayer(clipped_layer)
                
                feature_count = clipped_layer.featureCount()
                QgsMessageLog.logMessage(
                    f"Clip concluído: {feature_count} feições para {year} ({clip_source_name})",
                    "MapBiomas Fogo",
                    QgisMessageLevel.Info
                )
                
            except Exception as proc_exc:
                print(f"[MapBiomas Fogo] Erro ao executar algoritmo de clip: {str(proc_exc)}")
                QgsMessageLog.logMessage(
                    f"Erro ao executar algoritmo de clip: {str(proc_exc)}",
                    "MapBiomas Fogo",
                    QgisMessageLevel.Warning
                )
                # Fallback: carrega a camada sem clipe
                QgsProject.instance().addMapLayer(fgb_layer)
                
        except Exception as exc:
            print(f"[MapBiomas Fogo] Erro ao realizar clip: {str(exc)}")
            QgsMessageLog.logMessage(
                f"Erro ao realizar clip: {str(exc)}",
                "MapBiomas Fogo",
                QgisMessageLevel.Critical
            )

    def showEvent(self, event):
        # Desmarca todas as caixas de seleção toda vez que a janela for exibida
        for cb in self.checkboxes:
            cb.setChecked(False)
        self._clear_user_geometry_selection()
        self._reset_territory_tree_checks()
        # Valida o botão OK novamente
        self._validate_ok_button()
        super().showEvent(event)

    def accept(self):
        if self._has_user_geometry_selected():
            layer = self.mMapLayerComboBox_usergeometry.currentLayer()
            if layer is not None:
                selected_only_checkbox = False
                if hasattr(self, "SelectedGeometry"):
                    selected_only_checkbox = self.SelectedGeometry.isChecked()

                if selected_only_checkbox and layer.selectedFeatureCount() == 0:
                    QtWidgets.QMessageBox.warning(
                        self,
                        "MapBiomas Fogo",
                        "Selecione 1 ou mais feições antes de iniciar enquanto a caixa <b>Apenas feições selecionadas</b> estiver ativa"
                    )
                    return  # Não fecha o diálogo

        # Verifica se há anos selecionados no Download tab (FGBs)
        # O aviso de processamento é exibido apenas para FGBs com recortes
        years_selected = False
        if hasattr(self, "mComboBox"):
            years_selected = len(self.mComboBox.checkedItems()) > 0
        
        if years_selected:
            # Se há anos selecionados (FGB), verifica se há recortes e mostra aviso
            territory_ids = self.get_selected_territory_ids() or []
            has_custom_geometry = self._has_user_geometry_selected()
            
            if (territory_ids or has_custom_geometry):
                # Se há territórios ou geometria customizada para FGB, mostra aviso sobre tempo de processamento
                result = self._show_processing_time_warning(territory_ids, has_custom_geometry)
                if result != QMessageBox.StandardButton.Ok:
                    return  # Não fecha o diálogo se usuário não confirmar

        # Carrega as camadas FlatGeobuf dos anos selecionados no Download tab
        # Passa o api_client para realizar operações de intersect com territórios
        self._load_selected_fgb_layers(api_client=self.api_client)

        super().accept()

    def _show_processing_time_warning(self, territory_ids, has_custom_geometry):
        """Mostra aviso sobre tempo de processamento ao usar recortes com FGBs
        
        Este aviso é exibido apenas quando há FGBs selecionados (anos no Download tab)
        com recortes territoriais ou geometria customizada.
        
        Args:
            territory_ids: Lista de IDs de territórios selecionados
            has_custom_geometry: Se há geometria customizada selecionada
        
        Returns:
            Resultado do clique do usuário (Ok ou Cancel)
        """
        # Determina qual tipo de recorte está sendo usado
        recorte_type = "Geometria Personalizada"
        if territory_ids and not has_custom_geometry:
            recorte_type = "Recorte Territorial"
        elif territory_ids and has_custom_geometry:
            recorte_type = "Recorte Territorial e Geometria Personalizada"
        
        message = (
            "<b>⚠️ Aviso: Processamento de Recorte</b><br><br>"
            f"Você selecionou um <b>{recorte_type}</b>.<br><br>"
            "O tempo de processamento pode variar significativamente "
            "dependendo de:<br><br>"
            "• <b>Complexidade da geometria</b> do recorte<br>"
            "• <b>Extensão territorial</b> selecionada<br>"
            "• <b>Quantidade de feições</b> de fogo nos anos selecionados<br><br>"
            "<b>Neste período, o QGIS poderá apresentar travamentos</b> enquanto "
            "realiza os recortes solicitados. Isto é normal e o processamento "
            "continuará em background.<br><br>"
            "Deseja continuar?"
        )
        
        result = QtWidgets.QMessageBox.information(
            self,
            "MapBiomas Fogo - Aviso de Processamento",
            message,
            QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel,
            QMessageBox.StandardButton.Ok
        )
        
        return result