# -*- coding: utf-8 -*-
"""
/***************************************************************************
 VectorToMap - QGIS Plugin
 Automates the generation of print layouts for vector features.
 Author: Matheus Durso
 ***************************************************************************/
"""
import math
import re
import unicodedata
import os
import sip
from PyQt5.QtWidgets import QGridLayout
from PyQt5.QtWidgets import QDesktopWidget
from qgis.PyQt.QtCore import QSettings, QTranslator, QCoreApplication, Qt, QTimer
from qgis.PyQt.QtGui import QIcon, QPixmap, QImage, QFont
from qgis.utils import iface
from qgis.PyQt.QtWidgets import QAction, QCheckBox, QTableWidgetItem, QFrame, QPushButton, QDialogButtonBox, QButtonGroup
from qgis.core import (
    QgsProject, QgsPrintLayout, QgsLayoutSize, QgsUnitTypes, QgsLayoutItemMap, 
    QgsLayoutPoint, QgsMapLayerProxyModel, QgsFeatureRequest, QgsSettings, 
    QgsLayoutExporter, QgsRectangle, QgsCoordinateTransform, QgsLayoutItemPage, 
    QgsLayoutItemLabel, QgsLayoutItem, QgsWkbTypes
)
# Resources generated by Plugin Builder
from .resources import *
from .vector_to_map_dialog import VectorToMapDialog


