import pandas as pd
import re
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
import dash
import dash_bootstrap_components as dbc
from dash import dcc, html, Input, Output, State, ALL, ctx
# --- Load and Preprocess Data ---
df = pd.read_csv("pycafe_bookshelf.csv")
# Ensure 'num_pages' is numeric and handle missing values
df['num_pages'] = pd.to_numeric(df['num_pages'], errors='coerce').fillna(0)
# Text cleaning for descriptions
def clean_text(text):
    if pd.isna(text):
        return ""
    text = re.sub(r'<.*?>', '', text) # Remove HTML tags
    text = re.sub(r'\n+', ' ', text) # Replace multiple newlines with a single space
    text = re.sub(r'[^\w\s]', ' ', text) # Remove non-alphanumeric characters (except spaces)
    text = re.sub(r'\s+', ' ', text) # Replace multiple spaces with a single space
    return text.lower().strip()
df['clean_description'] = df['description'].apply(clean_text)
# TF-IDF Vectorization
vectorizer = TfidfVectorizer(
    max_features=4000,
    ngram_range=(1, 3),
    stop_words='english',
    min_df=3,
    max_df=0.7,
    sublinear_tf=True
)
tfidf_matrix = vectorizer.fit_transform(df['clean_description'])
feature_names = vectorizer.get_feature_names_out()
# --- INNOVATIVE READING DNA ANALYSIS SYSTEM ---
class ReadingDNAAnalyzer:
    def __init__(self, df, tfidf_matrix, vectorizer):
        self.df = df
        self.tfidf_matrix = tfidf_matrix
        self.vectorizer = vectorizer
        
    def analyze_reading_dna(self, selected_vibes, selected_elementos, selected_depths):
        """Innovative analysis that creates a 'Reading DNA' profile"""
        dna_analysis = {
            'personality_type': self.determine_reader_personality(selected_vibes, selected_elementos, selected_depths),
            'rarity_index': self.calculate_taste_rarity(selected_vibes, selected_elementos, selected_depths),
            'discovery_potential': self.predict_discovery_potential(selected_vibes, selected_elementos, selected_depths),
            'genre_crossover': self.analyze_genre_crossover_potential(selected_vibes, selected_elementos, selected_depths),
            'reading_mood_map': self.create_mood_mapping(selected_vibes, selected_elementos, selected_depths)
        }
        return dna_analysis
    
    def determine_reader_personality(self, vibes, elementos, depths):
        """Determine what type of reader you are based on selections"""
        personality_indicators = {
            'vibes': {
                'Mysterious yet Cozy': ['introspective', 'comfort_seeker'],
                'Tense yet Amusing': ['thrill_seeker', 'humor_lover'],
                'Profound yet Approachable': ['wisdom_seeker', 'accessible_learner'],
                'Intense yet Hopeful': ['emotional_processor', 'optimist'],
                'Romantic yet Realistic': ['relationship_focused', 'authenticity_seeker'],
                'Fantastic yet Believable': ['imagination_balanced', 'grounded_dreamer']
            },
            'elementos': {
                'Clever Dialogues': ['conversation_lover', 'wit_appreciator'],
                'Unfolding Mysteries': ['puzzle_solver', 'patience_rewarded'],
                'Complex Characters': ['psychology_interested', 'depth_seeker'],
                'Immersive Worlds': ['escapist', 'detail_oriented'],
                'Unexpected Twists': ['surprise_lover', 'flexible_mindset'],
                'Authentic Emotions': ['empathy_driven', 'emotional_intelligence']
            },
            'depths': {
                'Quietly Profound': ['contemplative_soul', 'subtle_appreciator'],
                'Layered Storytelling': ['analytical_reader', 'meaning_seeker'],
                'Understated Intensity': ['sophisticated_taste', 'nuance_detector'],
                'Philosophical Undertones': ['deep_thinker', 'question_asker'],
                'Psychological Nuance': ['mind_explorer', 'complexity_lover'],
                'Subtle Social Commentary': ['society_observer', 'critical_thinker']
            }
        }
        
        # Collect all traits
        traits = []
        for vibe in vibes:
            traits.extend(personality_indicators['vibes'].get(vibe, []))
        for elemento in elementos:
            traits.extend(personality_indicators['elementos'].get(elemento, []))
        for depth in depths:
            traits.extend(personality_indicators['depths'].get(depth, []))
        
        # Determine primary personality type
        trait_counts = {}
        for trait in traits:
            trait_counts[trait] = trait_counts.get(trait, 0) + 1
        
        if not trait_counts:
            return {'type': 'Curious Explorer 🧭', 'description': 'Open to all reading adventures!', 'confidence': 50}
        
        dominant_traits = sorted(trait_counts.items(), key=lambda x: x[1], reverse=True)[:3]
        
        # Map combinations to personality types
        personality_types = {
            'introspective': 'The Contemplative Reader 🧘',
            'thrill_seeker': 'The Adrenaline Reader ⚡',
            'wisdom_seeker': 'The Philosophical Explorer 🎯',
            'emotional_processor': 'The Heart-Centered Reader 💝',
            'conversation_lover': 'The Social Intellect 🗣️',
            'puzzle_solver': 'The Literary Detective 🔍',
            'psychology_interested': 'The Human Nature Scholar 🧠',
            'escapist': 'The World Traveler 🌍',
            'contemplative_soul': 'The Quiet Wisdom Seeker 🕯️',
            'deep_thinker': 'The Philosophy Explorer 💭'
        }
        
        primary_trait = dominant_traits[0][0]
        reader_type = personality_types.get(primary_trait, 'The Unique Reader 🦄')
        
        return {
            'type': reader_type,
            'dominant_traits': [trait[0] for trait in dominant_traits],
            'confidence': min(100, dominant_traits[0][1] * 25)
        }
    
    def calculate_taste_rarity(self, vibes, elementos, depths):
        """Calculate how unique/rare the user's taste is"""
        all_matches = []
        total_selections = len(vibes) + len(elementos) + len(depths)
        
        if total_selections == 0:
            return {'rarity': 'Explorer', 'percentile': 50, 'description': 'Charting new territories!'}
        
        # Simulate pattern matching (simplified for this example)
        patterns_dict = {
            'Mysterious yet Cozy': 0.15, 'Tense yet Amusing': 0.25, 'Profound yet Approachable': 0.12,
            'Intense yet Hopeful': 0.20, 'Romantic yet Realistic': 0.30, 'Fantastic yet Believable': 0.18,
            'Clever Dialogues': 0.22, 'Unfolding Mysteries': 0.28, 'Complex Characters': 0.16,
            'Immersive Worlds': 0.35, 'Unexpected Twists': 0.32, 'Authentic Emotions': 0.24,
            'Quietly Profound': 0.08, 'Layered Storytelling': 0.10, 'Understated Intensity': 0.06,
            'Philosophical Undertones': 0.12, 'Psychological Nuance': 0.14, 'Subtle Social Commentary': 0.09
        }
        
        for selection in vibes + elementos + depths:
            match_rate = patterns_dict.get(selection, 0.15)
            matches = int(len(self.df) * match_rate)
            all_matches.append(matches)
        
        if not all_matches:
            return {'rarity': 'Explorer', 'percentile': 50, 'description': 'Charting new territories!'}
        
        avg_matches = np.mean(all_matches)
        total_books = len(self.df)
        rarity_score = (total_books - avg_matches) / total_books * 100
        
        if rarity_score > 85:
            return {'rarity': 'Ultra Rare Unicorn 🦄', 'percentile': int(rarity_score), 
                   'description': 'You have exquisitely unique taste! Only a few books will truly captivate you.'}
        elif rarity_score > 70:
            return {'rarity': 'Sophisticated Connoisseur 🍷', 'percentile': int(rarity_score),
                   'description': 'You appreciate the finer nuances that most readers miss.'}
        elif rarity_score > 50:
            return {'rarity': 'Discerning Reader 🎭', 'percentile': int(rarity_score),
                   'description': 'You know what you like and have refined preferences.'}
        elif rarity_score > 30:
            return {'rarity': 'Popular Taste 📈', 'percentile': int(rarity_score),
                   'description': 'You enjoy books that resonate with many readers.'}
        else:
            return {'rarity': 'Universal Appeal 🌟', 'percentile': int(rarity_score),
                   'description': 'You love the classics and crowd favorites!'}
    
    def predict_discovery_potential(self, vibes, elementos, depths):
        """Predict likelihood of discovering hidden gems vs popular hits"""
        discovery_indicators = {
            'hidden_gem_signals': ['Quietly Profound', 'Understated Intensity', 'Mysterious yet Cozy', 'Complex Characters'],
            'mainstream_signals': ['Tense yet Amusing', 'Unexpected Twists', 'Intense yet Hopeful', 'Immersive Worlds'],
            'literary_fiction_signals': ['Philosophical Undertones', 'Subtle Social Commentary', 'Layered Storytelling'],
            'genre_blend_signals': ['Fantastic yet Believable', 'Romantic yet Realistic']
        }
        
        all_selections = vibes + elementos + depths
        hidden_gems = sum(1 for s in all_selections if s in discovery_indicators['hidden_gem_signals'])
        mainstream = sum(1 for s in all_selections if s in discovery_indicators['mainstream_signals'])
        literary = sum(1 for s in all_selections if s in discovery_indicators['literary_fiction_signals'])
        genre_blend = sum(1 for s in all_selections if s in discovery_indicators['genre_blend_signals'])
        
        total = len(all_selections)
        if total == 0:
            return {'type': 'Open Explorer', 'likelihood': 50, 'description': 'Ready for any adventure!', 'prediction': 'Expect variety!'}
        
        if hidden_gems / total > 0.5:
            return {
                'type': 'Hidden Gem Hunter 💎',
                'likelihood': 85,
                'description': 'You\'re likely to discover underrated masterpieces that others overlook.',
                'prediction': 'Expect to find 2-3 books under 1000 ratings that will become your new favorites!'
            }
        elif literary / total > 0.4:
            return {
                'type': 'Literary Archaeologist 📚',
                'likelihood': 75,
                'description': 'You dig deep into meaningful, thought-provoking literature.',
                'prediction': 'Award-winning literary fiction and philosophical novels await you.'
            }
        elif mainstream / total > 0.6:
            return {
                'type': 'Crowd Pleaser Finder 🎯',
                'likelihood': 60,
                'description': 'You enjoy books that have wide appeal and proven track records.',
                'prediction': 'Bestsellers and highly-rated popular fiction will dominate your list.'
            }
        else:
            return {
                'type': 'Genre Boundary Crosser 🌈',
                'likelihood': 70,
                'description': 'You blend different styles and discover unique cross-genre gems.',
                'prediction': 'Expect surprising combinations that defy traditional categorization!'
            }
    
    def analyze_genre_crossover_potential(self, vibes, elementos, depths):
        """Analyze how likely the user is to enjoy books outside their comfort zone"""
        flexibility_scores = {
            'Fantastic yet Believable': 0.9, 'Profound yet Approachable': 0.8, 'Romantic yet Realistic': 0.7,
            'Complex Characters': 0.8, 'Immersive Worlds': 0.9, 'Layered Storytelling': 0.9,
            'Philosophical Undertones': 0.7, 'Psychological Nuance': 0.8
        }
        
        all_selections = vibes + elementos + depths
        if not all_selections:
            return {'flexibility': 'Unknown Explorer', 'score': 50, 'description': 'Ready to explore!', 'recommendation': 'Try anything!'}
        
        scores = [flexibility_scores.get(selection, 0.5) for selection in all_selections]
        avg_flexibility = np.mean(scores)
        
        if avg_flexibility > 0.8:
            return {
                'flexibility': 'Genre Shapeshifter 🦋',
                'score': int(avg_flexibility * 100),
                'description': 'You easily move between genres and find gems everywhere!',
                'recommendation': 'Try books tagged with multiple genres - you\'ll love the variety!'
            }
        elif avg_flexibility > 0.6:
            return {
                'flexibility': 'Adventurous Reader 🎢',
                'score': int(avg_flexibility * 100),
                'description': 'You\'re open to exploring beyond your comfort zone.',
                'recommendation': 'Mix in 1-2 books from unfamiliar genres for delightful surprises!'
            }
        else:
            return {
                'flexibility': 'Comfort Zone Dweller 🏠',
                'score': int(avg_flexibility * 100),
                'description': 'You prefer to stick with what you know you\'ll love.',
                'recommendation': 'That\'s perfectly fine! We\'ll find the best within your preferred style.'
            }
    
    def create_mood_mapping(self, vibes, elementos, depths):
        """Create a 'mood map' showing what emotional states these books serve"""
        mood_mappings = {
            'Mysterious yet Cozy': ['contemplative_evening', 'rainy_afternoon', 'bedtime_reading'],
            'Tense yet Amusing': ['commute_entertainment', 'weekend_adventure', 'stress_relief'],
            'Profound yet Approachable': ['personal_growth', 'quiet_morning', 'life_transition'],
            'Intense yet Hopeful': ['emotional_processing', 'need_inspiration', 'difficult_times'],
            'Romantic yet Realistic': ['relationship_reflection', 'emotional_connection', 'heart_warming'],
            'Fantastic yet Believable': ['imagination_escape', 'wonder_seeking', 'creative_inspiration'],
            'Clever Dialogues': ['intellectual_stimulation', 'social_energy', 'wit_appreciation'],
            'Unfolding Mysteries': ['puzzle_solving_mood', 'patient_discovery', 'mental_engagement'],
            'Complex Characters': ['human_understanding', 'empathy_building', 'psychological_insight'],
            'Immersive Worlds': ['complete_escape', 'world_building_love', 'transportation_need'],
            'Unexpected Twists': ['surprise_seeking', 'mind_bending', 'predictability_escape'],
            'Authentic Emotions': ['emotional_validation', 'feeling_connection', 'heart_opening']
        }
        
        all_moods = []
        for selection in vibes + elementos + depths:
            all_moods.extend(mood_mappings.get(selection, []))
        
        mood_counts = {}
        for mood in all_moods:
            mood_counts[mood] = mood_counts.get(mood, 0) + 1
        
        if mood_counts:
            top_moods = sorted(mood_counts.items(), key=lambda x: x[1], reverse=True)[:3]
            return {
                'primary_moods': [mood[0].replace('_', ' ').title() for mood in top_moods],
                'mood_versatility': len(set(all_moods)),
                'description': f'Your books serve {len(set(all_moods))} different emotional needs!'
            }
        else:
            return {'primary_moods': ['Exploration'], 'mood_versatility': 1, 'description': 'Ready for any reading adventure!'}
