"""
TAC Core Services
Business logic and data services for the TAC application
"""

import json
import os
import platform
import shutil
import zipfile
import sqlite3
import threading
import re
from pathlib import Path
from typing import Dict, List, Optional, Any
from datetime import datetime

from .config import Config
from .models import Project, Paragraph, ParagraphType
from utils.helpers import FileHelper
from utils.i18n import _

IS_WINDOWS = platform.system() == 'Windows'

# PyLaTeX dependencies
try:
    from pylatex import Document, Section, Subsection, Command, Package, \
                        Figure, NoEscape, NewLine

    from pylatex.base_classes import Environment
    from pylatex.utils import italic, bold, escape_latex

    class Quote(Environment):
        pass

    PYLATEX_AVAILABLE = True
except ImportError as e:
    print(f"ERRO PYLATEX: {e}")
    PYLATEX_AVAILABLE = False

# ODT export dependencies
try:
    from xml.etree import ElementTree as ET
    ODT_AVAILABLE = True
except ImportError:
    ODT_AVAILABLE = False

# PDF export dependencies
try:
    from reportlab.lib.pagesizes import A4
    from reportlab.lib import colors
    from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
    from reportlab.lib.units import cm
    from reportlab.platypus import SimpleDocTemplate, Paragraph as RLParagraph, Spacer, Image as RLImage, Table as RLTable, TableStyle
    from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY
    PDF_AVAILABLE = True
except ImportError:
    PDF_AVAILABLE = False

# Image processing dependencies
try:
    from PIL import Image as PILImage
    import base64
    IMAGE_PROCESSING_AVAILABLE = True
except ImportError:
    IMAGE_PROCESSING_AVAILABLE = False


