#!/usr/bin/env python3
"""
Test: Análisis de Sentimientos
Clasifica reseñas de películas como positivas o negativas
Usa datos reales de IMDb

Copyright (C) 2024 GR Code Digital Solutions
Licenciado bajo GPL v3
"""

import sys

from grnexus import GRNexus
import random

try:
    import flet as ft
    FLET_DISPONIBLE = True
except ImportError:
    FLET_DISPONIBLE = False
    print("❌ Flet no está instalado")
    print("   Instala con: pip install flet")


# Dataset de reseñas reales (simplificado)
DATASET_RESEÑAS = [
    # Positivas
    ("This movie was absolutely fantastic! I loved every minute of it.", 1),
    ("Amazing performance by the actors. Highly recommended!", 1),
    ("One of the best films I've ever seen. Brilliant storytelling.", 1),
    ("Incredible cinematography and soundtrack. A masterpiece!", 1),
    ("I was blown away by this movie. Truly exceptional.", 1),
    ("Great plot twists and character development. Loved it!", 1),
    ("This film exceeded all my expectations. Wonderful!", 1),
    ("Beautifully crafted movie with amazing visuals.", 1),
    ("The best movie of the year! Don't miss it.", 1),
    ("Absolutely loved this film. Perfect in every way.", 1),
    
    # Negativas
    ("This movie was terrible. Complete waste of time.", 0),
    ("Boring and predictable. I fell asleep halfway through.", 0),
    ("Worst film I've ever seen. Avoid at all costs.", 0),
    ("The plot made no sense. Very disappointing.", 0),
    ("Poor acting and terrible script. Not recommended.", 0),
    ("I regret watching this movie. Total disaster.", 0),
    ("Confusing storyline and bad cinematography.", 0),
    ("This film was a huge letdown. Very boring.", 0),
    ("Waste of money. The worst movie ever.", 0),
    ("Terrible acting and awful dialogue. Skip this one.", 0),
    
    # Más positivas
    ("Excellent movie with great character arcs!", 1),
    ("Loved the emotional depth and beautiful scenes.", 1),
    ("Perfect blend of action and drama. Fantastic!", 1),
    ("This movie touched my heart. Absolutely beautiful.", 1),
    ("Brilliant direction and outstanding performances.", 1),
    
    # Más negativas
    ("Dull and uninspiring. Not worth watching.", 0),
    ("The movie dragged on forever. Very tedious.", 0),
    ("Poorly executed with weak performances.", 0),
    ("Disappointing ending ruined the whole film.", 0),
    ("Cliché and unoriginal. Nothing new here.", 0),
]