# --- Recommendation Logic Class ---
class IntuitiveBookFinder:
    def __init__(self, df, tfidf_matrix, vectorizer):
        self.df = df
        self.tfidf_matrix = tfidf_matrix
        self.vectorizer = vectorizer
        
        # Define vibe patterns
        self.vibes_patterns = {
            "Mysterious yet Cozy": {
                "keywords": ["mysterious", "cozy", "atmospheric", "intimate", "secrets", "warm", "comfort", "puzzle", "intrigue", "homey"],
                "weight": 1.5,
                "description": "Books that wrap you in intrigue but feel like coming home.",
                "icon": "fas fa-house-chimney-user"
            },
            "Tense yet Amusing": {
                "keywords": ["suspenseful", "witty", "entertaining", "thrilling", "humor", "fast paced", "exciting", "clever", "amusing", "engaging"],
                "weight": 1.3,
                "description": "Reads that keep you on the edge of your seat, but still make you chuckle.",
                "icon": "fas fa-face-grin-squint-tears"
            },
            "Profound yet Approachable": {
                "keywords": ["profound", "accessible", "thoughtful", "readable", "philosophical", "meaningful", "simple", "clear", "insightful", "gentle"],
                "weight": 1.4,
                "description": "Books that delve into deep themes without being a heavy read.",
                "icon": "fas fa-lightbulb"
            },
            "Intense yet Hopeful": {
                "keywords": ["intense", "powerful", "hopeful", "uplifting", "emotional", "inspiring", "dramatic", "moving", "optimistic", "resilient"],
                "weight": 1.2,
                "description": "Stories that hit you hard but leave you with a sense of optimism.",
                "icon": "fas fa-sun"
            },
            "Romantic yet Realistic": {
                "keywords": ["romantic", "realistic", "authentic", "genuine", "love", "relationship", "believable", "honest", "mature", "contemporary"],
                "weight": 1.3,
                "description": "Love stories that feel authentic and truly possible.",
                "icon": "fas fa-heart-circle-check"
            },
            "Fantastic yet Believable": {
                "keywords": ["fantasy", "magical", "believable", "grounded", "imaginative", "realistic", "wonder", "enchanting", "plausible", "vivid"],
                "weight": 1.4,
                "description": "Imaginative tales that could almost exist in our world.",
                "icon": "fas fa-wand-magic-sparkles"
            }
        }
        
        self.elementos_enganche = {
            "Clever Dialogues": {
                "keywords": ["dialogue", "conversation", "witty", "sharp", "banter", "clever", "verbal", "exchange", "talk", "discussion"],
                "weight": 1.5,
                "description": "Books where the conversations alone captivate you.",
                "icon": "fas fa-comments"
            },
            "Unfolding Mysteries": {
                "keywords": ["mystery", "reveals", "secrets", "unraveling", "clues", "discovery", "unveiling", "hidden", "solving", "truth"],
                "weight": 1.4,
                "description": "Reads that slowly unveil their secrets, one revelation at a time.",
                "icon": "fas fa-magnifying-glass"
            },
            "Complex Characters": {
                "keywords": ["complex", "flawed", "nuanced", "developed", "multifaceted", "realistic", "depth", "layered", "human", "relatable"],
                "weight": 1.6,
                "description": "Stories with people who feel real, flawed, and deeply intricate.",
                "icon": "fas fa-user-tie"
            },
            "Immersive Worlds": {
                "keywords": ["world", "setting", "atmosphere", "immersive", "vivid", "detailed", "rich", "environment", "place", "landscape"],
                "weight": 1.3,
                "description": "Books that completely transport you to another time or place.",
                "icon": "fas fa-earth-americas"
            },
            "Unexpected Twists": {
                "keywords": ["twist", "unexpected", "surprise", "shocking", "revelation", "turn", "unpredictable", "plot", "stunning", "jaw dropping"],
                "weight": 1.2,
                "description": "Reads that shock you when you least expect it.",
                "icon": "fas fa-shuffle"
            },
            "Authentic Emotions": {
                "keywords": ["emotional", "heartfelt", "genuine", "moving", "touching", "authentic", "real", "feelings", "poignant", "affecting"],
                "weight": 1.4,
                "description": "Books that stir genuine feelings and resonate deeply.",
                "icon": "fas fa-face-grin-hearts"
            }
        }
        
        # Literary Depths patterns
        self.literary_depths = {
            "Quietly Profound": {
                "keywords": ["subtle", "understated", "quiet", "contemplative", "reflective", "meditative", "nuanced", "gentle wisdom", "soft insights", "unspoken"],
                "weight": 1.4,
                "description": "Books that offer deep insights without dramatic flourishes.",
                "icon": "fas fa-dove"
            },
            "Layered Storytelling": {
                "keywords": ["layered", "multiple levels", "beneath surface", "hidden meanings", "symbolic", "metaphorical", "allegory", "subtext", "deeper meaning", "interconnected"],
                "weight": 1.5,
                "description": "Narratives that reveal new meanings with each reading.",
                "icon": "fas fa-layer-group"
            },
            "Understated Intensity": {
                "keywords": ["tension beneath", "quiet intensity", "simmering", "restrained", "controlled", "implicit", "unspoken tension", "subtle power", "contained emotion"],
                "weight": 1.3,
                "description": "Stories where the most powerful moments whisper rather than shout.",
                "icon": "fas fa-compress"
            },
            "Philosophical Undertones": {
                "keywords": ["philosophical", "existential", "moral questions", "ethical", "meaning of life", "human condition", "deeper questions", "thoughtful exploration", "wisdom"],
                "weight": 1.4,
                "description": "Books that explore life's big questions through compelling stories.",
                "icon": "fas fa-brain"
            },
            "Psychological Nuance": {
                "keywords": ["psychological", "mental landscape", "inner life", "consciousness", "psyche", "emotional complexity", "mental state", "introspection", "self awareness"],
                "weight": 1.5,
                "description": "Deep dives into the intricacies of human psychology.",
                "icon": "fas fa-head-side-virus"
            },
            "Subtle Social Commentary": {
                "keywords": ["social commentary", "society", "cultural critique", "social issues", "class", "inequality", "human nature", "civilization", "community", "social dynamics"],
                "weight": 1.3,
                "description": "Elegant examination of society and human relationships.",
                "icon": "fas fa-users"
            }
        }
    
    def find_books_by_preferences(self, selected_vibes, selected_elementos, selected_depths, num_books=8):
        """Enhanced method to handle all three preference types"""
        if not selected_vibes and not selected_elementos and not selected_depths:
            return self.df.sort_values(by='avg_rating', ascending=False).head(num_books).to_dict('records')
        
        preference_vector = np.zeros(self.tfidf_matrix.shape[1])
        
        for vibe in selected_vibes:
            if vibe in self.vibes_patterns:
                vibe_data = self.vibes_patterns[vibe]
                vibe_text = ' '.join(vibe_data['keywords'])
                vibe_vector = self.vectorizer.transform([vibe_text])
                preference_vector += vibe_vector.toarray().flatten() * vibe_data['weight']
        
        for elemento in selected_elementos:
            if elemento in self.elementos_enganche:
                elem_data = self.elementos_enganche[elemento]
                elem_text = ' '.join(elem_data['keywords'])
                elem_vector = self.vectorizer.transform([elem_text])
                preference_vector += elem_vector.toarray().flatten() * elem_data['weight']
        
        for depth in selected_depths:
            if depth in self.literary_depths:
                depth_data = self.literary_depths[depth]
                depth_text = ' '.join(depth_data['keywords'])
                depth_vector = self.vectorizer.transform([depth_text])
                preference_vector += depth_vector.toarray().flatten() * depth_data['weight']
        
        if np.linalg.norm(preference_vector) > 0:
            preference_vector = preference_vector / np.linalg.norm(preference_vector)
        else:
            return []
        similarities = cosine_similarity([preference_vector], self.tfidf_matrix).flatten()
        top_indices = similarities.argsort()[-num_books*3:][::-1]
        
        recommended_books = []
        for idx in top_indices:
            if similarities[idx] > 0.05:
                book = self.df.iloc[idx].copy()
                book['similarity_score'] = similarities[idx]
                book['match_percentage'] = min(100, similarities[idx] * 100 * 8)
                recommended_books.append(book)
            
            if len(recommended_books) >= num_books:
                break
        
        return recommended_books
