# ============================================================================
# [LIVRE] JAVASCRIPT - GUIDE ULTRA-DÉTAILLÉ POUR DÉBUTANTS
# ============================================================================
#
# [OBJECTIF] GUIDE COMPLET POUR MAÎTRISER JAVASCRIPT DE ZÉRO À EXPERT
#
# Ce guide est organisé en 4 parties progressives :
#
# PARTIE 1 : FONDAMENTAUX (javascript_partie1.txt)
# - Chapitre 0 : Introduction à JavaScript
# - Chapitre 1 : Variables et Types de Données
# - Chapitre 2 : Opérateurs et Expressions
# - Chapitre 3 : Structures de Contrôle
# - Chapitre 4 : Fonctions
#
# PARTIE 2 : STRUCTURES DE DONNÉES ET OBJETS (javascript_partie2.txt)
# - Chapitre 5 : Tableaux (Arrays)
# - Chapitre 6 : Objets
# - Chapitre 7 : Chaînes de Caractères (Strings)
# - Chapitre 8 : Destructuring et Spread/Rest
#
# PARTIE 3 : PROGRAMMATION MODERNE (javascript_partie3.txt)
# - Chapitre 9 : DOM Manipulation
# - Chapitre 10 : Événements
# - Chapitre 11 : Asynchrone (Promises, Async/Await)
# - Chapitre 12 : Modules ES6
# - Chapitre 13 : Classes et POO
# - Chapitre 14 : Gestion d'Erreurs
#
# PARTIE 4 : AVANCÉ ET PRATIQUE (javascript_partie4.txt)
# - Chapitre 15 : API Fetch et AJAX
# - Chapitre 16 : Storage (localStorage, sessionStorage)
# - Chapitre 17 : Regular Expressions
# - Chapitre 18 : JSON
# - Chapitre 19 : Bonnes Pratiques et Patterns
# - Chapitre 20 : Outils et Écosystème
#
# [TEMPS] TEMPS DE LECTURE TOTAL : ~20-25 heures
# [DOCS] PRÉREQUIS : Aucun (guide pour grands débutants)
#
# [IDEE] COMMENT UTILISER CE GUIDE :
# 1. Lisez les parties dans l'ordre
# 2. Testez TOUS les exemples dans la console du navigateur
# 3. Faites les exercices pratiques
# 4. Créez vos propres projets
#
# ============================================================================

"""
[OBJECTIF] PHILOSOPHIE DE CE GUIDE

COMMENT ? -> Explications pas à pas
POURQUOI ? -> Raisons et contexte
QUAND ? -> Cas d'usage concrets
PRATIQUE -> Exemples réels et exercices

Ce guide vise à être VOTRE SEULE RÉFÉRENCE JavaScript !
"""

# ============================================================================
# [NOTE] CONVENTIONS UTILISÉES DANS CE GUIDE
# ============================================================================

"""
[IDEE] Information importante
[REFLEXION] Question / Réflexion
[OK] Bonne pratique
[X] Mauvaise pratique
[ATTENTION] Attention / Avertissement
[CLE] Point clé à retenir
[COURS] Exercice pratique
[DOCS] Résumé
[OBJECTIF] Objectif
[TEMPS] Temps estimé
[RAPIDE] Prêt pour la suite
"""

# ============================================================================
# [OUTILS] CONFIGURATION DE L'ENVIRONNEMENT
# ============================================================================

"""
AVANT DE COMMENCER

1. NAVIGATEUR WEB MODERNE
   - Chrome (recommandé pour DevTools)
   - Firefox
   - Edge
   - Safari

2. ÉDITEUR DE CODE
   - VS Code (recommandé) : https://code.visualstudio.com/
   - Sublime Text
   - Atom
   - WebStorm

3. EXTENSIONS VS CODE RECOMMANDÉES
   - ESLint : Détection d'erreurs
   - Prettier : Formatage automatique
   - JavaScript (ES6) code snippets
   - Live Server : Serveur de développement
   - Debugger for Chrome


[IDEE] OUTILS DE DÉVELOPPEMENT NAVIGATEUR

CHROME DEVTOOLS :
1. Ouvrir : F12 ou Ctrl+Shift+I (Cmd+Option+I sur Mac)
2. Onglets importants :
   - Console : Tester du code JavaScript
   - Sources : Déboguer le code
   - Network : Voir les requêtes réseau
   - Application : Storage, cookies, etc.

CONSOLE JAVASCRIPT :
- Tester du code rapidement
- Voir les erreurs
- Déboguer avec console.log()


TESTER DU CODE JAVASCRIPT

Méthode 1 : Console du navigateur
-> F12 > Console
-> Tapez du code et appuyez sur Entrée

Méthode 2 : Fichier HTML
-> Créez index.html
-> Ajoutez <script> tag
-> Ouvrez dans le navigateur

Méthode 3 : Online Editors
-> CodePen.io
-> JSFiddle.net
-> JS Bin
-> Repl.it
"""

# ============================================================================
# [DOSSIER] STRUCTURE DE PROJET RECOMMANDÉE
# ============================================================================

"""
PROJET SIMPLE
-------------
"""
mon_projet/
├── index.html          # Page HTML principale
├── css/
│   └── style.css       # Styles CSS
├── js/
│   └── script.js       # Code JavaScript
└── images/             # Images du projet

"""
HTML DE BASE (index.html)
"""
"""
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Mon Projet JavaScript</title>
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <h1>Bienvenue en JavaScript !</h1>
    
    <!-- Votre contenu HTML -->
    
    <!-- Script à la fin du body -->
    <script src="js/script.js"></script>
</body>
</html>
"""

"""
[IDEE] POURQUOI <script> À LA FIN DU <body> ?

1. HTML chargé d'abord
   -> DOM accessible quand JS s'exécute
   
2. Meilleure performance
   -> Page s'affiche plus vite
   
3. Pas de blocage
   -> HTML ne attend pas JS


ALTERNATIVES MODERNES :
"""
# Dans <head> avec defer
"""
<head>
    <script src="js/script.js" defer></script>
</head>
"""

# defer : Télécharge en parallèle, exécute après HTML

# Avec async
"""
<script src="js/script.js" async></script>
"""

# async : Télécharge et exécute dès que prêt (ordre non garanti)


# ============================================================================
# [COURS] EXERCICE PRÉPARATOIRE
# ============================================================================

"""
AVANT DE COMMENCER LES CHAPITRES

1. [OK] Installer un navigateur moderne (Chrome recommandé)
2. [OK] Installer VS Code
3. [OK] Créer la structure de base :
   - Dossier projet
   - index.html
   - js/script.js

4. [OK] Tester l'installation :
"""

# Créer index.html
"""
<!DOCTYPE html>
<html>
<head>
    <title>Test JavaScript</title>
</head>
<body>
    <h1>Test JavaScript</h1>
    <script>
        console.log("JavaScript fonctionne ! [BRAVO]");
        alert("Bienvenue en JavaScript !");
    </script>
</body>
</html>
"""

# Ouvrir dans le navigateur
# F12 pour voir la console
# Vous devriez voir "JavaScript fonctionne ! [BRAVO]"
# Et une alerte qui s'affiche

"""
Si ça marche, vous êtes prêt ! [RAPIDE]
Commencez par javascript_partie1.txt
"""

# ============================================================================
# [DOCS] RESSOURCES COMPLÉMENTAIRES
# ============================================================================

"""
DOCUMENTATION OFFICIELLE
- MDN Web Docs : https://developer.mozilla.org/fr/docs/Web/JavaScript
- ECMAScript Spec : https://tc39.es/ecma262/
- Can I Use : https://caniuse.com/ (compatibilité navigateurs)

TUTORIELS RECOMMANDÉS
- JavaScript.info : https://javascript.info/
- freeCodeCamp : https://www.freecodecamp.org/
- Eloquent JavaScript : https://eloquentjavascript.net/
- You Don't Know JS : https://github.com/getify/You-Dont-Know-JS

PRATIQUE
- Codewars : https://www.codewars.com/
- LeetCode : https://leetcode.com/
- HackerRank : https://www.hackerrank.com/domains/tutorials/10-days-of-javascript
- Exercism : https://exercism.org/tracks/javascript

COMMUNAUTÉ
- Stack Overflow : Questions/Réponses
- Reddit : r/javascript, r/learnjavascript
- Discord : Serveurs JavaScript/Web Dev
- Twitter : #JavaScript #100DaysOfCode

CHAÎNES YOUTUBE
- Traversy Media
- The Net Ninja
- Web Dev Simplified
- Fireship
- Programming with Mosh
"""

# ============================================================================
# [OBJECTIF] PLAN D'APPRENTISSAGE RECOMMANDÉ
# ============================================================================

"""
SEMAINE 1 : FONDAMENTAUX (Partie 1)
- Jour 1-2 : Chapitres 0-1 (Introduction, Variables)
- Jour 3-4 : Chapitre 2 (Opérateurs)
- Jour 5-7 : Chapitres 3-4 (Structures de contrôle, Fonctions)
[COURS] Projet : Calculatrice simple

SEMAINE 2 : DONNÉES (Partie 2)
- Jour 1-2 : Chapitre 5 (Tableaux)
- Jour 3-4 : Chapitre 6 (Objets)
- Jour 5-7 : Chapitres 7-8 (Strings, Destructuring)
[COURS] Projet : Todo List (version console)

SEMAINE 3 : DOM ET MODERNE (Partie 3)
- Jour 1-2 : Chapitres 9-10 (DOM, Événements)
- Jour 3-4 : Chapitre 11 (Asynchrone)
- Jour 5-7 : Chapitres 12-14 (Modules, Classes, Erreurs)
[COURS] Projet : Todo List (version DOM)

SEMAINE 4 : AVANCÉ (Partie 4)
- Jour 1-2 : Chapitres 15-16 (Fetch, Storage)
- Jour 3-4 : Chapitres 17-18 (Regex, JSON)
- Jour 5-7 : Chapitres 19-20 (Best Practices, Outils)
[COURS] Projet : Application complète avec API

APRÈS 4 SEMAINES :
[OK] Vous maîtrisez JavaScript !
[OK] Vous pouvez créer des applications web interactives
[OK] Vous êtes prêt pour des frameworks (React, Vue, Angular)
"""

# ============================================================================
# [IDEE] CONSEILS POUR RÉUSSIR
# ============================================================================

"""
1. PRATIQUEZ QUOTIDIENNEMENT
   - 1-2 heures par jour > 10 heures le weekend
   - Tapez TOUS les exemples (ne copiez-collez pas !)
   - Expérimentez dans la console

2. UTILISEZ LA CONSOLE
   - console.log() est votre meilleur ami
   - Testez chaque concept immédiatement
   - Déboguez avec les DevTools

3. COMPRENEZ AVANT DE MÉMORISER
   - Posez-vous "Pourquoi ?" à chaque ligne
   - Essayez de prédire le résultat avant d'exécuter
   - Lisez les messages d'erreur attentivement

4. CONSTRUISEZ DES PROJETS
   - Commencez simple, complexifiez progressivement
   - Terminez vos projets (même imparfaits !)
   - Partagez sur GitHub

5. REJOIGNEZ LA COMMUNAUTÉ
   - Posez des questions (Stack Overflow, Reddit)
   - Aidez les débutants (meilleure façon d'apprendre !)
   - Participez à des challenges de code

6. GARDEZ CE GUIDE
   - Revenez régulièrement aux bases
   - Utilisez-le comme référence
   - Annotez, ajoutez vos notes
"""

# ============================================================================
# [RAPIDE] C'EST PARTI !
# ============================================================================

"""
Vous êtes maintenant prêt à commencer votre voyage JavaScript ! [BRAVO]

-> PROCHAINE ÉTAPE : Ouvrez javascript_partie1.txt

Bonne chance et amusez-vous bien ! [FORCE]

N'oubliez pas : Tout développeur expert était un jour débutant.
La seule différence ? Ils ont persisté et pratiqué.

Vous pouvez le faire ! *
"""

# ============================================================================
# [GUIDE] TABLE DES MATIÈRES COMPLÈTE - APERÇU
# ============================================================================

"""
═══════════════════════════════════════════════════════════════════════════
PARTIE 1 : FONDAMENTAUX DE JAVASCRIPT
═══════════════════════════════════════════════════════════════════════════

CHAPITRE 0 : Introduction à JavaScript
- Qu'est-ce que JavaScript ?
- Histoire et évolution
- JavaScript vs autres langages
- ECMAScript et versions
- Où s'exécute JavaScript ?

CHAPITRE 1 : Variables et Types de Données
- Déclaration (var, let, const)
- Types primitifs (Number, String, Boolean, null, undefined)
- Type Symbol et BigInt
- typeof operator
- Conversion de types

CHAPITRE 2 : Opérateurs et Expressions
- Opérateurs arithmétiques
- Opérateurs de comparaison
- Opérateurs logiques
- Opérateurs d'assignation
- Opérateur ternaire
- Priorité des opérateurs

CHAPITRE 3 : Structures de Contrôle
- if/else if/else
- switch
- Boucles (for, while, do...while)
- for...of et for...in
- break et continue

CHAPITRE 4 : Fonctions
- Déclaration de fonctions
- Expressions de fonctions
- Arrow functions
- Paramètres et arguments
- Return
- Scope et closures
- Fonctions callback


═══════════════════════════════════════════════════════════════════════════
PARTIE 2 : STRUCTURES DE DONNÉES ET OBJETS
═══════════════════════════════════════════════════════════════════════════

CHAPITRE 5 : Tableaux (Arrays)
- Création et accès
- Méthodes importantes (push, pop, shift, unshift)
- Itération (forEach, map, filter, reduce)
- find, findIndex, some, every
- sort, reverse, slice, splice
- Array destructuring

CHAPITRE 6 : Objets
- Création d'objets
- Propriétés et méthodes
- this keyword
- Object methods (keys, values, entries)
- Prototypes
- Object destructuring

CHAPITRE 7 : Chaînes de Caractères (Strings)
- Création et manipulation
- Méthodes courantes
- Template literals
- Méthodes de recherche
- RegEx basiques

CHAPITRE 8 : Destructuring et Spread/Rest
- Array destructuring
- Object destructuring
- Spread operator (...)
- Rest parameters
- Cas d'usage pratiques


═══════════════════════════════════════════════════════════════════════════
PARTIE 3 : PROGRAMMATION MODERNE
═══════════════════════════════════════════════════════════════════════════

CHAPITRE 9 : DOM Manipulation
- Qu'est-ce que le DOM ?
- Sélection d'éléments
- Modification de contenu
- Modification de styles
- Création et suppression d'éléments
- Navigation dans le DOM

CHAPITRE 10 : Événements
- Event listeners
- Types d'événements
- Event object
- Event bubbling et capturing
- Event delegation
- Prévention du comportement par défaut

CHAPITRE 11 : Asynchrone (Promises, Async/Await)
- Programmation synchrone vs asynchrone
- Callbacks
- Promises
- async/await
- Gestion d'erreurs asynchrones
- Promise.all, Promise.race

CHAPITRE 12 : Modules ES6
- Import/Export
- Default exports
- Named exports
- Module patterns

CHAPITRE 13 : Classes et POO
- Classes ES6
- Constructor
- Méthodes
- Héritage (extends)
- Static methods
- Getters/Setters
- Private fields

CHAPITRE 14 : Gestion d'Erreurs
- try/catch/finally
- throw
- Error types
- Custom errors
- Best practices


═══════════════════════════════════════════════════════════════════════════
PARTIE 4 : AVANCÉ ET PRATIQUE
═══════════════════════════════════════════════════════════════════════════

CHAPITRE 15 : API Fetch et AJAX
- Fetch API
- HTTP methods (GET, POST, PUT, DELETE)
- Headers et body
- Response handling
- async/await avec fetch
- Gestion d'erreurs réseau

CHAPITRE 16 : Storage (localStorage, sessionStorage)
- Web Storage API
- localStorage
- sessionStorage
- Stockage d'objets (JSON)
- Cas d'usage pratiques

CHAPITRE 17 : Regular Expressions
- Syntaxe de base
- Patterns courants
- Méthodes (test, match, replace)
- Flags
- Groupes de capture

CHAPITRE 18 : JSON
- Qu'est-ce que JSON ?
- JSON.parse()
- JSON.stringify()
- Travailler avec APIs JSON

CHAPITRE 19 : Bonnes Pratiques et Patterns
- Code propre
- Naming conventions
- Design patterns
- Performance
- Debugging

CHAPITRE 20 : Outils et Écosystème
- npm et package management
- Bundlers (Webpack, Vite)
- Linters (ESLint)
- Formatters (Prettier)
- Testing (Jest)
- Frameworks modernes (React, Vue, Angular)


═══════════════════════════════════════════════════════════════════════════
"""

# ============================================================================
# [WRAPPED_PRESENT] BONUS : PROJETS PROGRESSIFS
# ============================================================================

"""
PROJET 1 : CALCULATRICE (Après Partie 1)
- HTML/CSS pour l'interface
- JavaScript pour la logique
- Opérations basiques
- Affichage des résultats

PROJET 2 : TODO LIST CONSOLE (Après Partie 2)
- Gestion de tableaux
- Ajout/Suppression/Modification
- Filtrage
- localStorage

PROJET 3 : TODO LIST DOM (Après Partie 3)
- Interface interactive
- Event listeners
- Manipulation du DOM
- Persistance avec localStorage

PROJET 4 : WEATHER APP (Après Partie 4)
- Fetch API météo
- async/await
- Affichage dynamique
- Gestion d'erreurs
- Interface moderne

PROJET 5 : QUIZ APP
- Questions/Réponses
- Score tracking
- Timer
- Résultats finaux
- Animations CSS

PROJET 6 : MOVIE SEARCH APP
- API TMDB ou OMDB
- Recherche dynamique
- Affichage de résultats
- Détails de films
- Favoris (localStorage)
"""

# ============================================================================
# [GUIDE] CE GUIDE EST VOTRE COMPAGNON
# ============================================================================

"""
Ce guide JavaScript ultra-détaillé contient :
[OK] Explications complètes de TOUS les concepts
[OK] Exemples pratiques et testables
[OK] Exercices progressifs
[OK] Projets réels
[OK] Best practices
[OK] Pièges courants à éviter

VOUS N'AUREZ PLUS BESOIN DE :
[X] Chercher sur 10 sites différents
[X] Regarder des tutoriels incomplets
[X] Vous perdre dans la documentation

TODO CE DONT VOUS AVEZ BESOIN EST ICI ! [DOCS]

Bonne aventure dans le monde de JavaScript ! [RAPIDE]*
"""
-e 

# ============================================================================
# PARTIE 1 : FONDAMENTAUX
# ============================================================================

# ============================================================================
# [LIVRE] JAVASCRIPT - PARTIE 1 : FONDAMENTAUX
# ============================================================================
#
# [OBJECTIF] CETTE PARTIE COUVRE :
# - Chapitre 0 : Introduction complète à JavaScript
# - Chapitre 1 : Variables et Types de Données
# - Chapitre 2 : Opérateurs et Expressions
# - Chapitre 3 : Structures de Contrôle
# - Chapitre 4 : Fonctions
#
# [TEMPS] TEMPS : ~6-8 heures
# [DOCS] PRÉREQUIS : Aucun (guide pour débutants)
# ============================================================================


# ============================================================================
# [GUIDE] CHAPITRE 0 : INTRODUCTION COMPLÈTE À JAVASCRIPT
# ============================================================================

"""
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Ce qu'est JavaScript et son rôle
[OK] L'histoire et l'évolution du langage
[OK] JavaScript vs autres langages
[OK] Où et comment s'exécute JavaScript
[OK] Les outils nécessaires pour commencer
"""


# ----------------------------------------------------------------------------
# [REFLEXION] QU'EST-CE QUE JAVASCRIPT ?
# ----------------------------------------------------------------------------

"""
DÉFINITION SIMPLE

JavaScript est un langage de programmation qui permet de rendre
les pages web interactives et dynamiques.

[IDEE] EN RÉSUMÉ :

HTML = Structure (squelette de la page)
CSS = Présentation (apparence, style)
JavaScript = Comportement (interactivité, logique)


ANALOGIE SIMPLE [CONSTRUCTION]

Imaginez construire une maison :

HTML -> Structure et murs
    <h1>Titre</h1>
    <p>Paragraphe</p>

CSS -> Peinture et décoration
    h1 { color: blue; }
    p { font-size: 16px; }

JavaScript -> Électricité et automatisation
    button.addEventListener('click', () => {
        alert('Bouton cliqué !');
    });


QUE PEUT FAIRE JAVASCRIPT ?

[OK] INTERACTIVITÉ
- Réagir aux clics, survols, saisies utilisateur
- Menus déroulants, accordéons, tabs
- Animations et transitions

[OK] MANIPULATION DU DOM
- Modifier le contenu HTML
- Changer les styles CSS
- Ajouter/supprimer des éléments

[OK] VALIDATION DE FORMULAIRES
- Vérifier les entrées utilisateur
- Afficher des messages d'erreur
- Empêcher l'envoi de données invalides

[OK] REQUÊTES RÉSEAU (AJAX/Fetch)
- Communiquer avec des serveurs
- Charger des données sans recharger la page
- API REST, GraphQL

[OK] APPLICATIONS WEB COMPLÈTES
- Single Page Applications (SPA)
- Applications React, Vue, Angular
- Progressive Web Apps (PWA)

[OK] ET BIEN PLUS !
- Jeux navigateur (Canvas, WebGL)
- Applications mobiles (React Native, Ionic)
- Applications desktop (Electron)
- Serveurs (Node.js)
- IoT et robotique
"""


# ----------------------------------------------------------------------------
# [DOC] HISTOIRE ET ÉVOLUTION
# ----------------------------------------------------------------------------

"""
CHRONOLOGIE DE JAVASCRIPT


1995 - NAISSANCE [BABY_BOTTLE]
├─ Créé par Brendan Eich chez Netscape
├─ Développé en 10 jours (!!)
├─ Nom initial : Mocha -> LiveScript -> JavaScript
└─ But : Rendre les pages web interactives

[IDEE] POURQUOI "JavaScript" ?
Marketing ! Java était populaire à l'époque.
JavaScript n'a AUCUN rapport avec Java.


1997 - STANDARDISATION [DOC]
├─ ECMAScript (ES) devient le standard
├─ ES1 publié
└─ Géré par TC39 (Technical Committee 39)


1999 - ES3
├─ try/catch
├─ Regular expressions
└─ Devient vraiment utilisable


2005-2009 - L'ÂGE D'OR
├─ AJAX popularisé (Google Maps, Gmail)
├─ jQuery simplifie le DOM
├─ Node.js (2009) : JavaScript côté serveur
└─ JavaScript devient incontournable


2009 - ES5
├─ 'use strict'
├─ JSON natif
├─ Array methods (forEach, map, filter)
└─ Object.create


2015 - ES6 (ES2015) [RAPIDE] RÉVOLUTION !
├─ let/const
├─ Arrow functions
├─ Classes
├─ Promises
├─ Modules (import/export)
├─ Template literals
├─ Destructuring
├─ Spread/Rest operators
└─ Et bien plus !

[IDEE] ES6 = JAVASCRIPT MODERNE
Tout avant ES6 = "Old JavaScript"
Tout après ES6 = "Modern JavaScript"


2016-2025 - MISES À JOUR ANNUELLES
├─ ES2016 (ES7) : **, Array.includes()
├─ ES2017 (ES8) : async/await, Object entries/values
├─ ES2018 (ES9) : Rest/Spread pour objets, async iteration
├─ ES2019 (ES10) : Array.flat(), Object.fromEntries()
├─ ES2020 (ES11) : Optional chaining (?.),  Nullish coalescing (??)
├─ ES2021 (ES12) : Logical assignment (&&=, ||=, ??=)
├─ ES2022 (ES13) : Top-level await, Private fields (#)
├─ ES2023 (ES14) : Array.findLast(), toSorted()
└─ ES2024 (ES15) : Object.groupBy(), Promise.withResolvers()


AUJOURD'HUI (2025)
JavaScript est PARTOUT :
[OK] 98%+ des sites web
[OK] Frameworks populaires (React, Vue, Angular)
[OK] Mobile (React Native, Ionic)
[OK] Desktop (Electron : VS Code, Discord, Slack)
[OK] Serveurs (Node.js, Deno, Bun)
[OK] IoT, IA, blockchain
"""


# ----------------------------------------------------------------------------
# 🆚 JAVASCRIPT VS AUTRES LANGAGES
# ----------------------------------------------------------------------------

"""
COMPARAISON DÉTAILLÉE


┌──────────────┬────────────┬──────────┬──────────┬──────────┐
│              │ JavaScript │  Python  │   Java   │    C++   │
├──────────────┼────────────┼──────────┼──────────┼──────────┤
│ Type         │ Interprété │Interprété│ Compilé  │ Compilé  │
│ Typage       │ Dynamique  │ Dynamique│ Statique │ Statique │
│ Courbe       │ Facile     │ Facile   │ Moyenne  │ Difficile│
│ Usage Web    │ Dominant   │ Backend  │ Backend  │ Rare     │
│ Frontend     │ OUI [OK]     │ NON [X]   │ NON [X]   │ NON [X]   │
│ Backend      │ Oui (Node) │ Oui (DRF)│ Oui (JSP)│ Oui (C++)│
│ Mobile       │ Oui (RN)   │ Oui (Kiv)│ Oui(Java)│ Non      │
│ Popularité   │ #1 Web     │ #1 Data  │ #1 Enter │ #1 Perf  │
└──────────────┴────────────┴──────────┴──────────┴──────────┘


JAVASCRIPT - AVANTAGES [OK]

1. OMNIPRÉSENT SUR LE WEB
   - Seul langage natif des navigateurs
   - Incontournable pour le frontend
   - Pas d'alternative pour interactions web

2. POLYVALENT
   - Frontend (navigateur)
   - Backend (Node.js)
   - Mobile (React Native)
   - Desktop (Electron)
   - Un langage pour tout !

3. FACILE À APPRENDRE
   - Syntaxe accessible
   - Résultats visuels immédiats
   - Grande communauté
   - Ressources abondantes

4. ÉCOSYSTÈME RICHE
   - npm : millions de packages
   - Frameworks modernes (React, Vue, Angular)
   - Outils de développement excellents
   - Communauté active

5. OPPORTUNITÉS PROFESSIONNELLES
   - Très demandé sur le marché
   - Salaires compétitifs
   - Remote-friendly
   - Startups et grandes entreprises


JAVASCRIPT - INCONVÉNIENTS [X]

1. INCOHÉRENCES HISTORIQUES
   - Comportements étranges (== vs ===)
   - this keyword complexe
   - Hoisting peut surprendre

2. ÉVOLUTION RAPIDE
   - Nouvelles features chaque année
   - Frameworks changent souvent
   - Fatigue de l'écosystème

3. TYPAGE DYNAMIQUE
   - Erreurs détectées à l'exécution
   - TypeScript pour résoudre ça

4. COMPATIBILITÉ NAVIGATEURS
   - Différences entre navigateurs
   - Nécessite parfois des polyfills
   - Transpilation (Babel)


PYTHON - QUAND LE CHOISIR ?

[OK] Data Science et Machine Learning
[OK] Scripts et automatisation
[OK] Backend simple (Django, Flask)
[OK] Calculs scientifiques

[X] Frontend web (impossible)
[X] Applications mobiles natives


JAVA - QUAND LE CHOISIR ?

[OK] Applications d'entreprise
[OK] Android natif
[OK] Systèmes bancaires
[OK] Applications distribuées

[X] Frontend web moderne
[X] Prototypes rapides
"""


# ----------------------------------------------------------------------------
# [CODE] OÙ S'EXÉCUTE JAVASCRIPT ?
# ----------------------------------------------------------------------------

"""
JAVASCRIPT CÔTÉ CLIENT (NAVIGATEUR)


MOTEUR JAVASCRIPT

Chaque navigateur a un moteur JavaScript :

Chrome/Edge  -> V8 (Google)
Firefox      -> SpiderMonkey (Mozilla)
Safari       -> JavaScriptCore (Apple)

Ces moteurs :
1. Lisent le code JavaScript
2. Le compilent en code machine
3. L'exécutent très rapidement


APIS DU NAVIGATEUR

JavaScript dans le navigateur a accès à :

[OK] DOM (Document Object Model)
   - Manipuler HTML/CSS
   - document.querySelector()

[OK] BOM (Browser Object Model)
   - window, navigator, location
   - alert(), prompt(), confirm()

[OK] Events
   - Événements utilisateur (click, keypress, etc.)
   - addEventListener()

[OK] Fetch API
   - Requêtes HTTP
   - Communication avec serveurs

[OK] Web APIs
   - localStorage/sessionStorage
   - Geolocation
   - Canvas/WebGL
   - Web Workers
   - Et bien plus !


JAVASCRIPT CÔTÉ SERVEUR (NODE.JS)


QU'EST-CE QUE NODE.JS ?

Node.js = Environnement d'exécution JavaScript en dehors du navigateur
- Utilise le moteur V8 de Chrome
- Accès au système de fichiers
- Créer des serveurs
- Exécuter des scripts

DIFFÉRENCES :
Navigateur : DOM, window, alert()
Node.js : fs, http, process


EXEMPLE NODE.JS :
"""
// server.js
const http = require('http');

const server = http.createServer((req, res) => {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.end('<h1>Hello from Node.js!</h1>');
});

server.listen(3000);
console.log('Server running on http://localhost:3000');

# Lancer : node server.js


# ----------------------------------------------------------------------------
# [OUTILS] PREMIER CODE JAVASCRIPT
# ----------------------------------------------------------------------------

"""
MÉTHODE 1 : INLINE DANS HTML
"""

"""
<!DOCTYPE html>
<html>
<head>
    <title>JavaScript Inline</title>
</head>
<body>
    <h1>Ma première page JS</h1>
    
    <!-- JavaScript inline dans HTML -->
    <button onclick="alert('Bonjour !')">Cliquez-moi</button>
    
    <script>
        // Code JavaScript ici
        console.log('Hello World from JavaScript!');
    </script>
</body>
</html>
"""

"""
[X] PROBLÈME : Code mélangé avec HTML
Utilisez pour tests rapides seulement


MÉTHODE 2 : FICHIER EXTERNE (RECOMMANDÉ)
"""

# HTML (index.html)
"""
<!DOCTYPE html>
<html>
<head>
    <title>JavaScript Externe</title>
</head>
<body>
    <h1>Ma première page JS</h1>
    <button id="monBouton">Cliquez-moi</button>
    
    <!-- Script externe -->
    <script src="script.js"></script>
</body>
</html>
"""

# JavaScript (script.js)
"""
// Afficher dans la console
console.log('Hello from external file!');

// Sélectionner le bouton
const bouton = document.getElementById('monBouton');

// Ajouter un événement
bouton.addEventListener('click', function() {
    alert('Bouton cliqué !');
});
"""

"""
[OK] AVANTAGES :
- Code séparé et organisé
- Réutilisable
- Facile à maintenir
- Cache navigateur (performance)


MÉTHODE 3 : CONSOLE DU NAVIGATEUR
"""

# Ouvrir DevTools (F12)
# Onglet Console
# Taper directement :

console.log('Hello !');
let x = 10;
let y = 20;
console.log(x + y); // 30

"""
[OK] PARFAIT POUR :
- Tester du code rapidement
- Déboguer
- Apprendre la syntaxe
"""


# ----------------------------------------------------------------------------
# [NOTE] SYNTAXE DE BASE
# ----------------------------------------------------------------------------

"""
STRUCTURE D'UNE INSTRUCTION
"""

// Instruction simple (terminée par ;)
console.log('Hello World');

// Plusieurs instructions
let nom = 'Alice';
let age = 25;
console.log(nom, age);

"""
[IDEE] POINT-VIRGULE (;)

Optionnel en JavaScript (ASI - Automatic Semicolon Insertion)
Mais recommandé pour éviter les bugs
"""

// Sans ;
let x = 10
let y = 20
console.log(x + y) // Fonctionne

// Avec ; (recommandé)
let x = 10;
let y = 20;
console.log(x + y);


"""
COMMENTAIRES
"""

// Commentaire sur une ligne

/*
   Commentaire
   sur plusieurs
   lignes
*/

/**
 * Commentaire de documentation (JSDoc)
 * @param {string} nom - Le nom de l'utilisateur
 * @returns {string} Message de bienvenue
 */
function saluer(nom) {
    return `Bonjour ${nom} !`;
}


"""
SENSIBILITÉ À LA CASSE
"""

let nom = 'Alice';
let Nom = 'Bob';
let NOM = 'Charlie';

console.log(nom); // 'Alice'
console.log(Nom); // 'Bob'
console.log(NOM); // 'Charlie'

// Tous différents !


"""
CONVENTIONS DE NOMMAGE
"""

// [OK] camelCase (recommandé)
let monNom = 'Alice';
let maVariable = 123;
function calculerTotal() { }

// [OK] PascalCase (pour classes)
class MaClasse { }
class Utilisateur { }

// [X] snake_case (Python style, évitez en JS)
let mon_nom = 'Alice';

// [X] kebab-case (IMPOSSIBLE en JS, utilisez pour CSS)
// let mon-nom = 'Alice'; // Erreur !

// CONSTANTES (SCREAMING_SNAKE_CASE)
const MAX_USERS = 100;
const API_KEY = 'abc123';


"""
AFFICHAGE ET SORTIE
"""

// 1. console.log() - Afficher dans la console
console.log('Message');
console.log('Valeur:', 42);
console.log('Plusieurs', 'valeurs', 123);

// 2. alert() - Popup navigateur
alert('Message popup');

// 3. document.write() - Écrire dans la page (évitez !)
document.write('Hello');

// 4. innerHTML - Modifier un élément HTML
document.getElementById('demo').innerHTML = 'Nouveau contenu';


# ============================================================================
# [GUIDE] CHAPITRE 1 : VARIABLES ET TYPES DE DONNÉES
# ============================================================================

"""
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Déclarer des variables (var, let, const)
[OK] Comprendre les types de données primitifs
[OK] Utiliser typeof pour vérifier les types
[OK] Convertir entre types
[OK] Comprendre null vs undefined
"""


# ----------------------------------------------------------------------------
# [PACKAGE] DÉCLARATION DE VARIABLES
# ----------------------------------------------------------------------------

"""
3 FAÇONS DE DÉCLARER UNE VARIABLE


1. var (OLD - À ÉVITER)
"""

var nom = 'Alice';
var age = 25;

"""
[X] PROBLÈMES AVEC var :
- Function-scoped (pas block-scoped)
- Hoisting étrange
- Peut être re-déclaré
- Source de bugs


2. let (MODERNE - RECOMMANDÉ)
"""

let prenom = 'Bob';
let ville = 'Paris';

// Peut être réassigné
prenom = 'Charlie';

"""
[OK] AVANTAGES let :
- Block-scoped (portée de bloc)
- Pas de hoisting problématique
- Ne peut pas être re-déclaré
"""

let x = 10;
// let x = 20; // [X] Erreur ! Déjà déclaré

x = 20; // [OK] OK, réassignation


"""
3. const (MODERNE - RECOMMANDÉ POUR CONSTANTES)
"""

const PI = 3.14159;
const MAX_USERS = 100;

"""
[OK] AVANTAGES const :
- Valeur ne peut pas être réassignée
- Block-scoped
- Indique une valeur constante
"""

const age = 25;
// age = 30; // [X] Erreur ! Cannot assign to constant

"""
[ATTENTION] IMPORTANT : const et objets/tableaux
"""

const person = { nom: 'Alice' };
person.nom = 'Bob';  // [OK] OK (modification de propriété)
// person = {};  // [X] Erreur ! (réassignation)

const nombres = [1, 2, 3];
nombres.push(4);  // [OK] OK (modification du tableau)
// nombres = [];  // [X] Erreur ! (réassignation)


"""
[IDEE] RÈGLE D'OR :

Utilisez toujours const par défaut
Utilisez let seulement si vous devez réassigner
N'utilisez JAMAIS var


PORTÉE (SCOPE)
"""

// Portée globale
let global = 'Je suis global';

function test() {
    // Portée de fonction
    let local = 'Je suis local';
    console.log(global); // [OK] Accessible
    console.log(local);  // [OK] Accessible
}

test();
console.log(global); // [OK] Accessible
// console.log(local); // [X] Erreur ! Pas défini

// Portée de bloc
if (true) {
    let bloc = 'Je suis dans le bloc';
    console.log(bloc); // [OK] Accessible
}
// console.log(bloc); // [X] Erreur ! Pas défini


# ----------------------------------------------------------------------------
# [DESIGN] TYPES DE DONNÉES PRIMITIFS
# ----------------------------------------------------------------------------

"""
JAVASCRIPT A 7 TYPES PRIMITIFS


1. NUMBER (Nombres)
"""

let entier = 42;
let decimal = 3.14;
let negatif = -10;
let scientifique = 2.5e6;  // 2500000

// Valeurs spéciales
let infini = Infinity;
let infiniNegatif = -Infinity;
let pasUnNombre = NaN;  // Not a Number

"""
[IDEE] NUMBER EXPLIQUÉ

JavaScript a un seul type numérique (pas de int/float séparés)
- Stocké sur 64 bits (double precision)
- Range : ±(2^53 - 1)
"""

// Opérations
console.log(10 + 5);    // 15
console.log(10 - 3);    // 7
console.log(10 * 2);    // 20
console.log(10 / 3);    // 3.333...
console.log(10 % 3);    // 1 (modulo, reste)
console.log(2 ** 3);    // 8 (puissance)

// Vérifications
console.log(isNaN(NaN));        // true
console.log(isFinite(Infinity)); // false
console.log(Number.isInteger(10)); // true


"""
2. STRING (Chaînes de caractères)
"""

// Guillemets simples
let nom = 'Alice';

// Guillemets doubles
let ville = "Paris";

// Template literals (backticks) - ES6
let age = 25;
let message = `Bonjour ${nom}, vous avez ${age} ans`;

"""
[IDEE] TEMPLATE LITERALS

Avantages :
- Interpolation de variables avec ${...}
- Multiligne facile
- Expressions JavaScript
"""

// Multiligne
let texte = `Ligne 1
Ligne 2
Ligne 3`;

// Expressions
let a = 10, b = 20;
console.log(`La somme de ${a} et ${b} est ${a + b}`);

// Échappement
let quote = 'Il a dit: \'Bonjour\'';
let quote2 = "Il a dit: \"Bonjour\"";
let chemin = 'C:\\Users\\Alice';


"""
3. BOOLEAN (Booléen)
"""

let estVrai = true;
let estFaux = false;

// Résultats de comparaisons
let estMajeur = age >= 18;
let estEgal = (5 === 5);

"""
[IDEE] VALEURS TRUTHY ET FALSY

Falsy (évalués comme false) :
- false
- 0
- '' (chaîne vide)
- null
- undefined
- NaN

Truthy (évalués comme true) :
- Tout le reste !
- true
- Nombres non-zéro
- Chaînes non-vides
- Objets, tableaux (même vides)
"""

if ('') {
    console.log('Ne s'affiche pas');
}

if ('Hello') {
    console.log('S\'affiche !'); // [OK]
}


"""
4. UNDEFINED
"""

let nonDefini;
console.log(nonDefini); // undefined

// Fonction sans return
function test() {
    // pas de return
}
console.log(test()); // undefined

// Propriété inexistante
let obj = {};
console.log(obj.propriete); // undefined

"""
[IDEE] UNDEFINED = Absence de valeur
Variable déclarée mais pas initialisée


5. NULL
"""

let vide = null;

"""
[IDEE] NULL = Absence intentionnelle de valeur
null ≠ undefined

null : Valeur assignée volontairement
undefined : Absence de valeur
"""

let a;           // undefined (pas encore de valeur)
let b = null;    // null (explicitement vide)


"""
6. SYMBOL (ES6)
"""

let sym1 = Symbol('description');
let sym2 = Symbol('description');

console.log(sym1 === sym2); // false (toujours uniques)

"""
[IDEE] SYMBOL

Utilisé pour créer des propriétés d'objet uniques
Rarement utilisé par les débutants


7. BIGINT (ES2020)
"""

let grand = 9007199254740991n;
let tresgrand = BigInt("9999999999999999999999");

"""
[IDEE] BIGINT

Pour nombres entiers très grands
Au-delà de Number.MAX_SAFE_INTEGER
"""


# ----------------------------------------------------------------------------
# [RECHERCHE] typeof OPERATOR
# ----------------------------------------------------------------------------

"""
VÉRIFIER LE TYPE D'UNE VARIABLE
"""

console.log(typeof 42);          // "number"
console.log(typeof 'Hello');     // "string"
console.log(typeof true);        // "boolean"
console.log(typeof undefined);   // "undefined"
console.log(typeof null);        // "object" [ATTENTION] Bug historique !
console.log(typeof Symbol());    // "symbol"
console.log(typeof 10n);         // "bigint"

// Objets
console.log(typeof {});          // "object"
console.log(typeof []);          // "object" [ATTENTION]
console.log(typeof function(){}); // "function"

"""
[ATTENTION] PIÈGES :

1. typeof null === 'object'
   -> Bug historique de JavaScript
   -> Ne peut pas être corrigé (casse trop de code)

2. typeof [] === 'object'
   -> Les tableaux sont des objets
   -> Utilisez Array.isArray([])

3. typeof NaN === 'number'
   -> NaN est techniquement un nombre
   -> Utilisez isNaN(NaN)
"""


# ----------------------------------------------------------------------------
# [SYNC] CONVERSION DE TYPES
# ----------------------------------------------------------------------------

"""
CONVERSION STRING -> NUMBER
"""

// parseInt() - Entier
let str1 = '42';
let num1 = parseInt(str1);
console.log(num1); // 42
console.log(typeof num1); // 'number'

// parseFloat() - Décimal
let str2 = '3.14';
let num2 = parseFloat(str2);
console.log(num2); // 3.14

// Number() - Conversion générale
let num3 = Number('100');
console.log(num3); // 100

// Opérateur +
let num4 = +'50';
console.log(num4); // 50

"""
[IDEE] parseInt() avec radix
"""

parseInt('10');      // 10 (décimal)
parseInt('10', 2);   // 2 (binaire)
parseInt('10', 16);  // 16 (hexadécimal)


"""
CONVERSION NUMBER -> STRING
"""

let nombre = 42;

// toString()
let str1 = nombre.toString();
console.log(str1); // '42'

// String()
let str2 = String(nombre);
console.log(str2); // '42'

// Concatenation avec ''
let str3 = nombre + '';
console.log(str3); // '42'

// Template literal
let str4 = `${nombre}`;
console.log(str4); // '42'


"""
CONVERSION BOOLEAN
"""

// Boolean()
console.log(Boolean(1));       // true
console.log(Boolean(0));       // false
console.log(Boolean('Hello')); // true
console.log(Boolean(''));      // false
console.log(Boolean(null));    // false
console.log(Boolean([]));      // true (!)

// Double négation !!
console.log(!!'Hello'); // true
console.log(!!0);       // false


"""
COERCITION IMPLICITE (Automatique)
"""

// String + Number -> String
console.log('3' + 2);    // '32' (concatenation)
console.log('3' + 2 + 1); // '321'
console.log(1 + 2 + '3'); // '33'

// Autres opérateurs -> Number
console.log('5' - 2);   // 3
console.log('10' * '2'); // 20
console.log('12' / '3'); // 4

// Comparaisons
console.log('5' == 5);  // true (coercition)
console.log('5' === 5); // false (pas de coercition)

"""
[CLE] RÈGLE D'OR :

Utilisez toujours === (égalité stricte)
Évitez == (égalité avec coercition)
"""


# ----------------------------------------------------------------------------
# [COURS] EXERCICE PRATIQUE 1 : VARIABLES ET TYPES
# ----------------------------------------------------------------------------

"""
EXERCICE : CALCULATEUR D'ÂGE

Créez un programme qui :
1. Demande l'année de naissance
2. Calcule l'âge actuel
3. Affiche un message personnalisé
"""

// Solution
const ANNEE_ACTUELLE = 2025;

let anneeNaissance = prompt('En quelle année êtes-vous né(e) ?');
anneeNaissance = parseInt(anneeNaissance);

let age = ANNEE_ACTUELLE - anneeNaissance;

let message = `Vous avez ${age} ans`;

if (age >= 18) {
    message += ' - Vous êtes majeur(e)';
} else {
    message += ' - Vous êtes mineur(e)';
}

alert(message);


# Ce fichier continue dans javascript_partie1_suite.txt
# ============================================================================
# ============================================================================
# [LIVRE] JAVASCRIPT - PARTIE 1 (SUITE) : OPÉRATEURS ET STRUCTURES DE CONTRÔLE
# ============================================================================
#
# [OBJECTIF] CETTE PARTIE COUVRE :
# - Chapitre 2 : Opérateurs et Expressions
# - Chapitre 3 : Structures de Contrôle
# - Chapitre 4 : Fonctions
#
# [TEMPS] TEMPS : ~4-6 heures
# [DOCS] PRÉREQUIS : Chapitres 0-1 complétés
# ============================================================================


# ============================================================================
# [GUIDE] CHAPITRE 2 : OPÉRATEURS ET EXPRESSIONS
# ============================================================================

"""
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Utiliser tous les opérateurs arithmétiques
[OK] Comprendre les opérateurs de comparaison
[OK] Maîtriser les opérateurs logiques
[OK] Utiliser les opérateurs d'assignation
[OK] Comprendre la priorité des opérateurs
[OK] Utiliser l'opérateur ternaire
"""


# ----------------------------------------------------------------------------
# + OPÉRATEURS ARITHMÉTIQUES
# ----------------------------------------------------------------------------

"""
OPÉRATEURS DE BASE
"""

// Addition +
console.log(10 + 5);     // 15
console.log('Hello' + ' World'); // 'Hello World' (concatenation)

// Soustraction -
console.log(10 - 5);     // 5
console.log(-10);        // -10 (négation)

// Multiplication *
console.log(10 * 5);     // 50
console.log(2.5 * 4);    // 10

// Division /
console.log(10 / 2);     // 5
console.log(10 / 3);     // 3.333...
console.log(10 / 0);     // Infinity

// Modulo % (reste de la division)
console.log(10 % 3);     // 1
console.log(17 % 5);     // 2
console.log(20 % 4);     // 0

// Exponentiation ** (ES2016)
console.log(2 ** 3);     // 8 (2³)
console.log(5 ** 2);     // 25 (5²)
console.log(4 ** 0.5);   // 2 (racine carrée)

"""
[IDEE] CAS D'USAGE DU MODULO

1. VÉRIFIER SI NOMBRE PAIR/IMPAIR
"""
let nombre = 17;
if (nombre % 2 === 0) {
    console.log('Pair');
} else {
    console.log('Impair'); // [OK]
}

"""
2. CYCLE/ROTATION
"""
let index = 7;
let taille = 5;
let position = index % taille; // 2 (revient au début)

// Carrousel d'images
let images = ['img1.jpg', 'img2.jpg', 'img3.jpg'];
let compteur = 10;
let imageActuelle = images[compteur % images.length]; // img2.jpg

"""
3. OBTENIR LE DERNIER CHIFFRE
"""
let num = 12345;
let dernierChiffre = num % 10; // 5


"""
INCRÉMENTATION ET DÉCRÉMENTATION
"""

let x = 5;

// Post-incrémentation (utilise puis incrémente)
console.log(x++);  // 5 (affiche 5)
console.log(x);    // 6 (maintenant 6)

// Pré-incrémentation (incrémente puis utilise)
x = 5;
console.log(++x);  // 6 (affiche 6)
console.log(x);    // 6

// Post-décrémentation
x = 5;
console.log(x--);  // 5
console.log(x);    // 4

// Pré-décrémentation
x = 5;
console.log(--x);  // 4
console.log(x);    // 4

"""
[IDEE] DIFFÉRENCE IMPORTANTE
"""

let a = 5;
let b = a++;  // b = 5, a = 6
console.log(a, b); // 6, 5

let c = 5;
let d = ++c;  // d = 6, c = 6
console.log(c, d); // 6, 6

"""
[CLE] RÈGLE :

x++ : Utilise l'ancienne valeur, puis incrémente
++x : Incrémente d'abord, puis utilise la nouvelle valeur


OPÉRATIONS AVEC CHAÎNES
"""

// Concatenation avec +
let prenom = 'Alice';
let nom = 'Dupont';
let nomComplet = prenom + ' ' + nom;
console.log(nomComplet); // 'Alice Dupont'

// Convertit en string si nécessaire
console.log('Score: ' + 100); // 'Score: 100'
console.log('5' + 3); // '53' (string)
console.log(5 + 3 + '2'); // '82' (5+3=8, puis '8'+'2'='82')
console.log('2' + 5 + 3); // '253' ('2'+'5'='25', puis '25'+'3'='253')

"""
[ATTENTION] PIÈGE CLASSIQUE
"""

// Addition vs Concatenation
let result1 = 10 + 20;      // 30 (nombres)
let result2 = '10' + 20;    // '1020' (string)
let result3 = 10 + '20';    // '1020' (string)
let result4 = '10' + '20';  // '1020' (string)

// Solutions
let result5 = Number('10') + 20;  // 30
let result6 = parseInt('10') + 20; // 30
let result7 = +'10' + 20;         // 30


# ----------------------------------------------------------------------------
# [RECHERCHE] OPÉRATEURS DE COMPARAISON
# ----------------------------------------------------------------------------

"""
ÉGALITÉ ET INÉGALITÉ
"""

// Égalité stricte === (RECOMMANDÉ)
console.log(5 === 5);        // true
console.log(5 === '5');      // false (types différents)
console.log(true === 1);     // false

// Inégalité stricte !==
console.log(5 !== 6);        // true
console.log(5 !== '5');      // true (types différents)

// Égalité faible == (À ÉVITER)
console.log(5 == 5);         // true
console.log(5 == '5');       // true ([ATTENTION] coercition)
console.log(true == 1);      // true ([ATTENTION] coercition)
console.log(false == 0);     // true ([ATTENTION] coercition)
console.log(null == undefined); // true ([ATTENTION])

// Inégalité faible !=
console.log(5 != '5');       // false (coercition)

"""
[IDEE] POURQUOI ÉVITER == ?

== fait des conversions de types automatiques (coercition)
Peut causer des bugs inattendus
"""

// Exemples surprenants avec ==
console.log('' == 0);        // true [ATTENTION]
console.log('0' == 0);       // true [ATTENTION]
console.log(false == '0');   // true [ATTENTION]
console.log(null == 0);      // false [ATTENTION]
console.log(' \t\n' == 0);   // true [ATTENTION]

"""
[OK] TOUJOURS UTILISER === et !==
"""

// Bon code
if (user.age === 18) {  // [OK]
    // ...
}

// Mauvais code
if (user.age == 18) {   // [X]
    // ...
}


"""
COMPARAISONS DE GRANDEUR
"""

// Plus grand que >
console.log(10 > 5);    // true
console.log(5 > 10);    // false
console.log(5 > 5);     // false

// Plus grand ou égal >=
console.log(10 >= 5);   // true
console.log(5 >= 5);    // true
console.log(3 >= 5);    // false

// Plus petit que <
console.log(5 < 10);    // true
console.log(10 < 5);    // false

// Plus petit ou égal <=
console.log(5 <= 10);   // true
console.log(5 <= 5);    // true
console.log(10 <= 5);   // false

"""
COMPARAISON DE CHAÎNES
"""

// Comparaison lexicographique (ordre alphabétique)
console.log('a' < 'b');      // true
console.log('apple' < 'banana'); // true
console.log('A' < 'a');      // true (majuscule < minuscule)
console.log('10' < '9');     // true ([ATTENTION] comparaison de strings)
console.log('10' < '2');     // true ([ATTENTION] '1' < '2')

// Comparer des nombres sous forme de strings
console.log(10 < '9');       // false (conversion en nombre)
console.log('10' < 9);       // false (conversion en nombre)

"""
[IDEE] POUR COMPARER DES NOMBRES STOCKÉS COMME STRINGS :
"""

let str1 = '10';
let str2 = '9';

// [X] Mauvais (comparaison de strings)
console.log(str1 < str2); // true (incorrect)

// [OK] Bon (convertir d'abord)
console.log(Number(str1) < Number(str2)); // false
console.log(+str1 < +str2); // false


# ----------------------------------------------------------------------------
# [LOGIQUE] OPÉRATEURS LOGIQUES
# ----------------------------------------------------------------------------

"""
ET LOGIQUE && (AND)

true si TOUTES les conditions sont vraies
"""

console.log(true && true);     // true
console.log(true && false);    // false
console.log(false && true);    // false
console.log(false && false);   // false

// Avec variables
let age = 25;
let aPermis = true;

if (age >= 18 && aPermis) {
    console.log('Peut conduire'); // [OK]
}

// Plusieurs conditions
let estMajeur = age >= 18;
let estVerifie = true;
let aEmail = true;

if (estMajeur && estVerifie && aEmail) {
    console.log('Compte complet');
}

"""
[IDEE] COURT-CIRCUIT AVEC &&

Si la première condition est false, la deuxième n'est PAS évaluée
"""

let x = false && console.log('Ne s\'affiche pas'); // rien

// Utile pour éviter les erreurs
let user = null;
// user.name; // [X] Erreur !
let name = user && user.name; // [OK] OK (retourne null)

// Pattern courant
let userName = user && user.profile && user.profile.name;


"""
OU LOGIQUE || (OR)

true si AU MOINS UNE condition est vraie
"""

console.log(true || true);     // true
console.log(true || false);    // true
console.log(false || true);    // true
console.log(false || false);   // false

// Avec variables
let estWeekend = false;
let estFerie = true;

if (estWeekend || estFerie) {
    console.log('Pas de travail'); // [OK]
}

"""
[IDEE] COURT-CIRCUIT AVEC ||

Si la première condition est true, la deuxième n'est PAS évaluée
"""

let y = true || console.log('Ne s\'affiche pas'); // true

// Valeurs par défaut
let userName = userInput || 'Anonyme';
let port = process.env.PORT || 3000;

"""
[ATTENTION] PIÈGE AVEC ||
"""

let count = 0;
let value = count || 10; // 10 ([ATTENTION] 0 est falsy)

// Solution avec nullish coalescing (ES2020)
let value2 = count ?? 10; // 0 ([OK] seulement null/undefined)


"""
NON LOGIQUE ! (NOT)

Inverse la valeur booléenne
"""

console.log(!true);      // false
console.log(!false);     // true

console.log(!0);         // true (0 est falsy)
console.log(!1);         // false (1 est truthy)
console.log(!'');        // true ('' est falsy)
console.log(!'Hello');   // false ('Hello' est truthy)

// Double négation pour convertir en boolean
console.log(!!'Hello');  // true
console.log(!!0);        // false
console.log(!!'');       // false
console.log(!!{});       // true

// Inverser une condition
let estConnecte = true;
if (!estConnecte) {
    console.log('Veuillez vous connecter');
}


"""
COMBINAISON D'OPÉRATEURS LOGIQUES
"""

let age = 25;
let hasLicense = true;
let hasInsurance = true;
let hasTicket = false;

// Peut conduire si : majeur ET (permis OU assurance) ET pas d'amende
if (age >= 18 && (hasLicense || hasInsurance) && !hasTicket) {
    console.log('Peut conduire');
}

// Ordre de priorité : ! > && > ||
// Utilisez des parenthèses pour la clarté !

"""
[IDEE] PRIORITÉ DES OPÉRATEURS LOGIQUES

1. ! (NOT) - Plus haute priorité
2. && (AND)
3. || (OR) - Plus basse priorité
"""

// Sans parenthèses (peut prêter à confusion)
let result = false || true && false;  // false (AND d'abord)

// Avec parenthèses (clair)
let result2 = false || (true && false); // false
let result3 = (false || true) && false; // false


"""
NULLISH COALESCING ?? (ES2020)
"""

// Retourne le côté droit seulement si le gauche est null ou undefined
let value1 = null ?? 'default';      // 'default'
let value2 = undefined ?? 'default'; // 'default'
let value3 = 0 ?? 'default';         // 0 ([OK] 0 n'est pas null/undefined)
let value4 = '' ?? 'default';        // '' ([OK] '' n'est pas null/undefined)

"""
[IDEE] ?? vs ||

|| retourne le côté droit si le gauche est FALSY
?? retourne le côté droit si le gauche est NULL ou UNDEFINED
"""

let count = 0;
console.log(count || 10);  // 10 (0 est falsy)
console.log(count ?? 10);  // 0 ([OK] 0 n'est pas null/undefined)

let name = '';
console.log(name || 'Anonyme');  // 'Anonyme' ('' est falsy)
console.log(name ?? 'Anonyme');  // '' ([OK] '' n'est pas null/undefined)


"""
OPTIONAL CHAINING ?. (ES2020)
"""

let user = {
    name: 'Alice',
    address: {
        city: 'Paris'
    }
};

// Sans optional chaining
let city1 = user && user.address && user.address.city; // 'Paris'

// Avec optional chaining
let city2 = user?.address?.city; // 'Paris'

// Si user est null/undefined
user = null;
// let city3 = user.address.city; // [X] Erreur !
let city4 = user?.address?.city;  // [OK] undefined (pas d'erreur)

// Avec méthodes
let result = user?.getName?.(); // undefined (pas d'erreur si méthode n'existe pas)

// Avec tableaux
let firstItem = array?.[0]; // undefined si array est null/undefined


# ----------------------------------------------------------------------------
# [NOTE] OPÉRATEURS D'ASSIGNATION
# ----------------------------------------------------------------------------

"""
ASSIGNATION SIMPLE
"""

let x = 10;  // Assigner 10 à x

"""
ASSIGNATIONS COMBINÉES
"""

let a = 10;

// Addition-assignation +=
a += 5;  // Équivalent à : a = a + 5
console.log(a); // 15

// Soustraction-assignation -=
a -= 3;  // Équivalent à : a = a - 3
console.log(a); // 12

// Multiplication-assignation *=
a *= 2;  // Équivalent à : a = a * 2
console.log(a); // 24

// Division-assignation /=
a /= 4;  // Équivalent à : a = a / 4
console.log(a); // 6

// Modulo-assignation %=
a %= 4;  // Équivalent à : a = a % 4
console.log(a); // 2

// Exponentiation-assignation **=
a **= 3; // Équivalent à : a = a ** 3
console.log(a); // 8

"""
AVEC CHAÎNES
"""

let message = 'Bonjour';
message += ' le monde'; // 'Bonjour le monde'
message += '!';         // 'Bonjour le monde!'

"""
ASSIGNATIONS LOGIQUES (ES2021)
"""

let x = null;

// Logical OR assignment ||=
x ||= 10; // Si x est falsy, assigne 10
console.log(x); // 10

// Logical AND assignment &&=
let y = 5;
y &&= 10; // Si y est truthy, assigne 10
console.log(y); // 10

// Logical nullish assignment ??=
let z = null;
z ??= 20; // Si z est null/undefined, assigne 20
console.log(z); // 20

let w = 0;
w ??= 30; // w n'est pas null/undefined
console.log(w); // 0 (pas changé)

"""
[IDEE] CAS D'USAGE

Initialisation paresseuse (lazy initialization)
"""

let cache = null;

function getData() {
    cache ??= fetchFromAPI(); // Charge seulement si cache est null
    return cache;
}


# ----------------------------------------------------------------------------
# [?] OPÉRATEUR TERNAIRE (CONDITIONNEL)
# ----------------------------------------------------------------------------

"""
SYNTAXE DE BASE

condition ? valeurSiVrai : valeurSiFaux
"""

let age = 20;
let status = age >= 18 ? 'Majeur' : 'Mineur';
console.log(status); // 'Majeur'

"""
[IDEE] ÉQUIVALENT IF/ELSE
"""

// Ternaire
let result = (x > 0) ? 'Positif' : 'Négatif ou zéro';

// If/else équivalent
let result2;
if (x > 0) {
    result2 = 'Positif';
} else {
    result2 = 'Négatif ou zéro';
}

"""
EXEMPLES PRATIQUES
"""

// Affichage conditionnel
let score = 85;
let grade = score >= 90 ? 'A' : 
            score >= 80 ? 'B' : 
            score >= 70 ? 'C' : 'D';
console.log(grade); // 'B'

// Message pluriel
let count = 5;
let message = `${count} item${count !== 1 ? 's' : ''}`;
console.log(message); // '5 items'

count = 1;
message = `${count} item${count !== 1 ? 's' : ''}`;
console.log(message); // '1 item'

// Valeur par défaut
let userName = inputName ? inputName : 'Invité';
// Équivalent à :
let userName2 = inputName || 'Invité';

"""
TERNAIRE IMBRIQUÉ
"""

let age = 25;
let category = age < 13 ? 'Enfant' :
               age < 18 ? 'Adolescent' :
               age < 65 ? 'Adulte' : 'Senior';
console.log(category); // 'Adulte'

"""
[ATTENTION] NE PAS ABUSER

Le ternaire est utile pour les assignations simples
Pour la logique complexe, utilisez if/else
"""

// [X] Trop complexe
let result = condition1 ? 
             (condition2 ? value1 : value2) :
             (condition3 ? value3 : value4);

// [OK] Plus lisible
let result;
if (condition1) {
    result = condition2 ? value1 : value2;
} else {
    result = condition3 ? value3 : value4;
}


# ----------------------------------------------------------------------------
# [OBJECTIF] PRIORITÉ DES OPÉRATEURS
# ----------------------------------------------------------------------------

"""
ORDRE DE PRIORITÉ (du plus au moins prioritaire)

1. Parenthèses ()
2. Incrémentation/Décrémentation ++, --
3. Négation !, Négation unaire -
4. Exponentiation **
5. Multiplication/Division/Modulo *, /, %
6. Addition/Soustraction +, -
7. Comparaison <, >, <=, >=
8. Égalité ==, ===, !=, !==
9. AND logique &&
10. OR logique ||
11. Nullish coalescing ??
12. Ternaire ? :
13. Assignation =, +=, -=, etc.
"""

// Exemples
console.log(2 + 3 * 4);     // 14 (pas 20, * avant +)
console.log((2 + 3) * 4);   // 20 (parenthèses en premier)

console.log(10 / 2 * 5);    // 25 (gauche à droite)
console.log(10 / (2 * 5));  // 1

console.log(5 + 3 > 2 * 4); // false (8 > 8)
console.log((5 + 3) > (2 * 4)); // false (clair avec parenthèses)

console.log(true || false && false); // true (AND avant OR)
console.log((true || false) && false); // false

"""
[CLE] RÈGLE D'OR :

En cas de doute, utilisez des PARENTHÈSES !
La clarté est plus importante que la concision
"""

// Difficile à lire
let result = a + b * c / d - e % f;

// Clair
let result = a + ((b * c) / d) - (e % f);


# ----------------------------------------------------------------------------
# [COURS] EXERCICE PRATIQUE 2 : OPÉRATEURS
# ----------------------------------------------------------------------------

"""
EXERCICE 1 : CALCULATRICE SIMPLE

Créez une calculatrice qui :
1. Demande deux nombres
2. Demande l'opération (+, -, *, /, %)
3. Affiche le résultat
"""

// Solution
let num1 = parseFloat(prompt('Premier nombre :'));
let num2 = parseFloat(prompt('Deuxième nombre :'));
let operation = prompt('Opération (+, -, *, /, %) :');

let resultat;

if (operation === '+') {
    resultat = num1 + num2;
} else if (operation === '-') {
    resultat = num1 - num2;
} else if (operation === '*') {
    resultat = num1 * num2;
} else if (operation === '/') {
    resultat = num2 !== 0 ? num1 / num2 : 'Division par zéro impossible';
} else if (operation === '%') {
    resultat = num1 % num2;
} else {
    resultat = 'Opération invalide';
}

alert(`Résultat : ${resultat}`);

"""
EXERCICE 2 : VÉRIFICATION D'ÂGE

Créez un programme qui :
1. Demande l'âge
2. Affiche la catégorie (Enfant, Ado, Adulte, Senior)
3. Indique si peut voter (>= 18)
"""

// Solution
let age = parseInt(prompt('Votre âge :'));

let categorie = age < 13 ? 'Enfant' :
                age < 18 ? 'Adolescent' :
                age < 65 ? 'Adulte' : 'Senior';

let peutVoter = age >= 18;

let message = `Catégorie : ${categorie}\n`;
message += peutVoter ? 'Vous pouvez voter' : 'Vous ne pouvez pas encore voter';

alert(message);


# ============================================================================
# [GUIDE] CHAPITRE 3 : STRUCTURES DE CONTRÔLE
# ============================================================================

"""
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Utiliser if/else if/else
[OK] Utiliser switch
[OK] Maîtriser les boucles (for, while, do...while)
[OK] Utiliser for...of et for...in
[OK] Contrôler les boucles avec break et continue
[OK] Imbriquer des structures de contrôle
"""


# ----------------------------------------------------------------------------
# [MELANGE] IF / ELSE IF / ELSE
# ----------------------------------------------------------------------------

"""
IF SIMPLE
"""

let age = 20;

if (age >= 18) {
    console.log('Vous êtes majeur');
}

"""
[IDEE] SYNTAXE

if (condition) {
    // Code exécuté si condition est true
}

condition : Expression évaluée à true ou false
{ } : Bloc de code (optionnel si une seule instruction)
"""

// Sans accolades (une seule instruction)
if (age >= 18)
    console.log('Majeur'); // [OK] Fonctionne

// [ATTENTION] Risque d'erreur
if (age >= 18)
    console.log('Majeur');
    console.log('Peut voter'); // [X] S'exécute toujours !

// [OK] Toujours utiliser des accolades
if (age >= 18) {
    console.log('Majeur');
    console.log('Peut voter');
}


"""
IF / ELSE
"""

let age = 15;

if (age >= 18) {
    console.log('Majeur');
} else {
    console.log('Mineur');
}

"""
[IDEE] SYNTAXE COMPLÈTE

if (condition) {
    // Si true
} else {
    // Si false
}
"""


"""
IF / ELSE IF / ELSE
"""

let score = 85;

if (score >= 90) {
    console.log('A - Excellent');
} else if (score >= 80) {
    console.log('B - Très bien'); // [OK] Exécuté
} else if (score >= 70) {
    console.log('C - Bien');
} else if (score >= 60) {
    console.log('D - Passable');
} else {
    console.log('F - Insuffisant');
}

"""
[IDEE] COMMENT ÇA MARCHE ?

1. Évalue la première condition (score >= 90) -> false
2. Évalue la deuxième (score >= 80) -> true
3. Exécute ce bloc
4. IGNORE le reste (else if et else)


CONDITIONS MULTIPLES
"""

let age = 25;
let hasLicense = true;

if (age >= 18 && hasLicense) {
    console.log('Peut conduire');
} else if (age >= 18) {
    console.log('Doit passer le permis');
} else {
    console.log('Trop jeune');
}


"""
IMBRICATION
"""

let user = {
    isLoggedIn: true,
    isAdmin: false,
    isPremium: true
};

if (user.isLoggedIn) {
    console.log('Bienvenue !');
    
    if (user.isAdmin) {
        console.log('Accès admin');
    } else {
        console.log('Accès utilisateur');
        
        if (user.isPremium) {
            console.log('Fonctionnalités premium activées'); // [OK]
        }
    }
} else {
    console.log('Veuillez vous connecter');
}

"""
[ATTENTION] ÉVITER LES IMBRICATIONS TROP PROFONDES

Difficile à lire et maintenir
"""

// [X] Trop imbriqué
if (condition1) {
    if (condition2) {
        if (condition3) {
            if (condition4) {
                // ...
            }
        }
    }
}

// [OK] Mieux : Early returns ou conditions combinées
if (!condition1) return;
if (!condition2) return;
if (!condition3) return;
if (!condition4) return;
// ...


# ----------------------------------------------------------------------------
# [SYNC] SWITCH
# ----------------------------------------------------------------------------

"""
SYNTAXE DE BASE
"""

let jour = 'lundi';

switch (jour) {
    case 'lundi':
        console.log('Début de semaine');
        break;
    case 'mardi':
    case 'mercredi':
    case 'jeudi':
        console.log('Milieu de semaine');
        break;
    case 'vendredi':
        console.log('Bientôt le weekend !');
        break;
    case 'samedi':
    case 'dimanche':
        console.log('Weekend !');
        break;
    default:
        console.log('Jour invalide');
}

"""
[IDEE] COMMENT ÇA MARCHE ?

1. Évalue l'expression (jour)
2. Compare avec chaque case (===)
3. Si correspondance, exécute le code
4. break : Sort du switch
5. default : Si aucun case ne correspond


[ATTENTION] IMPORTANCE DU break
"""

let number = 2;

switch (number) {
    case 1:
        console.log('Un');
        // Pas de break !
    case 2:
        console.log('Deux'); // [OK] Exécuté
        // Pas de break !
    case 3:
        console.log('Trois'); // [OK] Exécuté aussi !
        break;
    default:
        console.log('Autre');
}

// Affiche : 'Deux' puis 'Trois' (fall-through)

"""
[IDEE] FALL-THROUGH INTENTIONNEL

Parfois utile pour plusieurs cases -> même action
"""

let mois = 'février';
let jours;

switch (mois) {
    case 'janvier':
    case 'mars':
    case 'mai':
    case 'juillet':
    case 'août':
    case 'octobre':
    case 'décembre':
        jours = 31;
        break;
    case 'avril':
    case 'juin':
    case 'septembre':
    case 'novembre':
        jours = 30;
        break;
    case 'février':
        jours = 28; // ou 29
        break;
    default:
        jours = 0;
}

console.log(`${mois} a ${jours} jours`);


"""
SWITCH vs IF/ELSE

SWITCH - QUAND L'UTILISER ?
"""

// [OK] Bon usage : Beaucoup de valeurs exactes
switch (color) {
    case 'red': ...
    case 'blue': ...
    case 'green': ...
    // ...
}

// [X] Mauvais usage : Plages de valeurs
switch (age) {
    case age < 18: // [X] Ne fonctionne pas comme attendu
        ...
}

// [OK] Utilisez if/else pour plages
if (age < 18) {
    ...
} else if (age < 65) {
    ...
}

"""
SWITCH MODERNE (ES6+)
"""

// Avec expressions
let action = 'save';
let message;

switch (action) {
    case 'save':
        message = 'Sauvegarde en cours...';
        break;
    case 'load':
        message = 'Chargement...';
        break;
    case 'delete':
        message = 'Suppression...';
        break;
    default:
        message = 'Action inconnue';
}

// Alternative moderne : Object lookup
const messages = {
    save: 'Sauvegarde en cours...',
    load: 'Chargement...',
    delete: 'Suppression...'
};

message = messages[action] || 'Action inconnue';


# ----------------------------------------------------------------------------
# [SYNC] BOUCLES
# ----------------------------------------------------------------------------

"""
BOUCLE FOR

La boucle la plus courante en JavaScript
"""

// Syntaxe de base
for (let i = 0; i < 5; i++) {
    console.log(i);
}
// Affiche : 0, 1, 2, 3, 4

"""
[IDEE] ANATOMIE DU FOR

for (initialisation; condition; incrémentation) {
    // Code à répéter
}

1. initialisation : Exécutée UNE FOIS au début (let i = 0)
2. condition : Vérifiée AVANT chaque itération (i < 5)
3. incrémentation : Exécutée APRÈS chaque itération (i++)
4. Si condition false -> Sortie de la boucle
"""

// Étape par étape
for (let i = 0; i < 3; i++) {
    console.log(`Itération ${i}`);
}

/*
Exécution :
1. i = 0, vérifie 0 < 3 (true) -> affiche "Itération 0", i devient 1
2. Vérifie 1 < 3 (true) -> affiche "Itération 1", i devient 2
3. Vérifie 2 < 3 (true) -> affiche "Itération 2", i devient 3
4. Vérifie 3 < 3 (false) -> FIN
*/


"""
EXEMPLES PRATIQUES
"""

// Compter de 1 à 10
for (let i = 1; i <= 10; i++) {
    console.log(i);
}

// Compter de 10 à 1
for (let i = 10; i >= 1; i--) {
    console.log(i);
}

// Par pas de 2
for (let i = 0; i < 10; i += 2) {
    console.log(i); // 0, 2, 4, 6, 8
}

// Parcourir un tableau
let fruits = ['pomme', 'banane', 'orange'];
for (let i = 0; i < fruits.length; i++) {
    console.log(fruits[i]);
}

// Avec index et valeur
for (let i = 0; i < fruits.length; i++) {
    console.log(`${i}: ${fruits[i]}`);
}
// 0: pomme
// 1: banane
// 2: orange

// Boucle imbriquée (multiplication)
for (let i = 1; i <= 3; i++) {
    for (let j = 1; j <= 3; j++) {
        console.log(`${i} x ${j} = ${i * j}`);
    }
}


"""
BOUCLE WHILE

Boucle tant que la condition est vraie
"""

let count = 0;
while (count < 5) {
    console.log(count);
    count++;
}
// Affiche : 0, 1, 2, 3, 4

"""
[IDEE] QUAND UTILISER WHILE ?

- Nombre d'itérations inconnu
- Basé sur une condition dynamique
"""

// Demander jusqu'à bonne réponse
let password;
while (password !== 'secret') {
    password = prompt('Mot de passe :');
}
console.log('Accès accordé !');

// Lire un fichier ligne par ligne (Node.js)
let line;
while (line = readNextLine()) {
    processLine(line);
}

"""
[ATTENTION] BOUCLE INFINIE

Si la condition est toujours vraie -> Boucle infinie !
"""

// [X] Ne faites JAMAIS ça
let i = 0;
while (i < 10) {
    console.log(i);
    // Oubli de i++ -> Boucle infinie !
}

// [OK] Toujours incrémenter/modifier la condition
let i = 0;
while (i < 10) {
    console.log(i);
    i++; // Important !
}


"""
BOUCLE DO...WHILE

Exécute AU MOINS UNE FOIS, puis vérifie la condition
"""

let number;
do {
    number = prompt('Entrez un nombre entre 1 et 10 :');
} while (number < 1 || number > 10);

console.log('Merci !');

"""
[IDEE] DIFFÉRENCE WHILE vs DO...WHILE

WHILE : Vérifie AVANT -> Peut ne jamais s'exécuter
"""
let x = 10;
while (x < 5) {
    console.log(x); // Ne s'affiche jamais
}

"""
DO...WHILE : Exécute AU MOINS UNE FOIS
"""
let y = 10;
do {
    console.log(y); // S'affiche une fois (10)
} while (y < 5);


"""
FOR...OF (ES6)

Parcourir les VALEURS d'un iterable (tableau, string, Set, Map)
"""

let fruits = ['pomme', 'banane', 'orange'];

for (let fruit of fruits) {
    console.log(fruit);
}
// pomme
// banane
// orange

"""
[IDEE] AVANTAGES FOR...OF

- Syntaxe simple et lisible
- Pas besoin d'index
- Fonctionne avec tous les iterables
"""

// Avec string
let text = 'Hello';
for (let char of text) {
    console.log(char);
}
// H, e, l, l, o

// Avec Set
let uniqueNumbers = new Set([1, 2, 3, 4, 5]);
for (let num of uniqueNumbers) {
    console.log(num);
}

// Avec Map
let map = new Map([
    ['nom', 'Alice'],
    ['age', 25]
]);
for (let [key, value] of map) {
    console.log(`${key}: ${value}`);
}


"""
FOR...IN

Parcourir les CLÉS (propriétés) d'un objet
"""

let person = {
    nom: 'Alice',
    age: 25,
    ville: 'Paris'
};

for (let key in person) {
    console.log(`${key}: ${person[key]}`);
}
// nom: Alice
// age: 25
// ville: Paris

"""
[ATTENTION] FOR...IN vs FOR...OF

FOR...IN -> CLÉS (propriétés)
FOR...OF -> VALEURS
"""

let arr = ['a', 'b', 'c'];

// FOR...IN (indices - pas recommandé pour tableaux)
for (let index in arr) {
    console.log(index); // 0, 1, 2
}

// FOR...OF (valeurs - recommandé)
for (let value of arr) {
    console.log(value); // 'a', 'b', 'c'
}

"""
[CLE] RÈGLE :

Tableaux -> for...of ou forEach()
Objets -> for...in ou Object.keys()


BREAK ET CONTINUE
"""

// BREAK : Sortir de la boucle
for (let i = 0; i < 10; i++) {
    if (i === 5) {
        break; // Sort immédiatement
    }
    console.log(i);
}
// Affiche : 0, 1, 2, 3, 4 (s'arrête à 5)

// CONTINUE : Passer à l'itération suivante
for (let i = 0; i < 10; i++) {
    if (i % 2 === 0) {
        continue; // Saute les pairs
    }
    console.log(i);
}
// Affiche : 1, 3, 5, 7, 9 (seulement les impairs)

"""
EXEMPLES PRATIQUES
"""

// Chercher dans un tableau
let nombres = [10, 25, 30, 45, 50];
let cible = 30;

for (let i = 0; i < nombres.length; i++) {
    if (nombres[i] === cible) {
        console.log(`Trouvé à l'index ${i}`);
        break; // Arrête dès qu'on trouve
    }
}

// Filtrer les nombres pairs
let nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
for (let num of nums) {
    if (num % 2 !== 0) continue; // Saute les impairs
    console.log(num); // 2, 4, 6, 8, 10
}

// Validation avec break
let isValid = true;
let inputs = ['alice', 'bob', '', 'charlie']; // '' invalide

for (let input of inputs) {
    if (input === '') {
        isValid = false;
        break; // Arrête dès qu'on trouve une erreur
    }
}

console.log(isValid ? 'Tout est valide' : 'Erreur détectée');


"""
[COURS] EXERCICE PRATIQUE 3 : BOUCLES

EXERCICE 1 : FizzBuzz
Affichez les nombres de 1 à 100, mais :
- Si divisible par 3 -> "Fizz"
- Si divisible par 5 -> "Buzz"  
- Si divisible par 3 ET 5 -> "FizzBuzz"
"""

// Solution
for (let i = 1; i <= 100; i++) {
    if (i % 3 === 0 && i % 5 === 0) {
        console.log('FizzBuzz');
    } else if (i % 3 === 0) {
        console.log('Fizz');
    } else if (i % 5 === 0) {
        console.log('Buzz');
    } else {
        console.log(i);
    }
}

"""
EXERCICE 2 : Somme des nombres
Calculez la somme de 1 à 100
"""

// Solution
let somme = 0;
for (let i = 1; i <= 100; i++) {
    somme += i;
}
console.log(somme); // 5050

"""
EXERCICE 3 : Table de multiplication
Affichez la table de multiplication de 1 à 10
"""

// Solution
for (let i = 1; i <= 10; i++) {
    console.log(`Table de ${i} :`);
    for (let j = 1; j <= 10; j++) {
        console.log(`${i} x ${j} = ${i * j}`);
    }
    console.log('---');
}


# ============================================================================
# [GUIDE] CHAPITRE 4 : FONCTIONS
# ============================================================================

"""
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Déclarer et appeler des fonctions
[OK] Utiliser paramètres et arguments
[OK] Comprendre return
[OK] Utiliser les arrow functions (ES6)
[OK] Comprendre le scope (portée)
[OK] Maîtriser les closures
[OK] Utiliser les callbacks
"""


# ----------------------------------------------------------------------------
# [PACKAGE] DÉCLARATION DE FONCTIONS
# ----------------------------------------------------------------------------

"""
SYNTAXE DE BASE
"""

// Déclarer une fonction
function saluer() {
    console.log('Bonjour !');
}

// Appeler la fonction
saluer(); // Bonjour !

"""
[IDEE] ANATOMIE D'UNE FONCTION

function nomFonction() {
    // Corps de la fonction
}

function : Mot-clé
nomFonction : Nom (camelCase recommandé)
() : Parenthèses (paramètres optionnels)
{} : Corps de la fonction
"""


"""
AVEC PARAMÈTRES
"""

function saluer(nom) {
    console.log(`Bonjour ${nom} !`);
}

saluer('Alice'); // Bonjour Alice !
saluer('Bob');   // Bonjour Bob !

"""
[IDEE] PARAMÈTRES vs ARGUMENTS

PARAMÈTRE : Variable dans la définition
ARGUMENT : Valeur passée lors de l'appel
"""

function additionner(a, b) { // a, b = paramètres
    return a + b;
}

let resultat = additionner(5, 3); // 5, 3 = arguments


"""
PLUSIEURS PARAMÈTRES
"""

function presenter(prenom, nom, age) {
    console.log(`Je m'appelle ${prenom} ${nom} et j'ai ${age} ans`);
}

presenter('Alice', 'Dupont', 25);
// Je m'appelle Alice Dupont et j'ai 25 ans


"""
PARAMÈTRES PAR DÉFAUT (ES6)
"""

function saluer(nom = 'Invité') {
    console.log(`Bonjour ${nom} !`);
}

saluer('Alice'); // Bonjour Alice !
saluer();        // Bonjour Invité ! (valeur par défaut)

// Avec plusieurs paramètres
function creerUtilisateur(nom, role = 'utilisateur', actif = true) {
    return {
        nom: nom,
        role: role,
        actif: actif
    };
}

let user1 = creerUtilisateur('Alice');
// { nom: 'Alice', role: 'utilisateur', actif: true }

let user2 = creerUtilisateur('Bob', 'admin');
// { nom: 'Bob', role: 'admin', actif: true }

let user3 = creerUtilisateur('Charlie', 'moderateur', false);
// { nom: 'Charlie', role: 'moderateur', actif: false }


"""
RETURN : RETOURNER UNE VALEUR
"""

function additionner(a, b) {
    return a + b;
}

let somme = additionner(5, 3);
console.log(somme); // 8

"""
[IDEE] RETURN

- Retourne une valeur à l'appelant
- ARRÊTE l'exécution de la fonction
- Sans return : fonction retourne undefined
"""

function tester() {
    console.log('Avant return');
    return 'Valeur retournée';
    console.log('Après return'); // [X] Ne s'exécute jamais
}

let resultat = tester();
// Affiche : 'Avant return'
console.log(resultat); // 'Valeur retournée'

// Fonction sans return
function direBonjour() {
    console.log('Bonjour');
}

let valeur = direBonjour(); // Affiche 'Bonjour'
console.log(valeur);        // undefined


"""
RETURN MULTIPLE (avec objet ou tableau)
"""

// Retourner un objet
function creerPersonne(nom, age) {
    return {
        nom: nom,
        age: age,
        majeur: age >= 18
    };
}

let personne = creerPersonne('Alice', 25);
console.log(personne.majeur); // true

// Retourner un tableau
function calculerStats(nombres) {
    let somme = 0;
    let max = nombres[0];
    let min = nombres[0];
    
    for (let num of nombres) {
        somme += num;
        if (num > max) max = num;
        if (num < min) min = num;
    }
    
    return [somme, max, min];
}

let [total, maximum, minimum] = calculerStats([5, 2, 9, 1, 7]);
console.log(total, maximum, minimum); // 24, 9, 1


"""
EARLY RETURN (RETOUR ANTICIPÉ)
"""

function diviser(a, b) {
    // Vérification et sortie anticipée
    if (b === 0) {
        return 'Division par zéro impossible';
    }
    
    return a / b;
}

console.log(diviser(10, 2)); // 5
console.log(diviser(10, 0)); // 'Division par zéro impossible'

"""
[OK] AVANTAGES EARLY RETURN

- Code plus lisible
- Évite imbrications profondes
- Gère les cas d'erreur d'abord
"""

// [X] Sans early return (imbriqué)
function traiter(data) {
    if (data) {
        if (data.isValid) {
            if (data.user) {
                // Logique principale
            }
        }
    }
}

// [OK] Avec early return (plat)
function traiter(data) {
    if (!data) return;
    if (!data.isValid) return;
    if (!data.user) return;
    
    // Logique principale
}


# ----------------------------------------------------------------------------
# [SCENARIO] EXPRESSIONS DE FONCTIONS
# ----------------------------------------------------------------------------

"""
FONCTION ANONYME ASSIGNÉE À UNE VARIABLE
"""

const saluer = function(nom) {
    console.log(`Bonjour ${nom} !`);
};

saluer('Alice'); // Bonjour Alice !

"""
[IDEE] DIFFÉRENCE AVEC DÉCLARATION

DÉCLARATION DE FONCTION :
"""
// Utilisable AVANT la déclaration (hoisting)
direBonjour(); // [OK] Fonctionne

function direBonjour() {
    console.log('Bonjour');
}

"""
EXPRESSION DE FONCTION :
"""
// [X] Erreur si utilisée avant
// saluer(); // ReferenceError

const saluer = function() {
    console.log('Bonjour');
};

saluer(); // [OK] Fonctionne

"""
[CLE] RÈGLE :

Déclarations -> Hoisting (montées en haut)
Expressions -> Pas de hoisting


FONCTIONS NOMMÉES DANS EXPRESSIONS
"""

const factorielle = function fact(n) {
    if (n <= 1) return 1;
    return n * fact(n - 1); // Récursion avec nom
};

console.log(factorielle(5)); // 120

"""
[IDEE] AVANTAGE : Nom utile pour récursion et stack traces
"""


# ----------------------------------------------------------------------------
# [CIBLE] ARROW FUNCTIONS (ES6)
# ----------------------------------------------------------------------------

"""
SYNTAXE DE BASE
"""

// Fonction classique
const additionner = function(a, b) {
    return a + b;
};

// Arrow function
const additionner = (a, b) => {
    return a + b;
};

// Arrow function simplifiée (return implicite)
const additionner = (a, b) => a + b;

"""
[IDEE] SYNTAXE ARROW FUNCTION

(paramètres) => { corps }

Simplifications possibles :
- Un paramètre : paramètre => ...
- Return implicite : (params) => expression
- Objet retourné : (params) => ({ prop: value })
"""


"""
EXEMPLES DE SIMPLIFICATION
"""

// Plusieurs paramètres
const multiplier = (a, b) => a * b;

// Un paramètre (pas besoin de parenthèses)
const doubler = x => x * 2;

// Zéro paramètre (parenthèses obligatoires)
const direBonjour = () => console.log('Bonjour');

// Corps multi-lignes (accolades nécessaires)
const calculer = (a, b) => {
    let somme = a + b;
    let produit = a * b;
    return { somme, produit };
};

// Retourner un objet (parenthèses nécessaires)
const creerPersonne = (nom, age) => ({ nom: nom, age: age });
// ou avec shorthand
const creerPersonne = (nom, age) => ({ nom, age });


"""
COMPARAISON SYNTAXES
"""

// Classique
function carre1(x) {
    return x * x;
}

// Expression
const carre2 = function(x) {
    return x * x;
};

// Arrow
const carre3 = (x) => {
    return x * x;
};

// Arrow simplifié
const carre4 = x => x * x;

// Tous équivalents !


"""
ARROW FUNCTIONS ET THIS

[ATTENTION] DIFFÉRENCE IMPORTANTE : Arrow functions n'ont pas leur propre this
"""

// Fonction classique
const obj1 = {
    nom: 'Alice',
    direNom: function() {
        console.log(this.nom); // 'Alice' (this = obj1)
    }
};

obj1.direNom(); // 'Alice'

// Arrow function
const obj2 = {
    nom: 'Bob',
    direNom: () => {
        console.log(this.nom); // undefined (this = window/global)
    }
};

obj2.direNom(); // undefined

"""
[IDEE] QUAND UTILISER ARROW FUNCTIONS ?

[OK] BON :
- Callbacks
- Fonctions courtes
- Pas besoin de this contextuel
"""

// Callbacks dans méthodes de tableaux
let nombres = [1, 2, 3, 4, 5];
let doubles = nombres.map(n => n * 2);

// setTimeout
setTimeout(() => console.log('Hello'), 1000);

// Event listeners (avec prudence)
button.addEventListener('click', () => console.log('Clicked'));

"""
[X] À ÉVITER :
- Méthodes d'objets (besoin de this)
- Constructeurs
- Fonctions avec arguments
"""


# ----------------------------------------------------------------------------
# [MONDE] SCOPE (PORTÉE)
# ----------------------------------------------------------------------------

"""
SCOPE GLOBAL
"""

let variableGlobale = 'Je suis global';

function test1() {
    console.log(variableGlobale); // [OK] Accessible
}

function test2() {
    console.log(variableGlobale); // [OK] Accessible
}

"""
SCOPE LOCAL (FONCTION)
"""

function test() {
    let variableLocale = 'Je suis local';
    console.log(variableLocale); // [OK] Accessible
}

test();
// console.log(variableLocale); // [X] Erreur : Not defined

"""
[IDEE] RÈGLE DE SCOPE

Code peut accéder :
[OK] Variables du même scope
[OK] Variables des scopes parents
[X] Variables des scopes enfants
"""

let global = 'global';

function externe() {
    let externe = 'externe';
    
    function interne() {
        let interne = 'interne';
        
        console.log(global);  // [OK] Accessible
        console.log(externe); // [OK] Accessible
        console.log(interne); // [OK] Accessible
    }
    
    interne();
    console.log(global);  // [OK] Accessible
    console.log(externe); // [OK] Accessible
    // console.log(interne); // [X] Erreur
}

externe();


"""
SCOPE DE BLOC (let/const)
"""

if (true) {
    let x = 10;
    const y = 20;
    var z = 30;
}

// console.log(x); // [X] Erreur
// console.log(y); // [X] Erreur
console.log(z);    // [OK] 30 (var ignore les blocs)

"""
[CLE] DIFFÉRENCE let/const vs var

let/const -> Block-scoped
var -> Function-scoped (ancien, à éviter)


SHADOWING (MASQUAGE)
"""

let nom = 'Global';

function test() {
    let nom = 'Local'; // Masque la variable globale
    console.log(nom);  // 'Local'
}

test();
console.log(nom); // 'Global'


# ----------------------------------------------------------------------------
# [SECURISE] CLOSURES
# ----------------------------------------------------------------------------

"""
QU'EST-CE QU'UNE CLOSURE ?

Une fonction qui "se souvient" de son environnement lexical
"""

function creerCompteur() {
    let count = 0; // Variable privée
    
    return function() {
        count++;
        return count;
    };
}

let compteur = creerCompteur();

console.log(compteur()); // 1
console.log(compteur()); // 2
console.log(compteur()); // 3

"""
[IDEE] COMMENT ÇA MARCHE ?

1. creerCompteur() crée une variable count
2. Retourne une fonction interne
3. La fonction interne "ferme" sur count (closure)
4. count reste accessible même après la fin de creerCompteur()
5. count est privé (inaccessible de l'extérieur)
"""

// console.log(count); // [X] Erreur : count n'existe pas ici

"""
EXEMPLES PRATIQUES
"""

// Fonction de salutation personnalisée
function creerSalutation(salut) {
    return function(nom) {
        return `${salut} ${nom} !`;
    };
}

let direBonjour = creerSalutation('Bonjour');
let direHello = creerSalutation('Hello');

console.log(direBonjour('Alice')); // Bonjour Alice !
console.log(direHello('Bob'));     // Hello Bob !

// Données privées
function creerCompte(soldeInitial) {
    let solde = soldeInitial; // Privé
    
    return {
        deposer: function(montant) {
            solde += montant;
            return solde;
        },
        retirer: function(montant) {
            if (montant <= solde) {
                solde -= montant;
                return solde;
            }
            return 'Solde insuffisant';
        },
        consulter: function() {
            return solde;
        }
    };
}

let compte = creerCompte(100);
console.log(compte.consulter()); // 100
console.log(compte.deposer(50)); // 150
console.log(compte.retirer(30)); // 120
// console.log(compte.solde); // [X] undefined (privé)


# ----------------------------------------------------------------------------
# [TEL] CALLBACKS
# ----------------------------------------------------------------------------

"""
FONCTION PASSÉE EN ARGUMENT
"""

function direBonjour(nom, callback) {
    console.log(`Bonjour ${nom} !`);
    callback();
}

function apresBonjour() {
    console.log('Ravi de vous rencontrer !');
}

direBonjour('Alice', apresBonjour);
// Bonjour Alice !
// Ravi de vous rencontrer !

"""
[IDEE] CALLBACK = Fonction appelée après une autre
"""

// Avec fonction anonyme
direBonjour('Bob', function() {
    console.log('Comment allez-vous ?');
});

// Avec arrow function
direBonjour('Charlie', () => {
    console.log('Belle journée !');
});


"""
EXEMPLES PRATIQUES
"""

// Traitement de tableau
function traiterTableau(tableau, callback) {
    for (let element of tableau) {
        callback(element);
    }
}

let nombres = [1, 2, 3, 4, 5];
traiterTableau(nombres, n => console.log(n * 2));
// 2, 4, 6, 8, 10

// Opération asynchrone (simulation)
function chargerDonnees(callback) {
    console.log('Chargement en cours...');
    setTimeout(() => {
        let data = { nom: 'Alice', age: 25 };
        callback(data);
    }, 2000);
}

chargerDonnees(donnees => {
    console.log('Données chargées :', donnees);
});


"""
[COURS] EXERCICE PRATIQUE 4 : FONCTIONS

EXERCICE 1 : Calculatrice avec fonctions
"""

// Solution
function calculer(a, b, operation) {
    switch(operation) {
        case '+': return a + b;
        case '-': return a - b;
        case '*': return a * b;
        case '/': return b !== 0 ? a / b : 'Division par zéro';
        default: return 'Opération invalide';
    }
}

console.log(calculer(10, 5, '+')); // 15
console.log(calculer(10, 5, '-')); // 5
console.log(calculer(10, 5, '*')); // 50
console.log(calculer(10, 5, '/')); // 2

"""
EXERCICE 2 : Palindrome checker
"""

// Solution
function estPalindrome(str) {
    let nettoye = str.toLowerCase().replace(/[^a-z0-9]/g, '');
    let inverse = nettoye.split('').reverse().join('');
    return nettoye === inverse;
}

console.log(estPalindrome('kayak')); // true
console.log(estPalindrome('A man a plan a canal Panama')); // true
console.log(estPalindrome('hello')); // false


# ============================================================================
# [DOCS] RÉCAPITULATIF PARTIE 1 COMPLÈTE
# ============================================================================

"""
[BRAVO] FÉLICITATIONS ! PARTIE 1 TERMINÉE !

VOUS MAÎTRISEZ MAINTENANT :

Chapitre 0 : Introduction
[OK] JavaScript et son rôle
[OK] Histoire et évolution
[OK] Où s'exécute JavaScript
[OK] Premiers pas

Chapitre 1 : Variables et Types
[OK] var, let, const
[OK] Types primitifs (Number, String, Boolean, etc.)
[OK] typeof et conversion de types
[OK] null vs undefined

Chapitre 2 : Opérateurs
[OK] Arithmétiques (+, -, *, /, %, **)
[OK] Comparaison (===, !==, <, >, <=, >=)
[OK] Logiques (&&, ||, !)
[OK] Assignation (=, +=, -=, etc.)
[OK] Ternaire (? :)
[OK] Optional chaining (?.) et Nullish (??)

Chapitre 3 : Structures de Contrôle
[OK] if/else if/else
[OK] switch
[OK] Boucles (for, while, do...while)
[OK] for...of et for...in
[OK] break et continue

Chapitre 4 : Fonctions
[OK] Déclaration et expressions
[OK] Arrow functions
[OK] Paramètres et return
[OK] Scope et closures
[OK] Callbacks


[OBJECTIF] VOUS POUVEZ MAINTENANT :

[OK] Écrire des programmes JavaScript de base
[OK] Manipuler des variables et types de données
[OK] Utiliser les structures de contrôle
[OK] Créer et utiliser des fonctions
[OK] Comprendre le scope et les closures


-> PROCHAINE ÉTAPE : PARTIE 2

La Partie 2 couvrira :
- Tableaux (Arrays)
- Objets
- Chaînes de caractères (Strings)
- Destructuring et Spread/Rest

Vous allez apprendre à :
- Manipuler des structures de données complexes
- Travailler avec des objets JavaScript
- Utiliser les méthodes avancées de tableaux
- Maîtriser la syntaxe moderne ES6+


[GUIDE] FICHIER SUIVANT : javascript_partie2.txt

Excellente progression ! Continuez ! [RAPIDE]
"""
-e 

# ============================================================================
# PARTIE 2 : STRUCTURES DE DONNÉES
# ============================================================================

# ============================================================================
# [LIVRE] JAVASCRIPT - PARTIE 2 : STRUCTURES DE DONNÉES ET OBJETS
# ============================================================================
#
# [OBJECTIF] CETTE PARTIE COUVRE :
# - Chapitre 5 : Tableaux (Arrays)
# - Chapitre 6 : Objets
# - Chapitre 7 : Chaînes de Caractères (Strings)
# - Chapitre 8 : Destructuring et Spread/Rest
#
# [TEMPS] TEMPS : ~8-10 heures
# [DOCS] PRÉREQUIS : Partie 1 complétée
# ============================================================================


# ============================================================================
# [GUIDE] CHAPITRE 5 : TABLEAUX (ARRAYS)
# ============================================================================

"""
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Créer et manipuler des tableaux
[OK] Accéder et modifier des éléments
[OK] Utiliser les méthodes essentielles (push, pop, shift, unshift)
[OK] Parcourir avec forEach, map, filter, reduce
[OK] Chercher avec find, findIndex, indexOf
[OK] Trier, inverser, découper
[OK] Maîtriser les méthodes modernes ES6+
"""


# ----------------------------------------------------------------------------
# [PACKAGE] CRÉATION ET ACCÈS
# ----------------------------------------------------------------------------

"""
CRÉER UN TABLEAU
"""

// Avec crochets [] (recommandé)
let fruits = ['pomme', 'banane', 'orange'];

// Tableau vide
let vide = [];

// Types mixtes (possible mais à éviter)
let mixte = [1, 'deux', true, null, { nom: 'Alice' }];

// Avec constructeur Array (rare)
let nombres = new Array(5); // Crée [empty × 5]
let items = new Array(1, 2, 3); // [1, 2, 3]

"""
[IDEE] TABLEAUX EN JAVASCRIPT

- Collection ordonnée d'éléments
- Index commence à 0
- Taille dynamique (grandit automatiquement)
- Peut contenir n'importe quel type
"""


"""
ACCÉDER AUX ÉLÉMENTS
"""

let fruits = ['pomme', 'banane', 'orange', 'kiwi'];

// Par index (commence à 0)
console.log(fruits[0]); // 'pomme'
console.log(fruits[1]); // 'banane'
console.log(fruits[2]); // 'orange'

// Dernier élément
console.log(fruits[fruits.length - 1]); // 'kiwi'

// Index négatif avec at() (ES2022)
console.log(fruits.at(-1)); // 'kiwi' (dernier)
console.log(fruits.at(-2)); // 'orange' (avant-dernier)

// Index hors limites
console.log(fruits[10]); // undefined

"""
LONGUEUR DU TABLEAU
"""

let nombres = [10, 20, 30, 40, 50];
console.log(nombres.length); // 5

// Modifier la longueur
nombres.length = 3;
console.log(nombres); // [10, 20, 30] (tronqué)

nombres.length = 5;
console.log(nombres); // [10, 20, 30, empty × 2]


"""
MODIFIER DES ÉLÉMENTS
"""

let fruits = ['pomme', 'banane', 'orange'];

// Modifier par index
fruits[1] = 'fraise';
console.log(fruits); // ['pomme', 'fraise', 'orange']

// Ajouter à la fin (augmente length)
fruits[3] = 'kiwi';
console.log(fruits); // ['pomme', 'fraise', 'orange', 'kiwi']

// Créer des trous (à éviter)
fruits[10] = 'mangue';
console.log(fruits.length); // 11
console.log(fruits); // ['pomme', 'fraise', 'orange', 'kiwi', empty × 6, 'mangue']


# ----------------------------------------------------------------------------
# + MÉTHODES D'AJOUT ET SUPPRESSION
# ----------------------------------------------------------------------------

"""
PUSH - Ajouter à la fin
"""

let nombres = [1, 2, 3];

nombres.push(4);
console.log(nombres); // [1, 2, 3, 4]

nombres.push(5, 6, 7);
console.log(nombres); // [1, 2, 3, 4, 5, 6, 7]

// Retourne la nouvelle longueur
let nouvelleLength = nombres.push(8);
console.log(nouvelleLength); // 8

"""
POP - Retirer du la fin
"""

let fruits = ['pomme', 'banane', 'orange'];

let dernier = fruits.pop();
console.log(dernier); // 'orange'
console.log(fruits);  // ['pomme', 'banane']

fruits.pop();
console.log(fruits); // ['pomme']

fruits.pop();
console.log(fruits); // []

fruits.pop();
console.log(fruits); // [] (pop sur tableau vide retourne undefined)

"""
UNSHIFT - Ajouter au début
"""

let nombres = [3, 4, 5];

nombres.unshift(2);
console.log(nombres); // [2, 3, 4, 5]

nombres.unshift(0, 1);
console.log(nombres); // [0, 1, 2, 3, 4, 5]

"""
SHIFT - Retirer du début
"""

let fruits = ['pomme', 'banane', 'orange'];

let premier = fruits.shift();
console.log(premier); // 'pomme'
console.log(fruits);  // ['banane', 'orange']

"""
[IDEE] PERFORMANCE

push/pop -> RAPIDE (O(1))
unshift/shift -> LENT (O(n)) - décale tous les éléments

Pour grandes manipulations au début, utilisez reverse() ou autre structure
"""


"""
SPLICE - Ajouter/Supprimer n'importe où
"""

let nombres = [1, 2, 3, 4, 5];

// Supprimer 2 éléments à partir de l'index 2
let supprimes = nombres.splice(2, 2);
console.log(supprimes); // [3, 4]
console.log(nombres);   // [1, 2, 5]

// Ajouter des éléments
nombres = [1, 2, 5];
nombres.splice(2, 0, 3, 4); // Index 2, supprimer 0, ajouter 3 et 4
console.log(nombres); // [1, 2, 3, 4, 5]

// Remplacer des éléments
nombres.splice(2, 2, 'a', 'b'); // Index 2, supprimer 2, ajouter 'a' et 'b'
console.log(nombres); // [1, 2, 'a', 'b', 5]

"""
[IDEE] SPLICE SYNTAXE

array.splice(start, deleteCount, item1, item2, ...)

start : Index de départ
deleteCount : Nombre d'éléments à supprimer
items : Éléments à ajouter


# ----------------------------------------------------------------------------
# [SYNC] PARCOURIR LES TABLEAUX
# ----------------------------------------------------------------------------

"""
FOR CLASSIQUE
"""

let fruits = ['pomme', 'banane', 'orange'];

for (let i = 0; i < fruits.length; i++) {
    console.log(i, fruits[i]);
}
// 0 pomme
// 1 banane
// 2 orange

"""
FOR...OF (ES6 - RECOMMANDÉ)
"""

for (let fruit of fruits) {
    console.log(fruit);
}
// pomme
// banane
// orange

// Avec index (entries)
for (let [index, fruit] of fruits.entries()) {
    console.log(index, fruit);
}

"""
FOREACH - Méthode de tableau
"""

fruits.forEach(function(fruit) {
    console.log(fruit);
});

// Avec arrow function (plus courant)
fruits.forEach(fruit => console.log(fruit));

// Avec index et tableau
fruits.forEach((fruit, index, array) => {
    console.log(`${index}: ${fruit} dans ${array}`);
});

"""
[IDEE] FOREACH vs FOR

forEach :
- Plus lisible
- Méthode fonctionnelle
- [X] Ne peut pas utiliser break/continue
- [X] Ne peut pas return de la fonction englobante

for/for...of :
- [OK] Peut utiliser break/continue
- [OK] Peut return


# ----------------------------------------------------------------------------
# [WORLD_MAP] MAP - TRANSFORMER UN TABLEAU
# ----------------------------------------------------------------------------

"""
MAP - Créer un nouveau tableau en transformant chaque élément
"""

let nombres = [1, 2, 3, 4, 5];

// Doubler chaque nombre
let doubles = nombres.map(n => n * 2);
console.log(doubles); // [2, 4, 6, 8, 10]
console.log(nombres); // [1, 2, 3, 4, 5] (original inchangé)

// Avec objets
let users = [
    { nom: 'Alice', age: 25 },
    { nom: 'Bob', age: 30 },
    { nom: 'Charlie', age: 35 }
];

let noms = users.map(user => user.nom);
console.log(noms); // ['Alice', 'Bob', 'Charlie']

// Transformer en HTML
let items = ['pomme', 'banane', 'orange'];
let html = items.map(item => `<li>${item}</li>`);
console.log(html);
// ['<li>pomme</li>', '<li>banane</li>', '<li>orange</li>']

"""
[IDEE] MAP

- Retourne TOUJOURS un tableau de même longueur
- Ne modifie PAS le tableau original
- Chaque élément est transformé
- Valeur retournée = nouvel élément


EXEMPLES PRATIQUES
"""

// Prix avec TVA
let prix = [10, 20, 30, 40];
let prixTTC = prix.map(p => p * 1.2);
console.log(prixTTC); // [12, 24, 36, 48]

// Extraire propriétés
let produits = [
    { nom: 'Laptop', prix: 1000 },
    { nom: 'Mouse', prix: 20 },
    { nom: 'Keyboard', prix: 50 }
];

let nomsProduits = produits.map(p => p.nom);
let prixProduits = produits.map(p => p.prix);

// Créer nouveaux objets
let produitsAvecTVA = produits.map(p => ({
    nom: p.nom,
    prixHT: p.prix,
    prixTTC: p.prix * 1.2
}));


# ----------------------------------------------------------------------------
# [RECHERCHE] FILTER - FILTRER UN TABLEAU
# ----------------------------------------------------------------------------

"""
FILTER - Créer un nouveau tableau avec éléments qui passent un test
"""

let nombres = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// Garder seulement les pairs
let pairs = nombres.filter(n => n % 2 === 0);
console.log(pairs); // [2, 4, 6, 8, 10]

// Garder les impairs
let impairs = nombres.filter(n => n % 2 !== 0);
console.log(impairs); // [1, 3, 5, 7, 9]

"""
[IDEE] FILTER

- Retourne nouveau tableau (peut être plus court)
- Ne modifie PAS le tableau original
- Garde les éléments où callback retourne true
- Élimine ceux où callback retourne false


EXEMPLES PRATIQUES
"""

// Filtrer par âge
let users = [
    { nom: 'Alice', age: 17 },
    { nom: 'Bob', age: 25 },
    { nom: 'Charlie', age: 30 },
    { nom: 'David', age: 15 }
];

let majeurs = users.filter(user => user.age >= 18);
console.log(majeurs);
// [{ nom: 'Bob', age: 25 }, { nom: 'Charlie', age: 30 }]

// Filtrer produits en stock
let produits = [
    { nom: 'Laptop', stock: 5 },
    { nom: 'Mouse', stock: 0 },
    { nom: 'Keyboard', stock: 3 }
];

let disponibles = produits.filter(p => p.stock > 0);

// Recherche par mot-clé
let mots = ['apple', 'banana', 'apricot', 'cherry'];
let recherche = mots.filter(mot => mot.includes('ap'));
console.log(recherche); // ['apple', 'apricot']

// Supprimer doublons (avec filter + indexOf)
let nombres = [1, 2, 2, 3, 4, 4, 5];
let uniques = nombres.filter((n, index, array) => {
    return array.indexOf(n) === index;
});
console.log(uniques); // [1, 2, 3, 4, 5]


"""
COMBINER MAP ET FILTER
"""

let users = [
    { nom: 'Alice', age: 17, actif: true },
    { nom: 'Bob', age: 25, actif: true },
    { nom: 'Charlie', age: 30, actif: false },
    { nom: 'David', age: 22, actif: true }
];

// Noms des majeurs actifs
let nomsActifs = users
    .filter(u => u.age >= 18 && u.actif)
    .map(u => u.nom);

console.log(nomsActifs); // ['Bob', 'David']


# ----------------------------------------------------------------------------
# [OBJECTIF] REDUCE - RÉDUIRE À UNE SEULE VALEUR
# ----------------------------------------------------------------------------

"""
REDUCE - Réduire le tableau à une seule valeur
"""

let nombres = [1, 2, 3, 4, 5];

// Somme
let somme = nombres.reduce((acc, n) => acc + n, 0);
console.log(somme); // 15

"""
[IDEE] REDUCE SYNTAXE

array.reduce((accumulator, currentValue) => {
    return newAccumulator;
}, initialValue)

accumulator : Valeur accumulée
currentValue : Élément actuel
initialValue : Valeur de départ


ÉTAPE PAR ÉTAPE
"""

let nombres = [1, 2, 3, 4];
let somme = nombres.reduce((acc, n) => {
    console.log(`acc: ${acc}, n: ${n}`);
    return acc + n;
}, 0);

/*
acc: 0, n: 1  -> retourne 1
acc: 1, n: 2  -> retourne 3
acc: 3, n: 3  -> retourne 6
acc: 6, n: 4  -> retourne 10
*/
console.log(somme); // 10


"""
EXEMPLES PRATIQUES
"""

// Produit (multiplication)
let nombres = [2, 3, 4];
let produit = nombres.reduce((acc, n) => acc * n, 1);
console.log(produit); // 24

// Maximum
let max = nombres.reduce((acc, n) => Math.max(acc, n));
console.log(max); // 4

// Minimum
let min = nombres.reduce((acc, n) => Math.min(acc, n));
console.log(min); // 2

// Compter occurrences
let fruits = ['pomme', 'banane', 'pomme', 'orange', 'banane', 'pomme'];
let compteur = fruits.reduce((acc, fruit) => {
    acc[fruit] = (acc[fruit] || 0) + 1;
    return acc;
}, {});
console.log(compteur);
// { pomme: 3, banane: 2, orange: 1 }

// Aplatir tableau (flatten)
let nested = [[1, 2], [3, 4], [5, 6]];
let plat = nested.reduce((acc, arr) => acc.concat(arr), []);
console.log(plat); // [1, 2, 3, 4, 5, 6]

// Grouper par propriété
let users = [
    { nom: 'Alice', role: 'admin' },
    { nom: 'Bob', role: 'user' },
    { nom: 'Charlie', role: 'admin' },
    { nom: 'David', role: 'user' }
];

let parRole = users.reduce((acc, user) => {
    if (!acc[user.role]) {
        acc[user.role] = [];
    }
    acc[user.role].push(user.nom);
    return acc;
}, {});

console.log(parRole);
// { admin: ['Alice', 'Charlie'], user: ['Bob', 'David'] }


"""
CHAÎNAGE DE MÉTHODES
"""

let nombres = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// Somme des carrés des pairs
let resultat = nombres
    .filter(n => n % 2 === 0)  // [2, 4, 6, 8, 10]
    .map(n => n * n)            // [4, 16, 36, 64, 100]
    .reduce((acc, n) => acc + n, 0); // 220

console.log(resultat); // 220


# ----------------------------------------------------------------------------
# [RECHERCHE] FIND ET RECHERCHE
# ----------------------------------------------------------------------------

"""
FIND - Trouver le premier élément qui correspond
"""

let users = [
    { id: 1, nom: 'Alice' },
    { id: 2, nom: 'Bob' },
    { id: 3, nom: 'Charlie' }
];

let user = users.find(u => u.id === 2);
console.log(user); // { id: 2, nom: 'Bob' }

let absent = users.find(u => u.id === 10);
console.log(absent); // undefined

"""
FINDINDEX - Trouver l'index du premier élément
"""

let index = users.findIndex(u => u.nom === 'Charlie');
console.log(index); // 2

let notFound = users.findIndex(u => u.nom === 'David');
console.log(notFound); // -1

"""
INDEXOF - Trouver l'index d'une valeur
"""

let fruits = ['pomme', 'banane', 'orange', 'banane'];

console.log(fruits.indexOf('banane')); // 1 (première occurrence)
console.log(fruits.indexOf('kiwi'));   // -1 (pas trouvé)

// Avec position de départ
console.log(fruits.indexOf('banane', 2)); // 3 (cherche à partir de l'index 2)

"""
LASTINDEXOF - Dernière occurrence
"""

console.log(fruits.lastIndexOf('banane')); // 3

"""
INCLUDES - Vérifier si contient (ES2016)
"""

console.log(fruits.includes('orange')); // true
console.log(fruits.includes('kiwi'));   // false

"""
SOME - Au moins un élément correspond
"""

let nombres = [1, 2, 3, 4, 5];

let aPair = nombres.some(n => n % 2 === 0);
console.log(aPair); // true

let aNegatif = nombres.some(n => n < 0);
console.log(aNegatif); // false

"""
EVERY - Tous les éléments correspondent
"""

let tousPositifs = nombres.every(n => n > 0);
console.log(tousPositifs); // true

let tousPairs = nombres.every(n => n % 2 === 0);
console.log(tousPairs); // false


# ----------------------------------------------------------------------------
# [OUTIL] TRI ET MANIPULATION
# ----------------------------------------------------------------------------

"""
SORT - Trier ([ATTENTION] MODIFIE L'ORIGINAL)
"""

let nombres = [3, 1, 4, 1, 5, 9, 2, 6];
nombres.sort();
console.log(nombres); // [1, 1, 2, 3, 4, 5, 6, 9]

// [ATTENTION] Sort numérique (piège)
let nums = [1, 2, 10, 21];
nums.sort();
console.log(nums); // [1, 10, 2, 21] [X] (tri alphabétique !)

// [OK] Sort numérique correct
nums.sort((a, b) => a - b);
console.log(nums); // [1, 2, 10, 21] [OK]

// Ordre décroissant
nums.sort((a, b) => b - a);
console.log(nums); // [21, 10, 2, 1]

"""
[IDEE] COMMENT FONCTIONNE SORT ?

Fonction de comparaison :
- Si retourne < 0 : a avant b
- Si retourne > 0 : b avant a  
- Si retourne 0 : ordre inchangé

(a, b) => a - b : Ordre croissant
(a, b) => b - a : Ordre décroissant


TRIER DES OBJETS
"""

let users = [
    { nom: 'Charlie', age: 30 },
    { nom: 'Alice', age: 25 },
    { nom: 'Bob', age: 35 }
];

// Par nom
users.sort((a, b) => {
    if (a.nom < b.nom) return -1;
    if (a.nom > b.nom) return 1;
    return 0;
});

// Par âge
users.sort((a, b) => a.age - b.age);

// Avec localeCompare (pour strings)
users.sort((a, b) => a.nom.localeCompare(b.nom));

"""
TOORTED - Sans modifier l'original (ES2023)
"""

let nombres = [3, 1, 4, 1, 5];
let tries = nombres.toSorted((a, b) => a - b);
console.log(nombres); // [3, 1, 4, 1, 5] (inchangé)
console.log(tries);   // [1, 1, 3, 4, 5]

"""
REVERSE - Inverser ([ATTENTION] MODIFIE L'ORIGINAL)
"""

let nombres = [1, 2, 3, 4, 5];
nombres.reverse();
console.log(nombres); // [5, 4, 3, 2, 1]

// toReversed - Sans modifier (ES2023)
let original = [1, 2, 3];
let inverse = original.toReversed();
console.log(original); // [1, 2, 3]
console.log(inverse);  // [3, 2, 1]


"""
SLICE - Copier une portion
"""

let fruits = ['pomme', 'banane', 'orange', 'kiwi', 'mangue'];

// Du début à la fin
let copie = fruits.slice();
console.log(copie); // ['pomme', 'banane', 'orange', 'kiwi', 'mangue']

// De l'index 1 à 3 (exclu)
let portion = fruits.slice(1, 3);
console.log(portion); // ['banane', 'orange']

// Depuis l'index 2
let depuis = fruits.slice(2);
console.log(depuis); // ['orange', 'kiwi', 'mangue']

// Index négatifs (depuis la fin)
let derniers = fruits.slice(-2);
console.log(derniers); // ['kiwi', 'mangue']

"""
[IDEE] SLICE vs SPLICE

SLICE : Ne modifie PAS l'original (copie)
SPLICE : MODIFIE l'original (ajoute/supprime)


CONCAT - Concaténer des tableaux
"""

let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let arr3 = [7, 8, 9];

let combine = arr1.concat(arr2, arr3);
console.log(combine); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(arr1);    // [1, 2, 3] (inchangé)

// Avec spread operator (moderne)
let combine2 = [...arr1, ...arr2, ...arr3];


"""
JOIN - Convertir en string
"""

let fruits = ['pomme', 'banane', 'orange'];

console.log(fruits.join());     // 'pomme,banane,orange'
console.log(fruits.join(' '));  // 'pomme banane orange'
console.log(fruits.join(' - ')); // 'pomme - banane - orange'

// Pour affichage HTML
let html = fruits.join('</li><li>');
html = '<li>' + html + '</li>';
console.log(html);


"""
FLAT - Aplatir tableaux imbriqués (ES2019)
"""

let nested = [1, [2, 3], [4, [5, 6]]];

// Aplatir 1 niveau
console.log(nested.flat()); // [1, 2, 3, 4, [5, 6]]

// Aplatir 2 niveaux
console.log(nested.flat(2)); // [1, 2, 3, 4, 5, 6]

// Aplatir complètement
console.log(nested.flat(Infinity)); // [1, 2, 3, 4, 5, 6]

"""
FLATMAP - Map puis flat (ES2019)
"""

let phrases = ['hello world', 'foo bar'];

// map + flat
let mots1 = phrases.map(p => p.split(' ')).flat();
console.log(mots1); // ['hello', 'world', 'foo', 'bar']

// flatMap (plus efficace)
let mots2 = phrases.flatMap(p => p.split(' '));
console.log(mots2); // ['hello', 'world', 'foo', 'bar']


# Ce fichier continue dans javascript_partie2_suite.txt
# ============================================================================
# ============================================================================
# [LIVRE] JAVASCRIPT - PARTIE 2 (SUITE) : OBJETS, STRINGS, DESTRUCTURING
# ============================================================================
#
# [OBJECTIF] CETTE PARTIE COUVRE :
# - Chapitre 6 : Objets
# - Chapitre 7 : Chaînes de Caractères (Strings)
# - Chapitre 8 : Destructuring et Spread/Rest
#
# [TEMPS] TEMPS : ~4-6 heures
# [DOCS] PRÉREQUIS : Chapitre 5 complété
# ============================================================================


# ============================================================================
# [GUIDE] CHAPITRE 6 : OBJETS
# ============================================================================

"""
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Créer et manipuler des objets
[OK] Accéder et modifier des propriétés
[OK] Utiliser les méthodes d'objets
[OK] Comprendre le mot-clé this
[OK] Utiliser Object.keys(), values(), entries()
[OK] Cloner et fusionner des objets
[OK] Comprendre les prototypes
"""


# ----------------------------------------------------------------------------
# [CONSTRUCTION] CRÉATION D'OBJETS
# ----------------------------------------------------------------------------

"""
OBJET LITTÉRAL (RECOMMANDÉ)
"""

// Objet vide
let personne = {};

// Objet avec propriétés
let user = {
    nom: 'Alice',
    age: 25,
    ville: 'Paris',
    estActif: true
};

console.log(user);
// { nom: 'Alice', age: 25, ville: 'Paris', estActif: true }

"""
[IDEE] OBJET EN JAVASCRIPT

- Collection de paires clé-valeur
- Clé (propriété) : string ou Symbol
- Valeur : n'importe quel type
- Équivalent à dictionnaire/map dans d'autres langages


PROPRIÉTÉS ET VALEURS
"""

let produit = {
    nom: 'Laptop',           // string
    prix: 999.99,            // number
    disponible: true,        // boolean
    couleurs: ['noir', 'gris'], // array
    specs: {                 // objet imbriqué
        ram: '16GB',
        ssd: '512GB'
    },
    afficher: function() {   // méthode
        console.log(this.nom);
    }
};

"""
SYNTAXE RACCOURCIE (ES6)
"""

let nom = 'Bob';
let age = 30;

// [X] Ancien (répétitif)
let user1 = {
    nom: nom,
    age: age
};

// [OK] Nouveau (ES6 shorthand)
let user2 = { nom, age };

console.log(user2); // { nom: 'Bob', age: 30 }


"""
CRÉER AVEC CONSTRUCTEUR Object (RARE)
"""

let obj = new Object();
obj.nom = 'Alice';
obj.age = 25;

// Équivalent à :
let obj2 = {
    nom: 'Alice',
    age: 25
};


# ----------------------------------------------------------------------------
# [CLE] ACCÉDER AUX PROPRIÉTÉS
# ----------------------------------------------------------------------------

"""
NOTATION POINT (PRÉFÉRÉE)
"""

let user = {
    nom: 'Alice',
    age: 25,
    ville: 'Paris'
};

console.log(user.nom);   // 'Alice'
console.log(user.age);   // 25
console.log(user.ville); // 'Paris'

"""
NOTATION CROCHET
"""

console.log(user['nom']);   // 'Alice'
console.log(user['age']);   // 25
console.log(user['ville']); // 'Paris'

"""
[IDEE] QUAND UTILISER LES CROCHETS ?

[OK] Clé contient espaces ou caractères spéciaux
[OK] Clé est dans une variable
[OK] Clé est dynamique
"""

// Clé avec espaces
let obj = {
    'prenom complet': 'Alice Dupont',
    'date de naissance': '1998-05-15'
};

// console.log(obj.prenom complet); // [X] Erreur de syntaxe
console.log(obj['prenom complet']); // [OK] 'Alice Dupont'

// Clé dynamique
let propriete = 'nom';
console.log(user[propriete]); // 'Alice'

// Parcourir propriétés
for (let key in user) {
    console.log(key + ': ' + user[key]);
}

"""
PROPRIÉTÉ INEXISTANTE
"""

let user = { nom: 'Alice', age: 25 };

console.log(user.ville); // undefined (pas d'erreur)
console.log(user.email); // undefined

// Vérifier existence
if (user.ville !== undefined) {
    console.log(user.ville);
}

// Meilleure méthode
if ('ville' in user) {
    console.log(user.ville);
}

// Avec optional chaining (ES2020)
console.log(user?.adresse?.rue); // undefined (pas d'erreur)


# ----------------------------------------------------------------------------
# [EDIT] MODIFIER DES PROPRIÉTÉS
# ----------------------------------------------------------------------------

"""
AJOUTER/MODIFIER
"""

let user = {
    nom: 'Alice',
    age: 25
};

// Modifier existante
user.age = 26;
console.log(user.age); // 26

// Ajouter nouvelle
user.ville = 'Paris';
console.log(user.ville); // 'Paris'

// Avec crochets
user['email'] = 'alice@example.com';
console.log(user);
// { nom: 'Alice', age: 26, ville: 'Paris', email: 'alice@example.com' }

"""
SUPPRIMER UNE PROPRIÉTÉ
"""

let user = {
    nom: 'Alice',
    age: 25,
    ville: 'Paris'
};

delete user.ville;
console.log(user); // { nom: 'Alice', age: 25 }

// Vérifier après suppression
console.log(user.ville); // undefined
console.log('ville' in user); // false


"""
PROPRIÉTÉS CALCULÉES (ES6)
"""

let propriete = 'couleur';
let valeur = 'bleu';

let obj = {
    [propriete]: valeur,
    ['age' + 'Max']: 100,
    [`${propriete}Préférée`]: 'rouge'
};

console.log(obj);
// { couleur: 'bleu', ageMax: 100, couleurPréférée: 'rouge' }


# ----------------------------------------------------------------------------
# [SCENARIO] MÉTHODES D'OBJETS
# ----------------------------------------------------------------------------

"""
DÉFINIR DES MÉTHODES
"""

let user = {
    nom: 'Alice',
    age: 25,
    
    // Méthode classique
    saluer: function() {
        console.log('Bonjour, je suis ' + this.nom);
    },
    
    // Syntaxe raccourcie ES6 (recommandée)
    direAge() {
        console.log('J\'ai ' + this.age + ' ans');
    },
    
    // Arrow function ([ATTENTION] pas de this propre)
    afficher: () => {
        console.log(this); // window ou undefined (pas l'objet)
    }
};

user.saluer();  // 'Bonjour, je suis Alice'
user.direAge(); // 'J'ai 25 ans'

"""
[IDEE] THIS DANS LES MÉTHODES

this = Référence à l'objet qui appelle la méthode
"""

let calculatrice = {
    a: 5,
    b: 10,
    
    additionner() {
        return this.a + this.b; // this = calculatrice
    },
    
    multiplier() {
        return this.a * this.b;
    }
};

console.log(calculatrice.additionner()); // 15
console.log(calculatrice.multiplier());  // 50

"""
[ATTENTION] PERTE DE CONTEXTE THIS
"""

let user = {
    nom: 'Alice',
    saluer() {
        console.log('Bonjour ' + this.nom);
    }
};

user.saluer(); // [OK] 'Bonjour Alice'

let fn = user.saluer;
fn(); // [X] 'Bonjour undefined' (this perdu)

// Solutions :

// 1. bind()
let fn2 = user.saluer.bind(user);
fn2(); // [OK] 'Bonjour Alice'

// 2. Arrow function wrapper
let fn3 = () => user.saluer();
fn3(); // [OK] 'Bonjour Alice'

// 3. call() ou apply()
user.saluer.call(user); // [OK] 'Bonjour Alice'


"""
GETTERS ET SETTERS
"""

let user = {
    prenom: 'Alice',
    nom: 'Dupont',
    
    // Getter
    get nomComplet() {
        return `${this.prenom} ${this.nom}`;
    },
    
    // Setter
    set nomComplet(value) {
        [this.prenom, this.nom] = value.split(' ');
    }
};

// Utiliser comme propriété (pas d'appel de fonction)
console.log(user.nomComplet); // 'Alice Dupont'

user.nomComplet = 'Bob Martin';
console.log(user.prenom); // 'Bob'
console.log(user.nom);    // 'Martin'

"""
[IDEE] AVANTAGES GETTERS/SETTERS

- Syntaxe simple (comme une propriété)
- Logique supplémentaire (validation, calcul)
- Encapsulation
"""

let compte = {
    _solde: 1000, // _ = convention pour "privé"
    
    get solde() {
        return this._solde;
    },
    
    set solde(montant) {
        if (montant < 0) {
            console.error('Solde ne peut pas être négatif');
            return;
        }
        this._solde = montant;
    }
};

console.log(compte.solde); // 1000
compte.solde = 1500;       // OK
compte.solde = -500;       // Erreur affichée


# ----------------------------------------------------------------------------
# [OUTIL] MÉTHODES OBJECT STATIQUES
# ----------------------------------------------------------------------------

"""
OBJECT.KEYS() - Obtenir les clés
"""

let user = {
    nom: 'Alice',
    age: 25,
    ville: 'Paris'
};

let keys = Object.keys(user);
console.log(keys); // ['nom', 'age', 'ville']

// Parcourir
Object.keys(user).forEach(key => {
    console.log(`${key}: ${user[key]}`);
});

"""
OBJECT.VALUES() - Obtenir les valeurs
"""

let values = Object.values(user);
console.log(values); // ['Alice', 25, 'Paris']

// Somme des valeurs numériques
let produit = { prix: 100, quantite: 5, remise: 10 };
let total = Object.values(produit).reduce((acc, val) => acc + val, 0);
console.log(total); // 115

"""
OBJECT.ENTRIES() - Obtenir paires [clé, valeur]
"""

let entries = Object.entries(user);
console.log(entries);
// [['nom', 'Alice'], ['age', 25], ['ville', 'Paris']]

// Parcourir avec destructuring
for (let [key, value] of Object.entries(user)) {
    console.log(`${key}: ${value}`);
}

// Filtrer et recréer objet
let filteredEntries = Object.entries(produit)
    .filter(([key, value]) => value > 10);
let filteredObj = Object.fromEntries(filteredEntries);

"""
OBJECT.FROMENTRIES() - Créer objet depuis entries (ES2019)
"""

let entries = [
    ['nom', 'Alice'],
    ['age', 25],
    ['ville', 'Paris']
];

let user = Object.fromEntries(entries);
console.log(user);
// { nom: 'Alice', age: 25, ville: 'Paris' }

// Convertir Map en objet
let map = new Map([
    ['nom', 'Bob'],
    ['age', 30]
]);
let obj = Object.fromEntries(map);

"""
OBJECT.ASSIGN() - Copier/Fusionner
"""

let user = { nom: 'Alice', age: 25 };
let details = { ville: 'Paris', pays: 'France' };

// Fusionner dans un nouveau objet
let combined = Object.assign({}, user, details);
console.log(combined);
// { nom: 'Alice', age: 25, ville: 'Paris', pays: 'France' }

// Copier un objet (shallow copy)
let copie = Object.assign({}, user);

// Avec spread operator (moderne, recommandé)
let combined2 = { ...user, ...details };
let copie2 = { ...user };

"""
[IDEE] SHALLOW vs DEEP COPY

SHALLOW COPY : Copie les valeurs directes
Objets imbriqués sont RÉFÉRENCÉS (pas copiés)
"""

let original = {
    nom: 'Alice',
    adresse: { ville: 'Paris' }
};

let copie = { ...original };

copie.nom = 'Bob'; // [OK] original.nom reste 'Alice'
copie.adresse.ville = 'Lyon'; // [X] original.adresse.ville devient 'Lyon' aussi

console.log(original.adresse.ville); // 'Lyon' (référence partagée)

"""
DEEP COPY : Vraie copie récursive
"""

// Solution 1 : JSON (simple mais limitations)
let deepCopy1 = JSON.parse(JSON.stringify(original));

// Solution 2 : structuredClone() (moderne, ES2022)
let deepCopy2 = structuredClone(original);

// Solution 3 : Bibliothèque (lodash)
// const deepCopy3 = _.cloneDeep(original);


"""
OBJECT.FREEZE() - Rendre immuable
"""

let user = {
    nom: 'Alice',
    age: 25
};

Object.freeze(user);

user.age = 30;        // [X] Ignoré (strict mode : erreur)
user.ville = 'Paris'; // [X] Ignoré
delete user.nom;      // [X] Ignoré

console.log(user); // { nom: 'Alice', age: 25 } (inchangé)

"""
OBJECT.SEAL() - Empêcher ajout/suppression
"""

let user = {
    nom: 'Alice',
    age: 25
};

Object.seal(user);

user.age = 30;        // [OK] OK (modification autorisée)
user.ville = 'Paris'; // [X] Ignoré (pas d'ajout)
delete user.nom;      // [X] Ignoré (pas de suppression)

console.log(user); // { nom: 'Alice', age: 30 }


# ----------------------------------------------------------------------------
# [COURS] EXERCICES PRATIQUES : OBJETS
# ----------------------------------------------------------------------------

"""
EXERCICE 1 : Carnet d'adresses

Créez un objet représentant un contact avec :
- nom, prénom, email, téléphone
- Méthode afficherContact()
- Méthode envoyerEmail()
"""

// Solution
let contact = {
    nom: 'Dupont',
    prenom: 'Alice',
    email: 'alice.dupont@example.com',
    telephone: '0612345678',
    
    afficherContact() {
        console.log(`${this.prenom} ${this.nom}`);
        console.log(`Email: ${this.email}`);
        console.log(`Tél: ${this.telephone}`);
    },
    
    envoyerEmail(sujet, message) {
        console.log(`Envoi email à ${this.email}`);
        console.log(`Sujet: ${sujet}`);
        console.log(`Message: ${message}`);
    }
};

contact.afficherContact();
contact.envoyerEmail('Bonjour', 'Comment allez-vous ?');

"""
EXERCICE 2 : Panier d'achats

Créez un objet panier avec :
- Tableau d'articles
- Méthode ajouterArticle()
- Méthode supprimerArticle()
- Méthode calculerTotal()
"""

// Solution
let panier = {
    articles: [],
    
    ajouterArticle(nom, prix, quantite = 1) {
        this.articles.push({ nom, prix, quantite });
    },
    
    supprimerArticle(nom) {
        this.articles = this.articles.filter(a => a.nom !== nom);
    },
    
    calculerTotal() {
        return this.articles.reduce((total, article) => {
            return total + (article.prix * article.quantite);
        }, 0);
    },
    
    afficher() {
        console.log('Articles dans le panier :');
        this.articles.forEach(a => {
            console.log(`${a.nom} - ${a.prix}€ x ${a.quantite}`);
        });
        console.log(`Total: ${this.calculerTotal()}€`);
    }
};

panier.ajouterArticle('Laptop', 1000, 1);
panier.ajouterArticle('Souris', 20, 2);
panier.ajouterArticle('Clavier', 50, 1);
panier.afficher();
panier.supprimerArticle('Souris');
panier.afficher();


# ============================================================================
# [GUIDE] CHAPITRE 7 : CHAÎNES DE CARACTÈRES (STRINGS)
# ============================================================================

"""
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Créer et manipuler des strings
[OK] Utiliser les méthodes essentielles
[OK] Maîtriser les template literals
[OK] Chercher et remplacer du texte
[OK] Diviser et joindre des strings
[OK] Utiliser les expressions régulières basiques
"""


# ----------------------------------------------------------------------------
# [NOTE] CRÉATION ET MANIPULATION
# ----------------------------------------------------------------------------

"""
CRÉER DES STRINGS
"""

// Guillemets simples
let str1 = 'Hello';

// Guillemets doubles
let str2 = "World";

// Template literals (backticks) - ES6
let str3 = `Hello World`;

// String vide
let vide = '';

// Multiligne avec template literals
let multiligne = `Ligne 1
Ligne 2
Ligne 3`;

"""
[IDEE] GUILLEMETS : QUELLE DIFFÉRENCE ?

'...' et "..." : Identiques en JavaScript
`...` : Template literals (interpolation + multiligne)


ÉCHAPPEMENT
"""

// Guillemets dans une string
let str1 = 'Il a dit: \'Bonjour\'';
let str2 = "Il a dit: \"Bonjour\"";
let str3 = `Il a dit: "Bonjour"`;

// Autres caractères spéciaux
let newline = 'Ligne 1\nLigne 2';      // Nouvelle ligne
let tab = 'Colonne1\tColonne2';        // Tabulation
let backslash = 'C:\\Users\\Alice';    // Backslash
let unicode = '\u00A9';                // © (unicode)

"""
CONCATÉNATION
"""

let prenom = 'Alice';
let nom = 'Dupont';

// Avec +
let nomComplet1 = prenom + ' ' + nom;

// Avec +=
let salutation = 'Bonjour ';
salutation += prenom;
salutation += ' ';
salutation += nom;

// Avec template literals (recommandé)
let nomComplet2 = `${prenom} ${nom}`;
let message = `Bonjour ${prenom} ${nom}!`;

"""
INTERPOLATION (TEMPLATE LITERALS)
"""

let age = 25;
let nom = 'Alice';

// Expressions JavaScript
let msg1 = `${nom} a ${age} ans`;
let msg2 = `Dans 5 ans, ${nom} aura ${age + 5} ans`;
let msg3 = `${nom} est ${age >= 18 ? 'majeur' : 'mineur'}`;

// Appel de fonctions
function majuscules(str) {
    return str.toUpperCase();
}

let msg4 = `${majuscules(nom)} a ${age} ans`;


# ----------------------------------------------------------------------------
# [RECHERCHE] PROPRIÉTÉS ET ACCÈS
# ----------------------------------------------------------------------------

"""
LENGTH - Longueur de la string
"""

let texte = 'Hello World';
console.log(texte.length); // 11

// Vérifier si vide
if (texte.length === 0) {
    console.log('String vide');
}

"""
ACCÉDER AUX CARACTÈRES
"""

let str = 'Hello';

// Avec crochets (recommandé)
console.log(str[0]); // 'H'
console.log(str[1]); // 'e'
console.log(str[4]); // 'o'

// Avec charAt()
console.log(str.charAt(0)); // 'H'
console.log(str.charAt(4)); // 'o'

// Dernier caractère
console.log(str[str.length - 1]); // 'o'

// Hors limites
console.log(str[10]); // undefined
console.log(str.charAt(10)); // '' (chaîne vide)

"""
[ATTENTION] STRINGS SONT IMMUABLES

On ne peut PAS modifier un caractère directement
"""

let str = 'Hello';
str[0] = 'h'; // [X] Ignoré (pas d'erreur mais pas de changement)
console.log(str); // 'Hello' (inchangé)

// Pour "modifier" : créer une nouvelle string
let nouveau = 'h' + str.slice(1);
console.log(nouveau); // 'hello'


# ----------------------------------------------------------------------------
# [OUTIL] MÉTHODES DE TRANSFORMATION
# ----------------------------------------------------------------------------

"""
TOUPPPERCASE / TOLOWERCASE
"""

let texte = 'Hello World';

console.log(texte.toUpperCase()); // 'HELLO WORLD'
console.log(texte.toLowerCase()); // 'hello world'

// Original inchangé
console.log(texte); // 'Hello World'

// Capitaliser première lettre
function capitaliser(str) {
    return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
}

console.log(capitaliser('hELLO')); // 'Hello'

"""
TRIM / TRIMSTART / TRIMEND
"""

let str = '   Hello World   ';

console.log(str.trim());      // 'Hello World'
console.log(str.trimStart()); // 'Hello World   '
console.log(str.trimEnd());   // '   Hello World'

// Utile pour input utilisateur
let input = prompt('Votre nom:');
input = input.trim(); // Enlever espaces inutiles

"""
REPEAT - Répéter (ES6)
"""

console.log('Ha'.repeat(3));     // 'HaHaHa'
console.log('='.repeat(20));     // '===================='
console.log('* '.repeat(5));     // '* * * * * '

// Créer indentation
function indent(niveau) {
    return '  '.repeat(niveau);
}

console.log(indent(2) + 'Texte indenté');

"""
PADSTART / PADEND - Remplir (ES2017)
"""

let num = '5';
console.log(num.padStart(3, '0')); // '005'
console.log(num.padEnd(3, '0'));   // '500'

// Formatter des nombres
let numero = '42';
console.log(numero.padStart(5, '0')); // '00042'

// Créer une table
let items = ['pomme', 'banane', 'kiwi'];
items.forEach((item, i) => {
    let index = String(i + 1).padStart(2, '0');
    let nom = item.padEnd(10);
    console.log(`${index}. ${nom} - En stock`);
});
// 01. pomme      - En stock
// 02. banane     - En stock
// 03. kiwi       - En stock


# ----------------------------------------------------------------------------
# [RECHERCHE] MÉTHODES DE RECHERCHE
# ----------------------------------------------------------------------------

"""
INDEXOF - Trouver première occurrence
"""

let texte = 'Hello World Hello';

console.log(texte.indexOf('Hello'));  // 0
console.log(texte.indexOf('World'));  // 6
console.log(texte.indexOf('hello'));  // -1 (sensible à la casse)
console.log(texte.indexOf('xyz'));    // -1 (pas trouvé)

// Chercher à partir d'une position
console.log(texte.indexOf('Hello', 1)); // 12 (deuxième occurrence)

"""
LASTINDEXOF - Trouver dernière occurrence
"""

console.log(texte.lastIndexOf('Hello')); // 12

"""
INCLUDES - Vérifier si contient (ES6)
"""

let texte = 'Hello World';

console.log(texte.includes('World')); // true
console.log(texte.includes('world')); // false (sensible à la casse)
console.log(texte.includes('xyz'));   // false

// Avec position de départ
console.log(texte.includes('Hello', 5)); // false (cherche après l'index 5)

"""
STARTSWITH / ENDSWITH - Vérifier début/fin (ES6)
"""

let url = 'https://example.com';

console.log(url.startsWith('https')); // true
console.log(url.startsWith('http'));  // true
console.log(url.startsWith('ftp'));   // false

console.log(url.endsWith('.com')); // true
console.log(url.endsWith('.org')); // false

// Avec position
console.log(url.startsWith('example', 8)); // true

// Cas pratiques
function estSecurise(url) {
    return url.startsWith('https://');
}

function estImage(filename) {
    return filename.endsWith('.jpg') || 
           filename.endsWith('.png') ||
           filename.endsWith('.gif');
}


# ----------------------------------------------------------------------------
# [BLACK_SCISSORS] EXTRACTION ET DÉCOUPAGE
# ----------------------------------------------------------------------------

"""
SLICE - Extraire une portion
"""

let texte = 'Hello World';

console.log(texte.slice(0, 5));    // 'Hello'
console.log(texte.slice(6, 11));   // 'World'
console.log(texte.slice(6));       // 'World' (jusqu'à la fin)

// Index négatifs (depuis la fin)
console.log(texte.slice(-5));      // 'World'
console.log(texte.slice(-5, -1));  // 'Worl'

"""
SUBSTRING - Similaire à slice (moins flexible)
"""

console.log(texte.substring(0, 5)); // 'Hello'
console.log(texte.substring(6));    // 'World'

// [ATTENTION] Différence : n'accepte pas index négatifs
console.log(texte.substring(-5)); // 'Hello World' (traite comme 0)

"""
[IDEE] RECOMMANDATION : Utilisez slice() (plus flexible)


SUBSTR - Extraire avec longueur ([ATTENTION] Déprécié)
"""

console.log(texte.substr(0, 5));  // 'Hello' (position 0, longueur 5)
console.log(texte.substr(6, 5));  // 'World' (position 6, longueur 5)

// [ATTENTION] Déprécié, utilisez slice() à la place


"""
SPLIT - Diviser en tableau
"""

let phrase = 'Hello World Welcome';
let mots = phrase.split(' ');
console.log(mots); // ['Hello', 'World', 'Welcome']

// Split par caractère
let texte = 'Hello';
let lettres = texte.split('');
console.log(lettres); // ['H', 'e', 'l', 'l', 'o']

// Split avec limite
let limited = phrase.split(' ', 2);
console.log(limited); // ['Hello', 'World']

// Exemples pratiques

// CSV
let csv = 'Alice,25,Paris';
let [nom, age, ville] = csv.split(',');

// Email
let email = 'user@example.com';
let [username, domain] = email.split('@');

// Date
let date = '2024-01-15';
let [annee, mois, jour] = date.split('-');


# ----------------------------------------------------------------------------
# [SYNC] REMPLACEMENT
# ----------------------------------------------------------------------------

"""
REPLACE - Remplacer première occurrence
"""

let texte = 'Hello World Hello';

let nouveau = texte.replace('Hello', 'Hi');
console.log(nouveau); // 'Hi World Hello'

// Original inchangé
console.log(texte); // 'Hello World Hello'

"""
REPLACEALL - Remplacer toutes occurrences (ES2021)
"""

let nouveau = texte.replaceAll('Hello', 'Hi');
console.log(nouveau); // 'Hi World Hi'

// Avant ES2021 : utiliser regex avec flag global
let nouveau2 = texte.replace(/Hello/g, 'Hi');

"""
AVEC FONCTION DE REMPLACEMENT
"""

let texte = 'Le prix est de 10€';

let nouveau = texte.replace(/\d+/, (match) => {
    return parseInt(match) * 2;
});

console.log(nouveau); // 'Le prix est de 20€'

// Capitaliser mots
let phrase = 'hello world welcome';
let capitalize = phrase.replace(/\b\w/g, (c) => c.toUpperCase());
console.log(capitalize); // 'Hello World Welcome'


# ----------------------------------------------------------------------------
# [COURS] EXERCICES PRATIQUES : STRINGS
# ----------------------------------------------------------------------------

"""
EXERCICE 1 : Validation email basique
"""

// Solution
function validerEmail(email) {
    email = email.trim().toLowerCase();
    
    // Vérifications basiques
    if (!email.includes('@')) return false;
    if (!email.includes('.')) return false;
    if (email.startsWith('@')) return false;
    if (email.endsWith('@')) return false;
    
    let [local, domain] = email.split('@');
    if (!local || !domain) return false;
    if (!domain.includes('.')) return false;
    
    return true;
}

console.log(validerEmail('test@example.com')); // true
console.log(validerEmail('invalid.email'));    // false

"""
EXERCICE 2 : Formater un numéro de téléphone
"""

// Solution
function formaterTelephone(numero) {
    // Enlever tous les non-chiffres
    let chiffres = numero.replace(/\D/g, '');
    
    // Formater 06 12 34 56 78
    if (chiffres.length === 10) {
        return chiffres.replace(/(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/, '$1 $2 $3 $4 $5');
    }
    
    return 'Format invalide';
}

console.log(formaterTelephone('0612345678'));      // '06 12 34 56 78'
console.log(formaterTelephone('06-12-34-56-78')); // '06 12 34 56 78'


# ============================================================================
# [GUIDE] CHAPITRE 8 : DESTRUCTURING ET SPREAD/REST
# ============================================================================

"""
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Destructurer des tableaux
[OK] Destructurer des objets
[OK] Utiliser le spread operator (...)
[OK] Utiliser les rest parameters
[OK] Combiner ces techniques
"""


# ----------------------------------------------------------------------------
# [PACKAGE] ARRAY DESTRUCTURING
# ----------------------------------------------------------------------------

"""
EXTRACTION DE BASE
"""

// Sans destructuring
let fruits = ['pomme', 'banane', 'orange'];
let fruit1 = fruits[0];
let fruit2 = fruits[1];
let fruit3 = fruits[2];

// Avec destructuring (ES6)
let [fruit1, fruit2, fruit3] = fruits;
console.log(fruit1); // 'pomme'
console.log(fruit2); // 'banane'
console.log(fruit3); // 'orange'

"""
SAUTER DES ÉLÉMENTS
"""

let nombres = [1, 2, 3, 4, 5];
let [premier, , troisieme, , cinquieme] = nombres;

console.log(premier);   // 1
console.log(troisieme); // 3
console.log(cinquieme); // 5

"""
VALEURS PAR DÉFAUT
"""

let [a, b, c = 3] = [1, 2];
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3 (valeur par défaut)

let [x, y, z = 10] = [5, 7, 9];
console.log(z); // 9 (valeur fournie)

"""
REST OPERATOR DANS DESTRUCTURING
"""

let nombres = [1, 2, 3, 4, 5];
let [premier, deuxieme, ...reste] = nombres;

console.log(premier);  // 1
console.log(deuxieme); // 2
console.log(reste);    // [3, 4, 5]

// Ignorer début
let [, , ...derniers] = nombres;
console.log(derniers); // [3, 4, 5]

"""
ÉCHANGER DES VARIABLES
"""

let a = 1;
let b = 2;

// Sans destructuring (nécessite variable temporaire)
let temp = a;
a = b;
b = temp;

// Avec destructuring (élégant)
[a, b] = [b, a];
console.log(a); // 2
console.log(b); // 1


"""
DESTRUCTURING IMBRIQUÉ
"""

let nested = [1, [2, 3], 4];
let [a, [b, c], d] = nested;

console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
console.log(d); // 4


# ----------------------------------------------------------------------------
# [CONSTRUCTION] OBJECT DESTRUCTURING
# ----------------------------------------------------------------------------

"""
EXTRACTION DE BASE
"""

let user = {
    nom: 'Alice',
    age: 25,
    ville: 'Paris'
};

// Sans destructuring
let nom = user.nom;
let age = user.age;
let ville = user.ville;

// Avec destructuring
let { nom, age, ville } = user;
console.log(nom);   // 'Alice'
console.log(age);   // 25
console.log(ville); // 'Paris'

"""
RENOMMER LES VARIABLES
"""

let { nom: username, age: userAge } = user;
console.log(username); // 'Alice'
console.log(userAge);  // 25

"""
VALEURS PAR DÉFAUT
"""

let { nom, age, pays = 'France' } = user;
console.log(pays); // 'France' (valeur par défaut)

// Avec renommage ET valeur par défaut
let { email: userEmail = 'non défini' } = user;
console.log(userEmail); // 'non défini'

"""
REST OPERATOR DANS OBJETS
"""

let user = {
    nom: 'Alice',
    age: 25,
    ville: 'Paris',
    pays: 'France',
    email: 'alice@example.com'
};

let { nom, age, ...autresInfos } = user;
console.log(nom);  // 'Alice'
console.log(age);  // 25
console.log(autresInfos);
// { ville: 'Paris', pays: 'France', email: 'alice@example.com' }

"""
DESTRUCTURING IMBRIQUÉ
"""

let user = {
    nom: 'Alice',
    adresse: {
        ville: 'Paris',
        codePostal: '75001'
    }
};

let {
    nom,
    adresse: { ville, codePostal }
} = user;

console.log(nom);        // 'Alice'
console.log(ville);      // 'Paris'
console.log(codePostal); // '75001'

"""
DESTRUCTURING DANS PARAMÈTRES DE FONCTIONS
"""

// Sans destructuring
function afficherUser(user) {
    console.log(user.nom);
    console.log(user.age);
}

// Avec destructuring
function afficherUser({ nom, age, ville = 'Inconnue' }) {
    console.log(nom);
    console.log(age);
    console.log(ville);
}

afficherUser({ nom: 'Alice', age: 25 });


# ----------------------------------------------------------------------------
# * SPREAD OPERATOR (...)
# ----------------------------------------------------------------------------

"""
SPREAD DANS LES TABLEAUX
"""

let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];

// Combiner tableaux
let combined = [...arr1, ...arr2];
console.log(combined); // [1, 2, 3, 4, 5, 6]

// Copier un tableau
let copie = [...arr1];
console.log(copie); // [1, 2, 3]

// Ajouter des éléments
let nouveau = [0, ...arr1, 4, 5];
console.log(nouveau); // [0, 1, 2, 3, 4, 5]

"""
SPREAD DANS LES OBJETS
"""

let user = { nom: 'Alice', age: 25 };
let details = { ville: 'Paris', pays: 'France' };

// Fusionner objets
let combined = { ...user, ...details };
console.log(combined);
// { nom: 'Alice', age: 25, ville: 'Paris', pays: 'France' }

// Copier un objet
let copie = { ...user };

// Ajouter/Modifier propriétés
let updated = { ...user, age: 26, email: 'alice@example.com' };
console.log(updated);
// { nom: 'Alice', age: 26, email: 'alice@example.com' }

"""
SPREAD DANS APPELS DE FONCTIONS
"""

function somme(a, b, c) {
    return a + b + c;
}

let nombres = [1, 2, 3];

// Sans spread
console.log(somme(nombres[0], nombres[1], nombres[2])); // 6

// Avec spread
console.log(somme(...nombres)); // 6

// Math.max avec spread
let nums = [5, 2, 9, 1, 7];
console.log(Math.max(...nums)); // 9


# ----------------------------------------------------------------------------
# [OBJECTIF] REST PARAMETERS
# ----------------------------------------------------------------------------

"""
NOMBRE VARIABLE D'ARGUMENTS
"""

function somme(...nombres) {
    return nombres.reduce((acc, n) => acc + n, 0);
}

console.log(somme(1, 2, 3));        // 6
console.log(somme(1, 2, 3, 4, 5));  // 15
console.log(somme(10));             // 10

"""
COMBINER AVEC PARAMÈTRES NORMAUX
"""

function creerMessage(prefixe, ...mots) {
    return prefixe + ' ' + mots.join(' ');
}

console.log(creerMessage('Bonjour', 'comment', 'allez', 'vous'));
// 'Bonjour comment allez vous'

"""
[IDEE] REST vs SPREAD

REST (...params) : Collecter plusieurs éléments en un tableau
SPREAD (...array) : Étaler un tableau en éléments individuels
"""

// REST (dans fonction)
function exemple(...args) { // Collecter arguments
    console.log(args); // [1, 2, 3]
}
exemple(1, 2, 3);

// SPREAD (dans appel)
let arr = [1, 2, 3];
console.log(...arr); // Étaler : 1 2 3


# ============================================================================
# [DOCS] RÉCAPITULATIF PARTIE 2 COMPLÈTE
# ============================================================================

"""
[BRAVO] FÉLICITATIONS ! PARTIE 2 TERMINÉE !

VOUS MAÎTRISEZ MAINTENANT :

Chapitre 5 : Tableaux (Arrays)
[OK] Création et manipulation
[OK] Méthodes essentielles (push, pop, shift, unshift)
[OK] Itération (forEach, map, filter, reduce)
[OK] Recherche (find, findIndex, includes)
[OK] Tri et manipulation (sort, slice, splice)

Chapitre 6 : Objets
[OK] Création et propriétés
[OK] Méthodes d'objets et this
[OK] Object.keys(), values(), entries()
[OK] Copie et fusion (Object.assign, spread)
[OK] Getters/Setters

Chapitre 7 : Strings
[OK] Création et manipulation
[OK] Template literals
[OK] Méthodes de transformation
[OK] Recherche et remplacement
[OK] Split et extraction

Chapitre 8 : Destructuring et Spread/Rest
[OK] Array destructuring
[OK] Object destructuring
[OK] Spread operator (...)
[OK] Rest parameters
[OK] Cas d'usage pratiques


[OBJECTIF] VOUS POUVEZ MAINTENANT :

[OK] Manipuler des structures de données complexes
[OK] Travailler efficacement avec tableaux et objets
[OK] Utiliser la syntaxe moderne ES6+
[OK] Écrire du code plus concis et lisible


-> PROCHAINE ÉTAPE : PARTIE 3

La Partie 3 couvrira :
- DOM Manipulation
- Événements
- Programmation asynchrone
- Modules ES6
- Classes et POO
- Gestion d'erreurs

Vous allez apprendre à :
- Créer des interfaces interactives
- Réagir aux actions utilisateur
- Faire des requêtes API
- Organiser du code en modules
- Programmer en orienté objet


[GUIDE] FICHIER SUIVANT : javascript_partie3.txt

Excellente progression ! Vous maîtrisez maintenant les fondamentaux ! [RAPIDE]
"""
-e 

# ============================================================================
# PARTIE 3 : PROGRAMMATION MODERNE
# ============================================================================

# ============================================================================
# [LIVRE] JAVASCRIPT - PARTIE 3 : PROGRAMMATION MODERNE
# ============================================================================
#
# [OBJECTIF] CETTE PARTIE COUVRE :
# - Chapitre 9 : DOM Manipulation
# - Chapitre 10 : Événements
# - Chapitre 11 : Asynchrone (Promises, Async/Await)
# - Chapitre 12 : Modules ES6
# - Chapitre 13 : Classes et POO
# - Chapitre 14 : Gestion d'Erreurs
#
# [TEMPS] TEMPS : ~10-12 heures
# [DOCS] PRÉREQUIS : Parties 1 et 2 complétées
# ============================================================================


# ============================================================================
# [GUIDE] CHAPITRE 9 : DOM MANIPULATION
# ============================================================================

"""
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Comprendre ce qu'est le DOM
[OK] Sélectionner des éléments HTML
[OK] Modifier le contenu et les styles
[OK] Créer et supprimer des éléments
[OK] Naviguer dans le DOM
[OK] Manipuler les classes et attributs
[OK] Créer des interfaces interactives
"""


# ----------------------------------------------------------------------------
# [ARBRE] QU'EST-CE QUE LE DOM ?
# ----------------------------------------------------------------------------

"""
DOM = Document Object Model

[IDEE] DÉFINITION SIMPLE

Le DOM est une représentation en arbre de la page HTML
JavaScript peut le manipuler pour modifier la page dynamiquement


ANALOGIE [ARBRE]

HTML = Plan architectural d'une maison
DOM = Maison construite que vous pouvez modifier
JavaScript = Outils pour rénover la maison


STRUCTURE DU DOM
"""

"""
HTML :
<!DOCTYPE html>
<html>
  <head>
    <title>Ma Page</title>
  </head>
  <body>
    <h1 id="titre">Bonjour</h1>
    <p class="texte">Paragraphe</p>
  </body>
</html>

DOM (arbre) :
document
  └── html
      ├── head
      │   └── title
      │       └── "Ma Page"
      └── body
          ├── h1 (id="titre")
          │   └── "Bonjour"
          └── p (class="texte")
              └── "Paragraphe"
"""

"""
[IDEE] COMMENT FONCTIONNE LE DOM ?

1. Navigateur charge le HTML
2. Parse (analyse) le HTML
3. Crée le DOM (arbre d'objets)
4. JavaScript peut manipuler ces objets
5. Modifications = Page mise à jour en temps réel


OBJET document

Point d'entrée principal pour accéder au DOM
"""

// Voir le document
console.log(document);

// Propriétés utiles
console.log(document.title);        // Titre de la page
console.log(document.URL);          // URL actuelle
console.log(document.domain);       // Domaine
console.log(document.documentElement); // <html>
console.log(document.head);         // <head>
console.log(document.body);         // <body>


# ----------------------------------------------------------------------------
# [OBJECTIF] SÉLECTIONNER DES ÉLÉMENTS
# ----------------------------------------------------------------------------

"""
HTML DE BASE POUR LES EXEMPLES
"""

"""
<!DOCTYPE html>
<html>
<body>
    <h1 id="titre">Titre Principal</h1>
    <p class="texte">Premier paragraphe</p>
    <p class="texte">Deuxième paragraphe</p>
    <ul id="liste">
        <li>Item 1</li>
        <li>Item 2</li>
        <li>Item 3</li>
    </ul>
    <button id="monBouton">Cliquez-moi</button>
</body>
</html>
"""


"""
GETELEMENTBYID - Par ID (unique)
"""

let titre = document.getElementById('titre');
console.log(titre); // <h1 id="titre">Titre Principal</h1>

// Si n'existe pas
let inexistant = document.getElementById('xyz');
console.log(inexistant); // null

"""
[IDEE] getElementById()

- Retourne UN élément (ou null)
- ID doit être unique dans la page
- Méthode la plus rapide
- Sensible à la casse


GETELEMENTSBYCLASSNAME - Par classe
"""

let paragraphes = document.getElementsByClassName('texte');
console.log(paragraphes); // HTMLCollection [p.texte, p.texte]

// C'est une collection "live" (se met à jour automatiquement)
console.log(paragraphes.length); // 2

// Accéder aux éléments
console.log(paragraphes[0]); // Premier <p>
console.log(paragraphes[1]); // Deuxième <p>

// Parcourir
for (let p of paragraphes) {
    console.log(p.textContent);
}

"""
[IDEE] getElementsByClassName()

- Retourne HTMLCollection (array-like)
- Collection LIVE (se met à jour automatiquement)
- Peut retourner plusieurs éléments
- Plus lent que getElementById


GETELEMENTSBYTAGNAME - Par nom de balise
"""

let tousLesParagraphes = document.getElementsByTagName('p');
console.log(tousLesParagraphes); // HTMLCollection de tous les <p>

let tousLesLi = document.getElementsByTagName('li');
console.log(tousLesLi.length); // 3

// Tous les éléments
let tousLesElements = document.getElementsByTagName('*');
console.log(tousLesElements.length);


"""
QUERYSELECTOR - Sélecteur CSS (moderne, recommandé)
"""

// Par ID
let titre = document.querySelector('#titre');

// Par classe
let premierTexte = document.querySelector('.texte');

// Par balise
let premierP = document.querySelector('p');

// Combinaisons CSS
let premierLi = document.querySelector('ul#liste li');
let bouton = document.querySelector('button#monBouton');

// Sélecteurs avancés
let dernierLi = document.querySelector('li:last-child');
let deuxiemeLi = document.querySelector('li:nth-child(2)');

"""
[IDEE] querySelector()

- Retourne PREMIER élément correspondant
- Utilise sélecteurs CSS
- Plus flexible et moderne
- Retourne null si pas trouvé


QUERYSELECTORALL - Tous les éléments correspondants
"""

let tousLesTextes = document.querySelectorAll('.texte');
console.log(tousLesTextes); // NodeList [p.texte, p.texte]

let tousLesLi = document.querySelectorAll('li');
console.log(tousLesLi.length); // 3

// Parcourir avec forEach (NodeList)
tousLesTextes.forEach(element => {
    console.log(element.textContent);
});

// Ou avec for...of
for (let li of tousLesLi) {
    console.log(li.textContent);
}

"""
[IDEE] querySelectorAll()

- Retourne NodeList (array-like)
- Collection STATIQUE (pas live)
- Supporte forEach() natif
- Plus moderne et flexible


COMPARAISON DES MÉTHODES
"""

// [OK] MODERNES (recommandées)
document.querySelector('#titre');           // Un élément
document.querySelectorAll('.texte');        // Plusieurs éléments

// [ATTENTION] ANCIENNES (encore utilisées)
document.getElementById('titre');           // Un élément par ID
document.getElementsByClassName('texte');   // Plusieurs par classe
document.getElementsByTagName('p');         // Plusieurs par balise

"""
[CLE] RECOMMANDATION :

Utilisez querySelector() et querySelectorAll()
Plus flexibles, syntaxe CSS familière


# ----------------------------------------------------------------------------
# [EDIT] MODIFIER LE CONTENU
# ----------------------------------------------------------------------------

"""
TEXTCONTENT - Texte brut
"""

let titre = document.querySelector('#titre');

// Lire le texte
console.log(titre.textContent); // 'Titre Principal'

// Modifier le texte
titre.textContent = 'Nouveau Titre';

// Ajouter du texte
titre.textContent += ' - Modifié';

"""
INNERTEXT - Texte visible (similaire à textContent)
"""

let p = document.querySelector('p');

console.log(p.innerText); // Texte visible à l'écran
p.innerText = 'Nouveau paragraphe';

"""
[IDEE] textContent vs innerText

textContent :
- Retourne TOUT le texte (même caché)
- Plus rapide
- Recommandé

innerText :
- Retourne texte VISIBLE seulement
- Respecte le CSS (display: none)
- Plus lent


INNERHTML - HTML complet
"""

let div = document.querySelector('div');

// Lire le HTML
console.log(div.innerHTML);

// Modifier le HTML
div.innerHTML = '<p>Nouveau <strong>contenu</strong></p>';

// Ajouter du HTML
div.innerHTML += '<p>Paragraphe ajouté</p>';

"""
[ATTENTION] ATTENTION : SÉCURITÉ

innerHTML peut créer des failles XSS si contenu utilisateur
"""

// [X] DANGEREUX
let userInput = '<img src=x onerror="alert(\'XSS\')">';
div.innerHTML = userInput; // Script s'exécute !

// [OK] SÉCURISÉ
div.textContent = userInput; // Affiché comme texte, pas exécuté

"""
[IDEE] QUAND UTILISER innerHTML ?

[OK] Contenu statique contrôlé par vous
[OK] Création rapide de structures HTML
[X] Jamais avec input utilisateur non sanitisé


OUTERHTML - Remplacer l'élément entier
"""

let p = document.querySelector('p');

console.log(p.outerHTML);
// <p class="texte">Contenu</p>

p.outerHTML = '<div class="nouveau">Remplacé</div>';
// Le <p> est maintenant un <div>


"""
VALUE - Pour les champs de formulaire
"""

// Input text
let input = document.querySelector('input[type="text"]');
console.log(input.value); // Valeur actuelle
input.value = 'Nouveau texte';

// Textarea
let textarea = document.querySelector('textarea');
textarea.value = 'Contenu du textarea';

// Select
let select = document.querySelector('select');
console.log(select.value); // Option sélectionnée
select.value = 'option2';  // Changer la sélection

// Checkbox
let checkbox = document.querySelector('input[type="checkbox"]');
console.log(checkbox.checked); // true ou false
checkbox.checked = true;       // Cocher


# ----------------------------------------------------------------------------
# [DESIGN] MODIFIER LES STYLES
# ----------------------------------------------------------------------------

"""
PROPRIÉTÉ STYLE - Styles inline
"""

let titre = document.querySelector('#titre');

// Modifier une propriété
titre.style.color = 'red';
titre.style.fontSize = '32px';
titre.style.backgroundColor = 'yellow';

// Propriétés CSS avec tirets -> camelCase
titre.style.fontWeight = 'bold';        // font-weight
titre.style.textAlign = 'center';       // text-align
titre.style.borderRadius = '5px';       // border-radius

// Plusieurs styles
titre.style.cssText = 'color: blue; font-size: 24px; padding: 10px;';

"""
[IDEE] STYLE.PROPERTY

- Ajoute/modifie styles inline
- Priorité élevée (override CSS externe)
- Propriétés CSS en camelCase
- Valeurs toujours en string avec unité


GETCOMPUTEDSTYLE - Lire les styles calculés
"""

let element = document.querySelector('.texte');

// Style calculé final (après CSS)
let styles = window.getComputedStyle(element);

console.log(styles.color);           // 'rgb(0, 0, 0)'
console.log(styles.fontSize);        // '16px'
console.log(styles.fontFamily);      // 'Arial, sans-serif'
console.log(styles.display);         // 'block'

// getPropertyValue()
console.log(styles.getPropertyValue('color'));

"""
MANIPULER LES CLASSES (RECOMMANDÉ)
"""

let element = document.querySelector('.box');

// Ajouter une classe
element.classList.add('active');

// Retirer une classe
element.classList.remove('hidden');

// Basculer (toggle) une classe
element.classList.toggle('expanded'); // Ajoute si absent, retire si présent

// Vérifier si contient
if (element.classList.contains('active')) {
    console.log('Element est actif');
}

// Remplacer une classe
element.classList.replace('old-class', 'new-class');

// Plusieurs classes
element.classList.add('class1', 'class2', 'class3');
element.classList.remove('class1', 'class2');

"""
[IDEE] CLASSLIST vs STYLE

classList (recommandé) :
[OK] Séparation HTML/CSS/JS
[OK] Facile à maintenir
[OK] Réutilisable
[OK] Performant

style (occasionnel) :
- Styles dynamiques calculés
- Animations JavaScript
- Modifications temporaires


EXEMPLE PRATIQUE : THÈME SOMBRE
"""

// HTML
"""
<button id="themeToggle">Changer de thème</button>
<div class="content">
    <h1>Mon Site</h1>
    <p>Contenu...</p>
</div>
"""

// CSS
"""
.dark-theme {
    background-color: #222;
    color: #fff;
}

.dark-theme .content {
    background-color: #333;
}
"""

// JavaScript
let themeToggle = document.querySelector('#themeToggle');
let body = document.body;

themeToggle.addEventListener('click', () => {
    body.classList.toggle('dark-theme');
    
    // Sauvegarder préférence
    let isDark = body.classList.contains('dark-theme');
    localStorage.setItem('theme', isDark ? 'dark' : 'light');
});

// Charger préférence au démarrage
if (localStorage.getItem('theme') === 'dark') {
    body.classList.add('dark-theme');
}


# ----------------------------------------------------------------------------
# + CRÉER ET AJOUTER DES ÉLÉMENTS
# ----------------------------------------------------------------------------

"""
CREATEELEMENT - Créer un nouvel élément
"""

// Créer un paragraphe
let p = document.createElement('p');

// Ajouter du contenu
p.textContent = 'Nouveau paragraphe';

// Ajouter des classes
p.classList.add('texte', 'nouveau');

// Ajouter des attributs
p.setAttribute('id', 'para1');

// Ajouter des styles
p.style.color = 'blue';

console.log(p); // <p class="texte nouveau" id="para1">Nouveau paragraphe</p>

// [ATTENTION] Élément créé mais PAS ENCORE dans la page !

"""
APPENDCHILD - Ajouter à la fin
"""

let container = document.querySelector('#container');
let p = document.createElement('p');
p.textContent = 'Nouveau paragraphe';

container.appendChild(p); // Ajoute à la fin de container

"""
APPEND - Ajouter plusieurs éléments (moderne)
"""

let container = document.querySelector('#container');

// Créer plusieurs éléments
let p1 = document.createElement('p');
p1.textContent = 'Paragraphe 1';

let p2 = document.createElement('p');
p2.textContent = 'Paragraphe 2';

// Ajouter plusieurs à la fois
container.append(p1, p2);

// Ajouter texte directement
container.append('Texte simple');

// Ajouter avant (début)
container.prepend(p1);

"""
INSERTBEFORE - Insérer avant un élément
"""

let liste = document.querySelector('#liste');
let nouveauLi = document.createElement('li');
nouveauLi.textContent = 'Nouvel item';

// Insérer avant le premier <li>
let premierLi = liste.querySelector('li');
liste.insertBefore(nouveauLi, premierLi);

"""
INSERTADJACENTELEMENT - Insérer à une position précise
"""

let element = document.querySelector('#element');
let nouveau = document.createElement('p');

// 'beforebegin' : Avant l'élément
element.insertAdjacentElement('beforebegin', nouveau);

// 'afterbegin' : Au début de l'élément (premier enfant)
element.insertAdjacentElement('afterbegin', nouveau);

// 'beforeend' : À la fin de l'élément (dernier enfant)
element.insertAdjacentElement('beforeend', nouveau);

// 'afterend' : Après l'élément
element.insertAdjacentElement('afterend', nouveau);

/*
Positions :
    <!-- beforebegin -->
    <element>
        <!-- afterbegin -->
        contenu existant
        <!-- beforeend -->
    </element>
    <!-- afterend -->
*/

"""
INSERTADJACENTHTML - Insérer HTML directement
"""

let container = document.querySelector('#container');

container.insertAdjacentHTML('beforeend', '<p>Nouveau paragraphe</p>');

"""
EXEMPLE COMPLET : Créer une liste dynamique
"""

// HTML de départ
"""
<div id="app">
    <h2>Ma Liste</h2>
    <input type="text" id="itemInput" placeholder="Nouvel item">
    <button id="addBtn">Ajouter</button>
    <ul id="liste"></ul>
</div>
"""

// JavaScript
let input = document.querySelector('#itemInput');
let btn = document.querySelector('#addBtn');
let liste = document.querySelector('#liste');

btn.addEventListener('click', () => {
    let texte = input.value.trim();
    
    if (texte === '') return;
    
    // Créer <li>
    let li = document.createElement('li');
    li.textContent = texte;
    
    // Ajouter à la liste
    liste.appendChild(li);
    
    // Vider l'input
    input.value = '';
    input.focus();
});


# ----------------------------------------------------------------------------
# [X] SUPPRIMER DES ÉLÉMENTS
# ----------------------------------------------------------------------------

"""
REMOVE - Supprimer un élément (ES6)
"""

let element = document.querySelector('#element');
element.remove(); // Supprime l'élément du DOM

"""
REMOVECHILD - Supprimer un enfant
"""

let parent = document.querySelector('#parent');
let enfant = document.querySelector('#enfant');

parent.removeChild(enfant);

"""
VIDER UN ÉLÉMENT
"""

let container = document.querySelector('#container');

// Méthode 1 : innerHTML
container.innerHTML = '';

// Méthode 2 : Boucle removeChild (plus sûr)
while (container.firstChild) {
    container.removeChild(container.firstChild);
}

// Méthode 3 : replaceChildren (moderne)
container.replaceChildren();

"""
EXEMPLE : Liste avec suppression
"""

// HTML
"""
<ul id="liste"></ul>
<input type="text" id="input">
<button id="addBtn">Ajouter</button>
"""

// JavaScript
let liste = document.querySelector('#liste');
let input = document.querySelector('#input');
let addBtn = document.querySelector('#addBtn');

function ajouterItem(texte) {
    let li = document.createElement('li');
    li.textContent = texte;
    
    // Bouton supprimer
    let btnSuppr = document.createElement('button');
    btnSuppr.textContent = 'X';
    btnSuppr.style.marginLeft = '10px';
    
    btnSuppr.addEventListener('click', () => {
        li.remove(); // Supprimer le <li>
    });
    
    li.appendChild(btnSuppr);
    liste.appendChild(li);
}

addBtn.addEventListener('click', () => {
    if (input.value.trim()) {
        ajouterItem(input.value);
        input.value = '';
    }
});


# ----------------------------------------------------------------------------
# [COMPASS] NAVIGUER DANS LE DOM
# ----------------------------------------------------------------------------

"""
RELATIONS PARENT/ENFANT
"""

let element = document.querySelector('#element');

// Parent
console.log(element.parentElement);  // Élément parent
console.log(element.parentNode);     // Nœud parent (similaire)

// Enfants
console.log(element.children);       // HTMLCollection des enfants
console.log(element.childNodes);     // NodeList (inclut texte/commentaires)

console.log(element.firstElementChild); // Premier enfant
console.log(element.lastElementChild);  // Dernier enfant

// Compter enfants
console.log(element.childElementCount);

"""
[IDEE] ELEMENT vs NODE

Element : Seulement éléments HTML (<div>, <p>, etc.)
Node : Tous types (éléments, texte, commentaires)

Recommandé : Utilisez les versions Element


FRÈRES ET SŒURS (SIBLINGS)
"""

let element = document.querySelector('#element');

// Frère suivant
console.log(element.nextElementSibling);

// Frère précédent
console.log(element.previousElementSibling);

"""
REMONTER DANS L'ARBRE
"""

// Trouver l'ancêtre le plus proche qui correspond
let element = document.querySelector('.inner');
let container = element.closest('.container');

// Vérifier si contient un élément
let parent = document.querySelector('#parent');
let enfant = document.querySelector('#enfant');

if (parent.contains(enfant)) {
    console.log('parent contient enfant');
}

"""
EXEMPLE : Navigation dans un menu
"""

// HTML
"""
<ul class="menu">
    <li><a href="#home">Accueil</a></li>
    <li><a href="#about">À propos</a></li>
    <li><a href="#contact">Contact</a></li>
</ul>
"""

// Sélectionner tous les liens
let menu = document.querySelector('.menu');
let liens = menu.querySelectorAll('a');

liens.forEach(lien => {
    lien.addEventListener('click', (e) => {
        // Retirer 'active' de tous
        liens.forEach(l => l.parentElement.classList.remove('active'));
        
        // Ajouter 'active' au parent (<li>) du lien cliqué
        lien.parentElement.classList.add('active');
    });
});


# ----------------------------------------------------------------------------
# [LABEL] ATTRIBUTS
# ----------------------------------------------------------------------------

"""
GETATTRIBUTE / SETATTRIBUTE
"""

let img = document.querySelector('img');

// Lire un attribut
console.log(img.getAttribute('src'));
console.log(img.getAttribute('alt'));

// Modifier un attribut
img.setAttribute('src', 'nouvelle-image.jpg');
img.setAttribute('alt', 'Nouvelle description');

// Ajouter un attribut personnalisé
img.setAttribute('data-id', '123');

// Vérifier existence
if (img.hasAttribute('title')) {
    console.log('A un attribut title');
}

// Supprimer un attribut
img.removeAttribute('title');

"""
ACCÈS DIRECT AUX ATTRIBUTS COURANTS
"""

let img = document.querySelector('img');

// Propriétés directes (plus simple)
console.log(img.src);    // Équivalent à getAttribute('src')
console.log(img.alt);
img.src = 'image.jpg';   // Équivalent à setAttribute('src', ...)
img.alt = 'Description';

// Autres propriétés courantes
let lien = document.querySelector('a');
console.log(lien.href);
console.log(lien.target);

let input = document.querySelector('input');
console.log(input.type);
console.log(input.name);
console.log(input.value);
console.log(input.disabled);
console.log(input.checked); // Pour checkbox/radio

"""
DATA ATTRIBUTES - Attributs personnalisés
"""

// HTML
"""
<div id="user" data-user-id="123" data-role="admin">
    Utilisateur
</div>
"""

// JavaScript
let user = document.querySelector('#user');

// Accéder via dataset (camelCase)
console.log(user.dataset.userId);  // '123'
console.log(user.dataset.role);    // 'admin'

// Modifier
user.dataset.userId = '456';
user.dataset.status = 'active';

// HTML résultant :
// <div data-user-id="456" data-role="admin" data-status="active">

"""
[IDEE] DATA ATTRIBUTES

- Préfixe : data-
- Accessible via element.dataset
- Tirets -> camelCase (data-user-id -> dataset.userId)
- Utile pour stocker métadonnées


# ----------------------------------------------------------------------------
# [COURS] EXERCICE PRATIQUE : TODO LIST COMPLÈTE
# ----------------------------------------------------------------------------

"""
PROJET : Application TODO List

Fonctionnalités :
- Ajouter des tâches
- Marquer comme complétées
- Supprimer des tâches
- Compter les tâches
- Filtrer (toutes/actives/complétées)
"""

// HTML
"""
<!DOCTYPE html>
<html>
<head>
    <title>Todo List</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: Arial; padding: 20px; background: #f5f5f5; }
        .container { max-width: 600px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; }
        h1 { margin-bottom: 20px; color: #333; }
        .input-group { display: flex; gap: 10px; margin-bottom: 20px; }
        input[type="text"] { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
        button { padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; }
        .btn-add { background: #4CAF50; color: white; }
        .filters { display: flex; gap: 10px; margin-bottom: 20px; }
        .filters button { background: #f0f0f0; color: #333; }
        .filters button.active { background: #2196F3; color: white; }
        .todo-list { list-style: none; }
        .todo-item { display: flex; align-items: center; padding: 10px; border-bottom: 1px solid #eee; }
        .todo-item.completed { opacity: 0.6; }
        .todo-item.completed .todo-text { text-decoration: line-through; }
        .todo-checkbox { margin-right: 10px; }
        .todo-text { flex: 1; }
        .btn-delete { background: #f44336; color: white; padding: 5px 10px; font-size: 12px; }
        .stats { margin-top: 20px; color: #666; font-size: 14px; }
    </style>
</head>
<body>
    <div class="container">
        <h1>[NOTE] Ma Todo List</h1>
        
        <div class="input-group">
            <input type="text" id="todoInput" placeholder="Nouvelle tâche...">
            <button class="btn-add" id="addBtn">Ajouter</button>
        </div>
        
        <div class="filters">
            <button data-filter="all" class="active">Toutes</button>
            <button data-filter="active">Actives</button>
            <button data-filter="completed">Complétées</button>
        </div>
        
        <ul id="todoList" class="todo-list"></ul>
        
        <div class="stats" id="stats"></div>
    </div>
    
    <script src="todo.js"></script>
</body>
</html>
"""

// JavaScript (todo.js)
"""
// Sélection des éléments
const todoInput = document.querySelector('#todoInput');
const addBtn = document.querySelector('#addBtn');
const todoList = document.querySelector('#todoList');
const filterBtns = document.querySelectorAll('.filters button');
const stats = document.querySelector('#stats');

// État actuel
let currentFilter = 'all';
let todos = [];

// Charger depuis localStorage
function chargerTodos() {
    const saved = localStorage.getItem('todos');
    if (saved) {
        todos = JSON.parse(saved);
        afficherTodos();
    }
}

// Sauvegarder dans localStorage
function sauvegarderTodos() {
    localStorage.setItem('todos', JSON.stringify(todos));
}

// Ajouter une tâche
function ajouterTodo() {
    const texte = todoInput.value.trim();
    
    if (texte === '') return;
    
    const todo = {
        id: Date.now(),
        texte: texte,
        completed: false
    };
    
    todos.push(todo);
    sauvegarderTodos();
    afficherTodos();
    
    todoInput.value = '';
    todoInput.focus();
}

// Basculer le statut complété
function toggleTodo(id) {
    const todo = todos.find(t => t.id === id);
    if (todo) {
        todo.completed = !todo.completed;
        sauvegarderTodos();
        afficherTodos();
    }
}

// Supprimer une tâche
function supprimerTodo(id) {
    todos = todos.filter(t => t.id !== id);
    sauvegarderTodos();
    afficherTodos();
}

// Afficher les tâches
function afficherTodos() {
    // Filtrer selon le filtre actuel
    let todosFiltrees = todos;
    
    if (currentFilter === 'active') {
        todosFiltrees = todos.filter(t => !t.completed);
    } else if (currentFilter === 'completed') {
        todosFiltrees = todos.filter(t => t.completed);
    }
    
    // Vider la liste
    todoList.innerHTML = '';
    
    // Afficher chaque tâche
    todosFiltrees.forEach(todo => {
        const li = document.createElement('li');
        li.className = 'todo-item';
        if (todo.completed) {
            li.classList.add('completed');
        }
        
        // Checkbox
        const checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.className = 'todo-checkbox';
        checkbox.checked = todo.completed;
        checkbox.addEventListener('change', () => toggleTodo(todo.id));
        
        // Texte
        const texte = document.createElement('span');
        texte.className = 'todo-text';
        texte.textContent = todo.texte;
        
        // Bouton supprimer
        const btnSupprimer = document.createElement('button');
        btnSupprimer.className = 'btn-delete';
        btnSupprimer.textContent = 'Supprimer';
        btnSupprimer.addEventListener('click', () => supprimerTodo(todo.id));
        
        li.appendChild(checkbox);
        li.appendChild(texte);
        li.appendChild(btnSupprimer);
        todoList.appendChild(li);
    });
    
    // Mettre à jour les statistiques
    mettreAJourStats();
}

// Mettre à jour les statistiques
function mettreAJourStats() {
    const total = todos.length;
    const actives = todos.filter(t => !t.completed).length;
    const completees = todos.filter(t => t.completed).length;
    
    stats.textContent = `${total} tâche(s) - ${actives} active(s) - ${completees} complétée(s)`;
}

// Gérer les filtres
filterBtns.forEach(btn => {
    btn.addEventListener('click', () => {
        // Retirer 'active' de tous
        filterBtns.forEach(b => b.classList.remove('active'));
        
        // Ajouter 'active' au bouton cliqué
        btn.classList.add('active');
        
        // Changer le filtre
        currentFilter = btn.dataset.filter;
        
        // Réafficher
        afficherTodos();
    });
});

// Event listeners
addBtn.addEventListener('click', ajouterTodo);

todoInput.addEventListener('keypress', (e) => {
    if (e.key === 'Enter') {
        ajouterTodo();
    }
});

// Charger au démarrage
chargerTodos();
"""

# ----------------------------------------------------------------------------
# Chapitre 10 : [PACKAGE] L'OBJET EVENT
# ----------------------------------------------------------------------------

"""
L'OBJET EVENT

Chaque fois qu'un événement se produit, un objet Event est créé
Contient des informations sur l'événement
"""

bouton.addEventListener('click', function(event) {
    console.log(event); // Objet MouseEvent
    console.log(event.type);   // "click"
    console.log(event.target); // Élément cliqué
});

// Raccourci commun : e ou evt
bouton.addEventListener('click', (e) => {
    console.log(e.target);
});

"""
PROPRIÉTÉS IMPORTANTES DE EVENT
"""

element.addEventListener('click', (e) => {
    // Type d'événement
    console.log(e.type); // "click"
    
    // Élément qui a déclenché l'événement
    console.log(e.target); // Élément cliqué
    
    // Élément sur lequel le listener est attaché
    console.log(e.currentTarget); // Peut être différent avec bubbling
    
    // Horodatage
    console.log(e.timeStamp);
    
    // Si l'événement fait des bulles
    console.log(e.bubbles);
});

"""
[IDEE] target vs currentTarget

target : Élément qui a VRAIMENT été cliqué
currentTarget : Élément sur lequel le listener est ATTACHÉ


EXEMPLE target vs currentTarget
"""

// HTML
"""
<div id="parent">
    <button id="enfant">Bouton</button>
</div>
"""

let parent = document.querySelector('#parent');

parent.addEventListener('click', (e) => {
    console.log('Target:', e.target);           // <button> (cliqué)
    console.log('CurrentTarget:', e.currentTarget); // <div> (listener attaché)
});


# ----------------------------------------------------------------------------
# [OBJECTIF] ÉVÉNEMENTS SOURIS
# ----------------------------------------------------------------------------

"""
TYPES D'ÉVÉNEMENTS SOURIS
"""

let element = document.querySelector('#element');

// click : Clic complet (mousedown + mouseup)
element.addEventListener('click', (e) => {
    console.log('Clic simple');
});

// dblclick : Double-clic
element.addEventListener('dblclick', (e) => {
    console.log('Double-clic');
});

// mousedown : Bouton de souris enfoncé
element.addEventListener('mousedown', (e) => {
    console.log('Bouton enfoncé');
});

// mouseup : Bouton de souris relâché
element.addEventListener('mouseup', (e) => {
    console.log('Bouton relâché');
});

// mousemove : Souris se déplace sur l'élément
element.addEventListener('mousemove', (e) => {
    console.log('Souris bouge');
});

// mouseenter : Souris entre dans l'élément (ne bulle pas)
element.addEventListener('mouseenter', (e) => {
    console.log('Souris entre');
});

// mouseleave : Souris quitte l'élément (ne bulle pas)
element.addEventListener('mouseleave', (e) => {
    console.log('Souris sort');
});

// mouseover : Souris entre (bulle)
element.addEventListener('mouseover', (e) => {
    console.log('Mouse over');
});

// mouseout : Souris sort (bulle)
element.addEventListener('mouseout', (e) => {
    console.log('Mouse out');
});

"""
PROPRIÉTÉS SPÉCIFIQUES SOURIS
"""

element.addEventListener('click', (e) => {
    // Position de la souris
    console.log('X:', e.clientX, 'Y:', e.clientY); // Relatif au viewport
    console.log('X:', e.pageX, 'Y:', e.pageY);     // Relatif à la page
    console.log('X:', e.screenX, 'Y:', e.screenY); // Relatif à l'écran
    
    // Bouton cliqué
    console.log(e.button); // 0: gauche, 1: milieu, 2: droit
    
    // Touches modificatrices
    console.log(e.ctrlKey);  // true si Ctrl enfoncé
    console.log(e.shiftKey); // true si Shift enfoncé
    console.log(e.altKey);   // true si Alt enfoncé
    console.log(e.metaKey);  // true si Cmd/Win enfoncé
});

"""
EXEMPLE : Suivre la souris
"""

// HTML
"""
<style>
#curseur {
    position: fixed;
    width: 20px;
    height: 20px;
    background: red;
    border-radius: 50%;
    pointer-events: none;
    transform: translate(-50%, -50%);
}
</style>
<div id="curseur"></div>
"""

// JavaScript
let curseur = document.querySelector('#curseur');

document.addEventListener('mousemove', (e) => {
    curseur.style.left = e.clientX + 'px';
    curseur.style.top = e.clientY + 'px';
});


"""
EXEMPLE : Drag & Drop simple
"""

let box = document.querySelector('#box');
let isDragging = false;
let offsetX, offsetY;

box.addEventListener('mousedown', (e) => {
    isDragging = true;
    offsetX = e.clientX - box.offsetLeft;
    offsetY = e.clientY - box.offsetTop;
    box.style.cursor = 'grabbing';
});

document.addEventListener('mousemove', (e) => {
    if (isDragging) {
        box.style.left = (e.clientX - offsetX) + 'px';
        box.style.top = (e.clientY - offsetY) + 'px';
    }
});

document.addEventListener('mouseup', () => {
    isDragging = false;
    box.style.cursor = 'grab';
});


# ----------------------------------------------------------------------------
# [KEYBOARD] ÉVÉNEMENTS CLAVIER
# ----------------------------------------------------------------------------

"""
TYPES D'ÉVÉNEMENTS CLAVIER
"""

// keydown : Touche enfoncée (se répète si maintenue)
document.addEventListener('keydown', (e) => {
    console.log('Touche enfoncée:', e.key);
});

// keyup : Touche relâchée
document.addEventListener('keyup', (e) => {
    console.log('Touche relâchée:', e.key);
});

// keypress : Touche caractère enfoncée ([ATTENTION] Déprécié, utilisez keydown)

"""
PROPRIÉTÉS ÉVÉNEMENTS CLAVIER
"""

document.addEventListener('keydown', (e) => {
    // La touche pressée
    console.log(e.key);        // "a", "Enter", "ArrowUp", etc.
    console.log(e.code);       // "KeyA", "Enter", "ArrowUp", etc.
    console.log(e.keyCode);    // Code numérique ([ATTENTION] Déprécié)
    
    // Touches modificatrices
    console.log(e.ctrlKey);
    console.log(e.shiftKey);
    console.log(e.altKey);
    console.log(e.metaKey);
    
    // Si c'est une touche qui se répète
    console.log(e.repeat);
});

"""
[IDEE] key vs code

key : Valeur du caractère ("A", "a", "1")
code : Position physique de la touche ("KeyA", "Digit1")

Utilisez key pour la plupart des cas


DÉTECTER TOUCHES SPÉCIFIQUES
"""

document.addEventListener('keydown', (e) => {
    // Touche Entrée
    if (e.key === 'Enter') {
        console.log('Entrée pressée');
    }
    
    // Touches flèches
    if (e.key === 'ArrowUp') {
        console.log('Haut');
    }
    if (e.key === 'ArrowDown') {
        console.log('Bas');
    }
    if (e.key === 'ArrowLeft') {
        console.log('Gauche');
    }
    if (e.key === 'ArrowRight') {
        console.log('Droite');
    }
    
    // Échappement
    if (e.key === 'Escape') {
        console.log('Échap');
    }
    
    // Ctrl + S
    if (e.ctrlKey && e.key === 's') {
        e.preventDefault(); // Empêcher la sauvegarde du navigateur
        console.log('Ctrl+S');
    }
});


"""
EXEMPLE : Contrôler un élément au clavier
"""

// HTML
"""
<style>
#player {
    position: absolute;
    width: 50px;
    height: 50px;
    background: blue;
    left: 100px;
    top: 100px;
}
</style>
<div id="player"></div>
"""

// JavaScript
let player = document.querySelector('#player');
let vitesse = 10;

document.addEventListener('keydown', (e) => {
    let left = parseInt(player.style.left) || 100;
    let top = parseInt(player.style.top) || 100;
    
    switch(e.key) {
        case 'ArrowUp':
            player.style.top = (top - vitesse) + 'px';
            break;
        case 'ArrowDown':
            player.style.top = (top + vitesse) + 'px';
            break;
        case 'ArrowLeft':
            player.style.left = (left - vitesse) + 'px';
            break;
        case 'ArrowRight':
            player.style.left = (left + vitesse) + 'px';
            break;
    }
});


# ----------------------------------------------------------------------------
# [NOTE] ÉVÉNEMENTS DE FORMULAIRE
# ----------------------------------------------------------------------------

"""
ÉVÉNEMENTS INPUT
"""

let input = document.querySelector('#monInput');

// input : Valeur change (chaque caractère)
input.addEventListener('input', (e) => {
    console.log('Valeur:', e.target.value);
});

// change : Valeur change ET élément perd le focus
input.addEventListener('change', (e) => {
    console.log('Changement:', e.target.value);
});

// focus : Élément reçoit le focus
input.addEventListener('focus', (e) => {
    console.log('Focus gagné');
    e.target.style.background = 'lightyellow';
});

// blur : Élément perd le focus
input.addEventListener('blur', (e) => {
    console.log('Focus perdu');
    e.target.style.background = 'white';
});

"""
[IDEE] input vs change

input : Se déclenche à chaque modification
change : Se déclenche seulement au blur après modification


ÉVÉNEMENT SUBMIT (FORMULAIRES)
"""

// HTML
"""
<form id="monForm">
    <input type="text" name="nom" required>
    <input type="email" name="email" required>
    <button type="submit">Envoyer</button>
</form>
"""

// JavaScript
let form = document.querySelector('#monForm');

form.addEventListener('submit', (e) => {
    e.preventDefault(); // [ATTENTION] IMPORTANT : Empêcher rechargement page
    
    // Récupérer les valeurs
    let formData = new FormData(form);
    let nom = formData.get('nom');
    let email = formData.get('email');
    
    console.log('Nom:', nom);
    console.log('Email:', email);
    
    // Traiter les données...
});

"""
[IDEE] e.preventDefault()

Empêche le comportement par défaut :
- Formulaire : Empêche rechargement page
- Lien : Empêche navigation
- Touche : Empêche action par défaut


VALIDATION DE FORMULAIRE
"""

let form = document.querySelector('#form');
let nom = document.querySelector('#nom');
let email = document.querySelector('#email');

form.addEventListener('submit', (e) => {
    e.preventDefault();
    
    // Réinitialiser erreurs
    document.querySelectorAll('.erreur').forEach(el => el.remove());
    
    let valide = true;
    
    // Valider nom
    if (nom.value.trim() === '') {
        afficherErreur(nom, 'Le nom est requis');
        valide = false;
    }
    
    // Valider email
    if (!validerEmail(email.value)) {
        afficherErreur(email, 'Email invalide');
        valide = false;
    }
    
    if (valide) {
        console.log('Formulaire valide !');
        // Soumettre...
    }
});

function afficherErreur(input, message) {
    let erreur = document.createElement('div');
    erreur.className = 'erreur';
    erreur.style.color = 'red';
    erreur.textContent = message;
    input.parentElement.appendChild(erreur);
}

function validerEmail(email) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}


# ----------------------------------------------------------------------------
# [BALLOON] EVENT BUBBLING ET CAPTURING
# ----------------------------------------------------------------------------

"""
EVENT PROPAGATION

Quand un événement se produit, il se propage dans le DOM :

1. CAPTURING (capture) : De document vers l'élément
2. TARGET : Sur l'élément cible
3. BUBBLING (remontée) : De l'élément vers document


ILLUSTRATION
"""

// HTML
"""
<div id="grand-parent">
    <div id="parent">
        <button id="enfant">Clic</button>
    </div>
</div>
"""

// Bubbling (par défaut) : enfant -> parent -> grand-parent
document.querySelector('#grand-parent').addEventListener('click', () => {
    console.log('Grand-parent');
});
document.querySelector('#parent').addEventListener('click', () => {
    console.log('Parent');
});
document.querySelector('#enfant').addEventListener('click', () => {
    console.log('Enfant');
});

// Résultat du clic sur bouton :
// Enfant
// Parent
// Grand-parent


"""
CAPTURING (PHASE CAPTURE)
"""

// Avec { capture: true }
document.querySelector('#grand-parent').addEventListener('click', () => {
    console.log('Grand-parent');
}, { capture: true });

document.querySelector('#parent').addEventListener('click', () => {
    console.log('Parent');
}, { capture: true });

document.querySelector('#enfant').addEventListener('click', () => {
    console.log('Enfant');
}, { capture: true });

// Résultat : ordre inversé
// Grand-parent
// Parent
// Enfant


"""
STOPPER LA PROPAGATION
"""

// stopPropagation() : Arrête la propagation
document.querySelector('#enfant').addEventListener('click', (e) => {
    console.log('Enfant');
    e.stopPropagation(); // Les parents ne recevront pas l'événement
});

// stopImmediatePropagation() : Arrête aussi les autres listeners sur le même élément
element.addEventListener('click', (e) => {
    console.log('Premier listener');
    e.stopImmediatePropagation();
});
element.addEventListener('click', () => {
    console.log('Deuxième listener'); // Ne s'exécute pas
});


# ----------------------------------------------------------------------------
# [OBJECTIF] EVENT DELEGATION
# ----------------------------------------------------------------------------

"""
EVENT DELEGATION

Technique : Attacher listener sur parent au lieu de tous les enfants
Utilise event bubbling


POURQUOI ?

[OK] Performances : Un seul listener au lieu de multiples
[OK] Dynamique : Fonctionne avec éléments ajoutés après
[OK] Moins de mémoire


EXEMPLE SANS DELEGATION (inefficace)
"""

// HTML
"""
<ul id="liste">
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
</ul>
"""

// [X] Mauvaise approche : Listener sur chaque <li>
let items = document.querySelectorAll('#liste li');
items.forEach(item => {
    item.addEventListener('click', () => {
        console.log('Cliqué:', item.textContent);
    });
});

// Problème : Les nouveaux <li> n'auront pas le listener !


"""
AVEC DELEGATION (efficace)
"""

// [OK] Bonne approche : Listener sur le parent
let liste = document.querySelector('#liste');

liste.addEventListener('click', (e) => {
    // Vérifier que c'est un <li> qui a été cliqué
    if (e.target.tagName === 'LI') {
        console.log('Cliqué:', e.target.textContent);
    }
});

// Fonctionne même avec des <li> ajoutés dynamiquement !


"""
EXEMPLE AVANCÉ : Todo List avec delegation
"""

let todoList = document.querySelector('#todoList');

// Un seul listener pour tous les boutons
todoList.addEventListener('click', (e) => {
    // Bouton supprimer
    if (e.target.classList.contains('btn-supprimer')) {
        e.target.parentElement.remove();
    }
    
    // Checkbox
    if (e.target.type === 'checkbox') {
        e.target.parentElement.classList.toggle('completed');
    }
});

// Ajouter des tâches (listeners automatiquement fonctionnels)
function ajouterTache(texte) {
    let li = document.createElement('li');
    li.innerHTML = `
        <input type="checkbox">
        <span>${texte}</span>
        <button class="btn-supprimer">X</button>
    `;
    todoList.appendChild(li);
}


"""
DELEGATION AVEC matches()
"""

// Sélecteur CSS plus flexible
element.addEventListener('click', (e) => {
    // Si c'est un bouton de classe 'delete'
    if (e.target.matches('button.delete')) {
        console.log('Bouton delete cliqué');
    }
    
    // Si c'est un lien dans .menu
    if (e.target.matches('.menu a')) {
        e.preventDefault();
        console.log('Lien menu cliqué');
    }
});


"""
DELEGATION AVEC closest()
"""

// Trouver l'ancêtre le plus proche
element.addEventListener('click', (e) => {
    // Trouver le .card parent le plus proche
    let card = e.target.closest('.card');
    
    if (card) {
        console.log('Card cliquée:', card.dataset.id);
    }
});


# ----------------------------------------------------------------------------
# [STOP] PRÉVENIR LES COMPORTEMENTS PAR DÉFAUT
# ----------------------------------------------------------------------------

"""
preventDefault()

Empêche l'action par défaut du navigateur
"""

// Empêcher navigation sur lien
let lien = document.querySelector('a');
lien.addEventListener('click', (e) => {
    e.preventDefault();
    console.log('Lien cliqué mais pas suivi');
});

// Empêcher soumission formulaire
let form = document.querySelector('form');
form.addEventListener('submit', (e) => {
    e.preventDefault();
    console.log('Formulaire pas soumis');
});

// Empêcher clic droit
document.addEventListener('contextmenu', (e) => {
    e.preventDefault();
    console.log('Clic droit bloqué');
});

// Empêcher Ctrl+S
document.addEventListener('keydown', (e) => {
    if (e.ctrlKey && e.key === 's') {
        e.preventDefault();
        console.log('Sauvegarde personnalisée');
    }
});


"""
RETOURNER false (ancienne méthode)
"""

// Avec addEventListener, return false ne fait RIEN
element.addEventListener('click', (e) => {
    return false; // [X] N'empêche pas le comportement par défaut
});

// Fonctionne seulement avec onclick
element.onclick = function(e) {
    return false; // [OK] Équivaut à preventDefault() + stopPropagation()
};


# ----------------------------------------------------------------------------
# [COURS] EXERCICES PRATIQUES : ÉVÉNEMENTS
# ----------------------------------------------------------------------------

"""
EXERCICE 1 : Calculatrice interactive

HTML :
<div class="calculatrice">
    <input type="text" id="affichage" readonly>
    <div class="boutons">
        <button data-valeur="7">7</button>
        <button data-valeur="8">8</button>
        <button data-valeur="9">9</button>
        <button data-valeur="/">/</button>
        <!-- ... autres boutons ... -->
        <button data-valeur="=">=</button>
        <button data-valeur="C">C</button>
    </div>
</div>
"""

// Solution
let affichage = document.querySelector('#affichage');
let boutons = document.querySelector('.boutons');

// Event delegation
boutons.addEventListener('click', (e) => {
    if (e.target.tagName !== 'BUTTON') return;
    
    let valeur = e.target.dataset.valeur;
    
    if (valeur === 'C') {
        affichage.value = '';
    } else if (valeur === '=') {
        try {
            affichage.value = eval(affichage.value);
        } catch {
            affichage.value = 'Erreur';
        }
    } else {
        affichage.value += valeur;
    }
});


"""
EXERCICE 2 : Modal avec Escape

HTML :
<button id="ouvrirModal">Ouvrir Modal</button>
<div id="modal" class="modal">
    <div class="modal-contenu">
        <span class="fermer">&times;</span>
        <h2>Titre Modal</h2>
        <p>Contenu...</p>
    </div>
</div>
"""

// Solution
let modal = document.querySelector('#modal');
let btnOuvrir = document.querySelector('#ouvrirModal');
let btnFermer = document.querySelector('.fermer');

function ouvrirModal() {
    modal.style.display = 'block';
    document.body.style.overflow = 'hidden'; // Bloquer scroll
}

function fermerModal() {
    modal.style.display = 'none';
    document.body.style.overflow = 'auto';
}

// Ouvrir
btnOuvrir.addEventListener('click', ouvrirModal);

// Fermer avec X
btnFermer.addEventListener('click', fermerModal);

// Fermer avec clic extérieur
modal.addEventListener('click', (e) => {
    if (e.target === modal) {
        fermerModal();
    }
});

// Fermer avec Escape
document.addEventListener('keydown', (e) => {
    if (e.key === 'Escape' && modal.style.display === 'block') {
        fermerModal();
    }
});


"""
EXERCICE 3 : Filtrage en temps réel

HTML :
<input type="text" id="recherche" placeholder="Rechercher...">
<ul id="liste">
    <li>Apple</li>
    <li>Banana</li>
    <li>Cherry</li>
    <li>Date</li>
    <li>Elderberry</li>
</ul>
"""

// Solution
let recherche = document.querySelector('#recherche');
let items = document.querySelectorAll('#liste li');

recherche.addEventListener('input', (e) => {
    let terme = e.target.value.toLowerCase();
    
    items.forEach(item => {
        let texte = item.textContent.toLowerCase();
        
        if (texte.includes(terme)) {
            item.style.display = 'block';
        } else {
            item.style.display = 'none';
        }
    });
});


# ============================================================================
# [DOCS] RÉCAPITULATIF CHAPITRE 10 : ÉVÉNEMENTS
# ============================================================================

"""
[BRAVO] CHAPITRE 10 TERMINÉ !

VOUS MAÎTRISEZ MAINTENANT :

[OK] addEventListener() et removeEventListener()
[OK] L'objet Event et ses propriétés
[OK] Événements souris (click, mousemove, etc.)
[OK] Événements clavier (keydown, keyup)
[OK] Événements formulaires (input, change, submit)
[OK] Event bubbling et capturing
[OK] Event delegation pour performances
[OK] preventDefault() et stopPropagation()


[CLE] BONNES PRATIQUES :

1. Toujours utiliser addEventListener()
2. Utiliser event delegation pour listes dynamiques
3. preventDefault() pour contrôler comportements
4. Retirer listeners inutilisés (removeEventListener)
5. Éviter listeners inline dans HTML


-> SUITE : CHAPITRE 11 - PROGRAMMATION ASYNCHRONE

Prochains sujets :
- Callbacks
- Promises
- Async/Await
- Fetch API


[GUIDE] FICHIER SUIVANT : javascript_partie3_async.txt
"""
# ============================================================================
# [LIVRE] JAVASCRIPT - PARTIE 3 (SUITE) : PROGRAMMATION ASYNCHRONE
# ============================================================================
#
# [OBJECTIF] CHAPITRE 11 : PROGRAMMATION ASYNCHRONE
#
# [TEMPS] TEMPS : ~4-5 heures
# [DOCS] PRÉREQUIS : Chapitres précédents complétés
# ============================================================================


# ============================================================================
# [GUIDE] CHAPITRE 11 : PROGRAMMATION ASYNCHRONE
# ============================================================================

"""
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Comprendre le JavaScript asynchrone
[OK] Utiliser les callbacks
[OK] Maîtriser les Promises
[OK] Utiliser async/await
[OK] Gérer les erreurs asynchrones
[OK] Faire des requêtes avec fetch()
[OK] Exécuter du code en parallèle
"""


# ----------------------------------------------------------------------------
# [TEMPS] COMPRENDRE L'ASYNCHRONE
# ----------------------------------------------------------------------------

"""
JAVASCRIPT = SINGLE-THREADED

JavaScript s'exécute dans un seul fil (thread)
Une seule chose à la fois


ANALOGIE [FORK_AND_KNIFE_WITH_PLATE] Restaurant

SYNCHRONE (Bloquant) :
- Serveur prend commande 1
- Va en cuisine, attend plat
- Revient, sert
- Puis seulement commande 2
-> Très lent !

ASYNCHRONE (Non-bloquant) :
- Serveur prend commande 1
- Donne à la cuisine
- Prend commande 2 pendant que cuisine prépare
- Sert quand prêt
-> Efficace !


CODE SYNCHRONE vs ASYNCHRONE
"""

// SYNCHRONE (bloquant)
console.log('1. Début');

function tachemLongue() {
    // Boucle bloquante (BAD!)
    for (let i = 0; i < 1000000000; i++) {}
    console.log('2. Tâche longue terminée');
}

tachemLongue();
console.log('3. Fin');

// Résultat : 1 -> 2 -> 3 (dans l'ordre, mais bloque tout)


// ASYNCHRONE (non-bloquant)
console.log('1. Début');

setTimeout(() => {
    console.log('2. Tâche asynchrone terminée');
}, 2000);

console.log('3. Fin');

// Résultat : 1 -> 3 -> 2 (3 s'exécute avant 2!)

"""
[IDEE] POURQUOI ASYNCHRONE ?

- Requêtes réseau (fetch)
- Lecture fichiers
- Timers (setTimeout, setInterval)
- Opérations base de données
-> Ne pas bloquer l'interface utilisateur


EVENT LOOP

Mécanisme qui gère l'asynchrone en JavaScript
"""

/*
┌─────────────────┐
│   Call Stack    │ <- Code en cours d'exécution
└─────────────────┘
        v
┌─────────────────┐
│   Web APIs      │ <- setTimeout, fetch, DOM events
└─────────────────┘
        v
┌─────────────────┐
│   Task Queue    │ <- File d'attente des callbacks
└─────────────────┘
        v
    Event Loop <- Vérifie si call stack vide, puis exécute queue
*/


# ----------------------------------------------------------------------------
# [BACK_WITH_LEFTWARDS_ARROW_ABOVE] CALLBACKS
# ----------------------------------------------------------------------------

"""
CALLBACK = Fonction passée en argument

Appelée quand opération asynchrone termine
"""

// Exemple simple
function faireQuelqueChose(callback) {
    console.log('Travail en cours...');
    setTimeout(() => {
        console.log('Travail terminé');
        callback(); // Appeler le callback
    }, 2000);
}

faireQuelqueChose(() => {
    console.log('Callback exécuté !');
});

/*
Résultat :
Travail en cours...
(après 2 secondes)
Travail terminé
Callback exécuté !
*/


"""
CALLBACKS AVEC PARAMÈTRES
"""

function chargerUtilisateur(id, callback) {
    console.log('Chargement utilisateur...');
    
    setTimeout(() => {
        let user = { id: id, nom: 'Alice' };
        callback(user);
    }, 1000);
}

chargerUtilisateur(1, (user) => {
    console.log('Utilisateur chargé:', user);
});


"""
GESTION D'ERREURS AVEC CALLBACKS

Convention Node.js : callback(error, result)
"""

function chargerDonnees(callback) {
    setTimeout(() => {
        let erreur = false;
        
        if (erreur) {
            callback('Erreur de chargement', null);
        } else {
            callback(null, { data: 'Données' });
        }
    }, 1000);
}

chargerDonnees((err, data) => {
    if (err) {
        console.error('Erreur:', err);
    } else {
        console.log('Données:', data);
    }
});


"""
CALLBACK HELL (Pyramide de la doom)

[X] Problème : Callbacks imbriqués illisibles
"""

// Charger utilisateur -> puis posts -> puis commentaires
chargerUtilisateur(1, (user) => {
    chargerPosts(user.id, (posts) => {
        chargerCommentaires(posts[0].id, (commentaires) => {
            afficherCommentaires(commentaires, () => {
                console.log('Tout chargé !');
            });
        });
    });
});

// Difficile à lire et maintenir !


# ----------------------------------------------------------------------------
# [ACCORD] PROMISES (ES6)
# ----------------------------------------------------------------------------

"""
PROMISE = Objet représentant une opération asynchrone

États :
- pending (en attente)
- fulfilled (réussie)
- rejected (échouée)


CRÉER UNE PROMISE
"""

let maPromise = new Promise((resolve, reject) => {
    // Opération asynchrone
    setTimeout(() => {
        let succes = true;
        
        if (succes) {
            resolve('Succès !'); // Résolution
        } else {
            reject('Erreur !');  // Rejet
        }
    }, 2000);
});

"""
[IDEE] ANATOMIE PROMISE

new Promise((resolve, reject) => {
    // resolve(valeur) : Succès
    // reject(erreur) : Échec
})


CONSOMMER UNE PROMISE
"""

maPromise
    .then(resultat => {
        console.log('Résultat:', resultat);
    })
    .catch(erreur => {
        console.error('Erreur:', erreur);
    })
    .finally(() => {
        console.log('Terminé (succès ou échec)');
    });

"""
[IDEE] MÉTHODES PROMISE

.then(callback) : Exécuté si resolve()
.catch(callback) : Exécuté si reject()
.finally(callback) : Toujours exécuté


EXEMPLE PRATIQUE : Charger utilisateur
"""

function chargerUtilisateur(id) {
    return new Promise((resolve, reject) => {
        console.log('Chargement utilisateur...');
        
        setTimeout(() => {
            if (id > 0) {
                resolve({ id: id, nom: 'Alice', age: 25 });
            } else {
                reject('ID invalide');
            }
        }, 1000);
    });
}

// Utilisation
chargerUtilisateur(1)
    .then(user => {
        console.log('Utilisateur:', user);
    })
    .catch(err => {
        console.error('Erreur:', err);
    });


"""
CHAÎNER LES PROMISES

Résout le callback hell !
"""

// [OK] Chaînage propre
chargerUtilisateur(1)
    .then(user => {
        console.log('User:', user);
        return chargerPosts(user.id); // Retourne nouvelle Promise
    })
    .then(posts => {
        console.log('Posts:', posts);
        return chargerCommentaires(posts[0].id);
    })
    .then(commentaires => {
        console.log('Commentaires:', commentaires);
    })
    .catch(err => {
        console.error('Erreur:', err); // Capture toutes les erreurs
    });

"""
[IDEE] RÈGLES CHAÎNAGE

1. .then() retourne toujours une Promise
2. Si on retourne une valeur -> Promise résolue avec cette valeur
3. Si on retourne une Promise -> attente de sa résolution
4. .catch() capture erreurs de toute la chaîne


TRANSFORMER LES DONNÉES
"""

chargerUtilisateur(1)
    .then(user => {
        // Transformer les données
        return user.nom.toUpperCase();
    })
    .then(nomMajuscule => {
        console.log(nomMajuscule); // 'ALICE'
    });


"""
PROMISE.ALL() - Exécuter en parallèle

Attend que TOUTES les Promises soient résolues
"""

let promise1 = Promise.resolve(3);
let promise2 = new Promise(resolve => setTimeout(() => resolve('Fait'), 1000));
let promise3 = new Promise(resolve => setTimeout(() => resolve('Terminé'), 2000));

Promise.all([promise1, promise2, promise3])
    .then(resultats => {
        console.log(resultats); // [3, 'Fait', 'Terminé']
    })
    .catch(err => {
        console.error('Une des promises a échoué:', err);
    });

"""
[IDEE] Promise.all()

- Attend TOUTES les Promises
- Si UNE échoue -> toute la chaîne échoue
- Retourne tableau des résultats


EXEMPLE : Charger plusieurs utilisateurs
"""

function chargerUtilisateur(id) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve({ id, nom: `User${id}` });
        }, 1000);
    });
}

let promises = [
    chargerUtilisateur(1),
    chargerUtilisateur(2),
    chargerUtilisateur(3)
];

Promise.all(promises)
    .then(users => {
        console.log('Tous les users:', users);
        // [{ id: 1, nom: 'User1' }, { id: 2, nom: 'User2' }, ...]
    });


"""
PROMISE.ALLSETTLED() - Attendre toutes (succès ou échec)
"""

let promises = [
    Promise.resolve('Succès 1'),
    Promise.reject('Erreur 2'),
    Promise.resolve('Succès 3')
];

Promise.allSettled(promises)
    .then(resultats => {
        console.log(resultats);
        /*
        [
            { status: 'fulfilled', value: 'Succès 1' },
            { status: 'rejected', reason: 'Erreur 2' },
            { status: 'fulfilled', value: 'Succès 3' }
        ]
        */
    });


"""
PROMISE.RACE() - Première qui termine

Retourne dès qu'UNE Promise se résout/rejette
"""

let promise1 = new Promise(resolve => setTimeout(() => resolve('Un'), 1000));
let promise2 = new Promise(resolve => setTimeout(() => resolve('Deux'), 500));

Promise.race([promise1, promise2])
    .then(result => {
        console.log(result); // 'Deux' (plus rapide)
    });

// Utile pour timeout
let requete = fetch('https://api.example.com/data');
let timeout = new Promise((_, reject) => 
    setTimeout(() => reject('Timeout'), 5000)
);

Promise.race([requete, timeout])
    .then(data => console.log('Données reçues'))
    .catch(err => console.error(err)); // 'Timeout' si trop long


"""
PROMISE.ANY() - Première qui RÉUSSIT (ES2021)
"""

let promises = [
    Promise.reject('Erreur 1'),
    Promise.reject('Erreur 2'),
    Promise.resolve('Succès 3')
];

Promise.any(promises)
    .then(result => {
        console.log(result); // 'Succès 3'
    })
    .catch(err => {
        console.error('Toutes ont échoué');
    });


# ----------------------------------------------------------------------------
# [RAPIDE] ASYNC/AWAIT (ES2017)
# ----------------------------------------------------------------------------

"""
ASYNC/AWAIT = Syntaxe plus simple pour Promises

Code asynchrone qui RESSEMBLE à du code synchrone !
"""

// Avec Promises (ancien)
function obtenirUtilisateur() {
    return chargerUtilisateur(1)
        .then(user => {
            console.log(user);
            return user;
        })
        .catch(err => {
            console.error(err);
        });
}

// Avec async/await (moderne)
async function obtenirUtilisateur() {
    try {
        let user = await chargerUtilisateur(1);
        console.log(user);
        return user;
    } catch (err) {
        console.error(err);
    }
}

"""
[IDEE] ASYNC/AWAIT

async : Déclare fonction asynchrone
await : Attend qu'une Promise se résolve
try/catch : Gère les erreurs


RÈGLES ASYNC/AWAIT
"""

// 1. async rend fonction asynchrone
async function maFonction() {
    return 'Bonjour';
}

// Équivalent à :
function maFonction() {
    return Promise.resolve('Bonjour');
}

// 2. await SEULEMENT dans fonction async
async function exemple() {
    let result = await maPromise; // [OK] OK
}

function exemple() {
    let result = await maPromise; // [X] Erreur !
}


"""
EXEMPLE SIMPLE
"""

function attendre(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

async function demo() {
    console.log('Début');
    
    await attendre(2000); // Attend 2 secondes
    console.log('2 secondes passées');
    
    await attendre(1000); // Attend 1 seconde
    console.log('1 seconde de plus');
    
    return 'Terminé';
}

demo().then(result => console.log(result));

/*
Résultat :
Début
(2 secondes)
2 secondes passées
(1 seconde)
1 seconde de plus
Terminé
*/


"""
CHAÎNER AVEC ASYNC/AWAIT
"""

// Callbacks imbriqués (callback hell)
chargerUtilisateur(1, (user) => {
    chargerPosts(user.id, (posts) => {
        chargerCommentaires(posts[0].id, (comments) => {
            console.log(comments);
        });
    });
});

// Avec Promises
chargerUtilisateur(1)
    .then(user => chargerPosts(user.id))
    .then(posts => chargerCommentaires(posts[0].id))
    .then(comments => console.log(comments));

// Avec async/await (le plus lisible!)
async function chargerTout() {
    let user = await chargerUtilisateur(1);
    let posts = await chargerPosts(user.id);
    let comments = await chargerCommentaires(posts[0].id);
    console.log(comments);
}


"""
GESTION D'ERREURS
"""

async function obtenirDonnees() {
    try {
        let user = await chargerUtilisateur(1);
        let posts = await chargerPosts(user.id);
        return posts;
    } catch (erreur) {
        console.error('Erreur:', erreur);
        return null;
    } finally {
        console.log('Nettoyage');
    }
}


"""
AWAIT EN PARALLÈLE

[ATTENTION] await exécute séquentiellement par défaut
"""

// [X] Séquentiel (lent)
async function chargerTout() {
    let user1 = await chargerUtilisateur(1);   // 1 seconde
    let user2 = await chargerUtilisateur(2);   // 1 seconde
    let user3 = await chargerUtilisateur(3);   // 1 seconde
    // Total : 3 secondes
}

// [OK] Parallèle (rapide)
async function chargerTout() {
    let [user1, user2, user3] = await Promise.all([
        chargerUtilisateur(1),
        chargerUtilisateur(2),
        chargerUtilisateur(3)
    ]);
    // Total : 1 seconde (parallèle)
}

"""
[IDEE] RÈGLE :

Opérations INDÉPENDANTES -> Promise.all()
Opérations DÉPENDANTES -> await séquentiel


ASYNC/AWAIT AVEC BOUCLES
"""

// [X] Ne fonctionne pas (forEach n'attend pas)
let ids = [1, 2, 3];
ids.forEach(async (id) => {
    let user = await chargerUtilisateur(id);
    console.log(user);
});

// [OK] Avec for...of (attend)
for (let id of ids) {
    let user = await chargerUtilisateur(id);
    console.log(user);
}

// [OK] En parallèle avec map + Promise.all
let promises = ids.map(id => chargerUtilisateur(id));
let users = await Promise.all(promises);


# ----------------------------------------------------------------------------
# [WEB] FETCH API
# ----------------------------------------------------------------------------

"""
FETCH = API moderne pour requêtes HTTP

Retourne une Promise
"""

// GET simple
fetch('https://api.example.com/users')
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(err => console.error(err));

// Avec async/await (plus lisible)
async function obtenirUtilisateurs() {
    try {
        let response = await fetch('https://api.example.com/users');
        let data = await response.json();
        console.log(data);
    } catch (err) {
        console.error('Erreur:', err);
    }
}


"""
VÉRIFIER LE STATUT
"""

async function obtenirDonnees(url) {
    try {
        let response = await fetch(url);
        
        // Vérifier si succès
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }
        
        let data = await response.json();
        return data;
    } catch (err) {
        console.error('Erreur:', err);
    }
}

"""
[IDEE] RESPONSE

response.ok : true si status 200-299
response.status : Code HTTP (200, 404, 500...)
response.statusText : Message ('OK', 'Not Found'...)


REQUÊTE POST
"""

async function creerUtilisateur(user) {
    try {
        let response = await fetch('https://api.example.com/users', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(user)
        });
        
        let data = await response.json();
        return data;
    } catch (err) {
        console.error('Erreur:', err);
    }
}

// Utilisation
creerUtilisateur({
    nom: 'Alice',
    email: 'alice@example.com'
});


"""
AUTRES MÉTHODES HTTP
"""

// PUT (mise à jour complète)
fetch('/api/users/1', {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ nom: 'Alice', age: 26 })
});

// PATCH (mise à jour partielle)
fetch('/api/users/1', {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ age: 26 })
});

// DELETE
fetch('/api/users/1', {
    method: 'DELETE'
});


"""
HEADERS ET AUTHENTIFICATION
"""

// Token Bearer
fetch('/api/data', {
    headers: {
        'Authorization': 'Bearer ' + token,
        'Content-Type': 'application/json'
    }
});

// API Key
fetch('/api/data', {
    headers: {
        'X-API-Key': apiKey
    }
});


"""
EXEMPLE COMPLET : API TODO
"""

const API_URL = 'https://jsonplaceholder.typicode.com/todos';

// Obtenir tous les todos
async function obtenirTodos() {
    try {
        let response = await fetch(API_URL);
        if (!response.ok) throw new Error('Erreur réseau');
        let todos = await response.json();
        return todos;
    } catch (err) {
        console.error('Erreur:', err);
        return [];
    }
}

// Créer un todo
async function creerTodo(todo) {
    try {
        let response = await fetch(API_URL, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(todo)
        });
        let newTodo = await response.json();
        return newTodo;
    } catch (err) {
        console.error('Erreur:', err);
    }
}

// Mettre à jour un todo
async function mettreAJourTodo(id, updates) {
    try {
        let response = await fetch(`${API_URL}/${id}`, {
            method: 'PATCH',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(updates)
        });
        return await response.json();
    } catch (err) {
        console.error('Erreur:', err);
    }
}

// Supprimer un todo
async function supprimerTodo(id) {
    try {
        await fetch(`${API_URL}/${id}`, {
            method: 'DELETE'
        });
        return true;
    } catch (err) {
        console.error('Erreur:', err);
        return false;
    }
}

// Utilisation
async function demo() {
    // Obtenir
    let todos = await obtenirTodos();
    console.log('Todos:', todos.slice(0, 3));
    
    // Créer
    let nouveau = await creerTodo({
        userId: 1,
        title: 'Nouvelle tâche',
        completed: false
    });
    console.log('Créé:', nouveau);
    
    // Mettre à jour
    let updated = await mettreAJourTodo(1, { completed: true });
    console.log('Mis à jour:', updated);
    
    // Supprimer
    let deleted = await supprimerTodo(1);
    console.log('Supprimé:', deleted);
}


# ----------------------------------------------------------------------------
# [COURS] EXERCICES PRATIQUES : ASYNCHRONE
# ----------------------------------------------------------------------------

"""
EXERCICE 1 : Simulateur de chargement

Créer une fonction qui simule le chargement progressif
"""

// Solution
function chargerAvecProgression(duree) {
    return new Promise((resolve) => {
        let progression = 0;
        let interval = setInterval(() => {
            progression += 10;
            console.log(`Chargement: ${progression}%`);
            
            if (progression >= 100) {
                clearInterval(interval);
                resolve('Chargement terminé !');
            }
        }, duree / 10);
    });
}

// Utilisation
async function demo() {
    let result = await chargerAvecProgression(3000);
    console.log(result);
}


"""
EXERCICE 2 : Récupérer utilisateurs avec gestion d'erreurs

API : https://jsonplaceholder.typicode.com/users
"""

// Solution
async function obtenirUtilisateursAvecRetry(maxRetries = 3) {
    let tentatives = 0;
    
    while (tentatives < maxRetries) {
        try {
            let response = await fetch('https://jsonplaceholder.typicode.com/users');
            
            if (!response.ok) {
                throw new Error(`HTTP ${response.status}`);
            }
            
            let users = await response.json();
            return users;
        } catch (err) {
            tentatives++;
            console.log(`Tentative ${tentatives} échouée:`, err.message);
            
            if (tentatives >= maxRetries) {
                throw new Error('Nombre maximum de tentatives atteint');
            }
            
            // Attendre avant réessayer
            await new Promise(resolve => setTimeout(resolve, 1000));
        }
    }
}

// Utilisation
obtenirUtilisateursAvecRetry()
    .then(users => console.log('Utilisateurs:', users))
    .catch(err => console.error('Erreur finale:', err));


# ============================================================================
# [DOCS] RÉCAPITULATIF CHAPITRE 11 : ASYNCHRONE
# ============================================================================

"""
[BRAVO] CHAPITRE 11 TERMINÉ !

VOUS MAÎTRISEZ MAINTENANT :

[OK] Différence synchrone vs asynchrone
[OK] Callbacks et callback hell
[OK] Promises (création, chaînage)
[OK] Promise.all(), allSettled(), race(), any()
[OK] async/await syntaxe moderne
[OK] Gestion d'erreurs asynchrones
[OK] Fetch API pour requêtes HTTP
[OK] Exécution parallèle vs séquentielle


[CLE] BONNES PRATIQUES :

1. Préférer async/await aux Promises chainées
2. Toujours gérer les erreurs (try/catch)
3. Utiliser Promise.all() pour parallélisme
4. Vérifier response.ok avec fetch
5. Éviter await dans boucles si possible


[RAPIDE] PERFORMANCE :

Séquentiel (lent) :
let a = await op1();
let b = await op2();
let c = await op3();

Parallèle (rapide) :
let [a, b, c] = await Promise.all([op1(), op2(), op3()]);


-> SUITE : CHAPITRE 12 - MODULES ES6

Prochains sujets :
- Import/Export
- Modules
- Organisation du code


[GUIDE] FICHIER SUIVANT : javascript_partie3_modules.txt
"""
# ============================================================================
# [LIVRE] JAVASCRIPT - PARTIE 3 (SUITE) : MODULES ES6
# ============================================================================
#
# [OBJECTIF] CHAPITRE 12 : MODULES ES6
#
# [TEMPS] TEMPS : ~2-3 heures
# [DOCS] PRÉREQUIS : Chapitres précédents complétés
# ============================================================================


# ============================================================================
# [GUIDE] CHAPITRE 12 : MODULES ES6
# ============================================================================

"""
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Comprendre les modules JavaScript
[OK] Exporter et importer des fonctions
[OK] Utiliser export default
[OK] Importer sélectivement
[OK] Renommer imports/exports
[OK] Organiser votre code en modules
[OK] Utiliser les modules dans le navigateur
"""


# ----------------------------------------------------------------------------
# [PACKAGE] QU'EST-CE QU'UN MODULE ?
# ----------------------------------------------------------------------------

"""
MODULE = Fichier JavaScript réutilisable

AVANT LES MODULES :
- Tout dans un seul fichier
- Variables globales partout
- Difficile à maintenir
- Conflits de noms


AVEC LES MODULES :
- Code organisé en fichiers
- Encapsulation (pas de pollution globale)
- Réutilisable
- Facile à maintenir


ANALOGIE [CONSTRUCTION]

Sans modules : Une seule grande boîte avec tout mélangé
Avec modules : Tiroirs organisés, chaque chose à sa place
"""

"""
AVANT ES6 (CommonJS - Node.js)
"""

// module.exports (Node.js)
module.exports = {
    fonctionA: function() {},
    fonctionB: function() {}
};

// require()
const monModule = require('./monModule');


"""
AVEC ES6 (MODERNE)
"""

// export
export function fonctionA() {}
export function fonctionB() {}

// import
import { fonctionA, fonctionB } from './monModule.js';

"""
[IDEE] AVANTAGES MODULES ES6

[OK] Syntaxe standard
[OK] Import/export statiques (optimisation)
[OK] Support navigateur moderne
[OK] Tree-shaking (suppression code inutilisé)


# ----------------------------------------------------------------------------
# [SORTIE] EXPORT - EXPORTER DU CODE
# ----------------------------------------------------------------------------

"""
EXPORT NOMMÉ (Named Export)

Exporter plusieurs éléments d'un module
"""

// ====== math.js ======

// Export individuel
export function additionner(a, b) {
    return a + b;
}

export function soustraire(a, b) {
    return a - b;
}

export const PI = 3.14159;

export class Calculatrice {
    multiplier(a, b) {
        return a * b;
    }
}


"""
EXPORT GROUPÉ
"""

// ====== math.js ======

function additionner(a, b) {
    return a + b;
}

function soustraire(a, b) {
    return a - b;
}

const PI = 3.14159;

class Calculatrice {
    multiplier(a, b) {
        return a * b;
    }
}

// Exporter à la fin
export { additionner, soustraire, PI, Calculatrice };


"""
EXPORT DEFAULT

Un export par défaut par module
Utile pour l'export principal
"""

// ====== User.js ======

export default class User {
    constructor(nom, age) {
        this.nom = nom;
        this.age = age;
    }
    
    saluer() {
        return `Bonjour, je suis ${this.nom}`;
    }
}

// Ou avec fonction
// ====== utils.js ======
export default function formaterDate(date) {
    return date.toLocaleDateString();
}


"""
COMBINER DEFAULT ET NAMED EXPORTS
"""

// ====== api.js ======

// Export par défaut
export default class API {
    constructor(url) {
        this.url = url;
    }
}

// Exports nommés
export const API_URL = 'https://api.example.com';
export const TIMEOUT = 5000;

export function handleError(error) {
    console.error(error);
}


"""
RENOMMER À L'EXPORT
"""

// ====== math.js ======

function add(a, b) {
    return a + b;
}

function subtract(a, b) {
    return a - b;
}

// Renommer lors de l'export
export {
    add as additionner,
    subtract as soustraire
};


# ----------------------------------------------------------------------------
# [ENTREE] IMPORT - IMPORTER DU CODE
# ----------------------------------------------------------------------------

"""
IMPORT NOMMÉ
"""

// ====== math.js ======
export function additionner(a, b) {
    return a + b;
}
export const PI = 3.14159;

// ====== app.js ======
import { additionner, PI } from './math.js';

console.log(additionner(5, 3)); // 8
console.log(PI); // 3.14159


"""
IMPORT DEFAULT
"""

// ====== User.js ======
export default class User {
    constructor(nom) {
        this.nom = nom;
    }
}

// ====== app.js ======
import User from './User.js';

let user = new User('Alice');


"""
[IDEE] DIFFÉRENCE

Named import : import { nom } from '...'
Default import : import nom from '...' (pas de {})


IMPORTER TOUT (*)
"""

// ====== math.js ======
export function additionner(a, b) { return a + b; }
export function soustraire(a, b) { return a - b; }
export const PI = 3.14159;

// ====== app.js ======
import * as Math from './math.js';

console.log(Math.additionner(5, 3)); // 8
console.log(Math.PI); // 3.14159

"""
[IDEE] NAMESPACE IMPORT

* as nom : Tout importer dans un objet namespace
Évite les conflits de noms


RENOMMER À L'IMPORT
"""

// ====== math.js ======
export function add(a, b) { return a + b; }

// ====== app.js ======
import { add as addition } from './math.js';

console.log(addition(5, 3)); // 8


"""
COMBINER DEFAULT ET NAMED IMPORTS
"""

// ====== api.js ======
export default class API { }
export const API_URL = 'https://api.example.com';
export function handleError() { }

// ====== app.js ======
import API, { API_URL, handleError } from './api.js';

// Ou tout importer
import API, * as apiUtils from './api.js';


"""
IMPORT DYNAMIQUE (ES2020)

Charger modules à la demande
"""

// Import classique (statique)
import { fonction } from './module.js'; // Chargé au début

// Import dynamique (à la demande)
button.addEventListener('click', async () => {
    const module = await import('./module.js');
    module.fonction();
});

"""
[IDEE] IMPORT DYNAMIQUE

- Retourne une Promise
- Charge le module à l'exécution
- Utile pour code splitting
- Réduit bundle initial


EXEMPLE : Charger module selon condition
"""

async function chargerModule(type) {
    if (type === 'admin') {
        const admin = await import('./admin.js');
        admin.initAdmin();
    } else {
        const user = await import('./user.js');
        user.initUser();
    }
}


# ----------------------------------------------------------------------------
# [CONSTRUCTION] STRUCTURE DE PROJET AVEC MODULES
# ----------------------------------------------------------------------------

"""
ORGANISATION TYPIQUE
"""

"""
projet/
├── index.html
├── src/
│   ├── main.js          <- Point d'entrée
│   ├── utils/
│   │   ├── math.js
│   │   ├── string.js
│   │   └── date.js
│   ├── components/
│   │   ├── Header.js
│   │   ├── Footer.js
│   │   └── Card.js
│   ├── services/
│   │   ├── api.js
│   │   └── auth.js
│   └── models/
│       ├── User.js
│       └── Product.js
└── package.json
"""


"""
EXEMPLE CONCRET : APPLICATION TODO
"""

// ====== models/Todo.js ======
export default class Todo {
    constructor(titre) {
        this.id = Date.now();
        this.titre = titre;
        this.completed = false;
    }
    
    toggle() {
        this.completed = !this.completed;
    }
}


// ====== services/todoService.js ======
import Todo from '../models/Todo.js';

let todos = [];

export function ajouterTodo(titre) {
    const todo = new Todo(titre);
    todos.push(todo);
    return todo;
}

export function obtenirTodos() {
    return [...todos];
}

export function supprimerTodo(id) {
    todos = todos.filter(t => t.id !== id);
}

export function toggleTodo(id) {
    const todo = todos.find(t => t.id === id);
    if (todo) todo.toggle();
}


// ====== components/TodoList.js ======
export default class TodoList {
    constructor(container) {
        this.container = container;
    }
    
    render(todos) {
        this.container.innerHTML = '';
        
        todos.forEach(todo => {
            const li = document.createElement('li');
            li.textContent = todo.titre;
            li.className = todo.completed ? 'completed' : '';
            this.container.appendChild(li);
        });
    }
}


// ====== utils/dom.js ======
export function createElement(tag, className, textContent) {
    const element = document.createElement(tag);
    if (className) element.className = className;
    if (textContent) element.textContent = textContent;
    return element;
}

export function $(selector) {
    return document.querySelector(selector);
}

export function $$(selector) {
    return document.querySelectorAll(selector);
}


// ====== main.js ======
import TodoList from './components/TodoList.js';
import { ajouterTodo, obtenirTodos, toggleTodo } from './services/todoService.js';
import { $ } from './utils/dom.js';

const todoList = new TodoList($('#todoList'));
const input = $('#todoInput');
const btnAjouter = $('#btnAjouter');

btnAjouter.addEventListener('click', () => {
    if (input.value.trim()) {
        ajouterTodo(input.value);
        input.value = '';
        afficher();
    }
});

function afficher() {
    const todos = obtenirTodos();
    todoList.render(todos);
}

afficher();


# ----------------------------------------------------------------------------
# [WEB] UTILISER LES MODULES DANS LE NAVIGATEUR
# ----------------------------------------------------------------------------

"""
HTML POUR MODULES
"""

"""
<!DOCTYPE html>
<html>
<head>
    <title>App avec Modules</title>
</head>
<body>
    <div id="app"></div>
    
    <!-- [ATTENTION] IMPORTANT : type="module" -->
    <script type="module" src="main.js"></script>
</body>
</html>
"""

"""
[IDEE] ATTRIBUT type="module"

[OK] Active le mode module
[OK] Portée isolée (pas de pollution globale)
[OK] Defer automatique (chargé après DOM)
[OK] Strict mode par défaut


DIFFÉRENCES MODE MODULE
"""

// Script normal
<script src="app.js"></script>
// - Variables globales
// - Pas de import/export
// - Exécuté immédiatement

// Script module
<script type="module" src="app.js"></script>
// - Portée isolée
// - Import/export disponibles
// - Defer automatique
// - Strict mode

"""
CHEMINS RELATIFS OBLIGATOIRES
"""

// [X] Ne fonctionne pas
import { fonction } from 'module';

// [OK] Chemin relatif nécessaire
import { fonction } from './module.js';
import { fonction } from '../utils/module.js';

// [OK] Ou URL complète
import { fonction } from 'https://cdn.example.com/module.js';


"""
CORS ET MODULES

[ATTENTION] Modules soumis à la politique CORS
"""

// Impossible d'ouvrir fichier HTML directement (file://)
// [X] Ne fonctionne pas

// [OK] Nécessite serveur HTTP
// - Live Server (VS Code)
// - python -m http.server
// - npx serve


# ----------------------------------------------------------------------------
# [DOCS] PATTERNS COURANTS
# ----------------------------------------------------------------------------

"""
PATTERN 1 : BARREL EXPORTS

Regrouper exports dans index.js
"""

// ====== components/index.js ======
export { default as Header } from './Header.js';
export { default as Footer } from './Footer.js';
export { default as Card } from './Card.js';

// ====== app.js ======
// Au lieu de :
import Header from './components/Header.js';
import Footer from './components/Footer.js';
import Card from './components/Card.js';

// On peut faire :
import { Header, Footer, Card } from './components/index.js';


"""
PATTERN 2 : RE-EXPORT

Exporter depuis un autre module
"""

// ====== utils/index.js ======
export { additionner, soustraire } from './math.js';
export { capitaliser, inverser } from './string.js';
export { formaterDate } from './date.js';

// Maintenant tout est accessible depuis utils/index.js


"""
PATTERN 3 : CONFIGURATION CENTRALISÉE
"""

// ====== config.js ======
export const CONFIG = {
    API_URL: 'https://api.example.com',
    TIMEOUT: 5000,
    MAX_RETRY: 3,
    ITEMS_PER_PAGE: 20
};

export const ROUTES = {
    HOME: '/',
    USERS: '/users',
    PRODUCTS: '/products'
};

// ====== app.js ======
import { CONFIG, ROUTES } from './config.js';

fetch(CONFIG.API_URL + ROUTES.USERS);


"""
PATTERN 4 : SINGLETON

Instance unique partagée
"""

// ====== services/auth.js ======
class AuthService {
    constructor() {
        if (AuthService.instance) {
            return AuthService.instance;
        }
        
        this.user = null;
        this.token = null;
        AuthService.instance = this;
    }
    
    login(credentials) {
        // ...
    }
    
    logout() {
        // ...
    }
}

// Exporter instance unique
export default new AuthService();

// ====== app.js ======
import auth from './services/auth.js';

auth.login({ email: 'user@example.com', password: '123' });


"""
PATTERN 5 : FACTORY FUNCTION
"""

// ====== factories/createUser.js ======
export default function createUser(nom, age) {
    return {
        nom,
        age,
        saluer() {
            return `Bonjour, je suis ${this.nom}`;
        }
    };
}

// ====== app.js ======
import createUser from './factories/createUser.js';

const user = createUser('Alice', 25);


# ----------------------------------------------------------------------------
# [COURS] EXERCICES PRATIQUES : MODULES
# ----------------------------------------------------------------------------

"""
EXERCICE 1 : Bibliothèque utilitaire modulaire

Créer une bibliothèque d'utilitaires organisée en modules
"""

// Structure :
"""
utils/
├── index.js
├── array.js
├── string.js
├── number.js
└── date.js
"""

// ====== utils/array.js ======
export function shuffle(array) {
    return array.sort(() => Math.random() - 0.5);
}

export function unique(array) {
    return [...new Set(array)];
}

export function chunk(array, size) {
    const chunks = [];
    for (let i = 0; i < array.length; i += size) {
        chunks.push(array.slice(i, i + size));
    }
    return chunks;
}


// ====== utils/string.js ======
export function capitaliser(str) {
    return str.charAt(0).toUpperCase() + str.slice(1);
}

export function slugify(str) {
    return str
        .toLowerCase()
        .trim()
        .replace(/[^\w\s-]/g, '')
        .replace(/[\s_-]+/g, '-');
}

export function tronquer(str, longueur) {
    return str.length > longueur 
        ? str.slice(0, longueur) + '...' 
        : str;
}


// ====== utils/number.js ======
export function random(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

export function clamp(num, min, max) {
    return Math.min(Math.max(num, min), max);
}

export function arrondir(num, decimales = 2) {
    return Math.round(num * 10 ** decimales) / 10 ** decimales;
}


// ====== utils/date.js ======
export function formaterDate(date) {
    return new Intl.DateTimeFormat('fr-FR').format(date);
}

export function ajouterJours(date, jours) {
    const result = new Date(date);
    result.setDate(result.getDate() + jours);
    return result;
}

export function differenceJours(date1, date2) {
    const diff = Math.abs(date1 - date2);
    return Math.ceil(diff / (1000 * 60 * 60 * 24));
}


// ====== utils/index.js ======
export * as array from './array.js';
export * as string from './string.js';
export * as number from './number.js';
export * as date from './date.js';

// ====== app.js ======
import * as utils from './utils/index.js';

const nombres = [1, 2, 2, 3, 4, 4, 5];
console.log(utils.array.unique(nombres)); // [1, 2, 3, 4, 5]

const titre = 'hello world';
console.log(utils.string.capitaliser(titre)); // 'Hello world'

const prix = 19.999;
console.log(utils.number.arrondir(prix)); // 20


"""
EXERCICE 2 : Application MVC modulaire

Créer une petite app avec architecture MVC
"""

// ====== models/Task.js ======
export default class Task {
    constructor(titre) {
        this.id = Date.now() + Math.random();
        this.titre = titre;
        this.completed = false;
        this.createdAt = new Date();
    }
    
    toggle() {
        this.completed = !this.completed;
    }
}


// ====== models/TaskList.js ======
import Task from './Task.js';

export default class TaskList {
    constructor() {
        this.tasks = [];
    }
    
    add(titre) {
        const task = new Task(titre);
        this.tasks.push(task);
        return task;
    }
    
    remove(id) {
        this.tasks = this.tasks.filter(t => t.id !== id);
    }
    
    toggle(id) {
        const task = this.tasks.find(t => t.id === id);
        if (task) task.toggle();
    }
    
    getAll() {
        return [...this.tasks];
    }
    
    getActive() {
        return this.tasks.filter(t => !t.completed);
    }
    
    getCompleted() {
        return this.tasks.filter(t => t.completed);
    }
}


// ====== views/TaskView.js ======
export default class TaskView {
    constructor(container) {
        this.container = container;
    }
    
    render(tasks) {
        this.container.innerHTML = tasks.map(task => `
            <li class="task ${task.completed ? 'completed' : ''}" data-id="${task.id}">
                <input type="checkbox" ${task.completed ? 'checked' : ''}>
                <span>${task.titre}</span>
                <button class="delete">×</button>
            </li>
        `).join('');
    }
    
    bindToggle(handler) {
        this.container.addEventListener('change', (e) => {
            if (e.target.type === 'checkbox') {
                const id = parseFloat(e.target.closest('.task').dataset.id);
                handler(id);
            }
        });
    }
    
    bindDelete(handler) {
        this.container.addEventListener('click', (e) => {
            if (e.target.classList.contains('delete')) {
                const id = parseFloat(e.target.closest('.task').dataset.id);
                handler(id);
            }
        });
    }
}


// ====== controllers/TaskController.js ======
export default class TaskController {
    constructor(model, view) {
        this.model = model;
        this.view = view;
        
        // Bind events
        this.view.bindToggle(this.handleToggle.bind(this));
        this.view.bindDelete(this.handleDelete.bind(this));
        
        // Initial render
        this.updateView();
    }
    
    handleAdd(titre) {
        this.model.add(titre);
        this.updateView();
    }
    
    handleToggle(id) {
        this.model.toggle(id);
        this.updateView();
    }
    
    handleDelete(id) {
        this.model.remove(id);
        this.updateView();
    }
    
    updateView() {
        const tasks = this.model.getAll();
        this.view.render(tasks);
    }
}


// ====== app.js ======
import TaskList from './models/TaskList.js';
import TaskView from './views/TaskView.js';
import TaskController from './controllers/TaskController.js';

const app = new TaskController(
    new TaskList(),
    new TaskView(document.querySelector('#taskList'))
);

// Ajouter tâche
document.querySelector('#addBtn').addEventListener('click', () => {
    const input = document.querySelector('#taskInput');
    if (input.value.trim()) {
        app.handleAdd(input.value);
        input.value = '';
    }
});


# ============================================================================
# [DOCS] RÉCAPITULATIF CHAPITRE 12 : MODULES
# ============================================================================

"""
[BRAVO] CHAPITRE 12 TERMINÉ !

VOUS MAÎTRISEZ MAINTENANT :

[OK] Export nommé et export default
[OK] Import nommé et import default
[OK] Import namespace (*)
[OK] Renommer imports/exports
[OK] Import dynamique
[OK] Organisation en modules
[OK] Patterns courants (barrel, singleton, factory)


[CLE] BONNES PRATIQUES :

1. Un module = Une responsabilité
2. Utiliser barrel exports pour simplifier imports
3. Préférer named exports (plus explicite)
4. Éviter export default pour objets/valeurs
5. Organiser par fonctionnalité, pas par type


[DOSSIER] STRUCTURE RECOMMANDÉE :

src/
├── models/       <- Classes métier
├── views/        <- Interface utilisateur
├── controllers/  <- Logique application
├── services/     <- API, authentification
├── utils/        <- Fonctions utilitaires
└── config/       <- Configuration


[RAPIDE] ASTUCES :

// Barrel export
export * from './module.js';

// Re-export avec renommage
export { fonction as alias } from './module.js';

// Import dynamique
const module = await import('./module.js');


-> SUITE : CHAPITRE 13 - CLASSES ET POO

Prochains sujets :
- Classes ES6
- Héritage
- Encapsulation
- Polymorphisme


[GUIDE] FICHIER SUIVANT : javascript_partie3_classes.txt
"""
# ============================================================================
# [LIVRE] JAVASCRIPT - PARTIE 3 (SUITE) : CLASSES ET POO
# ============================================================================
#
# [OBJECTIF] CHAPITRE 13 : CLASSES ET PROGRAMMATION ORIENTÉE OBJET
#
# [TEMPS] TEMPS : ~4-5 heures
# [DOCS] PRÉREQUIS : Chapitres précédents complétés
# ============================================================================


# ============================================================================
# [GUIDE] CHAPITRE 13 : CLASSES ET POO
# ============================================================================

"""
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Créer et utiliser des classes
[OK] Comprendre constructeurs et méthodes
[OK] Utiliser l'héritage
[OK] Maîtriser les getters et setters
[OK] Utiliser les propriétés et méthodes statiques
[OK] Comprendre l'encapsulation
[OK] Appliquer les principes de la POO
"""


# ----------------------------------------------------------------------------
# [COURS] INTRODUCTION À LA POO
# ----------------------------------------------------------------------------

"""
POO = Programmation Orientée Objet

ANALOGIE [VOITURE] Voiture

CLASSE : Le plan de fabrication
OBJET : Une voiture construite selon ce plan
PROPRIÉTÉS : Couleur, marque, vitesse
MÉTHODES : Démarrer(), accélérer(), freiner()


AVANT LES CLASSES (Prototypes)
"""

// Fonction constructeur (ancien style)
function Voiture(marque, modele) {
    this.marque = marque;
    this.modele = modele;
}

Voiture.prototype.demarrer = function() {
    console.log('Démarrage de ' + this.marque);
};

let maVoiture = new Voiture('Toyota', 'Corolla');


"""
AVEC LES CLASSES ES6 (MODERNE)
"""

class Voiture {
    constructor(marque, modele) {
        this.marque = marque;
        this.modele = modele;
    }
    
    demarrer() {
        console.log(`Démarrage de ${this.marque}`);
    }
}

let maVoiture = new Voiture('Toyota', 'Corolla');

"""
[IDEE] AVANTAGES CLASSES ES6

[OK] Syntaxe plus claire
[OK] Plus proche des autres langages (Java, Python)
[OK] Moins d'erreurs
[OK] Meilleures performances


# ----------------------------------------------------------------------------
# [CONSTRUCTION] CRÉER UNE CLASSE
# ----------------------------------------------------------------------------

"""
SYNTAXE DE BASE
"""

class Personne {
    // Constructeur : appelé lors de la création
    constructor(nom, age) {
        this.nom = nom;
        this.age = age;
    }
    
    // Méthode
    saluer() {
        return `Bonjour, je suis ${this.nom}`;
    }
    
    avoirAnniversaire() {
        this.age++;
    }
}

// Créer une instance
let alice = new Personne('Alice', 25);

console.log(alice.nom);        // 'Alice'
console.log(alice.saluer());   // 'Bonjour, je suis Alice'
alice.avoirAnniversaire();
console.log(alice.age);        // 26

"""
[IDEE] ANATOMIE D'UNE CLASSE

class : Mot-clé
constructor() : Initialise l'objet
this : Référence à l'instance actuelle
Méthodes : Fonctions de la classe
new : Crée une nouvelle instance


CONSTRUCTEUR
"""

class Utilisateur {
    constructor(nom, email) {
        // Initialiser les propriétés
        this.nom = nom;
        this.email = email;
        this.dateInscription = new Date();
        this.actif = true;
        
        console.log('Utilisateur créé:', nom);
    }
}

let user = new Utilisateur('Bob', 'bob@example.com');

"""
[IDEE] CONSTRUCTEUR

- Méthode spéciale appelée automatiquement
- Exécuté une seule fois à la création
- Initialise les propriétés
- Peut avoir des paramètres
- Peut être omis si pas de logique d'initialisation


SANS CONSTRUCTEUR
"""

class Compteur {
    // Propriétés avec valeurs par défaut
    count = 0;
    
    incrementer() {
        this.count++;
    }
}

let compteur = new Compteur();


"""
VALEURS PAR DÉFAUT
"""

class Produit {
    constructor(nom, prix = 0, stock = 0) {
        this.nom = nom;
        this.prix = prix;
        this.stock = stock;
    }
}

let produit1 = new Produit('Laptop', 999, 5);
let produit2 = new Produit('Souris'); // prix=0, stock=0


"""
MÉTHODES
"""

class Calculatrice {
    constructor() {
        this.resultat = 0;
    }
    
    ajouter(n) {
        this.resultat += n;
        return this; // Pour chaînage
    }
    
    soustraire(n) {
        this.resultat -= n;
        return this;
    }
    
    multiplier(n) {
        this.resultat *= n;
        return this;
    }
    
    obtenirResultat() {
        return this.resultat;
    }
    
    reinitialiser() {
        this.resultat = 0;
        return this;
    }
}

// Utilisation
let calc = new Calculatrice();
let resultat = calc
    .ajouter(10)
    .multiplier(2)
    .soustraire(5)
    .obtenirResultat();

console.log(resultat); // 15

"""
[IDEE] CHAÎNAGE DE MÉTHODES

Retourner this permet d'appeler plusieurs méthodes d'affilée


# ----------------------------------------------------------------------------
# [OBJECTIF] GETTERS ET SETTERS
# ----------------------------------------------------------------------------

"""
GETTERS : Obtenir une valeur

Accédé comme une propriété (sans parenthèses)
"""

class Rectangle {
    constructor(largeur, hauteur) {
        this.largeur = largeur;
        this.hauteur = hauteur;
    }
    
    // Getter pour aire
    get aire() {
        return this.largeur * this.hauteur;
    }
    
    // Getter pour périmètre
    get perimetre() {
        return 2 * (this.largeur + this.hauteur);
    }
}

let rect = new Rectangle(5, 10);
console.log(rect.aire);      // 50 (pas de parenthèses!)
console.log(rect.perimetre); // 30

"""
SETTERS : Définir une valeur avec validation
"""

class Utilisateur {
    constructor(nom) {
        this._nom = nom; // _ = convention "privé"
        this._age = 0;
    }
    
    // Getter
    get age() {
        return this._age;
    }
    
    // Setter avec validation
    set age(valeur) {
        if (valeur < 0) {
            console.error('Âge ne peut pas être négatif');
            return;
        }
        if (valeur > 150) {
            console.error('Âge irréaliste');
            return;
        }
        this._age = valeur;
    }
    
    get nom() {
        return this._nom;
    }
    
    set nom(valeur) {
        if (valeur.length < 2) {
            console.error('Nom trop court');
            return;
        }
        this._nom = valeur;
    }
}

let user = new Utilisateur('Alice');
user.age = 25;      // Utilise le setter
console.log(user.age); // 25 (utilise le getter)

user.age = -5;      // Erreur : Âge ne peut pas être négatif
user.age = 200;     // Erreur : Âge irréaliste

"""
[IDEE] GETTERS/SETTERS

get : Lire une valeur (propriété calculée ou contrôlée)
set : Définir une valeur avec validation/transformation
_ : Convention pour propriétés "privées"


PROPRIÉTÉS CALCULÉES
"""

class Personne {
    constructor(prenom, nom) {
        this.prenom = prenom;
        this.nom = nom;
    }
    
    get nomComplet() {
        return `${this.prenom} ${this.nom}`;
    }
    
    set nomComplet(valeur) {
        [this.prenom, this.nom] = valeur.split(' ');
    }
}

let personne = new Personne('Alice', 'Dupont');
console.log(personne.nomComplet); // 'Alice Dupont'

personne.nomComplet = 'Bob Martin';
console.log(personne.prenom); // 'Bob'
console.log(personne.nom);    // 'Martin'


# ----------------------------------------------------------------------------
# [VERROUILLE] PROPRIÉTÉS ET MÉTHODES PRIVÉES (ES2022)
# ----------------------------------------------------------------------------

"""
PROPRIÉTÉS PRIVÉES

Préfixe # pour vraie privauté
"""

class CompteBancaire {
    // Propriété privée
    #solde = 0;
    
    constructor(soldeInitial) {
        this.#solde = soldeInitial;
    }
    
    // Méthode publique pour accéder au solde
    getSolde() {
        return this.#solde;
    }
    
    deposer(montant) {
        if (montant > 0) {
            this.#solde += montant;
        }
    }
    
    retirer(montant) {
        if (montant > 0 && montant <= this.#solde) {
            this.#solde -= montant;
            return true;
        }
        return false;
    }
}

let compte = new CompteBancaire(1000);
console.log(compte.getSolde()); // 1000

// console.log(compte.#solde); // [X] Erreur : Propriété privée !

compte.deposer(500);
console.log(compte.getSolde()); // 1500

"""
MÉTHODES PRIVÉES
"""

class Authentification {
    #token = null;
    
    // Méthode privée
    #genererToken() {
        return Math.random().toString(36).substr(2);
    }
    
    #validerMotDePasse(motDePasse) {
        return motDePasse.length >= 8;
    }
    
    // Méthode publique
    login(email, motDePasse) {
        if (!this.#validerMotDePasse(motDePasse)) {
            throw new Error('Mot de passe invalide');
        }
        
        this.#token = this.#genererToken();
        return this.#token;
    }
    
    logout() {
        this.#token = null;
    }
}

"""
[IDEE] PUBLIC vs PRIVÉ

PUBLIC : Accessible partout
- Propriétés normales : this.propriete
- Méthodes normales : methode()

PRIVÉ : Accessible seulement dans la classe
- Propriétés privées : this.#propriete
- Méthodes privées : #methode()


# ----------------------------------------------------------------------------
# [GRAPHIQUE] PROPRIÉTÉS ET MÉTHODES STATIQUES
# ----------------------------------------------------------------------------

"""
MÉTHODES STATIQUES

Appartiennent à la classe, pas aux instances
"""

class MathUtils {
    // Méthode statique
    static additionner(a, b) {
        return a + b;
    }
    
    static multiplier(a, b) {
        return a * b;
    }
    
    static carre(n) {
        return n * n;
    }
}

// Appel sur la classe directement
console.log(MathUtils.additionner(5, 3)); // 8
console.log(MathUtils.carre(4));          // 16

// Pas sur les instances
let math = new MathUtils();
// math.additionner(5, 3); // [X] Erreur !

"""
[IDEE] MÉTHODES STATIQUES

- Appelées sur la classe, pas sur instance
- Utiles pour fonctions utilitaires
- N'accèdent pas à this
- Équivalent à fonctions standalone


PROPRIÉTÉS STATIQUES
"""

class Configuration {
    static API_URL = 'https://api.example.com';
    static TIMEOUT = 5000;
    static MAX_RETRY = 3;
}

console.log(Configuration.API_URL); // 'https://api.example.com'


"""
EXEMPLE : Compteur d'instances
"""

class Utilisateur {
    static #compteur = 0;
    
    constructor(nom) {
        this.nom = nom;
        this.id = ++Utilisateur.#compteur;
    }
    
    static getCompteur() {
        return Utilisateur.#compteur;
    }
    
    static reinitialiserCompteur() {
        Utilisateur.#compteur = 0;
    }
}

let user1 = new Utilisateur('Alice');
let user2 = new Utilisateur('Bob');
let user3 = new Utilisateur('Charlie');

console.log(user1.id); // 1
console.log(user2.id); // 2
console.log(user3.id); // 3

console.log(Utilisateur.getCompteur()); // 3


"""
FACTORY METHOD (Pattern)
"""

class Utilisateur {
    constructor(nom, role) {
        this.nom = nom;
        this.role = role;
    }
    
    // Factory methods statiques
    static creerAdmin(nom) {
        return new Utilisateur(nom, 'admin');
    }
    
    static creerModerator(nom) {
        return new Utilisateur(nom, 'moderator');
    }
    
    static creerInvite() {
        return new Utilisateur('Invité', 'guest');
    }
}

let admin = Utilisateur.creerAdmin('Alice');
let guest = Utilisateur.creerInvite();


# ----------------------------------------------------------------------------
# [SCIENCE] HÉRITAGE
# ----------------------------------------------------------------------------

"""
EXTENDS : Créer une classe enfant

La classe enfant hérite des propriétés et méthodes du parent
"""

class Animal {
    constructor(nom) {
        this.nom = nom;
    }
    
    manger() {
        console.log(`${this.nom} mange`);
    }
    
    dormir() {
        console.log(`${this.nom} dort`);
    }
}

// Chien hérite d'Animal
class Chien extends Animal {
    constructor(nom, race) {
        super(nom); // Appelle le constructeur parent
        this.race = race;
    }
    
    aboyer() {
        console.log(`${this.nom} aboie: Woof!`);
    }
}

let chien = new Chien('Rex', 'Labrador');
chien.manger();  // Hérité d'Animal
chien.dormir();  // Hérité d'Animal
chien.aboyer();  // Propre à Chien

"""
[IDEE] SUPER

super() : Appelle le constructeur parent
super.methode() : Appelle une méthode du parent
OBLIGATOIRE dans constructor avant d'utiliser this


SURCHARGE (OVERRIDE)

Redéfinir une méthode du parent
"""

class Animal {
    constructor(nom) {
        this.nom = nom;
    }
    
    faireBruit() {
        console.log(`${this.nom} fait un bruit`);
    }
}

class Chat extends Animal {
    // Surcharge de faireBruit()
    faireBruit() {
        console.log(`${this.nom} miaule: Miaou!`);
    }
}

class Chien extends Animal {
    faireBruit() {
        console.log(`${this.nom} aboie: Woof!`);
    }
}

let chat = new Chat('Félix');
let chien = new Chien('Rex');

chat.faireBruit();  // 'Félix miaule: Miaou!'
chien.faireBruit(); // 'Rex aboie: Woof!'


"""
APPELER MÉTHODE PARENT
"""

class Vehicule {
    constructor(marque) {
        this.marque = marque;
    }
    
    demarrer() {
        console.log(`${this.marque} démarre`);
    }
}

class Voiture extends Vehicule {
    constructor(marque, modele) {
        super(marque);
        this.modele = modele;
    }
    
    demarrer() {
        super.demarrer(); // Appelle méthode parent
        console.log(`${this.modele} en route!`);
    }
}

let voiture = new Voiture('Toyota', 'Corolla');
voiture.demarrer();
// 'Toyota démarre'
// 'Corolla en route!'


"""
HÉRITAGE MULTIPLE NIVEAUX
"""

class EtreVivant {
    respirer() {
        console.log('Respire');
    }
}

class Animal extends EtreVivant {
    manger() {
        console.log('Mange');
    }
}

class Chien extends Animal {
    aboyer() {
        console.log('Aboie');
    }
}

let chien = new Chien();
chien.respirer(); // D'EtreVivant
chien.manger();   // D'Animal
chien.aboyer();   // De Chien


"""
INSTANCEOF : Vérifier le type
"""

let chien = new Chien('Rex');

console.log(chien instanceof Chien);      // true
console.log(chien instanceof Animal);     // true
console.log(chien instanceof EtreVivant); // true
console.log(chien instanceof Chat);       // false


# ----------------------------------------------------------------------------
# [PRO] EXEMPLE COMPLET : SYSTÈME DE GESTION
# ----------------------------------------------------------------------------

"""
SYSTÈME DE GESTION D'EMPLOYÉS
"""

// Classe de base
class Personne {
    constructor(nom, age) {
        this.nom = nom;
        this.age = age;
    }
    
    sePresenter() {
        return `Je m'appelle ${this.nom} et j'ai ${this.age} ans`;
    }
}

// Classe Employé
class Employe extends Personne {
    static #compteurId = 1000;
    
    constructor(nom, age, poste, salaire) {
        super(nom, age);
        this.id = ++Employe.#compteurId;
        this.poste = poste;
        this.salaire = salaire;
        this.dateEmbauche = new Date();
    }
    
    get salaireAnnuel() {
        return this.salaire * 12;
    }
    
    augmenter(pourcentage) {
        this.salaire *= (1 + pourcentage / 100);
    }
    
    sePresenter() {
        return `${super.sePresenter()}, je suis ${this.poste}`;
    }
    
    obtenirInfos() {
        return {
            id: this.id,
            nom: this.nom,
            poste: this.poste,
            salaire: this.salaire
        };
    }
}

// Classe Manager
class Manager extends Employe {
    constructor(nom, age, salaire, departement) {
        super(nom, age, 'Manager', salaire);
        this.departement = departement;
        this.equipe = [];
    }
    
    ajouterMembre(employe) {
        if (employe instanceof Employe) {
            this.equipe.push(employe);
        }
    }
    
    retirerMembre(employeId) {
        this.equipe = this.equipe.filter(e => e.id !== employeId);
    }
    
    obtenirEquipe() {
        return this.equipe.map(e => e.obtenirInfos());
    }
    
    obtenirTailleDEquipe() {
        return this.equipe.length;
    }
}

// Classe Developpeur
class Developpeur extends Employe {
    constructor(nom, age, salaire, langages = []) {
        super(nom, age, 'Développeur', salaire);
        this.langages = langages;
        this.projets = [];
    }
    
    ajouterLangage(langage) {
        if (!this.langages.includes(langage)) {
            this.langages.push(langage);
        }
    }
    
    ajouterProjet(projet) {
        this.projets.push(projet);
    }
    
    obtenirCompetences() {
        return {
            langages: this.langages,
            nbProjets: this.projets.length
        };
    }
}

// Utilisation
let manager = new Manager('Alice', 35, 5000, 'IT');
let dev1 = new Developpeur('Bob', 28, 3500, ['JavaScript', 'Python']);
let dev2 = new Developpeur('Charlie', 30, 4000, ['Java', 'C++']);

manager.ajouterMembre(dev1);
manager.ajouterMembre(dev2);

console.log(manager.sePresenter());
// 'Je m'appelle Alice et j'ai 35 ans, je suis Manager'

console.log(manager.obtenirEquipe());
// [{ id: 1001, nom: 'Bob', ... }, { id: 1002, nom: 'Charlie', ... }]

dev1.ajouterLangage('TypeScript');
console.log(dev1.obtenirCompetences());
// { langages: ['JavaScript', 'Python', 'TypeScript'], nbProjets: 0 }


# ----------------------------------------------------------------------------
# [COURS] EXERCICES PRATIQUES : CLASSES
# ----------------------------------------------------------------------------

"""
EXERCICE 1 : Bibliothèque de livres

Créer un système de gestion de bibliothèque
"""

// Solution
class Livre {
    static #compteurId = 0;
    
    constructor(titre, auteur, annee, isbn) {
        this.id = ++Livre.#compteurId;
        this.titre = titre;
        this.auteur = auteur;
        this.annee = annee;
        this.isbn = isbn;
        this.disponible = true;
        this.emprunteur = null;
    }
    
    emprunter(nomEmprunteur) {
        if (!this.disponible) {
            return false;
        }
        this.disponible = false;
        this.emprunteur = nomEmprunteur;
        this.dateEmprunt = new Date();
        return true;
    }
    
    retourner() {
        this.disponible = true;
        this.emprunteur = null;
        this.dateEmprunt = null;
    }
    
    obtenirInfos() {
        return {
            id: this.id,
            titre: this.titre,
            auteur: this.auteur,
            disponible: this.disponible
        };
    }
}

class Bibliotheque {
    constructor(nom) {
        this.nom = nom;
        this.livres = [];
    }
    
    ajouterLivre(livre) {
        if (livre instanceof Livre) {
            this.livres.push(livre);
        }
    }
    
    rechercherParTitre(titre) {
        return this.livres.filter(livre => 
            livre.titre.toLowerCase().includes(titre.toLowerCase())
        );
    }
    
    rechercherParAuteur(auteur) {
        return this.livres.filter(livre =>
            livre.auteur.toLowerCase().includes(auteur.toLowerCase())
        );
    }
    
    obtenirLivresDisponibles() {
        return this.livres.filter(livre => livre.disponible);
    }
    
    obtenirLivresEmpruntes() {
        return this.livres.filter(livre => !livre.disponible);
    }
    
    emprunterLivre(livreId, emprunteur) {
        let livre = this.livres.find(l => l.id === livreId);
        if (livre) {
            return livre.emprunter(emprunteur);
        }
        return false;
    }
    
    retournerLivre(livreId) {
        let livre = this.livres.find(l => l.id === livreId);
        if (livre) {
            livre.retourner();
            return true;
        }
        return false;
    }
}

// Utilisation
let biblio = new Bibliotheque('Bibliothèque Centrale');

let livre1 = new Livre('1984', 'George Orwell', 1949, '978-0-452-28423-4');
let livre2 = new Livre('Le Petit Prince', 'Antoine de Saint-Exupéry', 1943, '978-2-07-061275-8');

biblio.ajouterLivre(livre1);
biblio.ajouterLivre(livre2);

biblio.emprunterLivre(1, 'Alice');
console.log(biblio.obtenirLivresDisponibles().length); // 1
console.log(biblio.obtenirLivresEmpruntes().length);   // 1


"""
EXERCICE 2 : Jeu RPG avec classes

Créer un système de personnages pour jeu de rôle
"""

// Solution
class Personnage {
    constructor(nom, pointsVie, attaque, defense) {
        this.nom = nom;
        this.pointsVieMax = pointsVie;
        this.pointsVie = pointsVie;
        this.attaque = attaque;
        this.defense = defense;
        this.niveau = 1;
        this.experience = 0;
    }
    
    get estVivant() {
        return this.pointsVie > 0;
    }
    
    get pourcentageVie() {
        return (this.pointsVie / this.pointsVieMax) * 100;
    }
    
    recevoirDegats(degats) {
        let degatsReels = Math.max(0, degats - this.defense);
        this.pointsVie = Math.max(0, this.pointsVie - degatsReels);
        return degatsReels;
    }
    
    soigner(montant) {
        this.pointsVie = Math.min(this.pointsVieMax, this.pointsVie + montant);
    }
    
    attaquer(cible) {
        if (!this.estVivant) {
            return { succes: false, message: `${this.nom} est KO !` };
        }
        
        let degats = this.attaque + Math.floor(Math.random() * 5);
        let degatsInfliges = cible.recevoirDegats(degats);
        
        return {
            succes: true,
            degats: degatsInfliges,
            message: `${this.nom} inflige ${degatsInfliges} dégâts à ${cible.nom}`
        };
    }
    
    gagnerExperience(xp) {
        this.experience += xp;
        
        // Monter de niveau tous les 100 XP
        while (this.experience >= 100) {
            this.niveau++;
            this.experience -= 100;
            this.monterNiveau();
        }
    }
    
    monterNiveau() {
        this.pointsVieMax += 10;
        this.pointsVie = this.pointsVieMax;
        this.attaque += 2;
        this.defense += 1;
        console.log(`${this.nom} monte au niveau ${this.niveau} !`);
    }
}

class Guerrier extends Personnage {
    constructor(nom) {
        super(nom, 120, 15, 8);
        this.rage = 0;
    }
    
    attaquer(cible) {
        // Attaque normale
        let resultat = super.attaquer(cible);
        
        // Gagner de la rage
        if (resultat.succes) {
            this.rage = Math.min(100, this.rage + 10);
        }
        
        return resultat;
    }
    
    attaqueSpeciale(cible) {
        if (this.rage < 50) {
            return { succes: false, message: 'Pas assez de rage !' };
        }
        
        this.rage -= 50;
        let degats = this.attaque * 2;
        let degatsInfliges = cible.recevoirDegats(degats);
        
        return {
            succes: true,
            degats: degatsInfliges,
            message: `${this.nom} utilise Coup Puissant ! ${degatsInfliges} dégâts !`
        };
    }
}

class Mage extends Personnage {
    constructor(nom) {
        super(nom, 80, 20, 3);
        this.mana = 100;
        this.manaMax = 100;
    }
    
    attaquer(cible) {
        if (this.mana < 10) {
            return { succes: false, message: 'Pas assez de mana !' };
        }
        
        this.mana -= 10;
        let resultat = super.attaquer(cible);
        return resultat;
    }
    
    sort(cible) {
        if (this.mana < 30) {
            return { succes: false, message: 'Pas assez de mana !' };
        }
        
        this.mana -= 30;
        let degats = this.attaque * 1.5 + Math.floor(Math.random() * 10);
        let degatsInfliges = cible.recevoirDegats(degats);
        
        return {
            succes: true,
            degats: degatsInfliges,
            message: `${this.nom} lance Boule de Feu ! ${degatsInfliges} dégâts !`
        };
    }
    
    regenererMana() {
        this.mana = Math.min(this.manaMax, this.mana + 20);
    }
}

// Utilisation - Combat
let guerrier = new Guerrier('Conan');
let mage = new Mage('Gandalf');

console.log(`=== Combat : ${guerrier.nom} vs ${mage.nom} ===`);

while (guerrier.estVivant && mage.estVivant) {
    // Tour du guerrier
    let attaque1 = guerrier.attaquer(mage);
    console.log(attaque1.message);
    console.log(`${mage.nom}: ${mage.pointsVie}/${mage.pointsVieMax} PV`);
    
    if (!mage.estVivant) {
        console.log(`${mage.nom} est vaincu !`);
        break;
    }
    
    // Tour du mage
    let attaque2 = mage.sort(guerrier);
    console.log(attaque2.message);
    console.log(`${guerrier.nom}: ${guerrier.pointsVie}/${guerrier.pointsVieMax} PV`);
    
    if (!guerrier.estVivant) {
        console.log(`${guerrier.nom} est vaincu !`);
        break;
    }
}


# ============================================================================
# [DOCS] RÉCAPITULATIF CHAPITRE 13 : CLASSES ET POO
# ============================================================================

"""
[BRAVO] CHAPITRE 13 TERMINÉ !

VOUS MAÎTRISEZ MAINTENANT :

[OK] Créer des classes avec constructor
[OK] Définir méthodes d'instance
[OK] Utiliser getters et setters
[OK] Propriétés et méthodes privées (#)
[OK] Propriétés et méthodes statiques
[OK] Héritage avec extends
[OK] Surcharge de méthodes
[OK] super() pour appeler parent


[CLE] CONCEPTS POO :

ENCAPSULATION : Regrouper données et méthodes
HÉRITAGE : Réutiliser code via classes parentes
POLYMORPHISME : Surcharger méthodes
ABSTRACTION : Cacher complexité


[IDEE] BONNES PRATIQUES :

1. Une classe = Une responsabilité
2. Propriétés privées pour encapsulation
3. Getters/setters pour validation
4. Méthodes statiques pour utilitaires
5. Héritage pour relation "est-un"
6. Composition pour relation "a-un"


[GRAPHIQUE] CHEATSHEET :

class NomClasse extends Parent {
    #prive = 'privé';           // Propriété privée
    static statique = 'static'; // Propriété statique
    
    constructor(param) {
        super(param);             // Appel parent
        this.publique = param;    // Propriété publique
    }
    
    get propriete() { }           // Getter
    set propriete(v) { }          // Setter
    
    methode() { }                 // Méthode instance
    static methodeStatique() { }  // Méthode statique
    #methodePrivee() { }          // Méthode privée
}


-> SUITE : CHAPITRE 14 - GESTION D'ERREURS

Prochains sujets :
- Try/Catch/Finally
- Throw
- Error types
- Custom errors


[GUIDE] FICHIER SUIVANT : javascript_partie3_errors.txt
"""
# ============================================================================
# [LIVRE] JAVASCRIPT - PARTIE 3 (FIN) : GESTION D'ERREURS
# ============================================================================
#
# [OBJECTIF] CHAPITRE 14 : GESTION D'ERREURS ET DEBUGGING
#
# [TEMPS] TEMPS : ~2-3 heures
# [DOCS] PRÉREQUIS : Chapitres précédents complétés
# ============================================================================


# ============================================================================
# [GUIDE] CHAPITRE 14 : GESTION D'ERREURS
# ============================================================================

"""
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Comprendre les types d'erreurs
[OK] Utiliser try/catch/finally
[OK] Lancer des erreurs avec throw
[OK] Créer des erreurs personnalisées
[OK] Gérer les erreurs asynchrones
[OK] Déboguer efficacement
[OK] Utiliser console pour debugging
"""


# ----------------------------------------------------------------------------
# [ATTENTION] TYPES D'ERREURS
# ----------------------------------------------------------------------------

"""
ERREURS EN JAVASCRIPT

3 catégories principales :
1. Erreurs de syntaxe
2. Erreurs d'exécution (runtime)
3. Erreurs logiques


1. ERREURS DE SYNTAXE

Code invalide, empêche l'exécution
"""

// [X] Erreur de syntaxe
let x = ;  // SyntaxError: Unexpected token ';'

if (x > 5 {  // SyntaxError: Missing closing parenthesis
    console.log(x);
}

function test( {  // SyntaxError
    // ...
}

"""
[IDEE] ERREURS DE SYNTAXE

- Détectées avant l'exécution
- Code ne démarre pas
- Faciles à corriger (message clair)


2. ERREURS D'EXÉCUTION (RUNTIME)

Code syntaxiquement correct mais erreur à l'exécution
"""

// ReferenceError : Variable non définie
console.log(variableInexistante);  // ReferenceError

// TypeError : Mauvais type
let nombre = 5;
nombre.toUpperCase();  // TypeError: nombre.toUpperCase is not a function

null.propriete;  // TypeError: Cannot read property 'propriete' of null

// RangeError : Valeur hors limites
let arr = new Array(-1);  // RangeError: Invalid array length

function recursion() {
    recursion();
}
recursion();  // RangeError: Maximum call stack size exceeded

"""
TYPES D'ERREURS COURANTS
"""

// ReferenceError : Variable non déclarée
console.log(x);  // ReferenceError: x is not defined

// TypeError : Opération sur mauvais type
let nombre = null;
nombre.toFixed(2);  // TypeError

// RangeError : Valeur invalide
let arr = new Array(Infinity);  // RangeError

// URIError : URI mal formée
decodeURIComponent('%');  // URIError

// EvalError : Erreur eval() (rare)

"""
3. ERREURS LOGIQUES

Code s'exécute mais résultat incorrect
"""

// Mauvaise logique
function calculerMoyenne(a, b) {
    return a * b;  // [X] Devrait être (a + b) / 2
}

// Condition inversée
if (age < 18) {
    console.log('Majeur');  // [X] Logique inversée
}

"""
[IDEE] ERREURS LOGIQUES

- Les plus difficiles à détecter
- Pas de message d'erreur
- Nécessitent tests et debugging


# ----------------------------------------------------------------------------
# [SECURITE] TRY / CATCH / FINALLY
# ----------------------------------------------------------------------------

"""
TRY/CATCH : Gérer les erreurs
"""

try {
    // Code pouvant causer une erreur
    let resultat = fonctionRisquee();
} catch (erreur) {
    // Code exécuté si erreur
    console.error('Erreur:', erreur.message);
}

"""
[IDEE] TRY/CATCH

try : Bloc de code à surveiller
catch : Exécuté si erreur dans try
erreur : Objet Error capturé


EXEMPLE PRATIQUE
"""

function diviser(a, b) {
    try {
        if (b === 0) {
            throw new Error('Division par zéro impossible');
        }
        return a / b;
    } catch (erreur) {
        console.error('Erreur:', erreur.message);
        return null;
    }
}

console.log(diviser(10, 2));  // 5
console.log(diviser(10, 0));  // null (erreur capturée)


"""
FINALLY : Toujours exécuté

Même si erreur ou return
"""

function chargerDonnees() {
    console.log('Début chargement...');
    
    try {
        // Simuler chargement
        let data = fetchData();
        return data;
    } catch (erreur) {
        console.error('Erreur chargement:', erreur);
        return null;
    } finally {
        // Toujours exécuté
        console.log('Nettoyage terminé');
    }
}

"""
[IDEE] FINALLY

- Exécuté TOUJOURS
- Même si return dans try/catch
- Utile pour nettoyage (fermer connexions, etc.)


ORDRE D'EXÉCUTION
"""

function test() {
    try {
        console.log('1. Try');
        return 'Retour try';
    } catch (err) {
        console.log('2. Catch');
        return 'Retour catch';
    } finally {
        console.log('3. Finally');
        // Finally s'exécute avant le return !
    }
}

test();
// 1. Try
// 3. Finally
// Retourne : 'Retour try'


"""
CATCH SANS VARIABLE (ES2019)

Si on n'utilise pas l'erreur
"""

try {
    riskyOperation();
} catch {  // Pas de variable erreur
    console.log('Une erreur est survenue');
}


# ----------------------------------------------------------------------------
# [IMPACT] THROW : LANCER DES ERREURS
# ----------------------------------------------------------------------------

"""
THROW : Lancer une erreur manuellement
"""

function verifierAge(age) {
    if (age < 0) {
        throw new Error('Âge ne peut pas être négatif');
    }
    if (age > 150) {
        throw new Error('Âge irréaliste');
    }
    return age;
}

try {
    verifierAge(-5);
} catch (err) {
    console.error(err.message);  // 'Âge ne peut pas être négatif'
}


"""
TYPES D'ERREURS À LANCER
"""

// Error générique
throw new Error('Message d\'erreur');

// TypeError
throw new TypeError('Type invalide');

// RangeError
throw new RangeError('Valeur hors limites');

// ReferenceError
throw new ReferenceError('Référence invalide');


"""
LANCER VALEURS PERSONNALISÉES

[ATTENTION] Pas recommandé, utilisez Error
"""

// Peut lancer n'importe quoi (mais pas recommandé)
throw 'Erreur string';
throw 42;
throw { erreur: 'Objet erreur' };

// [OK] Préférez toujours Error
throw new Error('Message clair');


"""
OBJET ERROR

Propriétés utiles
"""

try {
    throw new Error('Quelque chose a mal tourné');
} catch (err) {
    console.log(err.name);     // 'Error'
    console.log(err.message);  // 'Quelque chose a mal tourné'
    console.log(err.stack);    // Stack trace (utile pour debug)
}


# ----------------------------------------------------------------------------
# [DESIGN] ERREURS PERSONNALISÉES
# ----------------------------------------------------------------------------

"""
CRÉER CLASSE D'ERREUR PERSONNALISÉE
"""

class ValidationError extends Error {
    constructor(message) {
        super(message);
        this.name = 'ValidationError';
    }
}

class AuthenticationError extends Error {
    constructor(message) {
        super(message);
        this.name = 'AuthenticationError';
    }
}

// Utilisation
function validerEmail(email) {
    if (!email.includes('@')) {
        throw new ValidationError('Email invalide');
    }
    return email;
}

try {
    validerEmail('invalide');
} catch (err) {
    if (err instanceof ValidationError) {
        console.log('Erreur de validation:', err.message);
    } else {
        console.log('Autre erreur:', err);
    }
}


"""
ERREUR AVEC PROPRIÉTÉS PERSONNALISÉES
"""

class HttpError extends Error {
    constructor(message, status) {
        super(message);
        this.name = 'HttpError';
        this.status = status;
    }
}

function fetchData(url) {
    // Simuler requête HTTP
    throw new HttpError('Ressource non trouvée', 404);
}

try {
    fetchData('/api/users');
} catch (err) {
    if (err instanceof HttpError) {
        console.log(`HTTP ${err.status}: ${err.message}`);
    }
}


"""
ERREURS MÉTIER STRUCTURÉES
"""

class AppError extends Error {
    constructor(message, code, details = {}) {
        super(message);
        this.name = 'AppError';
        this.code = code;
        this.details = details;
        this.timestamp = new Date();
    }
    
    toJSON() {
        return {
            name: this.name,
            message: this.message,
            code: this.code,
            details: this.details,
            timestamp: this.timestamp
        };
    }
}

// Codes d'erreur standardisés
const ERROR_CODES = {
    VALIDATION: 'VALIDATION_ERROR',
    AUTH: 'AUTHENTICATION_ERROR',
    NOT_FOUND: 'NOT_FOUND',
    SERVER: 'SERVER_ERROR'
};

// Utilisation
function creerUtilisateur(data) {
    if (!data.email) {
        throw new AppError(
            'Email requis',
            ERROR_CODES.VALIDATION,
            { field: 'email' }
        );
    }
    
    if (!data.password || data.password.length < 8) {
        throw new AppError(
            'Mot de passe trop court',
            ERROR_CODES.VALIDATION,
            { field: 'password', minLength: 8 }
        );
    }
    
    // Créer utilisateur...
}

try {
    creerUtilisateur({ email: '', password: '123' });
} catch (err) {
    if (err instanceof AppError) {
        console.error('Erreur:', err.toJSON());
    }
}


# ----------------------------------------------------------------------------
# [RAPIDE] GESTION D'ERREURS ASYNCHRONES
# ----------------------------------------------------------------------------

"""
ERREURS AVEC PROMISES
"""

// Promise rejetée
let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject(new Error('Échec asynchrone'));
    }, 1000);
});

// Catch avec .catch()
promise
    .then(result => console.log(result))
    .catch(err => console.error('Erreur:', err.message));


"""
ERREURS AVEC ASYNC/AWAIT
"""

async function chargerUtilisateur(id) {
    try {
        let response = await fetch(`/api/users/${id}`);
        
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}`);
        }
        
        let user = await response.json();
        return user;
    } catch (err) {
        console.error('Erreur chargement:', err.message);
        return null;
    }
}

// Utilisation
async function main() {
    let user = await chargerUtilisateur(1);
    if (user) {
        console.log('Utilisateur:', user);
    }
}


"""
ERREURS NON CAPTURÉES (UNHANDLED)

[ATTENTION] Dangereux : Erreurs asynchrones non gérées
"""

// [X] Erreur non capturée
async function dangereux() {
    throw new Error('Boom !');
}

dangereux();  // UnhandledPromiseRejection

// [OK] Toujours capturer
async function safe() {
    try {
        await dangereux();
    } catch (err) {
        console.error('Capturé:', err);
    }
}


"""
GLOBAL ERROR HANDLERS
"""

// Erreurs promises non gérées
window.addEventListener('unhandledrejection', (event) => {
    console.error('Promise non gérée:', event.reason);
    event.preventDefault(); // Empêcher log par défaut
});

// Erreurs JavaScript non gérées
window.addEventListener('error', (event) => {
    console.error('Erreur globale:', event.error);
});


# ----------------------------------------------------------------------------
# [RECHERCHE] DEBUGGING
# ----------------------------------------------------------------------------

"""
CONSOLE METHODS

Plus que console.log !
"""

// log : Message normal
console.log('Message normal');

// error : Message d'erreur (rouge)
console.error('Erreur importante');

// warn : Avertissement (jaune)
console.warn('Attention !');

// info : Information
console.info('Information');

// debug : Message de débogage
console.debug('Debug info');


"""
CONSOLE AVANCÉE
"""

// table : Afficher tableau/objet en table
let users = [
    { nom: 'Alice', age: 25 },
    { nom: 'Bob', age: 30 },
    { nom: 'Charlie', age: 35 }
];
console.table(users);

// group : Grouper messages
console.group('Détails utilisateur');
console.log('Nom: Alice');
console.log('Age: 25');
console.log('Email: alice@example.com');
console.groupEnd();

// time : Mesurer temps d'exécution
console.time('Chargement');
// Code à mesurer...
setTimeout(() => {
    console.timeEnd('Chargement');  // Chargement: 1000ms
}, 1000);

// count : Compter appels
function incremente() {
    console.count('Appels');
}
incremente();  // Appels: 1
incremente();  // Appels: 2
incremente();  // Appels: 3

// trace : Stack trace
function a() { b(); }
function b() { c(); }
function c() { console.trace('Trace'); }
a();

// assert : Assertion
console.assert(2 + 2 === 4, 'Math fonctionne');
console.assert(2 + 2 === 5, 'Math cassé !');  // Erreur

// clear : Effacer console
console.clear();


"""
DEBUGGER STATEMENT

Point d'arrêt dans le code
"""

function calculer(x, y) {
    debugger;  // Pause l'exécution ici (si DevTools ouvert)
    let resultat = x + y;
    return resultat;
}

// L'exécution s'arrête au debugger si DevTools ouvert


"""
TECHNIQUES DE DEBUGGING
"""

// 1. Afficher variables importantes
function traiterDonnees(data) {
    console.log('Input:', data);
    
    let processed = data.map(x => x * 2);
    console.log('Processed:', processed);
    
    let filtered = processed.filter(x => x > 10);
    console.log('Filtered:', filtered);
    
    return filtered;
}

// 2. Vérifier types
function additionner(a, b) {
    console.log('Types:', typeof a, typeof b);
    console.log('Valeurs:', a, b);
    return a + b;
}

// 3. Tracer l'exécution
function comptexi() {
    console.log('1. Début');
    
    try {
        console.log('2. Avant opération risquée');
        riskyOperation();
        console.log('3. Après opération (pas atteint si erreur)');
    } catch (err) {
        console.log('4. Erreur capturée:', err.message);
    }
    
    console.log('5. Fin');
}


# ----------------------------------------------------------------------------
# [COURS] EXERCICES PRATIQUES : GESTION D'ERREURS
# ----------------------------------------------------------------------------

"""
EXERCICE 1 : Validateur de formulaire robuste

Créer un système de validation avec erreurs personnalisées
"""

// Solution
class ValidationError extends Error {
    constructor(field, message) {
        super(message);
        this.name = 'ValidationError';
        this.field = field;
    }
}

class FormValidator {
    constructor() {
        this.errors = [];
    }
    
    validerEmail(email) {
        if (!email || email.trim() === '') {
            throw new ValidationError('email', 'Email requis');
        }
        
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        if (!emailRegex.test(email)) {
            throw new ValidationError('email', 'Format email invalide');
        }
        
        return true;
    }
    
    validerMotDePasse(password) {
        if (!password || password.length < 8) {
            throw new ValidationError('password', 'Mot de passe min 8 caractères');
        }
        
        if (!/[A-Z]/.test(password)) {
            throw new ValidationError('password', 'Doit contenir une majuscule');
        }
        
        if (!/[0-9]/.test(password)) {
            throw new ValidationError('password', 'Doit contenir un chiffre');
        }
        
        return true;
    }
    
    validerAge(age) {
        age = parseInt(age);
        
        if (isNaN(age)) {
            throw new ValidationError('age', 'Âge doit être un nombre');
        }
        
        if (age < 18) {
            throw new ValidationError('age', 'Âge minimum 18 ans');
        }
        
        if (age > 120) {
            throw new ValidationError('age', 'Âge invalide');
        }
        
        return true;
    }
    
    validerFormulaire(data) {
        this.errors = [];
        
        // Valider chaque champ
        ['email', 'password', 'age'].forEach(field => {
            try {
                const methode = `valider${field.charAt(0).toUpperCase() + field.slice(1)}`;
                this[methode](data[field]);
            } catch (err) {
                if (err instanceof ValidationError) {
                    this.errors.push({
                        field: err.field,
                        message: err.message
                    });
                }
            }
        });
        
        return {
            valid: this.errors.length === 0,
            errors: this.errors
        };
    }
    
    getErrors() {
        return this.errors;
    }
}

// Utilisation
const validator = new FormValidator();

const formData = {
    email: 'invalide',
    password: '123',
    age: '15'
};

const result = validator.validerFormulaire(formData);

if (!result.valid) {
    console.log('Erreurs de validation:');
    result.errors.forEach(err => {
        console.log(`- ${err.field}: ${err.message}`);
    });
}


"""
EXERCICE 2 : Gestionnaire d'erreurs centralisé

Créer un système de gestion d'erreurs pour application
"""

// Solution
class ErrorHandler {
    constructor() {
        this.errors = [];
        this.maxErrors = 100;
        this.listeners = [];
    }
    
    handle(error, context = {}) {
        const errorEntry = {
            timestamp: new Date(),
            error: error,
            message: error.message,
            stack: error.stack,
            context: context,
            type: error.name || 'Error'
        };
        
        // Stocker erreur
        this.errors.push(errorEntry);
        
        // Limiter historique
        if (this.errors.length > this.maxErrors) {
            this.errors.shift();
        }
        
        // Logger
        this.log(errorEntry);
        
        // Notifier listeners
        this.notifyListeners(errorEntry);
        
        return errorEntry;
    }
    
    log(errorEntry) {
        console.error(
            `[${errorEntry.timestamp.toISOString()}] ${errorEntry.type}:`,
            errorEntry.message
        );
        
        if (errorEntry.context && Object.keys(errorEntry.context).length > 0) {
            console.error('Context:', errorEntry.context);
        }
    }
    
    onError(callback) {
        this.listeners.push(callback);
    }
    
    notifyListeners(errorEntry) {
        this.listeners.forEach(listener => {
            try {
                listener(errorEntry);
            } catch (err) {
                console.error('Erreur dans listener:', err);
            }
        });
    }
    
    getErrors(filter = {}) {
        let filtered = this.errors;
        
        if (filter.type) {
            filtered = filtered.filter(e => e.type === filter.type);
        }
        
        if (filter.since) {
            filtered = filtered.filter(e => e.timestamp >= filter.since);
        }
        
        return filtered;
    }
    
    clear() {
        this.errors = [];
    }
    
    getStats() {
        const stats = {};
        
        this.errors.forEach(err => {
            stats[err.type] = (stats[err.type] || 0) + 1;
        });
        
        return {
            total: this.errors.length,
            byType: stats
        };
    }
}

// Instance globale
const errorHandler = new ErrorHandler();

// Écouter erreurs
errorHandler.onError(error => {
    // Envoyer à service de monitoring
    console.log('Erreur capturée pour monitoring:', error.message);
});

// Wrapper pour fonctions asynchrones
async function safeAsync(fn, context = {}) {
    try {
        return await fn();
    } catch (err) {
        errorHandler.handle(err, context);
        throw err;
    }
}

// Utilisation
async function chargerDonnees() {
    return safeAsync(
        async () => {
            const response = await fetch('/api/data');
            if (!response.ok) {
                throw new Error(`HTTP ${response.status}`);
            }
            return response.json();
        },
        { operation: 'chargerDonnees', endpoint: '/api/data' }
    );
}


# ============================================================================
# [DOCS] RÉCAPITULATIF CHAPITRE 14 : GESTION D'ERREURS
# ============================================================================

"""
[BRAVO] CHAPITRE 14 TERMINÉ !

VOUS MAÎTRISEZ MAINTENANT :

[OK] Types d'erreurs (Syntax, Runtime, Logic)
[OK] try/catch/finally pour gérer erreurs
[OK] throw pour lancer erreurs
[OK] Erreurs personnalisées (extends Error)
[OK] Gestion erreurs asynchrones
[OK] Console methods pour debugging
[OK] Techniques de debugging


[CLE] BONNES PRATIQUES :

1. Toujours capturer erreurs asynchrones
2. Créer erreurs personnalisées pour domaine métier
3. Inclure contexte dans erreurs
4. Logger erreurs avec détails
5. Ne jamais avaler erreurs silencieusement
6. Utiliser finally pour nettoyage
7. Valider inputs tôt (fail fast)


[ATTENTION] PIÈGES À ÉVITER :

// [X] Erreur avalée
try {
    riskyOperation();
} catch (err) {
    // Rien (erreur perdue !)
}

// [OK] Toujours traiter
try {
    riskyOperation();
} catch (err) {
    console.error('Erreur:', err);
    // Ou relancer : throw err;
}


[GRAPHIQUE] CHEATSHEET :

// Gérer erreur
try {
    // Code risqué
} catch (err) {
    console.error(err.message);
} finally {
    // Nettoyage
}

// Lancer erreur
throw new Error('Message');

// Erreur personnalisée
class MyError extends Error {
    constructor(message) {
        super(message);
        this.name = 'MyError';
    }
}

// Async errors
try {
    await asyncOp();
} catch (err) {
    // Gérer
}


# ============================================================================
# [BRAVO] PARTIE 3 COMPLÈTE !
# ============================================================================

FÉLICITATIONS ! VOUS AVEZ TERMINÉ LA PARTIE 3 !

RÉCAPITULATIF PARTIE 3 :

[OK] Chapitre 9 : DOM Manipulation
   - Sélectionner éléments
   - Modifier contenu et styles
   - Créer/supprimer éléments
   - Naviguer dans le DOM

[OK] Chapitre 10 : Événements
   - addEventListener
   - Types d'événements (souris, clavier, formulaires)
   - Event bubbling et delegation
   - preventDefault

[OK] Chapitre 11 : Asynchrone
   - Callbacks
   - Promises
   - Async/Await
   - Fetch API

[OK] Chapitre 12 : Modules ES6
   - Import/Export
   - Default exports
   - Organisation code
   - Patterns courants

[OK] Chapitre 13 : Classes et POO
   - Classes et constructeurs
   - Héritage
   - Getters/Setters
   - Propriétés privées/statiques

[OK] Chapitre 14 : Gestion d'Erreurs
   - try/catch/finally
   - throw
   - Erreurs personnalisées
   - Debugging


[OBJECTIF] VOUS POUVEZ MAINTENANT :

[OK] Créer applications web interactives
[OK] Gérer événements utilisateur
[OK] Faire requêtes API
[OK] Organiser code en modules
[OK] Programmer orienté objet
[OK] Gérer erreurs proprement


-> PROCHAINE ÉTAPE : PARTIE 4

La Partie 4 couvrira :
- Storage (localStorage, sessionStorage)
- Regular Expressions
- JSON en détail
- Bonnes pratiques et patterns
- Outils et écosystème JavaScript


[GUIDE] FICHIER SUIVANT : javascript_partie4.txt

Excellente progression ! Vous maîtrisez maintenant JavaScript moderne ! [RAPIDE]
"""
-e 

# ============================================================================
# PARTIE 4 : AVANCÉ ET PRATIQUE
# ============================================================================

# ============================================================================
# [LIVRE] JAVASCRIPT - PARTIE 4 : AVANCÉ ET PRATIQUE
# ============================================================================
#
# [OBJECTIF] CETTE PARTIE COUVRE :
# - Chapitre 15 : Storage (localStorage, sessionStorage)
# - Chapitre 16 : Regular Expressions (Regex)
# - Chapitre 17 : JSON
# - Chapitre 18 : Bonnes Pratiques et Patterns
# - Chapitre 19 : Outils et Écosystème
# - Chapitre 20 : Projets Pratiques Complets
#
# [TEMPS] TEMPS : ~12-15 heures
# [DOCS] PRÉREQUIS : Parties 1, 2 et 3 complétées
# ============================================================================


# ============================================================================
# [GUIDE] CHAPITRE 15 : WEB STORAGE
# ============================================================================

"""
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Utiliser localStorage
[OK] Utiliser sessionStorage
[OK] Comprendre les différences
[OK] Stocker objets complexes
[OK] Gérer l'expiration des données
[OK] Créer un système de cache
[OK] Bonnes pratiques de stockage
"""


# ----------------------------------------------------------------------------
# [SAUVEGARDE] LOCAL STORAGE
# ----------------------------------------------------------------------------

"""
LOCAL STORAGE

Stockage persistant dans le navigateur
Données restent même après fermeture du navigateur


CARACTÉRISTIQUES
"""

// Capacité : ~5-10 MB selon navigateur
// Persistance : Permanente (jusqu'à suppression manuelle)
// Portée : Par domaine (protocol + domaine + port)
// Format : Clé-valeur (strings uniquement)
// Synchrone : Opérations bloquantes

"""
[IDEE] ANALOGIE

localStorage = Disque dur
- Données persistent
- Disponibles entre sessions
- Capacité limitée mais confortable


MÉTHODES DE BASE
"""

// Stocker une valeur
localStorage.setItem('nom', 'Alice');
localStorage.setItem('age', '25');

// Récupérer une valeur
let nom = localStorage.getItem('nom');
console.log(nom); // 'Alice'

// Vérifier si clé existe
if (localStorage.getItem('nom') !== null) {
    console.log('Nom existe');
}

// Supprimer une clé
localStorage.removeItem('age');

// Tout supprimer
localStorage.clear();

// Nombre de clés
let count = localStorage.length;

// Obtenir clé par index
let key = localStorage.key(0);

"""
SYNTAXE ALTERNATIVE (accès direct)
"""

// Écrire
localStorage.nom = 'Bob';
localStorage['email'] = 'bob@example.com';

// Lire
console.log(localStorage.nom);       // 'Bob'
console.log(localStorage['email']);  // 'bob@example.com'

// [ATTENTION] Préférez setItem/getItem (plus sûr)


"""
STOCKER DES OBJETS

[ATTENTION] localStorage stocke seulement des strings !
"""

// [X] Stocker directement un objet (ne fonctionne pas)
let user = { nom: 'Alice', age: 25 };
localStorage.setItem('user', user);
console.log(localStorage.getItem('user')); // '[object Object]' [X]

// [OK] Convertir en JSON
let user = { nom: 'Alice', age: 25 };
localStorage.setItem('user', JSON.stringify(user));

// Récupérer et parser
let userString = localStorage.getItem('user');
let userObj = JSON.parse(userString);
console.log(userObj.nom); // 'Alice' [OK]

"""
[IDEE] PATTERN JSON

Toujours :
1. JSON.stringify() avant setItem()
2. JSON.parse() après getItem()


HELPER FUNCTIONS
"""

// Sauvegarder objet
function saveObject(key, obj) {
    try {
        localStorage.setItem(key, JSON.stringify(obj));
        return true;
    } catch (e) {
        console.error('Erreur sauvegarde:', e);
        return false;
    }
}

// Charger objet
function loadObject(key) {
    try {
        let item = localStorage.getItem(key);
        return item ? JSON.parse(item) : null;
    } catch (e) {
        console.error('Erreur chargement:', e);
        return null;
    }
}

// Utilisation
let user = { nom: 'Alice', age: 25 };
saveObject('user', user);
let loaded = loadObject('user');


"""
STOCKER DES TABLEAUX
"""

let todos = [
    { id: 1, titre: 'Apprendre JS', fait: true },
    { id: 2, titre: 'Créer projet', fait: false }
];

// Sauvegarder
localStorage.setItem('todos', JSON.stringify(todos));

// Charger
let savedTodos = JSON.parse(localStorage.getItem('todos') || '[]');


"""
ITÉRER SUR TOUTES LES CLÉS
"""

// Méthode 1 : Boucle for
for (let i = 0; i < localStorage.length; i++) {
    let key = localStorage.key(i);
    let value = localStorage.getItem(key);
    console.log(key, value);
}

// Méthode 2 : Object.keys
Object.keys(localStorage).forEach(key => {
    console.log(key, localStorage.getItem(key));
});


"""
EXEMPLE : PRÉFÉRENCES UTILISATEUR
"""

class UserPreferences {
    constructor() {
        this.storageKey = 'userPrefs';
        this.defaults = {
            theme: 'light',
            language: 'fr',
            fontSize: 16,
            notifications: true
        };
    }
    
    load() {
        try {
            let saved = localStorage.getItem(this.storageKey);
            return saved ? { ...this.defaults, ...JSON.parse(saved) } : this.defaults;
        } catch (e) {
            return this.defaults;
        }
    }
    
    save(prefs) {
        try {
            localStorage.setItem(this.storageKey, JSON.stringify(prefs));
            return true;
        } catch (e) {
            console.error('Erreur sauvegarde préférences:', e);
            return false;
        }
    }
    
    get(key) {
        let prefs = this.load();
        return prefs[key];
    }
    
    set(key, value) {
        let prefs = this.load();
        prefs[key] = value;
        this.save(prefs);
    }
    
    reset() {
        this.save(this.defaults);
    }
}

// Utilisation
let prefs = new UserPreferences();

// Charger préférences
let theme = prefs.get('theme');
console.log(theme); // 'light'

// Changer préférence
prefs.set('theme', 'dark');
prefs.set('fontSize', 18);

// Réinitialiser
prefs.reset();


# ----------------------------------------------------------------------------
# [SYNC] SESSION STORAGE
# ----------------------------------------------------------------------------

"""
SESSION STORAGE

Stockage temporaire pour la session
Données supprimées à la fermeture du navigateur


DIFFÉRENCES AVEC localStorage
"""

/*
┌────────────────┬──────────────────┬──────────────────┐
│ Caractéristique│ localStorage     │ sessionStorage   │
├────────────────┼──────────────────┼──────────────────┤
│ Persistance    │ Permanente       │ Session seulement│
│ Onglets        │ Partagé          │ Isolé par onglet │
│ Capacité       │ ~5-10 MB         │ ~5-10 MB         │
│ API            │ Identique        │ Identique        │
└────────────────┴──────────────────┴──────────────────┘
*/

"""
[IDEE] QUAND UTILISER ?

localStorage :
- Préférences utilisateur
- Données de cache
- État de l'application
- Paniers d'achat

sessionStorage :
- Formulaires multi-pages
- Données temporaires
- Transactions en cours
- État de navigation


API IDENTIQUE À localStorage
"""

// Stocker
sessionStorage.setItem('token', 'abc123');
sessionStorage.setItem('user', JSON.stringify({ nom: 'Alice' }));

// Récupérer
let token = sessionStorage.getItem('token');

// Supprimer
sessionStorage.removeItem('token');

// Tout effacer
sessionStorage.clear();


"""
EXEMPLE : FORMULAIRE MULTI-PAGES
"""

// Page 1 : Infos personnelles
document.querySelector('#nextBtn').addEventListener('click', () => {
    let formData = {
        nom: document.querySelector('#nom').value,
        email: document.querySelector('#email').value,
        telephone: document.querySelector('#tel').value
    };
    
    sessionStorage.setItem('formPage1', JSON.stringify(formData));
    window.location.href = 'page2.html';
});

// Page 2 : Adresse
window.addEventListener('load', () => {
    // Récupérer données page 1
    let page1Data = JSON.parse(sessionStorage.getItem('formPage1') || '{}');
    console.log('Données page 1:', page1Data);
});

document.querySelector('#submitBtn').addEventListener('click', () => {
    let page1Data = JSON.parse(sessionStorage.getItem('formPage1') || '{}');
    let page2Data = {
        adresse: document.querySelector('#adresse').value,
        ville: document.querySelector('#ville').value
    };
    
    let completeData = { ...page1Data, ...page2Data };
    
    // Envoyer au serveur
    fetch('/api/submit', {
        method: 'POST',
        body: JSON.stringify(completeData)
    });
    
    // Nettoyer
    sessionStorage.clear();
});


# ----------------------------------------------------------------------------
# [TEMPS] EXPIRATION DES DONNÉES
# ----------------------------------------------------------------------------

"""
AJOUTER EXPIRATION

localStorage n'a pas d'expiration native
On peut l'implémenter nous-mêmes
"""

class StorageWithExpiry {
    setItem(key, value, expiryMinutes) {
        let now = new Date();
        let item = {
            value: value,
            expiry: now.getTime() + (expiryMinutes * 60 * 1000)
        };
        localStorage.setItem(key, JSON.stringify(item));
    }
    
    getItem(key) {
        let itemStr = localStorage.getItem(key);
        
        if (!itemStr) {
            return null;
        }
        
        let item = JSON.parse(itemStr);
        let now = new Date();
        
        // Vérifier si expiré
        if (now.getTime() > item.expiry) {
            localStorage.removeItem(key);
            return null;
        }
        
        return item.value;
    }
    
    removeItem(key) {
        localStorage.removeItem(key);
    }
}

// Utilisation
let storage = new StorageWithExpiry();

// Sauvegarder avec expiration de 30 minutes
storage.setItem('token', 'abc123', 30);

// Récupérer (null si expiré)
let token = storage.getItem('token');


"""
SYSTÈME DE CACHE
"""

class Cache {
    constructor(prefix = 'cache_') {
        this.prefix = prefix;
    }
    
    set(key, data, ttlMinutes = 60) {
        let item = {
            data: data,
            timestamp: Date.now(),
            ttl: ttlMinutes * 60 * 1000
        };
        
        try {
            localStorage.setItem(
                this.prefix + key,
                JSON.stringify(item)
            );
        } catch (e) {
            console.error('Cache plein:', e);
            this.clearOldest();
        }
    }
    
    get(key) {
        try {
            let itemStr = localStorage.getItem(this.prefix + key);
            
            if (!itemStr) {
                return null;
            }
            
            let item = JSON.parse(itemStr);
            let age = Date.now() - item.timestamp;
            
            // Vérifier expiration
            if (age > item.ttl) {
                this.delete(key);
                return null;
            }
            
            return item.data;
        } catch (e) {
            return null;
        }
    }
    
    delete(key) {
        localStorage.removeItem(this.prefix + key);
    }
    
    clear() {
        Object.keys(localStorage)
            .filter(key => key.startsWith(this.prefix))
            .forEach(key => localStorage.removeItem(key));
    }
    
    clearOldest() {
        let items = [];
        
        Object.keys(localStorage)
            .filter(key => key.startsWith(this.prefix))
            .forEach(key => {
                try {
                    let item = JSON.parse(localStorage.getItem(key));
                    items.push({ key, timestamp: item.timestamp });
                } catch (e) {}
            });
        
        // Trier par timestamp
        items.sort((a, b) => a.timestamp - b.timestamp);
        
        // Supprimer les 25% plus anciens
        let toRemove = Math.ceil(items.length * 0.25);
        items.slice(0, toRemove).forEach(item => {
            localStorage.removeItem(item.key);
        });
    }
}

// Utilisation
let cache = new Cache();

// Mettre en cache avec TTL de 30 minutes
cache.set('users', userData, 30);

// Récupérer du cache
let users = cache.get('users');

if (!users) {
    // Cache expiré ou vide, charger depuis API
    users = await fetchUsers();
    cache.set('users', users, 30);
}


# ----------------------------------------------------------------------------
# [ATTENTION] LIMITES ET GESTION D'ERREURS
# ----------------------------------------------------------------------------

"""
LIMITES DE STOCKAGE

Quota dépassé -> QuotaExceededError
"""

function safeSetItem(key, value) {
    try {
        localStorage.setItem(key, value);
        return true;
    } catch (e) {
        if (e.name === 'QuotaExceededError') {
            console.error('Quota localStorage dépassé');
            
            // Nettoyer anciennes données
            clearOldData();
            
            // Réessayer
            try {
                localStorage.setItem(key, value);
                return true;
            } catch (e) {
                console.error('Toujours pas de place');
                return false;
            }
        }
        return false;
    }
}

function clearOldData() {
    // Stratégie : supprimer les données les plus anciennes
    let items = [];
    
    for (let i = 0; i < localStorage.length; i++) {
        let key = localStorage.key(i);
        items.push(key);
    }
    
    // Supprimer 30% des items
    let toRemove = Math.ceil(items.length * 0.3);
    items.slice(0, toRemove).forEach(key => {
        localStorage.removeItem(key);
    });
}


"""
VÉRIFIER DISPONIBILITÉ
"""

function isLocalStorageAvailable() {
    try {
        let test = '__localStorage_test__';
        localStorage.setItem(test, test);
        localStorage.removeItem(test);
        return true;
    } catch (e) {
        return false;
    }
}

// Utilisation
if (isLocalStorageAvailable()) {
    // Utiliser localStorage
} else {
    // Fallback : cookies, mémoire, etc.
    console.warn('localStorage non disponible');
}


"""
ÉVÉNEMENT STORAGE

Détecte changements dans localStorage (autres onglets)
"""

window.addEventListener('storage', (e) => {
    console.log('Storage changé:');
    console.log('Clé:', e.key);
    console.log('Ancienne valeur:', e.oldValue);
    console.log('Nouvelle valeur:', e.newValue);
    console.log('URL:', e.url);
    
    // Réagir au changement
    if (e.key === 'theme') {
        applyTheme(e.newValue);
    }
});

"""
[IDEE] EVENT STORAGE

- Déclenché seulement dans les AUTRES onglets
- Pas dans l'onglet qui fait le changement
- Utile pour synchroniser entre onglets


# ----------------------------------------------------------------------------
# [COURS] EXERCICE PRATIQUE : PANIER D'ACHAT
# ----------------------------------------------------------------------------

"""
EXERCICE : Panier d'achat persistant
"""

class ShoppingCart {
    constructor() {
        this.storageKey = 'shopping_cart';
        this.items = this.load();
    }
    
    load() {
        try {
            let saved = localStorage.getItem(this.storageKey);
            return saved ? JSON.parse(saved) : [];
        } catch (e) {
            return [];
        }
    }
    
    save() {
        try {
            localStorage.setItem(
                this.storageKey,
                JSON.stringify(this.items)
            );
        } catch (e) {
            console.error('Erreur sauvegarde panier:', e);
        }
    }
    
    addItem(product, quantity = 1) {
        let existing = this.items.find(item => item.id === product.id);
        
        if (existing) {
            existing.quantity += quantity;
        } else {
            this.items.push({
                id: product.id,
                nom: product.nom,
                prix: product.prix,
                quantity: quantity
            });
        }
        
        this.save();
    }
    
    removeItem(productId) {
        this.items = this.items.filter(item => item.id !== productId);
        this.save();
    }
    
    updateQuantity(productId, quantity) {
        let item = this.items.find(item => item.id === productId);
        
        if (item) {
            if (quantity <= 0) {
                this.removeItem(productId);
            } else {
                item.quantity = quantity;
                this.save();
            }
        }
    }
    
    clear() {
        this.items = [];
        this.save();
    }
    
    getItems() {
        return [...this.items];
    }
    
    getTotal() {
        return this.items.reduce((total, item) => {
            return total + (item.prix * item.quantity);
        }, 0);
    }
    
    getItemCount() {
        return this.items.reduce((count, item) => {
            return count + item.quantity;
        }, 0);
    }
}

// Utilisation
let cart = new ShoppingCart();

// Ajouter produits
cart.addItem({ id: 1, nom: 'Laptop', prix: 999 }, 1);
cart.addItem({ id: 2, nom: 'Souris', prix: 29 }, 2);

// Afficher panier
console.log('Items:', cart.getItems());
console.log('Total:', cart.getTotal() + '€');
console.log('Nombre items:', cart.getItemCount());

// Mettre à jour quantité
cart.updateQuantity(2, 3);

// Supprimer item
cart.removeItem(1);


# ============================================================================
# [GUIDE] CHAPITRE 16 : REGULAR EXPRESSIONS (REGEX)
# ============================================================================

"""
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Créer des regex
[OK] Tester des patterns
[OK] Extraire des informations
[OK] Remplacer du texte
[OK] Valider des formats
[OK] Utiliser les groupes de capture
[OK] Maîtriser les caractères spéciaux
"""


# ----------------------------------------------------------------------------
# [TEXTE] INTRODUCTION AUX REGEX
# ----------------------------------------------------------------------------

"""
REGEX = Regular Expression

Pattern de recherche dans du texte


ANALOGIE [RECHERCHE]

Recherche simple : Chercher mot exact "chat"
Regex : Chercher "tous les mots commençant par c et finissant par t"


CRÉER UNE REGEX
"""

// Syntaxe littérale (recommandée)
let regex1 = /pattern/;
let regex2 = /bonjour/;

// Avec constructeur
let regex3 = new RegExp('pattern');
let regex4 = new RegExp('bonjour');

// Avec flags
let regex5 = /pattern/gi;  // g = global, i = insensible à la casse

"""
[IDEE] FLAGS COURANTS

g : global (trouve toutes les occurrences)
i : insensible à la casse
m : multiline
s : dotAll (. matche aussi \n)
u : unicode
y : sticky


TESTER UN PATTERN
"""

let regex = /chat/;
let texte = 'J\'ai un chat noir';

// test() : Retourne true/false
console.log(regex.test(texte)); // true

// match() : Retourne correspondances
console.log(texte.match(regex)); // ['chat']

// search() : Retourne position
console.log(texte.search(regex)); // 9


# ----------------------------------------------------------------------------
# [OBJECTIF] CARACTÈRES SPÉCIAUX
# ----------------------------------------------------------------------------

"""
CARACTÈRES LITTÉRAUX

Cherche le caractère exact
"""

/chat/.test('chat');        // true
/123/.test('abc123');       // true
/Hello/.test('Hello World'); // true


"""
POINT . (N'IMPORTE QUEL CARACTÈRE)
"""

/c.at/.test('chat');  // true (. = h)
/c.at/.test('coat');  // true (. = o)
/c.at/.test('c@at');  // true (. = @)
/c.at/.test('cat');   // false (manque un caractère)

"""
ÉCHAPPEMENT \ (CARACTÈRES SPÉCIAUX)
"""

// Pour chercher caractère spécial littéralement
/\./  .test('3.14');    // true (cherche vraiment un point)
/\$/  .test('$100');    // true (cherche vraiment $)
/\?/  .test('Quoi?');   // true (cherche vraiment ?)

// Caractères à échapper : . * + ? ^ $ { } ( ) | [ ] \


"""
ANCRES ^ ET $
"""

// ^ : Début de string
/^Hello/.test('Hello world');  // true
/^Hello/.test('Say Hello');    // false (Hello pas au début)

// $ : Fin de string
/world$/.test('Hello world');  // true
/world$/.test('world Hello');  // false (world pas à la fin)

// Début ET fin
/^Hello$/.test('Hello');       // true (exactement "Hello")
/^Hello$/.test('Hello world'); // false


"""
CLASSES DE CARACTÈRES [ ]
"""

// Un caractère parmi plusieurs
/[aeiou]/.test('hello');   // true (contient 'e' ou 'o')
/[0-9]/.test('abc123');    // true (contient chiffre)
/[a-z]/.test('Hello');     // true (contient minuscule)
/[A-Z]/.test('Hello');     // true (contient majuscule)

// Plages combinées
/[a-zA-Z]/.test('A');      // true (lettre)
/[a-zA-Z0-9]/.test('5');   // true (alphanumérique)

// Négation ^
/[^0-9]/.test('123');      // false (tous sont des chiffres)
/[^0-9]/.test('12a3');     // true (contient non-chiffre 'a')


"""
RACCOURCIS
"""

// \d : Chiffre [0-9]
/\d/.test('abc123');    // true

// \D : Non-chiffre [^0-9]
/\D/.test('123abc');    // true

// \w : Mot [a-zA-Z0-9_]
/\w/.test('Hello_123'); // true

// \W : Non-mot [^a-zA-Z0-9_]
/\W/.test('Hello!');    // true (! n'est pas un mot)

// \s : Espace blanc (espace, tab, newline)
/\s/.test('Hello World'); // true

// \S : Non-espace
/\S/.test('  a  ');     // true


"""
QUANTIFICATEURS
"""

// * : 0 ou plus
/ab*c/.test('ac');     // true (0 b)
/ab*c/.test('abc');    // true (1 b)
/ab*c/.test('abbbbc'); // true (4 b)

// + : 1 ou plus
/ab+c/.test('ac');     // false (0 b)
/ab+c/.test('abc');    // true (1 b)
/ab+c/.test('abbc');   // true (2 b)

// ? : 0 ou 1
/colou?r/.test('color');   // true
/colou?r/.test('colour');  // true
/colou?r/.test('colouur'); // false

// {n} : Exactement n
/\d{3}/.test('12');    // false
/\d{3}/.test('123');   // true
/\d{3}/.test('1234');  // true (contient 123)

// {n,m} : Entre n et m
/\d{2,4}/.test('1');     // false
/\d{2,4}/.test('12');    // true
/\d{2,4}/.test('123');   // true
/\d{2,4}/.test('1234');  // true
/\d{2,4}/.test('12345'); // true (contient 1234)

// {n,} : Au moins n
/\d{2,}/.test('1');    // false
/\d{2,}/.test('12');   // true
/\d{2,}/.test('123');  // true


# ----------------------------------------------------------------------------
# [RECHERCHE] MÉTHODES STRING AVEC REGEX
# ----------------------------------------------------------------------------

"""
MATCH() : Trouver correspondances
"""

let texte = 'Les nombres sont 123, 456 et 789';

// Sans flag g : première correspondance
let match1 = texte.match(/\d+/);
console.log(match1);  // ['123', index: 17, ...]

// Avec flag g : toutes les correspondances
let match2 = texte.match(/\d+/g);
console.log(match2);  // ['123', '456', '789']


"""
MATCHALL() : Itérer sur toutes (ES2020)
"""

let texte = 'Email: alice@example.com et bob@test.org';
let regex = /(\w+)@(\w+\.\w+)/g;

for (let match of texte.matchAll(regex)) {
    console.log('Email complet:', match[0]);
    console.log('Nom:', match[1]);
    console.log('Domaine:', match[2]);
}


"""
SEARCH() : Position de la première correspondance
"""

let texte = 'Hello World';
console.log(texte.search(/World/));  // 6
console.log(texte.search(/xyz/));    // -1 (pas trouvé)


"""
REPLACE() : Remplacer
"""

let texte = 'Les chats sont mignons';

// Remplacer première occurrence
let result1 = texte.replace(/chat/, 'chien');
console.log(result1); // 'Les chiens sont mignons'

// Remplacer toutes (flag g)
let texte2 = 'chat chat chat';
let result2 = texte2.replace(/chat/g, 'chien');
console.log(result2); // 'chien chien chien'

// Avec fonction de remplacement
let texte3 = 'Prix: 100€, 200€, 300€';
let result3 = texte3.replace(/(\d+)€/g, (match, nombre) => {
    return (nombre * 1.2) + '€';  // +20%
});
console.log(result3); // 'Prix: 120€, 240€, 360€'


"""
REPLACEALL() : Remplacer toutes (ES2021)
"""

let texte = 'chat chat chat';
let result = texte.replaceAll(/chat/g, 'chien');
console.log(result); // 'chien chien chien'


"""
SPLIT() : Diviser avec regex
"""

let texte = 'un,deux;trois:quatre';
let parts = texte.split(/[,;:]/);
console.log(parts); // ['un', 'deux', 'trois', 'quatre']

// Espaces multiples
let phrase = 'Hello    World   Test';
let mots = phrase.split(/\s+/);
console.log(mots); // ['Hello', 'World', 'Test']


# ----------------------------------------------------------------------------
# [UTILISATEURS] GROUPES DE CAPTURE
# ----------------------------------------------------------------------------

"""
GROUPES ( )

Capturer parties du pattern
"""

let texte = 'alice@example.com';
let regex = /(\w+)@(\w+)\.(\w+)/;
let match = texte.match(regex);

console.log(match[0]); // 'alice@example.com' (tout)
console.log(match[1]); // 'alice' (groupe 1)
console.log(match[2]); // 'example' (groupe 2)
console.log(match[3]); // 'com' (groupe 3)


"""
GROUPES NOMMÉS (ES2018)
"""

let texte = 'alice@example.com';
let regex = /(?<user>\w+)@(?<domain>\w+)\.(?<tld>\w+)/;
let match = texte.match(regex);

console.log(match.groups.user);   // 'alice'
console.log(match.groups.domain); // 'example'
console.log(match.groups.tld);    // 'com'


"""
GROUPES NON-CAPTURANTS (?:)

Grouper sans capturer
"""

// Avec capture (3 groupes)
let regex1 = /(\d{2})-(\d{2})-(\d{4})/;

// Sans capture le tiret (pattern seulement)
let regex2 = /\d{2}(?:-\d{2}){2}/;  // Pas de groupes capturés


"""
BACKREFERENCES

Réutiliser groupe capturé
"""

// \1 référence groupe 1
let regex = /(\w+)\s+\1/;  // Mot répété
console.log(regex.test('hello hello')); // true
console.log(regex.test('hello world')); // false

// Trouver tags HTML fermants
let html = '<div>Contenu</div>';
let regex = /<(\w+)>.*<\/\1>/;
console.log(regex.test(html)); // true


# ----------------------------------------------------------------------------
# [OK] VALIDATIONS COURANTES
# ----------------------------------------------------------------------------

"""
VALIDER EMAIL
"""

function validerEmail(email) {
    let regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return regex.test(email);
}

console.log(validerEmail('alice@example.com')); // true
console.log(validerEmail('invalide'));          // false

// Plus strict
function validerEmailStrict(email) {
    let regex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
    return regex.test(email);
}


"""
VALIDER TÉLÉPHONE
"""

function validerTelephone(tel) {
    // Format : 06 12 34 56 78 ou 0612345678
    let regex = /^0[1-9](?:\s?\d{2}){4}$/;
    return regex.test(tel);
}

console.log(validerTelephone('06 12 34 56 78')); // true
console.log(validerTelephone('0612345678'));     // true
console.log(validerTelephone('123456'));         // false


"""
VALIDER MOT DE PASSE

Au moins 8 caractères, 1 majuscule, 1 minuscule, 1 chiffre
"""

function validerMotDePasse(mdp) {
    let regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/;
    return regex.test(mdp);
}

console.log(validerMotDePasse('Password123')); // true
console.log(validerMotDePasse('password'));    // false (pas de maj/chiffre)
console.log(validerMotDePasse('Pass1'));       // false (trop court)

"""
[IDEE] LOOKAHEAD (?=...)

Vérifie présence sans consommer caractères
(?=.*[A-Z]) : Vérifie qu'il y a une majuscule quelque part


VALIDER URL
"""

function validerURL(url) {
    let regex = /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/;
    return regex.test(url);
}

console.log(validerURL('https://example.com'));      // true
console.log(validerURL('http://www.example.com'));   // true
console.log(validerURL('invalide'));                 // false


"""
VALIDER CODE POSTAL FRANÇAIS
"""

function validerCodePostal(code) {
    let regex = /^[0-9]{5}$/;
    return regex.test(code);
}

console.log(validerCodePostal('75001')); // true
console.log(validerCodePostal('ABC12')); // false


"""
VALIDER DATE (JJ/MM/AAAA)
"""

function validerDate(date) {
    let regex = /^(0[1-9]|[12]\d|3[01])\/(0[1-9]|1[0-2])\/\d{4}$/;
    return regex.test(date);
}

console.log(validerDate('15/01/2024')); // true
console.log(validerDate('32/01/2024')); // false
console.log(validerDate('15-01-2024')); // false


"""
EXTRAIRE INFORMATIONS
"""

// Extraire tous les emails d'un texte
function extraireEmails(texte) {
    let regex = /[^\s@]+@[^\s@]+\.[^\s@]+/g;
    return texte.match(regex) || [];
}

let texte = 'Contacts: alice@example.com, bob@test.org';
console.log(extraireEmails(texte));
// ['alice@example.com', 'bob@test.org']

// Extraire hashtags
function extraireHashtags(texte) {
    let regex = /#(\w+)/g;
    let hashtags = [];
    let match;
    
    while ((match = regex.exec(texte)) !== null) {
        hashtags.push(match[1]);
    }
    
    return hashtags;
}

let tweet = 'Apprendre #JavaScript et #WebDev';
console.log(extraireHashtags(tweet));
// ['JavaScript', 'WebDev']


# Ce fichier continue dans javascript_partie4_suite.txt
# ============================================================================
# ============================================================================
# [LIVRE] JAVASCRIPT - PARTIE 4 (SUITE) : JSON, BONNES PRATIQUES ET PROJETS
# ============================================================================
#
# [OBJECTIF] SUITE DE LA PARTIE 4
# - Chapitre 17 : JSON
# - Chapitre 18 : Bonnes Pratiques et Patterns
# - Chapitre 19 : Outils et Écosystème
# - Chapitre 20 : Projets Pratiques Complets
#
# [TEMPS] TEMPS : ~6-8 heures
# [DOCS] PRÉREQUIS : Chapitres précédents complétés
# ============================================================================


# ============================================================================
# [GUIDE] CHAPITRE 17 : JSON (JavaScript Object Notation)
# ============================================================================

"""
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Comprendre le format JSON
[OK] Convertir objets <-> JSON
[OK] Valider JSON
[OK] Gérer erreurs de parsing
[OK] Utiliser JSON avec APIs
[OK] Manipuler JSON complexe
"""


# ----------------------------------------------------------------------------
# [FICHIER] QU'EST-CE QUE JSON ?
# ----------------------------------------------------------------------------

"""
JSON = JavaScript Object Notation

Format léger d'échange de données
Lisible par humains et machines


CARACTÉRISTIQUES
"""

// - Format texte
// - Indépendant du langage
// - Basé sur objets JavaScript
// - Clés entre guillemets doubles
// - Pas de commentaires
// - Pas de fonctions

"""
TYPES DE DONNÉES JSON

1. String : "texte"
2. Number : 42, 3.14
3. Boolean : true, false
4. null : null
5. Array : [1, 2, 3]
6. Object : {"clé": "valeur"}


SYNTAXE JSON VALIDE
"""

{
    "nom": "Alice",
    "age": 25,
    "estActif": true,
    "adresse": {
        "rue": "123 Main St",
        "ville": "Paris",
        "codePostal": "75001"
    },
    "hobbies": ["lecture", "sport", "musique"],
    "salaire": null
}

"""
[IDEE] RÈGLES JSON

[OK] Clés TOUJOURS entre guillemets doubles
[OK] Strings entre guillemets doubles (pas simples)
[OK] Pas de trailing comma
[OK] Pas de undefined
[OK] Pas de fonctions ou Date


# ----------------------------------------------------------------------------
# [SYNC] JSON.STRINGIFY() - Objet -> JSON
# ----------------------------------------------------------------------------

"""
CONVERTIR OBJET EN JSON STRING
"""

let user = {
    nom: 'Alice',
    age: 25,
    actif: true
};

let json = JSON.stringify(user);
console.log(json);
// '{"nom":"Alice","age":25,"actif":true}'

console.log(typeof json); // 'string'

"""
AVEC FORMATAGE (PRETTY PRINT)
"""

// 3ème argument = indentation
let json = JSON.stringify(user, null, 2);
console.log(json);
/*
{
  "nom": "Alice",
  "age": 25,
  "actif": true
}
*/

// Avec tabulation
let jsonTab = JSON.stringify(user, null, '\t');


"""
FILTRER PROPRIÉTÉS (REPLACER)
"""

let user = {
    nom: 'Alice',
    age: 25,
    motDePasse: 'secret',
    email: 'alice@example.com'
};

// 2ème argument : tableau de clés à inclure
let json = JSON.stringify(user, ['nom', 'email']);
console.log(json);
// '{"nom":"Alice","email":"alice@example.com"}'

// Ou fonction
let json2 = JSON.stringify(user, (key, value) => {
    // Exclure motDePasse
    if (key === 'motDePasse') return undefined;
    return value;
});


"""
MÉTHODE toJSON()

Personnaliser serialization
"""

class Utilisateur {
    constructor(nom, age, motDePasse) {
        this.nom = nom;
        this.age = age;
        this.motDePasse = motDePasse;
    }
    
    // Méthode appelée automatiquement
    toJSON() {
        return {
            nom: this.nom,
            age: this.age
            // motDePasse exclu
        };
    }
}

let user = new Utilisateur('Alice', 25, 'secret');
let json = JSON.stringify(user);
console.log(json);
// '{"nom":"Alice","age":25}'


"""
GÉRER TYPES SPÉCIAUX
"""

let data = {
    date: new Date(),
    regex: /test/,
    fonction: function() {},
    undefined: undefined,
    null: null,
    nan: NaN,
    infinity: Infinity
};

let json = JSON.stringify(data);
console.log(json);
// '{"date":"2024-01-15T10:30:00.000Z","null":null}'

/*
[IDEE] CE QUI EST PERDU :
- regex -> objet vide {}
- fonction -> supprimée
- undefined -> supprimée
- NaN/Infinity -> null
- Date -> string ISO
*/


# ----------------------------------------------------------------------------
# [SYNC] JSON.PARSE() - JSON -> Objet
# ----------------------------------------------------------------------------

"""
CONVERTIR JSON STRING EN OBJET
"""

let json = '{"nom":"Alice","age":25,"actif":true}';
let user = JSON.parse(json);

console.log(user.nom);  // 'Alice'
console.log(user.age);  // 25
console.log(typeof user); // 'object'


"""
REVIVER FUNCTION

Transformer valeurs lors du parsing
"""

let json = '{"nom":"Alice","dateNaissance":"1998-05-15"}';

let user = JSON.parse(json, (key, value) => {
    // Convertir strings de date en Date
    if (key === 'dateNaissance') {
        return new Date(value);
    }
    return value;
});

console.log(user.dateNaissance instanceof Date); // true


"""
GESTION D'ERREURS

JSON invalide -> SyntaxError
"""

function parseJSON(jsonString) {
    try {
        return JSON.parse(jsonString);
    } catch (e) {
        console.error('JSON invalide:', e.message);
        return null;
    }
}

// JSON valide
let result1 = parseJSON('{"nom":"Alice"}');
console.log(result1); // { nom: 'Alice' }

// JSON invalide
let result2 = parseJSON('{nom:"Alice"}'); // Guillemets manquants
console.log(result2); // null


"""
VALIDER JSON
"""

function estJSONValide(str) {
    try {
        JSON.parse(str);
        return true;
    } catch (e) {
        return false;
    }
}

console.log(estJSONValide('{"nom":"Alice"}')); // true
console.log(estJSONValide('{nom:"Alice"}'));   // false


# ----------------------------------------------------------------------------
# [WEB] JSON ET APIs
# ----------------------------------------------------------------------------

"""
ENVOYER JSON À UNE API
"""

async function creerUtilisateur(userData) {
    try {
        let response = await fetch('/api/users', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(userData)
        });
        
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}`);
        }
        
        let data = await response.json();
        return data;
    } catch (err) {
        console.error('Erreur:', err);
        return null;
    }
}

// Utilisation
let newUser = await creerUtilisateur({
    nom: 'Alice',
    email: 'alice@example.com'
});


"""
RECEVOIR JSON D'UNE API
"""

async function obtenirUtilisateurs() {
    try {
        let response = await fetch('/api/users');
        
        // Vérifier type de contenu
        let contentType = response.headers.get('content-type');
        if (!contentType || !contentType.includes('application/json')) {
            throw new TypeError('Réponse non-JSON');
        }
        
        let users = await response.json();
        return users;
    } catch (err) {
        console.error('Erreur:', err);
        return [];
    }
}


"""
DEEP CLONE AVEC JSON

[ATTENTION] Limitations : perd fonctions, Date devient string, etc.
"""

let original = {
    nom: 'Alice',
    adresse: {
        ville: 'Paris'
    }
};

// Deep clone via JSON
let clone = JSON.parse(JSON.stringify(original));

clone.adresse.ville = 'Lyon';
console.log(original.adresse.ville); // 'Paris' (pas affecté)

"""
[IDEE] ALTERNATIVE MODERNE

structuredClone() (ES2022) - Meilleur que JSON
"""
let clone2 = structuredClone(original);


# ----------------------------------------------------------------------------
# [COURS] EXERCICE : API CLIENT
# ----------------------------------------------------------------------------

"""
EXERCICE : Client API complet avec gestion JSON
"""

class APIClient {
    constructor(baseURL) {
        this.baseURL = baseURL;
        this.defaultHeaders = {
            'Content-Type': 'application/json'
        };
    }
    
    setAuthToken(token) {
        this.defaultHeaders['Authorization'] = `Bearer ${token}`;
    }
    
    async request(endpoint, options = {}) {
        let url = this.baseURL + endpoint;
        
        let config = {
            ...options,
            headers: {
                ...this.defaultHeaders,
                ...options.headers
            }
        };
        
        // Convertir body en JSON si objet
        if (config.body && typeof config.body === 'object') {
            config.body = JSON.stringify(config.body);
        }
        
        try {
            let response = await fetch(url, config);
            
            // Gérer erreurs HTTP
            if (!response.ok) {
                let errorData = await response.json().catch(() => ({}));
                throw new Error(
                    errorData.message || `HTTP ${response.status}`
                );
            }
            
            // Parser JSON response
            let data = await response.json();
            return { success: true, data };
            
        } catch (err) {
            return { success: false, error: err.message };
        }
    }
    
    get(endpoint) {
        return this.request(endpoint, { method: 'GET' });
    }
    
    post(endpoint, data) {
        return this.request(endpoint, {
            method: 'POST',
            body: data
        });
    }
    
    put(endpoint, data) {
        return this.request(endpoint, {
            method: 'PUT',
            body: data
        });
    }
    
    delete(endpoint) {
        return this.request(endpoint, { method: 'DELETE' });
    }
}

// Utilisation
let api = new APIClient('https://jsonplaceholder.typicode.com');

// GET
let result = await api.get('/users/1');
if (result.success) {
    console.log('User:', result.data);
}

// POST
let newPost = await api.post('/posts', {
    title: 'Mon article',
    body: 'Contenu...',
    userId: 1
});


# ============================================================================
# [GUIDE] CHAPITRE 18 : BONNES PRATIQUES ET PATTERNS
# ============================================================================

"""
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Écrire du code propre et lisible
[OK] Appliquer les conventions
[OK] Utiliser les design patterns
[OK] Optimiser les performances
[OK] Éviter les erreurs courantes
[OK] Organiser votre code
"""


# ----------------------------------------------------------------------------
# [NOTE] CONVENTIONS DE NOMMAGE
# ----------------------------------------------------------------------------

"""
CAMELCASE (recommandé)
"""

// Variables et fonctions
let userName = 'Alice';
let isActive = true;
let maxCount = 100;

function getUserData() {}
function calculateTotal() {}

"""
PASCALCASE

Pour classes et constructeurs
"""

class UserAccount {}
class ShoppingCart {}
function Person() {}  // Constructeur

"""
SNAKE_CASE

Pour constantes
"""

const API_URL = 'https://api.example.com';
const MAX_RETRY_ATTEMPTS = 3;
const DEFAULT_TIMEOUT = 5000;

"""
NOMS DESCRIPTIFS

[OK] Clairs et explicites
[X] Abrégés ou cryptiques
"""

// [X] Mauvais
let d = new Date();
let usr = { n: 'Alice' };
function calc(a, b) { return a + b; }

// [OK] Bon
let currentDate = new Date();
let user = { nom: 'Alice' };
function calculateTotal(price, quantity) {
    return price * quantity;
}


"""
BOOLÉENS

Préfixer avec is, has, can, should
"""

let isActive = true;
let hasPermission = false;
let canEdit = true;
let shouldUpdate = false;


# ----------------------------------------------------------------------------
# [DESIGN] CODE PROPRE
# ----------------------------------------------------------------------------

"""
PRINCIPE DRY (Don't Repeat Yourself)

Éviter duplication de code
"""

// [X] Duplication
function calculateCircleArea(radius) {
    return 3.14159 * radius * radius;
}
function calculateCirclePerimeter(radius) {
    return 2 * 3.14159 * radius;
}

// [OK] Extraire constante
const PI = 3.14159;

function calculateCircleArea(radius) {
    return PI * radius * radius;
}
function calculateCirclePerimeter(radius) {
    return 2 * PI * radius;
}


"""
FONCTIONS COURTES

Une fonction = Une responsabilité
"""

// [X] Fonction trop longue
function processUser(user) {
    // Valider
    if (!user.email) return false;
    if (!user.nom) return false;
    
    // Formater
    user.email = user.email.toLowerCase();
    user.nom = user.nom.trim();
    
    // Sauvegarder
    fetch('/api/users', {
        method: 'POST',
        body: JSON.stringify(user)
    });
    
    // Logger
    console.log('User créé:', user);
}

// [OK] Découper en fonctions
function validateUser(user) {
    return user.email && user.nom;
}

function formatUser(user) {
    return {
        ...user,
        email: user.email.toLowerCase(),
        nom: user.nom.trim()
    };
}

async function saveUser(user) {
    return fetch('/api/users', {
        method: 'POST',
        body: JSON.stringify(user)
    });
}

async function processUser(user) {
    if (!validateUser(user)) return false;
    
    let formatted = formatUser(user);
    await saveUser(formatted);
    console.log('User créé:', formatted);
    
    return true;
}


"""
EARLY RETURN

Sortir tôt des fonctions
"""

// [X] Imbrication profonde
function calculateDiscount(user, amount) {
    if (user) {
        if (user.isPremium) {
            if (amount > 100) {
                return amount * 0.2;
            } else {
                return amount * 0.1;
            }
        } else {
            return 0;
        }
    }
    return 0;
}

// [OK] Early return
function calculateDiscount(user, amount) {
    if (!user || !user.isPremium) return 0;
    
    return amount > 100 ? amount * 0.2 : amount * 0.1;
}


"""
ÉVITER MAGIC NUMBERS

Utiliser constantes nommées
"""

// [X] Magic numbers
function calculatePrice(price) {
    if (price > 100) {
        return price * 0.9;
    }
    return price;
}

// [OK] Constantes nommées
const DISCOUNT_THRESHOLD = 100;
const DISCOUNT_RATE = 0.1;

function calculatePrice(price) {
    if (price > DISCOUNT_THRESHOLD) {
        return price * (1 - DISCOUNT_RATE);
    }
    return price;
}


# ----------------------------------------------------------------------------
# [CONSTRUCTION] DESIGN PATTERNS
# ----------------------------------------------------------------------------

"""
MODULE PATTERN

Encapsuler code et créer portée privée
"""

const CounterModule = (function() {
    // Privé
    let count = 0;
    
    function logCount() {
        console.log(`Count: ${count}`);
    }
    
    // Public
    return {
        increment() {
            count++;
            logCount();
        },
        
        decrement() {
            count--;
            logCount();
        },
        
        getCount() {
            return count;
        }
    };
})();

// Utilisation
CounterModule.increment(); // Count: 1
console.log(CounterModule.getCount()); // 1
// console.log(CounterModule.count); // undefined (privé)


"""
SINGLETON PATTERN

Une seule instance
"""

class Database {
    constructor() {
        if (Database.instance) {
            return Database.instance;
        }
        
        this.connection = null;
        Database.instance = this;
    }
    
    connect() {
        if (!this.connection) {
            this.connection = 'Connected';
            console.log('Database connected');
        }
    }
}

let db1 = new Database();
let db2 = new Database();
console.log(db1 === db2); // true (même instance)


"""
FACTORY PATTERN

Créer objets sans spécifier classe exacte
"""

class UserFactory {
    createUser(type, data) {
        switch(type) {
            case 'admin':
                return new Admin(data);
            case 'moderator':
                return new Moderator(data);
            case 'user':
                return new RegularUser(data);
            default:
                throw new Error('Type invalide');
        }
    }
}

// Utilisation
let factory = new UserFactory();
let admin = factory.createUser('admin', { nom: 'Alice' });
let user = factory.createUser('user', { nom: 'Bob' });


"""
OBSERVER PATTERN

Notification de changements
"""

class Observable {
    constructor() {
        this.observers = [];
    }
    
    subscribe(fn) {
        this.observers.push(fn);
    }
    
    unsubscribe(fn) {
        this.observers = this.observers.filter(obs => obs !== fn);
    }
    
    notify(data) {
        this.observers.forEach(obs => obs(data));
    }
}

// Utilisation
let subject = new Observable();

// Observer 1
subject.subscribe(data => {
    console.log('Observer 1:', data);
});

// Observer 2
subject.subscribe(data => {
    console.log('Observer 2:', data);
});

// Notifier tous les observers
subject.notify('Hello!');
// Observer 1: Hello!
// Observer 2: Hello!


"""
REVEALING MODULE PATTERN

Clarifier API publique
"""

const Calculator = (function() {
    // Privé
    function add(a, b) {
        return a + b;
    }
    
    function subtract(a, b) {
        return a - b;
    }
    
    function multiply(a, b) {
        return a * b;
    }
    
    function divide(a, b) {
        if (b === 0) throw new Error('Division par zéro');
        return a / b;
    }
    
    // Révéler API publique
    return {
        add,
        subtract,
        multiply,
        divide
    };
})();


# ----------------------------------------------------------------------------
# [RAPIDE] OPTIMISATION PERFORMANCES
# ----------------------------------------------------------------------------

"""
DEBOUNCING

Limiter fréquence d'appel de fonction
"""

function debounce(func, delay) {
    let timeoutId;
    
    return function(...args) {
        clearTimeout(timeoutId);
        
        timeoutId = setTimeout(() => {
            func.apply(this, args);
        }, delay);
    };
}

// Utilisation : recherche avec API
let searchInput = document.querySelector('#search');
let debouncedSearch = debounce(async (term) => {
    let results = await fetch(`/api/search?q=${term}`);
    // Afficher résultats...
}, 300);

searchInput.addEventListener('input', (e) => {
    debouncedSearch(e.target.value);
});


"""
THROTTLING

Limiter taux d'exécution
"""

function throttle(func, limit) {
    let inThrottle;
    
    return function(...args) {
        if (!inThrottle) {
            func.apply(this, args);
            inThrottle = true;
            
            setTimeout(() => {
                inThrottle = false;
            }, limit);
        }
    };
}

// Utilisation : scroll event
let throttledScroll = throttle(() => {
    console.log('Scroll position:', window.scrollY);
}, 1000);

window.addEventListener('scroll', throttledScroll);


"""
MEMOIZATION

Cacher résultats de fonctions
"""

function memoize(fn) {
    let cache = {};
    
    return function(...args) {
        let key = JSON.stringify(args);
        
        if (key in cache) {
            console.log('From cache');
            return cache[key];
        }
        
        console.log('Computing...');
        let result = fn.apply(this, args);
        cache[key] = result;
        return result;
    };
}

// Fonction coûteuse
function fibonacci(n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

let memoizedFib = memoize(fibonacci);

console.log(memoizedFib(40)); // Computing... (lent)
console.log(memoizedFib(40)); // From cache (instantané)


"""
LAZY LOADING

Charger ressources à la demande
"""

// Images lazy loading
document.querySelectorAll('img[data-src]').forEach(img => {
    let observer = new IntersectionObserver((entries) => {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                let img = entry.target;
                img.src = img.dataset.src;
                observer.unobserve(img);
            }
        });
    });
    
    observer.observe(img);
});


# ----------------------------------------------------------------------------
# [COURS] EXERCICE : APPLICATION COMPLÈTE
# ----------------------------------------------------------------------------

"""
PROJET : Gestionnaire de Tâches Complet

Application utilisant toutes les bonnes pratiques
"""

// ====== config.js ======
const CONFIG = {
    STORAGE_KEY: 'tasks',
    MAX_TASKS: 100
};

// ====== utils.js ======
const Utils = {
    generateId() {
        return Date.now() + Math.random().toString(36).substr(2, 9);
    },
    
    formatDate(date) {
        return new Intl.DateTimeFormat('fr-FR').format(date);
    },
    
    debounce(func, delay) {
        let timeoutId;
        return function(...args) {
            clearTimeout(timeoutId);
            timeoutId = setTimeout(() => func.apply(this, args), delay);
        };
    }
};

// ====== storage.js ======
const Storage = {
    save(key, data) {
        try {
            localStorage.setItem(key, JSON.stringify(data));
            return true;
        } catch (e) {
            console.error('Storage error:', e);
            return false;
        }
    },
    
    load(key) {
        try {
            let data = localStorage.getItem(key);
            return data ? JSON.parse(data) : null;
        } catch (e) {
            console.error('Load error:', e);
            return null;
        }
    }
};

// ====== Task.js ======
class Task {
    constructor(title, description = '') {
        this.id = Utils.generateId();
        this.title = title;
        this.description = description;
        this.completed = false;
        this.createdAt = new Date();
    }
    
    toggle() {
        this.completed = !this.completed;
    }
    
    update(data) {
        if (data.title) this.title = data.title;
        if (data.description !== undefined) {
            this.description = data.description;
        }
    }
    
    toJSON() {
        return {
            id: this.id,
            title: this.title,
            description: this.description,
            completed: this.completed,
            createdAt: this.createdAt.toISOString()
        };
    }
    
    static fromJSON(json) {
        let task = new Task(json.title, json.description);
        task.id = json.id;
        task.completed = json.completed;
        task.createdAt = new Date(json.createdAt);
        return task;
    }
}

// ====== TaskManager.js ======
class TaskManager {
    constructor() {
        this.tasks = [];
        this.observers = [];
        this.load();
    }
    
    load() {
        let data = Storage.load(CONFIG.STORAGE_KEY);
        if (data) {
            this.tasks = data.map(json => Task.fromJSON(json));
        }
    }
    
    save() {
        Storage.save(CONFIG.STORAGE_KEY, this.tasks);
        this.notify();
    }
    
    addTask(title, description) {
        if (this.tasks.length >= CONFIG.MAX_TASKS) {
            throw new Error('Maximum de tâches atteint');
        }
        
        let task = new Task(title, description);
        this.tasks.push(task);
        this.save();
        return task;
    }
    
    deleteTask(id) {
        this.tasks = this.tasks.filter(task => task.id !== id);
        this.save();
    }
    
    toggleTask(id) {
        let task = this.tasks.find(t => t.id === id);
        if (task) {
            task.toggle();
            this.save();
        }
    }
    
    updateTask(id, data) {
        let task = this.tasks.find(t => t.id === id);
        if (task) {
            task.update(data);
            this.save();
        }
    }
    
    getTasks(filter = 'all') {
        switch(filter) {
            case 'active':
                return this.tasks.filter(t => !t.completed);
            case 'completed':
                return this.tasks.filter(t => t.completed);
            default:
                return this.tasks;
        }
    }
    
    // Observer pattern
    subscribe(fn) {
        this.observers.push(fn);
    }
    
    notify() {
        this.observers.forEach(fn => fn(this.tasks));
    }
}

// ====== app.js ======
const app = {
    manager: new TaskManager(),
    currentFilter: 'all',
    
    init() {
        this.bindEvents();
        this.manager.subscribe(() => this.render());
        this.render();
    },
    
    bindEvents() {
        document.querySelector('#addTaskForm')
            .addEventListener('submit', (e) => {
                e.preventDefault();
                this.handleAddTask();
            });
        
        document.querySelector('#taskList')
            .addEventListener('click', (e) => {
                this.handleTaskAction(e);
            });
        
        document.querySelectorAll('.filter-btn')
            .forEach(btn => {
                btn.addEventListener('click', (e) => {
                    this.currentFilter = e.target.dataset.filter;
                    this.render();
                });
            });
    },
    
    handleAddTask() {
        let title = document.querySelector('#taskTitle').value.trim();
        let desc = document.querySelector('#taskDesc').value.trim();
        
        if (!title) return;
        
        try {
            this.manager.addTask(title, desc);
            document.querySelector('#addTaskForm').reset();
        } catch (err) {
            alert(err.message);
        }
    },
    
    handleTaskAction(e) {
        let taskId = e.target.closest('[data-task-id]')?.dataset.taskId;
        if (!taskId) return;
        
        if (e.target.matches('.toggle-btn')) {
            this.manager.toggleTask(taskId);
        } else if (e.target.matches('.delete-btn')) {
            if (confirm('Supprimer cette tâche ?')) {
                this.manager.deleteTask(taskId);
            }
        }
    },
    
    render() {
        let tasks = this.manager.getTasks(this.currentFilter);
        let html = tasks.map(task => this.renderTask(task)).join('');
        document.querySelector('#taskList').innerHTML = html;
        this.updateStats();
    },
    
    renderTask(task) {
        return `
            <li class="task ${task.completed ? 'completed' : ''}" data-task-id="${task.id}">
                <input type="checkbox" class="toggle-btn" ${task.completed ? 'checked' : ''}>
                <div class="task-content">
                    <h3>${task.title}</h3>
                    ${task.description ? `<p>${task.description}</p>` : ''}
                    <small>${Utils.formatDate(task.createdAt)}</small>
                </div>
                <button class="delete-btn">×</button>
            </li>
        `;
    },
    
    updateStats() {
        let total = this.manager.tasks.length;
        let active = this.manager.getTasks('active').length;
        let completed = this.manager.getTasks('completed').length;
        
        document.querySelector('#stats').textContent = 
            `${total} total, ${active} actives, ${completed} terminées`;
    }
};

// Démarrer l'application
app.init();


# ============================================================================
# [DOCS] RÉCAPITULATIF PARTIE 4 COMPLÈTE
# ============================================================================

"""
[BRAVO] PARTIE 4 TERMINÉE !

RÉCAPITULATIF :

Chapitre 15 : Storage
[OK] localStorage et sessionStorage
[OK] Stockage d'objets avec JSON
[OK] Gestion expiration
[OK] Cache système
[OK] Gestion erreurs et quotas

Chapitre 16 : Regular Expressions
[OK] Syntaxe regex
[OK] Caractères spéciaux
[OK] Groupes de capture
[OK] Validations courantes
[OK] Méthodes String avec regex

Chapitre 17 : JSON
[OK] JSON.stringify() et JSON.parse()
[OK] Formatage et filtrage
[OK] Gestion erreurs
[OK] Utilisation avec APIs
[OK] Deep cloning

Chapitre 18 : Bonnes Pratiques
[OK] Conventions de nommage
[OK] Code propre (DRY, KISS)
[OK] Design patterns
[OK] Optimisation performances
[OK] Debouncing et throttling


[OBJECTIF] VOUS MAÎTRISEZ MAINTENANT :

[OK] Stocker données côté client
[OK] Valider et manipuler texte avec regex
[OK] Échanger données avec JSON
[OK] Écrire code propre et maintenable
[OK] Appliquer design patterns
[OK] Optimiser performances


[GRAPHIQUE] GUIDE JAVASCRIPT COMPLET !

Vous avez terminé les 4 parties du guide :
- Partie 1 : Fondamentaux
- Partie 2 : Structures de données
- Partie 3 : Programmation moderne
- Partie 4 : Avancé et pratique

Vous êtes maintenant capable de créer des applications
JavaScript complètes et professionnelles ! [RAPIDE]


[COURS] PROCHAINES ÉTAPES :

1. Pratiquer avec projets réels
2. Explorer frameworks (React, Vue, Angular)
3. Approfondir Node.js pour backend
4. Apprendre TypeScript
5. Contribuer à projets open-source


FÉLICITATIONS ! [BRAVO]
"""