# Fichier: python_cheats/cheatsheets/javascript.txt
# Cheatsheet JavaScript - Guide Complet pour Débutants


[OK] INTRODUCTION & BASES

# JavaScript (JS) est un langage de programmation:
# [OK] Interprété (pas besoin de compilation)
# [OK] Dynamiquement typé (pas besoin de déclarer les types)
# [OK] Multi-paradigme (impératif, orienté objet, fonctionnel)
# [OK] Langage du web (s'exécute dans le navigateur)
# [OK] Aussi utilisé côté serveur (Node.js)

# Où utiliser JavaScript:
# 1. Dans le navigateur (client-side)
# 2. Sur un serveur avec Node.js (server-side)
# 3. Applications mobiles (React Native, Ionic)
# 4. Applications desktop (Electron)

# Comment exécuter du JavaScript:
# 1. Console du navigateur (F12 > Console)
# 2. Fichier HTML avec balise <script>
# 3. Node.js dans le terminal: node script.js


[OK] INCLURE JAVASCRIPT DANS HTML

<!-- Méthode 1: Script interne -->
<!DOCTYPE html>
<html>
<head>
    <title>Ma Page</title>
</head>
<body>
    <h1>Hello World</h1>
    
    <!-- Script en fin de body (recommandé) -->
    <script>
        console.log('Hello from JavaScript!');
    </script>
</body>
</html>

<!-- Méthode 2: Script externe (RECOMMANDÉ) -->
<!DOCTYPE html>
<html>
<head>
    <title>Ma Page</title>
</head>
<body>
    <h1>Hello World</h1>
    
    <!-- Fichier externe -->
    <script src="script.js"></script>
    
    <!-- Avec defer (charge en parallèle, exécute après HTML) -->
    <script src="script.js" defer></script>
    
    <!-- Avec async (charge et exécute immédiatement) -->
    <script src="script.js" async></script>
</body>
</html>

<!-- Méthode 3: Script dans head avec defer -->
<!DOCTYPE html>
<html>
<head>
    <title>Ma Page</title>
    <script src="script.js" defer></script>
</head>
<body>
    <h1>Hello World</h1>
</body>
</html>

# Différences defer vs async:
# defer: télécharge en parallèle, exécute après parsing HTML (ordre préservé)
# async: télécharge en parallèle, exécute dès que prêt (ordre non garanti)
# Aucun: bloque le parsing HTML pendant téléchargement et exécution


[OK] CONSOLE & DEBUGGING

// Afficher dans la console
console.log('Message simple');
console.log('Plusieurs', 'valeurs', 123);
console.log('Variable:', maVariable);

// Types de messages
console.info('Information');          // Info (icône bleue)
console.warn('Attention!');           // Avertissement (jaune)
console.error('Erreur!');             // Erreur (rouge)
console.debug('Debug info');          // Debug

// Affichage avancé
console.table([{nom: 'Alice', age: 25}, {nom: 'Bob', age: 30}]);
console.dir(objetComplexe);           // Affiche propriétés objet
console.group('Groupe');              // Début groupe
console.log('Message dans groupe');
console.groupEnd();                   // Fin groupe

// Mesurer le temps
console.time('MonTimer');
// ... code à mesurer ...
console.timeEnd('MonTimer');          // Affiche: MonTimer: 123.45ms

// Compter les appels
console.count('Compteur');            // Compteur: 1
console.count('Compteur');            // Compteur: 2
console.countReset('Compteur');       // Remet à zéro

// Effacer la console
console.clear();

// Assertion (affiche erreur si faux)
console.assert(age >= 18, 'Age doit être >= 18');

// Trace de la pile d'appels
console.trace();


[OK] VARIABLES & CONSTANTES

// === LET (moderne, recommandé) ===
let nom = 'Alice';              // Déclaration et assignation
let age = 25;
let ville;                      // Déclaration sans valeur (undefined)

ville = 'Paris';                // Assignation plus tard

// Réassignation possible
age = 26;                       // [OK] Fonctionne
age = age + 1;                  // [OK] Fonctionne

// Scope (portée) de bloc
{
    let x = 10;
    console.log(x);             // 10
}
console.log(x);                 // Erreur! x n'existe pas ici

// === CONST (constante, recommandé) ===
const PI = 3.14159;
const PAYS = 'France';
const MAX_USERS = 100;

// Réassignation impossible
PI = 3.14;                      // [X] Erreur!

// MAIS: contenu d'objets/arrays modifiable
const person = {nom: 'Alice'};
person.nom = 'Bob';             // [OK] Fonctionne (modifie propriété)
person.age = 25;                // [OK] Fonctionne (ajoute propriété)
person = {};                    // [X] Erreur! (réassignation)

const numbers = [1, 2, 3];
numbers.push(4);                // [OK] Fonctionne (modifie contenu)
numbers = [];                   // [X] Erreur! (réassignation)

// === VAR (ancien, éviter) ===
var ancienneVariable = 'valeur';

// Problèmes avec var:
// 1. Scope de fonction (pas de bloc)
// 2. Hoisting (remontée)
// 3. Redéclaration possible

if (true) {
    var x = 10;
}
console.log(x);                 // 10 (fuite du scope!)

// Hoisting (remontée)
console.log(y);                 // undefined (pas d'erreur!)
var y = 5;

// Équivalent à:
var y;
console.log(y);
y = 5;

// === RÈGLES DE NOMMAGE ===

// [OK] Valides
let userName = 'Alice';         // camelCase (recommandé)
let user_name = 'Alice';        // snake_case
let _private = 'secret';        // Commence par _
let $element = document.querySelector('#id');
let nombre2 = 42;

// [X] Invalides
let 2nombre = 42;               // Ne peut pas commencer par chiffre
let user-name = 'Alice';        // Pas de tiret
let let = 'mot-clé';            // Mot réservé

// Conventions:
const CONSTANTE_GLOBALE = 100;  // SCREAMING_SNAKE_CASE pour constantes
let maVariable = 'valeur';      // camelCase pour variables
function maFonction() {}        // camelCase pour fonctions
class MaClasse {}               // PascalCase pour classes

// === BONNES PRATIQUES ===

// 1. Préférer const par défaut
const API_URL = 'https://api.example.com';
const users = [];

// 2. Utiliser let quand réassignation nécessaire
let counter = 0;
let isActive = true;

// 3. Noms descriptifs
let u = 'Alice';                // [X] Pas clair
let userName = 'Alice';         // [OK] Clair

// 4. Éviter var complètement
var old = 'ne pas utiliser';    // [X]


[OK] TYPES DE DONNÉES PRIMITIFS

// === NUMBER (nombres) ===
let entier = 42;
let negatif = -10;
let decimal = 3.14;
let grand = 1000000;
let grandAvecSeparateur = 1_000_000;    // ES2021

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

// Vérifications
console.log(typeof 42);             // 'number'
console.log(Number.isNaN(NaN));     // true
console.log(Number.isFinite(100));  // true
console.log(Number.isInteger(42));  // true

// Conversion
let str = '123';
let num1 = Number(str);             // 123
let num2 = parseInt(str);           // 123
let num3 = parseFloat('3.14');      // 3.14
let num4 = +str;                    // 123 (conversion rapide)

// === STRING (chaînes de caractères) ===

// Guillemets simples
let prenom = 'Alice';

// Guillemets doubles
let nom = "Dupont";

// Template literals (backticks) - RECOMMANDÉ
let message = `Bonjour ${prenom} ${nom}!`;
let multiline = `Ligne 1
Ligne 2
Ligne 3`;

// Échappement
let guillemet = 'J\'aime JavaScript';   // Échapper '
let backslash = 'C:\\Users\\Alice';    // Échapper \
let newline = 'Ligne 1\nLigne 2';      // Saut de ligne
let tab = 'Col1\tCol2';                // Tabulation

// Concaténation
let fullName = prenom + ' ' + nom;                  // Ancien
let fullName2 = `${prenom} ${nom}`;                 // Moderne (préféré)

// Propriétés et méthodes
let text = 'Hello World';
console.log(text.length);               // 11
console.log(text.toLowerCase());        // 'hello world'
console.log(text.toUpperCase());        // 'HELLO WORLD'
console.log(text.includes('World'));    // true
console.log(text.startsWith('Hello')); // true
console.log(text.endsWith('World'));    // true
console.log(text.indexOf('World'));     // 6
console.log(text.slice(0, 5));          // 'Hello'
console.log(text.substring(0, 5));      // 'Hello'
console.log(text.replace('World', 'JS')); // 'Hello JS'
console.log(text.split(' '));           // ['Hello', 'World']
console.log(text.trim());               // Enlève espaces début/fin
console.log(text.repeat(3));            // 'Hello WorldHello WorldHello World'
console.log(text.charAt(0));            // 'H'
console.log(text[0]);                   // 'H' (accès par index)

// === BOOLEAN (booléens) ===
let vrai = true;
let faux = false;
let majeur = age >= 18;

// Conversion en booléen
Boolean(1);                 // true
Boolean(0);                 // false
Boolean('hello');           // true
Boolean('');                // false
Boolean(null);              // false
Boolean(undefined);         // false
Boolean(NaN);               // false

// Valeurs falsy (considérées comme false):
// false, 0, -0, '', null, undefined, NaN

// Valeurs truthy (toutes les autres):
// true, 1, -1, 'hello', [], {}, etc.

// === NULL ===
let vide = null;            // Absence intentionnelle de valeur
console.log(typeof null);   // 'object' (bug historique JavaScript!)

// === UNDEFINED ===
let nonDefini;              // Variable déclarée mais pas assignée
console.log(nonDefini);     // undefined
console.log(typeof undefined); // 'undefined'

function test(param) {
    console.log(param);     // undefined si pas d'argument
}
test();

// Différence null vs undefined:
// undefined: valeur pas encore assignée (automatique)
// null: valeur intentionnellement vide (manuel)

// === SYMBOL (ES6) ===
let sym1 = Symbol('description');
let sym2 = Symbol('description');
console.log(sym1 === sym2);     // false (toujours unique)

// Utilisation: propriétés d'objets uniques
const ID = Symbol('id');
let user = {
    [ID]: 12345,
    name: 'Alice'
};

// === BIGINT (ES2020) ===
let grandNombre = 9007199254740991n;    // Ajouter 'n'
let bigint2 = BigInt('9007199254740991');

// Pour nombres > Number.MAX_SAFE_INTEGER
console.log(Number.MAX_SAFE_INTEGER);   // 9007199254740991


[OK] OPÉRATEURS

// === OPÉRATEURS ARITHMÉTIQUES ===
let a = 10;
let b = 3;

console.log(a + b);     // 13 (addition)
console.log(a - b);     // 7 (soustraction)
console.log(a * b);     // 30 (multiplication)
console.log(a / b);     // 3.333... (division)
console.log(a % b);     // 1 (modulo - reste de la division)
console.log(a ** b);    // 1000 (puissance: 10³)

// Incrémentation / Décrémentation
let x = 5;
x++;                    // x = 6 (post-incrémentation)
++x;                    // x = 7 (pré-incrémentation)
x--;                    // x = 6 (post-décrémentation)
--x;                    // x = 5 (pré-décrémentation)

let y = 5;
console.log(y++);       // 5 (affiche puis incrémente)
console.log(y);         // 6

let z = 5;
console.log(++z);       // 6 (incrémente puis affiche)

// Opérateurs d'assignation composés
let n = 10;
n += 5;                 // n = n + 5 (15)
n -= 3;                 // n = n - 3 (12)
n *= 2;                 // n = n * 2 (24)
n /= 4;                 // n = n / 4 (6)
n %= 4;                 // n = n % 4 (2)
n **= 3;                // n = n ** 3 (8)

// === OPÉRATEURS DE COMPARAISON ===

// Égalité faible (avec conversion de type)
console.log(5 == '5');      // true (convertit string en number)
console.log(0 == false);    // true
console.log(null == undefined); // true

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

// Inégalité
console.log(5 != '5');      // false (avec conversion)
console.log(5 !== '5');     // true (stricte, RECOMMANDÉ)

// Comparaisons
console.log(5 > 3);         // true
console.log(5 < 3);         // false
console.log(5 >= 5);        // true
console.log(5 <= 4);        // false

// === OPÉRATEURS LOGIQUES ===

// AND (&&) - ET logique
console.log(true && true);      // true
console.log(true && false);     // false
console.log(false && true);     // false

let age = 25;
if (age >= 18 && age <= 65) {
    console.log('Âge valide');
}

// OR (||) - OU logique
console.log(true || false);     // true
console.log(false || false);    // false

if (age < 18 || age > 65) {
    console.log('Hors limites');
}

// NOT (!) - NON logique
console.log(!true);             // false
console.log(!false);            // true

let isActive = false;
if (!isActive) {
    console.log('Inactif');
}

// Court-circuit
let user = null;
let name = user && user.name;   // null (s'arrête si user est falsy)

let defaultName = userName || 'Anonyme';  // 'Anonyme' si userName falsy

// Nullish coalescing (??) - ES2020
let value = null;
console.log(value ?? 'défaut');     // 'défaut'
console.log(0 ?? 'défaut');         // 0 (0 n'est pas null/undefined)
console.log('' ?? 'défaut');        // '' ('' n'est pas null/undefined)

// Différence || vs ??
// || retourne à droite si gauche est falsy (0, '', false, null, undefined, NaN)
// ?? retourne à droite SEULEMENT si gauche est null ou undefined

// === OPÉRATEUR TERNAIRE ===
let age2 = 20;
let statut = age2 >= 18 ? 'majeur' : 'mineur';
console.log(statut);    // 'majeur'

// Équivalent à:
let statut2;
if (age2 >= 18) {
    statut2 = 'majeur';
} else {
    statut2 = 'mineur';
}

// Ternaire imbriqué (à éviter si trop complexe)
let note = 85;
let mention = note >= 90 ? 'A' : note >= 80 ? 'B' : note >= 70 ? 'C' : 'D';

// === OPÉRATEUR DE CHAÎNAGE OPTIONNEL (?.) - ES2020 ===
let user2 = {
    name: 'Alice',
    address: {
        city: 'Paris'
    }
};

// Sans chaînage optionnel
let city1 = user2 && user2.address && user2.address.city;

// Avec chaînage optionnel (MODERNE)
let city2 = user2?.address?.city;   // 'Paris'
let city3 = user2?.contact?.phone;  // undefined (pas d'erreur)

// Avec méthodes
let result = user2?.getInfo?.();    // undefined (méthode n'existe pas)

// Avec arrays
let firstUser = users?.[0];

// === OPÉRATEUR TYPEOF ===
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' (bug!)
console.log(typeof {});             // 'object'
console.log(typeof []);             // 'object' (array est un objet)
console.log(typeof function(){});   // 'function'

// Vérifier si array
console.log(Array.isArray([]));     // true

// === OPÉRATEUR DELETE ===
let obj = {name: 'Alice', age: 25};
delete obj.age;
console.log(obj);       // {name: 'Alice'}

// === OPÉRATEUR IN ===
console.log('name' in obj);     // true
console.log('age' in obj);      // false

// === OPÉRATEUR INSTANCEOF ===
let date = new Date();
console.log(date instanceof Date);      // true
console.log([] instanceof Array);       // true


[OK] STRUCTURES DE CONTRÔLE - IF/ELSE

// === IF SIMPLE ===
let age = 20;

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

// Sans accolades (une seule instruction)
if (age >= 18)
    console.log('Majeur');      // Fonctionne mais déconseillé

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

// === IF-ELSE IF-ELSE ===
let note = 85;

if (note >= 90) {
    console.log('A - Excellent');
} else if (note >= 80) {
    console.log('B - Très bien');
} else if (note >= 70) {
    console.log('C - Bien');
} else if (note >= 60) {
    console.log('D - Passable');
} else {
    console.log('F - Échec');
}

// === CONDITIONS MULTIPLES ===

// AND (&&)
let age2 = 25;
let hasLicense = true;

if (age2 >= 18 && hasLicense) {
    console.log('Peut conduire');
}

// OR (||)
if (age2 < 18 || !hasLicense) {
    console.log('Ne peut pas conduire');
}

// Combinaison
let isWeekend = true;
let isHoliday = false;

if ((isWeekend || isHoliday) && age2 >= 18) {
    console.log('Peut sortir tard');
}

// === CONDITIONS IMBRIQUÉES ===
let score = 85;
let hasCheated = false;

if (score >= 60) {
    if (hasCheated) {
        console.log('Disqualifié');
    } else {
        console.log('Réussi');
    }
} else {
    console.log('Échoué');
}

// === TERNAIRE (condition courte) ===
let message = age >= 18 ? 'Accès autorisé' : 'Accès refusé';

// Ternaire avec assignation
let reduction = 0;
reduction = age < 12 ? 50 : age < 18 ? 25 : 0;

// === SWITCH (alternative à if-else if) ===
// Voir section suivante


[OK] STRUCTURES DE CONTRÔLE - SWITCH

// === SWITCH BASIQUE ===
let jour = 'lundi';

switch (jour) {
    case 'lundi':
        console.log('Début de semaine');
        break;
    case 'mardi':
        console.log('Mardi');
        break;
    case 'mercredi':
        console.log('Milieu de semaine');
        break;
    case 'jeudi':
        console.log('Jeudi');
        break;
    case 'vendredi':
        console.log('Vendredi!');
        break;
    case 'samedi':
    case 'dimanche':
        console.log('Week-end!');
        break;
    default:
        console.log('Jour invalide');
}

// === SWITCH AVEC NOMBRES ===
let note = 2;

switch (note) {
    case 1:
        console.log('Très mauvais');
        break;
    case 2:
        console.log('Mauvais');
        break;
    case 3:
        console.log('Moyen');
        break;
    case 4:
        console.log('Bien');
        break;
    case 5:
        console.log('Excellent');
        break;
    default:
        console.log('Note invalide');
}

// === SWITCH AVEC FALL-THROUGH (sans break) ===
let mois = 2;

switch (mois) {
    case 12:
    case 1:
    case 2:
        console.log('Hiver');
        break;
    case 3:
    case 4:
    case 5:
        console.log('Printemps');
        break;
    case 6:
    case 7:
    case 8:
        console.log('Été');
        break;
    case 9:
    case 10:
    case 11:
        console.log('Automne');
        break;
    default:
        console.log('Mois invalide');
}

// === SWITCH AVEC RETURN (dans fonction) ===
function getDayType(day) {
    switch (day) {
        case 'samedi':
        case 'dimanche':
            return 'week-end';
        case 'lundi':
        case 'mardi':
        case 'mercredi':
        case 'jeudi':
        case 'vendredi':
            return 'jour ouvrable';
        default:
            return 'jour invalide';
    }
}

// === SWITCH VS IF-ELSE ===

// Switch: mieux pour valeurs exactes
switch (couleur) {
    case 'rouge': // ...
    case 'bleu': // ...
}

// If-else: mieux pour ranges/conditions complexes
if (age < 18) {
    // ...
} else if (age >= 18 && age < 65) {
    // ...
}

// === ATTENTION: COMPARAISON STRICTE ===
let value = '1';

switch (value) {
    case 1:
        console.log('Nombre 1');    // Ne s'exécute PAS
        break;
    case '1':
        console.log('String 1');    // S'exécute (comparaison stricte ===)
        break;
}


[OK] BOUCLES - FOR

// === FOR CLASSIQUE ===
// Syntaxe: for (initialisation; condition; incrémentation)

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

// Décroissant
for (let i = 5; i > 0; i--) {
    console.log(i);     // 5, 4, 3, 2, 1
}

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

// === FOR AVEC ARRAY ===
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(`Index ${i}: ${fruits[i]}`);
}

// === FOR...OF (itération sur valeurs) - ES6 ===
// RECOMMANDÉ pour arrays

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

// Avec destructuration
let users = [
    {name: 'Alice', age: 25},
    {name: 'Bob', age: 30}
];

for (let {name, age} of users) {
    console.log(`${name} a ${age} ans`);
}

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

// === FOR...IN (itération sur clés) ===
// Pour objets principalement

let person = {
    name: 'Alice',
    age: 25,
    city: 'Paris'
};

for (let key in person) {
    console.log(`${key}: ${person[key]}`);
}

// ATTENTION avec arrays (déconseillé)
for (let index in fruits) {
    console.log(fruits[index]);     // Fonctionne mais préférer for...of
}

// === BOUCLES IMBRIQUÉES ===

// Table de multiplication
for (let i = 1; i <= 3; i++) {
    for (let j = 1; j <= 3; j++) {
        console.log(`${i} x ${j} = ${i * j}`);
    }
}

// Matrice
let matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
];

for (let i = 0; i < matrix.length; i++) {
    for (let j = 0; j < matrix[i].length; j++) {
        console.log(matrix[i][j]);
    }
}

// === BREAK (sortir de la boucle) ===
for (let i = 0; i < 10; i++) {
    if (i === 5) {
        break;          // Sort de la boucle
    }
    console.log(i);     // 0, 1, 2, 3, 4
}

// Chercher dans array
let numbers = [1, 2, 3, 4, 5];
let target = 3;
let found = false;

for (let num of numbers) {
    if (num === target) {
        found = true;
        break;
    }
}

// === CONTINUE (passer à l'itération suivante) ===
for (let i = 0; i < 5; i++) {
    if (i === 2) {
        continue;       // Saute i = 2
    }
    console.log(i);     // 0, 1, 3, 4
}

// Filtrer les nombres pairs
for (let i = 0; i < 10; i++) {
    if (i % 2 === 0) {
        continue;       // Saute les pairs
    }
    console.log(i);     // 1, 3, 5, 7, 9 (impairs seulement)
}


[OK] BOUCLES - WHILE & DO-WHILE

// === WHILE ===
// Exécute tant que condition vraie

let i = 0;
while (i < 5) {
    console.log(i);
    i++;
}

// Attention: boucle infinie si pas d'incrémentation!
// while (true) {      // [ATTENTION] Bloque le navigateur!
//     console.log('Infini');
// }

// Exemple pratique: lire input
let password = '';
while (password !== 'secret') {
    password = prompt('Entrez le mot de passe:');
}

// === DO-WHILE ===
// Exécute AU MOINS une fois, puis vérifie condition

let j = 0;
do {
    console.log(j);
    j++;
} while (j < 5);

// Différence while vs do-while
let x = 10;

while (x < 5) {
    console.log('While');   // Ne s'exécute PAS
}

do {
    console.log('Do-While'); // S'exécute une fois
} while (x < 5);

// Exemple: menu
let choice;
do {
    console.log('1. Option A');
    console.log('2. Option B');
    console.log('3. Quitter');
    choice = prompt('Votre choix:');
} while (choice !== '3');


[OK] FONCTIONS - DÉCLARATIONS

// === FONCTION SIMPLE ===
function direBonjour() {
    console.log('Bonjour!');
}

// Appel
direBonjour();          // Bonjour!

// === FONCTION AVEC PARAMÈTRES ===
function saluer(nom) {
    console.log(`Bonjour ${nom}!`);
}

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

// Plusieurs paramètres
function additionner(a, b) {
    console.log(a + b);
}

additionner(5, 3);      // 8

// === FONCTION AVEC RETURN ===
function multiplier(a, b) {
    return a * b;
}

let resultat = multiplier(4, 5);
console.log(resultat);  // 20

// Return arrête la fonction
function verifierAge(age) {
    if (age < 18) {
        return 'Mineur';    // Sort de la fonction
    }
    return 'Majeur';
}

// === PARAMÈTRES PAR DÉFAUT ===
function saluerAvecDefault(nom = 'Inconnu') {
    console.log(`Bonjour ${nom}!`);
}

saluerAvecDefault('Alice');     // Bonjour Alice!
saluerAvecDefault();            // Bonjour Inconnu!

// Plusieurs paramètres par défaut
function creerUser(nom = 'Anonyme', age = 0, ville = 'Paris') {
    return {nom, age, ville};
}

console.log(creerUser());                       // {nom: 'Anonyme', age: 0, ville: 'Paris'}
console.log(creerUser('Alice', 25));           // {nom: 'Alice', age: 25, ville: 'Paris'}
console.log(creerUser('Bob', 30, 'Lyon'));     // {nom: 'Bob', age: 30, ville: 'Lyon'}

// === REST PARAMETERS (...) ===
// Collecter arguments restants dans un array

function somme(...numbers) {
    let total = 0;
    for (let num of numbers) {
        total += num;
    }
    return total;
}

console.log(somme(1, 2, 3));            // 6
console.log(somme(1, 2, 3, 4, 5));      // 15
console.log(somme(10, 20));             // 30

// Avec paramètres normaux avant
function afficherInfo(nom, ...infos) {
    console.log(`Nom: ${nom}`);
    console.log('Infos:', infos);
}

afficherInfo('Alice', 25, 'Paris', 'Ingénieur');
// Nom: Alice
// Infos: [25, 'Paris', 'Ingénieur']

// === ARGUMENTS OBJECT (ancien, éviter) ===
function ancienneMethode() {
    console.log(arguments);     // Objet array-like
    console.log(arguments[0]);
}

ancienneMethode(1, 2, 3);

// === HOISTING (remontée) ===
// Les déclarations de fonctions sont "remontées"

direHello();        // Fonctionne! (hoisting)

function direHello() {
    console.log('Hello!');
}

// Mais pas les expressions de fonctions (voir section suivante)


[OK] FONCTIONS - EXPRESSIONS & ARROW FUNCTIONS

// === EXPRESSION DE FONCTION ===
// Fonction assignée à une variable

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

saluer('Alice');

// Pas de hoisting
// direHello2();    // [X] Erreur! Pas encore définie
const direHello2 = function() {
    console.log('Hello');
};

// === FONCTION NOMMÉE (dans expression) ===
const factorielle = function fact(n) {
    if (n <= 1) return 1;
    return n * fact(n - 1);     // Utilise son propre nom
};

// === ARROW FUNCTIONS (fonctions flèches) - ES6 ===
// Syntaxe moderne et concise

// Syntaxe complète
const additionner = (a, b) => {
    return a + b;
};

// Syntaxe courte (return implicite, une seule expression)
const additionner2 = (a, b) => a + b;

// Un seul paramètre (parenthèses optionnelles)
const doubler = x => x * 2;
const carre = x => x * x;

// Pas de paramètres
const direBonjour = () => console.log('Bonjour!');