# Initialize the Finder class and DNA Analyzer
finder = IntuitiveBookFinder(df, tfidf_matrix, vectorizer)
finder.dna_analyzer = ReadingDNAAnalyzer(df, tfidf_matrix, vectorizer)
# --- Dash Application Configuration ---
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.FLATLY, 
    "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"
])
app.title = "☀️ Summer Reading List Builder"
app.config.suppress_callback_exceptions = True
# Custom CSS
custom_css = """
body {
    font-family: 'Inter', sans-serif;
    background: linear-gradient(135deg, #007bff 0%, #6a00ff 100%);
    min-height: 100vh;
}
.hover-card:hover {
    transform: translateY(-5px) !important;
    box-shadow: 0 8px 25px rgba(0,0,0,0.15) !important;
    transition: all 0.3s ease !important;
}
.shadow-custom {
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1) !important;
}
.btn-primary:hover {
    transform: translateY(-2px) !important;
    box-shadow: 0 6px 20px rgba(255,165,0,0.4) !important;
}
.gradient-text {
    background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
    font-weight: bold;
    font-size: 3rem;
}
.card-hover {
    transition: all 0.3s ease;
    border-radius: 10px;
}
.card-hover:hover {
    transform: translateY(-3px);
    box-shadow: 0 6px 20px rgba(0,0,0,0.1);
}
.selection-cards-container {
    height: 400px;
    overflow-y: auto;
    padding-right: 15px;
}
.selection-cards-container::-webkit-scrollbar {
    width: 8px;
}
.selection-cards-container::-webkit-scrollbar-track {
    background: #f1f1f1;
    border-radius: 10px;
}
.selection-cards-container::-webkit-scrollbar-thumb {
    background: #888;
    border-radius: 10px;
}
.selection-cards-container::-webkit-scrollbar-thumb:hover {
    background: #555;
}
"""
# --- DNA Analysis Interface Functions ---
def create_empty_analysis_interface():
    """Interface when no selections are made"""
    return html.Div([
        html.Div([
            html.I(className="fas fa-dna fa-3x text-muted mb-3"),
            html.H4("🧬 Your Reading DNA Awaits Discovery", className="text-muted mb-3"),
            html.P("Select your preferences to unlock your unique literary genetic profile!", 
                   className="text-muted")
        ], className="text-center py-5")
    ])
