import pygame
import random
import math
import os
import time
from dataclasses import dataclass
from typing import List, Optional

# --- CONFIGURATION INITIALE ---
WIDTH, HEIGHT = 720, 720
FPS = 60

DEBUG_DIR = "debug_screenshots"
if not os.path.exists(DEBUG_DIR):
    os.makedirs(DEBUG_DIR)

# Couleurs
WHITE = (255, 255, 255)
BLACK = (10, 10, 20)
GRAY = (50, 50, 50)
LIGHT_GRAY = (150, 150, 150)
NEON_BLUE = (0, 255, 255)
NEON_PINK = (255, 20, 147)
NEON_GREEN = (57, 255, 20)
NEON_YELLOW = (255, 255, 0)
NEON_ORANGE = (255, 165, 0)
RED = (255, 0, 0)

# --- REPERTOIRES ---
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
ASSETS_DIR = os.path.join(BASE_DIR, "assets")

# --- LISTE DES AMÉLIORATIONS (UPGRADES) ---
# Format: 10 compétences avec 5 niveaux chacune (Total: 50)
ALL_UPGRADES = [
    # SPEED
    {"id": "speed_1", "name": "Vitesse Lvl.1", "desc": "Augmente ta vitesse de deplacement (+30)", "type": "speed", "level": 1},
    {"id": "speed_2", "name": "Vitesse Lvl.2", "desc": "Augmente encore ta vitesse (+40)", "type": "speed", "level": 2},
    {"id": "speed_3", "name": "Vitesse Lvl.3", "desc": "Deplacement tres rapide (+40)", "type": "speed", "level": 3},
    {"id": "speed_4", "name": "Vitesse Lvl.4", "desc": "Vitesse extreme (+50)", "type": "speed", "level": 4},
    {"id": "speed_5", "name": "Vitesse MAX", "desc": "Vitesse fulgurante (+60)", "type": "speed", "level": 5},
    
    # FIRE RATE
    {"id": "firerate_1", "name": "Cadence Lvl.1", "desc": "Tire plus vite (-20ms)", "type": "firerate", "level": 1},
    {"id": "firerate_2", "name": "Cadence Lvl.2", "desc": "Cadence acceleree (-30ms)", "type": "firerate", "level": 2},
    {"id": "firerate_3", "name": "Cadence Lvl.3", "desc": "Tir rapide (-30ms)", "type": "firerate", "level": 3},
    {"id": "firerate_4", "name": "Cadence Lvl.4", "desc": "Mitrailleuse (-30ms)", "type": "firerate", "level": 4},
    {"id": "firerate_5", "name": "Cadence MAX", "desc": "Pluie de tirs (-40ms)", "type": "firerate", "level": 5},
    
    # DAMAGE
    {"id": "damage_1", "name": "Degats Lvl.1", "desc": "Degats +5", "type": "damage", "level": 1},
    {"id": "damage_2", "name": "Degats Lvl.2", "desc": "Degats +5", "type": "damage", "level": 2},
    {"id": "damage_3", "name": "Degats Lvl.3", "desc": "Degats +10", "type": "damage", "level": 3},
    {"id": "damage_4", "name": "Degats Lvl.4", "desc": "Degats +15", "type": "damage", "level": 4},
    {"id": "damage_5", "name": "Degats MAX", "desc": "Degats extrêmes +25", "type": "damage", "level": 5},
    
    # MAX HEALTH
    {"id": "maxhealth_1", "name": "Sante Max Lvl.1", "desc": "Vie Max +30 (+Soin)", "type": "maxhealth", "level": 1},
    {"id": "maxhealth_2", "name": "Sante Max Lvl.2", "desc": "Vie Max +40 (+Soin)", "type": "maxhealth", "level": 2},
    {"id": "maxhealth_3", "name": "Sante Max Lvl.3", "desc": "Vie Max +50 (+Soin)", "type": "maxhealth", "level": 3},
    {"id": "maxhealth_4", "name": "Sante Max Lvl.4", "desc": "Vie Max +50 (+Soin)", "type": "maxhealth", "level": 4},
    {"id": "maxhealth_5", "name": "Sante MAX", "desc": "Vie Max +100 (+Soin total)", "type": "maxhealth", "level": 5},
    
    # MULTISHOT
    {"id": "multishot_1", "name": "Multi-Tir Lvl.1", "desc": "Tire 2 projectiles", "type": "multishot", "level": 1},
    {"id": "multishot_2", "name": "Multi-Tir Lvl.2", "desc": "Tire 3 projectiles", "type": "multishot", "level": 2},
    {"id": "multishot_3", "name": "Multi-Tir Lvl.3", "desc": "Tire 4 projectiles", "type": "multishot", "level": 3},
    {"id": "multishot_4", "name": "Multi-Tir Lvl.4", "desc": "Tire 5 projectiles", "type": "multishot", "level": 4},
    {"id": "multishot_5", "name": "Multi-Tir MAX", "desc": "Eventail geant (7 projs)", "type": "multishot", "level": 5},
    
    # BULLETSPEED
    {"id": "bulletspeed_1", "name": "Vit. Tir Lvl.1", "desc": "Projectiles plus rapides", "type": "bulletspeed", "level": 1},
    {"id": "bulletspeed_2", "name": "Vit. Tir Lvl.2", "desc": "Projectiles fulgurants", "type": "bulletspeed", "level": 2},
    {"id": "bulletspeed_3", "name": "Vit. Tir Lvl.3", "desc": "Vitesse d'approche ++", "type": "bulletspeed", "level": 3},
    {"id": "bulletspeed_4", "name": "Vit. Tir Lvl.4", "desc": "Laser balistique ++", "type": "bulletspeed", "level": 4},
    {"id": "bulletspeed_5", "name": "Vit. Tir MAX", "desc": "Vitesse immediate", "type": "bulletspeed", "level": 5},
    
    # MAGNET (Aimant XP)
    {"id": "magnet_1", "name": "Aimant Lvl.1", "desc": "Attire l'XP de plus loin (+50)", "type": "magnet", "level": 1},
    {"id": "magnet_2", "name": "Aimant Lvl.2", "desc": "Attire l'XP (+70)", "type": "magnet", "level": 2},
    {"id": "magnet_3", "name": "Aimant Lvl.3", "desc": "Super Aimant (+100)", "type": "magnet", "level": 3},
    {"id": "magnet_4", "name": "Aimant Lvl.4", "desc": "Aimant Geant (+150)", "type": "magnet", "level": 4},
    {"id": "magnet_5", "name": "Aimant MAX", "desc": "Trou noir XP (+300)", "type": "magnet", "level": 5},
    
    # REGENERATION
    {"id": "regen_1", "name": "Regen Lvl.1", "desc": "Soigne 1 HP / sec", "type": "regen", "level": 1},
    {"id": "regen_2", "name": "Regen Lvl.2", "desc": "Soigne 3 HP / sec", "type": "regen", "level": 2},
    {"id": "regen_3", "name": "Regen Lvl.3", "desc": "Soigne 5 HP / sec", "type": "regen", "level": 3},
    {"id": "regen_4", "name": "Regen Lvl.4", "desc": "Soigne 10 HP / sec", "type": "regen", "level": 4},
    {"id": "regen_5", "name": "Regen MAX", "desc": "Soigne 20 HP / sec", "type": "regen", "level": 5},
    
    # CRITICAL HIT
    {"id": "crit_1", "name": "Critique Lvl.1", "desc": "10% chance de coup critique (x2 degats)", "type": "crit", "level": 1},
    {"id": "crit_2", "name": "Critique Lvl.2", "desc": "20% chance de coup critique", "type": "crit", "level": 2},
    {"id": "crit_3", "name": "Critique Lvl.3", "desc": "30% chance de coup critique", "type": "crit", "level": 3},
    {"id": "crit_4", "name": "Critique Lvl.4", "desc": "40% chance de coup critique", "type": "crit", "level": 4},
    {"id": "crit_5", "name": "Critique MAX", "desc": "50% chance de coup critique (x3 degats)", "type": "crit", "level": 5},
    
    # VAMPIRISM
    {"id": "vamp_1", "name": "Vampirisme Lvl.1", "desc": "2% chance de soin en tuant", "type": "vamp", "level": 1},
    {"id": "vamp_2", "name": "Vampirisme Lvl.2", "desc": "5% chance de soin en tuant", "type": "vamp", "level": 2},
    {"id": "vamp_3", "name": "Vampirisme Lvl.3", "desc": "10% chance de soin en tuant", "type": "vamp", "level": 3},
    {"id": "vamp_5", "name": "Vampirisme MAX", "desc": "25% chance de gros soin en tuant", "type": "vamp", "level": 5},
    
    # HARVESTING
    {"id": "harvest_1", "name": "Recolte Lvl.1", "desc": "Gagne +5 Or à chaque mission", "type": "harvesting", "level": 1},
    {"id": "harvest_2", "name": "Recolte Lvl.2", "desc": "Gagne +10 Or à chaque mission", "type": "harvesting", "level": 2},
    {"id": "harvest_3", "name": "Recolte Lvl.3", "desc": "Gagne +15 Or à chaque mission", "type": "harvesting", "level": 3},
    {"id": "harvest_4", "name": "Recolte Lvl.4", "desc": "Gagne +20 Or à chaque mission", "type": "harvesting", "level": 4},
    {"id": "harvest_5", "name": "Recolte MAX", "desc": "Gagne +30 Or à chaque mission", "type": "harvesting", "level": 5},
    
    # LUCK
    {"id": "luck_1", "name": "Chance Lvl.1", "desc": "Drops plus rares et prix -5%", "type": "luck", "level": 1},
    {"id": "luck_2", "name": "Chance Lvl.2", "desc": "Drops ameliores et prix -10%", "type": "luck", "level": 2},
    {"id": "luck_3", "name": "Chance Lvl.3", "desc": "Grosse chance et prix -15%", "type": "luck", "level": 3},
    {"id": "luck_4", "name": "Chance Lvl.4", "desc": "Destin favorise et prix -20%", "type": "luck", "level": 4},
    {"id": "luck_5", "name": "Chance MAX", "desc": "Fortune divine et prix -30%", "type": "luck", "level": 5},
    
    # NEW: DODGE
    {"id": "dodge_1", "name": "Esquive Lvl.1", "desc": "+5% de chance d'esquive", "type": "dodge", "level": 1},
    {"id": "dodge_2", "name": "Esquive Lvl.2", "desc": "+10% de chance d'esquive", "type": "dodge", "level": 2},
    {"id": "dodge_3", "name": "Esquive Lvl.3", "desc": "+15% de chance d'esquive", "type": "dodge", "level": 3},
    
    # NEW: ARMOR
    {"id": "armor_1", "name": "Armure Lvl.1", "desc": "Reduit les degats de 2", "type": "armor", "level": 1},
    {"id": "armor_2", "name": "Armure Lvl.2", "desc": "Reduit les degats de 4", "type": "armor", "level": 2},
    {"id": "armor_3", "name": "Armure Lvl.3", "desc": "Reduit les degats de 6", "type": "armor", "level": 3},

    # NEW: PIERCING
    {"id": "pierce_1", "name": "Perçage Lvl.1", "desc": "Traverse 1 ennemi de plus", "type": "piercing", "level": 1},
    {"id": "pierce_2", "name": "Perçage Lvl.2", "desc": "Traverse 2 ennemis", "type": "piercing", "level": 2},
]

# --- SYSTEME DE COMPETENCES MODULAIRE (Path of Exile style) ---
class SupportGem:
    def __init__(self, gem_id, name, mod_type, value, icon_key):
        self.id = gem_id
        self.name = name
        self.mod_type = mod_type # 'pierce', 'fork', 'speed', 'fire' ...
        self.value = value
        self.icon_key = icon_key

class WeaponSkin:
    def __init__(self, w_id, name, base_dmg_mult, speed_mult, range_bonus, icon_key, proj_key):
        self.id = w_id
        self.name = name
        self.base_dmg_mult = base_dmg_mult
        self.speed_mult = speed_mult
        self.range_bonus = range_bonus
        self.icon_key = icon_key
        self.proj_key = proj_key