// Retourner un objet (attention: entourer de parenthèses)
const creerUser = (nom, age) => ({nom, age});
// Sans parenthèses: const creerUser = (nom, age) => {nom, age}; // [X] Erreur!

// === EXEMPLES ARROW FUNCTIONS ===

// Avec array methods
const nombres = [1, 2, 3, 4, 5];

// map
const doubles = nombres.map(x => x * 2);        // [2, 4, 6, 8, 10]

// filter
const pairs = nombres.filter(x => x % 2 === 0); // [2, 4]

// reduce
const somme = nombres.reduce((acc, x) => acc + x, 0);  // 15

// forEach
nombres.forEach(x => console.log(x));

// Plusieurs lignes
const traiterUser = (user) => {
    const fullName = `${user.prenom} ${user.nom}`;
    const age = 2024 - user.anneeNaissance;
    return {fullName, age};
};

// === DIFFÉRENCES ARROW VS FONCTION NORMALE ===

// 1. Arrow n'a pas son propre 'this'
const obj = {
    nom: 'Alice',
    
    // Fonction normale
    saluer: function() {
        console.log(`Bonjour ${this.nom}`);     // Fonctionne
    },
    
    // Arrow function
    saluerArrow: () => {
        console.log(`Bonjour ${this.nom}`);     // undefined! (this différent)
    }
};

// 2. Arrow ne peut pas être constructeur
const Person = (nom) => {
    this.nom = nom;
};
// new Person('Alice');     // [X] Erreur!

// 3. Arrow n'a pas d'arguments object
const testArguments = () => {
    // console.log(arguments);  // [X] Erreur!
};

// === IIFE (Immediately Invoked Function Expression) ===
// Fonction qui s'exécute immédiatement

(function() {
    console.log('Exécuté immédiatement!');
})();

// Avec arrow
(() => {
    console.log('Arrow IIFE!');
})();

// Avec paramètres
((nom) => {
    console.log(`Hello ${nom}!`);
})('Alice');

// === CALLBACK FUNCTIONS ===
// Fonction passée en argument

function faireQuelqueChose(callback) {
    console.log('Début...');
    callback();
    console.log('Fin');
}

faireQuelqueChose(() => {
    console.log('Callback exécuté!');
});

// Exemple pratique: setTimeout
setTimeout(() => {
    console.log('Après 2 secondes');
}, 2000);

// Exemple: forEach avec callback
const fruits = ['pomme', 'banane', 'orange'];
fruits.forEach((fruit, index) => {
    console.log(`${index}: ${fruit}`);
});


[OK] FONCTIONS - CONCEPTS AVANCÉS

// === CLOSURE (fermeture) ===
// Fonction interne qui a accès aux variables de la fonction externe

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

const compteur1 = creerCompteur();
console.log(compteur1());       // 1
console.log(compteur1());       // 2
console.log(compteur1());       // 3

const compteur2 = creerCompteur();
console.log(compteur2());       // 1 (nouveau compteur indépendant)

// Exemple pratique: données privées
function creerBanque() {
    let solde = 0;              // Privé!
    
    return {
        deposer: function(montant) {
            solde += montant;
            return solde;
        },
        retirer: function(montant) {
            if (montant <= solde) {
                solde -= montant;
                return solde;
            }
            return 'Fonds insuffisants';
        },
        getSolde: function() {
            return solde;
        }
    };
}

const monCompte = creerBanque();
monCompte.deposer(100);         // 100
monCompte.retirer(30);          // 70
console.log(monCompte.getSolde()); // 70
// console.log(monCompte.solde);   // undefined (privé!)

// === RECURSION (récursivité) ===
// Fonction qui s'appelle elle-même

function factorielle(n) {
    if (n <= 1) {
        return 1;               // Cas de base
    }
    return n * factorielle(n - 1);  // Appel récursif
}

console.log(factorielle(5));    // 120 (5 * 4 * 3 * 2 * 1)

// Compte à rebours
function compteARebours(n) {
    if (n < 0) return;          // Cas de base
    console.log(n);
    compteARebours(n - 1);      // Appel récursif
}

compteARebours(5);              // 5, 4, 3, 2, 1, 0

// Fibonacci
function fibonacci(n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

// === HIGHER-ORDER FUNCTIONS ===
// Fonctions qui prennent ou retournent des fonctions

// Fonction qui retourne une fonction
function multiplierPar(facteur) {
    return function(nombre) {
        return nombre * facteur;
    };
}

const doubler = multiplierPar(2);
const tripler = multiplierPar(3);

console.log(doubler(5));        // 10
console.log(tripler(5));        // 15

// Fonction qui prend une fonction en paramètre
function appliquer(fn, valeur) {
    return fn(valeur);
}

console.log(appliquer(x => x * 2, 5));      // 10
console.log(appliquer(x => x + 10, 5));     // 15

// === PURE FUNCTIONS ===
// Fonctions sans effets de bord

// Pure (déterministe, pas d'effets de bord)
function additionner(a, b) {
    return a + b;
}

// Impure (modifie variable externe)
let total = 0;
function ajouterAuTotal(x) {
    total += x;                 // Effet de bord!
    return total;
}

// === MEMOIZATION (cache) ===
// Mémoriser résultats pour éviter recalculs

function memoize(fn) {
    const cache = {};
    return function(...args) {
        const key = JSON.stringify(args);
        if (key in cache) {
            console.log('Depuis cache');
            return cache[key];
        }
        console.log('Calcul...');
        const result = fn(...args);
        cache[key] = result;
        return result;
    };
}

const fibMemo = memoize(function fib(n) {
    if (n <= 1) return n;
    return fib(n - 1) + fib(n - 2);
});


[OK] ARRAYS (TABLEAUX) - CRÉATION & ACCÈS

// === CRÉATION D'ARRAYS ===

// Littéral (RECOMMANDÉ)
const fruits = ['pomme', 'banane', 'orange'];
const nombres = [1, 2, 3, 4, 5];
const mixte = [1, 'hello', true, null, {nom: 'Alice'}];

// Constructeur Array (rarement utilisé)
const arr1 = new Array();           // []
const arr2 = new Array(5);          // [ , , , , ] (5 emplacements vides)
const arr3 = new Array(1, 2, 3);    // [1, 2, 3]

// Array vide
const vide = [];

// === ACCÈS AUX ÉLÉMENTS ===

const fruits2 = ['pomme', 'banane', 'orange', 'fraise'];

// Par index (commence à 0)
console.log(fruits2[0]);        // 'pomme'
console.log(fruits2[1]);        // 'banane'
console.log(fruits2[3]);        // 'fraise'

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

// Élément inexistant
console.log(fruits2[10]);       // undefined

// Index négatif (ne fonctionne PAS comme Python)
console.log(fruits2[-1]);       // undefined (pas le dernier!)

// Méthode at() - ES2022 (index négatif supporté)
console.log(fruits2.at(0));     // 'pomme'
console.log(fruits2.at(-1));    // 'fraise' (dernier)
console.log(fruits2.at(-2));    // 'orange' (avant-dernier)

// === PROPRIÉTÉS ===

// Longueur
console.log(fruits2.length);    // 4

// Modifier la longueur
fruits2.length = 2;
console.log(fruits2);           // ['pomme', 'banane'] (tronqué!)

// === VÉRIFICATION ===

// Est-ce un array?
console.log(Array.isArray(fruits2));        // true
console.log(Array.isArray('hello'));        // false
console.log(Array.isArray({a: 1}));         // false

// Contient un élément?
console.log(fruits2.includes('pomme'));     // true
console.log(fruits2.includes('kiwi'));      // false

// Index d'un élément
console.log(fruits2.indexOf('banane'));     // 1
console.log(fruits2.indexOf('kiwi'));       // -1 (pas trouvé)

// Dernier index
const nums = [1, 2, 3, 2, 1];
console.log(nums.lastIndexOf(2));           // 3


[OK] ARRAYS - MÉTHODES DE MODIFICATION

// === AJOUTER DES ÉLÉMENTS ===

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

// push() - ajouter à la fin
fruits.push('orange');
console.log(fruits);            // ['pomme', 'banane', 'orange']

// push() retourne la nouvelle longueur
let newLength = fruits.push('fraise');
console.log(newLength);         // 4

// Ajouter plusieurs
fruits.push('kiwi', 'mangue');

// unshift() - ajouter au début
fruits.unshift('citron');
console.log(fruits);            // ['citron', 'pomme', 'banane', ...]

// === RETIRER DES ÉLÉMENTS ===

// pop() - retirer le dernier
let dernier = fruits.pop();
console.log(dernier);           // 'mangue'
console.log(fruits);            // Sans 'mangue'

// shift() - retirer le premier
let premier = fruits.shift();
console.log(premier);           // 'citron'

// === SPLICE (ajouter/retirer à position spécifique) ===
// array.splice(start, deleteCount, item1, item2, ...)

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

// Retirer éléments
arr.splice(2, 2);               // Retire 2 éléments à partir de l'index 2
console.log(arr);               // ['a', 'b', 'e']

// Ajouter éléments
arr = ['a', 'b', 'e'];
arr.splice(2, 0, 'c', 'd');     // Ajoute à l'index 2, retire 0
console.log(arr);               // ['a', 'b', 'c', 'd', 'e']

// Remplacer éléments
arr.splice(1, 2, 'X', 'Y');     // Remplace 2 éléments à partir de l'index 1
console.log(arr);               // ['a', 'X', 'Y', 'd', 'e']

// splice retourne les éléments retirés
let removed = arr.splice(1, 2);
console.log(removed);           // ['X', 'Y']

// === DELETE (crée un trou, éviter) ===
let arr2 = [1, 2, 3, 4];
delete arr2[1];
console.log(arr2);              // [1, empty, 3, 4]
console.log(arr2.length);       // 4 (longueur inchangée!)

// === VIDER UN ARRAY ===

// Méthode 1: nouvelle assignation
arr = [];

// Méthode 2: length = 0
arr.length = 0;

// Méthode 3: splice
arr.splice(0, arr.length);

// === MODIFIER PAR INDEX ===
let nums = [1, 2, 3];
nums[1] = 20;
console.log(nums);              // [1, 20, 3]

// Créer trou
nums[5] = 60;
console.log(nums);              // [1, 20, 3, empty × 2, 60]


[OK] ARRAYS - MÉTHODES D'ITÉRATION

// === FOREACH - parcourir array ===
const fruits = ['pomme', 'banane', 'orange'];

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

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

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

// === MAP - transformer chaque élément ===
// Crée un NOUVEAU array

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

const doubles = nombres.map(x => x * 2);
console.log(doubles);           // [2, 4, 6, 8, 10]
console.log(nombres);           // [1, 2, 3, 4, 5] (inchangé)

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

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

// Avec index
const avecIndex = fruits.map((fruit, i) => `${i + 1}. ${fruit}`);
console.log(avecIndex);         // ['1. pomme', '2. banane', '3. orange']

// === FILTER - filtrer éléments ===
// Crée un NOUVEAU array avec éléments qui passent le test

const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const pairs = nums.filter(x => x % 2 === 0);
console.log(pairs);             // [2, 4, 6, 8, 10]

const grands = nums.filter(x => x > 5);
console.log(grands);            // [6, 7, 8, 9, 10]

// Avec objets
const adultes = users.filter(user => user.age >= 18);

// Filtrer valeurs falsy
const values = [0, 1, false, 2, '', 3, null, undefined, 4];
const truthyValues = values.filter(Boolean);
console.log(truthyValues);      // [1, 2, 3, 4]

// === FIND - trouver premier élément ===
// Retourne le premier élément qui passe le test (ou undefined)

const premier Pair = nums.find(x => x % 2 === 0);
console.log(premierPair);       // 2

const user = users.find(u => u.nom === 'Alice');
console.log(user);              // {nom: 'Alice', age: 25}

// === FINDINDEX - trouver index ===
const index = nums.findIndex(x => x > 5);
console.log(index);             // 5 (index de 6)

// === SOME - au moins un élément passe le test ===
// Retourne true/false

const hasEven = nums.some(x => x % 2 === 0);
console.log(hasEven);           // true

const hasNegative = nums.some(x => x < 0);
console.log(hasNegative);       // false

// === EVERY - tous les éléments passent le test ===
const allPositive = nums.every(x => x > 0);
console.log(allPositive);       // true

const allEven = nums.every(x => x % 2 === 0);
console.log(allEven);           // false

// === REDUCE - réduire à une seule valeur ===
// array.reduce(callback, initialValue)

// Somme
const somme = nums.reduce((acc, x) => acc + x, 0);
console.log(somme);             // 55

// Produit
const produit = nums.reduce((acc, x) => acc * x, 1);

// Maximum
const max = nums.reduce((acc, x) => x > acc ? x : acc);

// Compter occurrences
const mots = ['chat', 'chien', 'chat', 'oiseau', 'chat'];
const comptage = mots.reduce((acc, mot) => {
    acc[mot] = (acc[mot] || 0) + 1;
    return acc;
}, {});
console.log(comptage);          // {chat: 3, chien: 1, oiseau: 1}

// Aplatir array
const nested = [[1, 2], [3, 4], [5, 6]];
const flat = nested.reduce((acc, arr) => acc.concat(arr), []);
console.log(flat);              // [1, 2, 3, 4, 5, 6]

// === REDUCERIGHT - comme reduce mais de droite à gauche ===
const arr = [1, 2, 3, 4];
const result = arr.reduceRight((acc, x) => acc + x, 0);


[OK] ARRAYS - AUTRES MÉTHODES UTILES

// === SLICE - extraire une portion ===
// Crée un NOUVEAU array (ne modifie pas l'original)

const fruits = ['pomme', 'banane', 'orange', 'fraise', 'kiwi'];

// slice(start, end) - end non inclus
const portion = fruits.slice(1, 3);
console.log(portion);           // ['banane', 'orange']
console.log(fruits);            // Inchangé

// Sans end (jusqu'à la fin)
const rest = fruits.slice(2);
console.log(rest);              // ['orange', 'fraise', 'kiwi']

// Index négatif (depuis la fin)
const last2 = fruits.slice(-2);
console.log(last2);             // ['fraise', 'kiwi']

// Copier array
const copie = fruits.slice();
// ou: const copie = [...fruits];

// === CONCAT - concaténer arrays ===
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = [7, 8, 9];

const combined = arr1.concat(arr2);
console.log(combined);          // [1, 2, 3, 4, 5, 6]

// Plusieurs arrays
const all = arr1.concat(arr2, arr3);
console.log(all);               // [1, 2, 3, 4, 5, 6, 7, 8, 9]

// Avec spread operator (MODERNE)
const combined2 = [...arr1, ...arr2, ...arr3];

// === JOIN - joindre en string ===
const words = ['Hello', 'World', 'JavaScript'];

console.log(words.join());      // 'Hello,World,JavaScript' (virgule par défaut)
console.log(words.join(' '));   // 'Hello World JavaScript'
console.log(words.join('-'));   // 'Hello-World-JavaScript'
console.log(words.join(''));    // 'HelloWorldJavaScript'

// === REVERSE - inverser array ===
// [ATTENTION] MODIFIE l'array original!

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

// Pour ne pas modifier l'original
const original = [1, 2, 3];
const reversed = [...original].reverse();

// === SORT - trier array ===
// [ATTENTION] MODIFIE l'array original!

// Tri alphabétique (par défaut)
const fruits2 = ['banane', 'pomme', 'orange'];
fruits2.sort();
console.log(fruits2);           // ['banane', 'orange', 'pomme']

// [ATTENTION] ATTENTION avec nombres!
const numbers = [10, 5, 40, 25, 1000, 1];
numbers.sort();
console.log(numbers);           // [1, 10, 1000, 25, 40, 5] (tri alphabétique!)

// Tri numérique correct
numbers.sort((a, b) => a - b);  // Croissant
console.log(numbers);           // [1, 5, 10, 25, 40, 1000]

numbers.sort((a, b) => b - a);  // Décroissant
console.log(numbers);           // [1000, 40, 25, 10, 5, 1]

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

// Par nom
users.sort((a, b) => a.nom.localeCompare(b.nom));

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

// === FILL - remplir avec une valeur ===
const arr = new Array(5).fill(0);
console.log(arr);               // [0, 0, 0, 0, 0]

// Remplir portion
const arr2 = [1, 2, 3, 4, 5];
arr2.fill(0, 2, 4);             // fill(valeur, start, end)
console.log(arr2);              // [1, 2, 0, 0, 5]

// === FLAT - aplatir array imbriqué ===
const nested = [1, [2, 3], [4, [5, 6]]];

console.log(nested.flat());     // [1, 2, 3, 4, [5, 6]] (1 niveau)
console.log(nested.flat(2));    // [1, 2, 3, 4, 5, 6] (2 niveaux)
console.log(nested.flat(Infinity)); // Tous les niveaux

// === FLATMAP - map puis flat ===
const arr3 = [1, 2, 3];
const result = arr3.flatMap(x => [x, x * 2]);
console.log(result);            // [1, 2, 2, 4, 3, 6]

// Équivalent à:
const result2 = arr3.map(x => [x, x * 2]).flat();

// === FROM - créer array depuis itérable ===
const str = 'hello';
const chars = Array.from(str);
console.log(chars);             // ['h', 'e', 'l', 'l', 'o']

// Avec fonction de mapping
const nums2 = Array.from([1, 2, 3], x => x * 2);
console.log(nums2);             // [2, 4, 6]

// Créer range
const range = Array.from({length: 5}, (_, i) => i + 1);
console.log(range);             // [1, 2, 3, 4, 5]

// === OF - créer array depuis arguments ===
const arr4 = Array.of(1, 2, 3);
console.log(arr4);              // [1, 2, 3]

// Différence avec constructeur
console.log(new Array(3));      // [empty × 3]
console.log(Array.of(3));       // [3]


[OK] SPREAD OPERATOR & DESTRUCTURING

// === SPREAD OPERATOR (...) ===

// Avec arrays
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

// Combiner arrays
const combined = [...arr1, ...arr2];
console.log(combined);          // [1, 2, 3, 4, 5, 6]

// Avec éléments supplémentaires
const extended = [0, ...arr1, 3.5, ...arr2, 7];
console.log(extended);          // [0, 1, 2, 3, 3.5, 4, 5, 6, 7]

// Copier array (shallow copy)
const copie = [...arr1];
copie[0] = 999;
console.log(arr1);              // [1, 2, 3] (inchangé)
console.log(copie);             // [999, 2, 3]

// Passer éléments comme arguments
const numbers = [5, 10, 15];
console.log(Math.max(...numbers));  // 15
// Équivalent à: Math.max(5, 10, 15)

// Avec strings
const str = 'hello';
const chars = [...str];
console.log(chars);             // ['h', 'e', 'l', 'l', 'o']

// Avec objets
const user = {nom: 'Alice', age: 25};
const userCopy = {...user};
console.log(userCopy);          // {nom: 'Alice', age: 25}

// Fusionner objets
const defaults = {theme: 'dark', lang: 'fr'};
const userSettings = {lang: 'en', size: 'large'};
const settings = {...defaults, ...userSettings};
console.log(settings);          // {theme: 'dark', lang: 'en', size: 'large'}

// Ajouter propriétés
const updatedUser = {...user, ville: 'Paris', age: 26};
console.log(updatedUser);       // {nom: 'Alice', age: 26, ville: 'Paris'}

// === DESTRUCTURING - ARRAYS ===

// Basique
const fruits = ['pomme', 'banane', 'orange'];
const [first, second, third] = fruits;
console.log(first);             // 'pomme'
console.log(second);            // 'banane'

// Sauter des éléments
const [premier, , troisieme] = fruits;
console.log(premier);           // 'pomme'
console.log(troisieme);         // 'orange'

// Avec rest operator
const nums = [1, 2, 3, 4, 5];
const [a, b, ...rest] = nums;
console.log(a);                 // 1
console.log(b);                 // 2
console.log(rest);              // [3, 4, 5]

// Valeurs par défaut
const [x, y, z = 0] = [1, 2];
console.log(z);                 // 0

// Échanger variables (swap)
let var1 = 'A';
let var2 = 'B';
[var1, var2] = [var2, var1];
console.log(var1, var2);        // 'B' 'A'

// === DESTRUCTURING - OBJETS ===

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

// Basique
const {nom, age, ville} = person;
console.log(nom);               // 'Alice'
console.log(age);               // 25

// Renommer variables
const {nom: name, age: years} = person;
console.log(name);              // 'Alice'
console.log(years);             // 25

// Valeurs par défaut
const {nom: n, pays = 'France'} = person;
console.log(pays);              // 'France'

// Avec rest
const {nom: n2, ...autres} = person;
console.log(autres);            // {age: 25, ville: 'Paris'}

// Destructuring imbriqué
const user = {
    id: 1,
    info: {
        nom: 'Alice',
        contact: {
            email: 'alice@example.com'
        }
    }
};

const {info: {nom: userName, contact: {email}}} = user;
console.log(userName);          // 'Alice'
console.log(email);             // 'alice@example.com'

// === DESTRUCTURING DANS FONCTIONS ===

// Paramètres de fonction
function afficherUser({nom, age, ville = 'Paris'}) {
    console.log(`${nom}, ${age} ans, ${ville}`);
}

afficherUser({nom: 'Alice', age: 25});
// Alice, 25 ans, Paris

// Avec arrays
function sommeMultiplication([a, b]) {
    return {
        somme: a + b,
        produit: a * b
    };
}

console.log(sommeMultiplication([3, 4]));
// {somme: 7, produit: 12}


[OK] OBJETS - CRÉATION & PROPRIÉTÉS

// === CRÉATION D'OBJETS ===

// Littéral d'objet (RECOMMANDÉ)
const person = {
    nom: 'Alice',
    age: 25,
    ville: 'Paris'
};

// Objet vide
const empty = {};

// Avec constructeur Object (rarement utilisé)
const obj = new Object();
obj.nom = 'Bob';

// === ACCÈS AUX PROPRIÉTÉS ===

// Notation point (RECOMMANDÉ)
console.log(person.nom);        // 'Alice'
console.log(person.age);        // 25

// Notation crochet
console.log(person['nom']);     // 'Alice'
console.log(person['age']);     // 25

// Avec variable
const prop = 'ville';
console.log(person[prop]);      // 'Paris'

// Propriété avec espaces (nécessite crochets)
const obj2 = {
    'full name': 'Alice Dupont'
};
console.log(obj2['full name']); // 'Alice Dupont'

// === AJOUTER/MODIFIER PROPRIÉTÉS ===

// Ajouter propriété
person.email = 'alice@example.com';
person['telephone'] = '0123456789';

// Modifier propriété
person.age = 26;
person['ville'] = 'Lyon';

console.log(person);
// {nom: 'Alice', age: 26, ville: 'Lyon', email: '...', telephone: '...'}

// === SUPPRIMER PROPRIÉTÉ ===

delete person.telephone;
console.log(person.telephone);  // undefined

// === VÉRIFIER EXISTENCE ===

// in operator
console.log('nom' in person);       // true
console.log('prenom' in person);    // false

// hasOwnProperty
console.log(person.hasOwnProperty('nom'));      // true
console.log(person.hasOwnProperty('toString')); // false (hérité)

// Vérifier undefined
console.log(person.email !== undefined);    // true

// === PROPRIÉTÉS CALCULÉES (ES6) ===

const key = 'dynamique';
const obj3 = {
    [key]: 'valeur',
    ['prop_' + 123]: 'test'
};
console.log(obj3);      // {dynamique: 'valeur', prop_123: 'test'}

// === SHORTHAND PROPERTY (ES6) ===

const nom = 'Alice';
const age = 25;

// Ancien
const user1 = {
    nom: nom,
    age: age
};

// Moderne (si nom variable = nom propriété)
const user2 = {nom, age};
console.log(user2);     // {nom: 'Alice', age: 25}

// === MÉTHODES D'OBJETS ===

const calculator = {
    // Ancien
    add: function(a, b) {
        return a + b;
    },
    
    // Moderne (method shorthand)
    subtract(a, b) {
        return a - b;
    },
    
    // Arrow function (attention au 'this')
    multiply: (a, b) => a * b
};

console.log(calculator.add(5, 3));      // 8
console.log(calculator.subtract(5, 3)); // 2

// === THIS DANS OBJETS ===

const user3 = {
    nom: 'Alice',
    age: 25,
    
    // Méthode normale (this = objet)
    saluer: function() {
        console.log(`Bonjour, je suis ${this.nom}`);
    },
    
    // Shorthand (this = objet)
    direAge() {
        console.log(`J'ai ${this.age} ans`);
    },
    
    // Arrow (this = contexte extérieur, pas l'objet!)
    saluerArrow: () => {
        console.log(`Bonjour ${this.nom}`); // undefined!
    }
};

user3.saluer();         // Bonjour, je suis Alice
user3.direAge();        // J'ai 25 ans

// === GETTERS & SETTERS ===

const personne = {
    prenom: 'Alice',
    nom: 'Dupont',
    
    // Getter
    get nomComplet() {
        return `${this.prenom} ${this.nom}`;
    },
    
    // Setter
    set nomComplet(valeur) {
        [this.prenom, this.nom] = valeur.split(' ');
    }
};

console.log(personne.nomComplet);   // 'Alice Dupont' (appelé comme propriété)
personne.nomComplet = 'Bob Martin'; // Utilise le setter
console.log(personne.prenom);       // 'Bob'

// === OBJETS IMBRIQUÉS ===

const company = {
    nom: 'TechCorp',
    adresse: {
        rue: '123 Main St',
        ville: 'Paris',
        pays: 'France'
    },
    employees: [
        {nom: 'Alice', poste: 'Dev'},
        {nom: 'Bob', poste: 'Designer'}
    ]
};

console.log(company.adresse.ville);         // 'Paris'
console.log(company.employees[0].nom);      // 'Alice'

// Avec optional chaining
console.log(company.adresse?.codePostal);   // undefined (pas d'erreur)


[OK] OBJETS - MÉTHODES UTILES

// === OBJECT.KEYS() - obtenir clés ===
const user = {
    nom: 'Alice',
    age: 25,
    ville: 'Paris'
};

const keys = Object.keys(user);
console.log(keys);              // ['nom', 'age', 'ville']

// Itérer sur clés
Object.keys(user).forEach(key => {
    console.log(`${key}: ${user[key]}`);
});

// === OBJECT.VALUES() - obtenir valeurs ===
const values = Object.values(user);
console.log(values);            // ['Alice', 25, 'Paris']

// === OBJECT.ENTRIES() - obtenir paires [clé, valeur] ===
const entries = Object.entries(user);
console.log(entries);
// [['nom', 'Alice'], ['age', 25], ['ville', 'Paris']]

// Itérer sur entries
Object.entries(user).forEach(([key, value]) => {
    console.log(`${key}: ${value}`);
});

// Convertir en Map
const map = new Map(Object.entries(user));

// === OBJECT.ASSIGN() - copier/fusionner objets ===

// Copier objet (shallow copy)
const original = {a: 1, b: 2};
const copie = Object.assign({}, original);

// Fusionner objets
const obj1 = {a: 1, b: 2};
const obj2 = {b: 3, c: 4};
const merged = Object.assign({}, obj1, obj2);
console.log(merged);            // {a: 1, b: 3, c: 4}

// Modifier premier argument
Object.assign(obj1, obj2);
console.log(obj1);              // {a: 1, b: 3, c: 4} (modifié!)

// Avec spread (MODERNE, préféré)
const merged2 = {...obj1, ...obj2};

// === OBJECT.FREEZE() - rendre immuable ===
const config = {
    API_URL: 'https://api.example.com',
    TIMEOUT: 5000
};

Object.freeze(config);

config.API_URL = 'autre';       // Ignoré en mode non-strict
delete config.TIMEOUT;          // Ignoré
config.nouvelle = 'prop';       // Ignoré

console.log(config);            // Inchangé

// Vérifier si frozen
console.log(Object.isFrozen(config));   // true

// === OBJECT.SEAL() - empêcher ajout/suppression ===
const obj = {a: 1, b: 2};
Object.seal(obj);

obj.a = 10;                     // [OK] Modification OK
obj.c = 3;                      // [X] Ajout ignoré
delete obj.b;                   // [X] Suppression ignorée

console.log(Object.isSealed(obj));      // true

// === OBJECT.PREVENTEXTENSIONS() - empêcher ajout ===
const obj3 = {a: 1};
Object.preventExtensions(obj3);

obj3.a = 10;                    // [OK] Modification OK
obj3.b = 2;                     // [X] Ajout ignoré
delete obj3.a;                  // [OK] Suppression OK

// === OBJECT.CREATE() - créer avec prototype ===
const proto = {
    saluer() {
        console.log(`Bonjour ${this.nom}`);
    }
};

const person = Object.create(proto);
person.nom = 'Alice';
person.saluer();                // Bonjour Alice

// === OBJECT.FROMENTRIES() - créer objet depuis entries ===
const entries2 = [['nom', 'Alice'], ['age', 25]];
const obj4 = Object.fromEntries(entries2);
console.log(obj4);              // {nom: 'Alice', age: 25}

// Convertir Map en objet
const map2 = new Map([['a', 1], ['b', 2]]);
const obj5 = Object.fromEntries(map2);

// === OBJECT.DEFINEPROPERTY() - définir propriété avec descripteurs ===
const obj6 = {};

Object.defineProperty(obj6, 'nom', {
    value: 'Alice',
    writable: false,        // Non modifiable
    enumerable: true,       // Apparaît dans boucles
    configurable: false     // Ne peut pas être supprimé
});

obj6.nom = 'Bob';           // Ignoré (writable: false)
console.log(obj6.nom);      // 'Alice'

// === OBJECT.GETOWNPROPERTYDESCRIPTOR() ===
const descriptor = Object.getOwnPropertyDescriptor(obj6, 'nom');
console.log(descriptor);
// {value: 'Alice', writable: false, enumerable: true, configurable: false}


[OK] JSON (JavaScript Object Notation)

// === JSON.STRINGIFY() - convertir en JSON ===

const user = {
    nom: 'Alice',
    age: 25,
    actif: true,
    hobbies: ['lecture', 'sport']
};

// Basique
const json = JSON.stringify(user);
console.log(json);
// '{"nom":"Alice","age":25,"actif":true,"hobbies":["lecture","sport"]}'

// Avec indentation (pretty print)
const jsonPretty = JSON.stringify(user, null, 2);
console.log(jsonPretty);
/*
{
  "nom": "Alice",
  "age": 25,
  "actif": true,
  "hobbies": [
    "lecture",
    "sport"
  ]
}
*/

// Avec replacer (filtrer propriétés)
const jsonFiltre = JSON.stringify(user, ['nom', 'age']);
console.log(jsonFiltre);        // '{"nom":"Alice","age":25}'

// Avec fonction replacer
const jsonCustom = JSON.stringify(user, (key, value) => {
    if (key === 'age') return undefined;    // Exclure age
    return value;
});

// [ATTENTION] Limites de stringify:
// - Fonctions: ignorées
// - undefined: ignoré
// - Symbol: ignoré
// - Date: converti en string
// - Infinity, NaN: converti en null

const obj = {
    fonction: function() {},    // Ignoré
    undef: undefined,          // Ignoré
    date: new Date(),          // Converti en string ISO
    num: Infinity              // null
};

console.log(JSON.stringify(obj));
// '{"date":"2024-01-15T10:30:00.000Z","num":null}'

// === JSON.PARSE() - convertir depuis JSON ===

const jsonString = '{"nom":"Alice","age":25}';
const objet = JSON.parse(jsonString);
console.log(objet);             // {nom: 'Alice', age: 25}
console.log(objet.nom);         // 'Alice'

// Avec reviver (transformer valeurs)
const jsonAvecDate = '{"nom":"Alice","date":"2024-01-15T10:30:00.000Z"}';
const objetAvecDate = JSON.parse(jsonAvecDate, (key, value) => {
    if (key === 'date') {
        return new Date(value); // Convertir string en Date
    }
    return value;
});

// Gestion d'erreurs
const jsonInvalide = '{nom: "Alice"}';  // Pas de guillemets autour de 'nom'
try {
    JSON.parse(jsonInvalide);
} catch (error) {
    console.log('JSON invalide!');
}

// === DEEP CLONE avec JSON ===
const original = {
    nom: 'Alice',
    adresse: {
        ville: 'Paris'
    }
};

// Clone profond (attention: perd fonctions, dates, etc.)
const clone = JSON.parse(JSON.stringify(original));
clone.adresse.ville = 'Lyon';
console.log(original.adresse.ville);    // 'Paris' (inchangé)

// === STOCKER DANS LOCALSTORAGE ===

// Sauvegarder
localStorage.setItem('user', JSON.stringify(user));

// Récupérer
const savedUser = JSON.parse(localStorage.getItem('user'));


[OK] STRINGS - MÉTHODES AVANCÉES

const texte = 'Hello World JavaScript';

// === RECHERCHE ===

// includes() - contient?
console.log(texte.includes('World'));       // true
console.log(texte.includes('Python'));      // false
console.log(texte.includes('world'));       // false (sensible à la casse)

// startsWith() - commence par?
console.log(texte.startsWith('Hello'));     // true
console.log(texte.startsWith('World'));     // false

// endsWith() - finit par?
console.log(texte.endsWith('Script'));      // true

// indexOf() - position première occurrence
console.log(texte.indexOf('o'));            // 4
console.log(texte.indexOf('o', 5));         // 7 (cherche à partir de index 5)
console.log(texte.indexOf('xyz'));          // -1 (pas trouvé)

// lastIndexOf() - position dernière occurrence
console.log(texte.lastIndexOf('o'));        // 7

// search() - avec regex
console.log(texte.search(/world/i));        // 6 (insensible à la casse)

// === EXTRACTION ===

// slice(start, end)
console.log(texte.slice(0, 5));             // 'Hello'
console.log(texte.slice(6, 11));            // 'World'
console.log(texte.slice(6));                // 'World JavaScript' (jusqu'à la fin)
console.log(texte.slice(-10));              // 'JavaScript' (depuis la fin)

// substring(start, end) - similaire à slice
console.log(texte.substring(0, 5));         // 'Hello'
// Différence: substring inverse si start > end
console.log(texte.substring(5, 0));         // 'Hello' (inversé automatiquement)

// substr(start, length) - DÉPRÉCIÉ
console.log(texte.substr(6, 5));            // 'World'

// charAt(index)
console.log(texte.charAt(0));               // 'H'
console.log(texte[0]);                      // 'H' (accès direct)

// charCodeAt(index) - code Unicode
console.log(texte.charCodeAt(0));           // 72 (code de 'H')

// === MODIFICATION ===

// replace() - remplacer première occurrence
console.log(texte.replace('o', 'O'));       // 'HellO World JavaScript'

// replaceAll() - remplacer toutes occurrences
console.log(texte.replaceAll('o', 'O'));    // 'HellO WOrld JavaScript'

// Avec regex
console.log(texte.replace(/o/g, 'O'));      // 'HellO WOrld JavaScript'
console.log(texte.replace(/o/gi, 'O'));     // Insensible à la casse

// toLowerCase() / toUpperCase()
console.log(texte.toLowerCase());           // 'hello world javascript'
console.log(texte.toUpperCase());           // 'HELLO WORLD JAVASCRIPT'

// trim() - enlever espaces début/fin
const str = '   Hello   ';
console.log(str.trim());                    // 'Hello'
console.log(str.trimStart());               // 'Hello   '
console.log(str.trimEnd());                 // '   Hello'

// padStart() / padEnd() - remplir
console.log('5'.padStart(3, '0'));          // '005'
console.log('5'.padEnd(3, '0'));            // '500'

// repeat()
console.log('Ha'.repeat(3));                // 'HaHaHa'

// === CONVERSION ===

// split() - convertir en array
console.log(texte.split(' '));              // ['Hello', 'World', 'JavaScript']
console.log(texte.split(''));               // ['H', 'e', 'l', 'l', 'o', ...]
console.log('a,b,c'.split(','));            // ['a', 'b', 'c']

// Avec limite
console.log(texte.split(' ', 2));           // ['Hello', 'World']

// === TEMPLATE LITERALS ===

const nom = 'Alice';
const age = 25;

// Interpolation
const message = `Bonjour ${nom}, vous avez ${age} ans`;

// Expression
const info = `L'année prochaine: ${age + 1} ans`;

// Multilignes
const html = `
    <div>
        <h1>${nom}</h1>
        <p>Âge: ${age}</p>
    </div>
`;

// Appel de fonction
const prix = 19.99;
const message2 = `Prix: ${prix.toFixed(2)} €`;

// === TAGGED TEMPLATES ===
function highlight(strings, ...values) {
    return strings.reduce((result, str, i) => {
        return `${result}${str}<strong>${values[i] || ''}</strong>`;
    }, '');
}

const name = 'Alice';
const tagged = highlight`Hello ${name}, welcome!`;
// 'Hello <strong>Alice</strong>, welcome!<strong></strong>'


[OK] NUMBERS & MATH

// === MÉTHODES NUMBER ===

// parseInt() - convertir en entier
console.log(parseInt('123'));               // 123
console.log(parseInt('123.45'));            // 123
console.log(parseInt('123px'));             // 123 (ignore le reste)
console.log(parseInt('px123'));             // NaN

// Avec base (radix)
console.log(parseInt('1010', 2));           // 10 (binaire)
console.log(parseInt('FF', 16));            // 255 (hexadécimal)

// parseFloat() - convertir en décimal
console.log(parseFloat('123.45'));          // 123.45
console.log(parseFloat('123.45px'));        // 123.45

// Number() - conversion stricte
console.log(Number('123'));                 // 123
console.log(Number('123px'));               // NaN
console.log(Number(true));                  // 1
console.log(Number(false));                 // 0

// toFixed() - arrondir avec décimales
const num = 123.456789;
console.log(num.toFixed(2));                // '123.46' (string!)
console.log(+num.toFixed(2));               // 123.46 (number)

// toPrecision() - précision totale
console.log(num.toPrecision(4));            // '123.5'

// toExponential() - notation scientifique
console.log(num.toExponential(2));          // '1.23e+2'

// toString() - convertir en string
console.log(num.toString());                // '123.456789'
console.log((255).toString(16));            // 'ff' (hexadécimal)
console.log((10).toString(2));              // '1010' (binaire)

// === VÉRIFICATIONS NUMBER ===

// isNaN() - est NaN?
console.log(Number.isNaN(NaN));             // true
console.log(Number.isNaN(123));             // false
console.log(Number.isNaN('hello'));         // false (pas converti!)

// isNaN global (avec conversion)
console.log(isNaN('hello'));                // true (converti en NaN)

// isFinite() - est fini?
console.log(Number.isFinite(123));          // true
console.log(Number.isFinite(Infinity));     // false
console.log(Number.isFinite(NaN));          // false

// isInteger() - est entier?
console.log(Number.isInteger(123));         // true
console.log(Number.isInteger(123.45));      // false

// isSafeInteger() - dans range sûr?
console.log(Number.isSafeInteger(123));     // true
console.log(Number.isSafeInteger(9007199254740992)); // false

// === CONSTANTES NUMBER ===

console.log(Number.MAX_VALUE);              // Plus grand nombre représentable
console.log(Number.MIN_VALUE);              // Plus petit nombre positif
console.log(Number.MAX_SAFE_INTEGER);       // 9007199254740991
console.log(Number.MIN_SAFE_INTEGER);       // -9007199254740991
console.log(Number.POSITIVE_INFINITY);      // Infinity
console.log(Number.NEGATIVE_INFINITY);      // -Infinity
console.log(Number.NaN);                    // NaN

// === MATH OBJECT ===

// Constantes
console.log(Math.PI);                       // 3.141592653589793
console.log(Math.E);                        // 2.718281828459045

// Arrondis
console.log(Math.round(4.5));               // 5
console.log(Math.round(4.4));               // 4
console.log(Math.ceil(4.1));                // 5 (arrondi supérieur)
console.log(Math.floor(4.9));               // 4 (arrondi inférieur)
console.log(Math.trunc(4.9));               // 4 (supprime décimales)

// Valeur absolue
console.log(Math.abs(-5));                  // 5

// Puissance et racine
console.log(Math.pow(2, 3));                // 8 (2³)
console.log(2 ** 3);                        // 8 (même chose)
console.log(Math.sqrt(16));                 // 4 (racine carrée)
console.log(Math.cbrt(27));                 // 3 (racine cubique)

// Min / Max
console.log(Math.min(5, 2, 9, 1));          // 1
console.log(Math.max(5, 2, 9, 1));          // 9

// Avec array
const nums = [5, 2, 9, 1];
console.log(Math.min(...nums));             // 1
console.log(Math.max(...nums));             // 9

// Random
console.log(Math.random());                 // 0 à 1 (exclu)

// Nombre aléatoire entre min et max
function randomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}
console.log(randomInt(1, 10));              // Nombre entre 1 et 10