def create_dna_analysis_interface(dna_analysis):
    """Create the innovative DNA analysis interface"""
    return html.Div([
        # Header
        html.Div([
            html.H4([
                html.I(className="fas fa-dna me-2", style={'color': '#9C27B0'}),
                "🧬 Your Reading DNA Profile"
            ], className="mb-3 text-center", style={'color': '#333', 'font-weight': 'bold'}),
            html.P("Your unique literary genetic makeup revealed!", 
                   className="text-center text-muted mb-4", style={'font-style': 'italic'})
        ]),
        
        # DNA Cards Row 1: Personality & Rarity
        dbc.Row([
            dbc.Col([
                dbc.Card([
                    dbc.CardBody([
                        html.H6([
                            html.I(className="fas fa-user-circle me-2"),
                            "Reader Personality"
                        ], className="card-title", style={'color': '#E91E63', 'font-weight': 'bold'}),
                        html.H5(dna_analysis['personality_type']['type'], 
                                className="mb-2", style={'color': '#333'}),
                        html.P(f"Confidence: {dna_analysis['personality_type']['confidence']}%", 
                               className="text-muted small"),
                        dbc.Progress(
                            value=dna_analysis['personality_type']['confidence'],
                            color="info",
                            className="mb-2",
                            style={'height': '6px'}
                        )
                    ])
                ], className="h-100 shadow-sm", style={'border-left': '4px solid #E91E63'})
            ], width=12, md=6, className="mb-3"),
            
            dbc.Col([
                dbc.Card([
                    dbc.CardBody([
                        html.H6([
                            html.I(className="fas fa-gem me-2"),
                            "Taste Rarity Index"
                        ], className="card-title", style={'color': '#9C27B0', 'font-weight': 'bold'}),
                        html.H5(dna_analysis['rarity_index']['rarity'], 
                                className="mb-2", style={'color': '#333'}),
                        html.P(f"{dna_analysis['rarity_index']['percentile']}th percentile", 
                               className="text-muted small"),
                        html.P(dna_analysis['rarity_index']['description'], 
                               className="small", style={'font-size': '0.85rem', 'line-height': '1.3'})
                    ])
                ], className="h-100 shadow-sm", style={'border-left': '4px solid #9C27B0'})
            ], width=12, md=6, className="mb-3")
        ]),
        
        # DNA Cards Row 2: Discovery & Flexibility
        dbc.Row([
            dbc.Col([
                dbc.Card([
                    dbc.CardBody([
                        html.H6([
                            html.I(className="fas fa-compass me-2"),
                            "Discovery Potential"
                        ], className="card-title", style={'color': '#FF9800', 'font-weight': 'bold'}),
                        html.H5(dna_analysis['discovery_potential']['type'], 
                                className="mb-2", style={'color': '#333'}),
                        html.P(f"Likelihood: {dna_analysis['discovery_potential']['likelihood']}%", 
                               className="text-muted small mb-2"),
                        html.P(dna_analysis['discovery_potential']['prediction'], 
                               className="small", style={'font-size': '0.85rem', 'line-height': '1.3'})
                    ])
                ], className="h-100 shadow-sm", style={'border-left': '4px solid #FF9800'})
            ], width=12, md=6, className="mb-3"),
            
            dbc.Col([
                dbc.Card([
                    dbc.CardBody([
                        html.H6([
                            html.I(className="fas fa-shuffle me-2"),
                            "Genre Flexibility"
                        ], className="card-title", style={'color': '#4CAF50', 'font-weight': 'bold'}),
                        html.H5(dna_analysis['genre_crossover']['flexibility'], 
                                className="mb-2", style={'color': '#333'}),
                        html.P(f"Adaptability: {dna_analysis['genre_crossover']['score']}%", 
                               className="text-muted small"),
                        dbc.Progress(
                            value=dna_analysis['genre_crossover']['score'],
                            color="success",
                            className="mb-2",
                            style={'height': '6px'}
                        ),
                        html.P(dna_analysis['genre_crossover']['recommendation'], 
                               className="small", style={'font-size': '0.85rem', 'line-height': '1.3'})
                    ])
                ], className="h-100 shadow-sm", style={'border-left': '4px solid #4CAF50'})
            ], width=12, md=6, className="mb-3")
        ]),
        
        # Mood Map Section
        html.Hr(className="my-3"),
        dbc.Row([
            dbc.Col([
                dbc.Card([
                    dbc.CardBody([
                        html.H6([
                            html.I(className="fas fa-heart me-2"),
                            "Reading Mood Map"
                        ], className="card-title", style={'color': '#FF5722', 'font-weight': 'bold'}),
                        html.P(dna_analysis['reading_mood_map']['description'], 
                               className="mb-3", style={'font-size': '0.9rem'}),
                        html.Div([
                            dbc.Badge(mood, color="primary", className="me-2 mb-2", 
                                     style={'font-size': '0.8rem', 'padding': '5px 10px'})
                            for mood in dna_analysis['reading_mood_map']['primary_moods']
                        ])
                    ])
                ], className="shadow-sm", style={'border-left': '4px solid #FF5722'})
            ], width=12)
        ])
    ])