class ProjectManager:
    """Manages project operations using a SQLite database"""
    
    def __init__(self):
        self.config = Config()
        self.db_path = self.config.database_path
        self._migration_lock = threading.Lock()
        self._init_db()
        self._run_migration_if_needed()
        
        print(_("ProjectManager inicializado com banco de dados: {}").format(self.db_path))

    def _get_db_connection(self):
        """Get a new database connection with optimized settings"""
        try:
            conn = sqlite3.connect(
                self.db_path, 
                timeout=30.0,
                check_same_thread=False
            )
            conn.row_factory = sqlite3.Row
            # Enable WAL mode for better concurrency
            conn.execute("PRAGMA journal_mode = WAL;")
            conn.execute("PRAGMA foreign_keys = ON;")
            conn.execute("PRAGMA synchronous = NORMAL;")
            return conn
        except sqlite3.Error as e:
            print(_("Erro de conexão com banco de dados: {}").format(e))
            raise
    
    def _project_exists(self, project_id: str) -> bool:
        """Check if project exists in database"""
        try:
            with self._get_db_connection() as conn:
                cursor = conn.cursor()
                cursor.execute("SELECT 1 FROM projects WHERE id = ? LIMIT 1", (project_id,))
                return cursor.fetchone() is not None
        except sqlite3.Error as e:
            print(_("Erro ao verificar existência do projeto: {}").format(e))
            return False

    def _validate_json_data(self, data: Dict[str, Any]) -> bool:
        """Validate that project data has required fields"""
        required_fields = ['id', 'name', 'created_at', 'modified_at']
        
        for field in required_fields:
            if field not in data:
                print(_("Dados de projeto inválidos: faltando campo '{}'").format(field))
                return False
        
        # Validate paragraphs if present
        if 'paragraphs' in data:
            for i, para_data in enumerate(data['paragraphs']):
                required_para_fields = ['id', 'type', 'content', 'order']
                for field in required_para_fields:
                    if field not in para_data:
                        print(_("Parágrafo inválido {}: faltando campo '{}'").format(i, field))
                        return False
        
        return True

    def _create_migration_backup(self, json_files: List[Path]) -> Optional[Path]:
        """Create a backup of JSON files before migration"""
        try:
            backup_dir = self.config.data_dir / 'migration_backup'
            backup_dir.mkdir(exist_ok=True)
            
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            backup_file = backup_dir / f"projects_backup_{timestamp}.zip"
            
            with zipfile.ZipFile(backup_file, 'w', zipfile.ZIP_DEFLATED) as zf:
                for json_file in json_files:
                    zf.write(json_file, json_file.name)
            
            print(_("Backup de migração criado: {}").format(backup_file))
            return backup_file
            
        except (OSError, zipfile.BadZipFile) as e:
            print(_("Falha ao criar backup de migração: {}").format(e))
            return None

    def _run_migration_if_needed(self):
        """Check for old JSON files and migrate them to SQLite with full transaction support"""
        with self._migration_lock:
            old_projects_dir = self.config.data_dir / 'projects'
            if not old_projects_dir.exists():
                return

            json_files = list(old_projects_dir.glob("*.json"))
            if not json_files:
                return

            print(_("Encontrados {} projetos JSON antigos. Iniciando migração...").format(len(json_files)))
            
            # Create backup first
            backup_file = self._create_migration_backup(json_files)
            if not backup_file:
                print(_("Migração abortada: Não foi possível criar backup"))
                return
            
            # Load and validate all projects before migration
            projects_to_migrate = []
            invalid_files = []
            
            for project_file in json_files:
                try:
                    with open(project_file, 'r', encoding='utf-8') as f:
                        project_data = json.load(f)
                    
                    if not self._validate_json_data(project_data):
                        invalid_files.append(project_file)
                        continue
                        
                    project = Project.from_dict(project_data)
                    projects_to_migrate.append((project, project_file))
                    
                except (json.JSONDecodeError, OSError) as e:
                    print(_("Erro ao carregar {}: {}").format(project_file.name, e))
                    invalid_files.append(project_file)
            
            if invalid_files:
                print(_("Aviso: {} arquivos têm erros de validação e serão pulados").format(len(invalid_files)))
            
            if not projects_to_migrate:
                print(_("Sem projetos válidos para migrar"))
                return
            
            # Perform migration in single transaction
            migrated_count = 0
            failed_projects = []
            
            try:
                with self._get_db_connection() as conn:
                    cursor = conn.cursor()
                    
                    # Begin transaction
                    cursor.execute("BEGIN IMMEDIATE;")
                    
                    try:
                        for project, project_file in projects_to_migrate:
                            if self._save_project_to_db(cursor, project):
                                migrated_count += 1
                            else:
                                failed_projects.append(project_file)
                        
                        if failed_projects:
                            raise RuntimeError(_("Falha ao migrar {} projetos").format(len(failed_projects)))
                        
                        # Commit transaction
                        conn.commit()
                        print(_("Transação de migração efetivada com sucesso"))
                        
                        # Mark files as migrated only after successful DB commit
                        for project, project_file in projects_to_migrate:
                            try:
                                migrated_file = project_file.with_suffix('.json.migrated')
                                project_file.rename(migrated_file)
                            except OSError as e:
                                print(_("Aviso: Não foi possível renomear {}: {}").format(project_file.name, e))
                        
                    except Exception as e:
                        # Rollback transaction
                        conn.rollback()
                        print(_("Migração falhou, transação revertida: {}").format(e))
                        return
                        
            except sqlite3.Error as e:
                print(_("Migração falhou com erro de banco de dados: {}").format(e))
                return
            
            print(_("Migração completa. {} projetos migrados com sucesso.").format(migrated_count))
            
            # Run database maintenance after migration
            self._vacuum_database()

    def _save_project_to_db(self, cursor: sqlite3.Cursor, project: Project) -> bool:
        """Save project using provided cursor (for transaction support)"""
        try:
            # Validate JSON serialization
            try:
                metadata_json = json.dumps(project.metadata)
                formatting_json = json.dumps(project.document_formatting)
            except (TypeError, ValueError) as e:
                print(_("Erro de serialização JSON para projeto {}: {}").format(project.name, e))
                return False
            
            cursor.execute("""
                INSERT INTO projects (id, name, created_at, modified_at, metadata, document_formatting)
                VALUES (?, ?, ?, ?, ?, ?)
                ON CONFLICT(id) DO UPDATE SET
                    name=excluded.name,
                    modified_at=excluded.modified_at,
                    metadata=excluded.metadata,
                    document_formatting=excluded.document_formatting;
            """, (
                project.id,
                project.name,
                project.created_at.isoformat(),
                datetime.now().isoformat(),
                metadata_json,
                formatting_json
            ))

            # Delete existing paragraphs for this project
            cursor.execute("DELETE FROM paragraphs WHERE project_id = ?", (project.id,))

            # Insert paragraphs
            paragraphs_data = []
            for p in project.paragraphs:
                try:
                    formatting_json = json.dumps(p.formatting)
                    footnotes_json = json.dumps(p.footnotes if hasattr(p, 'footnotes') else [])
                except (TypeError, ValueError) as e:
                    print(_("Erro de serialização JSON para parágrafo {}: {}").format(p.id, e))
                    return False
                    
                paragraphs_data.append((
                    p.id, project.id, p.type.value, p.content,
                    p.created_at.isoformat(), p.modified_at.isoformat(),
                    p.order, formatting_json, footnotes_json
                ))

            if paragraphs_data:
                cursor.executemany("""
                    INSERT INTO paragraphs (id, project_id, type, content, created_at, modified_at, "order", formatting, footnotes)
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);
                """, paragraphs_data)
            
            return True
            
        except sqlite3.Error as e:
            print(_("Erro de banco de dados ao salvar projeto {}: {}").format(project.name, e))
            return False

    def save_project(self, project: Project, is_migration: bool = False) -> bool:
        """Save project to the database (UPSERT)"""
        if is_migration:
            return True
        
        # Create database backup before saving
        self._create_database_backup()
            
        try:
            with self._get_db_connection() as conn:
                cursor = conn.cursor()
                cursor.execute("BEGIN IMMEDIATE;")
                
                try:
                    success = self._save_project_to_db(cursor, project)
                    if success:
                        conn.commit()
                        print(_("Projeto salvo no banco de dados: {}").format(project.name))
                        return True
                    else:
                        conn.rollback()
                        print(_("Falha ao salvar projeto no banco de dados: {}").format(project.name))
                        return False
                except sqlite3.Error as db_error:
                    conn.rollback()
                    print(_("Erro de banco de dados ao salvar projeto '{}': {}").format(project.name, db_error))
                    raise
                except Exception as e:
                    conn.rollback()
                    print(_("Erro inesperado ao salvar projeto '{}': {}: {}").format(
                        project.name, type(e).__name__, e))
                    raise
                    
        except sqlite3.Error as db_error:
            print(_("Erro de conexão para projeto '{}': {}").format(project.name, db_error))
            return False
        except Exception as e:
            print(_("Erro inesperado em save_project para '{}': {}: {}").format(
                project.name, type(e).__name__, e))
            import traceback
            traceback.print_exc()
            return False
        
    def _create_database_backup(self) -> bool:
        """Create backup of database file maintaining only 3 most recent backups"""
        if not self.config.get('backup_files', False):
            return True  # Backup disabled, consider success
        
        try:
            # Ensure database file exists
            if not self.db_path.exists():
                return False
            
            # Detect user's Documents directory (language-aware)
            documents_dir = self._get_documents_directory()
            backup_dir = documents_dir / "TAC Projects" / "database_backups"
            backup_dir.mkdir(parents=True, exist_ok=True)
            
            # Generate backup filename with timestamp
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            backup_filename = f"backup_{timestamp}.db"
            backup_path = backup_dir / backup_filename
            
            # Copy database file
            shutil.copy2(self.db_path, backup_path)
            
            # Clean old backups - keep only 3 most recent
            self._cleanup_old_backups(backup_dir)
            
            print(_("Backup do banco de dados criado: {}").format(backup_path))
            return True
            
        except (OSError, shutil.Error) as e:
            print(_("Aviso: Backup do banco de dados falhou: {}").format(e))
            return False

    def _cleanup_old_backups(self, backup_dir: Path, max_backups: int = 3):
        """Keep only the most recent backups"""
        try:
            backup_files = list(backup_dir.glob("backup_*.db"))
            
            # Sort by modification time (most recent first)
            backup_files.sort(key=lambda x: x.stat().st_mtime, reverse=True)
            
            # Remove files beyond the limit
            for old_backup in backup_files[max_backups:]:
                old_backup.unlink()
                print(_("Backup antigo removido: {}").format(old_backup))
                
        except OSError as e:
            print(_("Aviso: Limpeza de backups antigos falhou: {}").format(e))
            
    def _get_documents_directory(self) -> Path:
        """Get user's Documents directory - cross-platform, priorizando português"""
        home = Path.home()

        # ── Windows ──
        if IS_WINDOWS:
            # Method 1: Windows Known Folder via ctypes (most reliable)
            try:
                import ctypes
                from ctypes import wintypes

                CSIDL_PERSONAL = 0x0005  # "My Documents"
                SHGFP_TYPE_CURRENT = 0
                buf = ctypes.create_unicode_buffer(wintypes.MAX_PATH)
                ctypes.windll.shell32.SHGetFolderPathW(
                    None, CSIDL_PERSONAL, None, SHGFP_TYPE_CURRENT, buf
                )
                if buf.value:
                    documents_path = Path(buf.value)
                    if documents_path.exists():
                        return documents_path
            except Exception:
                pass

            # Method 2: USERPROFILE - prioriza "Documentos" (pt-BR)
            userprofile = os.environ.get('USERPROFILE', '')
            if userprofile:
                # Tenta português primeiro
                docs_pt = Path(userprofile) / 'Documentos'
                if docs_pt.exists():
                    return docs_pt
                # Depois inglês
                docs_en = Path(userprofile) / 'Documents'
                if docs_en.exists():
                    return docs_en

            # Method 3: OneDrive Documents
            onedrive = os.environ.get('OneDrive', '')
            if onedrive:
                docs_pt = Path(onedrive) / 'Documentos'
                if docs_pt.exists():
                    return docs_pt
                docs_en = Path(onedrive) / 'Documents'
                if docs_en.exists():
                    return docs_en

            # Fallback to home
            return home

        # ── Linux / macOS ──
        # Try XDG user dirs first (Linux)
        try:
            import subprocess
            result = subprocess.run(['xdg-user-dir', 'DOCUMENTS'], 
                                capture_output=True, text=True, timeout=5)
            if result.returncode == 0:
                documents_path = Path(result.stdout.strip())
                if documents_path.exists():
                    return documents_path
        except (subprocess.TimeoutExpired, subprocess.SubprocessError, FileNotFoundError):
            pass
        
        # Try common localized directory names — português primeiro
        possible_names = [
            'Documentos', 'Documents', 'Dokumente', 'Documenti',
            'Документы', 'Документи', 'Dokumenty', 'Dokumenter',
            'Έγγραφα', 'Dokumendid', 'Asiakirjat', 'מסמכים',
            'Dokumenti', 'Dokumentumok', 'Skjöl', 'ドキュメント',
            '문서', 'Documenten', 'Documente', 'Dokument',
            'Belgeler', '文档',
        ]
        
        for name in possible_names:
            candidate = home / name
            if candidate.exists() and candidate.is_dir():
                return candidate
        
        # Fallback: use data directory
        return self.config.data_dir

    def list_projects(self) -> List[Dict[str, Any]]:
        """List all projects from the database with optimized statistics"""
        projects_info = []
        try:
            with self._get_db_connection() as conn:
                cursor = conn.cursor()
                
                # Get all projects first
                cursor.execute("""
                    SELECT p.id, p.name, p.created_at, p.modified_at
                    FROM projects p
                    ORDER BY p.modified_at DESC;
                """)
                projects = cursor.fetchall()
                
                for project_row in projects:
                    project_id = project_row['id']
                    
                    # Get paragraphs for this project in correct order
                    cursor.execute("""
                        SELECT type, content FROM paragraphs 
                        WHERE project_id = ? ORDER BY "order" ASC
                    """, (project_id,))
                    paragraphs_rows = cursor.fetchall()
                    
                    # Convert database rows to lightweight paragraph objects for calculation
                    paragraph_data = []
                    for p_row in paragraphs_rows:
                        # Create a simple object with the attributes needed for statistics
                        class LightParagraph:
                            def __init__(self, p_type, content):
                                try:
                                    self.type = ParagraphType(p_type)
                                except ValueError:
                                    # Handle old 'argument_quote' -> 'quote' migration
                                    if p_type == 'argument_quote':
                                        self.type = ParagraphType.QUOTE
                                    else:
                                        # Skip invalid types
                                        self.type = None
                                self.content = content or ''
                        
                        light_p = LightParagraph(p_row['type'], p_row['content'])
                        if light_p.type is not None:
                            paragraph_data.append(light_p)
                    
                    # Use consolidated static methods from Project class
                    total_words = sum(
                        Project._calculate_word_count(p.content) 
                        for p in paragraph_data
                    )
                    total_paragraphs = Project._count_logical_paragraphs(paragraph_data)
                    
                    stats = {
                        'total_paragraphs': total_paragraphs,
                        'total_words': total_words,
                    }
                    
                    projects_info.append({
                        'id': project_row['id'],
                        'name': project_row['name'],
                        'created_at': project_row['created_at'],
                        'modified_at': project_row['modified_at'],
                        'statistics': stats,
                        'file_path': None
                    })
                    
        except sqlite3.Error as e:
            print(_("Erro de banco de dados ao listar projetos: {}").format(e))
        except Exception as e:
            print(_("Erro inesperado ao listar projetos: {}: {}").format(type(e).__name__, e))
        
        return projects_info

    def _vacuum_database(self):
        """Perform database maintenance"""
        try:
            with self._get_db_connection() as conn:
                conn.execute("VACUUM;")
                conn.execute("ANALYZE;")
                print(_("Manutenção do banco de dados concluída"))
        except sqlite3.Error as e:
            print(_("Manutenção do banco de dados falhou: {}").format(e))

    def get_database_info(self) -> Dict[str, Any]:
        """Get database statistics and health information"""
        try:
            with self._get_db_connection() as conn:
                cursor = conn.cursor()
                
                # Get table sizes
                cursor.execute("SELECT COUNT(*) as project_count FROM projects;")
                project_count = cursor.fetchone()['project_count']
                
                cursor.execute("SELECT COUNT(*) as paragraph_count FROM paragraphs;")
                paragraph_count = cursor.fetchone()['paragraph_count']
                
                # Get database file size
                db_size = self.db_path.stat().st_size if self.db_path.exists() else 0
                
                return {
                    'database_path': str(self.db_path),
                    'database_size_bytes': db_size,
                    'project_count': project_count,
                    'paragraph_count': paragraph_count,
                    'health_status': 'healthy'
                }
                
        except sqlite3.Error as e:
            return {
                'database_path': str(self.db_path),
                'health_status': _('erro: {}').format(e),
                'project_count': 0,
                'paragraph_count': 0
            }
        except Exception as e:
            return {
                'database_path': str(self.db_path),
                'health_status': _('erro inesperado: {}').format(e),
                'project_count': 0,
                'paragraph_count': 0
            }
    
    def _init_db(self):
        """Initialize the database and create tables if they don't exist"""
        try:
            with self._get_db_connection() as conn:
                cursor = conn.cursor()
                cursor.execute("""
                    CREATE TABLE IF NOT EXISTS projects (
                        id TEXT PRIMARY KEY,
                        name TEXT NOT NULL,
                        created_at TEXT NOT NULL,
                        modified_at TEXT NOT NULL,
                        metadata TEXT,
                        document_formatting TEXT
                    );
                """)
                cursor.execute("""
                    CREATE TABLE IF NOT EXISTS paragraphs (
                        id TEXT PRIMARY KEY,
                        project_id TEXT NOT NULL,
                        type TEXT NOT NULL,
                        content TEXT,
                        created_at TEXT NOT NULL,
                        modified_at TEXT NOT NULL,
                        "order" INTEGER NOT NULL,
                        formatting TEXT,
                        footnotes TEXT,
                        FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE
                    );
                """)
                
                # Add footnotes column if it doesn't exist (migration)
                try:
                    cursor.execute("ALTER TABLE paragraphs ADD COLUMN footnotes TEXT")
                except sqlite3.OperationalError:
                    # Column already exists
                    pass
                    
                conn.commit()
        except sqlite3.Error as e:
            print(_("Erro de inicialização do banco de dados: {}").format(e))
            raise

    def create_project(self, name: str, template: str = "academic_essay") -> Project:
        """Create a new project"""
        try:
            project = Project(name)
            
            if template == "academic_essay":
                pass
            
            if self.save_project(project):
                print(_("Projeto criado: {} ({})").format(project.name, project.id))
                return project
            else:
                raise RuntimeError(_("Falha ao salvar novo projeto no banco de dados"))
        except Exception as e:
            print(_("Erro ao criar projeto: {}: {}").format(type(e).__name__, e))
            raise

    def load_project(self, project_id: str) -> Optional[Project]:
        """Load project by ID from the database"""
        try:
            with self._get_db_connection() as conn:
                cursor = conn.cursor()
                
                cursor.execute("SELECT * FROM projects WHERE id = ?", (project_id,))
                project_row = cursor.fetchone()
                
                if not project_row:
                    print(_("Projeto com ID {} não encontrado no banco de dados.").format(project_id))
                    return None
                
                project_data = dict(project_row)
                project_data['metadata'] = json.loads(project_data['metadata'])
                project_data['document_formatting'] = json.loads(project_data['document_formatting'])
                
                cursor.execute("SELECT * FROM paragraphs WHERE project_id = ? ORDER BY \"order\" ASC", (project_id,))
                paragraphs_rows = cursor.fetchall()

                paragraphs_data = []
                for p_row in paragraphs_rows:
                    p_data = dict(p_row)
                    p_data['formatting'] = json.loads(p_data['formatting'])
                    
                    # Handle footnotes (with backward compatibility)
                    if p_data.get('footnotes'):
                        try:
                            p_data['footnotes'] = json.loads(p_data['footnotes'])
                        except (json.JSONDecodeError, TypeError):
                            p_data['footnotes'] = []
                    else:
                        p_data['footnotes'] = []
                        
                    paragraphs_data.append(p_data)
                
                project_data['paragraphs'] = paragraphs_data
                
                project = Project.from_dict(project_data)
                print(_("Projeto carregado do banco de dados: {}").format(project.name))
                return project
                
        except sqlite3.Error as e:
            print(_("Erro de banco de dados ao carregar projeto: {}").format(e))
            return None
        except (json.JSONDecodeError, KeyError, ValueError) as e:
            print(_("Erro de dados ao carregar projeto: {}: {}").format(type(e).__name__, e))
            return None
        except Exception as e:
            print(_("Erro inesperado ao carregar projeto: {}: {}").format(type(e).__name__, e))
            import traceback
            traceback.print_exc()
            return None

    def delete_project(self, project_id: str) -> bool:
        """Delete project from the database"""
        try:
            with self._get_db_connection() as conn:
                cursor = conn.cursor()
                cursor.execute("DELETE FROM projects WHERE id = ?", (project_id,))
                conn.commit()
                
                print(_("Projeto excluído do banco de dados: {}").format(project_id))
                return True
        except sqlite3.Error as e:
            print(_("Erro de banco de dados ao excluir projeto: {}").format(e))
            return False

    def create_manual_backup(self) -> Optional[Path]:
        """Create a manual backup of the database"""
        try:
            if not self.db_path.exists():
                return None
                
            # Get backup directory
            documents_dir = self._get_documents_directory()
            backup_dir = documents_dir / "TAC Projects" / "database_backups"
            backup_dir.mkdir(parents=True, exist_ok=True)
            
            # Generate backup filename with timestamp
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            backup_filename = f"manual_backup_{timestamp}.db"
            backup_path = backup_dir / backup_filename
            
            # Copy database file
            shutil.copy2(self.db_path, backup_path)
            
            # Clean old backups - keep only 10 most recent
            self._cleanup_old_backups(backup_dir, max_backups=10)
            
            print(_("Backup manual criado: {}").format(backup_path))
            return backup_path
            
        except (OSError, shutil.Error) as e:
            print(_("Erro ao criar backup manual: {}").format(e))
            return None

    def list_available_backups(self) -> List[Dict[str, Any]]:
        """List available backup files with metadata"""
        backups = []
        try:
            documents_dir = self._get_documents_directory()
            backup_dir = documents_dir / "TAC Projects" / "database_backups"
            
            if not backup_dir.exists():
                return backups
                
            # Find all backup files
            backup_files = list(backup_dir.glob("*.db"))
            
            for backup_file in backup_files:
                try:
                    # Get file stats
                    stat = backup_file.stat()
                    
                    # Try to get project count from backup
                    project_count = 0
                    try:
                        with sqlite3.connect(backup_file) as conn:
                            cursor = conn.cursor()
                            cursor.execute("SELECT COUNT(*) FROM projects")
                            project_count = cursor.fetchone()[0]
                    except sqlite3.Error:
                        pass
                    
                    backups.append({
                        'path': backup_file,
                        'name': backup_file.name,
                        'size': stat.st_size,
                        'created_at': datetime.fromtimestamp(stat.st_mtime),
                        'project_count': project_count,
                        'is_valid': self._validate_backup_file(backup_file)
                    })
                except (OSError, ValueError) as e:
                    print(_("Erro ao ler arquivo de backup {}: {}").format(backup_file, e))
                    continue
            
            # Sort by creation date (newest first)
            backups.sort(key=lambda x: x['created_at'], reverse=True)
            
        except OSError as e:
            print(_("Erro ao listar backups: {}").format(e))
            
        return backups

    def _validate_backup_file(self, backup_path: Path) -> bool:
        """Validate if backup file is a valid TAC database"""
        try:
            with sqlite3.connect(backup_path) as conn:
                cursor = conn.cursor()
                
                # Check if required tables exist
                cursor.execute("""
                    SELECT name FROM sqlite_master 
                    WHERE type='table' AND name IN ('projects', 'paragraphs')
                """)
                tables = cursor.fetchall()
                
                if len(tables) != 2:
                    return False
                
                # Check table structure
                cursor.execute("PRAGMA table_info(projects)")
                project_columns = [row[1] for row in cursor.fetchall()]
                required_project_columns = ['id', 'name', 'created_at', 'modified_at']
                
                for col in required_project_columns:
                    if col not in project_columns:
                        return False
                
                return True
                
        except sqlite3.Error as e:
            print(_("Erro de validação de backup: {}").format(e))
            return False

    def import_database(self, backup_path: Path) -> bool:
        """Import database from backup file"""
        try:
            # Validate backup file
            if not self._validate_backup_file(backup_path):
                print(_("Arquivo de backup inválido"))
                return False
            
            # Create backup of current database
            current_backup_path = None
            if self.db_path.exists():
                timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
                current_backup_path = self.db_path.with_suffix(f'.backup_{timestamp}.db')
                shutil.copy2(self.db_path, current_backup_path)
                print(_("Banco de dados atual salvo em: {}").format(current_backup_path))
            
            try:
                # Replace current database
                shutil.copy2(backup_path, self.db_path)
                
                # Test the imported database
                with self._get_db_connection() as conn:
                    cursor = conn.cursor()
                    cursor.execute("SELECT COUNT(*) FROM projects")
                    project_count = cursor.fetchone()[0]
                    print(_("Banco de dados importado com sucesso com {} projetos").format(project_count))
                
                return True
                
            except (shutil.Error, sqlite3.Error) as e:
                # Restore backup if import failed
                if current_backup_path and current_backup_path.exists():
                    shutil.copy2(current_backup_path, self.db_path)
                    print(_("Importação falhou, banco de dados anterior restaurado"))
                raise e
                
        except Exception as e:
            print(_("Erro ao importar banco de dados: {}: {}").format(type(e).__name__, e))
            import traceback
            traceback.print_exc()
            return False

    def delete_backup(self, backup_path: Path) -> bool:
        """Delete a backup file"""
        try:
            if backup_path.exists():
                backup_path.unlink()
                print(_("Backup excluído: {}").format(backup_path))
                return True
            return False
        except OSError as e:
            print(_("Erro ao excluir backup: {}").format(e))
            return False

    @property
    def projects_dir(self) -> Path:
        """Get projects directory for compatibility"""
        return self.config.data_dir / 'projects'

    def merge_database(self, external_db_path: str) -> dict:
        """
        Mescla um banco de dados externo com o atual.
        """
        from core.merger import DatabaseMerger
        
        # Caminho do banco atual (definido no config ou self.db_path)
        current_db = self.config.get('database_file')
        
        merger = DatabaseMerger(self.db_path)
        try:
            stats = merger.merge(external_db_path)
            return stats
        except Exception as e:
            print(f"Erro no merge: {e}")
            raise e


    def _run_migration_if_needed(self):
        """Check for old JSON files and migrate them to SQLite with full transaction support"""
        with self._migration_lock:
            old_projects_dir = self.config.data_dir / 'projects'
            if not old_projects_dir.exists():
                return

            json_files = list(old_projects_dir.glob("*.json"))
            if not json_files:
                return

            print(_("Encontrados {} projetos JSON antigos. Iniciando migração...").format(len(json_files)))
            
            backup_file = self._create_migration_backup(json_files)
            if not backup_file:
                print(_("Migração abortada: Não foi possível criar backup"))
                return
            
            projects_to_migrate = []
            invalid_files = []
            
            for project_file in json_files:
                try:
                    with open(project_file, 'r', encoding='utf-8') as f:
                        project_data = json.load(f)
                    
                    if not self._validate_json_data(project_data):
                        invalid_files.append(project_file)
                        continue
                        
                    project = Project.from_dict(project_data)
                    projects_to_migrate.append((project, project_file))
                    
                except (json.JSONDecodeError, OSError) as e:
                    print(_("Erro ao carregar {}: {}").format(project_file.name, e))
                    invalid_files.append(project_file)
            
            if invalid_files:
                print(_("Aviso: {} arquivos têm erros de validação e serão pulados").format(len(invalid_files)))
            
            if not projects_to_migrate:
                print(_("Sem projetos válidos para migrar"))
                return
            
            migrated_count = 0
            failed_projects = []
            
            try:
                with self._get_db_connection() as conn:
                    cursor = conn.cursor()
                    cursor.execute("BEGIN IMMEDIATE;")
                    
                    try:
                        for project, project_file in projects_to_migrate:
                            if self._save_project_to_db(cursor, project):
                                migrated_count += 1
                            else:
                                failed_projects.append(project_file)
                        
                        if failed_projects:
                            raise RuntimeError(_("Falha ao migrar {} projetos").format(len(failed_projects)))
                        
                        conn.commit()
                        print(_("Transação de migração efetivada com sucesso"))
                        
                        for project, project_file in projects_to_migrate:
                            try:
                                migrated_file = project_file.with_suffix('.json.migrated')
                                project_file.rename(migrated_file)
                            except OSError as e:
                                print(_("Aviso: Não foi possível renomear {}: {}").format(project_file.name, e))
                        
                    except Exception as e:
                        conn.rollback()
                        print(_("Migração falhou, transação revertida: {}").format(e))
                        return
                        
            except sqlite3.Error as e:
                print(_("Migração falhou com erro de banco de dados: {}").format(e))
                return
            
            print(_("Migração completa. {} projetos migrados com sucesso.").format(migrated_count))
            self._vacuum_database()


