"""
Texture Engine - Pad and Texture Layer System for AbletonMCP_AI

Este módulo proporciona generación avanzada de capas de pads y texturas
para enriquecer las producciones musicales con elementos atmosféricos,
armónicos y rítmicos.

Clases principales:
- TextureEngine: Motor principal para generación de pads y texturas
  * generate_ambient_pad: Pads atmosféricos largos y evolutivos
  * generate_rhythmic_pad: Pads con patrones rítmicos sincopados
  * generate_arpeggiated_pad: Pads con patrones arpegiados
  * layer_by_frequency_range: Organización por rangos de frecuencia
  * apply_pad_automation: Automatización de filtros y volúmenes

Autor: AbletonMCP_AI
"""
import logging
import random
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Any, Tuple, Union
from enum import Enum

logger = logging.getLogger("TextureEngine")


# =============================================================================
# ENUMS Y CONSTANTES
# =============================================================================

class PadType(Enum):
    """Tipos de pads disponibles."""
    AMBIENT = "ambient"
    RHYTHMIC = "rhythmic"
    ARPEGGIATED = "arpeggiated"
    EVOLVING = "evolving"
    DRONE = "drone"


class SyncopationPattern(Enum):
    """Patrones de sincopación para pads rítmicos."""
    STANDARD = "standard"
    OFFBEAT = "offbeat"
    PUSHED = "pushed"
    LATIN = "latin"
    COMPLEX = "complex"


class ArpeggioPattern(Enum):
    """Patrones de arpegio disponibles."""
    UP = "up"
    DOWN = "down"
    UP_DOWN = "up_down"
    DOWN_UP = "down_up"
    RANDOM = "random"
    PING_PONG = "ping_pong"


# Duraciones comunes en beats
DURATION_BEATS = {
    "1_bar": 4.0,
    "2_bars": 8.0,
    "4_bars": 16.0,
    "8_bars": 32.0,
    "16_bars": 64.0,
    "32_bars": 128.0,
}

# Rangos de frecuencia para layering
FREQUENCY_RANGES = {
    "sub": (20, 60),
    "low": (60, 250),
    "low_mid": (250, 500),
    "mid": (500, 2000),
    "high_mid": (2000, 4000),
    "high": (4000, 20000),
}

# Progresiones de acordes comunes para pads
CHORD_PROGRESSIONS = {
    "i_v_vi_iv": [0, 7, 9, 5],
    "i_vi_iv_v": [0, 9, 5, 7],
    "ii_v_i": [2, 7, 0],
    "i_iv_v": [0, 5, 7],
    "vi_iv_i_v": [9, 5, 0, 7],
    "i_v_iii_vi": [0, 7, 4, 9],
}

# Qualities de acordes para pads
CHORD_QUALITIES = {
    "triad": [0, 4, 7],
    "minor": [0, 3, 7],
    "sus2": [0, 2, 7],
    "sus4": [0, 5, 7],
    "add9": [0, 4, 7, 14],
    "m7": [0, 3, 7, 10],
    "m9": [0, 3, 7, 10, 14],
    "maj7": [0, 4, 7, 11],
    "maj9": [0, 4, 7, 11, 14],
    "7sus4": [0, 5, 7, 10],
}


# =============================================================================
# CLASES DE DATOS
# =============================================================================

@dataclass
class PadLayer:
    """Representa una capa de pad individual.
    
    Atributos:
        name: Nombre de la capa
        notes: Lista de notas MIDI
        duration: Duración en beats
        velocity_range: Rango de velocidad (min, max)
        pan: Posición de paneo (-1.0 a 1.0)
        frequency_range: Rango de frecuencia dominante
        automation: Puntos de automatización
    """
    name: str
    notes: List[Dict[str, Any]] = field(default_factory=list)
    duration: float = 16.0
    velocity_range: Tuple[int, int] = (50, 80)
    pan: float = 0.0
    frequency_range: str = "mid"
    automation: Dict[str, List[Dict[str, Any]]] = field(default_factory=dict)
    
    def to_dict(self) -> Dict[str, Any]:
        return {
            "name": self.name,
            "note_count": len(self.notes),
            "duration": self.duration,
            "velocity_range": self.velocity_range,
            "pan": self.pan,
            "frequency_range": self.frequency_range,
            "automation": self.automation,
        }