// Trigonométrie
console.log(Math.sin(Math.PI / 2));         // 1
console.log(Math.cos(0));                   // 1
console.log(Math.tan(Math.PI / 4));         // 1

// Logarithmes
console.log(Math.log(Math.E));              // 1 (ln)
console.log(Math.log10(100));               // 2
console.log(Math.log2(8));                  // 3

// Signe
console.log(Math.sign(-5));                 // -1
console.log(Math.sign(5));                  // 1
console.log(Math.sign(0));                  // 0


[OK] DATES

// === CRÉER DES DATES ===

// Date actuelle
const maintenant = new Date();
console.log(maintenant);

// Date spécifique (année, mois, jour, heure, minute, seconde, ms)
// [ATTENTION] Mois commence à 0! (0 = janvier, 11 = décembre)
const date1 = new Date(2024, 0, 15);        // 15 janvier 2024
const date2 = new Date(2024, 0, 15, 14, 30); // 15 jan 2024, 14:30

// Depuis string
const date3 = new Date('2024-01-15');
const date4 = new Date('January 15, 2024');
const date5 = new Date('2024-01-15T14:30:00');

// Depuis timestamp (millisecondes depuis 1/1/1970)
const date6 = new Date(1705329600000);

// === OBTENIR COMPOSANTS D'UNE DATE ===

const date = new Date('2024-01-15T14:30:45');

// Année, mois, jour
console.log(date.getFullYear());        // 2024
console.log(date.getMonth());           // 0 (janvier, 0-11)
console.log(date.getDate());            // 15 (jour du mois, 1-31)
console.log(date.getDay());             // 1 (jour de la semaine, 0-6, 0=dimanche)

// Heure, minute, seconde
console.log(date.getHours());           // 14
console.log(date.getMinutes());         // 30
console.log(date.getSeconds());         // 45
console.log(date.getMilliseconds());    // 0

// Timestamp
console.log(date.getTime());            // Millisecondes depuis 1/1/1970

// UTC (temps universel)
console.log(date.getUTCHours());
console.log(date.getUTCDate());

// === MODIFIER UNE DATE ===

const date7 = new Date('2024-01-15');

date7.setFullYear(2025);
date7.setMonth(5);                  // Juin (0-11)
date7.setDate(20);
date7.setHours(15, 45, 30);         // Heure, minute, seconde

console.log(date7);

// === FORMATER DATES ===

const now = new Date();

// Méthodes simples
console.log(now.toString());
// 'Mon Jan 15 2024 14:30:45 GMT+0100'

console.log(now.toDateString());
// 'Mon Jan 15 2024'

console.log(now.toTimeString());
// '14:30:45 GMT+0100'

console.log(now.toISOString());
// '2024-01-15T13:30:45.000Z' (UTC)

console.log(now.toLocaleDateString());
// '15/01/2024' (dépend de la locale)

console.log(now.toLocaleTimeString());
// '14:30:45'

console.log(now.toLocaleString());
// '15/01/2024 14:30:45'

// Avec options de formatage
console.log(now.toLocaleDateString('fr-FR', {
    weekday: 'long',
    year: 'numeric',
    month: 'long',
    day: 'numeric'
}));
// 'lundi 15 janvier 2024'

console.log(now.toLocaleString('fr-FR', {
    dateStyle: 'full',
    timeStyle: 'short'
}));

// === CALCULS DE DATES ===

// Différence entre dates
const date1b = new Date('2024-01-15');
const date2b = new Date('2024-01-20');

const diffMs = date2b - date1b;             // Différence en millisecondes
const diffDays = diffMs / (1000 * 60 * 60 * 24);
console.log(diffDays);                      // 5 jours

// Ajouter/soustraire jours
const today = new Date();
const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1);

const nextWeek = new Date(today);
nextWeek.setDate(nextWeek.getDate() + 7);

// Ajouter mois
const nextMonth = new Date(today);
nextMonth.setMonth(nextMonth.getMonth() + 1);

// === DATE.NOW() - timestamp actuel ===
const timestamp = Date.now();
console.log(timestamp);                     // Millisecondes depuis 1/1/1970

// === DATE.PARSE() - parser string ===
const ts = Date.parse('2024-01-15');
console.log(ts);                            // Timestamp

// === COMPARAISON DE DATES ===

const dateA = new Date('2024-01-15');
const dateB = new Date('2024-01-20');

console.log(dateA < dateB);                 // true
console.log(dateA > dateB);                 // false
console.log(dateA.getTime() === dateB.getTime()); // false

// === BIBLIOTHÈQUES RECOMMANDÉES ===
// Pour manipulation avancée de dates:
// - date-fns: moderne, fonctionnelle
// - Day.js: léger, compatible Moment.js
// - Luxon: par créateur de Moment.js


[OK] DOM (Document Object Model) - SÉLECTION

// === SÉLECTIONNER DES ÉLÉMENTS ===

// getElementById() - par ID (un seul élément)
const header = document.getElementById('header');
console.log(header);

// getElementsByClassName() - par classe (HTMLCollection)
const items = document.getElementsByClassName('item');
console.log(items);                         // HTMLCollection [...]
console.log(items[0]);                      // Premier élément
console.log(items.length);                  // Nombre d'éléments

// getElementsByTagName() - par tag (HTMLCollection)
const paragraphs = document.getElementsByTagName('p');
const divs = document.getElementsByTagName('div');
const allElements = document.getElementsByTagName('*'); // Tous

// querySelector() - premier élément correspondant (MODERNE)
const firstItem = document.querySelector('.item');
const firstP = document.querySelector('p');
const specific = document.querySelector('#header .title');
const input = document.querySelector('input[type="text"]');

// querySelectorAll() - tous les éléments (NodeList)
const allItems = document.querySelectorAll('.item');
const allP = document.querySelectorAll('p');

// Différence HTMLCollection vs NodeList:
// - HTMLCollection: live (se met à jour auto)
// - NodeList (de querySelectorAll): statique

// Convertir en array
const itemsArray = Array.from(items);
const itemsArray2 = [...allItems];

// === NAVIGUER DANS LE DOM ===

const element = document.querySelector('.item');

// Parents
console.log(element.parentElement);         // Élément parent
console.log(element.parentNode);            // Nœud parent
console.log(element.closest('.container')); // Ancêtre le plus proche

// Enfants
console.log(element.children);              // HTMLCollection des enfants
console.log(element.childNodes);            // NodeList (inclut texte)
console.log(element.firstElementChild);     // Premier enfant
console.log(element.lastElementChild);      // Dernier enfant

// Frères et sœurs
console.log(element.nextElementSibling);    // Élément suivant
console.log(element.previousElementSibling); // Élément précédent

// === DOCUMENT ===

// Éléments spéciaux
console.log(document.documentElement);      // <html>
console.log(document.head);                 // <head>
console.log(document.body);                 // <body>
console.log(document.title);                // Titre de la page

// Modifier titre
document.title = 'Nouveau titre';


[OK] DOM - MANIPULATION

// === CRÉER DES ÉLÉMENTS ===

// createElement()
const div = document.createElement('div');
const p = document.createElement('p');
const span = document.createElement('span');

// createTextNode()
const text = document.createTextNode('Hello World');

// === MODIFIER LE CONTENU ===

const element = document.querySelector('#myElement');

// innerHTML - contenu HTML
element.innerHTML = '<strong>Texte en gras</strong>';
element.innerHTML = element.innerHTML + ' plus de texte'; // Ajouter

// [ATTENTION] Attention: innerHTML peut être dangereux (XSS)
// Ne pas utiliser avec données utilisateur non filtrées

// textContent - texte seul (sûr)
element.textContent = 'Texte simple';
// Les balises HTML sont échappées:
element.textContent = '<strong>pas gras</strong>'; // Affiché tel quel

// innerText - texte visible (respecte CSS)
element.innerText = 'Texte visible';

// === MODIFIER LES ATTRIBUTS ===

const link = document.querySelector('a');

// getAttribute() / setAttribute()
const href = link.getAttribute('href');
link.setAttribute('href', 'https://example.com');
link.setAttribute('target', '_blank');

// Propriétés directes (préféré)
link.href = 'https://example.com';
link.id = 'myLink';
link.className = 'btn btn-primary';

// hasAttribute() / removeAttribute()
if (link.hasAttribute('target')) {
    link.removeAttribute('target');
}

// === CLASSES CSS ===

const el = document.querySelector('.item');

// classList (MODERNE, recommandé)
el.classList.add('active');
el.classList.add('selected', 'highlighted'); // Plusieurs
el.classList.remove('active');
el.classList.toggle('active');              // Ajouter/retirer
el.classList.contains('active');            // Vérifier
el.classList.replace('old', 'new');

// className (ancien)
el.className = 'item active';               // Remplace toutes les classes
el.className += ' selected';                // Ajouter classe

// === STYLES CSS ===

// Style inline
el.style.color = 'red';
el.style.backgroundColor = 'blue';
el.style.fontSize = '20px';
el.style.marginTop = '10px';

// [ATTENTION] Utilise camelCase pour propriétés CSS
// background-color -> backgroundColor
// font-size -> fontSize

// Plusieurs styles
Object.assign(el.style, {
    color: 'white',
    backgroundColor: 'black',
    padding: '10px'
});

// Obtenir styles calculés
const styles = window.getComputedStyle(el);
console.log(styles.color);
console.log(styles.fontSize);

// === AJOUTER/RETIRER DU DOM ===

const parent = document.querySelector('.container');
const child = document.createElement('div');

// appendChild() - ajouter à la fin
parent.appendChild(child);

// append() - moderne, multiple éléments
parent.append(child);
parent.append('Texte', child, 'Plus de texte');

// prepend() - ajouter au début
parent.prepend(child);

// insertBefore()
const reference = document.querySelector('.reference');
parent.insertBefore(child, reference);

// insertAdjacentElement()
element.insertAdjacentElement('beforebegin', child); // Avant element
element.insertAdjacentElement('afterbegin', child);  // Début de element
element.insertAdjacentElement('beforeend', child);   // Fin de element
element.insertAdjacentElement('afterend', child);    // Après element

// insertAdjacentHTML()
element.insertAdjacentHTML('afterbegin', '<p>HTML</p>');

// remove() - retirer élément
child.remove();

// removeChild() - retirer enfant
parent.removeChild(child);

// replaceChild() - remplacer
const newChild = document.createElement('span');
parent.replaceChild(newChild, child);

// === CLONER DES ÉLÉMENTS ===

const original = document.querySelector('.item');

// Clone superficiel (sans enfants)
const clone1 = original.cloneNode();

// Clone profond (avec enfants)
const clone2 = original.cloneNode(true);

parent.appendChild(clone2);

// === PROPRIÉTÉS UTILES ===

// Dimensions
console.log(el.offsetWidth);                // Largeur totale
console.log(el.offsetHeight);               // Hauteur totale
console.log(el.clientWidth);                // Largeur sans scrollbar
console.log(el.clientHeight);               // Hauteur sans scrollbar
console.log(el.scrollWidth);                // Largeur avec scroll
console.log(el.scrollHeight);               // Hauteur avec scroll

// Position
console.log(el.offsetTop);                  // Distance du haut
console.log(el.offsetLeft);                 // Distance de la gauche

// Scroll
console.log(el.scrollTop);                  // Position scroll verticale
console.log(el.scrollLeft);                 // Position scroll horizontale
el.scrollTop = 0;                           // Scroller en haut

// Vérifier visibilité
console.log(el.offsetParent);               // null si caché


[OK] ÉVÉNEMENTS - BASES

// === AJOUTER DES ÉCOUTEURS D'ÉVÉNEMENTS ===

const button = document.querySelector('button');