class ExportService:
    """Handles document export operations"""
    
    def __init__(self):
        self.odt_available = ODT_AVAILABLE
        self.pdf_available = PDF_AVAILABLE
        self.pylatex_available = PYLATEX_AVAILABLE

        # Configure font acoording to platform
        self._setup_fonts()
        
        if not self.odt_available:
            print(_("Aviso: Exportação ODT indisponível (faltando dependências xml)"))
        if not self.pdf_available:
            print(_("Aviso: Exportação PDF indisponível (faltando reportlab)"))
        if not self.pylatex_available:
            print(_("Aviso: Exportação LaTeX indisponível (faltando biblioteca pylatex)"))

    def _setup_fonts(self):
        """Configure fonts based on platform — Liberation for Linux, MS fonts for Windows"""
        if IS_WINDOWS:
            # ODT font names
            self.font_serif = "Times New Roman"
            self.font_sans = "Arial"
            self.font_mono = "Courier New"
            # PDF (ReportLab built-in PostScript fonts)
            self.pdf_font_roman = "Times-Roman"
            self.pdf_font_bold = "Times-Bold"
            self.pdf_font_italic = "Times-Italic"
        else:
            # ODT font names
            self.font_serif = "Liberation Serif"
            self.font_sans = "Liberation Sans"
            self.font_mono = "Liberation Mono"
            # PDF — tentar registrar Liberation, fallback para built-in
            self._register_liberation_fonts()

    def _register_liberation_fonts(self):
        """Register Liberation fonts for PDF export on Linux"""
        if not self.pdf_available:
            self.pdf_font_roman = "Times-Roman"
            self.pdf_font_bold = "Times-Bold"
            self.pdf_font_italic = "Times-Italic"
            return

        try:
            from reportlab.pdfbase import pdfmetrics
            from reportlab.pdfbase.ttfonts import TTFont

            # Diretórios comuns de fontes no Linux
            font_dirs = [
                Path('/usr/share/fonts/truetype/liberation'),
                Path('/usr/share/fonts/truetype/liberation2'),
                Path('/usr/share/fonts/liberation-serif'),
                Path('/usr/share/fonts/liberation-sans'),
                Path('/usr/share/fonts/liberation-mono'),
                Path('/usr/share/fonts/liberation'),
                Path('/usr/share/fonts/TTF'),
                Path.home() / '.local' / 'share' / 'fonts',
            ]

            fonts = {
                'LiberationSerif': 'LiberationSerif-Regular.ttf',
                'LiberationSerif-Bold': 'LiberationSerif-Bold.ttf',
                'LiberationSerif-Italic': 'LiberationSerif-Italic.ttf',
                'LiberationSerif-BoldItalic': 'LiberationSerif-BoldItalic.ttf',
            }

            registered = {}
            for font_name, font_file in fonts.items():
                for font_dir in font_dirs:
                    font_path = font_dir / font_file
                    if font_path.exists():
                        pdfmetrics.registerFont(TTFont(font_name, str(font_path)))
                        registered[font_name] = True
                        break

            if 'LiberationSerif' in registered:
                self.pdf_font_roman = 'LiberationSerif'
                self.pdf_font_bold = 'LiberationSerif-Bold' if 'LiberationSerif-Bold' in registered else 'LiberationSerif'
                self.pdf_font_italic = 'LiberationSerif-Italic' if 'LiberationSerif-Italic' in registered else 'LiberationSerif'

                # Registrar família para suporte a tags <b>/<i> no ReportLab
                from reportlab.pdfbase.pdfmetrics import registerFontFamily
                registerFontFamily(
                    'LiberationSerif',
                    normal='LiberationSerif',
                    bold=self.pdf_font_bold,
                    italic=self.pdf_font_italic,
                    boldItalic='LiberationSerif-BoldItalic' if 'LiberationSerif-BoldItalic' in registered else self.pdf_font_bold
                )
                print(_("Fontes Liberation registradas para exportação PDF"))
            else:
                print(_("Fontes Liberation não encontradas, usando fontes padrão PDF"))
                self.pdf_font_roman = "Times-Roman"
                self.pdf_font_bold = "Times-Bold"
                self.pdf_font_italic = "Times-Italic"

        except Exception as e:
            print(_("Aviso: Erro ao registrar fontes Liberation: {}").format(e))
            self.pdf_font_roman = "Times-Roman"
            self.pdf_font_bold = "Times-Bold"
            self.pdf_font_italic = "Times-Italic"
    
    def _collect_footnotes(self, project: Project) -> tuple:
        """
        Collect all unique footnotes from project and create mapping.
        
        Returns:
            tuple: (all_footnotes list, footnote_map dict)
        """
        all_footnotes = []
        footnote_map = {}
        
        for paragraph in project.paragraphs:
            if hasattr(paragraph, 'footnotes') and paragraph.footnotes:
                paragraph_footnotes = []
                for footnote_text in paragraph.footnotes:
                    # Check if footnote already exists
                    existing_num = None
                    for i, existing_footnote in enumerate(all_footnotes):
                        if footnote_text == existing_footnote:
                            existing_num = i + 1
                            break
                    
                    if existing_num is None:
                        # New footnote
                        footnote_num = len(all_footnotes) + 1
                        all_footnotes.append(footnote_text)
                        paragraph_footnotes.append(footnote_num)
                    else:
                        # Reuse existing footnote
                        paragraph_footnotes.append(existing_num)
                
                footnote_map[paragraph.id] = paragraph_footnotes
        
        return all_footnotes, footnote_map
    
    def _group_paragraphs(self, project: Project, footnote_map: dict) -> list:
        """
        Group paragraphs following TAC methodology.
        """
        grouped = []
        current_paragraph_content = []
        paragraph_starts_with_introduction = False
        last_was_quote = False
        
        for i, paragraph in enumerate(project.paragraphs):
            content = paragraph.content.strip()
            
            if paragraph.type == ParagraphType.TITLE_1:
                if current_paragraph_content:
                    combined = " ".join(current_paragraph_content)
                    grouped.append({
                        'type': 'content',
                        'content': combined,
                        'indent': paragraph_starts_with_introduction
                    })
                    current_paragraph_content = []
                    paragraph_starts_with_introduction = False
                
                grouped.append({'type': 'title1', 'content': content})
                last_was_quote = False
            
            elif paragraph.type == ParagraphType.TITLE_2:
                if current_paragraph_content:
                    combined = " ".join(current_paragraph_content)
                    grouped.append({
                        'type': 'content',
                        'content': combined,
                        'indent': paragraph_starts_with_introduction
                    })
                    current_paragraph_content = []
                    paragraph_starts_with_introduction = False
                
                grouped.append({'type': 'title2', 'content': content})
                last_was_quote = False
            
            elif paragraph.type == ParagraphType.QUOTE:
                if current_paragraph_content:
                    combined = " ".join(current_paragraph_content)
                    grouped.append({
                        'type': 'content',
                        'content': combined,
                        'indent': paragraph_starts_with_introduction
                    })
                    current_paragraph_content = []
                    paragraph_starts_with_introduction = False
                
                grouped.append({'type': 'quote', 'content': content})
                last_was_quote = True
            
            elif paragraph.type == ParagraphType.EPIGRAPH:
                if current_paragraph_content:
                    combined = " ".join(current_paragraph_content)
                    grouped.append({
                        'type': 'content',
                        'content': combined,
                        'indent': paragraph_starts_with_introduction
                    })
                    current_paragraph_content = []
                    paragraph_starts_with_introduction = False
                
                grouped.append({'type': 'epigraph', 'content': content})
                last_was_quote = True
            
            elif paragraph.type == ParagraphType.IMAGE:
                if current_paragraph_content:
                    combined = " ".join(current_paragraph_content)
                    grouped.append({
                        'type': 'content',
                        'content': combined,
                        'indent': paragraph_starts_with_introduction
                    })
                    current_paragraph_content = []
                    paragraph_starts_with_introduction = False
                
                img_metadata = paragraph.get_image_metadata()
                if img_metadata:
                    grouped.append({'type': 'image', 'metadata': img_metadata})
                last_was_quote = False
            
            elif paragraph.type == ParagraphType.CODE:
                if current_paragraph_content:
                    combined = " ".join(current_paragraph_content)
                    grouped.append({
                        'type': 'content',
                        'content': combined,
                        'indent': paragraph_starts_with_introduction
                    })
                    current_paragraph_content = []
                    paragraph_starts_with_introduction = False
                
                grouped.append({'type': 'code', 'content': content})
                last_was_quote = False

            elif paragraph.type in [ParagraphType.INTRODUCTION, ParagraphType.ARGUMENT, ParagraphType.CONCLUSION, ParagraphType.ARGUMENT_RESUMPTION]:
                should_start_new = (
                    paragraph.type == ParagraphType.INTRODUCTION or
                    paragraph.type == ParagraphType.ARGUMENT_RESUMPTION or
                    last_was_quote or
                    not current_paragraph_content
                )
                
                if should_start_new and current_paragraph_content:
                    combined = " ".join(current_paragraph_content)
                    grouped.append({
                        'type': 'content',
                        'content': combined,
                        'indent': paragraph_starts_with_introduction
                    })
                    current_paragraph_content = []
                    paragraph_starts_with_introduction = False
                
                if not current_paragraph_content:
                    if paragraph.type == ParagraphType.INTRODUCTION or paragraph.type == ParagraphType.ARGUMENT_RESUMPTION:
                        paragraph_starts_with_introduction = True
                    elif last_was_quote:
                        paragraph_starts_with_introduction = False
                
                if paragraph.id in footnote_map:
                    for footnote_num in footnote_map[paragraph.id]:
                        content += f"^{footnote_num}"
                
                current_paragraph_content.append(content)
                
                next_is_new = False
                if i + 1 < len(project.paragraphs):
                    next_p = project.paragraphs[i + 1]
                    if next_p.type in [ParagraphType.INTRODUCTION, ParagraphType.TITLE_1, 
                                      ParagraphType.TITLE_2, ParagraphType.QUOTE]:
                        next_is_new = True
                else:
                    next_is_new = True
                
                if next_is_new:
                    combined = " ".join(current_paragraph_content)
                    grouped.append({
                        'type': 'content',
                        'content': combined,
                        'indent': paragraph_starts_with_introduction
                    })
                    current_paragraph_content = []
                    paragraph_starts_with_introduction = False
                
                last_was_quote = False
        
        if current_paragraph_content:
            combined = " ".join(current_paragraph_content)
            grouped.append({
                'type': 'content',
                'content': combined,
                'indent': paragraph_starts_with_introduction
            })
        
        return grouped

    def get_available_formats(self) -> List[str]:
        """Get list of available export formats"""
        formats = ['txt', 'md']
        
        if self.odt_available:
            formats.append('odt')
        if self.pdf_available:
            formats.append('pdf')
        if self.pylatex_available:
            formats.append('tex')
            
        return formats

    def export_project(self, project: Project, file_path: str, format_type: str) -> bool:
        """Export project to specified format"""
        try:
            if format_type.lower() == 'txt':
                return self._export_txt(project, file_path)
            elif format_type.lower() == 'md':
                return self._export_md(project, file_path)
            elif format_type.lower() == 'odt' and self.odt_available:
                return self._export_odt(project, file_path)
            elif format_type.lower() == 'pdf' and self.pdf_available:
                return self._export_pdf(project, file_path)
            elif format_type.lower() == 'tex' and self.pylatex_available:
                return self._export_latex(project, file_path)
            else:
                print(_("Formato de exportação '{}' não disponível").format(format_type))
                return False
                
        except Exception as e:
            print(_("Erro ao exportar projeto: {}: {}").format(type(e).__name__, e))
            import traceback
            traceback.print_exc()
            return False

    def _export_txt(self, project: Project, file_path: str) -> bool:
        """Export to plain text format"""
        try:
            file_path_obj = Path(file_path)
            file_path_obj.parent.mkdir(parents=True, exist_ok=True)
            
            all_footnotes, footnote_map = self._collect_footnotes(project)
            grouped = self._group_paragraphs(project, footnote_map)
            
            with open(file_path, 'w', encoding='utf-8') as f:
                f.write(f"{project.name}\n")
                f.write("=" * len(project.name) + "\n\n")
                
                for item in grouped:
                    if item['type'] == 'title1':
                        f.write(f"\n{item['content']}\n")
                        f.write("-" * len(item['content']) + "\n\n")
                    
                    elif item['type'] == 'title2':
                        f.write(f"\n{item['content']}\n\n")
                    
                    elif item['type'] == 'quote':
                        f.write(f"        {item['content']}\n\n")

                    elif item['type'] == 'epigraph':
                        f.write(f"                            {item['content']}\n\n")
                    
                    elif item['type'] == 'image':
                        metadata = item['metadata']
                        caption = metadata.get('caption', '')
                        if caption:
                            f.write(f"\n[IMAGE: {metadata.get('filename', 'image')} - {caption}]\n\n")
                        else:
                            f.write(f"\n[IMAGE: {metadata.get('filename', 'image')}]\n\n")
                    
                    elif item['type'] == 'content':
                        if item['indent']:
                            f.write(f"    {item['content']}\n\n")
                        else:
                            f.write(f"{item['content']}\n\n")
                
                if all_footnotes:
                    f.write("\n" + "=" * 20 + "\n")
                    f.write(_("Notas de rodapé:") + "\n\n")
                    for i, footnote in enumerate(all_footnotes):
                        f.write(f"{i + 1}. {footnote}\n\n")
            
            return True
            
        except OSError as e:
            print(_("Erro de arquivo ao exportar para TXT: {}").format(e))
            return False
        except Exception as e:
            print(_("Erro inesperado ao exportar para TXT: {}: {}").format(type(e).__name__, e))
            import traceback
            traceback.print_exc()
            return False

    def _export_odt(self, project: Project, file_path: str) -> bool:
        """Export to OpenDocument Text format"""
        try:
            odt_path = Path(file_path)
            odt_path.parent.mkdir(parents=True, exist_ok=True)
            
            temp_dir = odt_path.parent / f"temp_odt_{project.id}"
            temp_dir.mkdir(exist_ok=True)
            
            try:
                (temp_dir / "META-INF").mkdir(exist_ok=True)
                (temp_dir / "Pictures").mkdir(exist_ok=True)
                
                image_files = []
                for paragraph in project.paragraphs:
                    if paragraph.type == ParagraphType.IMAGE:
                        img_metadata = paragraph.get_image_metadata()
                        if img_metadata:
                            img_path = Path(img_metadata['path'])
                            if img_path.exists():
                                dest_name = img_metadata['filename']
                                dest_path = temp_dir / "Pictures" / dest_name
                                shutil.copy2(img_path, dest_path)
                                image_files.append(dest_name)
                    # Imagens de gráficos
                    elif paragraph.type in (ParagraphType.CHART, 'chart'):
                        image_path, title = self._get_chart_meta(paragraph)
                        if image_path and Path(image_path).exists():
                            dest_name = Path(image_path).name
                            shutil.copy2(image_path, temp_dir / "Pictures" / dest_name)
                            if dest_name not in image_files:
                                image_files.append(dest_name)
                    elif paragraph.type in (ParagraphType.MAP, 'map'):
                        image_path, title = self._get_map_meta(paragraph)
                        if image_path and Path(image_path).exists():
                            dest_name = Path(image_path).name
                            shutil.copy2(image_path, temp_dir / "Pictures" / dest_name)
                            if dest_name not in image_files:
                                image_files.append(dest_name)
                
                self._create_manifest(temp_dir / "META-INF" / "manifest.xml", image_files)
                self._create_styles(temp_dir / "styles.xml")
                self._create_settings(temp_dir / "settings.xml")
                
                content_xml = self._generate_odt_content(project)
                with open(temp_dir / "content.xml", 'w', encoding='utf-8') as f:
                    f.write(content_xml)
                
                self._create_meta(temp_dir / "meta.xml", project)
                
                # ── FIX: os.walk() instead of Path.walk() for Python < 3.12 ──
                with zipfile.ZipFile(file_path, 'w', zipfile.ZIP_DEFLATED) as zf:
                    zf.writestr("mimetype", "application/vnd.oasis.opendocument.text", 
                            compress_type=zipfile.ZIP_STORED)
                    
                    for root, dirs, files in os.walk(temp_dir):
                        for file in files:
                            file_path_obj = Path(root) / file
                            arc_name = file_path_obj.relative_to(temp_dir)
                            zf.write(file_path_obj, arc_name)
                
                return True
                
            finally:
                shutil.rmtree(temp_dir, ignore_errors=True)
                
        except (OSError, zipfile.BadZipFile) as e:
            print(_("Erro de arquivo ao exportar para ODT: {}").format(e))
            return False
        except Exception as e:
            print(_("Erro inesperado ao exportar para ODT: {}: {}").format(type(e).__name__, e))
            import traceback
            traceback.print_exc()
            return False

    def _format_text_for_odt(self, text: str) -> str:
        """
        Converts internal HTML-like tags (<b>, <i>, <u>) to ODT XML tags.
        """
        if not text:
            return ""

        text = text.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')

        text = text.replace('&lt;b&gt;', '<text:span text:style-name="T_Bold">')
        text = text.replace('&lt;/b&gt;', '</text:span>')
        text = text.replace('&lt;i&gt;', '<text:span text:style-name="T_Italic">')
        text = text.replace('&lt;/i&gt;', '</text:span>')
        text = text.replace('&lt;u&gt;', '<text:span text:style-name="T_Underline">')
        text = text.replace('&lt;/u&gt;', '</text:span>')
        
        text = text.replace('\n', '<text:line-break/>')

        return text

    def _format_text_for_pdf(self, text: str) -> str:
        """
        Prepares text for ReportLab PDF.
        """
        if not text:
            return ""

        text = text.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')

        tags = ['b', 'i', 'u']
        for tag in tags:
            text = text.replace(f'&lt;{tag}&gt;', f'<{tag}>')
            text = text.replace(f'&lt;/{tag}&gt;', f'</{tag}>')
        
        text = text.replace('\n', '<br/>')

        return text

    def _format_text_for_latex(self, text: str) -> Any:
        """
        Converte tags internas (HTML-like) para comandos LaTeX usando utilitários do PyLaTeX.
        """
        if not text:
            return ""

        text = text.replace("<b>", "@@BOLD_START@@").replace("</b>", "@@BOLD_END@@")
        text = text.replace("<i>", "@@ITALIC_START@@").replace("</i>", "@@ITALIC_END@@")
        text = text.replace("<u>", "@@UNDER_START@@").replace("</u>", "@@UNDER_END@@")

        text = escape_latex(text)

        text = text.replace("@@BOLD_START@@", "\\textbf{").replace("@@BOLD_END@@", "}")
        text = text.replace("@@ITALIC_START@@", "\\textit{").replace("@@ITALIC_END@@", "}")
        text = text.replace("@@UNDER_START@@", "\\underline{").replace("@@UNDER_END@@", "}")
        
        text = text.replace("\n", "\n\n")

        return NoEscape(text)

    def _export_md(self, project, file_path):
        """Export to Markdown format"""
        try:
            from pathlib import Path
            file_path_obj = Path(file_path)
            file_path_obj.parent.mkdir(parents=True, exist_ok=True)

            with open(file_path, 'w', encoding='utf-8') as f:
                f.write(f"# {project.name}\n\n")
                if project.metadata.get('author'):
                    f.write(f"**Autor:** {project.metadata['author']}\n\n")

                for paragraph in project.paragraphs:
                    content = paragraph.content.strip()
                    content = content.replace("<b>", "**").replace("</b>", "**")
                    content = content.replace("<i>", "*").replace("</i>", "*")
                    content = content.replace("<u>", "").replace("</u>", "")

                    if paragraph.type == ParagraphType.TITLE_1:
                        f.write(f"# {content}\n\n")

                    elif paragraph.type == ParagraphType.TITLE_2:
                        f.write(f"## {content}\n\n")

                    elif paragraph.type == ParagraphType.CODE:
                        f.write("```\n")
                        f.write(paragraph.content)
                        f.write("\n```\n\n")

                    elif paragraph.type == ParagraphType.QUOTE:
                        f.write(f"> {content}\n\n")

                    elif paragraph.type == ParagraphType.IMAGE:
                        meta = paragraph.get_image_metadata()
                        if meta:
                            caption = meta.get('caption', '')
                            path = meta.get('filename', 'image.png')
                            f.write(f"![{caption}]({path})\n\n")

                    elif paragraph.type in (ParagraphType.TABLE, 'table'):
                        table_data, has_header, caption = self._get_table_meta(paragraph)
                        if caption:
                            f.write(f"**Tabela: {caption}**\n\n")
                        if table_data:
                            # Cabeçalho
                            header = table_data[0]
                            f.write('| ' + ' | '.join(str(c) for c in header) + ' |\n')
                            # Separador (negrito se has_header, senão linha comum)
                            if has_header:
                                f.write('| ' + ' | '.join('---' for _ in header) + ' |\n')
                            # Linhas de dados
                            for row in (table_data[1:] if has_header else table_data):
                                f.write('| ' + ' | '.join(str(c) for c in row) + ' |\n')
                            f.write('\n')

                    elif paragraph.type in (ParagraphType.CHART, 'chart'):
                        image_path, title = self._get_chart_meta(paragraph)
                        if title:
                            f.write(f"**Gráfico: {title}**\n\n")
                        if image_path and Path(image_path).exists():
                            filename = Path(image_path).name
                            f.write(f"![{title}]({filename})\n\n")
                        else:
                            f.write(f"*[Gráfico: {title}]*\n\n")

                    elif paragraph.type in (ParagraphType.MAP, 'map'):
                        image_path, title = self._get_map_meta(paragraph)
                        if title:
                            f.write(f"**Mapa: {title}**\n\n")
                        if image_path and Path(image_path).exists():
                            filename = Path(image_path).name
                            f.write(f"![{title}]({filename})\n\n")
                        else:
                            f.write(f"*[Mapa: {title}]*\n\n")

                    else:
                        f.write(f"{content}\n\n")

            return True
        except Exception as e:
            print(f"Error exporting to Markdown: {e}")
            return False

    def _generate_odt_content(self, project: Project) -> str:
        """Generate content.xml for ODT with proper formatting"""
        
        all_footnotes, footnote_map = self._collect_footnotes(project)
        
        grouped_odt = []
        current_paragraph_content = []
        paragraph_starts_with_introduction = False
        last_was_quote = False
        
        for i, paragraph in enumerate(project.paragraphs):
            content = self._format_text_for_odt(paragraph.content)
            
            if paragraph.type == ParagraphType.CODE:
                if current_paragraph_content:
                    combined = " ".join(current_paragraph_content)
                    style = "Introduction" if paragraph_starts_with_introduction else "Normal"
                    grouped_odt.append({'type': 'content', 'content': combined, 'style': style})
                    current_paragraph_content = []
                    paragraph_starts_with_introduction = False
                
                code_content = paragraph.content
                code_content = code_content.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
                code_content = code_content.replace("\n", "<text:line-break/>")
                code_content = code_content.replace("  ", "<text:s text:c=\"2\"/>")
                code_content = code_content.replace("\t", "<text:tab/>")
                
                grouped_odt.append({'type': 'code', 'content': code_content})
                last_was_quote = False

            if paragraph.type == ParagraphType.TITLE_1:
                if current_paragraph_content:
                    combined = " ".join(current_paragraph_content)
                    style = "Introduction" if paragraph_starts_with_introduction else "Normal"
                    grouped_odt.append({'type': 'content', 'content': combined, 'style': style})
                    current_paragraph_content = []
                    paragraph_starts_with_introduction = False
                
                grouped_odt.append({'type': 'title1', 'content': content})
                last_was_quote = False
            
            elif paragraph.type == ParagraphType.TITLE_2:
                if current_paragraph_content:
                    combined = " ".join(current_paragraph_content)
                    style = "Introduction" if paragraph_starts_with_introduction else "Normal"
                    grouped_odt.append({'type': 'content', 'content': combined, 'style': style})
                    current_paragraph_content = []
                    paragraph_starts_with_introduction = False
                
                grouped_odt.append({'type': 'title2', 'content': content})
                last_was_quote = False
            
            elif paragraph.type == ParagraphType.QUOTE:
                if current_paragraph_content:
                    combined = " ".join(current_paragraph_content)
                    style = "Introduction" if paragraph_starts_with_introduction else "Normal"
                    grouped_odt.append({'type': 'content', 'content': combined, 'style': style})
                    current_paragraph_content = []
                    paragraph_starts_with_introduction = False
                
                grouped_odt.append({'type': 'quote', 'content': content})
                last_was_quote = True
                
            elif paragraph.type == ParagraphType.EPIGRAPH:
                if current_paragraph_content:
                    combined = " ".join(current_paragraph_content)
                    style = "Introduction" if paragraph_starts_with_introduction else "Normal"
                    grouped_odt.append({'type': 'content', 'content': combined, 'style': style})
                    current_paragraph_content = []
                    paragraph_starts_with_introduction = False
                
                grouped_odt.append({'type': 'epigraph', 'content': content})
                last_was_quote = True
            
            elif paragraph.type == ParagraphType.IMAGE:
                if current_paragraph_content:
                    combined = " ".join(current_paragraph_content)
                    style = "Introduction" if paragraph_starts_with_introduction else "Normal"
                    grouped_odt.append({'type': 'content', 'content': combined, 'style': style})
                    current_paragraph_content = []
                    paragraph_starts_with_introduction = False
                
                img_metadata = paragraph.get_image_metadata()
                if img_metadata:
                    grouped_odt.append({'type': 'image', 'metadata': img_metadata})
                last_was_quote = False

            elif paragraph.type in (ParagraphType.TABLE, 'table'):
                if current_paragraph_content:
                    combined = " ".join(current_paragraph_content)
                    style = "Introduction" if paragraph_starts_with_introduction else "Normal"
                    grouped_odt.append({'type': 'content', 'content': combined, 'style': style})
                    current_paragraph_content = []
                    paragraph_starts_with_introduction = False
                table_data, has_header, caption = self._get_table_meta(paragraph)
                grouped_odt.append({
                    'type': 'table',
                    'data': table_data,
                    'has_header': has_header,
                    'caption': caption,
                })
                last_was_quote = False

            elif paragraph.type in (ParagraphType.CHART, 'chart'):
                if current_paragraph_content:
                    combined = " ".join(current_paragraph_content)
                    style = "Introduction" if paragraph_starts_with_introduction else "Normal"
                    grouped_odt.append({'type': 'content', 'content': combined, 'style': style})
                    current_paragraph_content = []
                    paragraph_starts_with_introduction = False
                image_path, title = self._get_chart_meta(paragraph)
                grouped_odt.append({
                    'type': 'chart',
                    'image_path': image_path,
                    'title': title,
                })
                last_was_quote = False

            elif paragraph.type in (ParagraphType.MAP, 'map'):
                if current_paragraph_content:
                    combined = " ".join(current_paragraph_content)
                    style = "Introduction" if paragraph_starts_with_introduction else "Normal"
                    grouped_odt.append({'type': 'content', 'content': combined, 'style': style})
                    current_paragraph_content = []
                    paragraph_starts_with_introduction = False
                image_path, title = self._get_map_meta(paragraph)
                grouped_odt.append({
                    'type': 'map',
                    'image_path': image_path,
                    'title': title,
                })
                last_was_quote = False

            elif paragraph.type in [ParagraphType.INTRODUCTION, ParagraphType.ARGUMENT,
                                     ParagraphType.CONCLUSION, ParagraphType.ARGUMENT_RESUMPTION]:
                should_start_new = (
                    paragraph.type == ParagraphType.INTRODUCTION or
                    paragraph.type == ParagraphType.ARGUMENT_RESUMPTION or
                    last_was_quote or
                    not current_paragraph_content
                )

                if should_start_new and current_paragraph_content:
                    combined = " ".join(current_paragraph_content)
                    style = "Introduction" if paragraph_starts_with_introduction else "Normal"
                    grouped_odt.append({'type': 'content', 'content': combined, 'style': style})
                    current_paragraph_content = []
                    paragraph_starts_with_introduction = False
                
                if not current_paragraph_content:
                    if paragraph.type == ParagraphType.INTRODUCTION or paragraph.type == ParagraphType.ARGUMENT_RESUMPTION:
                        paragraph_starts_with_introduction = True
                    elif last_was_quote:
                        paragraph_starts_with_introduction = False
                
                if paragraph.id in footnote_map:
                    for footnote_num in footnote_map[paragraph.id]:
                        footnote_text = all_footnotes[footnote_num - 1]
                        content += f'<text:note text:id="ftn{footnote_num}" text:note-class="footnote"><text:note-citation>{footnote_num}</text:note-citation><text:note-body><text:p text:style-name="Footnote">.{footnote_text}</text:p></text:note-body></text:note>'
                
                current_paragraph_content.append(content.strip())
                
                next_is_new = False
                if i + 1 < len(project.paragraphs):
                    next_p = project.paragraphs[i + 1]
                    if next_p.type in [ParagraphType.INTRODUCTION, ParagraphType.TITLE_1, 
                                      ParagraphType.TITLE_2, ParagraphType.QUOTE]:
                        next_is_new = True
                else:
                    next_is_new = True
                
                if next_is_new:
                    combined = " ".join(current_paragraph_content)
                    style = "Introduction" if paragraph_starts_with_introduction else "Normal"
                    grouped_odt.append({'type': 'content', 'content': combined, 'style': style})
                    current_paragraph_content = []
                    paragraph_starts_with_introduction = False
                
                last_was_quote = False
        
        if current_paragraph_content:
            combined = " ".join(current_paragraph_content)
            style = "Introduction" if paragraph_starts_with_introduction else "Normal"
            grouped_odt.append({'type': 'content', 'content': combined, 'style': style})
        
        serif = self.font_serif
        sans = self.font_sans
        mono = self.font_mono

        content_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