class SkillSlot:
    def __init__(self, trigger_type="frontal_shot"):
        self.trigger_type = trigger_type
        self.level = 1
        self.weapon = None      # Instance of WeaponSkin
        self.gems = []          # List of SupportGem instances
        self.max_gems = 2       # S'améliorera avec le level

    def get_combined_modifiers(self):
        mods = {
            "piercing": 0,
            "bounce": 0,
            "fork": 0,
            "fire_dmg": 0,
            "speed_mult": 1.0,
            "damage_mult": 1.0
        }
        for g in self.gems:
            if g.mod_type in mods and isinstance(mods[g.mod_type], int):
                mods[g.mod_type] += int(g.value)
            elif g.mod_type in mods and isinstance(mods[g.mod_type], float):
                mods[g.mod_type] *= float(g.value)
        return mods

# --- Liste des nouvelles armes (30 Objets) ---
ALL_WEAPONS = [
    WeaponSkin("w_laser", "Laser Précis", 1.0, 1.5, 0, "weapon_laser", "proj_laser"),
    WeaponSkin("w_plasma", "Plasma Lourd", 2.0, 0.6, 0, "weapon_plasma", "proj_plasma"),
    WeaponSkin("w_railgun", "Fusil Magnétique", 3.0, 2.0, 0, "weapon_railgun", "proj_railgun"),
    WeaponSkin("w_blaster", "Blaster Standard", 1.2, 1.0, 0, "weapon_blaster", "proj_blaster"),
    WeaponSkin("w_cannon", "Gros Canon", 2.5, 0.8, 0, "weapon_cannon", "proj_cannon"),
    WeaponSkin("w_shotgun", "Fusil à Pompe", 1.5, 0.8, -50, "weapon_shotgun", "proj_shotgun"),
    WeaponSkin("w_sniper", "Sniper", 4.0, 3.0, 100, "weapon_sniper", "proj_sniper"),
    WeaponSkin("w_flame", "Lance-Flammes", 0.5, 0.5, -80, "weapon_flamethrower", "proj_flamethrower"),
    WeaponSkin("w_missile", "Lance-Missiles", 3.5, 0.4, 0, "weapon_missile", "proj_missile"),
    WeaponSkin("w_star", "Lance-Étoiles", 1.0, 1.2, 0, "weapon_starburst", "proj_starburst")
]

# Modificateurs des Gemmes: color_1 à color_4
ALL_GEMS = []
for i in range(1, 5):
    ALL_GEMS.append(SupportGem(f"red_{i}", f"Rubis Niv.{i}", "damage_mult", 1.0 + (i * 0.2), f"gem_red_{i}"))
    ALL_GEMS.append(SupportGem(f"green_{i}", f"Émeraude Niv.{i}", "piercing", i, f"gem_green_{i}"))
    ALL_GEMS.append(SupportGem(f"blue_{i}", f"Saphir Niv.{i}", "speed_mult", 1.0 + (i * 0.3), f"gem_blue_{i}"))
    ALL_GEMS.append(SupportGem(f"yellow_{i}", f"Topaze Niv.{i}", "bounce", i, f"gem_yellow_{i}"))
    ALL_GEMS.append(SupportGem(f"purple_{i}", f"Améthyste Niv.{i}", "fire_dmg", i * 5, f"gem_purple_{i}"))