// addEventListener() (MODERNE, recommandé)
button.addEventListener('click', function() {
    console.log('Bouton cliqué!');
});

// Avec arrow function
button.addEventListener('click', () => {
    console.log('Cliqué!');
});

// Fonction nommée (pour pouvoir retirer)
function handleClick() {
    console.log('Cliqué!');
}
button.addEventListener('click', handleClick);

// === ÉVÉNEMENTS COURANTS ===

// Souris
element.addEventListener('click', () => {});        // Clic
element.addEventListener('dblclick', () => {});     // Double-clic
element.addEventListener('mousedown', () => {});    // Bouton enfoncé
element.addEventListener('mouseup', () => {});      // Bouton relâché
element.addEventListener('mouseenter', () => {});   // Souris entre
element.addEventListener('mouseleave', () => {});   // Souris sort
element.addEventListener('mousemove', () => {});    // Souris bouge
element.addEventListener('mouseover', () => {});    // Survol (bulle)
element.addEventListener('mouseout', () => {});     // Sort (bulle)

// Clavier
element.addEventListener('keydown', () => {});      // Touche enfoncée
element.addEventListener('keyup', () => {});        // Touche relâchée
element.addEventListener('keypress', () => {});     // Caractère (déprécié)

// Formulaires
input.addEventListener('input', () => {});          // Valeur change
input.addEventListener('change', () => {});         // Perd focus après changement
input.addEventListener('focus', () => {});          // Reçoit focus
input.addEventListener('blur', () => {});           // Perd focus
form.addEventListener('submit', () => {});          // Soumission formulaire

// Fenêtre
window.addEventListener('load', () => {});          // Page chargée
window.addEventListener('DOMContentLoaded', () => {}); // DOM prêt
window.addEventListener('resize', () => {});        // Fenêtre redimensionnée
window.addEventListener('scroll', () => {});        // Scroll

// Autres
element.addEventListener('contextmenu', () => {});  // Clic droit

// === OBJET EVENT ===

button.addEventListener('click', function(event) {
    console.log(event);                     // Objet Event
    
    // Propriétés communes
    console.log(event.type);                // 'click'
    console.log(event.target);              // Élément cliqué
    console.log(event.currentTarget);       // Élément avec listener (this)
    console.log(event.timeStamp);           // Timestamp
    
    // Empêcher comportement par défaut
    event.preventDefault();
    
    // Arrêter propagation
    event.stopPropagation();
});

// === EVENT SOURIS ===

element.addEventListener('click', (e) => {
    console.log(e.clientX, e.clientY);      // Position dans viewport
    console.log(e.pageX, e.pageY);          // Position dans page
    console.log(e.screenX, e.screenY);      // Position dans écran
    console.log(e.button);                  // 0: gauche, 1: milieu, 2: droit
    console.log(e.buttons);                 // Boutons enfoncés
    console.log(e.altKey);                  // Alt enfoncé?
    console.log(e.ctrlKey);                 // Ctrl enfoncé?
    console.log(e.shiftKey);                // Shift enfoncé?
    console.log(e.metaKey);                 // Meta/Cmd enfoncé?
});

// === EVENT CLAVIER ===

document.addEventListener('keydown', (e) => {
    console.log(e.key);                     // 'a', 'Enter', 'ArrowUp', etc.
    console.log(e.code);                    // 'KeyA', 'Enter', 'ArrowUp'
    console.log(e.keyCode);                 // Code numérique (déprécié)
    console.log(e.altKey);                  // Alt enfoncé?
    console.log(e.ctrlKey);                 // Ctrl enfoncé?
    console.log(e.shiftKey);                // Shift enfoncé?
    
    // Exemple: Ctrl+S
    if (e.ctrlKey && e.key === 's') {
        e.preventDefault();
        console.log('Sauvegarder!');
    }
});

// === EVENT INPUT ===

input.addEventListener('input', (e) => {
    console.log(e.target.value);            // Valeur actuelle
    console.log(e.data);                    // Données entrées
    console.log(e.inputType);               // Type d'input
});

// === RETIRER ÉCOUTEUR ===

function handler() {
    console.log('Cliqué');
}

button.addEventListener('click', handler);
button.removeEventListener('click', handler); // Même fonction!

// [ATTENTION] Ne fonctionne PAS avec fonction anonyme:
button.addEventListener('click', () => {});
button.removeEventListener('click', () => {}); // Pas la même fonction!

// === OPTIONS D'ÉVÉNEMENT ===

// once: exécuter une seule fois
button.addEventListener('click', handler, {once: true});

// capture: phase de capture (pas bubbling)
element.addEventListener('click', handler, {capture: true});

// passive: ne peut pas preventDefault (optimisation)
element.addEventListener('scroll', handler, {passive: true});

// === DÉLÉGATION D'ÉVÉNEMENTS ===

// Au lieu de mettre un listener sur chaque élément:
const items = document.querySelectorAll('.item');
items.forEach(item => {
    item.addEventListener('click', handler);
});

// Mettre un listener sur le parent:
const container = document.querySelector('.container');
container.addEventListener('click', (e) => {
    if (e.target.classList.contains('item')) {
        console.log('Item cliqué:', e.target);
    }
});

// === DÉCLENCHER DES ÉVÉNEMENTS ===

// Créer événement
const event = new Event('myevent');
const customEvent = new CustomEvent('myevent', {
    detail: {message: 'Hello'}
});

// Déclencher
element.dispatchEvent(event);
element.dispatchEvent(customEvent);

// Écouter événement custom
element.addEventListener('myevent', (e) => {
    console.log(e.detail.message);
});


[OK] ÉVÉNEMENTS - FORMULAIRES

// === INPUT EVENTS ===

const input = document.querySelector('input');

// input: chaque changement
input.addEventListener('input', (e) => {
    console.log('Valeur:', e.target.value);
});

// change: après perte de focus (si changé)
input.addEventListener('change', (e) => {
    console.log('Changé:', e.target.value);
});

// focus / blur
input.addEventListener('focus', () => {
    console.log('Focus reçu');
});

input.addEventListener('blur', () => {
    console.log('Focus perdu');
});

// === SOUMISSION DE FORMULAIRE ===

const form = document.querySelector('form');

form.addEventListener('submit', (e) => {
    e.preventDefault();                 // Empêcher rechargement page
    
    // Récupérer les données
    const formData = new FormData(form);
    
    // Accéder aux valeurs
    const email = formData.get('email');
    const password = formData.get('password');
    
    // Itérer sur toutes les valeurs
    for (let [key, value] of formData.entries()) {
        console.log(`${key}: ${value}`);
    }
    
    // Convertir en objet
    const data = Object.fromEntries(formData);
    console.log(data);
    
    // Validation
    if (!email || !password) {
        console.log('Champs requis!');
        return;
    }
    
    // Envoyer au serveur
    // fetch('/api/login', {
    //     method: 'POST',
    //     body: formData
    // });
});

// === ACCÉDER AUX VALEURS D'INPUT ===

// Input text
const textInput = document.querySelector('input[type="text"]');
console.log(textInput.value);
textInput.value = 'Nouvelle valeur';

// Checkbox
const checkbox = document.querySelector('input[type="checkbox"]');
console.log(checkbox.checked);          // true/false
checkbox.checked = true;

// Radio buttons
const radio = document.querySelector('input[type="radio"]:checked');
console.log(radio.value);

// Select
const select = document.querySelector('select');
console.log(select.value);
select.value = 'option2';

// Select multiple
const multiSelect = document.querySelector('select[multiple]');
const selectedOptions = Array.from(multiSelect.selectedOptions)
    .map(option => option.value);

// Textarea
const textarea = document.querySelector('textarea');
console.log(textarea.value);

// === VALIDATION HTML5 ===

// Vérifier validité
console.log(input.validity.valid);      // Globalement valide?
console.log(input.validity.valueMissing); // Requis mais vide?
console.log(input.validity.typeMismatch); // Type invalide?
console.log(input.validity.tooLong);    // Trop long?
console.log(input.validity.tooShort);   // Trop court?
console.log(input.validity.rangeOverflow); // > max?
console.log(input.validity.rangeUnderflow); // < min?
console.log(input.validity.patternMismatch); // Pattern invalide?

// Message d'erreur
console.log(input.validationMessage);

// Validation personnalisée
input.setCustomValidity('Ce champ est invalide');
input.setCustomValidity('');            // Réinitialiser

// Vérifier validité du formulaire
console.log(form.checkValidity());      // true/false
form.reportValidity();                  // Affiche messages d'erreur

// === EXEMPLE COMPLET ===

form.addEventListener('submit', (e) => {
    e.preventDefault();
    
    // Validation personnalisée
    const email = form.querySelector('#email');
    const password = form.querySelector('#password');
    
    if (!email.value.includes('@')) {
        email.setCustomValidity('Email invalide');
        email.reportValidity();
        return;
    } else {
        email.setCustomValidity('');
    }
    
    if (password.value.length < 8) {
        password.setCustomValidity('Minimum 8 caractères');
        password.reportValidity();
        return;
    } else {
        password.setCustomValidity('');
    }
    
    // Si tout est valide
    const formData = new FormData(form);
    console.log('Données valides:', Object.fromEntries(formData));
    
    // Reset formulaire
    form.reset();
});


[OK] ASYNC - SETTIMEOUT & SETINTERVAL

// === SETTIMEOUT - exécuter après délai ===

// Syntaxe basique
setTimeout(() => {
    console.log('Après 2 secondes');
}, 2000);                               // Délai en millisecondes

// Avec fonction nommée
function direBonjour() {
    console.log('Bonjour!');
}
setTimeout(direBonjour, 1000);

// Avec paramètres
function saluer(nom, age) {
    console.log(`Bonjour ${nom}, ${age} ans`);
}
setTimeout(saluer, 1000, 'Alice', 25);

// Récupérer ID du timer
const timerId = setTimeout(() => {
    console.log('Ceci ne s\'exécutera pas');
}, 5000);

// Annuler le timer
clearTimeout(timerId);

// === SETINTERVAL - exécuter à intervalle régulier ===

// Répéter toutes les secondes
const intervalId = setInterval(() => {
    console.log('Tick');
}, 1000);

// Arrêter après 5 secondes
setTimeout(() => {
    clearInterval(intervalId);
    console.log('Arrêté');
}, 5000);

// Exemple: compte à rebours
let count = 10;
const countdown = setInterval(() => {
    console.log(count);
    count--;
    
    if (count < 0) {
        clearInterval(countdown);
        console.log('Terminé!');
    }
}, 1000);

// Exemple: horloge
function updateClock() {
    const now = new Date();
    console.log(now.toLocaleTimeString());
}

setInterval(updateClock, 1000);

// === DEBOUNCE (anti-rebond) ===
// Exécuter après que l'utilisateur arrête d'agir

function debounce(func, delay) {
    let timeoutId;
    return function(...args) {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => {
            func.apply(this, args);
        }, delay);
    };
}

// Utilisation: recherche en temps réel
const searchInput = document.querySelector('#search');
const search = debounce((value) => {
    console.log('Recherche:', value);
    // Appel API...
}, 500);

searchInput.addEventListener('input', (e) => {
    search(e.target.value);
});

// === THROTTLE (limitation) ===
// Exécuter maximum une fois par période

function throttle(func, delay) {
    let lastTime = 0;
    return function(...args) {
        const now = Date.now();
        if (now - lastTime >= delay) {
            lastTime = now;
            func.apply(this, args);
        }
    };
}

// Utilisation: scroll
const handleScroll = throttle(() => {
    console.log('Scrolling...');
}, 1000);

window.addEventListener('scroll', handleScroll);


[OK] PROMESSES (PROMISES)

// === CRÉER UNE PROMESSE ===

const maPromesse = new Promise((resolve, reject) => {
    // Code asynchrone
    const success = true;
    
    if (success) {
        resolve('Succès!');         // Promesse résolue
    } else {
        reject('Erreur!');          // Promesse rejetée
    }
});

// === CONSOMMER UNE PROMESSE ===

// then() - succès
maPromesse.then((resultat) => {
    console.log(resultat);          // 'Succès!'
});

// catch() - erreur
maPromesse.catch((erreur) => {
    console.log(erreur);            // 'Erreur!'
});

// finally() - toujours exécuté
maPromesse.finally(() => {
    console.log('Terminé');
});

// Chaîner then/catch/finally
maPromesse
    .then(resultat => console.log(resultat))
    .catch(erreur => console.log(erreur))
    .finally(() => console.log('Fin'));

// === EXEMPLE RÉALISTE ===

function attendreDelai(ms) {
    return new Promise(resolve => {
        setTimeout(resolve, ms);
    });
}

attendreDelai(2000)
    .then(() => {
        console.log('2 secondes écoulées');
    });

// Avec valeur
function telecharger(url) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (url) {
                resolve({data: 'Contenu du fichier'});
            } else {
                reject(new Error('URL invalide'));
            }
        }, 1000);
    });
}

telecharger('https://example.com/file.txt')
    .then(response => {
        console.log(response.data);
        return 'Traitement terminé';
    })
    .then(message => {
        console.log(message);
    })
    .catch(error => {
        console.error('Erreur:', error.message);
    });

// === CHAÎNER DES PROMESSES ===

function etape1() {
    return new Promise(resolve => {
        setTimeout(() => resolve('Étape 1'), 1000);
    });
}

function etape2(precedent) {
    return new Promise(resolve => {
        setTimeout(() => resolve(precedent + ' -> Étape 2'), 1000);
    });
}

function etape3(precedent) {
    return new Promise(resolve => {
        setTimeout(() => resolve(precedent + ' -> Étape 3'), 1000);
    });
}

etape1()
    .then(result1 => etape2(result1))
    .then(result2 => etape3(result2))
    .then(result3 => {
        console.log(result3);
        // 'Étape 1 -> Étape 2 -> Étape 3'
    });

// === PROMISE.ALL() - attendre toutes ===

const promesse1 = Promise.resolve(3);
const promesse2 = attendreDelai(1000).then(() => 42);
const promesse3 = fetch('https://api.example.com/data');

Promise.all([promesse1, promesse2, promesse3])
    .then(([resultat1, resultat2, resultat3]) => {
        console.log(resultat1, resultat2, resultat3);
    })
    .catch(error => {
        // Si UNE promesse échoue, tout échoue
        console.error(error);
    });

// === PROMISE.ALLSETTLED() - attendre toutes (succès ou échec) ===

Promise.allSettled([promesse1, promesse2, promesse3])
    .then(results => {
        results.forEach(result => {
            if (result.status === 'fulfilled') {
                console.log('Succès:', result.value);
            } else {
                console.log('Échec:', result.reason);
            }
        });
    });

// === PROMISE.RACE() - première à se terminer ===

const rapide = attendreDelai(500).then(() => 'Rapide');
const lent = attendreDelai(2000).then(() => 'Lent');

Promise.race([rapide, lent])
    .then(resultat => {
        console.log(resultat);      // 'Rapide'
    });

// === PROMISE.ANY() - première à réussir ===

const promesses = [
    Promise.reject('Erreur 1'),
    Promise.reject('Erreur 2'),
    Promise.resolve('Succès!'),
    Promise.resolve('Autre succès')
];

Promise.any(promesses)
    .then(result => {
        console.log(result);        // 'Succès!' (premier succès)
    })
    .catch(error => {
        // AggregateError si TOUTES échouent
        console.error('Toutes ont échoué:', error.errors);
    });

// === CRÉER PROMESSES RÉSOLUES/REJETÉES ===

// Promesse déjà résolue
const resolved = Promise.resolve('valeur');
resolved.then(val => console.log(val));     // 'valeur'

// Promesse déjà rejetée
const rejected = Promise.reject(new Error('erreur'));
rejected.catch(err => console.error(err));

// === CONVERTIR CALLBACK EN PROMESSE ===

// Ancienne API avec callback
function oldApiCall(url, callback) {
    setTimeout(() => {
        callback(null, {data: 'response'});
    }, 1000);
}

// Convertir en promesse
function apiCall(url) {
    return new Promise((resolve, reject) => {
        oldApiCall(url, (error, result) => {
            if (error) {
                reject(error);
            } else {
                resolve(result);
            }
        });
    });
}

// Utilisation moderne
apiCall('https://api.example.com')
    .then(data => console.log(data))
    .catch(err => console.error(err));

// === PROMISIFY (Node.js style) ===

function promisify(fn) {
    return function(...args) {
        return new Promise((resolve, reject) => {
            fn(...args, (error, result) => {
                if (error) {
                    reject(error);
                } else {
                    resolve(result);
                }
            });
        });
    };
}

// Utilisation
const readFilePromise = promisify(fs.readFile);
readFilePromise('file.txt', 'utf8')
    .then(content => console.log(content));

// === TIMEOUT DE PROMESSE ===

function promiseWithTimeout(promise, ms) {
    const timeout = new Promise((_, reject) => {
        setTimeout(() => reject(new Error('Timeout')), ms);
    });
    
    return Promise.race([promise, timeout]);
}

// Utilisation
promiseWithTimeout(
    fetch('https://api.example.com/data'),
    5000  // Timeout de 5 secondes
)
.then(response => response.json())
.catch(error => {
    if (error.message === 'Timeout') {
        console.error('La requête a pris trop de temps');
    } else {
        console.error('Autre erreur:', error);
    }
});

// === RETRY AVEC PROMESSES ===

async function retryPromise(fn, maxRetries = 3, delay = 1000) {
    for (let i = 0; i < maxRetries; i++) {
        try {
            return await fn();
        } catch (error) {
            if (i === maxRetries - 1) {
                throw error;  // Dernière tentative, propager erreur
            }
            console.log(`Tentative ${i + 1} échouée, nouvelle tentative...`);
            await new Promise(resolve => setTimeout(resolve, delay));
        }
    }
}

// Utilisation
retryPromise(
    () => fetch('https://api.unstable.com/data'),
    3,
    2000
)
.then(response => response.json())
.then(data => console.log('Données:', data))
.catch(error => console.error('Échec après 3 tentatives:', error));

// === SÉQUENTIEL VS PARALLÈLE ===

// Séquentiel (lent - attend chaque promesse)
async function sequentiel() {
    console.time('Séquentiel');
    
    const result1 = await fetch('url1');  // 1 sec
    const result2 = await fetch('url2');  // 1 sec
    const result3 = await fetch('url3');  // 1 sec
    
    console.timeEnd('Séquentiel');        // ~3 secondes
    return [result1, result2, result3];
}

// Parallèle (rapide - toutes en même temps)
async function parallele() {
    console.time('Parallèle');
    
    const [result1, result2, result3] = await Promise.all([
        fetch('url1'),
        fetch('url2'),
        fetch('url3')
    ]);
    
    console.timeEnd('Parallèle');         // ~1 seconde
    return [result1, result2, result3];
}

// === BATCH PROCESSING (traitement par lots) ===

async function processBatch(items, batchSize, processFn) {
    const results = [];
    
    for (let i = 0; i < items.length; i += batchSize) {
        const batch = items.slice(i, i + batchSize);
        const batchResults = await Promise.all(
            batch.map(item => processFn(item))
        );
        results.push(...batchResults);
        
        console.log(`Batch ${i / batchSize + 1} traité`);
    }
    
    return results;
}

// Utilisation: traiter 100 items par lots de 10
const items = Array.from({length: 100}, (_, i) => i);
processBatch(items, 10, async (item) => {
    // Traiter chaque item
    return item * 2;
})
.then(results => console.log('Tous traités:', results.length));

// === LIMITER CONCURRENCE ===

class PromiseQueue {
    constructor(maxConcurrent = 3) {
        this.maxConcurrent = maxConcurrent;
        this.running = 0;
        this.queue = [];
    }
    
    async add(promiseFn) {
        while (this.running >= this.maxConcurrent) {
            await new Promise(resolve => this.queue.push(resolve));
        }
        
        this.running++;
        
        try {
            return await promiseFn();
        } finally {
            this.running--;
            const resolve = this.queue.shift();
            if (resolve) resolve();
        }
    }
}

// Utilisation: max 3 requêtes simultanées
const queue = new PromiseQueue(3);

const urls = ['url1', 'url2', 'url3', 'url4', 'url5'];
const promises = urls.map(url => 
    queue.add(() => fetch(url))
);

Promise.all(promises)
    .then(results => console.log('Toutes terminées'));

// === PROMISE CHAIN AVEC GESTION D'ERREURS ===

fetch('https://api.example.com/users')
    .then(response => {
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}`);
        }
        return response.json();
    })
    .then(users => {
        console.log('Users:', users);
        return fetch(`https://api.example.com/posts/${users[0].id}`);
    })
    .then(response => response.json())
    .then(posts => {
        console.log('Posts:', posts);
    })
    .catch(error => {
        // Attrape toutes les erreurs de la chaîne
        console.error('Erreur dans la chaîne:', error);
    })
    .finally(() => {
        console.log('Nettoyage final');
    });

// === DEBOUNCE AVEC PROMESSE ===

function debouncePromise(fn, delay) {
    let timeoutId;
    let latestResolve;
    let latestReject;
    
    return function(...args) {
        clearTimeout(timeoutId);
        
        return new Promise((resolve, reject) => {
            latestResolve = resolve;
            latestReject = reject;
            
            timeoutId = setTimeout(async () => {
                try {
                    const result = await fn.apply(this, args);
                    latestResolve(result);
                } catch (error) {
                    latestReject(error);
                }
            }, delay);
        });
    };
}

// Utilisation
const debouncedSearch = debouncePromise(async (query) => {
    const response = await fetch(`/api/search?q=${query}`);
    return response.json();
}, 500);

// N'exécute que la dernière recherche après 500ms
input.addEventListener('input', async (e) => {
    try {
        const results = await debouncedSearch(e.target.value);
        displayResults(results);
    } catch (error) {
        console.error(error);
    }
});


[OK] ASYNC/AWAIT (ES2017)

// === BASES ASYNC/AWAIT ===

// Fonction asynchrone
async function maFonction() {
    return 'Hello';
}

// Retourne toujours une promesse
maFonction().then(result => console.log(result)); // 'Hello'

// === AWAIT - attendre une promesse ===

async function attendreResultat() {
    // await attend que la promesse se résolve
    const result = await maPromesse;
    console.log(result);
}

// Exemple pratique
function telecharger(url) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(`Données de ${url}`);
        }, 1000);
    });
}

async function chargerDonnees() {
    console.log('Début du téléchargement...');
    
    const data = await telecharger('https://api.example.com');
    console.log(data);
    
    console.log('Téléchargement terminé');
}

chargerDonnees();

// === GESTION DES ERREURS ===

// Avec try/catch (RECOMMANDÉ)
async function chargerAvecGestionErreur() {
    try {
        const data = await telecharger('https://api.example.com');
        console.log('Succès:', data);
        return data;
    } catch (error) {
        console.error('Erreur:', error);
        return null;
    } finally {
        console.log('Nettoyage...');
    }
}

// Sans try/catch (avec .catch())
async function chargerSansTry() {
    const data = await telecharger('url').catch(err => {
        console.error(err);
        return null;
    });
}

// === CHAÎNER DES APPELS ===

// Avec promesses (ancien)
function ancienneMethode() {
    return fetch('https://api.example.com/users')
        .then(response => response.json())
        .then(users => {
            return fetch(`https://api.example.com/posts/${users[0].id}`);
        })
        .then(response => response.json())
        .then(posts => {
            console.log(posts);
        });
}

// Avec async/await (MODERNE, plus lisible)
async function nouvelleMethode() {
    const usersResponse = await fetch('https://api.example.com/users');
    const users = await usersResponse.json();
    
    const postsResponse = await fetch(`https://api.example.com/posts/${users[0].id}`);
    const posts = await postsResponse.json();
    
    console.log(posts);
}

// === APPELS PARALLÈLES ===

// Séquentiel (lent - 6 secondes)
async function sequentiel() {
    const data1 = await telecharger('url1'); // 2 sec
    const data2 = await telecharger('url2'); // 2 sec
    const data3 = await telecharger('url3'); // 2 sec
    return [data1, data2, data3];
}

// Parallèle (rapide - 2 secondes)
async function parallele() {
    const [data1, data2, data3] = await Promise.all([
        telecharger('url1'),
        telecharger('url2'),
        telecharger('url3')
    ]);
    return [data1, data2, data3];
}

// === EXEMPLE COMPLET ===

async function recupererUtilisateur(id) {
    try {
        // 1. Récupérer utilisateur
        const userResponse = await fetch(`https://api.example.com/users/${id}`);
        if (!userResponse.ok) {
            throw new Error(`HTTP error! status: ${userResponse.status}`);
        }
        const user = await userResponse.json();
        
        // 2. Récupérer posts et friends en parallèle
        const [posts, friends] = await Promise.all([
            fetch(`https://api.example.com/users/${id}/posts`).then(r => r.json()),
            fetch(`https://api.example.com/users/${id}/friends`).then(r => r.json())
        ]);
        
        // 3. Retourner tout
        return {
            user,
            posts,
            friends
        };
    } catch (error) {
        console.error('Erreur lors de la récupération:', error);
        throw error; // Re-throw pour que l'appelant puisse gérer
    }
}

// Utilisation
recupererUtilisateur(1)
    .then(data => console.log(data))
    .catch(err => console.error(err));

// === BOUCLES AVEC AWAIT ===

// for...of (fonctionne avec await)
async function traiterListe(urls) {
    for (const url of urls) {
        const data = await telecharger(url); // Séquentiel
        console.log(data);
    }
}

// forEach (NE FONCTIONNE PAS avec await!)
async function mauvaisExemple(urls) {
    urls.forEach(async (url) => {
        const data = await telecharger(url); // N'attend pas!
        console.log(data);
    });
}

// map avec Promise.all (parallèle)
async function traiterParallele(urls) {
    const promises = urls.map(url => telecharger(url));
    const results = await Promise.all(promises);
    return results;
}

// === TOP-LEVEL AWAIT (ES2022) ===
// Seulement dans modules ES6

// Dans un module:
// const data = await fetch('url').then(r => r.json());
// console.log(data);


[OK] ASYNC/AWAIT - APPROFONDISSEMENT

// === PATTERNS ASYNC/AWAIT ===