<office:document-content
    xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
    xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0"
    xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"
    xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0"
    xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"
    xmlns:xlink="http://www.w3.org/1999/xlink"
    xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"
    xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0">

<office:font-face-decls>
    <style:font-face style:name="{serif}"
        svg:font-family="&apos;{serif}&apos;"
        style:font-family-generic="roman" style:font-pitch="variable"/>
    <style:font-face style:name="{sans}"
        svg:font-family="&apos;{sans}&apos;"
        style:font-family-generic="swiss" style:font-pitch="variable"/>
    <style:font-face style:name="{mono}"
        svg:font-family="&apos;{mono}&apos;"
        style:font-family-generic="modern" style:font-pitch="fixed"/>
</office:font-face-decls>

<office:automatic-styles>
    <style:style style:name="T_Bold" style:family="text">
      <style:text-properties fo:font-weight="bold"
          style:font-weight-asian="bold" style:font-weight-complex="bold"/>
    </style:style>
    <style:style style:name="T_Italic" style:family="text">
      <style:text-properties fo:font-style="italic"
          style:font-style-asian="italic" style:font-style-complex="italic"/>
    </style:style>
    <style:style style:name="T_Underline" style:family="text">
      <style:text-properties style:text-underline-style="solid"
          style:text-underline-width="auto" style:text-underline-color="font-color"/>
    </style:style>

    <style:style style:name="TacTable" style:family="table">
      <style:table-properties style:width="15cm" fo:margin-top="0.3cm" fo:margin-bottom="0.3cm"
          table:align="center"/>
    </style:style>
    <style:style style:name="TacTableCol" style:family="table-column">
      <style:table-column-properties style:use-optimal-column-width="true"/>
    </style:style>
    <style:style style:name="TacTableCell" style:family="table-cell">
      <style:table-cell-properties fo:padding="0.15cm" fo:border="0.05pt solid #000000"/>
    </style:style>
    <style:style style:name="TacTableHeaderCell" style:family="table-cell">
      <style:table-cell-properties fo:padding="0.15cm" fo:border="0.05pt solid #000000"
          fo:background-color="#e8e8e8"/>
    </style:style>