@dataclass
class TextureConfiguration:
    """Configuración completa de texturas y pads.
    
    Atributos:
        layers: Lista de capas de pad
        total_duration: Duración total en beats
        root_key: Tonalidad raíz
        progression: Progresión de acordes usada
        density: Densidad de textura (0.0-1.0)
    """
    layers: List[PadLayer] = field(default_factory=list)
    total_duration: float = 64.0
    root_key: str = "C"
    progression: str = "i_v_vi_iv"
    density: float = 0.5
    
    def to_dict(self) -> Dict[str, Any]:
        return {
            "layers": [layer.to_dict() for layer in self.layers],
            "total_duration": self.total_duration,
            "root_key": self.root_key,
            "progression": self.progression,
            "density": self.density,
            "layer_count": len(self.layers),
        }


@dataclass
class AutomationPoint:
    """Punto de automatización.
    
    Atributos:
        time: Tiempo en beats
        value: Valor del parámetro (0.0-1.0)
        curve: Tipo de curva ("linear", "exp", "log")
    """
    time: float
    value: float
    curve: str = "linear"
    
    def to_dict(self) -> Dict[str, Any]:
        return {
            "time": self.time,
            "value": self.value,
            "curve": self.curve,
        }


# =============================================================================
# TEXTURE ENGINE - MOTOR PRINCIPAL
# =============================================================================