// Pattern 1: Try-Catch par fonction
async function fetchUserData(userId) {
    try {
        const response = await fetch(`/api/users/${userId}`);
        
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}`);
        }
        
        const user = await response.json();
        return user;
    } catch (error) {
        console.error('Erreur fetchUserData:', error);
        throw error; // Re-throw pour que l'appelant puisse gérer
    }
}

// Pattern 2: Wrapper pour éviter try-catch partout
function asyncWrapper(fn) {
    return async function(...args) {
        try {
            return [null, await fn(...args)];
        } catch (error) {
            return [error, null];
        }
    };
}

// Utilisation
const safeFetch = asyncWrapper(fetchUserData);
const [error, user] = await safeFetch(123);

if (error) {
    console.error('Erreur:', error);
} else {
    console.log('User:', user);
}

// === ASYNC/AWAIT AVEC PROMISE.ALL ===

async function fetchAllData() {
    try {
        // Parallèle (RAPIDE)
        const [users, posts, comments] = await Promise.all([
            fetch('/api/users').then(r => r.json()),
            fetch('/api/posts').then(r => r.json()),
            fetch('/api/comments').then(r => r.json())
        ]);
        
        return {users, posts, comments};
    } catch (error) {
        console.error('Une requête a échoué:', error);
        throw error;
    }
}

// === ASYNC/AWAIT AVEC PROMISE.ALLSETTLED ===

async function fetchMultipleWithFallback() {
    const results = await Promise.allSettled([
        fetch('/api/primary').then(r => r.json()),
        fetch('/api/secondary').then(r => r.json()),
        fetch('/api/tertiary').then(r => r.json())
    ]);
    
    const successfulData = results
        .filter(result => result.status === 'fulfilled')
        .map(result => result.value);
    
    const errors = results
        .filter(result => result.status === 'rejected')
        .map(result => result.reason);
    
    if (errors.length > 0) {
        console.warn('Certaines requêtes ont échoué:', errors);
    }
    
    return successfulData;
}

// === BOUCLES AVEC ASYNC/AWAIT ===

// FOR OF (séquentiel - attend chaque itération)
async function processSequential(items) {
    for (const item of items) {
        const result = await processItem(item);
        console.log('Traité:', result);
    }
}

// MAP + PROMISE.ALL (parallèle - toutes en même temps)
async function processParallel(items) {
    const promises = items.map(item => processItem(item));
    const results = await Promise.all(promises);
    return results;
}

// REDUCE (séquentiel avec accumulation)
async function processWithReduce(items) {
    return await items.reduce(async (accPromise, item) => {
        const acc = await accPromise;
        const result = await processItem(item);
        return [...acc, result];
    }, Promise.resolve([]));
}

// [ATTENTION] FOREACH NE FONCTIONNE PAS AVEC AWAIT
async function wrongUsage(items) {
    items.forEach(async (item) => {
        await processItem(item); // N'attend PAS!
    });
    console.log('Terminé'); // S'affiche immédiatement!
}

// === FOR AWAIT...OF (async iterables) ===

// Async generator
async function* fetchPages() {
    let page = 1;
    while (page <= 3) {
        const response = await fetch(`/api/items?page=${page}`);
        const data = await response.json();
        yield data;
        page++;
    }
}

// Consommer avec for await...of
async function getAllItems() {
    const allItems = [];
    
    for await (const pageData of fetchPages()) {
        allItems.push(...pageData.items);
        console.log(`Page récupérée, total: ${allItems.length}`);
    }
    
    return allItems;
}

// === ASYNC IIFE (Immediately Invoked Function Expression) ===

// Exécuter code async au top-level (avant top-level await)
(async () => {
    const data = await fetch('/api/data').then(r => r.json());
    console.log(data);
})();

// Avec gestion d'erreur
(async () => {
    try {
        const data = await fetch('/api/data').then(r => r.json());
        console.log(data);
    } catch (error) {
        console.error('Erreur:', error);
    }
})();

// === ASYNC DANS EVENT HANDLERS ===

// Correct
button.addEventListener('click', async (event) => {
    try {
        const response = await fetch('/api/action');
        const result = await response.json();
        console.log('Résultat:', result);
    } catch (error) {
        console.error('Erreur:', error);
    }
});

// === ASYNC DANS CLASSES ===

class DataService {
    constructor(apiUrl) {
        this.apiUrl = apiUrl;
        this.cache = new Map();
    }
    
    async fetchData(id) {
        // Vérifier cache
        if (this.cache.has(id)) {
            console.log('Cache hit');
            return this.cache.get(id);
        }
        
        // Fetch depuis API
        const response = await fetch(`${this.apiUrl}/data/${id}`);
        
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}`);
        }
        
        const data = await response.json();
        
        // Mettre en cache
        this.cache.set(id, data);
        
        return data;
    }
    
    async fetchMultiple(ids) {
        return await Promise.all(
            ids.map(id => this.fetchData(id))
        );
    }
    
    clearCache() {
        this.cache.clear();
    }
}

// Utilisation
const service = new DataService('https://api.example.com');

try {
    const data = await service.fetchData(123);
    console.log(data);
} catch (error) {
    console.error('Erreur:', error);
}

// === AWAIT AVEC DESTRUCTURING ===

// Destructurer résultat directement
const {name, email} = await fetch('/api/user/1').then(r => r.json());

// Avec Promise.all
const [users, posts] = await Promise.all([
    fetch('/api/users').then(r => r.json()),
    fetch('/api/posts').then(r => r.json())
]);

// === GÉRER TIMEOUTS AVEC ASYNC/AWAIT ===

async function fetchWithTimeout(url, timeout = 5000) {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), timeout);
    
    try {
        const response = await fetch(url, {
            signal: controller.signal
        });
        clearTimeout(timeoutId);
        return await response.json();
    } catch (error) {
        if (error.name === 'AbortError') {
            throw new Error('Request timeout');
        }
        throw error;
    }
}

// === SEQUENTIAL PIPELINE ===

async function pipeline(...fns) {
    return async (initialValue) => {
        let value = initialValue;
        for (const fn of fns) {
            value = await fn(value);
        }
        return value;
    };
}

// Utilisation
const process = pipeline(
    async (data) => await validateData(data),
    async (data) => await enrichData(data),
    async (data) => await saveData(data)
);

const result = await process(rawData);


[OK] GÉNÉRATEURS & ASYNC GENERATORS

// === GÉNÉRATEURS BASIQUES ===

function* simpleGenerator() {
    yield 1;
    yield 2;
    yield 3;
}

const gen = simpleGenerator();
console.log(gen.next());        // {value: 1, done: false}
console.log(gen.next());        // {value: 2, done: false}
console.log(gen.next());        // {value: 3, done: false}
console.log(gen.next());        // {value: undefined, done: true}

// Avec boucle
for (const value of simpleGenerator()) {
    console.log(value);         // 1, 2, 3
}

// === GÉNÉRATEUR AVEC PARAMÈTRES ===

function* counter(start = 0, end = 10) {
    for (let i = start; i <= end; i++) {
        yield i;
    }
}

console.log([...counter(5, 8)]);    // [5, 6, 7, 8]

// === GÉNÉRATEUR INFINI ===

function* fibonacci() {
    let [a, b] = [0, 1];
    while (true) {
        yield a;
        [a, b] = [b, a + b];
    }
}

// Prendre seulement 10 premiers
const fib = fibonacci();
const first10 = [];
for (let i = 0; i < 10; i++) {
    first10.push(fib.next().value);
}
console.log(first10);           // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

// === YIELD* (délégation) ===

function* gen1() {
    yield 1;
    yield 2;
}

function* gen2() {
    yield 'a';
    yield* gen1();              // Délègue à gen1
    yield 'b';
}

console.log([...gen2()]);       // ['a', 1, 2, 'b']

// === ENVOYER DES VALEURS AU GÉNÉRATEUR ===

function* dialogue() {
    const name = yield 'Quel est votre nom?';
    const age = yield `Bonjour ${name}, quel âge avez-vous?`;
    yield `${name}, ${age} ans. Enchanté!`;
}

const conv = dialogue();
console.log(conv.next().value);         // 'Quel est votre nom?'
console.log(conv.next('Alice').value);  // 'Bonjour Alice, quel âge avez-vous?'
console.log(conv.next(25).value);       // 'Alice, 25 ans. Enchanté!'

// === RETURN DANS GÉNÉRATEUR ===

function* genWithReturn() {
    yield 1;
    yield 2;
    return 'terminé';
    yield 3;                    // Jamais atteint
}

const g = genWithReturn();
console.log(g.next());          // {value: 1, done: false}
console.log(g.next());          // {value: 2, done: false}
console.log(g.next());          // {value: 'terminé', done: true}

// [ATTENTION] return value n'apparaît pas avec for...of
console.log([...genWithReturn()]);  // [1, 2] (pas 'terminé')

// === THROW DANS GÉNÉRATEUR ===

function* errorGen() {
    try {
        yield 1;
        yield 2;
    } catch (error) {
        console.log('Erreur capturée:', error);
    }
    yield 3;
}

const eg = errorGen();
console.log(eg.next().value);       // 1
eg.throw(new Error('Oups!'));       // Erreur capturée: Error: Oups!
console.log(eg.next().value);       // 3

// === GÉNÉRATEURS ASYNCHRONES ===

async function* asyncGenerator() {
    yield await Promise.resolve(1);
    yield await Promise.resolve(2);
    yield await Promise.resolve(3);
}

// Consommer avec for await...of
(async () => {
    for await (const value of asyncGenerator()) {
        console.log(value);     // 1, 2, 3
    }
})();

// === PAGINATION AVEC ASYNC GENERATOR ===

async function* fetchAllPages(baseUrl) {
    let page = 1;
    let hasMore = true;
    
    while (hasMore) {
        const response = await fetch(`${baseUrl}?page=${page}`);
        const data = await response.json();
        
        yield data.items;
        
        hasMore = data.hasNextPage;
        page++;
    }
}

// Utilisation
(async () => {
    for await (const items of fetchAllPages('/api/items')) {
        console.log('Batch reçu:', items.length);
        processItems(items);
    }
})();

// === GÉNÉRATEUR POUR LAZY EVALUATION ===

function* range(start, end, step = 1) {
    for (let i = start; i < end; i += step) {
        yield i;
    }
}

function* map(iterable, fn) {
    for (const item of iterable) {
        yield fn(item);
    }
}

function* filter(iterable, predicate) {
    for (const item of iterable) {
        if (predicate(item)) {
            yield item;
        }
    }
}

function* take(iterable, n) {
    let count = 0;
    for (const item of iterable) {
        if (count >= n) break;
        yield item;
        count++;
    }
}

// Composition lazy (n'évalue que ce qui est nécessaire)
const result = take(
    filter(
        map(range(0, 1000000), x => x * 2),
        x => x % 3 === 0
    ),
    5
);

console.log([...result]);       // [0, 6, 12, 18, 24]


[OK] ITERATORS & ITERABLES

// === CRÉER UN ITÉRABLE PERSONNALISÉ ===

const myIterable = {
    data: [1, 2, 3, 4, 5],
    
    [Symbol.iterator]() {
        let index = 0;
        const data = this.data;
        
        return {
            next() {
                if (index < data.length) {
                    return {
                        value: data[index++],
                        done: false
                    };
                } else {
                    return {done: true};
                }
            }
        };
    }
};

// Utilisation
for (const value of myIterable) {
    console.log(value);         // 1, 2, 3, 4, 5
}

// Spread operator
console.log([...myIterable]);   // [1, 2, 3, 4, 5]

// === RANGE ITÉRABLE ===

class Range {
    constructor(start, end) {
        this.start = start;
        this.end = end;
    }
    
    [Symbol.iterator]() {
        let current = this.start;
        const end = this.end;
        
        return {
            next() {
                if (current <= end) {
                    return {value: current++, done: false};
                }
                return {done: true};
            }
        };
    }
}

const range = new Range(1, 5);
console.log([...range]);        // [1, 2, 3, 4, 5]

// === ITÉRABLE INFINI ===

const infiniteSequence = {
    [Symbol.iterator]() {
        let n = 0;
        return {
            next() {
                return {value: n++, done: false};
            }
        };
    }
};

// Prendre seulement ce qu'on veut
const iterator = infiniteSequence[Symbol.iterator]();
console.log(iterator.next().value);     // 0
console.log(iterator.next().value);     // 1
console.log(iterator.next().value);     // 2

// === ASYNC ITERATOR ===

const asyncIterable = {
    async *[Symbol.asyncIterator]() {
        for (let i = 1; i <= 3; i++) {
            await new Promise(resolve => setTimeout(resolve, 1000));
            yield i;
        }
    }
};

// Consommer
(async () => {
    for await (const value of asyncIterable) {
        console.log(value);     // 1, 2, 3 (avec délai)
    }
})();


[OK] COMPOSITION DE FONCTIONS

// === COMPOSE (droite à gauche) ===

const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);

// Utilisation
const addOne = x => x + 1;
const double = x => x * 2;
const square = x => x * x;

const compute = compose(square, double, addOne);
console.log(compute(5));        // ((5 + 1) * 2)² = 144

// === PIPE (gauche à droite) ===

const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);

const compute2 = pipe(addOne, double, square);
console.log(compute2(5));       // ((5 + 1) * 2)² = 144

// === ASYNC COMPOSE ===

const composeAsync = (...fns) => x => 
    fns.reduceRight((acc, fn) => acc.then(fn), Promise.resolve(x));

const fetchUser = async id => {
    const response = await fetch(`/api/users/${id}`);
    return response.json();
};

const enrichUser = async user => ({
    ...user,
    fullName: `${user.firstName} ${user.lastName}`
});

const formatUser = async user => ({
    id: user.id,
    name: user.fullName,
    email: user.email
});

const getUserData = composeAsync(formatUser, enrichUser, fetchUser);

getUserData(123).then(console.log);

// === CURRY ===

// Transformer fonction multi-arguments en série de fonctions
function curry(fn) {
    return function curried(...args) {
        if (args.length >= fn.length) {
            return fn.apply(this, args);
        } else {
            return function(...args2) {
                return curried.apply(this, args.concat(args2));
            };
        }
    };
}

// Utilisation
const sum3 = (a, b, c) => a + b + c;
const curriedSum = curry(sum3);

console.log(curriedSum(1)(2)(3));       // 6
console.log(curriedSum(1, 2)(3));       // 6
console.log(curriedSum(1)(2, 3));       // 6

// === PARTIAL APPLICATION ===

const partial = (fn, ...presetArgs) => {
    return (...laterArgs) => {
        return fn(...presetArgs, ...laterArgs);
    };
};

// Utilisation
const multiply = (a, b, c) => a * b * c;
const multiplyBy2 = partial(multiply, 2);
console.log(multiplyBy2(3, 4));         // 2 * 3 * 4 = 24


[OK] PROGRAMMATION FONCTIONNELLE

// === PURE FUNCTIONS ===

// [OK] Pure (pas d'effets de bord)
function add(a, b) {
    return a + b;
}

// [X] Impure (modifie variable externe)
let total = 0;
function addToTotal(x) {
    total += x;             // Effet de bord!
    return total;
}

// [X] Impure (résultat non déterministe)
function getRandomValue() {
    return Math.random();   // Pas déterministe
}

// === IMMUTABILITÉ ===

// [X] Mutable (modifie l'original)
function addItemMutable(arr, item) {
    arr.push(item);
    return arr;
}

// [OK] Immutable (retourne nouveau tableau)
function addItemImmutable(arr, item) {
    return [...arr, item];
}

// Objet immutable
const updateUser = (user, updates) => ({
    ...user,
    ...updates
});

// === MAP, FILTER, REDUCE ===

const numbers = [1, 2, 3, 4, 5];

// Map: transformer
const doubled = numbers.map(x => x * 2);

// Filter: filtrer
const evens = numbers.filter(x => x % 2 === 0);

// Reduce: réduire à une valeur
const sum = numbers.reduce((acc, x) => acc + x, 0);

// Chaîner
const result = numbers
    .filter(x => x > 2)
    .map(x => x * 2)
    .reduce((acc, x) => acc + x, 0);

// === HIGHER-ORDER FUNCTIONS ===

// Fonction qui retourne une fonction
const multiplier = factor => number => number * factor;

const double2 = multiplier(2);
const triple = multiplier(3);

console.log(double2(5));        // 10
console.log(triple(5));         // 15


[OK] FETCH API - REQUÊTES HTTP

// === GET - récupérer des données ===

// Basique
fetch('https://api.example.com/users')
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.error('Erreur:', error));

// Avec async/await (RECOMMANDÉ)
async function getUsers() {
    try {
        const response = await fetch('https://api.example.com/users');
        const data = await response.json();
        console.log(data);
    } catch (error) {
        console.error('Erreur:', error);
    }
}

// === VÉRIFIER LE STATUS ===

async function fetchWithErrorHandling(url) {
    const response = await fetch(url);
    
    // fetch ne rejette PAS sur erreur HTTP (404, 500, etc.)
    if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    return await response.json();
}

// === POST - envoyer des données ===

// JSON
async function createUser(userData) {
    const response = await fetch('https://api.example.com/users', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(userData)
    });
    
    if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    return await response.json();
}

// Utilisation
createUser({
    name: 'Alice',
    email: 'alice@example.com',
    age: 25
})
.then(data => console.log('Utilisateur créé:', data))
.catch(error => console.error('Erreur:', error));

// === PUT - mettre à jour ===

async function updateUser(id, userData) {
    const response = await fetch(`https://api.example.com/users/${id}`, {
        method: 'PUT',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(userData)
    });
    
    return await response.json();
}

// === PATCH - mise à jour partielle ===

async function patchUser(id, updates) {
    const response = await fetch(`https://api.example.com/users/${id}`, {
        method: 'PATCH',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(updates)
    });
    
    return await response.json();
}

// === DELETE - supprimer ===

async function deleteUser(id) {
    const response = await fetch(`https://api.example.com/users/${id}`, {
        method: 'DELETE'
    });
    
    if (!response.ok) {
        throw new Error('Suppression échouée');
    }
    
    return response.status === 204 ? null : await response.json();
}

// === HEADERS PERSONNALISÉS ===

async function fetchWithAuth(url) {
    const response = await fetch(url, {
        headers: {
            'Authorization': 'Bearer TOKEN_HERE',
            'Content-Type': 'application/json',
            'Accept': 'application/json',
            'X-Custom-Header': 'value'
        }
    });
    
    return await response.json();
}

// === FORMDATA - envoyer un formulaire ===

async function submitForm(formElement) {
    const formData = new FormData(formElement);
    
    const response = await fetch('/api/submit', {
        method: 'POST',
        body: formData  // Pas de Content-Type (auto)
    });
    
    return await response.json();
}

// Ajouter des données manuellement
const formData = new FormData();
formData.append('name', 'Alice');
formData.append('email', 'alice@example.com');
formData.append('file', fileInput.files[0]);

// === UPLOAD DE FICHIER ===

async function uploadFile(file) {
    const formData = new FormData();
    formData.append('file', file);
    formData.append('description', 'Mon fichier');
    
    const response = await fetch('/api/upload', {
        method: 'POST',
        body: formData
    });
    
    return await response.json();
}

// Avec progression
async function uploadWithProgress(file) {
    const xhr = new XMLHttpRequest();
    
    return new Promise((resolve, reject) => {
        xhr.upload.addEventListener('progress', (e) => {
            if (e.lengthComputable) {
                const percent = (e.loaded / e.total) * 100;
                console.log(`Progression: ${percent}%`);
            }
        });
        
        xhr.addEventListener('load', () => {
            resolve(JSON.parse(xhr.response));
        });
        
        xhr.addEventListener('error', () => {
            reject(new Error('Upload failed'));
        });
        
        xhr.open('POST', '/api/upload');
        
        const formData = new FormData();
        formData.append('file', file);
        
        xhr.send(formData);
    });
}

// === TIMEOUT ===

async function fetchWithTimeout(url, timeout = 5000) {
    const controller = new AbortController();
    const id = setTimeout(() => controller.abort(), timeout);
    
    try {
        const response = await fetch(url, {
            signal: controller.signal
        });
        clearTimeout(id);
        return await response.json();
    } catch (error) {
        if (error.name === 'AbortError') {
            throw new Error('Request timeout');
        }
        throw error;
    }
}

// === ANNULER UNE REQUÊTE ===

const controller = new AbortController();

fetch('https://api.example.com/data', {
    signal: controller.signal
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => {
    if (error.name === 'AbortError') {
        console.log('Requête annulée');
    }
});

// Annuler la requête
controller.abort();

// === TYPES DE RÉPONSE ===

// JSON
const json = await response.json();

// Texte
const text = await response.text();

// Blob (fichiers binaires)
const blob = await response.blob();

// ArrayBuffer
const buffer = await response.arrayBuffer();

// FormData
const formData = await response.formData();

// === EXEMPLE COMPLET API ===

class UserAPI {
    constructor(baseUrl) {
        this.baseUrl = baseUrl;
    }
    
    async request(endpoint, options = {}) {
        const url = `${this.baseUrl}${endpoint}`;
        const config = {
            headers: {
                'Content-Type': 'application/json',
                ...options.headers
            },
            ...options
        };
        
        try {
            const response = await fetch(url, config);
            
            if (!response.ok) {
                throw new Error(`HTTP ${response.status}: ${response.statusText}`);
            }
            
            return await response.json();
        } catch (error) {
            console.error('API Error:', error);
            throw error;
        }
    }
    
    async getAll() {
        return this.request('/users');
    }
    
    async getById(id) {
        return this.request(`/users/${id}`);
    }
    
    async create(userData) {
        return this.request('/users', {
            method: 'POST',
            body: JSON.stringify(userData)
        });
    }
    
    async update(id, userData) {
        return this.request(`/users/${id}`, {
            method: 'PUT',
            body: JSON.stringify(userData)
        });
    }
    
    async delete(id) {
        return this.request(`/users/${id}`, {
            method: 'DELETE'
        });
    }
}

// Utilisation
const api = new UserAPI('https://api.example.com');

async function main() {
    try {
        const users = await api.getAll();
        console.log('Users:', users);
        
        const newUser = await api.create({
            name: 'Alice',
            email: 'alice@example.com'
        });
        console.log('Created:', newUser);
    } catch (error) {
        console.error('Error:', error);
    }
}


[OK] CLASSES (ES6)

// === CRÉER UNE CLASSE ===

class Person {
    // Constructeur (appelé lors de new Person())
    constructor(nom, age) {
        this.nom = nom;
        this.age = age;
    }
    
    // Méthode
    saluer() {
        console.log(`Bonjour, je suis ${this.nom}`);
    }
    
    // Méthode avec paramètre
    anniversaire() {
        this.age++;
        console.log(`J'ai maintenant ${this.age} ans`);
    }
}

// Créer une instance
const alice = new Person('Alice', 25);
alice.saluer();                 // Bonjour, je suis Alice
alice.anniversaire();           // J'ai maintenant 26 ans

// === GETTERS & SETTERS ===

class Rectangle {
    constructor(largeur, hauteur) {
        this.largeur = largeur;
        this.hauteur = hauteur;
    }
    
    // Getter (appelé comme propriété)
    get aire() {
        return this.largeur * this.hauteur;
    }
    
    // Setter
    set dimensions({largeur, hauteur}) {
        this.largeur = largeur;
        this.hauteur = hauteur;
    }
}

const rect = new Rectangle(10, 5);
console.log(rect.aire);             // 50 (pas de parenthèses!)
rect.dimensions = {largeur: 20, hauteur: 10};
console.log(rect.aire);             // 200

// === MÉTHODES STATIQUES ===

class MathUtils {
    // Méthode statique (appelée sur la classe, pas l'instance)
    static addition(a, b) {
        return a + b;
    }
    
    static PI = 3.14159;
}

// Appel sur la classe
console.log(MathUtils.addition(5, 3));  // 8
console.log(MathUtils.PI);              // 3.14159

// Pas sur l'instance
// const utils = new MathUtils();
// utils.addition(5, 3);                // [X] Erreur!

// === HÉRITAGE (EXTENDS) ===

class Animal {
    constructor(nom) {
        this.nom = nom;
    }
    
    faireBruit() {
        console.log(`${this.nom} fait un bruit`);
    }
}

class Chien extends Animal {
    constructor(nom, race) {
        super(nom);             // Appeler constructeur parent
        this.race = race;
    }
    
    // Surcharger méthode
    faireBruit() {
        console.log(`${this.nom} aboie: Woof!`);
    }
    
    // Nouvelle méthode
    sePresenter() {
        console.log(`Je suis ${this.nom}, un ${this.race}`);
    }
}

const rex = new Chien('Rex', 'Berger Allemand');
rex.faireBruit();               // Rex aboie: Woof!
rex.sePresenter();              // Je suis Rex, un Berger Allemand

// Vérifications
console.log(rex instanceof Chien);      // true
console.log(rex instanceof Animal);     // true

// === PROPRIÉTÉS PRIVÉES (ES2022) ===

class BankAccount {
    // Propriété privée (commence par #)
    #solde = 0;
    
    constructor(soldeInitial) {
        this.#solde = soldeInitial;
    }
    
    // Méthode privée
    #validerMontant(montant) {
        return montant > 0;
    }
    
    deposer(montant) {
        if (this.#validerMontant(montant)) {
            this.#solde += montant;
        }
    }
    
    getSolde() {
        return this.#solde;
    }
}

const compte = new BankAccount(1000);
compte.deposer(500);
console.log(compte.getSolde());     // 1500
// console.log(compte.#solde);      // [X] Erreur! Privé

// === PROPRIÉTÉS STATIQUES PRIVÉES ===

class Config {
    static #apiKey = 'secret123';
    
    static getApiKey() {
        return this.#apiKey;
    }
}

// === CHAMPS PUBLICS (ES2022) ===

class User {
    // Champs publics (pas besoin de this. dans constructor)
    nom = 'Anonyme';
    age = 0;
    actif = true;
    
    constructor(nom, age) {
        this.nom = nom;
        this.age = age;
    }
}

// === EXEMPLE COMPLET ===

class TodoList {
    #todos = [];
    #nextId = 1;
    
    constructor(titre) {
        this.titre = titre;
    }
    
    ajouter(texte) {
        const todo = {
            id: this.#nextId++,
            texte,
            termine: false,
            dateCreation: new Date()
        };
        this.#todos.push(todo);
        return todo;
    }
    