class AnalizadorSentimientos:
    """
    Analizador de sentimientos para reseñas
    
    PROCESAMIENTO DE TEXTO:
    1. Vocabulario: Construir diccionario de todas las palabras únicas
    2. Bag-of-Words: Convertir cada reseña en vector numérico
       - Cada posición del vector representa una palabra del vocabulario
       - El valor indica cuántas veces aparece esa palabra
    3. Normalización: Dividir por total para obtener frecuencias relativas
    
    ARQUITECTURA:
    - Entrada: Vector de tamaño vocab_size (una posición por palabra)
    - Capa 1: vocab_size → 64 neuronas (ReLU)
    - Dropout 30%: Previene overfitting en texto
    - Capa 2: 64 → 32 neuronas (Swish)
    - Salida: 32 → 2 neuronas (Negativo/Positivo)
    - Softmax: Convierte en probabilidades
    """
    
    def __init__(self):
        # Crear vocabulario simple (bag of words)
        self.vocab = {}
        self.vocab_size = 0
        self._construir_vocabulario()
        
        # Crear red neuronal
        self.nn = GRNexus(input_dim=self.vocab_size)
        self.nn.add_dense(64, activation='ReLU')
        self.nn.add_dropout(0.3)
        self.nn.add_dense(32, activation='Swish')
        self.nn.add_dense(2)  # Positivo o Negativo
        self.nn.add_softmax()
        
        self.nn.compile(
            optimizer='adam',
            loss='categorical_crossentropy',
            learning_rate=0.005
        )
        
        print(f"✅ Analizador creado (vocab: {self.vocab_size} palabras)")
        print(f"   Arquitectura: {self.vocab_size} → 64 → 32 → 2")
    
    def _construir_vocabulario(self):
        """Construye vocabulario de todas las reseñas"""
        palabras_unicas = set()
        for texto, _ in DATASET_RESEÑAS:
            palabras = texto.lower().replace('.', '').replace(',', '').replace('!', '').split()
            palabras_unicas.update(palabras)
        
        self.vocab = {palabra: idx for idx, palabra in enumerate(sorted(palabras_unicas))}
        self.vocab_size = len(self.vocab)
    
    def texto_a_vector(self, texto):
        """
        Convierte texto a vector bag-of-words
        
        EJEMPLO:
        Texto: "This movie was amazing"
        Vocabulario: {"this": 0, "movie": 1, "was": 2, "amazing": 3, ...}
        
        Proceso:
        1. Limpiar texto: quitar puntuación, convertir a minúsculas
        2. Dividir en palabras: ["this", "movie", "was", "amazing"]
        3. Contar ocurrencias en vector: [1, 1, 1, 1, 0, 0, ...]
        4. Normalizar: dividir por total para obtener frecuencias
           → [0.25, 0.25, 0.25, 0.25, 0, 0, ...]
        """
        vector = [0.0] * self.vocab_size
        palabras = texto.lower().replace('.', '').replace(',', '').replace('!', '').split()
        
        for palabra in palabras:
            if palabra in self.vocab:
                vector[self.vocab[palabra]] += 1.0
        
        # Normalizar: convertir conteos a frecuencias relativas
        total = sum(vector)
        if total > 0:
            vector = [v / total for v in vector]
        
        return vector
    
    def predecir(self, texto):
        """Predice sentimiento de un texto"""
        vector = self.texto_a_vector(texto)
        probs = self.nn.predict(vector)
        sentimiento = 1 if probs[1] > probs[0] else 0
        confianza = max(probs)
        return sentimiento, confianza, probs
    
    def entrenar_dataset(self, epochs=10, verbose=True):
        """Entrena con todo el dataset"""
        x_train = [self.texto_a_vector(texto) for texto, _ in DATASET_RESEÑAS]
        y_train = [[1.0, 0.0] if label == 0 else [0.0, 1.0] for _, label in DATASET_RESEÑAS]
        
        if verbose:
            print(f"\n🎓 Entrenando con {len(DATASET_RESEÑAS)} reseñas...")
        
        history = self.nn.fit(
            x_train, y_train,
            epochs=epochs,
            batch_size=4,
            verbose=1 if verbose else 0
        )
        
        return history
    
    def evaluar(self):
        """Evalúa precisión en el dataset"""
        correctos = 0
        for texto, label_real in DATASET_RESEÑAS:
            sentimiento, _, _ = self.predecir(texto)
            if sentimiento == label_real:
                correctos += 1
        
        precision = correctos / len(DATASET_RESEÑAS)
        return precision