</office:automatic-styles>

<office:body>
<office:text>'''

        content_xml += f'<text:p text:style-name="Title">{project.name}</text:p>\n'
        
        for item in grouped_odt:
            if item['type'] == 'title1':
                content_xml += f'<text:p text:style-name="Heading_20_1">{item["content"]}</text:p>\n'
            elif item['type'] == 'code':
                content_xml += f'<text:p text:style-name="CodeBlock">{item["content"]}</text:p>\n'
            elif item['type'] == 'title2':
                content_xml += f'<text:p text:style-name="Heading_20_2">{item["content"]}</text:p>\n'
            elif item['type'] == 'quote':
                content_xml += f'<text:p text:style-name="Quote">{item["content"]}</text:p>\n'
            elif item['type'] == 'epigraph':
                content_xml += f'<text:p text:style-name="Epigraph">{item["content"]}</text:p>\n'
            elif item['type'] == 'image':
                metadata = item['metadata']
                filename = metadata.get('filename', 'image')
                original_size = metadata.get('original_size', (800, 600))
                width_percent = metadata.get('width_percent', 80.0)
                alignment = metadata.get('alignment', 'center')
                caption = metadata.get('caption', '')
                
                usable_page_width_cm = 15.0
                img_width_cm = usable_page_width_cm * (width_percent / 100.0)
                
                aspect_ratio = original_size[1] / original_size[0]
                img_height_cm = img_width_cm * aspect_ratio
                
                if alignment == 'center':
                    style_name = 'GraphicsCenter'
                elif alignment == 'right':
                    style_name = 'GraphicsRight'
                else:
                    style_name = 'GraphicsLeft'
                
                content_xml += f'''<text:p text:style-name="Normal">
  <draw:frame draw:style-name="{style_name}" draw:name="{filename}" text:anchor-type="paragraph" 
              svg:width="{img_width_cm:.2f}cm" svg:height="{img_height_cm:.2f}cm" 
              draw:z-index="0">
    <draw:image xlink:href="Pictures/{filename}" xlink:type="simple" xlink:show="embed" xlink:actuate="onLoad"/>
  </draw:frame>
</text:p>\n'''
                
                if caption:
                    content_xml += f'<text:p text:style-name="ImageCaption">{caption}</text:p>\n'
            # TABLE
            elif item['type'] == 'table':
                table_data  = item['data']
                has_header  = item['has_header']
                caption     = item['caption']
                if caption:
                    content_xml += f'<text:p text:style-name="ImageCaption">Tabela: {caption}</text:p>\n'
                if table_data:
                    num_cols = max(len(row) for row in table_data)
                    col_width_cm = round(15.0 / num_cols, 2) if num_cols else 15.0
                    content_xml += '<table:table table:name="TacTable" table:style-name="TacTable">\n'
                    for _ in range(num_cols):
                        content_xml += f'  <table:table-column table:style-name="TacTableCol"/>\n'
                    for r_idx, row in enumerate(table_data):
                        content_xml += '  <table:table-row>\n'
                        cell_style = "TacTableHeaderCell" if (has_header and r_idx == 0) else "TacTableCell"
                        for cell in row:
                            cell_escaped = str(cell).replace('&','&amp;').replace('<','&lt;').replace('>','&gt;')
                            content_xml += (
                                f'    <table:table-cell table:style-name="{cell_style}"'
                                f' office:value-type="string">'
                                f'<text:p>{cell_escaped}</text:p>'
                                f'</table:table-cell>\n'
                            )
                        content_xml += '  </table:table-row>\n'
                    content_xml += '</table:table>\n'

            # CHART
            elif item['type'] == 'chart':
                from pathlib import Path as _Path
                image_path = item['image_path']
                title      = item['title']
                if title:
                    content_xml += f'<text:p text:style-name="ImageCaption">Gráfico: {title}</text:p>\n'
                if image_path and _Path(image_path).exists():
                    filename = _Path(image_path).name
                    # Largura padrão 80% da página para gráficos
                    img_width_cm  = 12.0
                    img_height_cm = 8.0
                    try:
                        from PIL import Image as _PILImage
                        with _PILImage.open(image_path) as im:
                            w, h = im.size
                            img_height_cm = round(img_width_cm * h / w, 2)
                    except Exception:
                        pass
                    content_xml += f'''<text:p text:style-name="Normal">
  <draw:frame draw:style-name="GraphicsCenter" draw:name="{filename}" text:anchor-type="paragraph"
              svg:width="{img_width_cm:.2f}cm" svg:height="{img_height_cm:.2f}cm"
              draw:z-index="0">
    <draw:image xlink:href="Pictures/{filename}" xlink:type="simple" xlink:show="embed" xlink:actuate="onLoad"/>
  </draw:frame>