# --- Application Layout ---
app.layout = html.Div([
    # Store components for maintaining state
    dcc.Store(id='last-recommendations', data=[]),
    dcc.Store(id='last-selections', data={'vibes': [], 'elementos': [], 'depths': []}),
    dcc.Download(id="download-csv"),
    dcc.Download(id="download-txt"),
    
    # Custom CSS
    html.Link(
        rel='stylesheet',
        href='data:text/css;charset=utf-8,' + custom_css.replace('\n', ' '),
    ),
    
    dbc.Container([
        # Header
        dbc.Row([
            dbc.Col([
                html.Div([
                    # Fila del título principal con icono
                    dbc.Row([
                        dbc.Col([
                            html.H1("☀️ Your Perfect Summer Reading List Awaits",
                                     className="mb-0",
                                     style={
                                         'color': 'white',
                                         'font-weight': 'bold',
                                         'font-size': '2.3rem',
                                         'text-align': 'left',
                                         'text-shadow': '2px 2px 4px rgba(0,0,0,0.3)'
                                     })
                        ], width=8, className="d-flex align-items-center"),
                        dbc.Col([
                            html.Div([
                                html.I(className="fas fa-books", 
                                       style={
                                           'font-size': '4rem',
                                           'color': '#FFD700',
                                           'text-shadow': '2px 2px 4px rgba(0,0,0,0.3)'
                                       }),
                                html.I(className="fas fa-book-open ms-3", 
                                       style={
                                           'font-size': '3.5rem',
                                           'color': '#FFD700',
                                           'text-shadow': '2px 2px 4px rgba(0,0,0,0.3)'
                                       })
                            ], style={'text-align': 'right', 'padding-right': '0px'})
                        ], width=4, className="d-flex align-items-center justify-content-end")
                    ], className="align-items-center mb-3"),
                    # Fila del subtítulo centrado
                    dbc.Row([
                        dbc.Col([
                            html.P("Curate your perfect literary escape for the sun-drenched days ahead.",
                                    className="text-muted mb-4 text-center",
                                    style={'font-size': '1.2rem', 'font-style': 'italic'})
                        ], width=12)
                    ])
                ], style={
                    'padding': '25px 35px',
                    'background': 'linear-gradient(135deg, #FFE066 0%, #FF6B35 25%, #F7931E 50%, #FFD23F 75%, #06FFA5 100%)',
                    'border-radius': '20px',
                    'box-shadow': '0 10px 30px rgba(255, 107, 53, 0.4), 0 6px 20px rgba(0, 0, 0, 0.15)',
                    'border': '2px solid rgba(255, 255, 255, 0.3)',
                    'backdrop-filter': 'blur(10px)'
                })
            ])
        ], className="mb-5"),
                
        # Main controls row
        dbc.Row(className="mb-5", style={'min-height': '650px'}, children=[
            # Left Column: Selection
            dbc.Col(className="d-flex flex-column", width=12, lg=6, children=[
                dbc.Card(className="shadow-custom flex-grow-1 d-flex flex-column", style={'border-radius': '10px'}, children=[
                    dbc.CardHeader([
                        html.H3("✨ Curate Your Reading Experience", 
                                 style={'color': 'white', 'font-weight': 'bold'}),
                        html.P("Choose your preferred reading style",
                                 className="text-white-50", style={'font-size': '1.1rem'})
                    ], style={'background': 'linear-gradient(135deg, #4CAF50 0%, #81C784 100%)', 'color': 'white', 'border-radius': '10px 10px 0 0'}),
                    
                    dbc.CardBody(className="flex-grow-1 overflow-auto", children=[
                        # Mode selection with reset button
                        html.Div([
                            dcc.RadioItems(
                                id='selection-mode-radio',
                                options=[
                                    {'label': '🌈 Reading Vibes', 'value': 'vibes'},
                                    {'label': '🎣 Engaging Elements', 'value': 'elements'},
                                    {'label': '🎭 Literary Depths', 'value': 'depths'}
                                ],
                                value='vibes',
                                inline=True,
                                className="mb-3 d-flex justify-content-around flex-wrap",
                                inputStyle={"margin-right": "8px"},
                                labelStyle={"margin-right": "15px", "font-weight": "bold", "font-size": "1rem"}
                            ),
                            # Reset button
                            dbc.Button(
                                [html.I(className="fas fa-broom me-2"), "Clear All Selections"],
                                id='reset-selections-btn',
                                color='warning',
                                size='sm',
                                className='w-100 mb-4',
                                style={'border-radius': '6px', 'font-weight': 'bold'}
                            )
                        ]),
                        
                        # Vibes container
                        html.Div(id='vibes-container', className="selection-cards-container", children=[
                            dbc.Row(justify="center", children=[
                                dbc.Col([
                                    dbc.Card([
                                        dbc.CardBody([
                                            html.Div([
                                                dbc.Checkbox(
                                                    id=f"vibe-{vibe.replace(' ', '-').lower()}",
                                                    label=html.Span([
                                                        html.I(className=data['icon'], style={'margin-right': '10px'}),
                                                        html.Strong(vibe, style={'font-size': '1.1rem'}),
                                                    ]),
                                                    value=False,
                                                    className="mb-2"
                                                ),
                                                html.P(data['description'], 
                                                        className="text-muted mt-2",
                                                        style={'font-size': '0.95rem', 'line-height': '1.4'})
                                            ])
                                        ])
                                    ], color="light", outline=True, className="h-100 shadow-sm card-hover",
                                    style={'transition': 'all 0.3s ease', 'border-width': '2px', 'border-radius': '8px'})
                                ], width=10, className="mb-3")
                                for vibe, data in finder.vibes_patterns.items()
                            ])
                        ]),
                        
                        # Elements container
                        html.Div(id='elements-container', className="selection-cards-container", style={'display': 'none'}, children=[
                            dbc.Row(justify="center", children=[
                                dbc.Col([
                                    dbc.Card([
                                        dbc.CardBody([
                                            html.Div([
                                                dbc.Checkbox(
                                                    id=f"elemento-{elemento.replace(' ', '-').lower()}",
                                                    label=html.Span([
                                                        html.I(className=data['icon'], style={'margin-right': '10px'}),
                                                        html.Strong(elemento, style={'font-size': '1.1rem'}),
                                                    ]),
                                                    value=False,
                                                    className="mb-2"
                                                ),
                                                html.P(data['description'], 
                                                        className="text-muted mt-2",
                                                        style={'font-size': '0.95rem', 'line-height': '1.4'})
                                            ])
                                        ])
                                    ], color="light", outline=True, className="h-100 shadow-sm card-hover",
                                    style={'transition': 'all 0.3s ease', 'border-width': '2px', 'border-radius': '8px'})
                                ], width=10, className="mb-3")
                                for elemento, data in finder.elementos_enganche.items()
                            ])
                        ]),
                        
                        # Depths container
                        html.Div(id='depths-container', className="selection-cards-container", style={'display': 'none'}, children=[
                            dbc.Row(justify="center", children=[
                                dbc.Col([
                                    dbc.Card([
                                        dbc.CardBody([
                                            html.Div([
                                                dbc.Checkbox(
                                                    id=f"depth-{depth.replace(' ', '-').lower()}",
                                                    label=html.Span([
                                                        html.I(className=data['icon'], style={'margin-right': '10px'}),
                                                        html.Strong(depth, style={'font-size': '1.1rem'}),
                                                    ]),
                                                    value=False,
                                                    className="mb-2"
                                                ),
                                                html.P(data['description'], 
                                                        className="text-muted mt-2",
                                                        style={'font-size': '0.95rem', 'line-height': '1.4'})
                                            ])
                                        ])
                                    ], color="light", outline=True, className="h-100 shadow-sm card-hover",
                                    style={'transition': 'all 0.3s ease', 'border-width': '2px', 'border-radius': '8px'})
                                ], width=10, className="mb-3")
                                for depth, data in finder.literary_depths.items()
                            ])
                        ])
                    ])
                ])
            ]),
            
            # Right Column: Search Options and DNA Analysis
            dbc.Col(className="d-flex flex-column", width=12, lg=6, children=[
                dbc.Card(className="shadow-custom flex-grow-1 d-flex flex-column", style={'border-radius': '10px'}, children=[
                    dbc.CardHeader([
                        html.H3("⚙️ Refine Your Search", 
                                 style={'color': 'white', 'font-weight': 'bold'}),
                        html.P("Specify how many literary treasures you seek", 
                                 className="text-white-50", style={'font-size': '1.1rem'})
                    ], style={'background': 'linear-gradient(135deg, #2196F3 0%, #64B5F6 100%)', 'color': 'white', 'border-radius': '10px 10px 0 0'}),
                    dbc.CardBody(className="flex-grow-1 overflow-auto", children=[
                        dbc.Row(align="center", className="g-3 mb-4", children=[
                            dbc.Col([
                                html.Label("How many books?", 
                                           className="fw-bold mb-2", 
                                           style={'font-size': '1.2rem', 'color': '#333'}),
                                dcc.Input(
                                    id='num-books-input',
                                    type='number',
                                    min=5, max=12, step=1, value=8,
                                    className="form-control", 
                                    style={'border-radius': '8px', 'padding': '5px 8px', 'font-size': '0.9rem', 'width': '60px'}
                                )
                            ], width=12, md=6),
                            dbc.Col([
                                dbc.Button(
                                    [
                                        html.I(className="fas fa-search me-2"),
                                        "Uncover My Perfect Books"
                                    ],
                                    id='find-books-btn',
                                    color='primary',
                                    size='lg',
                                    className='w-100 shadow-custom', 
                                    style={
                                        'background': 'linear-gradient(135deg, #FFD700 0%, #FFA500 100%)',
                                        'border': 'none',
                                        'font-weight': 'bold',
                                        'font-size': '1.1rem',
                                        'padding': '12px 20px',
                                        'transition': 'all 0.3s ease',
                                        'border-radius': '8px'
                                    }
                                )
                            ], width=12, md=6)
                        ]),
                        
                        # Separator
                        html.Hr(className="my-4"),
                        # DNA Analysis Container
                        html.Div([
                            dbc.CardBody(id='analysis-container', style={'maxHeight': '350px', 'overflowY': 'auto', 'paddingRight': '15px'}, 
                                        children=[create_empty_analysis_interface()])
                        ])
                    ])
                ])
            ])
        ]), 
        
        # Results Row
        dbc.Row([
            dbc.Col([
                html.Div(id='results-container')
            ])
        ]),
        
        # Footer
        dbc.Row([
            dbc.Col([
                html.Hr(className="my-4", style={'border-color': '#dee2e6', 'opacity': '0.5'}),
                dbc.Card([
                    dbc.CardBody([
                        html.Div([
                            html.H5("☀️ Summer Reading List Builder", 
                                   className="text-center mb-3",
                                   style={'color': '#495057', 'font-weight': 'bold'}),
                            html.P([
                                "Built with ",
                                html.Strong("Python"), "|",
                                html.Strong("Pandas"),  "|",
                                 html.Strong("Scikit-learn"), "|",
                                html.Strong("Dash"), 
                               
                                
                            ], className="text-center text-muted mb-2"),
                            html.P([
                                "Developed by ",
                                html.Strong("Alexander Cabrera", style={'color': '#007bff'})
                            ], className="text-center text-muted mb-0")
                        ], className="text-center")
                    ], style={'padding': '20px'})
                ], className="shadow-sm", 
                   style={'background': 'linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%)', 
                          'border': '1px solid #dee2e6', 
                          'border-radius': '10px'})
            ])
        ], className="mt-4"),
        # Container for the book details modal
        html.Div(id='book-modal-container', children=dbc.Modal(id="book-modal", is_open=False))
        
    ], fluid=True, style={
        'padding': '10px',
        'background': 'linear-gradient(135deg, #FAF7F0 0%, #F0E6D6 25%, #E8DCC0 50%, #DDD1B8 100%)'#cafe literario
    })
])
# --- Application Callbacks ---
# Callback to control visibility of sections
@app.callback(
    Output('vibes-container', 'style'),
    Output('elements-container', 'style'),
    Output('depths-container', 'style'),
    Input('selection-mode-radio', 'value')
)
def toggle_sections_visibility(selected_mode):
    base_style = {'height': '400px', 'overflowY': 'auto', 'paddingRight': '15px'}
    
    vibes_style = base_style.copy()
    elements_style = base_style.copy()
    depths_style = base_style.copy()
    if selected_mode == 'vibes':
        elements_style['display'] = 'none'
        depths_style['display'] = 'none'
    elif selected_mode == 'elements':
        vibes_style['display'] = 'none'
        depths_style['display'] = 'none'
    elif selected_mode == 'depths':
        vibes_style['display'] = 'none'
        elements_style['display'] = 'none'
    
    return vibes_style, elements_style, depths_style