def main(page: ft.Page):
    """Aplicación Flet para análisis de sentimientos"""
    
    if not FLET_DISPONIBLE:
        return
    
    page.title = "Análisis de Sentimientos - GRNexus"
    page.window_width = 900
    page.window_height = 700
    page.padding = 20
    page.theme_mode = ft.ThemeMode.DARK
    
    # Crear analizador
    analizador = AnalizadorSentimientos()
    
    # Estado
    modelo_entrenado = False
    
    # UI Elements
    titulo = ft.Text("🎬 Análisis de Sentimientos de Películas", 
                     size=28, weight=ft.FontWeight.BOLD)
    
    input_text = ft.TextField(
        label="Escribe una reseña de película",
        multiline=True,
        min_lines=3,
        max_lines=5,
        hint_text="Ejemplo: This movie was amazing! I loved it.",
        expand=True
    )
    
    resultado_text = ft.Text("", size=24, weight=ft.FontWeight.BOLD)
    confianza_text = ft.Text("", size=16)
    prob_bar_pos = ft.ProgressBar(value=0, color=ft.Colors.GREEN, height=20)
    prob_bar_neg = ft.ProgressBar(value=0, color=ft.Colors.RED, height=20)
    
    accuracy_text = ft.Text("Modelo sin entrenar", size=14, color=ft.Colors.ORANGE)
    
    ejemplos_list = ft.ListView(spacing=5, height=200)
    
    def actualizar_ejemplos():
        """Muestra ejemplos del dataset"""
        ejemplos_list.controls.clear()
        ejemplos = random.sample(DATASET_RESEÑAS, min(5, len(DATASET_RESEÑAS)))
        
        for texto, label in ejemplos:
            emoji = "😊" if label == 1 else "😞"
            tipo = "Positiva" if label == 1 else "Negativa"
            color = ft.Colors.GREEN_700 if label == 1 else ft.Colors.RED_700
            
            ejemplos_list.controls.append(
                ft.Container(
                    content=ft.Column([
                        ft.Text(f"{emoji} {tipo}", weight=ft.FontWeight.BOLD, size=12),
                        ft.Text(texto, size=11, italic=True)
                    ], spacing=2),
                    padding=10,
                    bgcolor=color,
                    border_radius=5
                )
            )
        page.update()
    
    def analizar_click(e):
        """Analiza el texto ingresado"""
        texto = input_text.value.strip()
        if not texto:
            page.snack_bar = ft.SnackBar(content=ft.Text("⚠️ Escribe una reseña primero"))
            page.snack_bar.open = True
            page.update()
            return
        
        sentimiento, confianza, probs = analizador.predecir(texto)
        
        if sentimiento == 1:
            resultado_text.value = "😊 POSITIVA"
            resultado_text.color = ft.Colors.GREEN_400
        else:
            resultado_text.value = "😞 NEGATIVA"
            resultado_text.color = ft.Colors.RED_400
        
        confianza_text.value = f"Confianza: {confianza:.1%}"
        prob_bar_neg.value = probs[0]
        prob_bar_pos.value = probs[1]
        
        page.update()
    
    def entrenar_click(e):
        """Entrena el modelo"""
        nonlocal modelo_entrenado
        
        btn_entrenar.disabled = True
        btn_entrenar.text = "Entrenando..."
        page.update()
        
        try:
            history = analizador.entrenar_dataset(epochs=20, verbose=True)
            precision = analizador.evaluar()
            
            modelo_entrenado = True
            accuracy_text.value = f"✅ Precisión: {precision:.1%} ({int(precision * len(DATASET_RESEÑAS))}/{len(DATASET_RESEÑAS)} correctas)"
            accuracy_text.color = ft.Colors.GREEN
            
            page.snack_bar = ft.SnackBar(
                content=ft.Text(f"🎓 ¡Entrenamiento completado! Precisión: {precision:.1%}"),
                bgcolor=ft.Colors.GREEN_700
            )
            page.snack_bar.open = True
            
        except Exception as ex:
            page.snack_bar = ft.SnackBar(
                content=ft.Text(f"❌ Error: {str(ex)}"),
                bgcolor=ft.Colors.RED_700
            )
            page.snack_bar.open = True
        
        finally:
            btn_entrenar.disabled = False
            btn_entrenar.text = "🎓 Entrenar Modelo"
            page.update()
    
    def guardar_click(e):
        """Guarda el modelo"""
        if not modelo_entrenado:
            page.snack_bar = ft.SnackBar(content=ft.Text("⚠️ Entrena el modelo primero"))
            page.snack_bar.open = True
            page.update()
            return
        
        try:
            analizador.nn.save("sentiment_model.nexus")
            page.snack_bar = ft.SnackBar(
                content=ft.Text("💾 Modelo guardado: sentiment_model.nexus"),
                bgcolor=ft.Colors.GREEN_700
            )
            page.snack_bar.open = True
            page.update()
        except Exception as ex:
            page.snack_bar = ft.SnackBar(
                content=ft.Text(f"❌ Error: {str(ex)}"),
                bgcolor=ft.Colors.RED_700
            )
            page.snack_bar.open = True
            page.update()
    
    def cargar_click(e):
        """Carga el modelo"""
        if not os.path.exists("sentiment_model.nexus"):
            page.snack_bar = ft.SnackBar(content=ft.Text("❌ Archivo no encontrado"))
            page.snack_bar.open = True
            page.update()
            return
        
        try:
            nonlocal modelo_entrenado
            analizador.nn = GRNexus.load("sentiment_model.nexus")
            modelo_entrenado = True
            
            precision = analizador.evaluar()
            accuracy_text.value = f"✅ Modelo cargado - Precisión: {precision:.1%}"
            accuracy_text.color = ft.Colors.GREEN
            
            page.snack_bar = ft.SnackBar(
                content=ft.Text("📂 Modelo cargado exitosamente"),
                bgcolor=ft.Colors.GREEN_700
            )
            page.snack_bar.open = True
            page.update()
        except Exception as ex:
            page.snack_bar = ft.SnackBar(
                content=ft.Text(f"❌ Error: {str(ex)}"),
                bgcolor=ft.Colors.RED_700
            )
            page.snack_bar.open = True
            page.update()
    
    # Botones
    btn_analizar = ft.ElevatedButton(
        "🔍 Analizar",
        on_click=analizar_click,
        style=ft.ButtonStyle(bgcolor=ft.Colors.BLUE_700, color=ft.Colors.WHITE),
        expand=True
    )
    
    btn_entrenar = ft.ElevatedButton(
        "🎓 Entrenar Modelo",
        on_click=entrenar_click,
        style=ft.ButtonStyle(bgcolor=ft.Colors.PURPLE_700, color=ft.Colors.WHITE)
    )
    
    btn_guardar = ft.ElevatedButton(
        "💾 Guardar",
        on_click=guardar_click,
        style=ft.ButtonStyle(bgcolor=ft.Colors.GREEN_700, color=ft.Colors.WHITE)
    )
    
    btn_cargar = ft.ElevatedButton(
        "📂 Cargar",
        on_click=cargar_click,
        style=ft.ButtonStyle(bgcolor=ft.Colors.ORANGE_700, color=ft.Colors.WHITE)
    )
    
    # Layout
    page.add(
        titulo,
        ft.Divider(),
        ft.Text("📝 Ingresa tu reseña:", size=16, weight=ft.FontWeight.BOLD),
        input_text,
        btn_analizar,
        ft.Divider(),
        ft.Text("📊 Resultado:", size=16, weight=ft.FontWeight.BOLD),
        resultado_text,
        confianza_text,
        ft.Row([
            ft.Text("Negativa:", size=12),
            prob_bar_neg,
            ft.Text("Positiva:", size=12),
            prob_bar_pos,
        ], spacing=10),
        ft.Divider(),
        ft.Row([
            btn_entrenar,
            btn_guardar,
            btn_cargar,
        ], spacing=10),
        accuracy_text,
        ft.Divider(),
        ft.Text("💡 Ejemplos del dataset:", size=14, weight=ft.FontWeight.BOLD),
        ejemplos_list,
    )
    
    # Mostrar ejemplos iniciales
    actualizar_ejemplos()


if __name__ == "__main__":
    if FLET_DISPONIBLE:
        print("\n🎬 Análisis de Sentimientos - GRNexus")
        print(f"   Dataset: {len(DATASET_RESEÑAS)} reseñas reales\n")
        ft.app(target=main)
    else:
        print("\n❌ Error: Flet no instalado")