</text:p>\n'''
                    
            # MAP
            elif item['type'] == 'map':
                from pathlib import Path as _Path2
                image_path = item['image_path']
                title      = item['title']
                if title:
                    content_xml += f'<text:p text:style-name="ImageCaption">Mapa: {title}</text:p>\n'
                if image_path and _Path2(image_path).exists():
                    filename = _Path2(image_path).name
                    img_width_cm  = 14.0
                    img_height_cm = 10.0
                    try:
                        from PIL import Image as _PILImage2
                        with _PILImage2.open(image_path) as im:
                            w, h = im.size
                            img_height_cm = round(img_width_cm * h / w, 2)
                    except Exception:
                        pass
                    content_xml += f'''<text:p text:style-name="Normal">
  <draw:frame draw:style-name="GraphicsCenter" draw:name="{filename}" text:anchor-type="paragraph"
              svg:width="{img_width_cm:.2f}cm" svg:height="{img_height_cm:.2f}cm"
              draw:z-index="0">
    <draw:image xlink:href="Pictures/{filename}" xlink:type="simple" xlink:show="embed" xlink:actuate="onLoad"/>
  </draw:frame>
</text:p>\n'''

            elif item['type'] == 'content':
                content_xml += f'<text:p text:style-name="{item["style"]}">{item["content"]}</text:p>\n'
        
        content_xml += '''</office:text>
</office:body>
</office:document-content>'''
        
        return content_xml

    def _create_manifest(self, file_path: Path, image_files: list = None):
        """Create manifest.xml for ODT"""
        manifest_xml = '''<?xml version="1.0" encoding="UTF-8"?>
<manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"
    manifest:version="1.3">
  <manifest:file-entry manifest:full-path="/" manifest:version="1.3" manifest:media-type="application/vnd.oasis.opendocument.text"/>
  <manifest:file-entry manifest:full-path="content.xml" manifest:media-type="text/xml"/>
  <manifest:file-entry manifest:full-path="styles.xml" manifest:media-type="text/xml"/>
  <manifest:file-entry manifest:full-path="meta.xml" manifest:media-type="text/xml"/>
  <manifest:file-entry manifest:full-path="settings.xml" manifest:media-type="text/xml"/>'''

        if image_files:
            for img_file in image_files:
                ext = Path(img_file).suffix.lower()
                mime_types = {
                    '.png': 'image/png',
                    '.jpg': 'image/jpeg',
                    '.jpeg': 'image/jpeg',
                    '.webp': 'image/webp'
                }
                mime_type = mime_types.get(ext, 'image/png')
                manifest_xml += f'\n  <manifest:file-entry manifest:full-path="Pictures/{img_file}" manifest:media-type="{mime_type}"/>'

        manifest_xml += '\n</manifest:manifest>'

        with open(file_path, 'w', encoding='utf-8') as f:
            f.write(manifest_xml)

    def _create_styles(self, file_path: Path):
        """Create styles.xml for ODT — MS Office compatible"""
        serif = self.font_serif
        sans = self.font_sans
        mono = self.font_mono

        styles_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
<office:document-styles
    xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
    xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0"
    xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"
    xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"
    xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0">

<office:font-face-decls>
    <style:font-face style:name="{serif}"
        svg:font-family="&apos;{serif}&apos;"
        style:font-family-generic="roman" style:font-pitch="variable"/>
    <style:font-face style:name="{sans}"
        svg:font-family="&apos;{sans}&apos;"
        style:font-family-generic="swiss" style:font-pitch="variable"/>
    <style:font-face style:name="{mono}"
        svg:font-family="&apos;{mono}&apos;"
        style:font-family-generic="modern" style:font-pitch="fixed"/>
</office:font-face-decls>

<office:styles>

  <!-- Default paragraph style -->
  <style:default-style style:family="paragraph">
    <style:paragraph-properties
        fo:text-align="justify"
        fo:margin-top="0cm" fo:margin-bottom="0cm"
        fo:line-height="150%"
        fo:orphans="2" fo:widows="2"
        fo:hyphenation-ladder-count="no-limit"
        style:text-autospace="ideograph-alpha"
        style:punctuation-wrap="hanging"
        style:writing-mode="page"/>
    <style:text-properties
        style:font-name="{serif}"
        fo:font-size="12pt"
        fo:language="pt" fo:country="BR"
        style:font-name-asian="{serif}"
        style:font-size-asian="12pt"
        style:font-name-complex="{serif}"
        style:font-size-complex="12pt"/>
  </style:default-style>

  <!-- Default text (character) style -->
  <style:default-style style:family="text">
    <style:text-properties
        style:font-name="{serif}"
        fo:font-size="12pt"
        style:font-name-asian="{serif}"
        style:font-size-asian="12pt"
        style:font-name-complex="{serif}"
        style:font-size-complex="12pt"/>
  </style:default-style>

  <!-- Title -->
  <style:style style:name="Title" style:family="paragraph" style:class="chapter">
    <style:paragraph-properties
        fo:text-align="center"
        fo:margin-top="0cm" fo:margin-bottom="0.5cm"
        fo:line-height="150%"/>
    <style:text-properties
        style:font-name="{sans}" fo:font-size="18pt" fo:font-weight="bold"
        style:font-name-asian="{sans}" style:font-size-asian="18pt" style:font-weight-asian="bold"
        style:font-name-complex="{sans}" style:font-size-complex="18pt" style:font-weight-complex="bold"/>
  </style:style>

  <!-- Heading 1 -->
  <style:style style:name="Heading_20_1" style:display-name="Heading 1"
      style:family="paragraph" style:default-outline-level="1" style:class="text">
    <style:paragraph-properties
        fo:text-align="start"
        fo:margin-top="0.5cm" fo:margin-bottom="0.3cm"
        fo:line-height="150%"
        fo:keep-with-next="always"/>
    <style:text-properties
        style:font-name="{sans}" fo:font-size="16pt" fo:font-weight="bold"
        style:font-name-asian="{sans}" style:font-size-asian="16pt" style:font-weight-asian="bold"
        style:font-name-complex="{sans}" style:font-size-complex="16pt" style:font-weight-complex="bold"/>
  </style:style>

  <!-- Heading 2 -->
  <style:style style:name="Heading_20_2" style:display-name="Heading 2"
      style:family="paragraph" style:default-outline-level="2" style:class="text">
    <style:paragraph-properties
        fo:text-align="start"
        fo:margin-top="0.4cm" fo:margin-bottom="0.2cm"
        fo:line-height="150%"
        fo:keep-with-next="always"/>
    <style:text-properties
        style:font-name="{sans}" fo:font-size="14pt" fo:font-weight="bold"
        style:font-name-asian="{sans}" style:font-size-asian="14pt" style:font-weight-asian="bold"
        style:font-name-complex="{sans}" style:font-size-complex="14pt" style:font-weight-complex="bold"/>
  </style:style>

  <!-- Introduction (with first-line indent) -->
  <style:style style:name="Introduction" style:family="paragraph">
    <style:paragraph-properties
        fo:text-align="justify"
        fo:text-indent="1.5cm"
        fo:margin-top="0cm" fo:margin-bottom="0cm"
        fo:line-height="150%"/>
    <style:text-properties
        style:font-name="{serif}" fo:font-size="12pt"
        style:font-name-asian="{serif}" style:font-size-asian="12pt"
        style:font-name-complex="{serif}" style:font-size-complex="12pt"/>
  </style:style>

  <!-- Normal body text -->
  <style:style style:name="Normal" style:family="paragraph">
    <style:paragraph-properties
        fo:text-align="justify"
        fo:margin-top="0cm" fo:margin-bottom="0cm"
        fo:line-height="150%"/>
    <style:text-properties
        style:font-name="{serif}" fo:font-size="12pt"
        style:font-name-asian="{serif}" style:font-size-asian="12pt"
        style:font-name-complex="{serif}" style:font-size-complex="12pt"/>
  </style:style>

  <!-- Quote (ABNT: 10pt, 4cm left, single-spaced) -->
  <style:style style:name="Quote" style:family="paragraph">
    <style:paragraph-properties
        fo:text-align="justify"
        fo:margin-left="4cm"
        fo:margin-top="0.3cm" fo:margin-bottom="0.3cm"
        fo:line-height="100%"/>
    <style:text-properties
        style:font-name="{serif}" fo:font-size="10pt"
        style:font-name-asian="{serif}" style:font-size-asian="10pt"
        style:font-name-complex="{serif}" style:font-size-complex="10pt"/>
  </style:style>

  <!-- Epigraph -->
  <style:style style:name="Epigraph" style:family="paragraph">
    <style:paragraph-properties
        fo:text-align="end"
        fo:margin-left="7.5cm"
        fo:margin-top="0cm" fo:margin-bottom="0.3cm"
        fo:line-height="150%"/>
    <style:text-properties
        style:font-name="{serif}" fo:font-size="12pt" fo:font-style="italic"
        style:font-name-asian="{serif}" style:font-size-asian="12pt" style:font-style-asian="italic"
        style:font-name-complex="{serif}" style:font-size-complex="12pt" style:font-style-complex="italic"/>
  </style:style>

  <!-- Footnote -->
  <style:style style:name="Footnote" style:family="paragraph">
    <style:paragraph-properties
        fo:text-align="justify"
        fo:margin-top="0cm" fo:margin-bottom="0.2cm"
        fo:line-height="100%"/>
    <style:text-properties
        style:font-name="{serif}" fo:font-size="10pt"
        style:font-name-asian="{serif}" style:font-size-asian="10pt"
        style:font-name-complex="{serif}" style:font-size-complex="10pt"/>
  </style:style>

  <!-- Image Caption -->
  <style:style style:name="ImageCaption" style:family="paragraph">
    <style:paragraph-properties
        fo:text-align="center"
        fo:margin-top="0.2cm" fo:margin-bottom="0.5cm"/>
    <style:text-properties
        style:font-name="{serif}" fo:font-size="10pt" fo:font-style="italic"
        style:font-name-asian="{serif}" style:font-size-asian="10pt"
        style:font-name-complex="{serif}" style:font-size-complex="10pt"/>
  </style:style>

  <!-- Code Block -->
  <style:style style:name="CodeBlock" style:family="paragraph">
    <style:paragraph-properties
        fo:text-align="start"
        fo:background-color="#f5f5f5"
        fo:border="0.06pt solid #cccccc"
        fo:padding="0.2cm"
        fo:margin-top="0.2cm" fo:margin-bottom="0.3cm"
        fo:line-height="100%"/>
    <style:text-properties
        style:font-name="{mono}" fo:font-size="10pt"
        style:font-name-asian="{mono}" style:font-size-asian="10pt"
        style:font-name-complex="{mono}" style:font-size-complex="10pt"/>
  </style:style>

  <!-- Graphics styles -->
  <style:style style:name="GraphicsLeft" style:family="graphic">
    <style:graphic-properties style:run-through="foreground" style:wrap="none"
        style:horizontal-pos="left" style:horizontal-rel="paragraph"
        style:vertical-pos="top" style:vertical-rel="paragraph"/>
  </style:style>

  <style:style style:name="GraphicsCenter" style:family="graphic">
    <style:graphic-properties style:run-through="foreground" style:wrap="none"
        style:horizontal-pos="center" style:horizontal-rel="paragraph"
        style:vertical-pos="top" style:vertical-rel="paragraph"/>
  </style:style>

  <style:style style:name="GraphicsRight" style:family="graphic">
    <style:graphic-properties style:run-through="foreground" style:wrap="none"
        style:horizontal-pos="right" style:horizontal-rel="paragraph"
        style:vertical-pos="top" style:vertical-rel="paragraph"/>
  </style:style>

</office:styles>

<!-- Page layout -->
<office:automatic-styles>
  <style:page-layout style:name="pm1">
    <style:page-layout-properties
        fo:page-width="21cm" fo:page-height="29.7cm"
        fo:margin-top="3cm" fo:margin-bottom="2cm"
        fo:margin-left="3cm" fo:margin-right="2cm"
        style:print-orientation="portrait"/>
  </style:page-layout>
</office:automatic-styles>

<office:master-styles>
  <style:master-page style:name="Standard" style:page-layout-name="pm1"/>
</office:master-styles>

</office:document-styles>'''

        with open(file_path, 'w', encoding='utf-8') as f:
            f.write(styles_xml)

    def _create_settings(self, file_path: Path):
        """Create settings.xml — required by MS Office for proper rendering"""
        settings_xml = '''<?xml version="1.0" encoding="UTF-8"?>
<office:document-settings
    xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
    xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0"
    office:version="1.3">
<office:settings>
  <config:config-item-set config:name="ooo:configuration-settings">
    <config:config-item config:name="UseFormerLineSpacing" config:type="boolean">false</config:config-item>
    <config:config-item config:name="TabsRelativeToIndent" config:type="boolean">true</config:config-item>
    <config:config-item config:name="AddParaTableSpacingAtStart" config:type="boolean">true</config:config-item>
    <config:config-item config:name="UseFormerObjectPositioning" config:type="boolean">false</config:config-item>
  </config:config-item-set>
</office:settings>
</office:document-settings>'''

        with open(file_path, 'w', encoding='utf-8') as f:
            f.write(settings_xml)

    def _create_meta(self, file_path: Path, project: Project):
        """Create meta.xml for ODT"""
        meta_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