# Reset selections callback
@app.callback(
    [Output(f"vibe-{vibe.replace(' ', '-').lower()}", 'value') for vibe in finder.vibes_patterns.keys()] +
    [Output(f"elemento-{elemento.replace(' ', '-').lower()}", 'value') for elemento in finder.elementos_enganche.keys()] +
    [Output(f"depth-{depth.replace(' ', '-').lower()}", 'value') for depth in finder.literary_depths.keys()],
    Input('reset-selections-btn', 'n_clicks'),
    prevent_initial_call=True
)
def reset_all_selections(n_clicks):
    if n_clicks:
        return [False] * (len(finder.vibes_patterns) + len(finder.elementos_enganche) + len(finder.literary_depths))
    return [False] * (len(finder.vibes_patterns) + len(finder.elementos_enganche) + len(finder.literary_depths))
# Main search callback with DNA Analysis
@app.callback(
    Output('results-container', 'children'),
    Output('book-modal', 'is_open', allow_duplicate=True), 
    Output('book-modal-container', 'children', allow_duplicate=True), 
    Output('analysis-container', 'children'),
    Output('last-recommendations', 'data'),
    Output('last-selections', 'data'),
    Input('find-books-btn', 'n_clicks'),
    [State(f"vibe-{vibe.replace(' ', '-').lower()}", 'value') 
     for vibe in finder.vibes_patterns.keys()] +
    [State(f"elemento-{elemento.replace(' ', '-').lower()}", 'value') 
     for elemento in finder.elementos_enganche.keys()] +
    [State(f"depth-{depth.replace(' ', '-').lower()}", 'value') 
     for depth in finder.literary_depths.keys()] +
    [State('num-books-input', 'value')], 
    prevent_initial_call=True 
)
def find_and_display_books(n_clicks, *args):
    if not n_clicks:
        return html.Div(), False, dbc.Modal(id="book-modal", is_open=False), create_empty_analysis_interface(), [], {'vibes': [], 'elementos': [], 'depths': []}
    
    num_books = args[-1]
    
    # Parse arguments
    vibe_values = args[:len(finder.vibes_patterns)]
    elemento_values = args[len(finder.vibes_patterns):len(finder.vibes_patterns) + len(finder.elementos_enganche)]
    depth_values = args[len(finder.vibes_patterns) + len(finder.elementos_enganche):-1]
    
    selected_vibes = [vibe for vibe, selected in zip(finder.vibes_patterns.keys(), vibe_values) if selected]
    selected_elementos = [elemento for elemento, selected in zip(finder.elementos_enganche.keys(), elemento_values) if selected]
    selected_depths = [depth for depth, selected in zip(finder.literary_depths.keys(), depth_values) if selected]
    
    # Store selections for export
    selections_data = {
        'vibes': selected_vibes,
        'elementos': selected_elementos,
        'depths': selected_depths
    }
    
    # ============ DNA ANALYSIS MAGIC ============
    if selected_vibes or selected_elementos or selected_depths:
        dna_analysis = finder.dna_analyzer.analyze_reading_dna(selected_vibes, selected_elementos, selected_depths)
        analysis_content = create_dna_analysis_interface(dna_analysis)
    else:
        analysis_content = create_empty_analysis_interface()
    # ==========================================
    
    # Handle no selections
    if not selected_vibes and not selected_elementos and not selected_depths:
        alert_message = dbc.Alert([
            html.H4("🤔 What Sparks Your Interest?"),
            html.P("Select at least one preference to discover books perfectly suited for you.")
        ], color="info", className="text-center")
        return alert_message, False, dbc.Modal(id="book-modal", is_open=False), analysis_content, [], selections_data
    
    # Get recommendations
    recommended_books = finder.find_books_by_preferences(selected_vibes, selected_elementos, selected_depths, num_books)
    
    if not recommended_books:
        alert_message = dbc.Alert([
            html.H4("😔 No Matches Found"),
            html.P("Try different combinations of preferences.")
        ], color="warning", className="text-center")
        return alert_message, False, dbc.Modal(id="book-modal", is_open=False), analysis_content, [], selections_data
    
    # Convert recommended_books to proper format for storage
    books_for_storage = []
    for book in recommended_books:
        if hasattr(book, 'to_dict'):
            book_dict = book.to_dict()
        else:
            book_dict = dict(book)
        books_for_storage.append(book_dict)
    
    # Create result cards
    result_cards = []
    for i, book in enumerate(recommended_books):
        if book['match_percentage'] > 70:
            border_color = "#66BB6A"
            badge_color = "success"
        elif book['match_percentage'] > 50:
            border_color = "#FFCA28"
            badge_color = "warning"
        else:
            border_color = "#42A5F5"
            badge_color = "info"
        
        card = dbc.Col([
            dbc.Card([
                dbc.CardHeader([
                    html.Div([
                        html.H6(f"📚 Match #{i+1}", className="mb-0"),
                        dbc.Badge(f"{book['match_percentage']:.0f}% Match", color=badge_color)
                    ], className="d-flex justify-content-between align-items-center")
                ], style={'background': 'linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%)', 'border-radius': '8px 8px 0 0'}),
                dbc.CardBody([
                    html.H5(book['original_title'], className="card-title fw-bold text-truncate mb-1"), 
                    html.P(f"✍️ Author: {book['author']}", className="text-muted mb-2"),
                    html.P(f"⭐ Average Rating: {book['avg_rating']:.1f}/5 ({int(book['ratings_count'])} votes)", className="mb-3"), 
                    dbc.Button(
                        "View Details",
                        id={'type': 'open-book-modal', 'index': str(book.name)}, 
                        color="secondary",
                        className="mt-auto w-100", 
                        n_clicks=0,
                        style={'border-radius': '5px'}
                    )
                ])
            ], className="h-100 card-hover d-flex flex-column", style={'border': f'3px solid {border_color}', 'border-radius': '10px'})
        ], width=12, sm=6, md=4, lg=3, className="mb-4")
        result_cards.append(card)
    
    results_display = html.Div([
        dbc.Card([
            dbc.CardHeader([
                html.Div([
                    html.H2([
                        html.I(className="fas fa-star me-3"),
                        "Your Perfect Books Await"
                    ], className="text-center mb-0", 
                       style={'color': 'white', 'font-weight': 'bold'}),
                    html.P(f"We found {len(recommended_books)} books that align with your preferences.", 
                            className="text-center mb-0 mt-2", 
                            style={'color': 'rgba(255,255,255,0.9)', 'font-size': '1.1rem'})
                ])
            ], style={'background': 'linear-gradient(135deg, #FFD700 0%, #FFA500 100%)', 'padding': '30px', 'border-radius': '10px 10px 0 0'}),
            dbc.CardBody([
                html.Div([
                    dbc.ButtonGroup([
                        dbc.Button(
                            [html.I(className="fas fa-download me-2"), "Download CSV"],
                            id="download-csv-btn",
                            color="success",
                            outline=True,
                            size="sm"
                        ),
                        dbc.Button(
                            [html.I(className="fas fa-file-text me-2"), "Download TXT"],
                            id="download-txt-btn", 
                            color="info",
                            outline=True,
                            size="sm"
                        )
                    ], className="mb-4")
                ], className="text-center"),
                
                dbc.Row(result_cards) 
            ], style={'padding': '30px'})
        ], className="shadow-lg", style={'border-radius': '10px'})
    ])
    
    return results_display, False, dbc.Modal(id="book-modal", is_open=False), analysis_content, books_for_storage, selections_data