    supprimer(id) {
        const index = this.#todos.findIndex(t => t.id === id);
        if (index !== -1) {
            this.#todos.splice(index, 1);
            return true;
        }
        return false;
    }
    
    marquerTermine(id) {
        const todo = this.#todos.find(t => t.id === id);
        if (todo) {
            todo.termine = true;
        }
    }
    
    getTous() {
        return [...this.#todos]; // Copie pour protéger données privées
    }
    
    get nombreTotal() {
        return this.#todos.length;
    }
    
    get nombreTermines() {
        return this.#todos.filter(t => t.termine).length;
    }
    
    afficher() {
        console.log(`=== ${this.titre} ===`);
        this.#todos.forEach(todo => {
            const statut = todo.termine ? '[OK]' : ' ';
            console.log(`[${statut}] ${todo.id}. ${todo.texte}`);
        });
    }
}

// Utilisation
const liste = new TodoList('Ma Liste');
liste.ajouter('Apprendre JavaScript');
liste.ajouter('Faire les courses');
liste.ajouter('Faire du sport');
liste.marquerTermine(1);
liste.afficher();
console.log(`${liste.nombreTermines}/${liste.nombreTotal} terminés`);


[OK] MODULES (ES6)

// === EXPORTER DEPUIS UN MODULE ===

// fichier: utils.js

// Export nommé
export const PI = 3.14159;

export function addition(a, b) {
    return a + b;
}

export function soustraction(a, b) {
    return a - b;
}

// Ou grouper:
const PI2 = 3.14159;
function addition2(a, b) { return a + b; }
function soustraction2(a, b) { return a - b; }

export { PI2, addition2, soustraction2 };

// Export par défaut (un seul par fichier)
export default function multiplication(a, b) {
    return a * b;
}

// Ou:
function multiplication2(a, b) {
    return a * b;
}
export default multiplication2;

// === IMPORTER DANS UN AUTRE MODULE ===

// fichier: main.js

// Import nommés
import { PI, addition, soustraction } from './utils.js';

console.log(PI);                    // 3.14159
console.log(addition(5, 3));        // 8

// Import avec alias
import { addition as add, soustraction as sub } from './utils.js';

console.log(add(5, 3));             // 8

// Import tout
import * as Utils from './utils.js';

console.log(Utils.PI);              // 3.14159
console.log(Utils.addition(5, 3));  // 8

// Import par défaut
import multiplication from './utils.js';

console.log(multiplication(5, 3));  // 15

// Import par défaut avec alias
import mult from './utils.js';

// Combiner default et nommés
import multiplication, { PI, addition } from './utils.js';

// === STRUCTURE DE MODULES ===

// fichier: user.js
export class User {
    constructor(nom) {
        this.nom = nom;
    }
}

export const DEFAULT_ROLE = 'user';

export function createUser(nom) {
    return new User(nom);
}

// fichier: api.js
export async function fetchUsers() {
    const response = await fetch('/api/users');
    return response.json();
}

export async function createUser(data) {
    const response = await fetch('/api/users', {
        method: 'POST',
        body: JSON.stringify(data)
    });
    return response.json();
}

// fichier: main.js
import { User, DEFAULT_ROLE } from './user.js';
import * as API from './api.js';

async function main() {
    const users = await API.fetchUsers();
    console.log(users);
}

// === UTILISER MODULES DANS HTML ===

// index.html
<!DOCTYPE html>
<html>
<head>
    <title>Modules</title>
</head>
<body>
    <!-- type="module" REQUIS -->
    <script type="module" src="main.js"></script>
    
    <!-- Ou inline -->
    <script type="module">
        import { addition } from './utils.js';
        console.log(addition(5, 3));
    </script>
</body>
</html>

// === IMPORT DYNAMIQUE ===

// Import à la demande (retourne une promesse)
async function chargerModule() {
    const module = await import('./utils.js');
    console.log(module.PI);
    console.log(module.addition(5, 3));
}

// Avec then
import('./utils.js')
    .then(module => {
        console.log(module.PI);
    });

// Conditionnel
if (condition) {
    const module = await import('./feature.js');
    module.init();
}

// === RE-EXPORT ===

// fichier: index.js (point d'entrée centralisé)

// Re-exporter tout
export * from './user.js';
export * from './api.js';

// Re-exporter spécifiques
export { User, DEFAULT_ROLE } from './user.js';
export { fetchUsers } from './api.js';

// Re-exporter default
export { default as Utils } from './utils.js';

// === NOTES IMPORTANTES ===

// 1. Extensions de fichier requises (.js)
// 2. Chemins relatifs requis (./ ou ../)
// 3. Modules sont en mode strict automatiquement
// 4. Modules sont différés (defer) automatiquement
// 5. Même module importé plusieurs fois = une seule exécution
// 6. Portée: variables sont privées au module


[OK] DESTRUCTURING AVANCÉ

// === DESTRUCTURING AVEC VALEURS PAR DÉFAUT ===

const user = {nom: 'Alice', age: 25};

const {nom, age, ville = 'Paris'} = user;
console.log(ville);                 // 'Paris' (valeur par défaut)

// === DESTRUCTURING IMBRIQUÉ ===

const data = {
    utilisateur: {
        info: {
            nom: 'Alice',
            age: 25
        },
        preferences: {
            theme: 'dark'
        }
    }
};

// Extraire valeurs imbriquées
const {
    utilisateur: {
        info: { nom, age },
        preferences: { theme }
    }
} = data;

console.log(nom, age, theme);       // Alice 25 dark

// === DESTRUCTURING DANS PARAMÈTRES ===

// Avec objets
function afficherUser({nom, age, ville = 'Paris'}) {
    console.log(`${nom}, ${age} ans, ${ville}`);
}

afficherUser({nom: 'Alice', age: 25});
// Alice, 25 ans, Paris

// Avec arrays
function sommeMultiplication([a, b]) {
    return {
        somme: a + b,
        produit: a * b
    };
}

console.log(sommeMultiplication([3, 4]));
// {somme: 7, produit: 12}

// === REST AVEC DESTRUCTURING ===

const {a, b, ...rest} = {a: 1, b: 2, c: 3, d: 4};
console.log(rest);                  // {c: 3, d: 4}

const [first, second, ...others] = [1, 2, 3, 4, 5];
console.log(others);                // [3, 4, 5]

// === SWAP (ÉCHANGER) VARIABLES ===

let x = 1;
let y = 2;
[x, y] = [y, x];
console.log(x, y);                  // 2 1

// === IGNORER DES VALEURS ===

const [, , third] = [1, 2, 3, 4];
console.log(third);                 // 3

// === DESTRUCTURING DE RETOUR DE FONCTION ===

function getCoordinates() {
    return [10, 20];
}

const [x2, y2] = getCoordinates();

function getUserInfo() {
    return {
        nom: 'Alice',
        age: 25,
        email: 'alice@example.com'
    };
}

const {nom: userName, email} = getUserInfo();


[OK] MAP & SET

// === MAP (dictionnaire clé-valeur) ===

// Créer Map
const map = new Map();

// Ajouter éléments
map.set('nom', 'Alice');
map.set('age', 25);
map.set('actif', true);

// Clés de tout type (pas juste strings!)
map.set(1, 'nombre');
map.set({id: 1}, 'objet');
map.set(function(){}, 'fonction');

// Obtenir valeur
console.log(map.get('nom'));        // 'Alice'
console.log(map.get('ville'));      // undefined

// Vérifier existence
console.log(map.has('nom'));        // true
console.log(map.has('ville'));      // false

// Supprimer
map.delete('age');
console.log(map.has('age'));        // false

// Taille
console.log(map.size);              // 2

// Vider
map.clear();

// Créer avec valeurs initiales
const map2 = new Map([
    ['nom', 'Alice'],
    ['age', 25],
    ['ville', 'Paris']
]);

// === ITÉRER SUR MAP ===

const userMap = new Map([
    ['nom', 'Alice'],
    ['age', 25],
    ['ville', 'Paris']
]);

// Itérer sur entries
for (const [key, value] of userMap) {
    console.log(`${key}: ${value}`);
}

// forEach
userMap.forEach((value, key) => {
    console.log(`${key}: ${value}`);
});

// Obtenir clés
console.log([...userMap.keys()]);   // ['nom', 'age', 'ville']

// Obtenir valeurs
console.log([...userMap.values()]); // ['Alice', 25, 'Paris']

// Obtenir entries
console.log([...userMap.entries()]);
// [['nom', 'Alice'], ['age', 25], ['ville', 'Paris']]

// === CONVERTIR MAP <-> OBJET ===

// Map vers Objet
const obj = Object.fromEntries(userMap);
console.log(obj);                   // {nom: 'Alice', age: 25, ville: 'Paris'}

// Objet vers Map
const map3 = new Map(Object.entries(obj));

// === SET (collection de valeurs uniques) ===

// Créer Set
const set = new Set();

// Ajouter éléments
set.add(1);
set.add(2);
set.add(3);
set.add(2);                         // Ignoré (déjà présent)

console.log(set.size);              // 3

// Créer avec valeurs initiales
const set2 = new Set([1, 2, 3, 4, 4, 5]);
console.log(set2);                  // Set {1, 2, 3, 4, 5}

// Vérifier existence
console.log(set.has(2));            // true
console.log(set.has(10));           // false

// Supprimer
set.delete(2);
console.log(set.has(2));            // false

// Vider
set.clear();

// === ITÉRER SUR SET ===

const nombres = new Set([1, 2, 3, 4, 5]);

// for...of
for (const num of nombres) {
    console.log(num);
}

// forEach
nombres.forEach(num => console.log(num));

// Convertir en array
const array = [...nombres];
const array2 = Array.from(nombres);

// === OPÉRATIONS SUR SETS ===

const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);

// Union
const union = new Set([...setA, ...setB]);
console.log(union);                 // Set {1, 2, 3, 4, 5, 6}

// Intersection
const intersection = new Set(
    [...setA].filter(x => setB.has(x))
);
console.log(intersection);          // Set {3, 4}

// Différence
const difference = new Set(
    [...setA].filter(x => !setB.has(x))
);
console.log(difference);            // Set {1, 2}

// === UTILISATION PRATIQUE ===

// Supprimer doublons d'un array
const arrayAvecDoublons = [1, 2, 2, 3, 4, 4, 5];
const arrayUnique = [...new Set(arrayAvecDoublons)];
console.log(arrayUnique);           // [1, 2, 3, 4, 5]

// Compter valeurs uniques
const values = [1, 2, 2, 3, 3, 3, 4];
const uniqueCount = new Set(values).size;
console.log(uniqueCount);           // 4

// === WEAKMAP & WEAKSET ===

// WeakMap: clés DOIVENT être des objets, garbage collected
const weakMap = new WeakMap();
let obj = {id: 1};
weakMap.set(obj, 'valeur');
console.log(weakMap.get(obj));      // 'valeur'
obj = null;                         // Objet peut être garbage collected

// WeakSet: similaire
const weakSet = new WeakSet();
let obj2 = {id: 1};
weakSet.add(obj2);
console.log(weakSet.has(obj2));     // true


[OK] GESTION DES ERREURS

// === TRY-CATCH-FINALLY ===

try {
    // Code qui peut générer une erreur
    const result = riskyOperation();
    console.log(result);
} catch (error) {
    // Gestion de l'erreur
    console.error('Erreur:', error.message);
} finally {
    // Toujours exécuté (optionnel)
    console.log('Nettoyage...');
}

// === TYPES D'ERREURS ===

// Error générique
throw new Error('Quelque chose a mal tourné');

// TypeError
throw new TypeError('Type invalide');

// ReferenceError
throw new ReferenceError('Variable non définie');

// SyntaxError
throw new SyntaxError('Syntaxe invalide');

// RangeError
throw new RangeError('Valeur hors limites');

// === CRÉER DES ERREURS PERSONNALISÉES ===

class ValidationError extends Error {
    constructor(message) {
        super(message);
        this.name = 'ValidationError';
    }
}

class DatabaseError extends Error {
    constructor(message, query) {
        super(message);
        this.name = 'DatabaseError';
        this.query = query;
    }
}

// Utilisation
function validerEmail(email) {
    if (!email.includes('@')) {
        throw new ValidationError('Email invalide');
    }
    return true;
}

try {
    validerEmail('test');
} catch (error) {
    if (error instanceof ValidationError) {
        console.error('Validation:', error.message);
    } else {
        console.error('Autre erreur:', error);
    }
}

// === PROPRIÉTÉS DE L'OBJET ERROR ===

try {
    throw new Error('Mon erreur');
} catch (error) {
    console.log(error.name);        // 'Error'
    console.log(error.message);     // 'Mon erreur'
    console.log(error.stack);       // Stack trace
}

// === GESTION D'ERREURS ASYNC ===

// Avec async/await
async function chargerDonnees() {
    try {
        const response = await fetch('https://api.example.com/data');
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}`);
        }
        const data = await response.json();
        return data;
    } catch (error) {
        console.error('Erreur de chargement:', error);
        throw error; // Re-throw si besoin
    } finally {
        console.log('Tentative terminée');
    }
}

// Avec promises
fetch('https://api.example.com/data')
    .then(response => {
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}`);
        }
        return response.json();
    })
    .then(data => console.log(data))
    .catch(error => console.error('Erreur:', error))
    .finally(() => console.log('Terminé'));

// === ERREURS NON CAPTURÉES ===

// Erreurs globales (navigateur)
window.addEventListener('error', (event) => {
    console.error('Erreur globale:', event.error);
});

// Promesses rejetées non gérées
window.addEventListener('unhandledrejection', (event) => {
    console.error('Promesse rejetée:', event.reason);
});

// === EXEMPLE COMPLET ===

class APIError extends Error {
    constructor(message, statusCode) {
        super(message);
        this.name = 'APIError';
        this.statusCode = statusCode;
    }
}

async function fetchUser(id) {
    try {
        const response = await fetch(`https://api.example.com/users/${id}`);
        
        if (!response.ok) {
            throw new APIError(
                'Impossible de récupérer l\'utilisateur',
                response.status
            );
        }
        
        const user = await response.json();
        
        if (!user.email) {
            throw new ValidationError('Email manquant');
        }
        
        return user;
    } catch (error) {
        if (error instanceof APIError) {
            console.error(`Erreur API (${error.statusCode}):`, error.message);
        } else if (error instanceof ValidationError) {
            console.error('Validation:', error.message);
        } else if (error instanceof TypeError) {
            console.error('Erreur de type:', error.message);
        } else {
            console.error('Erreur inconnue:', error);
        }
        
        // Logger l'erreur (service externe)
        // logError(error);
        
        throw error; // Re-throw pour permettre gestion en amont
    }
}


[OK] LOCALSTORAGE & SESSIONSTORAGE

// === LOCALSTORAGE (persistant) ===

// Sauvegarder
localStorage.setItem('nom', 'Alice');
localStorage.setItem('age', '25');

// Récupérer
const nom = localStorage.getItem('nom');
console.log(nom);                   // 'Alice'

// Supprimer
localStorage.removeItem('nom');

// Vider tout
localStorage.clear();

// Nombre d'éléments
console.log(localStorage.length);

// Accéder par index
const key = localStorage.key(0);
const value = localStorage.getItem(key);

// === STOCKER DES OBJETS ===

// [ATTENTION] localStorage stocke seulement des strings!

// Sauvegarder objet (convertir en JSON)
const user = {
    nom: 'Alice',
    age: 25,
    ville: 'Paris'
};

localStorage.setItem('user', JSON.stringify(user));

// Récupérer objet
const savedUser = JSON.parse(localStorage.getItem('user'));
console.log(savedUser.nom);         // 'Alice'

// === STOCKER DES ARRAYS ===

const todos = [
    {id: 1, texte: 'Tâche 1', termine: false},
    {id: 2, texte: 'Tâche 2', termine: true}
];

localStorage.setItem('todos', JSON.stringify(todos));

const savedTodos = JSON.parse(localStorage.getItem('todos'));
console.log(savedTodos);

// === SESSIONSTORAGE (temporaire) ===

// API identique à localStorage
// Différence: données supprimées à la fermeture de l'onglet

sessionStorage.setItem('sessionData', 'valeur');
const data = sessionStorage.getItem('sessionData');
sessionStorage.removeItem('sessionData');
sessionStorage.clear();

// === ÉVÉNEMENT STORAGE ===

// Détecte changements dans localStorage (autre onglet/fenêtre)
window.addEventListener('storage', (event) => {
    console.log('Clé:', event.key);
    console.log('Ancienne valeur:', event.oldValue);
    console.log('Nouvelle valeur:', event.newValue);
    console.log('URL:', event.url);
});

// === WRAPPER UTILITAIRE ===

class Storage {
    static set(key, value) {
        try {
            const serialized = JSON.stringify(value);
            localStorage.setItem(key, serialized);
            return true;
        } catch (error) {
            console.error('Erreur de sauvegarde:', error);
            return false;
        }
    }
    
    static get(key, defaultValue = null) {
        try {
            const item = localStorage.getItem(key);
            if (item === null) {
                return defaultValue;
            }
            return JSON.parse(item);
        } catch (error) {
            console.error('Erreur de récupération:', error);
            return defaultValue;
        }
    }
    
    static remove(key) {
        localStorage.removeItem(key);
    }
    
    static clear() {
        localStorage.clear();
    }
    
    static has(key) {
        return localStorage.getItem(key) !== null;
    }
}

// Utilisation
Storage.set('user', {nom: 'Alice', age: 25});
const user2 = Storage.get('user');
console.log(user2);

// === LIMITES ===

// - Capacité: ~5-10MB par domaine
// - Synchrone (peut bloquer UI si trop de données)
// - Accessible seulement côté client
// - Pas sécurisé (ne pas stocker données sensibles)

// Vérifier espace disponible
try {
    const test = 'x'.repeat(1024 * 1024); // 1MB
    localStorage.setItem('test', test);
    localStorage.removeItem('test');
    console.log('Espace suffisant');
} catch (error) {
    console.error('Quota dépassé');
}


[OK] REGEX (EXPRESSIONS RÉGULIÈRES)

// === CRÉER UN REGEX ===

// Littéral (RECOMMANDÉ)
const regex1 = /pattern/;
const regex2 = /hello/i;            // i = insensible à la casse

// Constructeur
const regex3 = new RegExp('pattern');
const regex4 = new RegExp('hello', 'i');

// === FLAGS ===

// i - insensible à la casse
const caseInsensitive = /hello/i;
console.log(caseInsensitive.test('HELLO')); // true

// g - global (toutes les occurrences)
const global = /o/g;

// m - multiline (^ et $ pour chaque ligne)
const multiline = /^hello/m;

// s - dotall (. inclut \n)
const dotall = /./s;

// u - unicode
const unicode = /\u{1F600}/u;

// Combiner flags
const combined = /pattern/gi;

// === TEST() - vérifier correspondance ===

const regex = /hello/i;
console.log(regex.test('Hello World'));     // true
console.log(regex.test('Hi there'));        // false

// === EXEC() - extraire correspondances ===

const regex5 = /(\d{4})-(\d{2})-(\d{2})/;
const result = regex5.exec('Date: 2024-01-15');
console.log(result[0]);             // '2024-01-15' (match complet)
console.log(result[1]);             // '2024' (groupe 1)
console.log(result[2]);             // '01' (groupe 2)
console.log(result[3]);             // '15' (groupe 3)

// === MÉTHODES STRING AVEC REGEX ===

const texte = 'Hello World, hello JavaScript';

// match() - trouver correspondances
console.log(texte.match(/hello/i));         // ['Hello']
console.log(texte.match(/hello/gi));        // ['Hello', 'hello']

// matchAll() - toutes correspondances avec détails
const matches = texte.matchAll(/hello/gi);
for (const match of matches) {
    console.log(match[0], match.index);
}

// search() - trouver position
console.log(texte.search(/world/i));        // 6

// replace() - remplacer
console.log(texte.replace(/hello/i, 'Hi')); // 'Hi World, hello JavaScript'
console.log(texte.replace(/hello/gi, 'Hi'));// 'Hi World, Hi JavaScript'

// replaceAll()
console.log(texte.replaceAll(/hello/gi, 'Hi'));

// split() - découper
console.log('a,b,c'.split(/,/));            // ['a', 'b', 'c']
console.log('a1b2c3'.split(/\d/));          // ['a', 'b', 'c', '']

// === CARACTÈRES SPÉCIAUX ===

// . - n'importe quel caractère (sauf \n)
/h.llo/.test('hello');              // true
/h.llo/.test('hallo');              // true

// * - 0 ou plus
/hel*o/.test('heo');                // true
/hel*o/.test('hello');              // true

// + - 1 ou plus
/hel+o/.test('heo');                // false
/hel+o/.test('hello');              // true

// ? - 0 ou 1 (optionnel)
/colou?r/.test('color');            // true
/colou?r/.test('colour');           // true

// ^ - début de string
/^hello/.test('hello world');       // true
/^hello/.test('say hello');         // false

// $ - fin de string
/world$/.test('hello world');       // true
/world$/.test('world peace');       // false

// | - OU
/chat|chien/.test('J\'ai un chat'); // true

// === CLASSES DE CARACTÈRES ===

// [abc] - a, b ou c
/[aeiou]/.test('hello');            // true

// [^abc] - tout SAUF a, b, c
/[^aeiou]/.test('hello');           // true (h)

// [a-z] - range a à z
/[a-z]/.test('hello');              // true

// [0-9] - chiffres
/[0-9]/.test('abc123');             // true

// Combinaisons
/[a-zA-Z0-9]/.test('Hello123');     // true

// === RACCOURCIS ===

// \d - chiffre [0-9]
/\d/.test('abc123');                // true

// \D - non-chiffre [^0-9]
/\D/.test('abc123');                // true

// \w - mot [a-zA-Z0-9_]
/\w/.test('hello_123');             // true

// \W - non-mot
/\W/.test('hello!');                // true (!)

// \s - espace blanc (espace, tab, \n)
/\s/.test('hello world');           // true

// \S - non-espace
/\S/.test('hello');                 // true

// === QUANTIFICATEURS ===

// {n} - exactement n fois
/\d{4}/.test('2024');               // true
/\d{4}/.test('24');                 // false

// {n,} - au moins n fois
/\d{2,}/.test('12345');             // true

// {n,m} - entre n et m fois
/\d{2,4}/.test('123');              // true

// === GROUPES ===

// () - groupe de capture
const date = /(\d{4})-(\d{2})-(\d{2})/;
const match = date.exec('2024-01-15');
console.log(match[1], match[2], match[3]); // 2024 01 15

// (?:) - groupe non-capturant
/(?:https?):\/\//;

// === LOOKAHEAD & LOOKBEHIND ===

// (?=) - positive lookahead
/hello(?= world)/.test('hello world');      // true
/hello(?= world)/.test('hello there');      // false

// (?!) - negative lookahead
/hello(?! world)/.test('hello there');      // true

// (?<=) - positive lookbehind
/(?<=hello )world/.test('hello world');     // true

// (?<!) - negative lookbehind
/(?<!hello )world/.test('hi world');        // true

// === ÉCHAPPEMENT ===

// Caractères spéciaux à échapper: . * + ? ^ $ { } ( ) | [ ] \ /
const escaped = /\./;               // Point littéral
const url = /https?:\/\//;          // Slashes échappés

// === EXEMPLES PRATIQUES ===

// Email
const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
console.log(emailRegex.test('alice@example.com')); // true

// Téléphone (France)
const phoneRegex = /^0[1-9](\d{2}){4}$/;
console.log(phoneRegex.test('0612345678')); // true

// URL
const urlRegex = /^https?:\/\/.+/;
console.log(urlRegex.test('https://example.com')); // true

// Code postal (France)
const zipRegex = /^\d{5}$/;
console.log(zipRegex.test('75001')); // true

// Mot de passe (min 8 chars, 1 maj, 1 min, 1 chiffre)
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/;
console.log(passwordRegex.test('Password123')); // true

// Extraire hashtags
const hashtagRegex = /#\w+/g;
const tweet = 'J\'adore #JavaScript et #coding!';
console.log(tweet.match(hashtagRegex)); // ['#JavaScript', '#coding']

// Valider date YYYY-MM-DD
const dateRegex = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/;
console.log(dateRegex.test('2024-01-15')); // true


[OK] SYMBOLES & ITERATEURS

// === SYMBOL ===

// Créer symbole unique
const sym1 = Symbol();
const sym2 = Symbol();
console.log(sym1 === sym2);         // false (toujours unique)

// Avec description
const id = Symbol('id');
console.log(id.toString());         // 'Symbol(id)'

// Utilisation comme clé d'objet
const user = {
    nom: 'Alice',
    [id]: 12345
};

console.log(user[id]);              // 12345
console.log(user.id);               // undefined

// Symboles ne sont pas énumérables
console.log(Object.keys(user));     // ['nom'] (pas id)
console.log(Object.getOwnPropertySymbols(user)); // [Symbol(id)]

// === SYMBOLES BIEN CONNUS ===

// Symbol.iterator - rendre objet itérable
const range = {
    from: 1,
    to: 5,
    
    [Symbol.iterator]() {
        let current = this.from;
        const last = this.to;
        
        return {
            next() {
                if (current <= last) {
                    return { value: current++, done: false };
                } else {
                    return { done: true };
                }
            }
        };
    }
};

// Maintenant itérable
for (const num of range) {
    console.log(num);               // 1, 2, 3, 4, 5
}

// === GÉNÉRATEURS ===

// Fonction génératrice
function* generateur() {
    yield 1;
    yield 2;
    yield 3;
}

const gen = generateur();
console.log(gen.next());            // {value: 1, done: false}
console.log(gen.next());            // {value: 2, done: false}
console.log(gen.next());            // {value: 3, done: false}
console.log(gen.next());            // {done: true}

// Avec boucle
for (const value of generateur()) {
    console.log(value);             // 1, 2, 3
}

// Générateur avec paramètres
function* compteur(debut, fin) {
    for (let i = debut; i <= fin; i++) {
        yield i;
    }
}

console.log([...compteur(1, 5)]);   // [1, 2, 3, 4, 5]

// Générateur infini
function* fibonacci() {
    let [a, b] = [0, 1];
    while (true) {
        yield a;
        [a, b] = [b, a + b];
    }
}