<office:document-meta xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
                     xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0"
                     xmlns:dc="http://purl.org/dc/elements/1.1/">
<office:meta>
  <meta:generator>TAC - Continuous Argumentation Technique</meta:generator>
  <dc:title>{project.name}</dc:title>
  <dc:creator>{project.metadata.get('author', '')}</dc:creator>
  <dc:description>{project.metadata.get('description', '')}</dc:description>
  <meta:creation-date>{project.created_at.isoformat()}</meta:creation-date>
  <dc:date>{project.modified_at.isoformat()}</dc:date>
</office:meta>
</office:document-meta>'''
        
        with open(file_path, 'w', encoding='utf-8') as f:
            f.write(meta_xml)

    def _export_pdf(self, project: Project, file_path: str) -> bool:
        """Export to PDF format"""
        try:
            file_path_obj = Path(file_path)
            file_path_obj.parent.mkdir(parents=True, exist_ok=True)
            
            doc = SimpleDocTemplate(
                file_path,
                pagesize=A4,
                rightMargin=3*cm,
                leftMargin=3*cm,
                topMargin=2.5*cm,
                bottomMargin=2.5*cm
            )
            
            styles = getSampleStyleSheet()

            # Fontes configuradas por plataforma
            font_roman = self.pdf_font_roman
            font_bold = self.pdf_font_bold
            font_italic = self.pdf_font_italic
            
            title_style = ParagraphStyle(
                'CustomTitle',
                parent=styles['Title'],
                fontSize=18,
                spaceAfter=30,
                alignment=TA_CENTER,
                fontName=font_bold
            )

            title1_style = ParagraphStyle(
                'CustomTitle1',
                parent=styles['Heading1'],
                fontSize=16,
                spaceBefore=24,
                spaceAfter=12,
                leftIndent=0,
                fontName=font_bold
            )

            title2_style = ParagraphStyle(
                'CustomTitle2',
                parent=styles['Heading2'],
                fontSize=14,
                spaceBefore=18,
                spaceAfter=9,
                leftIndent=0,
                fontName=font_bold
            )

            introduction_style = ParagraphStyle(
                'Introduction',
                parent=styles['Normal'],
                fontSize=12,
                leading=18,
                firstLineIndent=1.5*cm,
                spaceBefore=12,
                spaceAfter=12,
                alignment=TA_JUSTIFY,
                fontName=font_roman
            )

            normal_style = ParagraphStyle(
                'CustomNormal',
                parent=styles['Normal'],
                fontSize=12,
                leading=18,
                spaceBefore=12,
                spaceAfter=12,
                alignment=TA_JUSTIFY,
                fontName=font_roman
            )

            quote_style = ParagraphStyle(
                'Quote',
                parent=styles['Normal'],
                fontSize=10,
                leading=12,
                leftIndent=4*cm,
                spaceBefore=12,
                spaceAfter=12,
                fontName=font_roman,
                alignment=TA_JUSTIFY
            )
            
            epigraph_style = ParagraphStyle(
                'Epigraph',
                parent=styles['Normal'],
                fontSize=12,
                leading=18,
                leftIndent=7.5*cm,
                spaceBefore=12,
                spaceAfter=12,
                fontName=font_italic,
                alignment=TA_RIGHT
            )
            
            footnote_style = ParagraphStyle(
                'Footnote',
                parent=styles['Normal'],
                fontSize=9,
                leading=11,
                spaceBefore=6,
                spaceAfter=6,
                fontName=font_roman,
                alignment=TA_JUSTIFY
            )

            caption_style = ParagraphStyle(
                'Caption',
                parent=normal_style,
                fontSize=10,
                alignment=TA_CENTER,
                fontName=font_italic
            )
            
            all_footnotes, footnote_map = self._collect_footnotes(project)
            
            grouped_pdf = []
            current_paragraph_content = []
            current_style = None
            paragraph_starts_with_introduction = False
            last_was_quote = False
            
            for i, paragraph in enumerate(project.paragraphs):
                content = self._format_text_for_pdf(paragraph.content)
                
                if paragraph.type == ParagraphType.TITLE_1:
                    if current_paragraph_content:
                        combined = " ".join(current_paragraph_content)
                        grouped_pdf.append({'type': 'content', 'content': combined, 'style': current_style})
                        current_paragraph_content = []
                        current_style = None
                        paragraph_starts_with_introduction = False
                    
                    grouped_pdf.append({'type': 'title1', 'content': content})
                    last_was_quote = False
                
                elif paragraph.type == ParagraphType.TITLE_2:
                    if current_paragraph_content:
                        combined = " ".join(current_paragraph_content)
                        grouped_pdf.append({'type': 'content', 'content': combined, 'style': current_style})
                        current_paragraph_content = []
                        current_style = None
                        paragraph_starts_with_introduction = False
                    
                    grouped_pdf.append({'type': 'title2', 'content': content})
                    last_was_quote = False
                
                elif paragraph.type == ParagraphType.QUOTE:
                    if current_paragraph_content:
                        combined = " ".join(current_paragraph_content)
                        grouped_pdf.append({'type': 'content', 'content': combined, 'style': current_style})
                        current_paragraph_content = []
                        current_style = None
                        paragraph_starts_with_introduction = False
                    
                    grouped_pdf.append({'type': 'quote', 'content': content})
                    last_was_quote = True
                
                elif paragraph.type == ParagraphType.EPIGRAPH:
                    if current_paragraph_content:
                        combined = " ".join(current_paragraph_content)
                        grouped_pdf.append({'type': 'content', 'content': combined, 'style': current_style})
                        current_paragraph_content = []
                        current_style = None
                        paragraph_starts_with_introduction = False
                    
                    grouped_pdf.append({'type': 'epigraph', 'content': content})
                    last_was_quote = True
                
                elif paragraph.type == ParagraphType.IMAGE:
                    if current_paragraph_content:
                        combined = " ".join(current_paragraph_content)
                        grouped_pdf.append({'type': 'content', 'content': combined, 'style': current_style})
                        current_paragraph_content = []
                        current_style = None
                        paragraph_starts_with_introduction = False
                    
                    img_metadata = paragraph.get_image_metadata()
                    if img_metadata:
                        grouped_pdf.append({'type': 'image', 'metadata': img_metadata})
                    last_was_quote = False

                elif paragraph.type in (ParagraphType.TABLE, 'table'):
                    if current_paragraph_content:
                        combined = " ".join(current_paragraph_content)
                        grouped_pdf.append({'type': 'content', 'content': combined, 'style': current_style})
                        current_paragraph_content = []
                        current_style = None
                        paragraph_starts_with_introduction = False
                    table_data, has_header, caption = self._get_table_meta(paragraph)
                    grouped_pdf.append({
                        'type': 'table',
                        'data': table_data,
                        'has_header': has_header,
                        'caption': caption,
                    })
                    last_was_quote = False

                elif paragraph.type in (ParagraphType.CHART, 'chart'):
                    if current_paragraph_content:
                        combined = " ".join(current_paragraph_content)
                        grouped_pdf.append({'type': 'content', 'content': combined, 'style': current_style})
                        current_paragraph_content = []
                        current_style = None
                        paragraph_starts_with_introduction = False
                    image_path, title = self._get_chart_meta(paragraph)
                    grouped_pdf.append({'type': 'chart', 'image_path': image_path, 'title': title})
                    last_was_quote = False

                elif paragraph.type in (ParagraphType.MAP, 'map'):
                    if current_paragraph_content:
                        combined = " ".join(current_paragraph_content)
                        grouped_pdf.append({'type': 'content', 'content': combined, 'style': current_style})
                        current_paragraph_content = []
                        current_style = None
                        paragraph_starts_with_introduction = False
                    image_path, title = self._get_map_meta(paragraph)
                    grouped_pdf.append({'type': 'map', 'image_path': image_path, 'title': title})
                    last_was_quote = False
                
                elif paragraph.type in [ParagraphType.INTRODUCTION, ParagraphType.ARGUMENT, ParagraphType.CONCLUSION, ParagraphType.ARGUMENT_RESUMPTION]:
                    should_start_new = (
                        paragraph.type == ParagraphType.INTRODUCTION or
                        paragraph.type == ParagraphType.ARGUMENT_RESUMPTION or
                        last_was_quote or
                        not current_paragraph_content
                    )
                    
                    if should_start_new and current_paragraph_content:
                        combined = " ".join(current_paragraph_content)
                        grouped_pdf.append({'type': 'content', 'content': combined, 'style': current_style})
                        current_paragraph_content = []
                        current_style = None
                        paragraph_starts_with_introduction = False
                    
                    if not current_paragraph_content:
                        if paragraph.type == ParagraphType.INTRODUCTION or paragraph.type == ParagraphType.ARGUMENT_RESUMPTION:
                            paragraph_starts_with_introduction = True
                            current_style = introduction_style
                        elif last_was_quote:
                            paragraph_starts_with_introduction = False
                            current_style = normal_style
                        else:
                            if current_style is None:
                                current_style = normal_style
                    
                    if paragraph.id in footnote_map:
                        for footnote_num in footnote_map[paragraph.id]:
                            content += f"<sup>{footnote_num}</sup>"
                    
                    current_paragraph_content.append(content)
                    
                    next_is_new = False
                    if i + 1 < len(project.paragraphs):
                        next_p = project.paragraphs[i + 1]
                        if next_p.type in [ParagraphType.INTRODUCTION, ParagraphType.TITLE_1, 
                                          ParagraphType.TITLE_2, ParagraphType.QUOTE]:
                            next_is_new = True
                    else:
                        next_is_new = True
                    
                    if next_is_new:
                        combined = " ".join(current_paragraph_content)
                        grouped_pdf.append({'type': 'content', 'content': combined, 'style': current_style})
                        current_paragraph_content = []
                        current_style = None
                        paragraph_starts_with_introduction = False
                    
                    last_was_quote = False
            
            if current_paragraph_content:
                combined = " ".join(current_paragraph_content)
                grouped_pdf.append({'type': 'content', 'content': combined, 'style': current_style})
            
            story = []
            story.append(RLParagraph(project.name, title_style))
            story.append(Spacer(1, 20))

            usable_width = 15.0 * cm   # largura útil entre margens
            
            for item in grouped_pdf:
                if item['type'] == 'title1':
                    story.append(RLParagraph(item['content'], title1_style))
                elif item['type'] == 'title2':
                    story.append(RLParagraph(item['content'], title2_style))
                elif item['type'] == 'quote':
                    story.append(RLParagraph(item['content'], quote_style))
                elif item['type'] == 'epigraph':
                    story.append(RLParagraph(item['content'], epigraph_style))
                elif item['type'] == 'image':
                    try:
                        metadata = item['metadata']
                        img_path = Path(metadata['path'])
                        
                        if img_path.exists():
                            original_size = metadata.get('original_size', (800, 600))
                            width_percent = metadata.get('width_percent', 80.0)
                            alignment = metadata.get('alignment', 'center')
                            
                            usable_page_width = 15.0
                            img_width_cm = usable_page_width * (width_percent / 100.0)
                            
                            aspect_ratio = original_size[1] / original_size[0]
                            img_height_cm = img_width_cm * aspect_ratio
                            
                            pdf_img = RLImage(str(img_path), width=img_width_cm*cm, height=img_height_cm*cm)
                            
                            if alignment == 'center':
                                pdf_img.hAlign = 'CENTER'
                            elif alignment == 'right':
                                pdf_img.hAlign = 'RIGHT'
                            else:
                                pdf_img.hAlign = 'LEFT'
                            
                            story.append(pdf_img)
                            story.append(Spacer(1, 12))
                            
                            caption = metadata.get('caption', '')
                            if caption:
                                if alignment == 'left':
                                    caption_alignment = TA_LEFT
                                elif alignment == 'right':
                                    caption_alignment = TA_RIGHT
                                else:
                                    caption_alignment = TA_CENTER
                                
                                caption_style = ParagraphStyle(
                                    'ImageCaption',
                                    parent=normal_style,
                                    fontSize=10,
                                    alignment=caption_alignment,
                                    fontName=font_italic
                                )
                                story.append(RLParagraph(caption, caption_style))
                                story.append(Spacer(1, 12))
                    except Exception as e:
                        print(_("Erro ao adicionar imagem ao PDF: {}").format(e))
                        story.append(RLParagraph(f"[Image: {metadata.get('filename', 'image')}]", normal_style))
                
                # TABLE
                elif item['type'] == 'table':
                    table_data = item['data']
                    has_header = item['has_header']
                    caption    = item['caption']
                    if table_data:
                        cell_normal = ParagraphStyle('TCellN', parent=styles['Normal'],
                                                 fontSize=10, leading=12, fontName=font_roman)
                        cell_header = ParagraphStyle('TCellH', parent=styles['Normal'],
                                                 fontSize=10, leading=12, fontName=font_bold)
                        rl_data = []
                        for r_idx, row in enumerate(table_data):
                            style = cell_header if (has_header and r_idx == 0) else cell_normal
                            rl_data.append([RLParagraph(str(c), style) for c in row])

                        num_cols = max(len(r) for r in table_data)
                        col_width = usable_width / num_cols

                        ts = TableStyle([
                            ('GRID',        (0, 0), (-1, -1), 0.5, colors.grey),
                            ('VALIGN',      (0, 0), (-1, -1), 'MIDDLE'),
                            ('TOPPADDING',  (0, 0), (-1, -1), 4),
                            ('BOTTOMPADDING',(0,0), (-1, -1), 4),
                            ('LEFTPADDING', (0, 0), (-1, -1), 6),
                            ('RIGHTPADDING',(0, 0), (-1, -1), 6),
                        ])
                        if has_header:
                            ts.add('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#E8E8E8'))

                        tbl = RLTable(rl_data, colWidths=[col_width] * num_cols, repeatRows=1 if has_header else 0)
                        tbl.setStyle(ts)
                        story.append(Spacer(1, 6))
                        story.append(tbl)
                        story.append(Spacer(1, 6))
                        if caption:
                            story.append(RLParagraph(f"Tabela: {caption}", caption_style))
                            story.append(Spacer(1, 6))

                # CHART
                elif item['type'] == 'chart':
                    image_path = item['image_path']
                    title      = item['title']
                    if image_path and Path(image_path).exists():
                        try:
                            img_width_cm  = 12.0
                            img_height_cm = 8.0
                            try:
                                from PIL import Image as _PIL
                                with _PIL.open(image_path) as im:
                                    w, h = im.size
                                    img_height_cm = round(img_width_cm * h / w, 2)
                            except Exception:
                                pass
                            pdf_img = RLImage(image_path, width=img_width_cm*cm, height=img_height_cm*cm)
                            pdf_img.hAlign = 'CENTER'
                            story.append(Spacer(1, 6))
                            story.append(pdf_img)
                            story.append(Spacer(1, 6))
                        except Exception as e:
                            print(_("Erro ao adicionar gráfico ao PDF: {}").format(e))
                            story.append(RLParagraph(f"[Gráfico: {title}]", normal_style))
                    else:
                        story.append(RLParagraph(f"[Gráfico: {title}]", normal_style))
                    if title:
                        story.append(RLParagraph(f"Gráfico: {title}", caption_style))
                        story.append(Spacer(1, 6))

                # MAP
                elif item['type'] == 'map':
                    image_path = item['image_path']
                    title      = item['title']
                    if image_path and Path(image_path).exists():
                        try:
                            img_width_cm  = 14.0
                            img_height_cm = 10.0
                            try:
                                from PIL import Image as _PIL2
                                with _PIL2.open(image_path) as im:
                                    w, h = im.size
                                    img_height_cm = round(img_width_cm * h / w, 2)
                            except Exception:
                                pass
                            pdf_img = RLImage(image_path, width=img_width_cm*cm, height=img_height_cm*cm)
                            pdf_img.hAlign = 'CENTER'
                            story.append(Spacer(1, 6))
                            story.append(pdf_img)
                            story.append(Spacer(1, 6))
                        except Exception as e:
                            print(_("Erro ao adicionar mapa ao PDF: {}").format(e))
                            story.append(RLParagraph(f"[Mapa: {title}]", normal_style))
                    else:
                        story.append(RLParagraph(f"[Mapa: {title}]", normal_style))
                    if title:
                        story.append(RLParagraph(f"Mapa: {title}", caption_style))
                        story.append(Spacer(1, 6))

                elif item['type'] == 'content':
                    story.append(RLParagraph(item['content'], item['style']))
            
            if all_footnotes:
                story.append(Spacer(1, 20))
                story.append(RLParagraph(_("Notas de rodapé:"), title2_style))
                for i, footnote_text in enumerate(all_footnotes):
                    formatted_footnote = self._format_text_for_pdf(footnote_text)
                    footnote_content = f"{i + 1}. {formatted_footnote}"
                    story.append(RLParagraph(footnote_content, footnote_style))
            
            doc.build(story)
            return True
            
        except OSError as e:
            print(_("Erro de arquivo ao exportar para PDF: {}").format(e))
            return False
        except Exception as e:
            print(_("Erro inesperado ao exportar para PDF: {}: {}").format(type(e).__name__, e))
            import traceback
            traceback.print_exc()
            return False

    def _export_latex(self, project: Project, file_path: str) -> bool:
        """Export for LaTeX format (.tex)"""
        try:
            file_path_obj = Path(file_path)
            file_path_obj.parent.mkdir(parents=True, exist_ok=True)

            geometry_options = {"tmargin": "3cm", "lmargin": "3cm", "rmargin": "2cm", "bmargin": "2cm"}
            doc = Document(
                documentclass='article',
                document_options=['12pt', 'a4paper'], 
                geometry_options=geometry_options
            )

            doc.packages.append(Package('babel', options=['brazilian'])) 
            doc.packages.append(Package('inputenc', options=['utf8']))
            doc.packages.append(Package('fontenc', options=['T1']))
            doc.packages.append(Package('graphicx'))
            doc.packages.append(Package('amsmath'))
            
            doc.packages.append(Package('indentfirst'))
            doc.packages.append(Package('setspace'))
            doc.packages.append(Package('listings'))
            doc.packages.append(Package('booktabs'))
            doc.packages.append(Package('array')) 
            
            doc.preamble.append(NoEscape(r'\lstset{basicstyle=\ttfamily\footnotesize, breaklines=true, frame=single}'))

            doc.preamble.append(NoEscape(r'\setlength{\parindent}{1.25cm}'))
            doc.preamble.append(Command('onehalfspacing'))

            doc.preamble.append(NoEscape(r'''