class Player(pygame.sprite.Sprite):
    def __init__(self, original_image):
        super().__init__()
        self.original_image = original_image
        self.image = self.original_image.copy()
        
        self.rect = self.image.get_rect(center=(WIDTH//2, HEIGHT//2))
        self.pos = pygame.math.Vector2(WIDTH//2, HEIGHT//2)
        
        # --- STATISTIQUES AVANCEES (Brotato-like) ---
        self.stats = {
            # OFFENSIF
            "damage": 10,                 # Dégâts de base bruts
            "ranged_damage": 0,           # Dégâts ajoutés aux projectiles
            "damage_percent": 0.0,        # Multiplicateur de dégâts ext (ex: 0.10 = +10%)
            "attack_speed": 1.0,          # Multiplicateur de vitesse d'attaque (ex: 1.5 = +50% plus vite)
            "crit_chance": 0.0,           # Chances de coup critique (0.0 à 1.0)
            "crit_damage": 2.0,           # Multiplicateur de dégâts en cas de critique (ex: 2.0 = double dgt)
            "piercing": 0,                # Nombre d'ennemis traversés
            "range": 0,                   # Portée bonus (ou durée de vie projectile)
            "projectile_speed": 600,      # Vitesse des projectiles de base
            "multishot": 1,               # Nombre de projectiles de base tirés
            
            # DEFENSIF
            "max_health": 100,            # Santé maximum
            "armor": 0,                   # Réduction de dégâts bruts ou en pourcentage
            "dodge": 0.0,                 # Chances d'esquiver une attaque (0.0 à 0.6 max)
            "speed": 300,                 # Vitesse de déplacement du joueur
            "health_regen": 0.0,          # PV récupérés par seconde
            "life_steal": 0.0,            # Pourcentage de chance de voler *1 PV* par coup
            
            # UTILITAIRE / ECO
            "harvesting": 0,              # Quantité de monnaie/XP gagnée à chaque fin de vague/Nœud
            "luck": 0.0,                  # Influe sur la qualité des drops et du shop (+0.05 = +5% chance)
            "magnet_radius": 80           # Rayon de ramassage d'XP
        }
        
        # Etats actuels in-game
        self.health = self.stats["max_health"]
        self.shoot_delay_base = 200 # Délai de base impacté par "attack_speed"
        self.last_shot = 0
        self.invincible_timer = 0 
        self.angle = 0
        
        # Progression (XP / Niveau)
        self.level = 1
        self.current_xp = 0
        self.xp_to_next_level = 50
        
        # Inventaire & Équipement
        self.skills = {}     # Pour l'instant, contient les skills actuels (Bientôt obsolète pour le combat)
        self.gold = 0        # Nouvelle monnaie du jeu
        
        # --- NOUVEAU ARSENAL MODULAIRE ---
        self.active_skills = [] 
        # On donne un slot de base au joueur (Auto-shoot dirigé)
        default_slot = SkillSlot("directional_shot")
        default_slot.weapon = WeaponSkin("w_laser", "Laser Précis", 1.0, 1.5, 0, "weapon_laser", "proj_laser")
        self.active_skills.append(default_slot)

    def rotate_towards(self, dir_x, dir_y):
        if math.hypot(dir_x, dir_y) > 0.1:
            self.angle = math.degrees(math.atan2(-dir_y, dir_x))
            self.image = pygame.transform.rotate(self.original_image, self.angle)
            self.rect = self.image.get_rect(center=self.rect.center)


    def update(self, dt, dx, dy):
        if self.invincible_timer > 0:
            self.invincible_timer -= dt
            if int(self.invincible_timer * 15) % 2 == 0:
                self.image.set_alpha(80)
            else:
                self.image.set_alpha(255)
        else:
            self.image.set_alpha(255)

        if dx != 0 or dy != 0:
            length = math.hypot(dx, dy)
            if length > 1:
                dx /= length
                dy /= length

        self.pos.x += dx * self.stats["speed"] * dt
        self.pos.y += dy * self.stats["speed"] * dt
        
        self.rect.center = self.pos
        self.rect.clamp_ip(pygame.Rect(0, 0, WIDTH, HEIGHT))
        self.pos.x, self.pos.y = self.rect.center

class Bullet(pygame.sprite.Sprite):
    def __init__(self, x, y, dir_x, dir_y, damage, speed, image, pierce_count=0, bounce_count=0):
        super().__init__()
        self.image = image
        self.rect = self.image.get_rect(center=(x, y))
        self.pos = pygame.math.Vector2(x, y)
        self.damage = damage
        
        self.dir = pygame.math.Vector2(dir_x, dir_y)
        if self.dir.length() > 0:
            self.dir = self.dir.normalize()
        else:
            self.dir = pygame.math.Vector2(0, -1)
            
        self.speed = speed
        self.pierce_count = pierce_count
        self.bounce_count = bounce_count
        self.hit_enemies = [] # Liste d'ID d'ennemis déjà touchés pour éviter le multi-hit direct

    def update(self, dt, *args):
        self.pos += self.dir * self.speed * dt
        self.rect.center = self.pos
        
        # Gestion des rebonds (Bounce)
        screen_rect = pygame.Rect(0, 0, WIDTH, HEIGHT)
        if not screen_rect.colliderect(self.rect):
            if self.bounce_count > 0:
                self.bounce_count -= 1
                if self.pos.x <= 0 or self.pos.x >= WIDTH:
                    self.dir.x *= -1
                if self.pos.y <= 0 or self.pos.y >= HEIGHT:
                    self.dir.y *= -1
                # Recentre pour éviter de rester coincé dehors
                self.pos.x = max(0, min(WIDTH, self.pos.x))
                self.pos.y = max(0, min(HEIGHT, self.pos.y))
            else:
                self.kill()

class Enemy(pygame.sprite.Sprite):
    def __init__(self, e_type, wave, images_dict):
        super().__init__()
        self.type = e_type
        
        side = random.choice(['top', 'bottom', 'left', 'right'])
        if side == 'top':
            x, y = random.randint(0, WIDTH), -50
        elif side == 'bottom':
            x, y = random.randint(0, WIDTH), HEIGHT + 50
        elif side == 'left':
            x, y = -50, random.randint(0, HEIGHT)
        else: 
            x, y = WIDTH + 50, random.randint(0, HEIGHT)
            
        self.pos = pygame.math.Vector2(x, y)
        
        hp_mult = 1.0 + (wave * 0.18) # Réduit de 0.3 à 0.18 pour équilibrage étage 10+
        speed_mult = 1.0 + (wave * 0.07) # Réduit de 0.12 à 0.07
        
        if e_type == 'fast':
            self.original_image = images_dict['fast']
            self.color = NEON_YELLOW
            self.speed = random.randint(200, 260) * speed_mult
            self.max_health = 10 * hp_mult
            self.damage = 10
        elif e_type == 'tank':
            self.original_image = images_dict['tank']
            self.color = NEON_ORANGE
            self.speed = random.randint(40, 80) * speed_mult
            self.max_health = 60 * hp_mult
            self.damage = 30
        else: 
            self.original_image = images_dict['basic']
            self.color = NEON_PINK
            self.speed = random.randint(110, 160) * speed_mult
            self.max_health = 25 * hp_mult
            self.damage = 15

        self.rotation_angle = random.randint(0, 360)
        self.rotation_speed = random.choice([-100, 100, -50, 50])
        
        self.image = self.original_image.copy()
        self.rect = self.image.get_rect(center=(x, y))
        self.health = self.max_health
        self.elite_shoot_callback = None # Sera rempli par Game

    def update(self, dt, player_pos):
        self.rotation_angle = (self.rotation_angle + self.rotation_speed * dt) % 360
        self.image = pygame.transform.rotate(self.original_image, self.rotation_angle)
        self.rect = self.image.get_rect(center=self.pos)
        
        direction = player_pos - self.pos
        if direction.length() > 0:
            direction = direction.normalize()
        
        self.pos += direction * self.speed * dt
        self.rect.center = self.pos
        
        # --- IA SPECIALISEE ELITE ---
        if getattr(self, 'is_elite', False):
            self.elite_action_timer -= dt
            if self.elite_action_timer <= 0:
                self.elite_action_timer = random.uniform(2.0, 4.0)
                
                # Choix d'une action : Dash ou Tir
                action = random.choice(['dash', 'shoot'])
                if action == 'dash':
                    self.is_dashing = True
                    self.dash_timer = 0.5
                    self.dash_dir = (player_pos - self.pos).normalize() if (player_pos - self.pos).length() > 0 else pygame.math.Vector2(1,0)
                elif action == 'shoot' and self.elite_shoot_callback:
                    self.elite_shoot_callback(self) # Appelle Game pour tirer
                    
        if getattr(self, 'is_dashing', False):
            self.pos += self.dash_dir * self.speed * 3 * dt
            self.dash_timer -= dt
            if self.dash_timer <= 0: self.is_dashing = False

    def take_damage(self, amount):
        self.health -= amount
        return self.health <= 0

class Particle(pygame.sprite.Sprite):
    def __init__(self, x, y, color):
        super().__init__()
        size = random.randint(3, 8)
        self.image = pygame.Surface((size, size), pygame.SRCALPHA)
        self.image.fill(color)
        self.rect = self.image.get_rect(center=(x, y))
        self.pos = pygame.math.Vector2(x, y)
        
        angle = random.uniform(0, math.pi * 2)
        speed = random.uniform(50, 250)
        self.velocity = pygame.math.Vector2(math.cos(angle) * speed, math.sin(angle) * speed)
        
        self.lifetime = random.uniform(0.2, 0.6)
        self.age = 0

    def update(self, dt, *args):
        self.pos += self.velocity * dt
        self.rect.center = self.pos
        self.age += dt
        
        alpha = max(0, int(255 * (1 - self.age / self.lifetime)))
        self.image.set_alpha(alpha)
        
        if self.age >= self.lifetime:
            self.kill()

class ExperienceOrb(pygame.sprite.Sprite):
    def __init__(self, x, y, image, value=10):
        super().__init__()
        self.original_image = image
        self.image = self.original_image.copy()
        self.rect = self.image.get_rect(center=(x, y))
        self.pos = pygame.math.Vector2(x, y)
        self.value = value
        self.age = random.uniform(0, 10) 
        self.collected = False

    def update(self, dt, player):
        self.age += dt
        # Effet de lévitation douce
        offset_y = math.sin(self.age * 3) * 5
        self.rect.center = (int(self.pos.x), int(self.pos.y + offset_y))
        
        # Attraction aimant (Magnet)
        diff = player.pos - self.pos
        dist = diff.length()
        if dist < player.stats["magnet_radius"] and dist > 0:
            direction = diff.normalize()
            speed = 400 * (1 - dist/player.stats["magnet_radius"]) + 100
            self.pos += direction * speed * dt
            
        if dist < 20 and not self.collected:
            self.collected = True
            player.current_xp += self.value
            player.gold += self.value # L'XP donne aussi de l'Or (Brotato style)
            self.kill()

class FloatingText(pygame.sprite.Sprite):
    def __init__(self, x, y, text, color, font):
        super().__init__()
        self.image = font.render(text, True, color)
        self.rect = self.image.get_rect(center=(x, y))
        self.pos = pygame.math.Vector2(x, y)
        self.lifetime = 0.8
        self.age = 0

    def update(self, dt, *args):
        self.age += dt
        self.pos.y -= 30 * dt # Monte doucement
        self.rect.center = self.pos
        
        alpha = max(0, int(255 * (1 - self.age / self.lifetime)))
        self.image.set_alpha(alpha)
        
        if self.age >= self.lifetime:
            self.kill()


# --- NOUVEAU SYSTEME DE CARTE (Slay the Spire Style) ---
class MapNode:
    def __init__(self, node_id, node_type, x, y, floor):
        self.id = node_id
        self.type = node_type  # 'combat', 'elite', 'shop', 'event', 'rest'
        self.x = x
        self.y = y
        self.floor = floor
        self.children = [] # IDs des noeuds accessibles depuis celui-ci
        self.completed = False
        
        # Mission spécifique pour le fun
        self.mission_type = "survival"
        if node_type == 'combat':
            self.mission_type = random.choice(["survival", "targets", "dodge"])
        elif node_type == 'elite':
            self.mission_type = "duel"
        elif node_type == 'rest':
            self.mission_type = "rest"

class GameMap:
    def __init__(self):
        self.nodes = {}
        self.current_node_id = None
        self.generate_map()
        
    def generate_map(self):
        # Génération d'un arbre de profondeur 20
        self.nodes = {}
        self.floors = 20
        floors = self.floors
        node_counter = 0
        
        # Structure de base : Largeur variable par étage
        layers = []
        for f in range(floors):
            num_nodes = random.randint(2, 4) if f > 0 else 1
            if f == floors - 1: num_nodes = 1 # Boss final de la zone
            
            layer_nodes = []
            for n in range(num_nodes):
                n_type = random.choices(['combat', 'elite', 'shop', 'event'], weights=[50, 15, 15, 20])[0]
                if f == 0: n_type = 'combat' # Premier toujours combat
                elif f == floors - 1: n_type = 'elite' # Dernier toujours boss/elite
                
                # Répartition spatiale pour le dessin
                # Tentative de placement avec espacement
                placed = False
                attempts = 0
                max_attempts = 100
                min_node_distance = 80 # Minimum distance between node centers

                while not placed and attempts < max_attempts:
                    if num_nodes == 1:
                        x_pos = WIDTH // 2
                    else:
                        # Distribute nodes more evenly across the width
                        segment_width = WIDTH / num_nodes
                        x_pos = int(segment_width * n + segment_width / 2) + random.randint(-20, 20) # Add some randomness

                    y_pos = HEIGHT - 80 - (f * 60) + random.randint(-15, 15) # Part du bas vers le haut, add randomness

                    valid_position = True
                    for existing_node in layer_nodes:
                        dist = math.hypot(x_pos - existing_node.x, y_pos - existing_node.y)
                        if dist < min_node_distance:
                            valid_position = False
                            break
                    
                    # Also check against nodes in the previous layer to prevent too much overlap
                    if f > 0:
                        for prev_node in layers[f-1]:
                            dist = math.hypot(x_pos - prev_node.x, y_pos - prev_node.y)
                            if dist < min_node_distance:
                                valid_position = False
                                break

                    if valid_position:
                        node = MapNode(node_counter, n_type, x_pos, y_pos, f)
                        self.nodes[node_counter] = node
                        layer_nodes.append(node)
                        node_counter += 1
                        placed = True
                    attempts += 1
                
                if not placed: # Fallback if placement fails after many attempts
                    x_pos = WIDTH//2 if num_nodes == 1 else (WIDTH//num_nodes) * n + (WIDTH//num_nodes)//2
                    y_pos = HEIGHT - 80 - (f * 60)
                    node = MapNode(node_counter, n_type, x_pos, y_pos, f)
                    self.nodes[node_counter] = node
                    layer_nodes.append(node)
                    node_counter += 1

            layers.append(layer_nodes)
            
        # Création des liens (chemins)
        for f in range(floors - 1):
            current_layer = layers[f]
            next_layer = layers[f+1]
            for node in current_layer:
                # Chaque nœud se connecte à 1 ou 2 nœuds de l'étage supérieur
                targets = random.sample(next_layer, k=min(len(next_layer), random.randint(1, 2)))
                for t in targets:
                    if t.id not in node.children:
                        node.children.append(t.id)
                        
            # Sécurité : Chaque nœud de next_layer doit avoir au moins un parent, sinon on le relie
            for next_node in next_layer:
                parent_exists = any(next_node.id in n.children for n in current_layer)
                if not parent_exists:
                    random.choice(current_layer).children.append(next_node.id)

    def get_accessible_nodes(self):
        if self.current_node_id is None:
            # Retourne tous les nœuds de l'étage 0
            return [n for n in self.nodes.values() if n.floor == 0]
        else:
            return [self.nodes[c_id] for c_id in self.nodes[self.current_node_id].children]

class Game:
    def __init__(self):
        pygame.init()
        pygame.mixer.init()
        pygame.joystick.init()
        
        # Initialisation du plein écran par défaut avec SCALED pour le lissage
        self.is_fullscreen = True
        self.screen = pygame.display.set_mode((WIDTH, HEIGHT), pygame.FULLSCREEN | pygame.SCALED)
        pygame.display.set_caption("Neon Nebula Survivor")
        self.clock = pygame.time.Clock()
        
        # Gestion intelligente des entrées
        self.joystick = None
        self.joystick_count = pygame.joystick.get_count()
        self.test_mode = False        # DESACTIVÉ PAR DÉFAUT POUR LA LIVRAISON
        self.screenshot_timer = 0
        self.check_joystick()

        self.font_very_small = pygame.font.SysFont("Courier New", 12, bold=False)
        self.font_small = pygame.font.SysFont("Courier New", 14, bold=False)
        self.font = pygame.font.SysFont("Courier New", 20, bold=True)
        self.title_font = pygame.font.SysFont("Courier New", 36, bold=True)
        
        self.load_assets()
        self.play_music("map")
        
        # NOUVEAU STATUT DE JEU
        self.state = "map"  # 'map', 'combat', 'shop', 'event'
        self.game_map = GameMap()
        self.run_timer = 0  # Temps passé dans le combat actuel
        self.run_target_time = 60 # Durée d'un combat (ex: 60 sec)
        self.floors = 20
        
        self.paused = False
        self.upgrade_menu = False
        self.game_over = False
        self.victory = False
        
        self.reset_game()

    def load_assets(self):
        self.images = {}
        def load_img(name, default_size, color):
            path = os.path.join(ASSETS_DIR, name)
            if os.path.exists(path):
                img = pygame.image.load(path).convert_alpha()
                if img.get_size() != default_size:
                    img = pygame.transform.smoothscale(img, default_size)
                return img
            else:
                surf = pygame.Surface(default_size, pygame.SRCALPHA)
                surf.fill(color)
                return surf
                
        self.images['player'] = load_img('player.png', (40, 40), WHITE)
        self.images['bullet'] = load_img('bullet.png', (16, 16), NEON_GREEN)
        self.images['basic'] = load_img('enemy_basic.png', (32, 32), NEON_PINK)
        self.images['fast'] = load_img('enemy_fast.png', (24, 24), NEON_YELLOW)
        self.images['tank'] = load_img('enemy_tank.png', (48, 48), NEON_ORANGE)
        self.images['xp_small'] = load_img('xp_orb_small.png', (16, 16), NEON_BLUE)
        self.images['xp_med'] = load_img('xp_orb_med.png', (24, 24), (50, 205, 50))
        self.images['xp_large'] = load_img('xp_orb_large.png', (32, 32), (255, 215, 0))
        
        # --- NOUVELLES ARMES & PROJECTILES ---
        for w in ["laser", "plasma", "railgun", "blaster", "cannon", "shotgun", "sniper", "flamethrower", "missile", "starburst"]:
            self.images[f'weapon_{w}'] = load_img(f'icon_w_{w}.png', (32, 32), GRAY)
            self.images[f'proj_{w}'] = load_img(f'proj_{w}.png', (32, 32), WHITE)
            
        # --- NOUVELLES GEMMES ---
        for col in ["red", "green", "blue", "yellow", "purple"]:
            for i in range(1, 5):
                self.images[f'gem_{col}_{i}'] = load_img(f'gem_{col}_{i}.png', (24, 24), WHITE)
        
        # --- ICONES DE CARTE ---
        for nt in ['combat', 'elite', 'shop', 'dodge', 'targets', 'rest', 'event']:
            self.images[f'node_{nt}'] = load_img(f'node_{nt}.png', (32, 32), (0,0,0,0))
            
        # --- ICONES D'AMELIORATIONS (PASSIFS) ---
        for ut in ['speed', 'firerate', 'damage', 'maxhealth', 'multishot', 'bulletspeed', 'magnet', 'regen', 'crit', 'vamp', 'harvesting', 'luck', 'armor', 'piercing']:
            self.images[f'up_{ut}'] = load_img(f'up_{ut}.png', (48, 48), NEON_BLUE)
        
    def play_music(self, category="combat"):
        # Liste de musiques par catégorie (Map, Combat, Boss)
        # Si on n'a qu'un fichier, on l'utilise pour tout par défaut
        files = [f for f in os.listdir(BASE_DIR) if f.endswith(".mp3") or f.endswith(".ogg")]
        if not files: return
        
        # Filtre simple : si "boss" est dans le nom, c'est pour l'Elite, etc.
        target_file = files[0]
        if category == "map":
            # Cherche une musique plus calme
            map_musics = [f for f in files if "chill" in f.lower() or "map" in f.lower()]
            if map_musics: target_file = map_musics[0]
        elif category == "boss":
            boss_musics = [f for f in files if "boss" in f.lower() or "action" in f.lower()]
            if boss_musics: target_file = boss_musics[0]
            
        try:
            pygame.mixer.music.load(os.path.join(BASE_DIR, target_file))
            pygame.mixer.music.set_volume(0.4)
            pygame.mixer.music.play(-1)
        except:
            pass

    def check_joystick(self):
        current_count = pygame.joystick.get_count()
        # Si un pad a été branché ou débranché
        if current_count != self.joystick_count:
            self.joystick_count = current_count
            
            # Forcer la pause (uniquement si le jeu n'est pas déjà Game Over ou dans un Menu d'Upgrade)
            if hasattr(self, 'paused') and not self.game_over and not self.upgrade_menu:
                self.paused = True
                
            if current_count > 0:
                self.joystick = pygame.joystick.Joystick(0)
                self.joystick.init()
                self.input_mode = "gamepad"
            else:
                self.joystick = None
                self.input_mode = "keyboard"

    def reset_game(self):
        self.all_sprites = pygame.sprite.Group()
        self.bullets = pygame.sprite.Group()
        self.enemies = pygame.sprite.Group()
        self.particles = pygame.sprite.Group()
        self.xp_orbs = pygame.sprite.Group()
        self.floating_texts = pygame.sprite.Group()
        self.enemy_bullets = pygame.sprite.Group()

        self.player = Player(self.images['player'])
        self.all_sprites.add(self.player)

        self.score = 0
        self.wave = 1
        self.spawn_timer = 0
        self.spawn_rate = 1.0 
        
        self.screen_flash = 0
        self.game_over = False
        self.victory = False
        self.upgrade_menu = False
        self.paused = False
        self.running = True
        self.mission_started = False
        
        self.current_upgrades = []
        self.selected_upgrade_idx = 0
        self.axis_moved = False
        self.stick_aim_x, self.stick_aim_y = 0, -1
        
        self.map_msg = ""
        self.map_msg_timer = 0
        
        # --- RESET CARTE ET ETAGE ---
        self.state = "map"
        self.game_map = GameMap()
        self.run_timer = 0
        self.play_music("map")

    def spawn_particle_explosion(self, x, y, color, count=10):
        for _ in range(count):
            p = Particle(x, y, color)
            self.particles.add(p)
            self.all_sprites.add(p)

    def generate_upgrades(self):
        # On ne propose que des améliorations éligibles (niveau + 1 de ce que possède le joueur)
        eligible = []
        for u in ALL_UPGRADES:
            s_type = u["type"]
            s_level = u["level"]
            current_level = self.player.skills.get(s_type, 0)
            if s_level == current_level + 1:
                eligible.append(u)
        
        if len(eligible) > 0:
            self.current_upgrades = random.sample(eligible, min(3, len(eligible)))
        else:
            # Fallback (Soin d'urgence par ex)
            self.current_upgrades = [{"id": "heal_default", "name": "Poulet Roti", "desc": "Soigne 50 HP", "type": "heal", "level": 1}]
            
        # Assigner les icônes
        for up in self.current_upgrades:
            u_type = up.get("type", "heal")
            up["icon"] = self.images.get(f"up_{u_type}", self.images.get('node_shop'))
            
        self.selected_upgrade_idx = 0

    def apply_upgrade(self, upg_dict):
        u_id = upg_dict["id"]
        u_type = upg_dict.get("type", "heal")
        
        if u_id == "heal_default":
            self.player.health = min(self.player.stats["max_health"], self.player.health + 50)
            return

        # Enregistrer le niveau acquis
        self.player.skills[u_type] = upg_dict["level"]
        
        # Effets
        if u_type == "speed": self.player.stats["speed"] += 30
        elif u_type == "firerate": self.player.stats["attack_speed"] += 0.15
        elif u_type == "damage": self.player.stats["damage"] += 5
        elif u_type == "maxhealth":
            self.player.stats["max_health"] += 30
            self.player.health = min(self.player.stats["max_health"], self.player.health + 30)
        elif u_type == "multishot": self.player.stats["multishot"] += 1
        elif u_type == "bulletspeed": self.player.stats["projectile_speed"] += 100
        elif u_type == "magnet": self.player.stats["magnet_radius"] += 40
        elif u_type == "regen": self.player.stats["health_regen"] += 1.0
        elif u_type == "crit": self.player.stats["crit_chance"] += 0.1
        elif u_type == "vamp": self.player.stats["life_steal"] += 0.05
        elif u_type == "harvesting": self.player.stats["harvesting"] += 5
        elif u_type == "luck": self.player.stats["luck"] += 0.05
        elif u_type == "dodge": self.player.stats["dodge"] = min(0.6, self.player.stats["dodge"] + 0.05)
        elif u_type == "armor": self.player.stats["armor"] += 2
        elif u_type == "piercing": self.player.stats["piercing"] += 1

    def check_level_up(self):
        # Boucle pour gérer l'overflow d'XP (monte plusieurs niveaux si bcp d'XP d'un coup)
        while self.player.current_xp >= self.player.xp_to_next_level:
            self.player.current_xp -= self.player.xp_to_next_level
            self.player.level += 1
            # Courbe d'XP plus équilibrée (Recalibrage: +25% au lieu de +20%)
            self.player.xp_to_next_level = int(self.player.xp_to_next_level * 1.25)
            
            # Déclencher le menu
            self.upgrade_menu = True
            self.generate_upgrades()
            
            # Afficher texte
            txt = FloatingText(self.player.rect.centerx, self.player.rect.top - 20, "LEVEL UP!", NEON_YELLOW, self.font)
            self.all_sprites.add(txt)
            self.floating_texts.add(txt) 
    def run(self):
        while self.running:
            dt = self.clock.tick(FPS) / 1000.0
            
            # Polling des connexions manettes
            self.check_joystick()
            
            # --- GESTION UNIQUE DES ÉVÉNEMENTS ---
            self.handle_events()
            
            if self.state == "map":
                if self.test_mode: self.update_ai_logic(dt)
                self.update_map()
                self.draw_map()
            elif self.state == "shop":
                if self.test_mode: self.update_ai_logic(dt)
                # update_shop ne fait plus d'event.get()
                self.draw_shop()
            elif self.state == "victory":
                if self.test_mode: self.update_ai_logic(dt)
                self.update_victory_logic(dt) # Ancien handle_victory_events sans loop
                self.draw_victory_screen()
            elif self.state == "combat":
                if self.paused:
                    self.draw_combat()
                    self.draw_pause_menu()
                elif self.upgrade_menu:
                    if self.test_mode: self.update_ai_logic(dt)
                    self.update_upgrade_menu()
                    self.draw_upgrade_menu()
                elif self.game_over:
                    if self.test_mode: self.update_ai_logic(dt)
                    self.draw_combat()
                else:
                    if self.test_mode: self.update_ai_logic(dt)
                    self.update_combat(dt)
                    self.draw_combat()
            
            if self.test_mode:
                self.screenshot_timer += dt
                if self.screenshot_timer >= 5.0: # Toutes les 5 secondes
                    self.save_screenshot("periodic")
                    self.screenshot_timer = 0
            
            pygame.display.flip()


    def handle_events(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.running = False
                
            # --- DETECTION AUTOMATIQUE DU PORT (SMART SWITCH) ---
            # Si le joueur tape au clavier ou bouge la souris -> Mode Clavier
            if event.type in (pygame.KEYDOWN, pygame.MOUSEBUTTONDOWN, pygame.MOUSEMOTION):
                self.input_mode = "keyboard"
            # Si le joueur appuie sur une touche manette ou bouge un stick de manette valide -> Mode Gamepad
            elif event.type in (pygame.JOYBUTTONDOWN, pygame.JOYHATMOTION):
                if self.joystick:
                    self.input_mode = "gamepad"
            elif event.type == pygame.JOYAXISMOTION:
                if abs(event.value) > 0.2 and self.joystick: # Ne considérer que les vrais mouvements pour switch
                    self.input_mode = "gamepad"
            
            # --- GESTION PAUSE (Bouton +, Start ou Echap) ---
            if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
                if not self.game_over and not self.upgrade_menu:
                    self.paused = not self.paused
                
            # Les boutons "Menu", "+" ou "Start" sont généralement les index 6, 7 ou 9
            if event.type == pygame.JOYBUTTONDOWN and event.button in [6, 7, 9]:
                if not self.game_over and not self.upgrade_menu:
                    self.paused = not self.paused

            # --- EVENTS EN FONCTION DU CONTEXTE ---
            if self.paused:
                pass 

            elif self.game_over:
                if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
                    self.reset_game()
                elif getattr(event, 'button', -1) == 0:
                    self.reset_game()
                    
            elif self.upgrade_menu:
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_LEFT: self.selected_upgrade_idx = max(0, self.selected_upgrade_idx - 1)
                    elif event.key == pygame.K_RIGHT: self.selected_upgrade_idx = min(len(self.current_upgrades) - 1, self.selected_upgrade_idx + 1)
                    elif event.key == pygame.K_RETURN or event.key == pygame.K_SPACE:
                        self.apply_upgrade(self.current_upgrades[self.selected_upgrade_idx])
                        self.upgrade_menu = False
                        
                elif event.type == pygame.JOYHATMOTION:
                    hx, hy = event.value
                    if hx < 0: self.selected_upgrade_idx = max(0, self.selected_upgrade_idx - 1)
                    elif hx > 0: self.selected_upgrade_idx = min(len(self.current_upgrades) - 1, self.selected_upgrade_idx + 1)
                        
                elif event.type == pygame.JOYBUTTONDOWN and event.button == 0: 
                    self.apply_upgrade(self.current_upgrades[self.selected_upgrade_idx])
                    self.upgrade_menu = False

    def update_upgrade_menu(self):
        if self.joystick and self.input_mode == "gamepad":
            ax = self.joystick.get_axis(0)
            if ax < -0.5 and not self.axis_moved:
                self.selected_upgrade_idx = max(0, self.selected_upgrade_idx - 1)
                self.axis_moved = True
            elif ax > 0.5 and not self.axis_moved:
                self.selected_upgrade_idx = min(len(self.current_upgrades) - 1, self.selected_upgrade_idx + 1)
                self.axis_moved = True
            elif abs(ax) < 0.2:
                self.axis_moved = False

    def shoot_towards_mouse(self):
        mx, my = pygame.mouse.get_pos()
        dx, dy = mx - self.player.pos.x, my - self.player.pos.y
        length = math.hypot(dx, dy)
        if length > 0:
            self.shoot(dx/length, dy/length)

    def shoot(self, dx, dy):
        now = pygame.time.get_ticks()
        
        for skill in self.player.active_skills:
            if skill.trigger_type == "directional_shot":
                mods = skill.get_combined_modifiers()
                
                # Le délai entre chaque tir est influencé par attack_speed et la vitesse de l'arme
                w_speed = skill.weapon.speed_mult if skill.weapon else 1.0
                actual_delay = self.player.shoot_delay_base / max(0.1, self.player.stats["attack_speed"] * w_speed)
                
                if now - self.player.last_shot > actual_delay:
                    self.player.last_shot = now
                    
                    base_angle = math.atan2(dy, dx)
                    spread = math.radians(12) 
                    n = self.player.stats["multishot"]
                    start_angle = base_angle - (spread * (n - 1)) / 2
                    
                    # Calcul des dégâts finaux
                    base_dmg = self.player.stats["damage"] + self.player.stats["ranged_damage"]
                    w_dmg_mult = skill.weapon.base_dmg_mult if skill.weapon else 1.0
                    final_dmg = base_dmg * w_dmg_mult * (1.0 + self.player.stats["damage_percent"]) * mods["damage_mult"]
                    
                    b_speed = self.player.stats["projectile_speed"] * mods["speed_mult"]
                    p_count = self.player.stats["piercing"] + mods["piercing"]
                    b_count = mods["bounce"]
                    
                    icon = self.images[skill.weapon.proj_key] if skill.weapon and skill.weapon.proj_key in self.images else self.images['bullet']
                    
                    for i in range(n):
                        angle = start_angle + i * spread
                        dir_x = math.cos(angle)
                        dir_y = math.sin(angle)
                        b = Bullet(self.player.rect.centerx, self.player.rect.centery, 
                                   dir_x, dir_y, final_dmg, b_speed, icon, p_count, b_count)
                        self.all_sprites.add(b)
                        self.bullets.add(b)

    def spawn_wave_enemy(self, is_boss=False):
        m_node = self.game_map.nodes.get(self.game_map.current_node_id)
        if m_node and m_node.mission_type == "duel":
            if len(self.enemies) > 0: return # Un seul boss !
            is_boss = True
            
        types = ['basic']
        m_node = self.game_map.nodes.get(self.game_map.current_node_id)
        is_dodge = m_node and m_node.mission_type == "dodge"
        
        if self.wave >= 2 and not is_dodge: types.append('fast')
        if self.wave >= 3: types.append('tank')
        
        weights = [60]
        if 'fast' in types: weights.append(30)
        if 'tank' in types: weights.append(15)
        
        chosen_type = random.choices(types, weights=weights[:len(types)], k=1)[0]
        
        # Un ennemi a une chance de devenir Elite (ou forcé si is_boss)
        is_elite = is_boss or (random.random() < 0.05 + (self.wave * 0.02))
        
        if m_node and m_node.mission_type == "targets":
            # Image cible
            e = Enemy(chosen_type, self.wave, self.images)
            e.original_image = self.images['node_targets']
            e.image = e.original_image.copy()
            e.is_target = True
        else:
            e = Enemy(chosen_type, self.wave, self.images)
            e.is_target = False
            
        if is_elite:
            e.is_elite = True
            # PV Elites/Boss : Scaling plus doux pour l'étage 10
            boss_scaling = 3.2 + (m_node.floor * 0.12)
            e.max_health *= boss_scaling 
            e.health = e.max_health
            e.speed *= 1.2
            e.damage *= 2
            e.elite_action_timer = random.uniform(1, 3)
            # Teinte l'image en rouge/violet pour l'Elite
            e.image.fill((255, 100, 100), special_flags=pygame.BLEND_RGB_MULT)
            e.elite_shoot_callback = self.elite_shoot
            
        self.all_sprites.add(e)
        self.enemies.add(e)
        self.mission_started = True

    def elite_shoot(self, enemy):
        # L'Elite tire plusieurs motifs selon un hasard
        pattern = random.choice(['spread', 'burst', 'spiral'])
        target_pos = self.player.pos
        base_dir = (target_pos - enemy.pos)
        if base_dir.length() == 0: base_dir = pygame.math.Vector2(1, 0)
        else: base_dir = base_dir.normalize()
        base_angle = math.atan2(base_dir.y, base_dir.x)

        if pattern == 'spread':
            # Eventail de 5 balles
            for i in range(-2, 3):
                angle = base_angle + i * 0.2
                dx, dy = math.cos(angle), math.sin(angle)
                b = Bullet(enemy.rect.centerx, enemy.rect.centery, dx, dy, 15, 260, self.images['bullet'])
                b.image = b.image.copy()
                b.image.fill(RED, special_flags=pygame.BLEND_RGB_MULT)
                self.all_sprites.add(b)
                self.enemy_bullets.add(b)
        elif pattern == 'burst':
            # Rafale de 3 balles
            for i in range(3):
                b = Bullet(enemy.rect.centerx, enemy.rect.centery, base_dir.x, base_dir.y, 12, 450, self.images['bullet'])
                b.image = b.image.copy()
                b.image.fill(NEON_PINK, special_flags=pygame.BLEND_RGB_MULT)
                # Légère randomisation pour la rafale
                b.pos += base_dir * (i * 20) 
                self.all_sprites.add(b)
                self.enemy_bullets.add(b)
        elif pattern == 'spiral':
            # Cercle de 12 balles
            for i in range(12):
                angle = base_angle + i * (math.pi / 6)
                dx, dy = math.cos(angle), math.sin(angle)
                b = Bullet(enemy.rect.centerx, enemy.rect.centery, dx, dy, 10, 220, self.images['bullet'])
                b.image = b.image.copy()
                b.image.fill(NEON_BLUE, special_flags=pygame.BLEND_RGB_MULT)
                self.all_sprites.add(b)
                self.enemy_bullets.add(b)

    def get_gamepad_shoot_input(self):
        """Détermine si le joueur tire continuellement avec la manette (Gâchette ZR/R2 souvent mappée en Axe ou Bouton)"""
        if not self.joystick:
            return False
            
        shooting = False
        
        # 1. Vérification par Bouton Classique
        # ZR / R2 / RB selon les manettes est souvent le bouton 5 ou 7
        for btn in [5, 7]:
            if self.joystick.get_numbuttons() > btn:
                if self.joystick.get_button(btn):
                    shooting = True
                    
        # 2. Vérification par Axe (Gâchettes analogiques de Xbox / PS)
        # Souvent l'axe de gâchette droite est le 5, ou Axe 2 positif
        if self.joystick.get_numaxes() >= 6:
            # Gâchette droite sur manette moderne
            if self.joystick.get_axis(5) > 0.1:
                shooting = True
        elif self.joystick.get_numaxes() >= 3:
            # Layout legacy Xbox (les deux gâchettes sur 1 axe: Z)
            if self.joystick.get_axis(2) < -0.2:
                shooting = True
                
        return shooting

    def save_screenshot(self, event_name):
        # Nettoyage du nom pour Windows
        clean_name = str(event_name).replace('<', '').replace('>', '').replace(' ', '_').replace(':', '')
        filename = f"screenshot_{clean_name}_{int(time.time()*1000)}.png"
        path = os.path.join(DEBUG_DIR, filename)
        pygame.image.save(self.screen, path)
        print(f"DEBUG: Screenshot saved to {path}")

    def update_ai_logic(self, dt):
        if self.upgrade_menu:
            # Humain-like delay et choix visuel
            if not hasattr(self, 'ai_menu_timer'): self.ai_menu_timer = 2.0
            self.ai_menu_timer -= dt
            
            # Faire défiler les options visuellement pour simuler la réflexion
            if self.ai_menu_timer > 0.5:
                if int(self.ai_menu_timer * 10) % 5 == 0: # Change tous les 0.5s environ
                    self.selected_upgrade_idx = (self.selected_upgrade_idx + 1) % len(self.current_upgrades)
            
            if self.ai_menu_timer <= 0:
                up = self.current_upgrades[self.selected_upgrade_idx]
                self.save_screenshot(f"upgrade_choice_{up['type']}")
                self.apply_upgrade(up)
                self.upgrade_menu = False
                delattr(self, 'ai_menu_timer')
            return # Sortir car on est en menu

        if self.game_over or self.victory:
            # Attendre un peu avant de relancer
            if not hasattr(self, 'ai_reset_timer'): self.ai_reset_timer = 4.0
            self.ai_reset_timer -= dt
            if self.ai_reset_timer <= 0:
                self.save_screenshot("final_screen_reset")
                self.reset_run_total()
                delattr(self, 'ai_reset_timer')
            return

        if self.state == "shop":
            # Acheter un truc au hasard si on a de l'or
            if not hasattr(self, 'ai_shop_timer'): self.ai_shop_timer = 3.0
            self.ai_shop_timer -= dt
            
            if self.ai_shop_timer <= 0:
                self.save_screenshot("leaving_shop")
                self.game_map.nodes[self.game_map.current_node_id].completed = True
                self.state = "map"
                self.play_music("map")
                self.ai_shop_timer = 3.0
            return

        if self.state == "combat":
            # 1. Deplacement vers l'ennemi le plus proche ou esquive
            target = None
            min_dist = 9999
            for enemy in self.enemies:
                d = math.hypot(enemy.pos.x - self.player.pos.x, enemy.pos.y - self.player.pos.y)
                if d < min_dist:
                    min_dist = d
                    target = enemy
            
            if target:
                dir_x = target.pos.x - self.player.pos.x
                dir_y = target.pos.y - self.player.pos.y
                dist = math.hypot(dir_x, dir_y)
                if dist > 0:
                    # Garder un peu de distance
                    if dist > 150:
                        self.player.update(dt, dir_x/dist, dir_y/dist)
                    elif dist < 100:
                        self.player.update(dt, -dir_x/dist, -dir_y/dist)
                    
                    # 2. Tir auto (simulé via handle_events ou direct)
                    m_node = self.game_map.nodes.get(self.game_map.current_node_id)
                    if m_node and m_node.mission_type != "dodge":
                        self.shoot(dir_x/dist, dir_y/dist)
            else:
                # Chercher orbes
                for orb in self.xp_orbs:
                    d = math.hypot(orb.rect.centerx - self.player.pos.x, orb.rect.centery - self.player.pos.y)
                    if d < 400:
                        dist_o = d if d > 0 else 1
                        self.player.update(dt, (orb.rect.centerx - self.player.pos.x)/dist_o, (orb.rect.centery - self.player.pos.y)/dist_o)
                        break

        elif self.state == "map":
            # Humain-like delay avant de choisir un noeud
            if not hasattr(self, 'ai_map_timer'): self.ai_map_timer = 2.0
            self.ai_map_timer -= dt
            
            if self.ai_map_timer <= 0:
                accessible = self.game_map.get_accessible_nodes()
                if accessible:
                    node = random.choice(accessible)
                    self.save_screenshot(f"map_to_{node.type}")
                    self.enter_node(node)
                    delattr(self, 'ai_map_timer')

    def update_combat(self, dt):
        m_node = self.game_map.nodes.get(self.game_map.current_node_id)
        if not m_node: return

        # === 0. VALIDATION DES OBJECTIFS ===
        self.run_timer += dt
        mission_complete = False
        
        if m_node.mission_type in ["survival", "dodge"]:
            if self.run_timer >= self.run_target_time:
                mission_complete = True
        elif m_node.mission_type == "targets":
            if getattr(self, 'mission_targets', 0) <= 0:
                mission_complete = True
        elif m_node.mission_type == "duel":
            if self.mission_started and len(self.enemies) == 0:
                mission_complete = True
                
        # --- UPDATE MESSAGE SUR LA CARTE ---
        if self.state == "map" and self.map_msg_timer > 0:
            self.map_msg_timer -= dt
            if self.map_msg_timer <= 0:
                self.map_msg = ""
                    
        if mission_complete:
            m_node.completed = True
            
            # --- RÉCOMPENSES DE MISSION ---
            # XP Bonus selon le type et l'étage
            base_xp_reward = 0
            if m_node.mission_type == "dodge": base_xp_reward = 100
            elif m_node.mission_type == "survival": base_xp_reward = 50
            elif m_node.mission_type == "targets": base_xp_reward = 60
            elif m_node.mission_type == "duel": base_xp_reward = 300 # BOSS
            
            final_xp_reward = int(base_xp_reward * (1 + m_node.floor * 0.2))
            self.player.current_xp += final_xp_reward
            
            # Texte flottant pour la récompense
            xp_txt = FloatingText(self.player.rect.centerx, self.player.rect.centery, f"+{final_xp_reward} XP !", NEON_BLUE, self.font)
            self.floating_texts.add(xp_txt)
            self.all_sprites.add(xp_txt)

            # Or via Harvesting
            harvest_bonus = int(self.player.stats["harvesting"] * (1 + m_node.floor * 0.1))
            self.player.gold += 20 + harvest_bonus # Base 20 + bonus
            
            if m_node.floor >= self.floors - 1:
                self.victory = True
                self.state = "victory"
                return
                
            self.state = "map"
            self.play_music("map")
            self.all_sprites.empty()
            for g in [self.bullets, self.enemies, self.particles, self.xp_orbs, self.floating_texts, self.enemy_bullets]:
                g.empty()
            self.all_sprites.add(self.player)
            self.mission_started = False
            return # Sortie immédiate pour éviter de process le reste de la frame
            
        dx, dy = 0, 0
        shoot_dx, shoot_dy = 0, 0
        is_shooting = False

        # === 1. LECTURE DES INPUTS SELON LE MODE ===
        if self.input_mode == "gamepad" and self.joystick:
            # Mouvement Vaisseau (Stick Gauche)
            ax0 = self.joystick.get_axis(0)
            ax1 = self.joystick.get_axis(1)
            if abs(ax0) > 0.2: dx = ax0
            if abs(ax1) > 0.2: dy = ax1
            
            # --- Visée Vaisseau (Stick Droit) ---
            rx, ry = 0, 0
            
            num_axes = self.joystick.get_numaxes()
            is_xbox = self.joystick.get_name() == 'Xbox 360 Controller' or "Xbox" in self.joystick.get_name()
            
            if is_xbox:
                # Sur Xbox Windows / Pygame, les vrais sticks sont TOUJOURS :
                # Gauche : 0 (X), 1 (Y)
                # Droit : 3 (X), 4 (Y)
                # L'axe 2 ou 5 sont les gâchettes (et polluent souvent la visée si mal mappés)
                if num_axes >= 5:
                    rx, ry = self.joystick.get_axis(3), self.joystick.get_axis(4)
            else:
                # Manettes génériques
                if num_axes >= 4:
                    rx, ry = self.joystick.get_axis(2), self.joystick.get_axis(3)
                
            # Deadzone forte pour le stick de visée pour éviter le "drift"
            is_shooting = False
            if math.hypot(rx, ry) > 0.3:
                self.stick_aim_x, self.stick_aim_y = rx, ry
                
            # TIR MANUEL: Bouton A (souvent 0) ou RB/R1 (souvent 5)
            if self.joystick.get_button(0):
                is_shooting = True
            elif self.joystick.get_numbuttons() > 5 and self.joystick.get_button(5):
                is_shooting = True 
                    
            shoot_dx, shoot_dy = self.stick_aim_x, self.stick_aim_y
            
        else: # Mode Keyboard / Mouse
            keys = pygame.key.get_pressed()
            if keys[pygame.K_LEFT] or keys[pygame.K_q] or keys[pygame.K_a]: dx = -1
            if keys[pygame.K_RIGHT] or keys[pygame.K_d]: dx = 1
            if keys[pygame.K_UP] or keys[pygame.K_z] or keys[pygame.K_w]: dy = -1
            if keys[pygame.K_DOWN] or keys[pygame.K_s]: dy = 1
            
            mx, my = pygame.mouse.get_pos()
            shoot_dx = mx - self.player.pos.x
            shoot_dy = my - self.player.pos.y
            
            is_shooting = pygame.mouse.get_pressed()[0]

        # === 2. APPLICATION DES COMMANDES ===
        self.player.rotate_towards(shoot_dx, shoot_dy)
        
        if is_shooting and math.hypot(shoot_dx, shoot_dy) > 0:
            # En mission DODGE, on ne peut pas tirer !
            m_node = self.game_map.nodes.get(self.game_map.current_node_id)
            if m_node and m_node.mission_type == "dodge":
                pass
            else:
                # Normaliser le vecteur avec math.hypot pour pygame s'il n'est pas déjà normalisé par la souris
                length = math.hypot(shoot_dx, shoot_dy)
                self.shoot(shoot_dx / length, shoot_dy / length)


        # Les mises à jour standards du jeu
        self.player.update(dt, dx, dy)
        self.bullets.update(dt)
        self.particles.update(dt)
        self.xp_orbs.update(dt, self.player)
        self.floating_texts.update(dt)
        self.enemies.update(dt, self.player.pos)
        self.enemy_bullets.update(dt)
        
        # Regeneration
        if self.player.stats["health_regen"] > 0 and self.player.health < self.player.stats["max_health"]:
            self.player.health = min(self.player.stats["max_health"], self.player.health + self.player.stats["health_regen"] * dt)
            
        self.check_level_up()

        # Augmentation continue de la difficulté en foncion du Floor + Temps
        # Le Floor actuel de la carte est l'indicateur principal
        m_node = self.game_map.nodes.get(self.game_map.current_node_id)
        current_floor = m_node.floor if m_node else 0
        effective_difficulty = 1 + (current_floor * 1.5) + (self.run_timer / 15.0)
        self.wave = int(effective_difficulty)
        
        # Le spawn rate accélère avec la difficulté
        self.spawn_rate = max(0.15, 1.2 - (effective_difficulty * 0.08))

        self.spawn_timer -= dt
        if self.spawn_timer <= 0:
            m_node = self.game_map.nodes.get(self.game_map.current_node_id)
            if m_node and m_node.mission_type == "duel":
                if len(self.enemies) == 0:
                    self.spawn_wave_enemy(is_boss=True)
            else:
                is_elite_node = m_node.type == 'elite' if m_node else False
                self.spawn_wave_enemy(is_boss=(is_elite_node and random.random() < 0.3)) # Plus de boss en Elite Node
            self.spawn_timer = self.spawn_rate

        # Collisions Piercing/Bounce
        for bullet in self.bullets:
            hit_enemies = pygame.sprite.spritecollide(bullet, self.enemies, False)
            for enemy in hit_enemies:
                if id(enemy) in bullet.hit_enemies:
                    continue
                bullet.hit_enemies.append(id(enemy))
                
                # Calcul de dégâts (Critique ?)
                dmg = bullet.damage
                is_crit = random.random() < self.player.stats["crit_chance"]
                if is_crit:
                    dmg *= self.player.stats["crit_damage"]
                    
                self.spawn_particle_explosion(bullet.rect.centerx, bullet.rect.centery, enemy.color, 3)
                if enemy.take_damage(dmg):
                    # Mort de l'ennemi -> XP
                    base_xp = 10
                    if enemy.type == 'fast': base_xp = 15
                    elif enemy.type == 'tank': base_xp = 40
                    
                    # Plus la vague avance, et selon "harvesting", plus ils donnent d'XP !
                    xp_amount = int(base_xp * (1 + self.wave * 0.1) * (1 + (self.player.stats["harvesting"] * 0.05)))
                    
                    # On choisit l'orbe selon la quantité d'XP donnée ET la chance
                    luck_roll = random.random() + self.player.stats["luck"]
                    orb_img = self.images['xp_small']
                    if xp_amount >= 30 or luck_roll > 0.95:
                        orb_img = self.images['xp_large']
                    elif xp_amount >= 15 or luck_roll > 0.7:
                        orb_img = self.images['xp_med']
                        
                    orb = ExperienceOrb(enemy.rect.centerx, enemy.rect.centery, orb_img, xp_amount)
                    self.all_sprites.add(orb)
                    self.xp_orbs.add(orb)
                    
                    # Vampirisme / Life Steal (Désormais une chance d'obtenir 1 HP de base, selon la stat "life_steal")
                    if self.player.health < self.player.stats["max_health"] and random.random() < self.player.stats["life_steal"]:
                        heal_val = 1
                        self.player.health = min(self.player.stats["max_health"], self.player.health + heal_val)
                        txt = FloatingText(self.player.rect.centerx, self.player.rect.top - 10, f"+{heal_val}", NEON_GREEN, self.font_small)
                        self.all_sprites.add(txt)
                        self.floating_texts.add(txt)
                    
                    self.score += int(xp_amount * 5)
                    self.spawn_particle_explosion(enemy.rect.centerx, enemy.rect.centery, enemy.color, 20)
                    
                    # Boss Drop !
                    if getattr(enemy, 'is_elite', False):
                        # 50% chance de drop une Gemme ou une Arme
                        if random.random() < 0.5:
                            item_type = random.choice(["weapon", "gem"])
                            if item_type == "weapon":
                                item_data = random.choice(ALL_WEAPONS)
                                self.player.active_skills[0].weapon = item_data
                                txt = FloatingText(enemy.rect.centerx, enemy.rect.centery, f"NEW: {item_data.name}!", NEON_PINK, self.font)
                            else:
                                item_data = random.choice(ALL_GEMS)
                                self.player.active_skills[0].gems.append(item_data)
                                if len(self.player.active_skills[0].gems) > self.player.active_skills[0].max_gems:
                                    self.player.active_skills[0].gems.pop(0)
                                txt = FloatingText(enemy.rect.centerx, enemy.rect.centery, f"GEMME: {item_data.name}!", NEON_BLUE, self.font)
                            
                            # Augmenter le slot de gèmes si on en ramasse beaucoup ?
                            self.player.active_skills[0].max_gems = min(5, self.player.active_skills[0].max_gems + 1)
                            self.floating_texts.add(txt)
                            self.all_sprites.add(txt)

                    if getattr(enemy, 'is_target', False):
                        if hasattr(self, 'mission_targets'):
                            self.mission_targets -= 1
                    enemy.kill()
                
                # Logique Piercing
                if bullet.pierce_count > 0:
                    bullet.pierce_count -= 1
                else:
                    bullet.kill()
                    break

        touched = pygame.sprite.spritecollide(self.player, self.enemies, False)
        for enemy in touched:
            dist = math.hypot(self.player.pos.x - enemy.pos.x, self.player.pos.y - enemy.pos.y)
            if dist < 25: 
                if self.player.invincible_timer <= 0:
                    # Esquive ?
                    if random.random() < self.player.stats["dodge"]:
                        txt = FloatingText(self.player.rect.centerx, self.player.rect.top - 10, "ESQUIVE !", NEON_BLUE, self.font_small)
                        self.all_sprites.add(txt)
                        self.floating_texts.add(txt)
                        self.player.invincible_timer = 0.5 # Petite frame d'invincibilité après esquive
                        enemy.kill() # On "évite" l'ennemi (il disparait ici pour la dynamique, ou on le traverse)
                        continue

                    # Réduction par Armure
                    d_final = max(1, enemy.damage - self.player.stats["armor"])
                    self.player.health -= d_final
                    self.player.invincible_timer = 1.0
                    self.screen_flash = 0.5
                    self.spawn_particle_explosion(self.player.rect.centerx, self.player.rect.centery, RED, 25)
                    enemy.kill()
                    
                    if self.player.health <= 0:
                        self.game_over = True

        # Collisions Projectiles Ennemis
        enemy_proj_hits = pygame.sprite.spritecollide(self.player, self.enemy_bullets, True)
        for proj in enemy_proj_hits:
            if self.player.invincible_timer <= 0:
                self.player.health -= proj.damage
                self.player.invincible_timer = 1.0
                self.screen_flash = 0.5
                if self.player.health <= 0:
                    self.game_over = True


    def draw_combat(self):
        if self.screen_flash > 0:
            flash_color = (int(RED[0] * self.screen_flash), 0, 0)
            self.screen.fill(flash_color)
            self.screen_flash -= 0.05
        else:
            self.screen.fill(BLACK)
            
        self.all_sprites.draw(self.screen)
        self.draw_hud()

        if self.game_over:
            go_txt = self.title_font.render("GAME OVER", True, NEON_PINK)
            restart_txt = self.font.render("Appuie sur A ou ESPACE pour relancer", True, WHITE)
            self.screen.blit(go_txt, (WIDTH//2 - go_txt.get_width()//2, HEIGHT//2 - 50))
            self.screen.blit(restart_txt, (WIDTH//2 - restart_txt.get_width()//2, HEIGHT//2 + 20))

    def draw_hud(self):
        # --- COMPTEUR D'OR (HAUT DROITE) ---
        gold_txt = self.font.render(f"OR: {int(self.player.gold)} G", True, (255, 215, 0))
        self.screen.blit(gold_txt, (WIDTH - gold_txt.get_width() - 20, 60))
        
        # --- MISSION INFO (HAUT MILIEU) ---
        m_node = self.game_map.nodes.get(self.game_map.current_node_id)
        if m_node:
            info = f"MISSION: {m_node.mission_type.upper()}"
            if m_node.mission_type in ["survival", "dodge"]:
                info += f" - {max(0, int(self.run_target_time - self.run_timer))}s"
            elif m_node.mission_type == "targets":
                info += f" - RESTE: {getattr(self, 'mission_targets', 0)}"
            elif m_node.mission_type == "duel":
                info += " - ELIMINE L'ELITE !"
                
            m_txt = self.font.render(info, True, NEON_YELLOW)
            self.screen.blit(m_txt, (WIDTH//2 - m_txt.get_width()//2, 10))

        # --- INFO MANETTE ---
        cx, cy = 25, 60
        pygame.draw.circle(self.screen, GRAY, (cx, cy), 10)
        
        if self.input_mode == "gamepad":
            pygame.draw.circle(self.screen, NEON_GREEN, (cx, cy), 8)
        else:
            pygame.draw.circle(self.screen, RED, (cx, cy), 8)

        # --- SCORE ---
        score_txt = self.font.render(f"Score: {self.score}", True, WHITE)
        self.screen.blit(score_txt, (10, 10))
        
        # --- BARRE DE VIE ---
        hp_w, hp_h = 320, 22
        hp_x = WIDTH//2 - hp_w//2
        hp_y = HEIGHT - 40
        hp_ratio = max(0.0, self.player.health / self.player.stats["max_health"])
        pygame.draw.rect(self.screen, (20, 20, 20), (hp_x, hp_y, hp_w, hp_h), border_radius=6)
        if hp_ratio > 0:
            pygame.draw.rect(self.screen, NEON_GREEN, (hp_x, hp_y, int(hp_w * hp_ratio), hp_h), border_radius=6)
            # Effet Glossy / Brillance en dégradé alpha
            gloss = pygame.Surface((int(hp_w * hp_ratio), hp_h//2), pygame.SRCALPHA)
            gloss.fill((255, 255, 255, 60))
            self.screen.blit(gloss, (hp_x, hp_y))
            
        pygame.draw.rect(self.screen, WHITE, (hp_x, hp_y, hp_w, hp_h), 2, border_radius=6)
        
        # Texte PV avec ombre (Clampé à 0 pour l'affichage)
        display_hp = max(0, int(self.player.health))
        hp_str = f"PV: {display_hp} / {int(self.player.stats['max_health'])}"
        hp_txt_s = self.font_small.render(hp_str, True, BLACK)
        hp_txt = self.font_small.render(hp_str, True, WHITE)
        self.screen.blit(hp_txt_s, (hp_x + hp_w//2 - hp_txt.get_width()//2 + 1, hp_y + 3))
        self.screen.blit(hp_txt, (hp_x + hp_w//2 - hp_txt.get_width()//2, hp_y + 2))

        # --- BARRE D'XP (Top Screen Style Vampire Survivors) ---
        xp_h = 16
        xp_y = 0
        xp_ratio = min(1.0, max(0.0, self.player.current_xp / self.player.xp_to_next_level))
        
        # Fond sombre
        pygame.draw.rect(self.screen, (15, 15, 30), (0, xp_y, WIDTH, xp_h))
        # Remplissage Cyan
        if xp_ratio > 0:
            pygame.draw.rect(self.screen, NEON_BLUE, (0, xp_y, int(WIDTH * xp_ratio), xp_h))
            # Brillance horizontale
            pygame.draw.rect(self.screen, (135, 206, 250), (0, xp_y, int(WIDTH * xp_ratio), xp_h//2))
            
        # Contour fin en bas
        pygame.draw.line(self.screen, WHITE, (0, xp_h), (WIDTH, xp_h), 1)

        # Level Centré sur la barre d'XP
        level_str = f"LVL {self.player.level}"
        lvl_s = self.font_small.render(level_str, True, BLACK)
        lvl_t = self.font_small.render(level_str, True, WHITE)
        # Positionné SUR la barre d'XP de façon discrète
        self.screen.blit(lvl_s, (WIDTH//2 - lvl_t.get_width()//2 + 1, xp_y + 1))
        self.screen.blit(lvl_t, (WIDTH//2 - lvl_t.get_width()//2, xp_y))

        # --- INVENTAIRE COMPÉTENCES (HUD GAUCHE) ---
        sk_y = 100
        # Afficher l'arme
        if self.player.active_skills:
            w = self.player.active_skills[0].weapon
            if w:
                w_txt = self.font_small.render(f"ARME: {w.name}", True, WHITE)
                self.screen.blit(w_txt, (10, sk_y))
                sk_y += 20
        
        for s_type, s_level in self.player.skills.items():
            col = NEON_YELLOW
            if s_level >= 5: col = NEON_PINK
            sk_txt = self.font_small.render(f"{s_type.upper()} Lvl.{s_level}", True, col)
            self.screen.blit(sk_txt, (10, sk_y))
            sk_y += 20

    def draw_pause_menu(self):
        overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
        overlay.fill((0, 0, 0, 180))
        self.screen.blit(overlay, (0, 0))
        
        state_txt = "-- JEU EN PAUSE --"
        if not self.joystick and self.joystick_count == 0:
             state_txt = "-- MANETTE DECONNECTEE --"
        
        title = self.title_font.render(state_txt, True, WHITE)
        self.screen.blit(title, (WIDTH//2 - title.get_width()//2, 80))
        
        controls = [
            ("--- CONTROLES MANETTE ---", NEON_BLUE),
            ("Stick Gauche : Deplacement", WHITE),
            ("Stick Droit  : Viser une direction", WHITE),
            ("Bouton A / RB : Tirer", NEON_GREEN),
            ("Bouton A     : Valider", WHITE),
            ("Bouton + / Start : Pause", WHITE),
            ("", WHITE),
            ("--- CONTROLES CLAVIER/SOURIS ---", NEON_PINK),
            ("Z,Q,S,D / Fleches : Deplacement", WHITE),
            ("Souris : Viser", WHITE),
            ("Clic Gauche Souris : Tirer", NEON_GREEN),
            ("ENTREE : Valider", WHITE),
            ("Touche ECHAP : Pause", WHITE)
        ]
        
        y_pos = 140
        for text, color in controls:
            txt_surf = self.font_small.render(text, True, color)
            self.screen.blit(txt_surf, (WIDTH//4 - txt_surf.get_width()//2, y_pos))
            y_pos += 20
            
        # --- Affichage Inventaire Pause ---
        inv_title = self.font.render("--- COMPÉTENCES ACQUISES ---", True, NEON_YELLOW)
        self.screen.blit(inv_title, (WIDTH*3//4 - inv_title.get_width()//2 + 50, 60))
        
        sk_y = 100
        for s_type, s_level in self.player.skills.items():
            details = [u for u in ALL_UPGRADES if u["type"] == s_type and u["level"] == s_level]
            name = details[0]["name"] if len(details) > 0 else f"{s_type} Lvl.{s_level}"
            
            col = NEON_PINK if s_level >= 5 else WHITE
            sk_txt = self.font_small.render(f"- {name}", True, col)
            self.screen.blit(sk_txt, (WIDTH*3//4 - 50, sk_y))
            sk_y += 20
            
        # --- Affichage Statistiques Détaillées (Brotato style) ---
        stat_title = self.font.render("--- CARACTÉRISTIQUES ---", True, NEON_BLUE)
        self.screen.blit(stat_title, (50, 60))
        
        stats_to_show = [
            ("PV Max", f"{self.player.stats['max_health']}", NEON_GREEN),
            ("Regen. PV", f"+{self.player.stats['health_regen']}/s", NEON_GREEN),
            ("Vol de Vie", f"{int(self.player.stats['life_steal']*100)}%", RED),
            ("Armure", f"{self.player.stats['armor']}", WHITE),
            ("Esquive", f"{int(self.player.stats['dodge']*100)}%", WHITE),
            ("Vitesse", f"{self.player.stats['speed']}", NEON_BLUE),
            ("", "", WHITE),
            ("Dégâts Base", f"{self.player.stats['damage']}", RED),
            ("Dégâts Dist", f"{self.player.stats['ranged_damage']}", RED),
            ("Dégâts %", f"+{int(self.player.stats['damage_percent']*100)}%", RED),
            ("Vitesse Atk", f"+{int((self.player.stats['attack_speed']-1)*100)}%", NEON_YELLOW),
            ("Ch. Critique", f"{int(self.player.stats['crit_chance']*100)}%", NEON_YELLOW),
            ("Dég. Critique", f"x{self.player.stats['crit_damage']}", NEON_YELLOW),
            ("Projectiles", f"{self.player.stats['multishot']}", WHITE),
            ("Perçage", f"{self.player.stats['piercing']}", WHITE),
            ("", "", WHITE),
            ("Récolte", f"{self.player.stats['harvesting']}", NEON_ORANGE),
            ("Chance", f"{int(self.player.stats['luck']*100)}%", NEON_PINK),
            ("Rayon Ramassage", f"{self.player.stats['magnet_radius']}", WHITE)
        ]
        
        sy = 100
        for name, val, col in stats_to_show:
            if name:
                txt_n = self.font_small.render(name + ":", True, WHITE)
                txt_v = self.font_small.render(val, True, col)
                self.screen.blit(txt_n, (50, sy))
                self.screen.blit(txt_v, (220, sy))
            sy += 22
            
        # pygame.display.flip() retiré pour éviter le clignotement
        pass

    def update_shop(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.running = False
            
            if event.type in (pygame.KEYDOWN, pygame.MOUSEBUTTONDOWN, pygame.MOUSEMOTION):
                self.input_mode = "keyboard"
            if event.type in (pygame.JOYAXISMOTION, pygame.JOYBUTTONDOWN, pygame.JOYBUTTONUP, pygame.JOYHATMOTION):
                self.input_mode = "gamepad"
                
            if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
                self.game_map.nodes[self.game_map.current_node_id].completed = True
                self.state = "map"
                self.play_music("map")
                
            if event.type == pygame.JOYBUTTONDOWN and event.button in [1, 6, 7]: # B ou Start
                self.game_map.nodes[self.game_map.current_node_id].completed = True
                self.state = "map"
                self.play_music("map")
                
            # Clic pour acheter ou quitter
            if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
                mx, my = pygame.mouse.get_pos()
                
                # Bouton quitter (en bas)
                rect_quit = pygame.Rect(WIDTH//2 - 100, HEIGHT - 80, 200, 50)
                if rect_quit.collidepoint(mx, my):
                    self.state = "map"
                    self.play_music("map")
                    
                # Cartes d'achat (Grid 2x2)
                card_w, card_h = 240, 260
                gap = 40
                start_x = (WIDTH - (2 * card_w + gap)) // 2
                start_y = 120
                
                for i, item in enumerate(self.shop_items):
                    row, col = i // 2, i % 2
                    rect = pygame.Rect(start_x + col * (card_w + gap), start_y + row * (card_h + gap), card_w, card_h)
                    if rect.collidepoint(mx, my) and not item["bought"]:
                        if self.player.gold >= item["price"]:
                            self.player.gold -= item["price"]
                            item["bought"] = True
                            
                            # Appliquer l'effet
                            if item["item_type"] == "upgrade":
                                self.apply_upgrade(item["data"])
                            if item["item_type"] == "weapon":
                                self.player.active_skills[0].weapon = item["data"]
                            if item["item_type"] == "gem":
                                if len(self.player.active_skills[0].gems) < self.player.active_skills[0].max_gems:
                                    self.player.active_skills[0].gems.append(item["data"])
                                else:
                                    self.player.active_skills[0].gems[0] = item["data"] # Remplace la première pour l'instant
                                    
    def generate_shop_items(self):
        self.shop_items = []
        # On génère désormais 4 objets au lieu de 3 pour plus de choix
        categories = ["upgrade", "weapon", "gem", "upgrade"] # Plus de passifs
        for i in range(4):
            cat = random.choice(categories)
            price = random.randint(40, 120)
            
            # Ajustement du prix selon la chance
            price = int(price * (1 - self.player.stats["luck"] * 0.5))
            price = max(10, price)
            
            if cat == "upgrade":
                data = random.choice(ALL_UPGRADES)
                name = data["name"]
                desc = data["desc"]
                icon = self.images.get(f"up_{data['type']}", self.images.get('node_shop'))
            elif cat == "weapon":
                data = random.choice(ALL_WEAPONS)
                name = data.name
                desc = f"Dgt x{data.base_dmg_mult} | Vit x{data.speed_mult}"
                icon = self.images.get(data.icon_key)
                price += 50
            elif cat == "gem":
                data = random.choice(ALL_GEMS)
                name = data.name
                # Traduction des types de Gemmes pour la description
                trans = {
                    "damage_mult": "Dégâts", "speed_mult": "Vitesse", 
                    "piercing": "Perçage", "bounce": "Rebonds", "fire_dmg": "Feu"
                }
                m_txt = trans.get(data.mod_type, data.mod_type)
                val = f"+{int((data.value-1)*100)}%" if "mult" in data.mod_type else f"+{data.value}"
                desc = f"Gemme : {m_txt} {val}"
                icon = self.images.get(data.icon_key)
                
            self.shop_items.append({
                "item_type": cat,
                "data": data,
                "name": name,
                "desc": desc,
                "icon": icon,
                "price": price,
                "bought": False
            })

    def draw_shop(self):
        self.screen.fill((20, 15, 30))
        
        # Titre et Or
        title = self.title_font.render("BOUTIQUE", True, NEON_BLUE)
        self.screen.blit(title, (WIDTH//2 - title.get_width()//2, 30))
        gold_txt = self.font.render(f"Or : {int(self.player.gold)} G", True, NEON_YELLOW)
        self.screen.blit(gold_txt, (WIDTH - gold_txt.get_width() - 40, 80))
        
        # Objets (Grid 2x2)
        card_w, card_h = 240, 260
        gap = 40
        start_x = (WIDTH - (2 * card_w + gap)) // 2
        start_y = 120
        
        for i, item in enumerate(self.shop_items):
            row, col = i // 2, i % 2
            x = start_x + col * (card_w + gap)
            y = start_y + row * (card_h + gap)
            
            if item["bought"]:
                pygame.draw.rect(self.screen, (30, 30, 30), (x, y, card_w, card_h))
                s = self.font.render("VENDU", True, GRAY)
                self.screen.blit(s, (x + card_w//2 - s.get_width()//2, y + card_h//2))
                continue
                
            bg_color = (40, 40, 60)
            border_color = NEON_BLUE if self.player.gold >= item["price"] else RED
            
            pygame.draw.rect(self.screen, bg_color, (x, y, card_w, card_h))
            pygame.draw.rect(self.screen, border_color, (x, y, card_w, card_h), 2)
            
            # Icon
            if item["icon"]:
                idx_rect = item["icon"].get_rect(center=(x + card_w//2, y + 50))
                self.screen.blit(pygame.transform.scale(item["icon"], (64, 64)), (idx_rect.x-16, idx_rect.y-16))
                
            # Name (Taille réduite si trop long)
            f_name = self.font if len(item["name"]) < 15 else self.font_small
            name_txt = f_name.render(item["name"], True, WHITE)
            self.screen.blit(name_txt, (x + card_w//2 - name_txt.get_width()//2, y + 90))
            
            # Desc
            sys_font = pygame.font.SysFont("Arial", 14)
            desc_lines = item["desc"].split(' ')
            line = ""
            dy = 120
            for w in desc_lines:
                if sys_font.size(line + w + " ")[0] < card_w - 20:
                    line += w + " "
                else:
                    t = sys_font.render(line.strip(), True, LIGHT_GRAY)
                    self.screen.blit(t, (x + card_w//2 - t.get_width()//2, y + dy))
                    line = w + " "
                    dy += 20
            if line:
                t = sys_font.render(line.strip(), True, LIGHT_GRAY)
                self.screen.blit(t, (x + card_w//2 - t.get_width()//2, y + dy))
                
            # Price
            p_color = NEON_YELLOW if self.player.gold >= item["price"] else RED
            p_txt = self.font.render(f"{item['price']} G", True, p_color)
            pygame.draw.rect(self.screen, (20,20,20), (x + card_w//2 - 50, y + card_h - 35, 100, 30))
            self.screen.blit(p_txt, (x + card_w//2 - p_txt.get_width()//2, y + card_h - 30))

        # Bouton quitter
        rect_quit = pygame.Rect(WIDTH//2 - 100, HEIGHT - 80, 200, 50)
        pygame.draw.rect(self.screen, GRAY, rect_quit)
        pygame.draw.rect(self.screen, WHITE, rect_quit, 2)
        q_txt = self.font.render("Quitter (ECHAP)", True, WHITE)
        self.screen.blit(q_txt, (WIDTH//2 - q_txt.get_width()//2, HEIGHT - 65))

        pygame.display.flip()

    # --- NOUVELLES METHODES DE LA CARTE ---
    def update_map(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.running = False
            
            # --- DETECTION AUTOMATIQUE DU PORT (SMART SWITCH) ---
            if event.type in (pygame.KEYDOWN, pygame.MOUSEBUTTONDOWN, pygame.MOUSEMOTION):
                self.input_mode = "keyboard"
            if event.type in (pygame.JOYAXISMOTION, pygame.JOYBUTTONDOWN, pygame.JOYBUTTONUP, pygame.JOYHATMOTION):
                self.input_mode = "gamepad"
            
            # Navigation Souris
            if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
                mx, my = pygame.mouse.get_pos()
                accessible = self.game_map.get_accessible_nodes()
                for node in accessible:
                    dist = math.hypot(mx - node.x, my - node.y)
                    if dist < 20: # Rayon de clic
                        self.enter_node(node)
                        break

    def enter_node(self, node):
        self.game_map.current_node_id = node.id
        if node.type in ['combat', 'elite']:
            # Lancement The Combat
            self.state = "combat"
            self.play_music("boss" if node.type == 'elite' else "combat")
            self.run_timer = 0
            self.wave = node.floor + 1
            
            # Init Mission
            self.run_target_time = 30 # Plus court pour les mini-jeux
            if node.mission_type == "targets":
                self.mission_targets = 10 + node.floor * 2
                self.run_target_time = 120 # Large temps pour les cibles
            elif node.mission_type == "dodge":
                self.run_target_time = 20
                
            # Reset des entités
            self.mission_started = False
            self.all_sprites.empty()
            for g in [self.bullets, self.enemies, self.particles, self.xp_orbs, self.floating_texts, self.enemy_bullets]:
                g.empty()
            self.all_sprites.add(self.player)
            
            if node.mission_type == "duel":
                self.spawn_wave_enemy(is_boss=True)
                self.mission_started = True
            self.player.pos = pygame.math.Vector2(WIDTH//2, HEIGHT//2)
            self.player.rect.center = (WIDTH//2, HEIGHT//2)
            self.player.health = self.player.stats["max_health"]
        elif node.type == 'shop':
            self.state = "shop"
            self.generate_shop_items()
        elif node.type == 'event':
            choices = [
                ("SOIN COMPLET", lambda: setattr(self.player, 'health', self.player.stats["max_health"])),
                ("BUTIN: +100 OR", lambda: setattr(self.player, 'gold', self.player.gold + 100)),
                ("BONUS XP: +50", lambda: setattr(self.player, 'current_xp', self.player.current_xp + 50))
            ]
            msg, action = random.choice(choices)
            action()
            self.map_msg = f"EVENEMENT: {msg}"
            self.map_msg_timer = 3.0
            node.completed = True

    def draw_map(self):
        self.screen.fill((10, 10, 20)) # Fond sombre spatial
        title = self.title_font.render("CARTE DU SECTEUR", True, NEON_BLUE)
        self.screen.blit(title, (WIDTH//2 - title.get_width()//2, 20))
        
        gold_txt = self.font.render(f"MONNAIE: {int(self.player.gold)} G", True, (255, 215, 0))
        self.screen.blit(gold_txt, (WIDTH - gold_txt.get_width() - 20, 20))
        
        if self.map_msg:
            msg_txt = self.font.render(self.map_msg, True, NEON_YELLOW)
            pygame.draw.rect(self.screen, (0,0,0,180), (WIDTH//2 - msg_txt.get_width()//2 - 10, 80, msg_txt.get_width() + 20, 40))
            self.screen.blit(msg_txt, (WIDTH//2 - msg_txt.get_width()//2, 85))
        
        # 1. Dessiner les lignes
        for node in self.game_map.nodes.values():
            for child_id in node.children:
                child = self.game_map.nodes[child_id]
                color = (50, 50, 80)
                if node.completed and child.completed: color = NEON_GREEN
                elif node.completed and child.id in [n.id for n in self.game_map.get_accessible_nodes()]: color = WHITE
                pygame.draw.line(self.screen, color, (node.x, node.y), (child.x, child.y), 3)
                
        # 2. Dessiner les icônes
        accessible_ids = [n.id for n in self.game_map.get_accessible_nodes()]
        
        for node in self.game_map.nodes.values():
            color = GRAY
            if node.completed: color = NEON_GREEN
            elif node.id in accessible_ids: color = WHITE
            
            pygame.draw.circle(self.screen, color, (node.x, node.y), 15)
            
            # Image de l'icône selon le type
            img_key = f"node_{node.type}"
            if node.mission_type == "dodge": img_key = "node_dodge"
            elif node.mission_type == "targets": img_key = "node_targets"
            
            node_img = self.images.get(img_key, self.images.get('node_combat'))
            
            if not node.completed:
                if node.id in accessible_ids:
                    # Pulsation pour les nœuds accessibles
                    pulse = (math.sin(pygame.time.get_ticks() * 0.005) + 1) / 2
                    pygame.draw.circle(self.screen, (255, 255, 255, 50 + pulse * 50), (node.x, node.y), 25)
                
            node_rect = node_img.get_rect(center=(node.x, node.y))
            self.screen.blit(node_img, node_rect)
            
            # Symbole Textuel (Backup si image echoue)
            if img_key not in self.images:
                symbol = "C" # Combat
                if node.type == 'elite': symbol = "E"
                elif node.type == 'shop': symbol = "$"
                elif node.type == 'event': symbol = "?"
                
                txt = self.font_very_small.render(symbol, True, BLACK)
                self.screen.blit(txt, (node.x - txt.get_width()//2, node.y - txt.get_height()//2))

        # Instructions
        inst = self.font_small.render("Cliquer sur un Noeud Blanc pour avancer", True, GRAY)
        self.screen.blit(inst, (WIDTH//2 - inst.get_width()//2, HEIGHT - 30))

    def draw_upgrade_menu(self):
        overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
        overlay.fill((0, 0, 0, 220))
        self.screen.blit(overlay, (0, 0))
        
        title = self.title_font.render("--- LEVEL UP ! ---", True, NEON_BLUE)
        sub = self.font.render("Choisis une Amelioration", True, WHITE)
        self.screen.blit(title, (WIDTH//2 - title.get_width()//2, 80))
        self.screen.blit(sub, (WIDTH//2 - sub.get_width()//2, 130))
        
        card_w, card_h = 220, 260
        gap = 40
        start_x = (WIDTH - (3 * card_w + 2 * gap)) // 2
        start_y = HEIGHT // 2 - card_h // 2 + 20
        
        for i, upg in enumerate(self.current_upgrades):
            x = start_x + i * (card_w + gap)
            y = start_y
            
            is_selected = (i == self.selected_upgrade_idx)
            border_color = NEON_GREEN if is_selected else GRAY
            bg_color = (40, 40, 40) if is_selected else (20, 20, 20)
            
            pygame.draw.rect(self.screen, bg_color, (x, y, card_w, card_h))
            pygame.draw.rect(self.screen, border_color, (x, y, card_w, card_h), 5 if is_selected else 2)
            
            # Icon drawing
            if "icon" in upg and upg["icon"]:
                idx_rect = upg["icon"].get_rect(center=(x + card_w//2, y + 60))
                self.screen.blit(pygame.transform.scale(upg["icon"], (64, 64)), (idx_rect.x-8, idx_rect.y-8))

            name_txt = self.font.render(upg["name"], True, NEON_BLUE if is_selected else LIGHT_GRAY)
            self.screen.blit(name_txt, (x + card_w//2 - name_txt.get_width()//2, y + 100))
            
            pygame.draw.line(self.screen, border_color, (x + 20, y + 130), (x + card_w - 20, y + 130), 2)
            
            words = upg["desc"].split(" ")
            line = ""
            dy_txt = 150
            for w in words:
                test_line = line + w + " "
                if self.font_small.size(test_line)[0] < card_w - 20:
                    line = test_line
                else:
                    t = self.font_small.render(line.strip(), True, WHITE)
                    self.screen.blit(t, (x + card_w//2 - t.get_width()//2, y + dy_txt))
                    line = w + " "
                    dy_txt += 25
            if line:
                t = self.font_small.render(line.strip(), True, WHITE)
                self.screen.blit(t, (x + card_w//2 - t.get_width()//2, y + dy_txt)) # Centré
                dy_txt += 25

        instruct = self.font_small.render("Stick/Fleches pour naviguer | A / ENTREE pour valider", True, GRAY)
        self.screen.blit(instruct, (WIDTH//2 - instruct.get_width()//2, HEIGHT - 50))

    def draw_victory_screen(self):
        overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
        overlay.fill((0, 0, 0, 230))
        self.screen.blit(overlay, (0, 0))
        
        v_title = self.title_font.render("VICTOIRE ABSOLUE !", True, NEON_GREEN)
        self.screen.blit(v_title, (WIDTH//2 - v_title.get_width()//2, 100))
        
        v_msg = self.font.render("Le Secteur Nebula est sauve !", True, WHITE)
        self.screen.blit(v_msg, (WIDTH//2 - v_msg.get_width()//2, 160))
        
        # Stats de fin
        stats = [
            f"Score Final: {self.score}",
            f"Niveau: {self.player.level}",
            f"Or Final: {int(self.player.gold)} G"
        ]
        
        sy = 250
        for s in stats:
            t = self.font.render(s, True, NEON_BLUE)
            self.screen.blit(t, (WIDTH//2 - t.get_width()//2, sy))
            sy += 40
            
        inst = self.font_small.render("Appuie sur ESPACE ou A pour une nouvelle run", True, GRAY)
        self.screen.blit(inst, (WIDTH//2 - inst.get_width()//2, HEIGHT - 100))

    def handle_victory_events(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.running = False
            if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
                self.reset_run_total()
            if event.type == pygame.JOYBUTTONDOWN and event.button == 0:
                self.reset_run_total()

    def reset_run_total(self):
        # Reset complet pour une nouvelle tentative
        self.reset_game()
        self.game_map = GameMap()
        self.state = "map"
        self.victory = False

if __name__ == "__main__":
    game = Game()
    game.run()