# Book modal callbacks
@app.callback(
    Output('book-modal-container', 'children', allow_duplicate=True), 
    Output('book-modal', 'is_open', allow_duplicate=True), 
    Input({'type': 'open-book-modal', 'index': ALL}, 'n_clicks'), 
    State('book-modal', 'is_open'),
    prevent_initial_call=True
)
def open_book_modal(n_clicks, is_open):
    if not any(n_clicks) or all(n is None for n in n_clicks):
        raise dash.exceptions.PreventUpdate
    ctx = dash.callback_context
    if not ctx.triggered:
        raise dash.exceptions.PreventUpdate
    triggered_id = ctx.triggered[0]['prop_id'].split('.')[0]
    book_index = eval(triggered_id)['index']
    
    try:
        book_index = int(book_index)
        book = df.iloc[book_index]
        
        # Crear el contenido del modal
        modal_content = dbc.Modal([
            dbc.ModalHeader([
                html.H4([
                    html.I(className="fas fa-book-open me-2"),
                    book['original_title']
                ], className="modal-title")
            ]),
            dbc.ModalBody([
                dbc.Row([
                    dbc.Col([
                        html.H5("📖 Book Details", className="mb-3"),
                        html.P([html.Strong("Author: "), book['author']]),
                        html.P([html.Strong("Average Rating: "), f"{book['avg_rating']:.1f}/5"]),
                        html.P([html.Strong("Total Ratings: "), f"{int(book['ratings_count']):,}"]),
                        html.P([html.Strong("Pages: "), f"{int(book['num_pages']) if book['num_pages'] > 0 else 'N/A'}"]),
                        html.P([html.Strong("Publication Year: "), f"{int(book['original_publication_year']) if pd.notna(book['original_publication_year']) else 'N/A'}"]),
                    ], width=12)
                ]),
                html.Hr(),
                html.H5("📝 Description", className="mb-3"),
                html.P(book['description'][:500] + "..." if len(str(book['description'])) > 500 else book['description'],
                       style={'line-height': '1.6', 'text-align': 'justify'})
            ]),
            dbc.ModalFooter([
                dbc.Button("Close", id="close-modal", className="ms-auto", color="secondary")
            ])
        ], id="book-modal", is_open=True, size="lg")
        
        return modal_content, True
        
    except Exception as e:
        # En caso de error, mostrar modal de error
        error_modal = dbc.Modal([
            dbc.ModalHeader([
                html.H4("Error", className="modal-title")
            ]),
            dbc.ModalBody([
                html.P("Could not load book details. Please try again.")
            ]),
            dbc.ModalFooter([
                dbc.Button("Close", id="close-modal", className="ms-auto", color="secondary")
            ])
        ], id="book-modal", is_open=True)
        
        return error_modal, True