\newenvironment{citacao}
  {\begin{list}{}{\setlength{\leftmargin}{4cm}}\item[]\footnotesize\singlespacing}
  {\end{list}}
'''))
            
            class CitacaoABNT(Environment):
                _latex_name = 'citacao'

            doc.preamble.append(Command('title', project.name))
            if project.metadata.get('author'):
                doc.preamble.append(Command('author', project.metadata.get('author')))
            doc.preamble.append(Command('date', NoEscape(r'\today')))
            
            doc.append(NoEscape(r'\maketitle'))

            text_buffer = []

            def flush_buffer():
                if text_buffer:
                    full_text = " ".join([str(t) for t in text_buffer])
                    doc.append(NoEscape(full_text))
                    doc.append(NewLine())
                    text_buffer.clear()

            for paragraph in project.paragraphs:
                
                if paragraph.type in [ParagraphType.INTRODUCTION, ParagraphType.ARGUMENT, 
                                     ParagraphType.CONCLUSION, ParagraphType.ARGUMENT_RESUMPTION]:
                    
                    chunk = self._format_text_for_latex(paragraph.content)
                    
                    if hasattr(paragraph, 'footnotes') and paragraph.footnotes:
                        for note in paragraph.footnotes:
                            note_fmt = self._format_text_for_latex(note)
                            chunk += NoEscape(r'\footnote{' + note_fmt + r'}')
                    
                    text_buffer.append(chunk)
                    continue

                flush_buffer()

                if paragraph.type == ParagraphType.TITLE_1:
                    doc.append(Section(self._format_text_for_latex(paragraph.content)))
                
                elif paragraph.type == ParagraphType.TITLE_2:
                    doc.append(Subsection(self._format_text_for_latex(paragraph.content)))
                
                elif paragraph.type == ParagraphType.QUOTE:
                    citacao = CitacaoABNT()
                    citacao.append(self._format_text_for_latex(paragraph.content))
                    doc.append(citacao)
                    
                elif paragraph.type == ParagraphType.EPIGRAPH:
                    formatted = self._format_text_for_latex(paragraph.content)
                    doc.append(NoEscape(r'\begin{flushright}\textit{' + formatted + r'}\end{flushright}'))
                
                elif paragraph.type == ParagraphType.CODE:
                    doc.append(NoEscape(r'\begin{lstlisting}'))
                    doc.append(NoEscape(paragraph.content))
                    doc.append(NoEscape(r'\end{lstlisting}'))
                
                elif paragraph.type == ParagraphType.LATEX:
                    content = paragraph.content.strip()
                    if content:
                        if content.startswith('\\begin') or content.startswith('$$') or content.startswith('\\['):
                            doc.append(NoEscape(content))
                        else:
                            doc.append(NoEscape(r'\begin{equation}'))
                            doc.append(NoEscape(content))
                            doc.append(NoEscape(r'\end{equation}'))

                elif paragraph.type == ParagraphType.IMAGE:
                    img_metadata = paragraph.get_image_metadata()
                    if img_metadata and Path(img_metadata['path']).exists():
                        with doc.create(Figure(position='h!')) as pic:
                            width_str = r'0.8\textwidth'
                            if 'width_percent' in img_metadata:
                                width_str = f"{img_metadata['width_percent']/100:.2f}\\textwidth"
                            
                            pic.add_image(img_metadata['path'], width=NoEscape(width_str))
                            
                            if img_metadata.get('caption'):
                                pic.add_caption(img_metadata['caption'])

                # TABLE
                elif paragraph.type in (ParagraphType.TABLE, 'table'):
                    table_data, has_header, caption = self._get_table_meta(paragraph)
                    if table_data:
                        num_cols = max(len(row) for row in table_data)
                        col_spec = ' '.join(['>{\\centering\\arraybackslash}p{' +
                                         f'{12.0/num_cols:.1f}' + 'cm}'] * num_cols)
                        lines = [
                            NoEscape(r'\begin{table}[h!]'),
                            NoEscape(r'\centering'),
                        ]
                        if caption:
                            lines.append(NoEscape(r'\caption{' + escape_latex(caption) + '}'))
                        lines.append(NoEscape(r'\begin{tabular}{' + col_spec + '}'))
                        lines.append(NoEscape(r'\toprule'))
                        for r_idx, row in enumerate(table_data):
                            row_escaped = [escape_latex(str(c)) for c in row]
                            row_str     = ' & '.join(row_escaped) + r' \\'
                            lines.append(NoEscape(row_str))
                            if has_header and r_idx == 0:
                                lines.append(NoEscape(r'\midrule'))
                        lines.append(NoEscape(r'\bottomrule'))
                        lines.append(NoEscape(r'\end{tabular}'))
                        lines.append(NoEscape(r'\end{table}'))
                        for line in lines:
                            doc.append(line)

                # CHART
                elif paragraph.type in (ParagraphType.CHART, 'chart'):
                    image_path, title = self._get_chart_meta(paragraph)
                    if image_path and Path(image_path).exists():
                        with doc.create(Figure(position='h!')) as pic:
                            pic.add_image(image_path, width=NoEscape(r'0.8\textwidth'))
                            if title:
                                pic.add_caption(title)

                # MAP
                elif paragraph.type in (ParagraphType.MAP, 'map'):
                    image_path, title = self._get_map_meta(paragraph)
                    if image_path and Path(image_path).exists():
                        with doc.create(Figure(position='h!')) as pic:
                            pic.add_image(image_path, width=NoEscape(r'0.9\textwidth'))
                            if title:
                                pic.add_caption(title)

            flush_buffer()

            doc.generate_tex(str(file_path_obj.with_suffix('')))
            
            return True

        except Exception as e:
            print(_("Erro inesperado ao exportar para LaTeX: {}: {}").format(type(e).__name__, e))
            import traceback
            traceback.print_exc()
            return False

    def _get_table_meta(self, paragraph):
        """Retorna (table_data, has_header, caption) de um parágrafo TABLE."""
        metadata = getattr(paragraph, 'metadata', {}) or {}
        formatting = getattr(paragraph, 'formatting', {}) or {}
        meta = metadata.get('table_data') or formatting.get('table_data', {})
        return (
            meta.get('data', []),
            meta.get('has_header', True),
            meta.get('caption', ''),
        )

    def _get_chart_meta(self, paragraph):
        """Retorna (image_path, title) de um parágrafo CHART."""
        formatting = getattr(paragraph, 'formatting', {}) or {}
        meta = formatting.get('chart_data', {})
        return meta.get('image_path', ''), meta.get('title', 'Gráfico')

    def _get_map_meta(self, paragraph):
        """Retorna (image_path, title) de um parágrafo MAP."""
        formatting = getattr(paragraph, 'formatting', {}) or {}
        meta = formatting.get('map_data', {})
        image_path = meta.get('image_path', '')
        # Reconstrói título estruturado (Local: Tema (Ano))
        local = meta.get('title_local', '').strip()
        tema  = meta.get('title_tema',  '').strip()
        ano   = meta.get('title_ano',   '').strip()
        parts = []
        if local and tema:
            parts.append(f"{local}: {tema}")
        elif local:
            parts.append(local)
        elif tema:
            parts.append(tema)
        if ano:
            parts.append(f"({ano})")
        title = " ".join(parts) or meta.get('title', 'Mapa')
        return image_path, title