const fib = fibonacci();
console.log(fib.next().value);      // 0
console.log(fib.next().value);      // 1
console.log(fib.next().value);      // 1
console.log(fib.next().value);      // 2

// Yield* - déléguer à autre générateur
function* gen1() {
    yield 1;
    yield 2;
}

function* gen2() {
    yield* gen1();
    yield 3;
    yield 4;
}

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


[OK] PROXY & REFLECT

// === PROXY - intercepter opérations ===

// Créer proxy
const target = {
    nom: 'Alice',
    age: 25
};

const handler = {
    // Intercepter lecture
    get(target, property) {
        console.log(`Lecture de ${property}`);
        return target[property];
    },
    
    // Intercepter écriture
    set(target, property, value) {
        console.log(`Écriture de ${property} = ${value}`);
        target[property] = value;
        return true; // Succès
    }
};

const proxy = new Proxy(target, handler);

console.log(proxy.nom);             // Lecture de nom -> 'Alice'
proxy.age = 26;                     // Écriture de age = 26

// === VALIDATION AVEC PROXY ===

const validatedUser = new Proxy({}, {
    set(target, property, value) {
        if (property === 'age') {
            if (typeof value !== 'number') {
                throw new TypeError('Age doit être un nombre');
            }
            if (value < 0 || value > 150) {
                throw new RangeError('Age invalide');
            }
        }
        target[property] = value;
        return true;
    }
});

validatedUser.age = 25;             // [OK] OK
// validatedUser.age = 'vingt';     // [X] TypeError
// validatedUser.age = -5;          // [X] RangeError

// === VALEURS PAR DÉFAUT ===

const withDefaults = new Proxy({}, {
    get(target, property) {
        return property in target ? target[property] : 'N/A';
    }
});

withDefaults.nom = 'Alice';
console.log(withDefaults.nom);      // 'Alice'
console.log(withDefaults.age);      // 'N/A' (pas défini)

// === REFLECT ===

// Reflect fournit méthodes pour opérations sur objets

const obj = {nom: 'Alice', age: 25};

// get
console.log(Reflect.get(obj, 'nom'));           // 'Alice'

// set
Reflect.set(obj, 'age', 26);

// has (in operator)
console.log(Reflect.has(obj, 'nom'));           // true

// deleteProperty
Reflect.deleteProperty(obj, 'age');

// ownKeys
console.log(Reflect.ownKeys(obj));              // ['nom']

// Utilisation avec Proxy
const handler2 = {
    get(target, property) {
        console.log(`Get: ${property}`);
        return Reflect.get(target, property);
    },
    
    set(target, property, value) {
        console.log(`Set: ${property} = ${value}`);
        return Reflect.set(target, property, value);
    }
};


[OK] OPÉRATEURS AVANCÉS

// === OPTIONAL CHAINING (?.) ===

const user = {
    nom: 'Alice',
    adresse: {
        ville: 'Paris'
    }
};

// Sans optional chaining (risqué)
// const zip = user.adresse.codePostal.numero; // [X] Erreur!

// Avec optional chaining (sûr)
const zip = user.adresse?.codePostal?.numero; // undefined

// Avec méthodes
const result = user.getInfo?.();              // undefined si méthode n'existe pas

// Avec arrays
const firstItem = users?.[0]?.nom;

// === NULLISH COALESCING (??) ===

// Retourne valeur de droite si gauche est null ou undefined
const value1 = null ?? 'défaut';              // 'défaut'
const value2 = undefined ?? 'défaut';         // 'défaut'
const value3 = 0 ?? 'défaut';                 // 0 (pas null/undefined!)
const value4 = '' ?? 'défaut';                // '' (pas null/undefined!)
const value5 = false ?? 'défaut';             // false (pas null/undefined!)

// Différence avec ||
console.log(0 || 'défaut');                   // 'défaut' (0 est falsy)
console.log(0 ?? 'défaut');                   // 0 (0 n'est pas null/undefined)

// === ASSIGNATION NULLISH (??=) ===

let config = {timeout: 0};

// Assigne seulement si null/undefined
config.timeout ??= 5000;
console.log(config.timeout);                  // 0 (pas null/undefined)

config.retries ??= 3;
console.log(config.retries);                  // 3 (était undefined)

// === LOGICAL ASSIGNMENT ===

// &&= - assigne si truthy
let x = 5;
x &&= 10;
console.log(x);                               // 10

let y = 0;
y &&= 10;
console.log(y);                               // 0 (falsy, pas assigné)

// ||= - assigne si falsy
let z = 0;
z ||= 10;
console.log(z);                               // 10

// === OPÉRATEUR VIRGULE ===

let a = (1, 2, 3);
console.log(a);                               // 3 (dernière expression)

// === OPÉRATEUR VOID ===

void expression;                              // Évalue et retourne undefined

// Utilisation: hyperliens sans navigation
// <a href="javascript:void(0)">Lien</a>

// === OPÉRATEUR IN ===

const car = {marque: 'Toyota', modele: 'Corolla'};
console.log('marque' in car);                 // true
console.log('couleur' in car);                // false

// Avec arrays
const arr = [1, 2, 3];
console.log(0 in arr);                        // true (index 0 existe)
console.log(5 in arr);                        // false


[OK] PERFORMANCE & OPTIMISATION

// === CONSOLE.TIME() - mesurer performance ===

console.time('operation');
// ... code à mesurer ...
for (let i = 0; i < 1000000; i++) {
    // opération
}
console.timeEnd('operation');                 // operation: 12.34ms

// === PERFORMANCE API ===

const start = performance.now();
// ... code ...
const end = performance.now();
console.log(`Temps: ${end - start}ms`);

// === DEBOUNCE - limiter appels fréquents ===

function debounce(func, delay) {
    let timeoutId;
    return function(...args) {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => func.apply(this, args), delay);
    };
}

// Utilisation
const search = debounce((query) => {
    console.log('Recherche:', query);
}, 500);

// === THROTTLE - 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);
        }
    };
}

// === MEMOIZATION - cache résultats ===

function memoize(fn) {
    const cache = {};
    return function(...args) {
        const key = JSON.stringify(args);
        if (key in cache) {
            return cache[key];
        }
        const result = fn.apply(this, args);
        cache[key] = result;
        return result;
    };
}

// Exemple avec Fibonacci
function fibonacci(n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

const fibMemo = memoize(fibonacci);
console.time('Avec cache');
console.log(fibMemo(40));           // Rapide après premier appel
console.timeEnd('Avec cache');

// === ÉVITER MODIFICATIONS DOM FRÉQUENTES ===

// [X] Mauvais (modifie DOM à chaque itération)
const list = document.querySelector('ul');
for (let i = 0; i < 100; i++) {
    const li = document.createElement('li');
    li.textContent = `Item ${i}`;
    list.appendChild(li);           // Reflow à chaque fois!
}

// [OK] Bon (une seule modification)
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
    const li = document.createElement('li');
    li.textContent = `Item ${i}`;
    fragment.appendChild(li);
}
list.appendChild(fragment);         // Un seul reflow

// === EVENT DELEGATION ===

// [X] Mauvais (listener sur chaque élément)
document.querySelectorAll('.item').forEach(item => {
    item.addEventListener('click', handleClick);
});

// [OK] Bon (un seul listener sur parent)
document.querySelector('.container').addEventListener('click', (e) => {
    if (e.target.classList.contains('item')) {
        handleClick(e);
    }
});

// === LAZY LOADING ===

// Charger images au scroll
const images = document.querySelectorAll('img[data-src]');
const imageObserver = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            const img = entry.target;
            img.src = img.dataset.src;
            imageObserver.unobserve(img);
        }
    });
});

images.forEach(img => imageObserver.observe(img));

// === REQUESTANIMATIONFRAME ===

// [X] Mauvais (pas synchronisé avec rafraîchissement)
setInterval(() => {
    updateAnimation();
}, 16);                             // ~60fps mais pas précis

// [OK] Bon (synchronisé avec navigateur)
function animate() {
    updateAnimation();
    requestAnimationFrame(animate);
}
requestAnimationFrame(animate);

// === WEB WORKERS - calculs lourds ===

// main.js
const worker = new Worker('worker.js');

worker.postMessage({data: largeDataset});

worker.onmessage = (e) => {
    console.log('Résultat:', e.data);
};

// worker.js
onmessage = (e) => {
    const result = heavyComputation(e.data);
    postMessage(result);
};


[OK] DESTRUCTURING & SPREAD AVANCÉS

// === DESTRUCTURING AVEC ALIAS ===

const user = {
    nom: 'Alice',
    age: 25,
    adresse: {
        ville: 'Paris',
        pays: 'France'
    }
};

// Renommer en destructurant
const {nom: userName, age: userAge} = user;
console.log(userName, userAge);     // Alice 25

// Destructuring imbriqué avec alias
const {adresse: {ville: cityName}} = user;
console.log(cityName);              // Paris

// === REST AVEC OBJETS ===

const {nom, ...reste} = user;
console.log(reste);                 // {age: 25, adresse: {...}}

// === SWAP AVANCÉ ===

let a = 1, b = 2, c = 3;
[a, b, c] = [b, c, a];
console.log(a, b, c);               // 2 3 1

// === DESTRUCTURING DANS BOUCLES ===

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

for (const {nom, age} of users) {
    console.log(`${nom}: ${age} ans`);
}

// Avec Object.entries
const scores = {alice: 95, bob: 87, charlie: 92};

for (const [nom, score] of Object.entries(scores)) {
    console.log(`${nom}: ${score}`);
}

// === SPREAD AVEC FONCTIONS ===

function somme(a, b, c) {
    return a + b + c;
}

const nombres = [1, 2, 3];
console.log(somme(...nombres));     // 6

// === CLONER PROFONDÉMENT ===

// Clone superficiel (shallow)
const original = {a: 1, b: {c: 2}};
const clone1 = {...original};
clone1.b.c = 999;
console.log(original.b.c);          // 999 (modifié!)

// Clone profond (deep)
const clone2 = JSON.parse(JSON.stringify(original));
// [ATTENTION] Perd: fonctions, Date, undefined, Symbol

// Avec structuredClone (moderne)
const clone3 = structuredClone(original);

// === FUSIONNER OBJETS PROFONDÉMENT ===

function deepMerge(target, source) {
    const output = {...target};
    for (const key in source) {
        if (source[key] instanceof Object && key in target) {
            output[key] = deepMerge(target[key], source[key]);
        } else {
            output[key] = source[key];
        }
    }
    return output;
}

const obj1 = {a: 1, b: {c: 2, d: 3}};
const obj2 = {b: {c: 20, e: 4}, f: 5};
const merged = deepMerge(obj1, obj2);
console.log(merged);
// {a: 1, b: {c: 20, d: 3, e: 4}, f: 5}


[OK] ASTUCES & PATTERNS UTILES

// === CONVERTIR EN BOOLEAN ===

const value = 'hello';
const bool = !!value;               // true
const bool2 = Boolean(value);       // true

// === CONVERTIR EN NUMBER ===

const str = '123';
const num1 = +str;                  // 123
const num2 = Number(str);           // 123
const num3 = parseInt(str);         // 123

// === CONVERTIR EN STRING ===

const num = 123;
const str1 = '' + num;              // '123'
const str2 = String(num);           // '123'
const str3 = num.toString();        // '123'

// === ARRONDIR NOMBRES ===

const num4 = 4.7;
console.log(~~num4);                // 4 (double NOT binaire)
console.log(num4 | 0);              // 4 (OR binaire)
console.log(Math.trunc(num4));      // 4 (recommandé)

// === VALEUR PAR DÉFAUT ===

// Ancien
function saluer(nom) {
    nom = nom || 'Anonyme';
    console.log(`Bonjour ${nom}`);
}

// Moderne avec ?? (mieux)
function saluer2(nom) {
    nom = nom ?? 'Anonyme';
    console.log(`Bonjour ${nom}`);
}

// Encore mieux: paramètre par défaut
function saluer3(nom = 'Anonyme') {
    console.log(`Bonjour ${nom}`);
}

// === SWAP SANS VARIABLE TEMPORAIRE ===

let x = 5, y = 10;
[x, y] = [y, x];

// Ou avec arithmétique (nombres seulement)
x = x + y;
y = x - y;
x = x - y;

// === GÉNÉRER ARRAY DE NOMBRES ===

// 0 à 9
const nums1 = [...Array(10).keys()];
const nums2 = Array.from({length: 10}, (_, i) => i);

// 1 à 10
const nums3 = Array.from({length: 10}, (_, i) => i + 1);

// Range personnalisé
const range = (start, end) => {
    return Array.from({length: end - start + 1}, (_, i) => start + i);
};
console.log(range(5, 10));          // [5, 6, 7, 8, 9, 10]

// === SHUFFLER UN ARRAY ===

function shuffle(array) {
    const arr = [...array];
    for (let i = arr.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [arr[i], arr[j]] = [arr[j], arr[i]];
    }
    return arr;
}

// === ÉLÉMENT ALÉATOIRE D'UN ARRAY ===

const getRandomItem = (arr) => arr[Math.floor(Math.random() * arr.length)];

const fruits = ['pomme', 'banane', 'orange'];
console.log(getRandomItem(fruits));

// === SUPPRIMER DOUBLONS ===

const arr = [1, 2, 2, 3, 4, 4, 5];

// Avec Set (simple)
const unique1 = [...new Set(arr)];

// Avec filter
const unique2 = arr.filter((item, index) => arr.indexOf(item) === index);

// Objets (par propriété)
const users2 = [
    {id: 1, nom: 'Alice'},
    {id: 2, nom: 'Bob'},
    {id: 1, nom: 'Alice'}
];

const uniqueUsers = users2.filter((user, index, self) =>
    index === self.findIndex(u => u.id === user.id)
);

// === APLATIR ARRAY ===

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

// Niveau 1
const flat1 = nested.flat();        // [1, 2, 3, 4, [5, 6]]

// Tous niveaux
const flatAll = nested.flat(Infinity); // [1, 2, 3, 4, 5, 6]

// Manuel (récursif)
const flattenDeep = (arr) => {
    return arr.reduce((acc, val) => 
        Array.isArray(val) ? acc.concat(flattenDeep(val)) : acc.concat(val)
    , []);
};

// === GROUPER PAR PROPRIÉTÉ ===

const people = [
    {nom: 'Alice', age: 25},
    {nom: 'Bob', age: 30},
    {nom: 'Charlie', age: 25}
];

const groupBy = (arr, key) => {
    return arr.reduce((acc, obj) => {
        const keyValue = obj[key];
        (acc[keyValue] = acc[keyValue] || []).push(obj);
        return acc;
    }, {});
};

const grouped = groupBy(people, 'age');
console.log(grouped);
// {25: [{...Alice}, {...Charlie}], 30: [{...Bob}]}

// === SOMME D'ARRAY ===

const numbers = [1, 2, 3, 4, 5];

// Avec reduce
const sum1 = numbers.reduce((acc, num) => acc + num, 0);

// Avec boucle
let sum2 = 0;
for (const num of numbers) {
    sum2 += num;
}

// === MOYENNE D'ARRAY ===

const average = numbers.reduce((acc, num) => acc + num, 0) / numbers.length;

// === MIN/MAX D'ARRAY ===

const min = Math.min(...numbers);
const max = Math.max(...numbers);

// Avec reduce
const min2 = numbers.reduce((acc, num) => Math.min(acc, num));
const max2 = numbers.reduce((acc, num) => Math.max(acc, num));

// === COMPTER OCCURRENCES ===

const items = ['a', 'b', 'a', 'c', 'b', 'a'];

const count = items.reduce((acc, item) => {
    acc[item] = (acc[item] || 0) + 1;
    return acc;
}, {});

console.log(count);                 // {a: 3, b: 2, c: 1}

// === CHUNK ARRAY (diviser en morceaux) ===

const chunk = (arr, size) => {
    return Array.from({length: Math.ceil(arr.length / size)}, (_, i) =>
        arr.slice(i * size, i * size + size)
    );
};

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

// === CAPITALISER STRING ===

const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);

const capitalizeWords = (str) => {
    return str.split(' ').map(capitalize).join(' ');
};

console.log(capitalizeWords('hello world'));  // 'Hello World'

// === TRONQUER STRING ===

const truncate = (str, length) => {
    return str.length > length ? str.slice(0, length) + '...' : str;
};

console.log(truncate('Texte très long', 10));  // 'Texte très...'

// === VÉRIFIER OBJET VIDE ===

const isEmpty = (obj) => Object.keys(obj).length === 0;

console.log(isEmpty({}));           // true
console.log(isEmpty({a: 1}));       // false

// === DEEP EQUAL (comparaison profonde) ===

function deepEqual(obj1, obj2) {
    if (obj1 === obj2) return true;
    
    if (typeof obj1 !== 'object' || typeof obj2 !== 'object' ||
        obj1 === null || obj2 === null) {
        return false;
    }
    
    const keys1 = Object.keys(obj1);
    const keys2 = Object.keys(obj2);
    
    if (keys1.length !== keys2.length) return false;
    
    for (const key of keys1) {
        if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) {
            return false;
        }
    }
    
    return true;
}

// === WAIT/SLEEP ===

const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));

// Utilisation
async function demo() {
    console.log('Début');
    await wait(2000);
    console.log('Après 2 secondes');
}

// === RETRY (réessayer) ===

async function retry(fn, retries = 3, delay = 1000) {
    for (let i = 0; i < retries; i++) {
        try {
            return await fn();
        } catch (error) {
            if (i === retries - 1) throw error;
            await wait(delay);
        }
    }
}

// Utilisation
await retry(() => fetch('https://api.example.com/data'), 3, 1000);


[OK] DESIGN PATTERNS

// === SINGLETON ===

class Singleton {
    static instance = null;
    
    constructor() {
        if (Singleton.instance) {
            return Singleton.instance;
        }
        Singleton.instance = this;
        this.data = [];
    }
    
    static getInstance() {
        if (!Singleton.instance) {
            Singleton.instance = new Singleton();
        }
        return Singleton.instance;
    }
}

const s1 = new Singleton();
const s2 = new Singleton();
console.log(s1 === s2);             // true

// === FACTORY ===

class User {
    constructor(name, role) {
        this.name = name;
        this.role = role;
    }
}

class UserFactory {
    static createUser(name, role) {
        switch (role) {
            case 'admin':
                return new AdminUser(name);
            case 'moderator':
                return new ModeratorUser(name);
            default:
                return new User(name, 'user');
        }
    }
}

class AdminUser extends User {
    constructor(name) {
        super(name, 'admin');
        this.permissions = ['read', 'write', 'delete'];
    }
}

class ModeratorUser extends User {
    constructor(name) {
        super(name, 'moderator');
        this.permissions = ['read', 'write'];
    }
}

// Utilisation
const admin = UserFactory.createUser('Alice', 'admin');

// === OBSERVER (PUB/SUB) ===

class EventEmitter {
    constructor() {
        this.events = {};
    }
    
    on(event, listener) {
        if (!this.events[event]) {
            this.events[event] = [];
        }
        this.events[event].push(listener);
    }
    
    emit(event, ...args) {
        if (this.events[event]) {
            this.events[event].forEach(listener => {
                listener(...args);
            });
        }
    }
    
    off(event, listenerToRemove) {
        if (this.events[event]) {
            this.events[event] = this.events[event].filter(
                listener => listener !== listenerToRemove
            );
        }
    }
}

// Utilisation
const emitter = new EventEmitter();

const handler = (data) => console.log('Données reçues:', data);
emitter.on('data', handler);
emitter.emit('data', {value: 123});
emitter.off('data', handler);

// === MODULE PATTERN ===

const Calculator = (function() {
    // Privé
    let result = 0;
    
    function log() {
        console.log('Résultat:', result);
    }
    
    // Public
    return {
        add(x) {
            result += x;
            log();
            return this;
        },
        
        subtract(x) {
            result -= x;
            log();
            return this;
        },
        
        multiply(x) {
            result *= x;
            log();
            return this;
        },
        
        getResult() {
            return result;
        },
        
        reset() {
            result = 0;
            return this;
        }
    };
})();

// Utilisation (méthode chaining)
Calculator.add(5).multiply(2).subtract(3);
console.log(Calculator.getResult());    // 7

// === BUILDER ===

class QueryBuilder {
    constructor() {
        this.query = {};
    }
    
    select(...fields) {
        this.query.select = fields;
        return this;
    }
    
    from(table) {
        this.query.from = table;
        return this;
    }
    
    where(conditions) {
        this.query.where = conditions;
        return this;
    }
    
    orderBy(field, direction = 'ASC') {
        this.query.orderBy = {field, direction};
        return this;
    }
    
    limit(n) {
        this.query.limit = n;
        return this;
    }
    
    build() {
        return this.query;
    }
}

// Utilisation
const query = new QueryBuilder()
    .select('id', 'name', 'email')
    .from('users')
    .where({active: true})
    .orderBy('name', 'ASC')
    .limit(10)
    .build();

console.log(query);

// === STRATEGY ===

class PaymentStrategy {
    pay(amount) {
        throw new Error('pay() doit être implémenté');
    }
}

class CreditCardPayment extends PaymentStrategy {
    constructor(cardNumber) {
        super();
        this.cardNumber = cardNumber;
    }
    
    pay(amount) {
        console.log(`Paiement de ${amount}€ par carte ${this.cardNumber}`);
    }
}

class PayPalPayment extends PaymentStrategy {
    constructor(email) {
        super();
        this.email = email;
    }
    
    pay(amount) {
        console.log(`Paiement de ${amount}€ via PayPal (${this.email})`);
    }
}

class ShoppingCart {
    constructor(strategy) {
        this.strategy = strategy;
    }
    
    setPaymentStrategy(strategy) {
        this.strategy = strategy;
    }
    
    checkout(amount) {
        this.strategy.pay(amount);
    }
}

// Utilisation
const cart = new ShoppingCart(new CreditCardPayment('1234-5678'));
cart.checkout(100);

cart.setPaymentStrategy(new PayPalPayment('user@example.com'));
cart.checkout(50);


[OK] BONNES PRATIQUES

// === NOMMAGE ===

// [OK] Variables et fonctions: camelCase
let userName = 'Alice';
function getUserData() {}

// [OK] Classes: PascalCase
class UserAccount {}

// [OK] Constantes: SCREAMING_SNAKE_CASE
const API_URL = 'https://api.example.com';
const MAX_RETRY = 3;

// [OK] Privé: préfixe _
class MyClass {
    constructor() {
        this._privateVar = 'secret';
    }
    
    _privateMethod() {}
}

// [OK] Boolean: préfixes is, has, can, should
let isActive = true;
let hasPermission = false;
let canEdit = true;
let shouldUpdate = false;

// === CONSTANTES EN HAUT ===

// [OK] Bon
const MAX_USERS = 100;
const API_URL = 'https://api.example.com';

function getUsers() {
    // ...
}

// === FONCTIONS COURTES ===

// [X] Mauvais (fonction trop longue)
function processUser(user) {
    // 100 lignes de code...
}

// [OK] Bon (découper)
function processUser(user) {
    validateUser(user);
    enrichUserData(user);
    saveUser(user);
    sendNotification(user);
}

// === ÉVITER CALLBACKS IMBRIQUÉS (CALLBACK HELL) ===

// [X] Mauvais
getData(function(a) {
    getMoreData(a, function(b) {
        getMoreData(b, function(c) {
            getMoreData(c, function(d) {
                // ...
            });
        });
    });
});

// [OK] Bon (async/await)
async function fetchData() {
    const a = await getData();
    const b = await getMoreData(a);
    const c = await getMoreData(b);
    const d = await getMoreData(c);
}

// === UTILISER CONST PAR DÉFAUT ===

// [OK] Préférer const
const user = {name: 'Alice'};

// Utiliser let seulement si réassignation
let counter = 0;
counter++;

// [X] Éviter var
var oldStyle = 'non';

// === EARLY RETURN ===

// [X] Mauvais (imbrication)
function processData(data) {
    if (data) {
        if (data.isValid) {
            if (data.hasPermission) {
                // traitement
            }
        }
    }
}

// [OK] Bon (early return)
function processData(data) {
    if (!data) return;
    if (!data.isValid) return;
    if (!data.hasPermission) return;
    
    // traitement
}

// === ÉVITER MAGIC NUMBERS ===

// [X] Mauvais
if (user.age > 18) {}
setTimeout(callback, 86400000);

// [OK] Bon
const LEGAL_AGE = 18;
const ONE_DAY_MS = 24 * 60 * 60 * 1000;

if (user.age > LEGAL_AGE) {}
setTimeout(callback, ONE_DAY_MS);

// === COMMENTAIRES UTILES ===

// [X] Mauvais (évident)
// Incrémente i
i++;

// [OK] Bon (explique pourquoi)
// Sauter le premier élément car il est l'en-tête
for (let i = 1; i < data.length; i++) {}

// [OK] Documentation de fonction
/**
 * Calcule la distance entre deux points
 * @param {Object} point1 - Premier point {x, y}
 * @param {Object} point2 - Deuxième point {x, y}
 * @returns {number} Distance euclidienne
 */
function distance(point1, point2) {
    const dx = point2.x - point1.x;
    const dy = point2.y - point1.y;
    return Math.sqrt(dx * dx + dy * dy);
}

// === UTILISER OPTIONAL CHAINING ===

// [X] Mauvais
const city = user && user.address && user.address.city;

// [OK] Bon
const city2 = user?.address?.city;

// === ÉVITER COMPARAISONS FAIBLES ===

// [X] Mauvais
if (x == y) {}

// [OK] Bon
if (x === y) {}

// === DESTRUCTURING POUR CLARTÉ ===

// [X] Mauvais
function displayUser(user) {
    console.log(user.name);
    console.log(user.email);
    console.log(user.age);
}

// [OK] Bon
function displayUser({name, email, age}) {
    console.log(name);
    console.log(email);
    console.log(age);
}

// === UTILISER ARRAY METHODS ===

// [X] Mauvais (boucle manuelle)
const doubled = [];
for (let i = 0; i < numbers.length; i++) {
    doubled.push(numbers[i] * 2);
}

// [OK] Bon (map)
const doubled2 = numbers.map(n => n * 2);

// === ÉVITER MUTATIONS ===

// [X] Mauvais (mutable)
function addItem(arr, item) {
    arr.push(item);
    return arr;
}