class TextureEngine:
    """Motor de generación de pads y texturas para producciones musicales.
    
    Este motor genera diferentes tipos de pads atmosféricos, rítmicos y
    arpegiados que enriquecen la profundidad y dimensión de las producciones.
    
    Características:
    - Pads ambientales largos y evolutivos
    - Pads rítmicos con sincopación
    - Pads arpegiados con patrones configurables
    - Organización por rangos de frecuencia
    - Automatización de parámetros
    """
    
    def __init__(self):
        self.logger = logging.getLogger("TextureEngine")
        self.base_octave = 4  # C4 como base
        self.velocity_range = (40, 90)
        
    def _note_to_midi(self, note_name: str, octave: int = 4) -> int:
        """Convierte nombre de nota a número MIDI.
        
        Args:
            note_name: Nombre de nota (C, C#, D, Eb, etc.)
            octave: Octava MIDI
            
        Returns:
            Número de nota MIDI (0-127)
        """
        notes = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
        note_lower = note_name.replace("Db", "C#").replace("Eb", "D#") \
                            .replace("Gb", "F#").replace("Ab", "G#").replace("Bb", "A#")
        
        if note_lower in notes:
            note_idx = notes.index(note_lower)
            return (octave + 1) * 12 + note_idx
        return 60  # Default to C4
    
    def _get_scale(self, key: str) -> List[int]:
        """Obtiene la escala menor o mayor para una tonalidad.
        
        Args:
            key: Tonalidad (e.g., "Am", "C", "Gm")
            
        Returns:
            Lista de semitonos relativos a la tónica
        """
        key_lower = key.lower()
        
        # Escala menor natural
        if "m" in key_lower:
            return [0, 2, 3, 5, 7, 8, 10]
        # Escala mayor
        return [0, 2, 4, 5, 7, 9, 11]
    
    def _generate_chord_notes(self, root: int, quality: str, duration: float,
                             velocity: int = 70, spread: float = 0.0) -> List[Dict[str, Any]]:
        """Genera notas para un acorde.
        
        Args:
            root: Nota raíz MIDI
            quality: Calidad del acorde (triad, minor, maj7, etc.)
            duration: Duración en beats
            velocity: Velocidad MIDI
            spread: Separación temporal entre notas del acorde
            
        Returns:
            Lista de diccionarios de notas
        """
        intervals = CHORD_QUALITIES.get(quality, CHORD_QUALITIES["triad"])
        notes = []
        
        for i, interval in enumerate(intervals):
            note = {
                "pitch": root + interval,
                "start_time": i * spread,
                "duration": duration - (i * spread),
                "velocity": velocity - (i * 3),  # Ligera caída en velocity
            }
            notes.append(note)
        
        return notes
    
    def generate_ambient_pad(self, chord_progression: List[int], duration: float = 32.0,
                            key: str = "Am", quality: str = "add9",
                            voicing: str = "spread") -> PadLayer:
        """Genera un pad ambiental largo y evolutivo.
        
        Args:
            chord_progression: Lista de grados de la progresión (0-11)
            duration: Duración total en beats
            key: Tonalidad raíz
            quality: Calidad de acordes
            voicing: Tipo de voicing ("spread", "close", "open")
            
        Returns:
            PadLayer configurado con notas ambientales
        """
        root_midi = self._note_to_midi(key.replace("m", "").replace("M", ""), 3)
        scale = self._get_scale(key)
        notes = []
        
        # Dividir duración entre acordes
        chord_duration = duration / len(chord_progression)
        
        for i, degree in enumerate(chord_progression):
            # Obtener nota raíz del acorde
            chord_root = root_midi + scale[degree % len(scale)] + (12 if degree >= 12 else 0)
            
            # Ajustar octava según voicing
            octave_offset = 0
            if voicing == "spread":
                octave_offset = 12 if i % 2 == 0 else 0
            elif voicing == "open":
                octave_offset = 12
            
            # Generar notas del acorde con overlap para suavidad
            overlap = chord_duration * 0.2
            chord_notes = self._generate_chord_notes(
                chord_root + octave_offset,
                quality,
                chord_duration + overlap,
                velocity=random.randint(50, 75),
                spread=0.05 if voicing == "spread" else 0.0
            )
            
            # Ajustar tiempos de inicio
            start_offset = i * chord_duration
            for note in chord_notes:
                note["start_time"] += start_offset
            
            notes.extend(chord_notes)
        
        layer = PadLayer(
            name=f"AmbientPad_{quality}_{voicing}",
            notes=notes,
            duration=duration,
            velocity_range=(45, 75),
            pan=0.0,
            frequency_range="mid",
        )
        
        self.logger.info(f"Generated ambient pad: {len(notes)} notes, {duration} beats")
        return layer
    
    def generate_rhythmic_pad(self, chord_progression: List[int], 
                             syncopation_pattern: str = "latin",
                             duration: float = 32.0, key: str = "Am",
                             density: float = 0.5) -> PadLayer:
        """Genera un pad con patrón rítmico sincopado.
        
        Args:
            chord_progression: Lista de grados de la progresión
            syncopation_pattern: Patrón de sincopación
            duration: Duración total en beats
            key: Tonalidad raíz
            density: Densidad de notas (0.0-1.0)
            
        Returns:
            PadLayer con patrón rítmico
        """
        root_midi = self._note_to_midi(key.replace("m", "").replace("M", ""), 4)
        scale = self._get_scale(key)
        notes = []
        
        # Definir patrones de sincopación (posiciones en beats de 1 compás 4/4)
        patterns = {
            "standard": [0, 2],
            "offbeat": [1, 3],
            "pushed": [0.5, 2.5],
            "latin": [0, 1.5, 2, 3.5],
            "complex": [0, 0.75, 2, 2.5, 3, 3.75],
        }
        
        rhythm = patterns.get(syncopation_pattern, patterns["latin"])
        bars = int(duration / 4)
        chord_duration = 4.0  # Un acorde por compás
        
        for bar in range(bars):
            chord_idx = bar % len(chord_progression)
            degree = chord_progression[chord_idx]
            chord_root = root_midi + scale[degree % len(scale)]
            
            # Determinar si este compás tiene actividad basado en densidad
            if random.random() > density and bar > 0:
                continue
            
            # Generar notas en las posiciones rítmicas
            for pos in rhythm:
                beat_time = bar * 4 + pos
                if beat_time < duration:
                    # Elegir nota del acorde basado en posición
                    if pos in [0, 2]:  # Tiempos fuertes: raíz o tercera
                        pitch = chord_root
                    else:  # Tiempos débiles: quinta o séptima
                        pitch = chord_root + 7
                    
                    note = {
                        "pitch": pitch,
                        "start_time": beat_time,
                        "duration": 1.5,  # Ligeramente staccato para ritmo
                        "velocity": random.randint(55, 80) if pos in [0, 2] else random.randint(45, 65),
                    }
                    notes.append(note)
        
        layer = PadLayer(
            name=f"RhythmicPad_{syncopation_pattern}",
            notes=notes,
            duration=duration,
            velocity_range=(45, 80),
            pan=0.1,
            frequency_range="mid",
        )
        
        self.logger.info(f"Generated rhythmic pad: {len(notes)} notes, pattern={syncopation_pattern}")
        return layer
    
    def generate_arpeggiated_pad(self, chord_progression: List[int], 
                                arp_pattern: str = "up",
                                duration: float = 32.0, key: str = "Am",
                                rate: str = "8th", octave_range: int = 1) -> PadLayer:
        """Genera un pad con patrón arpegiado.
        
        Args:
            chord_progression: Lista de grados de la progresión
            arp_pattern: Patrón de arpegio
            duration: Duración total en beats
            key: Tonalidad raíz
            rate: Velocidad del arpegio ("4th", "8th", "16th")
            octave_range: Rango de octavas para el arpegio
            
        Returns:
            PadLayer con arpegios
        """
        root_midi = self._note_to_midi(key.replace("m", "").replace("M", ""), 4)
        scale = self._get_scale(key)
        notes = []
        
        # Duración de cada nota del arpegio
        rate_beats = {"4th": 1.0, "8th": 0.5, "16th": 0.25}
        step_duration = rate_beats.get(rate, 0.5)
        
        bars = int(duration / 4)
        notes_per_chord = int(4 / step_duration)
        
        for bar in range(bars):
            chord_idx = bar % len(chord_progression)
            degree = chord_progression[chord_idx]
            chord_root = root_midi + scale[degree % len(scale)]
            
            # Definir notas del acorde
            chord_tones = [
                chord_root,
                chord_root + 3 if "m" in key.lower() else chord_root + 4,  # 3ra
                chord_root + 7,  # 5ta
                chord_root + (10 if "m" in key.lower() else 11),  # 7ma
            ]
            
            # Expandir por octavas si es necesario
            if octave_range > 1:
                expanded = []
                for oct_shift in range(octave_range):
                    for tone in chord_tones:
                        expanded.append(tone + (oct_shift * 12))
                chord_tones = expanded
            
            # Generar arpegio según patrón
            arp_notes = self._apply_arp_pattern(chord_tones, arp_pattern, notes_per_chord)
            
            # Añadir notas al resultado
            for i, pitch in enumerate(arp_notes[:notes_per_chord]):
                beat_time = bar * 4 + (i * step_duration)
                if beat_time < duration:
                    # Velocity con acento en el primer paso
                    vel = 85 if i == 0 else random.randint(55, 70)
                    
                    note = {
                        "pitch": pitch,
                        "start_time": beat_time,
                        "duration": step_duration * 0.9,  # Ligeramente separadas
                        "velocity": vel,
                    }
                    notes.append(note)
        
        layer = PadLayer(
            name=f"ArpeggiatedPad_{arp_pattern}_{rate}",
            notes=notes,
            duration=duration,
            velocity_range=(55, 85),
            pan=-0.1,
            frequency_range="high_mid",
        )
        
        self.logger.info(f"Generated arpeggiated pad: {len(notes)} notes, pattern={arp_pattern}")
        return layer
    
    def _apply_arp_pattern(self, chord_tones: List[int], pattern: str, count: int) -> List[int]:
        """Aplica un patrón de arpegio a las notas del acorde.
        
        Args:
            chord_tones: Lista de notas del acorde
            pattern: Nombre del patrón
            count: Cuántas notas generar
            
        Returns:
            Lista de notas en orden del arpegio
        """
        if not chord_tones:
            return []
        
        result = []
        tones = sorted(set(chord_tones))
        
        if pattern == "up":
            for i in range(count):
                result.append(tones[i % len(tones)])
        elif pattern == "down":
            for i in range(count):
                result.append(tones[~(i % len(tones))])
        elif pattern == "up_down":
            idx = 0
            direction = 1
            for _ in range(count):
                result.append(tones[idx])
                idx += direction
                if idx >= len(tones) - 1:
                    direction = -1
                elif idx <= 0:
                    direction = 1
        elif pattern == "ping_pong":
            for i in range(count):
                cycle_pos = i % (len(tones) * 2 - 2)
                if cycle_pos < len(tones):
                    result.append(tones[cycle_pos])
                else:
                    result.append(tones[len(tones) * 2 - 2 - cycle_pos])
        elif pattern == "random":
            for _ in range(count):
                result.append(random.choice(tones))
        else:
            # Default: up
            for i in range(count):
                result.append(tones[i % len(tones)])
        
        return result
    
    def layer_by_frequency_range(self, low_chords: Optional[PadLayer] = None,
                                mid_chords: Optional[PadLayer] = None,
                                high_chords: Optional[PadLayer] = None) -> List[PadLayer]:
        """Organiza capas de pads por rangos de frecuencia.
        
        Args:
            low_chords: Capa de frecuencias bajas (opcional)
            mid_chords: Capa de frecuencias medias (opcional)
            high_chords: Capa de frecuencias altas (opcional)
            
        Returns:
            Lista de capas organizadas
        """
        layers = []
        
        if low_chords:
            low_chords.frequency_range = "low"
            low_chords.pan = -0.3
            # Transponer una octava abajo para low
            for note in low_chords.notes:
                note["pitch"] -= 12
            layers.append(low_chords)
            
        if mid_chords:
            mid_chords.frequency_range = "mid"
            mid_chords.pan = 0.0
            layers.append(mid_chords)
            
        if high_chords:
            high_chords.frequency_range = "high"
            high_chords.pan = 0.3
            # Transponer una octava arriba para high
            for note in high_chords.notes:
                note["pitch"] += 12
            layers.append(high_chords)
        
        self.logger.info(f"Created frequency-layered pads: {len(layers)} layers")
        return layers
    
    def apply_pad_automation(self, layer: PadLayer, 
                            filter_sweep: Optional[Dict[str, Any]] = None,
                            volume_swells: Optional[Dict[str, Any]] = None,
                            pan_movement: Optional[Dict[str, Any]] = None) -> PadLayer:
        """Aplica automatización a un pad.
        
        Args:
            layer: Capa de pad a automatizar
            filter_sweep: Configuración de sweep de filtro
                {"start_freq": 200, "end_freq": 8000, "start_bar": 0, "end_bar": 8}
            volume_swells: Configuración de swells de volumen
                {"swells": [(0, 0.3), (4, 0.8), (8, 0.3)], "smooth": True}
            pan_movement: Configuración de movimiento de paneo
                {"start_pan": -0.5, "end_pan": 0.5, "speed": "slow"}
            
        Returns:
            PadLayer con automatización aplicada
        """
        automation = {}
        
        # Filter sweep automation
        if filter_sweep:
            start_freq = filter_sweep.get("start_freq", 200)
            end_freq = filter_sweep.get("end_freq", 8000)
            start_bar = filter_sweep.get("start_bar", 0) * 4  # Convertir a beats
            end_bar = filter_sweep.get("end_bar", 8) * 4
            
            points = [
                {"time": start_bar, "value": self._freq_to_normalized(start_freq)},
                {"time": end_bar, "value": self._freq_to_normalized(end_freq)},
            ]
            automation["filter_freq"] = points
            
        # Volume swells
        if volume_swells:
            swells = volume_swells.get("swells", [(0, 0.5), (8, 0.8), (16, 0.5)])
            points = []
            for bar, level in swells:
                points.append({"time": bar * 4, "value": level})
            automation["volume"] = points
            
        # Pan movement
        if pan_movement:
            start_pan = pan_movement.get("start_pan", -0.5)
            end_pan = pan_movement.get("end_pan", 0.5)
            speed = pan_movement.get("speed", "slow")
            
            # Determinar duración del movimiento
            if speed == "slow":
                duration_bars = 16
            elif speed == "medium":
                duration_bars = 8
            else:  # fast
                duration_bars = 4
            
            points = [
                {"time": 0, "value": start_pan},
                {"time": duration_bars * 4, "value": end_pan},
                {"time": duration_bars * 8, "value": start_pan},  # Volver
            ]
            automation["pan"] = points
        
        layer.automation = automation
        self.logger.info(f"Applied automation to pad: {list(automation.keys())}")
        return layer
    
    def _freq_to_normalized(self, freq: float) -> float:
        """Convierte frecuencia Hz a valor normalizado (0-1).
        
        Asume rango de 20Hz a 20kHz en escala logarítmica.
        """
        import math
        min_freq, max_freq = 20.0, 20000.0
        log_min, log_max = math.log10(min_freq), math.log10(max_freq)
        log_freq = math.log10(max(freq, min_freq))
        return (log_freq - log_min) / (log_max - log_min)
    
    def create_full_texture_stack(self, key: str = "Am", 
                                 duration: float = 64.0,
                                 style: str = "ambient",
                                 progression_name: str = "i_v_vi_iv") -> TextureConfiguration:
        """Crea una pila completa de texturas con múltiples capas.
        
        Args:
            key: Tonalidad musical
            duration: Duración total en beats
            style: Estilo de textura ("ambient", "rhythmic", "arpeggiated", "full")
            progression_name: Nombre de la progresión a usar
            
        Returns:
            TextureConfiguration completa
        """
        progression = CHORD_PROGRESSIONS.get(progression_name, CHORD_PROGRESSIONS["i_v_vi_iv"])
        layers = []
        
        if style == "ambient":
            # Capa única ambiental
            pad = self.generate_ambient_pad(progression, duration, key, "add9", "spread")
            pad = self.apply_pad_automation(pad, volume_swells={"swells": [(0, 0.2), (16, 0.7), (32, 0.5)]})
            layers.append(pad)
            
        elif style == "rhythmic":
            # Base ambiental + capa rítmica
            ambient = self.generate_ambient_pad(progression, duration, key, "sus2", "open")
            rhythmic = self.generate_rhythmic_pad(progression, "latin", duration, key, 0.6)
            layers = self.layer_by_frequency_range(None, ambient, rhythmic)
            
        elif style == "arpeggiated":
            # Arpegios en capas separadas
            low_arp = self.generate_arpeggiated_pad(progression, "up", duration, key, "8th", 1)
            high_arp = self.generate_arpeggiated_pad(progression, "up_down", duration, key, "16th", 2)
            
            # Ajustar octavas
            for note in low_arp.notes:
                note["pitch"] -= 12
            for note in high_arp.notes:
                note["pitch"] += 12
                
            layers = [low_arp, high_arp]
            
        elif style == "full":
            # Pila completa con todas las capas
            low_pad = self.generate_ambient_pad(progression[:2], duration, key, "m7", "spread")
            mid_pad = self.generate_rhythmic_pad(progression, "offbeat", duration, key, 0.4)
            high_arp = self.generate_arpeggiated_pad(progression, "ping_pong", duration, key, "8th", 1)
            
            layers = self.layer_by_frequency_range(low_pad, mid_pad, high_arp)
            
            # Añadir automatización a cada capa
            layers[0] = self.apply_pad_automation(
                layers[0],
                filter_sweep={"start_freq": 100, "end_freq": 500, "start_bar": 0, "end_bar": 16}
            )
            layers[-1] = self.apply_pad_automation(
                layers[-1],
                pan_movement={"start_pan": -0.4, "end_pan": 0.4, "speed": "slow"}
            )
        
        config = TextureConfiguration(
            layers=layers,
            total_duration=duration,
            root_key=key,
            progression=progression_name,
            density=0.6 if style in ["rhythmic", "full"] else 0.4,
        )
        
        self.logger.info(f"Created texture stack: {len(layers)} layers, style={style}")
        return config
    
    def get_available_progressions(self) -> Dict[str, List[int]]:
        """Retorna las progresiones disponibles."""
        return CHORD_PROGRESSIONS.copy()
    
    def get_available_patterns(self) -> Dict[str, List[str]]:
        """Retorna los patrones disponibles."""
        return {
            "syncopation": [p.value for p in SyncopationPattern],
            "arpeggio": [p.value for p in ArpeggioPattern],
        }


# =============================================================================
# FUNCIONES DE CONVENIENCIA
# =============================================================================

def create_texture_engine() -> TextureEngine:
    """Crea y retorna una instancia del TextureEngine."""
    return TextureEngine()


def generate_quick_pad(key: str = "Am", bars: int = 16, style: str = "ambient") -> TextureConfiguration:
    """Genera rápidamente una configuración de pad.
    
    Args:
        key: Tonalidad
        bars: Duración en compases
        style: Estilo del pad
        
    Returns:
        TextureConfiguration lista para usar
    """
    engine = TextureEngine()
    return engine.create_full_texture_stack(key, bars * 4, style)


# Instancia singleton global
_texture_engine: Optional[TextureEngine] = None

def get_texture_engine() -> TextureEngine:
    """Retorna instancia singleton del TextureEngine."""
    global _texture_engine
    if _texture_engine is None:
        _texture_engine = TextureEngine()
    return _texture_engine
