# -*- coding: utf-8 -*-
"""
/***************************************************************************
 VectorToMap - QGIS Plugin
 Automates the generation of print layouts for vector features.
 Author: Matheus Durso
 ***************************************************************************/
"""

import re
import unicodedata
import os.path

# Standard library and QGIS/PyQt imports
from qgis.PyQt.QtCore import QSettings, QTranslator, QCoreApplication, Qt, QTimer
from qgis.PyQt.QtGui import QIcon, QPixmap, QImage, QFont
from qgis.PyQt.QtWidgets import QAction, QCheckBox, QTableWidgetItem, QFrame
from qgis.core import (
    QgsProject, QgsPrintLayout, QgsLayoutSize, QgsUnitTypes, QgsLayoutItemMap, 
    QgsLayoutPoint, QgsMapLayerProxyModel, QgsFeatureRequest, QgsSettings, 
    QgsLayoutExporter, QgsRectangle, QgsCoordinateTransform, QgsLayoutItemPage, 
    QgsLayoutItemLabel, QgsLayoutItem
)
from qgis.utils import iface

# Resources generated by Plugin Builder
from .resources import *
from .vector_to_map_dialog import VectorToMapDialog

class VectorToMap:
    def __init__(self, iface):
        """Initialize the plugin and define global state variables."""
        self.iface = iface
        self.plugin_dir = os.path.dirname(__file__)
        self.actions = []
        self.menu = self.tr(u'&VectorToMap')
        self.first_start = True
        self.is_rendering = False # Flag to prevent infinite rendering loops

    def tr(self, message):
        """Translate strings using the QGIS internationalization system."""
        return QCoreApplication.translate('VectorToMap', message)

    def add_action(self, icon_path, text, callback, enabled_flag=True, add_to_menu=True, add_to_toolbar=True, status_tip=None, whats_this=None, parent=None):
        """Helper function to add buttons and menus to the QGIS interface."""
        icon = QIcon(icon_path)
        action = QAction(icon, text, parent)
        action.triggered.connect(callback)
        action.setEnabled(enabled_flag)
        if status_tip: action.setStatusTip(status_tip)
        if whats_this: action.setWhatsThis(whats_this)
        if add_to_toolbar: self.iface.addToolBarIcon(action)
        if add_to_menu: self.iface.addPluginToMenu(self.menu, action)
        self.actions.append(action)
        return action

    def initGui(self):
        """Cria as entradas de menu e a barra de ferramentas personalizada."""
        icon_path = ':/plugins/vector_to_map/icon.png'
        
        # 1. CRIAÇÃO DA BARRA DE FERRAMENTAS EXCLUSIVA
        # Isso permite que o ícone fique em destaque na parte superior do QGIS
        self.toolbar = self.iface.addToolBar(u'VectorToMap')
        self.toolbar.setObjectName(u'VectorToMap')

        # 2. CRIAÇÃO DA AÇÃO PRINCIPAL
        # O parâmetro add_to_toolbar=True usa o helper para colocar o ícone na barra
        self.action = self.add_action(
            icon_path,
            text=self.tr(u'VectorToMap'),
            callback=self.run,
            parent=self.iface.mainWindow(),
            add_to_toolbar=True,
            add_to_menu=True
        )

    def unload(self):
        """Remove o menu e a barra de ferramentas da interface do QGIS ao desativar o plugin."""
        # Remove cada ação registrada nos menus e ícones padrão
        for action in self.actions:
            self.iface.removePluginMenu(self.menu, action)
            self.iface.removeToolBarIcon(action)
        
        # 4. REMOÇÃO DA BARRA DE FERRAMENTAS FÍSICA
        # Garante que a barra 'VectorToMap' desapareça completamente da interface
        if hasattr(self, 'toolbar'):
            # Deletar o objeto remove a barra visualmente do QGIS
            del self.toolbar

    def run(self):
        """Initialize the UI and set up window flags and button logic (v0.2.4)."""
        from qgis.PyQt.QtWidgets import QPushButton, QDialogButtonBox
        from qgis.PyQt.QtCore import Qt # Import necessário para as Window Flags

        if self.first_start:
            self.first_start = False
            self.dlg = VectorToMapDialog()
            
            # --- CONFIGURAÇÃO DE BRANDING (CABEÇALHO) ---
            # Define o ícone que aparece ao lado do nome na barra de título
            self.dlg.setWindowIcon(QIcon(':/plugins/vector_to_map/icon.png'))
            # Define o nome limpo conforme sua solicitação
            self.dlg.setWindowTitle("VectorToMap")

            # --- CONFIGURAÇÃO DE JANELA PROFISSIONAL ---
            # Adiciona botões de minimizar e maximizar que não vêm por padrão em QDialog
            self.dlg.setWindowFlags(self.dlg.windowFlags() | 
                                   Qt.WindowMinimizeButtonHint | 
                                   Qt.WindowMaximizeButtonHint)
            
            # Setup rendering engine timer
            self.is_rendering = False
            self.timer_preview = QTimer()
            self.timer_preview.setSingleShot(True)
            self.timer_preview.timeout.connect(self.atualizar_preview)

            # --- SETUP 'Render Preview' BUTTON ---
            self.btn_render = QPushButton("Render Preview")
            self.dlg.button_box.addButton(self.btn_render, QDialogButtonBox.ActionRole)
            self.btn_render.clicked.connect(self.atualizar_preview)

            # INITIAL STATE: Auto-preview setup
            if hasattr(self.dlg, 'chk_preview_auto'):
                self.dlg.chk_preview_auto.setChecked(False)
                self.dlg.chk_preview_auto.stateChanged.connect(
                    lambda state: self.timer_preview.start(100) if state == Qt.Checked else None
                )

            # Restore window geometry and Splitter positions
            settings = QgsSettings()
            geometria = settings.value("/VectorToMap/geometry")
            if geometria: 
                self.dlg.restoreGeometry(geometria)
            else: 
                self.dlg.resize(1100, 750) 
            
            if hasattr(self.dlg, 'splitter'): 
                self.dlg.splitter.setSizes([550, 550]) 
            
            self.dlg.lbl_preview.setFrameShape(QFrame.StyledPanel)
            self.dlg.mMapLayerComboBox.setFilters(QgsMapLayerProxyModel.VectorLayer)

            # Layer and selection signals
            self.dlg.mMapLayerComboBox.layerChanged.connect(self.atualizar_lista_atributos)
            self.dlg.mMapLayerComboBox.layerChanged.connect(self.disparar_preview_se_autorizado)
            self.dlg.chk_selecionar_todos.stateChanged.connect(self.marcar_desmarcar_todos)

            # --- MONITORING WIDGETS ---
            widgets = [
                self.dlg.combo_tamanho_pagina, 
                self.dlg.combo_presets, 
                self.dlg.rb_retrato, 
                self.dlg.rb_paisagem, 
                self.dlg.chk_modo_formulario, 
                self.dlg.chk_modo_individual,
                self.dlg.combo_atlas
            ]
            
            for w in widgets:
                if hasattr(w, 'currentIndexChanged'): 
                    w.currentIndexChanged.connect(self.disparar_preview_se_autorizado)
                elif hasattr(w, 'stateChanged'): 
                    w.stateChanged.connect(self.disparar_preview_se_autorizado)
                elif hasattr(w, 'toggled'): 
                    w.toggled.connect(self.disparar_preview_se_autorizado)

            self.dlg.resizeEvent = lambda event: self.disparar_preview_se_autorizado()
            self.dlg.button_box.accepted.connect(self.processar_clique_ok)
            self.dlg.button_box.rejected.connect(self.dlg.close)
            self.atualizar_lista_atributos()

        self.dlg.show()

    def disparar_preview_se_autorizado(self):
        """Start the preview timer only if auto-update is checked."""
        if hasattr(self.dlg, 'chk_preview_auto') and self.dlg.chk_preview_auto.isChecked():
            self.timer_preview.start(600)

    def marcar_desmarcar_todos(self, state):
        """Bulk check/uncheck attributes and consult auto-preview status."""
        check = (state == Qt.Checked)
        container = self.dlg.scrollAreaWidgetContents
        for cb in container.findChildren(QCheckBox):
            cb.setChecked(check)
        
        self.disparar_preview_se_autorizado()

    def montar_design_da_pagina(self, layout, camada, feicoes_da_pagina, preset, orientacao, tamanho_pg, pagina_index=0):
        """
        Factory Core v0.2.6: Restauração do Formulário + Uma feição por linha (Individual).
        """
        from qgis.core import (QgsLayoutItemMap, QgsLayoutItemLabel, QgsLayoutSize, 
                               QgsUnitTypes, QgsLayoutPoint, QgsCoordinateTransform, 
                               QgsRectangle, QgsProject, QgsLayoutItem)
        
        # --- 1. SETUP DA PÁGINA ---
        tamanhos = {'A4': (210, 297), 'A3': (297, 420), 'A2': (420, 594), 'A1': (594, 841), 'A0': (841, 1189)}
        dim = tamanhos.get(tamanho_pg, (210, 297))
        w_pg, h_pg = (dim[1], dim[0]) if orientacao == "Paisagem" else (dim[0], dim[1])
        
        pagina = layout.pageCollection().pages()[pagina_index]
        pagina.setPageSize(QgsLayoutSize(w_pg, h_pg, QgsUnitTypes.LayoutMillimeters))
        
        y_zero_folha = pagina_index * (h_pg + 10) 
        margin_padrao = 10.0

        # --- 2. GEOMETRIA MOLDURA (IRON-LOCK) ---
        if preset == "75% da Altura (Base fixada)":
            header_h = 35.0
            h_util = h_pg - (2 * margin_padrao) - header_h
            w_map_f, h_map_f = w_pg - (2 * margin_padrao), h_util * 0.75
            x_map_f, y_map_f = margin_padrao, y_zero_folha + margin_padrao + header_h
        else:
            if orientacao == "Retrato":
                w_map_f, h_map_f = w_pg - (2 * margin_padrao), w_pg - (2 * margin_padrao)
                x_map_f, y_map_f = margin_padrao, y_zero_folha + 40.0
            else:
                h_map_f = h_pg - (2 * margin_padrao)
                w_map_f = h_map_f
                x_map_f, y_map_f = w_pg - margin_padrao - w_map_f, y_zero_folha + margin_padrao

        map_item = QgsLayoutItemMap(layout)
        map_item.setFrameEnabled(True)
        layout.addLayoutItem(map_item)
        map_item.attemptResize(QgsLayoutSize(w_map_f, h_map_f, QgsUnitTypes.LayoutMillimeters))
        map_item.attemptMove(QgsLayoutPoint(x_map_f, y_map_f, QgsUnitTypes.LayoutMillimeters))
        
        # --- 3. ESCALA (v0.2.4) ---
        if feicoes_da_pagina:
            ext = QgsRectangle()
            ext.setMinimal()
            for f in feicoes_da_pagina: ext.combineExtentWith(f.geometry().boundingBox())
            project_crs = QgsProject.instance().crs()
            trans = QgsCoordinateTransform(camada.crs(), project_crs, QgsProject.instance())
            ext_proj = trans.transformBoundingBox(ext)
            map_item.setExtent(ext_proj)
            unit_to_mm = QgsUnitTypes.fromUnitToUnitFactor(project_crs.mapUnits(), QgsUnitTypes.DistanceMillimeters)
            scale_w = (ext_proj.width() * unit_to_mm) / w_map_f
            scale_h = (ext_proj.height() * unit_to_mm) / h_map_f
            map_item.setScale(max(scale_w, scale_h) * 1.25)
            map_item.attemptResize(QgsLayoutSize(w_map_f, h_map_f, QgsUnitTypes.LayoutMillimeters))
            map_item.attemptMove(QgsLayoutPoint(x_map_f, y_map_f, QgsUnitTypes.LayoutMillimeters))

        # --- 4. DISTRIBUIÇÃO DOS DADOS ---
        colunas = [cb.text() for cb in self.dlg.scrollAreaWidgetContents.findChildren(QCheckBox) if cb.isChecked()]
        limite_fundo = y_zero_folha + h_pg - margin_padrao

        if colunas and feicoes_da_pagina:
            # Áreas de Posicionamento
            if preset == "75% da Altura (Base fixada)" and orientacao == "Paisagem":
                mid_x = w_pg / 2
                x_form, y_form = margin_padrao, y_map_f + h_map_f + 3.0
                w_form, h_form = mid_x - margin_padrao - 2.0, limite_fundo - (y_map_f + h_map_f + 3.0)
                x_ind_start, y_ind_min = mid_x + 2.0, y_map_f + h_map_f + 2.0
                w_ind_max = w_pg - margin_padrao - x_ind_start
            else:
                x_form = margin_padrao
                y_form = y_map_f + h_map_f + 5.0 if orientacao == "Retrato" else y_map_f + (h_map_f / 2)
                w_form = w_pg - (2 * margin_padrao) if orientacao == "Retrato" else x_map_f - (2 * margin_padrao)
                h_form = (limite_fundo - y_form) * 0.5 if self.dlg.chk_modo_individual.isChecked() else (limite_fundo - y_form)
                x_ind_start, y_ind_min = x_form, y_form + h_form + 2.0 if self.dlg.chk_modo_individual.isChecked() else y_form
                w_ind_max = w_form

            # RESTAURAÇÃO DO FORMULÁRIO
            if self.dlg.chk_modo_formulario.isChecked():
                txt_html = ""
                for idx, f in enumerate(feicoes_da_pagina):
                    if len(feicoes_da_pagina) > 1: txt_html += f"<b>ITEM {idx+1}</b><br>"
                    for col in colunas:
                        try: txt_html += f"<b>{col}:</b> {str(f.attribute(col)).strip()}<br>"
                        except: continue
                lbl_f = QgsLayoutItemLabel(layout)
                lbl_f.setMode(QgsLayoutItemLabel.ModeHtml)
                lbl_f.setText(txt_html)
                lbl_f.attemptMove(QgsLayoutPoint(x_form, y_form, QgsUnitTypes.LayoutMillimeters))
                lbl_f.attemptResize(QgsLayoutSize(w_form, h_form, QgsUnitTypes.LayoutMillimeters))
                layout.addLayoutItem(lbl_f)

            # MODO INDIVIDUAL: UMA LINHA POR FEIÇÃO
            if self.dlg.chk_modo_individual.isChecked():
                altura_linha = 5.5
                altura_total = len(feicoes_da_pagina) * altura_linha
                yi = (limite_fundo - altura_total) if (y_ind_min + altura_total) > limite_fundo else y_ind_min

                for f in feicoes_da_pagina:
                    xi = x_ind_start
                    for col in colunas:
                        try:
                            lbl_i = QgsLayoutItemLabel(layout)
                            lbl_i.setText(f"{col}: {str(f.attribute(col)).strip()}")
                            lbl_i.setFrameEnabled(True)
                            lbl_i.adjustSizeToText()
                            ancho_folga = lbl_i.rect().width() + 2.0 # Correção de 2mm mantida
                            lbl_i.attemptResize(QgsLayoutSize(ancho_folga, lbl_i.rect().height(), QgsUnitTypes.LayoutMillimeters))
                            lbl_i.attemptMove(QgsLayoutPoint(xi, yi, QgsUnitTypes.LayoutMillimeters))
                            layout.addLayoutItem(lbl_i)
                            xi += ancho_folga + 2.0
                        except: continue
                    yi += altura_linha

    def atualizar_preview(self):
        """Renders a sample layout preview based on the current grouping selection."""
        if self.is_rendering or not self.dlg.isVisible(): 
            return
            
        self.is_rendering = True
        QCoreApplication.processEvents()

        camada = self.dlg.mMapLayerComboBox.currentLayer()
        if not camada: 
            self.is_rendering = False
            return

        # Prepare a temporary layout for the preview
        layout = QgsPrintLayout(QgsProject.instance())
        layout.initializeDefaults()
        
        # --- PREVIEW GROUPING LOGIC ---
        campo_atlas = self.dlg.combo_atlas.currentData()
        feicoes = []

        # Get a sample feature to start with
        sample_f = next(camada.getFeatures(QgsFeatureRequest().setLimit(1)), None)
        
        if sample_f:
            if campo_atlas is None:
                # No grouping: preview only the first feature
                feicoes = [sample_f]
            else:
                # Grouping active: find all features that share the same attribute value as the sample
                val_amostra = sample_f.attribute(campo_atlas)
                feicoes = [f for f in camada.getFeatures() if f.attribute(campo_atlas) == val_amostra]

        # Call the design factory with the grouped features
        self.montar_design_da_pagina(
            layout, 
            camada, 
            feicoes, 
            self.dlg.combo_presets.currentText(),
            "Retrato" if self.dlg.rb_retrato.isChecked() else "Paisagem",
            self.dlg.combo_tamanho_pagina.currentText()
        )

        # Export the first page of the layout to an image for the UI
        exporter = QgsLayoutExporter(layout)
        image = exporter.renderPageToImage(0)
        
        if not image.isNull():
            # Apply smooth scaling to fit the preview label
            self.dlg.lbl_preview.setPixmap(QPixmap.fromImage(image).scaled(
                self.dlg.lbl_preview.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))
        
        self.is_rendering = False
        QCoreApplication.processEvents()

    def criar_layout_multi_paginas(self, preset, camada, orientacao, tamanho_pg, paginas_dados, colunas, modos):
        """Generate the definitive multi-page layout in the QGIS Layout Manager."""
        project = QgsProject.instance()
        manager = project.layoutManager()
        
        nome_camada = re.sub(r'[^a-zA-Z0-9_]', '_', unicodedata.normalize('NFD', camada.name()).encode('ascii', 'ignore').decode('utf-8'))
        layout_name = f"VectorToMap_{nome_camada}"
        
        contador = 1
        while manager.layoutByName(layout_name):
            layout_name = f"VectorToMap_{nome_camada}_{contador}"; contador += 1

        layout = QgsPrintLayout(project)
        layout.initializeDefaults()
        layout.setName(layout_name)

        for i, dados in enumerate(paginas_dados):
            if i > 0: layout.pageCollection().addPage(QgsLayoutItemPage(layout))
            self.montar_design_da_pagina(layout, camada, dados['feicoes'], preset, orientacao, tamanho_pg, pagina_index=i)

        layout.refresh()
        manager.addLayout(layout)
        self.iface.openLayoutDesigner(layout) # Open the designer window for the user

    def processar_clique_ok(self):
        """Processes configurations and generates the multi-page layout."""
        camada = self.dlg.mMapLayerComboBox.currentLayer()
        if not camada: 
            return
        
        # Get selected columns for the data display
        container = self.dlg.scrollAreaWidgetContents
        colunas_selecionadas = [cb.text() for cb in container.findChildren(QCheckBox) if cb.isChecked()]

        paginas_dados = []
        campo_atlas = self.dlg.combo_atlas.currentData()
        
        # MODE 1: Individual (One page per feature)
        if campo_atlas is None:
            for f in camada.getFeatures(): 
                paginas_dados.append({'feicoes': [f]})
        
        # MODE 2: Grouped (Atlas mode by attribute)
        else:
            # Dictionary to store features grouped by attribute value
            grupos = {}
            for f in camada.getFeatures():
                # Get the raw value of the attribute
                val = f.attribute(campo_atlas)
                # Ensure the key exists in the dictionary
                if val not in grupos:
                    grupos[val] = []
                grupos[val].append(f)
            
            # Convert the dictionary groups into the layout data format
            for val in grupos:
                paginas_dados.append({'feicoes': grupos[val]})
        
        # Execute layout generation if data is present
        if paginas_dados: 
            self.criar_layout_multi_paginas(
                self.dlg.combo_presets.currentText(), 
                camada, 
                "Retrato" if self.dlg.rb_retrato.isChecked() else "Paisagem", 
                self.dlg.combo_tamanho_pagina.currentText(), 
                paginas_dados, 
                colunas_selecionadas, 
                None
            )

    def atualizar_lista_atributos(self):
        """Clear the interface and rebuild the attribute list with preview triggers."""
        camada = self.dlg.mMapLayerComboBox.currentLayer()
        self.dlg.tableWidget.setRowCount(0)
        self.dlg.tableWidget.setColumnCount(0)

        # Clear existing dynamic checkboxes
        layout_dest = self.dlg.verticalLayout 
        while layout_dest.count():
            item = layout_dest.takeAt(0)
            if item.widget(): item.widget().deleteLater()
        
        self.dlg.chk_selecionar_todos.blockSignals(True)
        self.dlg.chk_selecionar_todos.setChecked(False)
        self.dlg.chk_selecionar_todos.blockSignals(False)
        self.dlg.combo_atlas.clear()
        self.dlg.combo_atlas.addItem("--- No Grouping (Each Feature) ---", None)

        if not camada: 
            self.atualizar_estado_modos_exibicao()
            return

        # Rebuild list and connect each checkbox to the preview engine
        for campo in camada.fields():
            nome = campo.name()
            cb = QCheckBox(nome)
            cb.stateChanged.connect(self.atualizar_tabela_por_selecao)
            cb.stateChanged.connect(self.atualizar_estado_modos_exibicao)
            cb.stateChanged.connect(self.verificar_estado_selecionar_todos)
            
            # Individual trigger for auto-preview logic
            cb.stateChanged.connect(self.disparar_preview_se_autorizado)
            
            layout_dest.addWidget(cb)
            self.dlg.combo_atlas.addItem(nome, nome)

        layout_dest.addStretch(1)
        self.atualizar_estado_modos_exibicao()

    def atualizar_tabela_por_selecao(self):
        """Synchronize the sample data table with the user's attribute selection."""
        camada = self.dlg.mMapLayerComboBox.currentLayer()
        tabela = self.dlg.tableWidget
        if not camada: return
        
        colunas = [cb.text() for cb in self.dlg.scrollAreaWidgetContents.findChildren(QCheckBox) if cb.isChecked()]
        tabela.setColumnCount(len(colunas))
        tabela.setHorizontalHeaderLabels(colunas)
        
        if not colunas:
            tabela.setRowCount(0); return
            
        num_linhas = min(10, camada.featureCount())
        tabela.setRowCount(num_linhas)
        for i, feicao in enumerate(camada.getFeatures(QgsFeatureRequest().setLimit(num_linhas))):
            for j, col in enumerate(colunas):
                tabela.setItem(i, j, QTableWidgetItem(str(feicao[col])))
        tabela.resizeColumnsToContents()

    def atualizar_estado_modos_exibicao(self):
        """Manage accessibility for display modes and set 'Form' as default."""
        container = self.dlg.scrollAreaWidgetContents
        tem_selecao = any(cb.isChecked() for cb in container.findChildren(QCheckBox))
        
        # Enable modes only when at least one column is selected
        self.dlg.chk_modo_formulario.setEnabled(tem_selecao)
        self.dlg.chk_modo_individual.setEnabled(tem_selecao)
        
        if tem_selecao:
            # Activate 'Form' mode by default if no mode is currently active
            if not self.dlg.chk_modo_formulario.isChecked() and not self.dlg.chk_modo_individual.isChecked():
                self.dlg.chk_modo_formulario.setChecked(True)
        else:
            # Clear modes if no attributes are selected
            self.dlg.chk_modo_formulario.setChecked(False)
            self.dlg.chk_modo_individual.setChecked(False)

    def verificar_estado_selecionar_todos(self):
        """Synchronize the master 'Select All' checkbox state."""
        container = self.dlg.scrollAreaWidgetContents
        cbs = container.findChildren(QCheckBox)
        total = len(cbs)
        marcados = sum(1 for cb in cbs if cb.isChecked())
        self.dlg.chk_selecionar_todos.blockSignals(True)
        self.dlg.chk_selecionar_todos.setChecked(total > 0 and total == marcados)
        self.dlg.chk_selecionar_todos.blockSignals(False)