// [OK] Bon (immutable)
function addItem2(arr, item) {
    return [...arr, item];
}

// === VALIDATION D'ENTRÉE ===

function divide(a, b) {
    // Valider paramètres
    if (typeof a !== 'number' || typeof b !== 'number') {
        throw new TypeError('Les paramètres doivent être des nombres');
    }
    
    if (b === 0) {
        throw new Error('Division par zéro impossible');
    }
    
    return a / b;
}


[OK] DEBUGGING & OUTILS

// === CONSOLE AVANCÉ ===

// Grouper logs
console.group('User Info');
console.log('Name:', user.name);
console.log('Age:', user.age);
console.groupEnd();

// Table pour arrays/objets
console.table([
    {name: 'Alice', age: 25},
    {name: 'Bob', age: 30}
]);

// Assertion
console.assert(age >= 18, 'Âge doit être >= 18');

// Trace de pile
console.trace('Où suis-je?');

// Timer
console.time('operation');
// ... code ...
console.timeEnd('operation');

// === DEBUGGER ===

function buggyFunction(x) {
    debugger;               // Arrête l'exécution ici (DevTools ouvert)
    return x * 2;
}

// === BREAKPOINTS CONDITIONNELS ===

// Dans DevTools: clic droit sur ligne > Edit breakpoint
// Condition: x > 10

// === SOURCE MAPS ===

// Permet de déboguer code transpilé (TypeScript, minifié)
// Activer dans build tools

// === PERFORMANCE PROFILING ===

// Dans DevTools: Performance tab
// Enregistrer, effectuer actions, analyser

// === NETWORK MONITORING ===

// Network tab: voir toutes requêtes HTTP
// Filtrer, inspecter, throttler réseau

// === MEMORY LEAKS ===

// Memory tab: prendre snapshots
// Comparer pour trouver fuites

// Exemple fuite
let leakyArray = [];
setInterval(() => {
    leakyArray.push(new Array(1000000));  // Fuite!
}, 1000);

// === ERROR BOUNDARIES (avec frameworks) ===

class ErrorBoundary extends React.Component {
    state = {hasError: false};
    
    static getDerivedStateFromError(error) {
        return {hasError: true};
    }
    
    componentDidCatch(error, info) {
        console.error('Error:', error, info);
    }
    
    render() {
        if (this.state.hasError) {
            return <h1>Quelque chose s'est mal passé</h1>;
        }
        return this.props.children;
    }
}

// === LINTERS & FORMATTERS ===

// ESLint - détection erreurs
// npm install --save-dev eslint

// Prettier - formatage code
// npm install --save-dev prettier

// === TYPES AVEC JSDOC ===

/**
 * @typedef {Object} User
 * @property {string} name
 * @property {number} age
 * @property {string} email
 */

/**
 * @param {User} user
 * @returns {string}
 */
function formatUser(user) {
    return `${user.name} (${user.age})`;
}


[OK] SÉCURITÉ

// === XSS (Cross-Site Scripting) ===

// [X] DANGER (XSS vulnérable)
element.innerHTML = userInput;      // Ne JAMAIS faire!

// [OK] Sûr
element.textContent = userInput;    // Échappe HTML automatiquement

// Nettoyer HTML si absolument nécessaire
function escapeHtml(text) {
    const map = {
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        '"': '&quot;',
        "'": '&#039;'
    };
    return text.replace(/[&<>"']/g, m => map[m]);
}

// Utiliser bibliothèque: DOMPurify
// const clean = DOMPurify.sanitize(dirty);

// === CSRF (Cross-Site Request Forgery) ===

// Inclure token CSRF dans requêtes
fetch('/api/action', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Token': csrfToken
    },
    body: JSON.stringify(data)
});

// === VALIDATION CÔTÉ CLIENT & SERVEUR ===

// [ATTENTION] Validation client n'est PAS suffisante!
// TOUJOURS valider côté serveur aussi

function validateEmail(email) {
    const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return regex.test(email);
}

// === DONNÉES SENSIBLES ===

// [X] NE JAMAIS stocker en localStorage/sessionStorage
localStorage.setItem('password', userPassword);     // DANGER!
localStorage.setItem('apiKey', apiKey);             // DANGER!

// [OK] Utiliser cookies httpOnly (côté serveur)
// [OK] Utiliser sessions serveur
// [OK] Tokens JWT avec expiration courte

// === INJECTIONS ===

// [X] Injection SQL (si construit côté client)
const query = `SELECT * FROM users WHERE id = ${userId}`;  // DANGER!

// [OK] Utiliser paramètres préparés (côté serveur)
// [OK] Valider et nettoyer entrées

// === CONTENT SECURITY POLICY (CSP) ===

// Dans HTML head
// <meta http-equiv="Content-Security-Policy" 
//       content="default-src 'self'; script-src 'self' 'unsafe-inline'">

// === HTTPS SEULEMENT ===

// Forcer HTTPS
if (location.protocol !== 'https:') {
    location.href = 'https:' + window.location.href.substring(window.location.protocol.length);
}

// === ÉVITER eval() ===

// [X] DANGER
eval(userInput);                    // Ne JAMAIS faire!
new Function(userInput)();          // Ne JAMAIS faire!
setTimeout(userInput, 1000);        // Ne JAMAIS faire!

// [OK] Alternatives sûres
const data = JSON.parse(jsonString);

// === CORS (Cross-Origin Resource Sharing) ===

// Côté serveur: configurer headers CORS
// Access-Control-Allow-Origin: https://example.com

// Côté client: credentials si cookies nécessaires
fetch('https://api.example.com/data', {
    credentials: 'include'
});

// === RATE LIMITING ===

// Limiter requêtes API (côté client: UI feedback)
class RateLimiter {
    constructor(maxRequests, timeWindow) {
        this.maxRequests = maxRequests;
        this.timeWindow = timeWindow;
        this.requests = [];
    }
    
    canMakeRequest() {
        const now = Date.now();
        this.requests = this.requests.filter(
            time => now - time < this.timeWindow
        );
        
        if (this.requests.length < this.maxRequests) {
            this.requests.push(now);
            return true;
        }
        return false;
    }
}

const limiter = new RateLimiter(5, 60000); // 5 req/minute

if (limiter.canMakeRequest()) {
    // Faire requête
} else {
    console.log('Trop de requêtes');
}

// === SÉCURISER DONNÉES UTILISATEUR ===

// Ne jamais logger données sensibles
console.log('User:', {
    name: user.name,
    // password: user.password,    // [X] JAMAIS
    // creditCard: user.card        // [X] JAMAIS
});

// === DEPENDENCIES SÉCURISÉES ===

// Vérifier vulnérabilités avec npm audit
// npm audit
// npm audit fix


[OK] TESTING

// === TESTS MANUELS ===

// Assertions simples
function assertEquals(actual, expected, message) {
    if (actual !== expected) {
        throw new Error(`${message}: attendu ${expected}, reçu ${actual}`);
    }
    console.log(`[OK] ${message}`);
}

// Tests
function testAddition() {
    assertEquals(addition(2, 3), 5, 'addition(2, 3)');
    assertEquals(addition(-1, 1), 0, 'addition(-1, 1)');
}

testAddition();

// === JEST (framework populaire) ===

// Installation: npm install --save-dev jest

// fichier: sum.test.js
describe('fonction addition', () => {
    test('additionne 2 + 3 = 5', () => {
        expect(addition(2, 3)).toBe(5);
    });
    
    test('additionne nombres négatifs', () => {
        expect(addition(-1, -1)).toBe(-2);
    });
    
    test('gère zéro', () => {
        expect(addition(0, 5)).toBe(5);
    });
});

// === MATCHERS JEST ===

// Égalité
expect(value).toBe(5);                      // Égalité stricte
expect(obj).toEqual({a: 1});                // Deep equality

// Vérités
expect(value).toBeTruthy();
expect(value).toBeFalsy();
expect(value).toBeNull();
expect(value).toBeUndefined();
expect(value).toBeDefined();

// Nombres
expect(value).toBeGreaterThan(3);
expect(value).toBeGreaterThanOrEqual(3.5);
expect(value).toBeLessThan(5);
expect(value).toBeCloseTo(0.3);             // Floats

// Strings
expect(str).toMatch(/pattern/);
expect(str).toContain('substring');

// Arrays
expect(arr).toContain(item);
expect(arr).toHaveLength(3);

// Objets
expect(obj).toHaveProperty('name');
expect(obj).toHaveProperty('age', 25);

// Exceptions
expect(() => {
    throw new Error('erreur');
}).toThrow();
expect(() => fn()).toThrow('message');

// === ASYNC TESTS ===

// Avec async/await
test('données async', async () => {
    const data = await fetchData();
    expect(data).toBe('valeur');
});

// Avec promises
test('données promise', () => {
    return fetchData().then(data => {
        expect(data).toBe('valeur');
    });
});

// === MOCK FUNCTIONS ===

// Créer mock
const mockFn = jest.fn();
mockFn('arg1');
expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledWith('arg1');
expect(mockFn).toHaveBeenCalledTimes(1);

// Mock avec implémentation
const mockAdd = jest.fn((a, b) => a + b);
expect(mockAdd(1, 2)).toBe(3);

// Mock valeur de retour
mockFn.mockReturnValue(42);
mockFn.mockReturnValueOnce(1).mockReturnValueOnce(2);

// Mock promise
mockFn.mockResolvedValue('data');
mockFn.mockRejectedValue(new Error('erreur'));

// === MOCK MODULES ===

// Mock fetch
global.fetch = jest.fn(() =>
    Promise.resolve({
        json: () => Promise.resolve({data: 'test'})
    })
);

// Reset mock
fetch.mockClear();

// === SETUP & TEARDOWN ===

beforeAll(() => {
    // Avant tous les tests
    console.log('Setup global');
});

afterAll(() => {
    // Après tous les tests
    console.log('Cleanup global');
});

beforeEach(() => {
    // Avant chaque test
});

afterEach(() => {
    // Après chaque test
});

// === TEST COVERAGE ===

// Exécuter avec coverage
// npm test -- --coverage

// Viser 80%+ de couverture


[OK] ES6+ FEATURES MODERNES

// === BIGINT ===

const bigNumber = 9007199254740991n;
const bigInt = BigInt('9007199254740991');

console.log(bigNumber + 1n);        // Opérations avec n

// === NUMERIC SEPARATORS ===

const million = 1_000_000;
const billion = 1_000_000_000;
const bytes = 0b1010_0001_1000_0101;

// === PRIVATE CLASS FIELDS ===

class Counter {
    #count = 0;                     // Privé
    
    increment() {
        this.#count++;
    }
    
    get value() {
        return this.#count;
    }
}

const counter = new Counter();
// console.log(counter.#count);    // [X] Erreur!
console.log(counter.value);         // [OK] Via getter

// === STATIC INITIALIZATION BLOCKS ===

class MyClass {
    static value;
    
    static {
        // Initialisation statique
        this.value = Math.random() > 0.5 ? 'A' : 'B';
    }
}

// === LOGICAL ASSIGNMENT ===

let x = 0;

// &&= - assigne si truthy
x &&= 10;                           // x reste 0

// ||= - assigne si falsy
x ||= 10;                           // x devient 10

// ??= - assigne si null/undefined
let y;
y ??= 5;                            // y devient 5

// === STRING.REPLACEALL() ===

const text = 'hello world hello';
const newText = text.replaceAll('hello', 'hi');
// 'hi world hi'

// === PROMISE.ANY() ===

const promises = [
    Promise.reject('error1'),
    Promise.resolve('success'),
    Promise.resolve('success2')
];

Promise.any(promises)
    .then(result => console.log(result))    // 'success' (premier succès)
    .catch(error => console.log(error));    // Si tous échouent

// === WEAKREF & FINALIZATIONREGISTRY ===

// Référence faible (garbage collection possible)
let obj = {data: 'important'};
const weakRef = new WeakRef(obj);

// Plus tard
const deref = weakRef.deref();
if (deref) {
    console.log(deref.data);
}

// FinalizationRegistry - callback quand objet GC
const registry = new FinalizationRegistry((heldValue) => {
    console.log(`Object ${heldValue} was garbage collected`);
});

// === TOP-LEVEL AWAIT (modules) ===

// Dans un module ES6
// const data = await fetch('url').then(r => r.json());

// === IMPORT.META ===

// Dans module
console.log(import.meta.url);       // URL du module actuel

// === ARRAY.AT() ===

const arr = [1, 2, 3, 4, 5];
console.log(arr.at(0));             // 1
console.log(arr.at(-1));            // 5 (dernier)
console.log(arr.at(-2));            // 4

// === OBJECT.HASOWN() ===

const obj = {prop: 'value'};
console.log(Object.hasOwn(obj, 'prop'));        // true (moderne)
console.log(obj.hasOwnProperty('prop'));        // true (ancien)

// === ERROR.CAUSE ===

try {
    throw new Error('Outer error', {
        cause: new Error('Inner error')
    });
} catch (error) {
    console.log(error.message);     // 'Outer error'
    console.log(error.cause);       // Error: Inner error
}


[OK] WEB APIs UTILES

// === GEOLOCATION ===

if ('geolocation' in navigator) {
    navigator.geolocation.getCurrentPosition(
        (position) => {
            console.log('Lat:', position.coords.latitude);
            console.log('Lon:', position.coords.longitude);
        },
        (error) => {
            console.error('Erreur:', error.message);
        }
    );
}

// Suivre position
const watchId = navigator.geolocation.watchPosition(
    position => console.log(position.coords)
);

// Arrêter suivi
navigator.geolocation.clearWatch(watchId);

// === NOTIFICATIONS ===

if ('Notification' in window) {
    // Demander permission
    Notification.requestPermission().then(permission => {
        if (permission === 'granted') {
            new Notification('Titre', {
                body: 'Message de notification',
                icon: '/icon.png',
                tag: 'unique-id'
            });
        }
    });
}

// === CLIPBOARD API ===

// Copier
navigator.clipboard.writeText('Texte à copier')
    .then(() => console.log('Copié!'))
    .catch(err => console.error('Erreur:', err));

// Lire
navigator.clipboard.readText()
    .then(text => console.log('Presse-papier:', text));

// === VIBRATION API ===

if ('vibrate' in navigator) {
    // Vibrer 200ms
    navigator.vibrate(200);
    
    // Pattern: vibrer 200ms, pause 100ms, vibrer 200ms
    navigator.vibrate([200, 100, 200]);
}

// === BATTERY API ===

if ('getBattery' in navigator) {
    navigator.getBattery().then(battery => {
        console.log('Niveau:', battery.level * 100 + '%');
        console.log('En charge:', battery.charging);
        console.log('Temps restant:', battery.dischargingTime);
        
        // Événements
        battery.addEventListener('levelchange', () => {
            console.log('Niveau changé:', battery.level);
        });
    });
}

// === FULLSCREEN API ===

const elem = document.documentElement;

// Activer plein écran
if (elem.requestFullscreen) {
    elem.requestFullscreen();
} else if (elem.webkitRequestFullscreen) {
    elem.webkitRequestFullscreen();
}

// Quitter plein écran
if (document.exitFullscreen) {
    document.exitFullscreen();
}

// Vérifier si plein écran
console.log(document.fullscreenElement);

// === PAGE VISIBILITY API ===

document.addEventListener('visibilitychange', () => {
    if (document.hidden) {
        console.log('Page cachée');
        // Pause vidéo, arrêter timers, etc.
    } else {
        console.log('Page visible');
        // Reprendre
    }
});

// === INTERSECTION OBSERVER ===

// Détecter quand élément entre dans viewport
const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            console.log('Élément visible:', entry.target);
            // Charger image, animer, etc.
        }
    });
}, {
    threshold: 0.5,                 // 50% visible
    rootMargin: '0px'
});

// Observer élément
const element = document.querySelector('.lazy-load');
observer.observe(element);

// === RESIZE OBSERVER ===

const resizeObserver = new ResizeObserver(entries => {
    entries.forEach(entry => {
        console.log('Nouvelle taille:', entry.contentRect);
    });
});

resizeObserver.observe(document.querySelector('.element'));

// === MUTATION OBSERVER ===

// Observer changements DOM
const mutationObserver = new MutationObserver(mutations => {
    mutations.forEach(mutation => {
        console.log('Mutation:', mutation.type);
        console.log('Nœuds ajoutés:', mutation.addedNodes);
        console.log('Nœuds retirés:', mutation.removedNodes);
    });
});

mutationObserver.observe(document.body, {
    childList: true,                // Enfants ajoutés/retirés
    attributes: true,               // Attributs modifiés
    characterData: true,            // Contenu texte modifié
    subtree: true                   // Observer descendants
});

// === WEB STORAGE API ===

// Capacité disponible
if ('storage' in navigator && 'estimate' in navigator.storage) {
    navigator.storage.estimate().then(estimate => {
        console.log('Utilisé:', estimate.usage);
        console.log('Quota:', estimate.quota);
    });
}

// === NETWORK INFORMATION API ===

if ('connection' in navigator) {
    const connection = navigator.connection;
    console.log('Type:', connection.effectiveType);  // '4g', '3g', etc.
    console.log('Downlink:', connection.downlink);   // Mbps
    console.log('RTT:', connection.rtt);             // ms
    
    connection.addEventListener('change', () => {
        console.log('Connexion changée');
    });
}

// === SHARE API ===

if (navigator.share) {
    navigator.share({
        title: 'Titre',
        text: 'Description',
        url: 'https://example.com'
    })
    .then(() => console.log('Partagé'))
    .catch(err => console.error('Erreur:', err));
}


[OK] TRICKS & RACCOURCIS

// === CONVERTIR NODELIST EN ARRAY ===

const divs = document.querySelectorAll('div');

// Méthode 1: spread
const arr1 = [...divs];

// Méthode 2: Array.from
const arr2 = Array.from(divs);

// === VIDER UN ARRAY ===

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

// Méthode 1
arr.length = 0;

// Méthode 2
arr.splice(0);

// Méthode 3
arr = [];

// === COPIE SUPERFICIELLE RAPIDE ===

const obj = {a: 1, b: 2};
const copy1 = {...obj};
const copy2 = Object.assign({}, obj);

const arr3 = [1, 2, 3];
const copyArr1 = [...arr3];
const copyArr2 = arr3.slice();

// === INVERSER STRING ===

const str = 'hello';
const reversed = str.split('').reverse().join('');

// === VÉRIFIER SI ARRAY EST VIDE ===

const isEmpty = arr.length === 0;
const isEmpty2 = !arr.length;

// === OBTENIR PARAMÈTRES URL ===

// URL: https://example.com?name=Alice&age=25

const params = new URLSearchParams(window.location.search);
console.log(params.get('name'));    // 'Alice'
console.log(params.get('age'));     // '25'

// Tous les paramètres
for (const [key, value] of params) {
    console.log(key, value);
}

// === REDIRECTION ===

// Méthode 1 (avec historique)
window.location.href = 'https://example.com';

// Méthode 2 (remplace dans historique)
window.location.replace('https://example.com');

// Méthode 3 (recharger)
window.location.reload();

// === DÉTECTER MODE SOMBRE ===

const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;

// Écouter changements
window.matchMedia('(prefers-color-scheme: dark)')
    .addEventListener('change', e => {
        console.log('Dark mode:', e.matches);
    });

// === DÉTECTER DEVICE MOBILE ===

const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);

// === SCROLL TO TOP ===

// Instantané
window.scrollTo(0, 0);

// Smooth
window.scrollTo({
    top: 0,
    behavior: 'smooth'
});

// Scroll vers élément
element.scrollIntoView({behavior: 'smooth'});

// === COPIER OBJET AVEC MODIFICATIONS ===

const user = {name: 'Alice', age: 25, city: 'Paris'};

// Changer une propriété
const updated = {...user, age: 26};

// Ajouter propriété
const withEmail = {...user, email: 'alice@example.com'};

// === GÉNÉRER ID UNIQUE ===

const uniqueId = () => Date.now().toString(36) + Math.random().toString(36).substr(2);

// Ou avec crypto (meilleur)
const uuid = crypto.randomUUID();   // '123e4567-e89b-12d3-a456-426614174000'

// === ATTENDRE CHARGEMENT DOM ===

// Méthode 1
document.addEventListener('DOMContentLoaded', () => {
    // DOM prêt
});

// Méthode 2 (si script en fin de body)
// Pas besoin d'attendre

// Méthode 3 (moderne - avec defer)
// <script src="script.js" defer></script>

// === DOWNLOAD FILE ===

function downloadFile(data, filename, type) {
    const blob = new Blob([data], {type});
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = filename;
    a.click();
    URL.revokeObjectURL(url);
}

// Utilisation
downloadFile('Hello World', 'file.txt', 'text/plain');

// === PRINT PAGE ===

window.print();

// === CHAINING OPTIONAL ===

const value = obj?.prop1?.prop2?.prop3 ?? 'default';


[OK] RESSOURCES & NEXT STEPS

// === DOCUMENTATION ===

// MDN Web Docs (MEILLEURE ressource)
// https://developer.mozilla.org/fr/docs/Web/JavaScript

// JavaScript.info (tutoriels excellents)
// https://javascript.info/

// === PRATIQUE ===

// Exercices:
// - Codewars (https://www.codewars.com/)
// - LeetCode (https://leetcode.com/)
// - HackerRank (https://www.hackerrank.com/)
// - freeCodeCamp (https://www.freecodecamp.org/)

// === FRAMEWORKS & BIBLIOTHÈQUES ===

// Frontend:
// - React: UI components
// - Vue.js: progressive framework
// - Angular: full framework
// - Svelte: compile-time framework

// Backend (Node.js):
// - Express: web framework
// - NestJS: TypeScript framework
// - Fastify: fast framework

// Utilitaires:
// - Lodash: utility functions
// - Axios: HTTP client
// - Day.js: date manipulation
// - Zod: validation schemas

// === OUTILS ===

// Build tools:
// - Vite: modern build tool
// - Webpack: bundler
// - Rollup: module bundler

// Package managers:
// - npm: default
// - yarn: alternative
// - pnpm: fast alternative

// === CONCEPTS AVANCÉS À APPRENDRE ===

// 1. TypeScript (typage statique)
// 2. Node.js (JavaScript côté serveur)
// 3. REST APIs (communication client-serveur)
// 4. GraphQL (alternative à REST)
// 5. WebSockets (temps réel)
// 6. Service Workers (PWA)
// 7. WebAssembly (performance)
// 8. Functional Programming (paradigme)
// 9. Design Patterns (architecture)
// 10. Testing (Jest, Vitest, Cypress)

// === LIVRES RECOMMANDÉS ===

// - "You Don't Know JS" (Kyle Simpson)
// - "Eloquent JavaScript" (Marijn Haverbeke)
// - "JavaScript: The Good Parts" (Douglas Crockford)
// - "Clean Code" (Robert C. Martin)

// === COMMUNAUTÉS ===

// - Stack Overflow
// - Reddit: r/javascript, r/webdev
// - Discord: serveurs dev
// - Twitter/X: #JavaScript

// === VEILLE TECHNOLOGIQUE ===

// Newsletters:
// - JavaScript Weekly
// - Frontend Focus
// - Node Weekly

// Blogs:
// - CSS-Tricks
// - Smashing Magazine
// - Dev.to


[OK] GLOSSAIRE

// API - Application Programming Interface
// Ensemble de fonctions pour interagir avec un service

// Asynchrone - Opération qui ne bloque pas l'exécution
// Exemple: fetch, setTimeout

// Callback - Fonction passée en argument à une autre fonction

// Closure - Fonction qui a accès aux variables de sa fonction parente

// DOM - Document Object Model
// Représentation de la structure HTML en JavaScript

// Event Loop - Mécanisme gérant l'exécution asynchrone

// Hoisting - Remontée des déclarations en haut du scope

// Immutable - Qui ne peut pas être modifié

// JSON - JavaScript Object Notation
// Format d'échange de données

// Promise - Objet représentant une opération asynchrone future

// Prototype - Mécanisme d'héritage en JavaScript

// REST - Representational State Transfer
// Style d'architecture pour APIs web

// Scope - Portée des variables

// Syntax Sugar - Syntaxe plus simple pour écrire du code

// Transpiler - Convertit code moderne en code compatible
// Exemple: Babel (ES6+ -> ES5)

// Truthy/Falsy - Valeurs considérées comme true/false

// Vanilla JS - JavaScript pur (sans framework)

// === FIN DU GUIDE ===

// [BRAVO] Félicitations! Vous avez maintenant une base solide en JavaScript!

// Prochaines étapes:
// 1. Pratiquer en construisant des projets
// 2. Lire le code d'autres développeurs
// 3. Contribuer à des projets open source
// 4. Continuer à apprendre et rester curieux!

// Bon codage! [CODE]*


[OK] INDEX RAPIDE DES CONCEPTS

// BASICS
// - Variables: let, const, var
// - Types: string, number, boolean, null, undefined, symbol
// - Opérateurs: +, -, *, /, %, **, ++, --, ===, !==
// - Conditions: if, else, switch, ternaire
// - Boucles: for, while, do-while, for...of, for...in

// FONCTIONS
// - Déclaration: function name() {}
// - Expression: const fn = function() {}
// - Arrow: const fn = () => {}
// - Paramètres: defaults, rest (...)
// - Return, closures, récursion

// OBJETS & ARRAYS
// - Création: {}, []
// - Accès: dot notation, bracket notation
// - Méthodes: push, pop, map, filter, reduce
// - Destructuring: {a, b} = obj, [x, y] = arr
// - Spread: {...obj}, [...arr]

// DOM
// - Sélection: querySelector, getElementById
// - Manipulation: innerHTML, textContent, style
// - Événements: addEventListener, click, input
// - Création: createElement, appendChild

// ASYNC
// - Callbacks: setTimeout, setInterval
// - Promises: then, catch, finally
// - Async/Await: async function, await
// - Fetch: requêtes HTTP

// MODERNE
// - Classes: class, extends, constructor
// - Modules: import, export
// - Optional chaining: ?.
// - Nullish coalescing: ??
// - Template literals: `${}`

// AVANCÉ
// - Regex: /pattern/flags
// - Map, Set, WeakMap, WeakSet
// - Proxy, Reflect
// - Symbols, Generators
// - Web APIs: Geolocation, Notifications, etc.