# Callback para cerrar el modal
@app.callback(
    Output('book-modal', 'is_open', allow_duplicate=True),
    Input('close-modal', 'n_clicks'),
    State('book-modal', 'is_open'),
    prevent_initial_call=True
)
def close_modal(n_clicks, is_open):
    if n_clicks:
        return False
    return is_open
# Callback para descargar CSV
@app.callback(
    Output("download-csv", "data"),
    Input("download-csv-btn", "n_clicks"),
    State('last-recommendations', 'data'),
    State('last-selections', 'data'),
    prevent_initial_call=True
)
def download_csv(n_clicks, recommendations, selections):
    if n_clicks and recommendations:
        # Convertir recomendaciones a DataFrame
        df_export = pd.DataFrame(recommendations)
        
        # Seleccionar columnas relevantes para exportar
        columns_to_export = ['original_title', 'author', 'avg_rating', 'ratings_count', 
                           'num_pages', 'original_publication_year', 'match_percentage']
        
        # Filtrar columnas que existen
        available_columns = [col for col in columns_to_export if col in df_export.columns]
        df_filtered = df_export[available_columns]
        
        # Agregar información de selecciones como metadatos
        timestamp = pd.Timestamp.now().strftime("%Y%m%d_%H%M%S")
        filename = f"summer_reading_list_{timestamp}.csv"
        
        return dcc.send_data_frame(df_filtered.to_csv, filename, index=False)
# Callback para descargar TXT
@app.callback(
    Output("download-txt", "data"),
    Input("download-txt-btn", "n_clicks"),
    State('last-recommendations', 'data'),
    State('last-selections', 'data'),
    prevent_initial_call=True
)
def download_txt(n_clicks, recommendations, selections):
    if n_clicks and recommendations:
        # Crear contenido del archivo de texto
        content = "☀️ YOUR SUMMER READING LIST ☀️\n"
        content += "=" * 50 + "\n\n"
        
        # Agregar información de selecciones
        if selections['vibes']:
            content += f"🌈 Selected Vibes: {', '.join(selections['vibes'])}\n"
        if selections['elementos']:
            content += f"🎣 Selected Elements: {', '.join(selections['elementos'])}\n"
        if selections['depths']:
            content += f"🎭 Selected Depths: {', '.join(selections['depths'])}\n"
        content += "\n" + "-" * 50 + "\n\n"
        
        # Agregar libros recomendados
        for i, book in enumerate(recommendations, 1):
            content += f"{i}. {book.get('original_title', 'N/A')}\n"
            content += f"   Author: {book.get('author', 'N/A')}\n"
            content += f"   Rating: {book.get('avg_rating', 0):.1f}/5 ({book.get('ratings_count', 0)} votes)\n"
            content += f"   Match: {book.get('match_percentage', 0):.0f}%\n"
            if book.get('num_pages', 0) > 0:
                content += f"   Pages: {int(book['num_pages'])}\n"
            content += "\n"
        
        content += "\n" + "=" * 50 + "\n"
        content += "Generated by Summer Reading List Builder\n"
        content += f"Created on: {pd.Timestamp.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
        
        timestamp = pd.Timestamp.now().strftime("%Y%m%d_%H%M%S")
        filename = f"summer_reading_list_{timestamp}.txt"
        
        return dict(content=content, filename=filename)