class VectorToMap:

    # --- 1. CICLO DE VIDA E CONFIGURAÇÕES INICIAIS ---

    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
        self.colunas_atuais = []
        
        # No seu __init__
        locale = QgsSettings().value('locale/userLocale')[0:2] 

        if locale != 'pt':
            # O path vai buscar vector_to_map_en.qm ou vector_to_map_es.qm
            path = os.path.join(self.plugin_dir, 'i18n', f'vector_to_map_{locale}.qm')
            
            # Se o espanhol não existir por algum motivo, ele volta pro inglês
            if not os.path.exists(path):
                path = os.path.join(self.plugin_dir, 'i18n', 'vector_to_map_en.qm')

            if os.path.exists(path):
                self.translator = QTranslator()
                self.translator.load(path)
                QCoreApplication.installTranslator(self.translator)

    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
        self.toolbar = self.iface.addToolBar(u'VectorToMap')
        self.toolbar.setObjectName(u'VectorToMap')

        #Botão Render Preview
        self.btn_render = QPushButton("Render Preview")

        # 2. CRIAÇÃO DA AÇÃO PRINCIPAL
        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."""
        for action in self.actions:
            self.iface.removePluginMenu(self.menu, action)
            self.iface.removeToolBarIcon(action)
        
        if hasattr(self, 'toolbar'):
            del self.toolbar


    # --- 2. INTERFACE E DIÁLOGO ---

    def run(self):
        """Initialize the UI and set up window flags and button logic."""
        if self.first_start:
            self.first_start = False
            self.dlg = VectorToMapDialog()

            # 1. Tenta desconectar qualquer link automático que o Qt Designer criou
            try:
                self.dlg.button_box.accepted.disconnect()
            except:
                pass # Caso não haja conexão prévia

            # --- ESTILO CONSOLIDADO V0.3.0 (AZUL PROFISSIONAL & CLEAN) ---
            estilo_consolidado = """
                /* 1. ESTILO GERAL DAS BARRAS DE PROGRESSO */
                QProgressBar {
                    border: 1px solid #dcdcdc;
                    border-radius: 4px;
                    text-align: center;
                    background-color: #f0f0f0;
                    height: 14px;
                    font-size: 10px;
                    color: #333;
                }
                QProgressBar::chunk {
                    background-color: #3498db; /* Azul Claro */
                    border-radius: 3px;
                }

                /* 2. BARRA DA PREVIEW (ULTRA-FINA ESTILO YOUTUBE) */
                QProgressBar#previewProgressBar {
                    border: 1px solid #dcdcdc;
                    border-radius: 4px;
                    background-color: #f0f0f0;
                    height: 3px;
                    color: #333; 
                }
                QProgressBar#previewProgressBar::chunk {
                    background-color: #3498db;
                    border-radius: 3px;
                }

                /* 3. BARRAS DE ROLAGEM MODERNAS (SCROLLBARS) */
                QScrollBar:vertical, QScrollBar:horizontal {
                    border: none;
                    background: #f8f8f8;
                    width: 8px;
                    height: 8px;
                    margin: 0px;
                }
                QScrollBar::handle {
                    background: #cccccc;
                    border-radius: 4px;
                    min-height: 20px;
                    min-width: 20px;
                }
                QScrollBar::handle:hover {
                    background: #707070; /* cinza */
                }
                QScrollBar::add-line, QScrollBar::sub-line {
                    border: none;
                    background: none;
                    height: 0px;
                    width: 0px;
                }
            """
            self.dlg.setStyleSheet(estilo_consolidado)
            self.dlg.previewProgressBar.hide()
            self.dlg.progressBar.hide()

            # --- ISOLAMENTO DOS RADIO BUTTONS (v0.2.8) ---
            self.grupo_orientacao = QButtonGroup(self.dlg)
            self.grupo_orientacao.addButton(self.dlg.rb_retrato)
            self.grupo_orientacao.addButton(self.dlg.rb_paisagem)

            self.grupo_escala = QButtonGroup(self.dlg)
            self.grupo_escala.addButton(self.dlg.rb_escala_auto)
            self.grupo_escala.addButton(self.dlg.rb_escala_fixa)

            # --- SETUP TAMANHOS DE PÁGINA (v0.2.8) ---
            self.dlg.combo_tamanho_pagina.clear()
            tamanhos_disponiveis = [
                ("A4 (210 x 297 mm)", (210.0, 297.0)),
                ("A3 (297 x 420 mm)", (297.0, 420.0)),
                ("A2 (420 x 594 mm)", (420.0, 594.0)),
                ("A1 (594 x 841 mm)", (594.0, 841.0)),
                ("A0 (841 x 1189 mm)", (841.0, 1189.0))
            ]
            for texto, dimensoes in tamanhos_disponiveis:
                self.dlg.combo_tamanho_pagina.addItem(texto, dimensoes)

            # --- SETUP ESCALA (v0.2.8) ---
            self.dlg.combo_escala_fixa.clear()
            escalas = [
                ("1:1.000", 1000), ("1:5.000", 5000), ("1:10.000", 10000),
                ("1:50.000", 50000), ("1:100.000", 100000), 
                ("1:250.000", 250000), ("1:500.000", 500000)
            ]
            for texto, valor in escalas:
                self.dlg.combo_escala_fixa.addItem(texto, valor)
            self.dlg.combo_escala_fixa.setCurrentIndex(2)

            self.dlg.mMapLayerComboBox.layerChanged.connect(self.configurar_escala_ao_mudar_camada)
            self.configurar_escala_ao_mudar_camada() 

            self.dlg.rb_escala_auto.toggled.connect(self.validar_geometria_escala)
            self.dlg.rb_escala_fixa.toggled.connect(self.validar_geometria_escala)
            self.dlg.rb_escala_auto.toggled.connect(self.disparar_preview_se_autorizado)
            self.dlg.rb_escala_fixa.toggled.connect(self.disparar_preview_se_autorizado)
            self.dlg.combo_escala_fixa.currentIndexChanged.connect(self.disparar_preview_se_autorizado)
            
            # --- SETUP 'Render Preview' BUTTON ---
            self.dlg.button_box.addButton(self.btn_render, QDialogButtonBox.ActionRole)
            self.btn_render.clicked.connect(self.atualizar_preview)

            self.dlg.combo_presets.setItemData(0, "quadrado")
            self.dlg.combo_presets.setItemData(1, "75_altura")

            self.dlg.setWindowIcon(QIcon(':/plugins/vector_to_map/icon.png'))
            self.dlg.setWindowTitle("VectorToMap")
            self.dlg.lbl_aviso_escala.hide()

            # --- CONFIGURAÇÃO DE JANELA PROFISSIONAL ---
            self.dlg.setWindowFlags(
                Qt.Window | Qt.WindowMinimizeButtonHint | 
                Qt.WindowMaximizeButtonHint | Qt.WindowCloseButtonHint
            )
            
            self.is_rendering = False
            self.timer_preview = QTimer()
            self.timer_preview.setSingleShot(True)
            self.timer_preview.timeout.connect(self.atualizar_preview)
            
            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
            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)

            self.dlg.mMapLayerComboBox.layerChanged.connect(self.disparar_preview_se_autorizado)
            self.dlg.chk_selecionar_todos.stateChanged.connect(self.marcar_desmarcar_todos)
            self.dlg.chk_numeracao.toggled.connect(self.disparar_preview_se_autorizado)

            # --- 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.button(QDialogButtonBox.Ok).clicked.connect(self.processar_clique_ok)
            self.abort_processing = False
            self.dlg.button_box.rejected.connect(self.cancelar_e_fechar)
            self.configurar_escala_ao_mudar_camada()

        # --- CENTRALIZAR NA TELA ---        
        geometria_janela = self.dlg.frameGeometry()
        centro_tela = QDesktopWidget().availableGeometry().center()
        geometria_janela.moveCenter(centro_tela)
        self.dlg.move(geometria_janela.topLeft())

        self.setup_ui_strings()  
        self.dlg.show()

    def setup_ui_strings(self):
        """Centraliza todos os Tooltips e textos da interface para tradução."""
        self.dlg.chk_modo_formulario.setToolTip(self.tr("Exibe todos os atributos em um bloco único de texto (HTML)."))
        self.dlg.chk_modo_individual.setToolTip(self.tr("Cria uma linha horizontal para cada feição (evita sobreposições)."))
        self.dlg.chk_preview_auto.setToolTip(self.tr("Atualização automática da Preview. Pode ser lento. Alternativamente use o botão Render Preview."))
        self.dlg.chk_selecionar_todos.setToolTip(self.tr("Marca ou desmarca todos os campos da lista de uma vez."))
        self.dlg.combo_atlas.setToolTip(self.tr("Campo da tabela usado para agrupar as feições em cada página."))
        self.dlg.combo_presets.setToolTip(self.tr("Define o tamanho do mapa na folha (ex: 75% da página ou Quadrado)."))
        self.btn_render.setToolTip(self.tr("Gera uma prévia do layout com as configurações atuais."))

    def cancelar_e_fechar(self):
        """Interrompe o processamento e fecha o diálogo."""
        self.abort_processing = True
        self.dlg.reject()


    # --- 3. GESTÃO DE ATRIBUTOS E WIDGETS ---

    def atualizar_lista_atributos(self):
        """Limpa a interface e reconstrói a lista com isolamento total."""
        self.dlg.chk_modo_formulario.blockSignals(True)
        self.dlg.chk_modo_individual.blockSignals(True)
        self.dlg.chk_modo_formulario.setChecked(False)
        self.dlg.chk_modo_individual.setChecked(False)
        self.dlg.chk_modo_formulario.setEnabled(False) 
        self.dlg.chk_modo_individual.setEnabled(False) 

        camada = self.dlg.mMapLayerComboBox.currentLayer()
        self.dlg.tableWidget.setRowCount(0)
        self.dlg.tableWidget.setColumnCount(0)
        self.colunas_atuais = []

        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.dlg.chk_modo_formulario.blockSignals(False)
            self.dlg.chk_modo_individual.blockSignals(False)
            return

        self.atualizar_lista_colunas(camada)
        self.dlg.chk_modo_formulario.blockSignals(False)
        self.dlg.chk_modo_individual.blockSignals(False)
        self.atualizar_estado_modos_exibicao()

    def atualizar_lista_colunas(self, camada):
        """Reconstrói a grade de 3 colunas com distribuição balanceada descendente."""
        container = self.dlg.scrollAreaWidgetContents
        layout_atual = container.layout()

        if layout_atual:
            while layout_atual.count():
                item = layout_atual.takeAt(0)
                if item.widget(): item.widget().deleteLater()
            if not isinstance(layout_atual, QGridLayout):
                sip.delete(layout_atual)
                layout_atual = QGridLayout(container)
                container.setLayout(layout_atual)
        else:
            layout_atual = QGridLayout(container)
            container.setLayout(layout_atual)

        layout_atual.setAlignment(Qt.AlignTop)
        self.colunas_atuais = [] 
        
        campos = list(camada.fields())
        N = len(campos)
        if N == 0: return

        if N <= 2:
            distribuicao = [N, 0, 0]
        elif N <= 4:
            c1 = math.ceil(N / 2)
            distribuicao = [c1, N - c1, 0]
        else:
            base = N // 3
            resto = N % 3
            distribuicao = [base + (1 if resto > 0 else 0), base + (1 if resto > 1 else 0), base]

        idx_campo = 0
        for col_index, limite_linhas in enumerate(distribuicao):
            for row_index in range(limite_linhas):
                if idx_campo < N:
                    campo = campos[idx_campo]
                    nome = campo.name()
                    cb = QCheckBox(nome)
                    self.colunas_atuais.append(cb)
                    cb.stateChanged.connect(self.atualizar_tabela_por_selecao)                    
                    cb.stateChanged.connect(self.verificar_estado_selecionar_todos)
                    cb.stateChanged.connect(self.atualizar_estado_modos_exibicao)
                    cb.stateChanged.connect(self.disparar_preview_se_autorizado)
                    layout_atual.addWidget(cb, row_index, col_index)
                    self.dlg.combo_atlas.addItem(nome, nome)
                    idx_campo += 1

    def marcar_desmarcar_todos(self, state):
        """Bulk check/uncheck attributes using the control list."""
        check = (state == Qt.Checked)
        for cb in self.colunas_atuais:
            cb.setChecked(check)

    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)

    def atualizar_estado_modos_exibicao(self):
        """Manage accessibility for display modes using the control list."""
        tem_selecao = any(cb.isChecked() for cb in self.colunas_atuais)
        self.dlg.chk_modo_formulario.setEnabled(tem_selecao)
        self.dlg.chk_modo_individual.setEnabled(tem_selecao)
        if tem_selecao:
            if not self.dlg.chk_modo_formulario.isChecked() and not self.dlg.chk_modo_individual.isChecked():
                self.dlg.chk_modo_formulario.setChecked(True)
        else:
            self.dlg.chk_modo_formulario.setChecked(False)
            self.dlg.chk_modo_individual.setChecked(False)

    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()


    # --- 4. LÓGICA GEOGRÁFICA E ESCALA ---

    def configurar_escala_ao_mudar_camada(self):
        """Define o botão padrão inicial ao trocar de camada."""
        camada = self.dlg.mMapLayerComboBox.currentLayer()
        if not camada: return
        tipo_geom = camada.geometryType()
        if tipo_geom == 0:
            self.dlg.rb_escala_fixa.setChecked(True)
        else:
            self.dlg.rb_escala_auto.setChecked(True)
        self.atualizar_lista_atributos()
        self.validar_geometria_escala()

    def validar_geometria_escala(self):
        """Gerencia habilitar/desabilitar, avisos e trava em 1:10.000."""
        camada = self.dlg.mMapLayerComboBox.currentLayer()
        if not camada: return
        tipo_geom = camada.geometryType() 
        wkb_type = camada.wkbType()
        is_multi = QgsWkbTypes.isMultiType(wkb_type)

        self.dlg.combo_escala_fixa.blockSignals(True)
        self.dlg.rb_escala_auto.setEnabled(not (tipo_geom == 0 and not is_multi))
        self.dlg.combo_escala_fixa.setEnabled(self.dlg.rb_escala_fixa.isChecked())

        if self.dlg.rb_escala_fixa.isChecked():
            self.dlg.combo_escala_fixa.setCurrentIndex(2)

        perigo = (tipo_geom == 0 and is_multi and self.dlg.rb_escala_auto.isChecked())
        if perigo:
            self.dlg.lbl_aviso_escala.show()
            self.dlg.lbl_aviso_escala.setText(self.tr("⚠️ Atenção: Feições de ponto único podem quebrar a escala automática."))
        else:
            self.dlg.lbl_aviso_escala.hide()

        self.dlg.combo_escala_fixa.blockSignals(False)
        self.disparar_preview_se_autorizado()


    # --- 5. RENDERIZAÇÃO E LAYOUT ---

    def atualizar_preview(self):
        """Renders a sample layout preview with an indeterminate progress bar."""
        if self.is_rendering or not self.dlg.isVisible(): 
            return
            
        self.is_rendering = True
        self.dlg.previewProgressBar.show() 
        self.dlg.previewProgressBar.setRange(0, 0) 
        QCoreApplication.processEvents()
        QCoreApplication.processEvents() 

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

            layout = QgsPrintLayout(QgsProject.instance())
            layout.initializeDefaults()
            
            campo_atlas = self.dlg.combo_atlas.currentData()
            feicoes = []
            sample_f = next(camada.getFeatures(QgsFeatureRequest().setLimit(1)), None)
            
            if sample_f:
                if campo_atlas is None:
                    feicoes = [sample_f]
                else:
                    val_amostra = sample_f.attribute(campo_atlas)
                    feicoes = [f for f in camada.getFeatures() if f.attribute(campo_atlas) == val_amostra]

            self.montar_design_da_pagina(
                layout, camada, feicoes, self.dlg.combo_presets.currentData(),
                "Retrato" if self.dlg.rb_retrato.isChecked() else "Paisagem",
            )

            exporter = QgsLayoutExporter(layout)
            image = exporter.renderPageToImage(0)
            if not image.isNull():
                self.dlg.lbl_preview.setPixmap(QPixmap.fromImage(image).scaled(
                    self.dlg.lbl_preview.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))
        finally:
            self.is_rendering = False
            self.dlg.previewProgressBar.hide()
            self.dlg.previewProgressBar.setRange(0, 100)
            QCoreApplication.processEvents()

    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(150)

    def processar_clique_ok(self):
        """Processes configurations and generates the multi-page layout."""
        camada = self.dlg.mMapLayerComboBox.currentLayer()
        if not camada: return
        
        self.abort_processing = False
        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()
        
        if campo_atlas is None:
            for f in camada.getFeatures(): 
                paginas_dados.append({'feicoes': [f]})
        else:
            grupos = {}
            for f in camada.getFeatures():
                val = f.attribute(campo_atlas)
                if val not in grupos: grupos[val] = []
                grupos[val].append(f)
            for val in grupos:
                paginas_dados.append({'feicoes': grupos[val]})
        
        if paginas_dados: 
            self.criar_layout_multi_paginas(
                self.dlg.combo_presets.currentData(), camada, 
                "Retrato" if self.dlg.rb_retrato.isChecked() else "Paisagem", 
                self.dlg.combo_tamanho_pagina.currentData(), 
                paginas_dados, colunas_selecionadas, None
            )
            if not self.abort_processing:
                self.dlg.accept()

    def criar_layout_multi_paginas(self, preset, camada, orientacao, tamanho_pg, paginas_dados, colunas, modos):
        """Generate the definitive layout with a real-time progress bar."""
        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)

        total_paginas = len(paginas_dados)
        self.dlg.progressBar.show()
        self.dlg.progressBar.setRange(0, total_paginas)
        self.dlg.progressBar.setValue(0)

        for i, dados in enumerate(paginas_dados):
            if self.abort_processing: break
            if i > 0: layout.pageCollection().addPage(QgsLayoutItemPage(layout))
            self.montar_design_da_pagina(layout, camada, dados['feicoes'], preset, orientacao, pagina_index=i)
            self.dlg.progressBar.setValue(i + 1)
            QCoreApplication.processEvents() 

        self.dlg.progressBar.hide()
        if not self.abort_processing:
            layout.refresh()
            manager.addLayout(layout)
            self.iface.openLayoutDesigner(layout)

    def montar_design_da_pagina(self, layout, camada, feicoes_da_pagina, preset, orientacao, pagina_index=0):
        """Factory Core v0.2.8: Usa dimensões dinâmicas do ComboBox."""
        dim = self.dlg.combo_tamanho_pagina.currentData()
        if not dim: dim = (210.0, 297.0)

        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
        
        if preset == "75_altura":
            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))
        
        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)
            
            if ext_proj.width() == 0 or ext_proj.height() == 0: ext_proj.grow(1.0)
            map_item.setExtent(ext_proj)

            if self.dlg.rb_escala_fixa.isChecked():
                escala_val = self.dlg.combo_escala_fixa.currentData()
                map_item.setScale(float(escala_val) if escala_val else 10000.0)
            else:
                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.refresh()
            map_item.attemptResize(QgsLayoutSize(w_map_f, h_map_f, QgsUnitTypes.LayoutMillimeters))
            map_item.attemptMove(QgsLayoutPoint(x_map_f, y_map_f, QgsUnitTypes.LayoutMillimeters))

        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_altura":
                # Layout de 1/3 lateral para o Preset 75 (Independente da orientação)
                largura_util = w_pg - (2 * margin_padrao)
                terco_x = margin_padrao + (largura_util * 0.33) 
                
                x_form, y_form = margin_padrao, y_map_f + h_map_f + 3.0
                w_form = (largura_util * 0.33) - 2.0
                h_form = limite_fundo - (y_map_f + h_map_f + 3.0)
                
                x_ind_start = terco_x + 2.0
                y_ind_min = y_map_f + h_map_f + 2.0
            else:
                # Layout original para Mapas Quadrados
                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

            # --- RENDERIZAÇÃ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)

            # --- RENDERIZAÇÃO DOS RÓTULOS INDIVIDUAIS ---
            if self.dlg.chk_modo_individual.isChecked():
                campo_atlas = self.dlg.combo_atlas.currentData() 

                if campo_atlas is None:
                    altura_linha = 5.5
                    f = feicoes_da_pagina[0] 
                    xi = x_ind_start
                    yi = y_ind_min
                    
                    # --- DEFINIÇÃO RIGOROSA DOS LIMITES ---
                    if preset == "75_altura":
                        limite_colunas = 3 if orientacao == "Retrato" else 5
                    else:
                        # Casos de Mapa Quadrado
                        limite_colunas = 5 if orientacao == "Retrato" else 3
                    
                    for idx, col in enumerate(colunas):
                        if idx > 0 and idx % limite_colunas == 0:
                            yi += altura_linha
                            xi = x_ind_start
                            
                        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
                            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
                else:
                    # Comportamento Agrupado (Uma linha por feição)
                    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
                                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
        
        self.adicionar_numeracao_pagina(layout, w_pg, h_pg, y_zero_folha)

    def adicionar_numeracao_pagina(self, layout, w_pg, h_pg, y_zero_folha):
        """Adiciona o número da página no canto inferior direito."""
        if not self.dlg.chk_numeracao.isChecked(): return
        label_page = QgsLayoutItemLabel(layout)
        label_page.setText("[% @layout_page %]") 
        label_page.adjustSizeToText() 
        label_page.setReferencePoint(QgsLayoutItem.LowerRight)
        x, y = w_pg - 5, y_zero_folha + h_pg - 5
        label_page.attemptMove(QgsLayoutPoint(x, y, QgsUnitTypes.LayoutMillimeters))
        label_page.setZValue(100)
        layout.addLayoutItem(label_page)