// ============================================================================
// [LIVRE] TYPESCRIPT - PARTIE 1 : FONDAMENTAUX
// ============================================================================
//
// [OBJECTIF] CETTE PARTIE COUVRE :
// - Chapitre 0 : Introduction à TypeScript
// - Chapitre 1 : Types de Base
// - Chapitre 2 : Fonctions Typées
// - Chapitre 3 : Interfaces et Types
//
// [TEMPS] TEMPS : ~6-8 heures
// [DOCS] PRÉREQUIS : JavaScript de base (variables, fonctions, objets)
// ============================================================================


// ============================================================================
// [GUIDE] CHAPITRE 0 : INTRODUCTION À TYPESCRIPT
// ============================================================================

/*
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Ce qu'est TypeScript et pourquoi l'utiliser
[OK] La différence entre TypeScript et JavaScript
[OK] Installer et configurer TypeScript
[OK] Compiler du TypeScript en JavaScript
[OK] Comprendre le système de types statiques
*/


// ----------------------------------------------------------------------------
// [REFLEXION] QU'EST-CE QUE TYPESCRIPT ?
// ----------------------------------------------------------------------------

/*
DÉFINITION SIMPLE

TypeScript = JavaScript + Types Statiques + Fonctionnalités Modernes

TypeScript est un sur-ensemble (superset) de JavaScript développé par Microsoft.
Il ajoute un SYSTÈME DE TYPES STATIQUES au JavaScript dynamique.

[IDEE] SUPER-ENSEMBLE signifie :
- Tout code JavaScript valide est aussi du TypeScript valide
- TypeScript ajoute des fonctionnalités au-dessus de JavaScript
- TypeScript se compile en JavaScript (transpilation)
- Le navigateur n'exécute JAMAIS du TypeScript directement


ANALOGIE SIMPLE [CONSTRUCTION]

JavaScript = Voiture sans tableau de bord
-> Elle roule, mais vous ne savez pas :
  - La vitesse (types)
  - Le carburant restant (valeurs nulles)
  - Les erreurs moteur (bugs silencieux)

TypeScript = Voiture avec tableau de bord complet
-> Elle roule avec des INDICATEURS :
  - Alertes immédiatement si quelque chose est faux
  - Autocomplétion intelligente
  - Refactoring en sécurité


PROBLÈME CONCRET QUE TYPESCRIPT RÉSOUT
*/

// [X] JAVASCRIPT : Bugs silencieux découverts à l'exécution
function addJavaScript(a, b) {
  return a + b;
}

console.log(addJavaScript(5, 3));        // 8 [OK]
console.log(addJavaScript("5", 3));      // "53" [X] Bug ! Pas d'erreur détectée
console.log(addJavaScript(5, undefined)); // NaN [X] Bug ! Pas d'erreur détectée

// [OK] TYPESCRIPT : Bugs détectés À LA COMPILATION
function addTypeScript(a: number, b: number): number {
  return a + b;
}

// addTypeScript("5", 3);      // [X] ERREUR à la compilation : 'string' n'est pas 'number'
// addTypeScript(5, undefined); // [X] ERREUR à la compilation : 'undefined' n'est pas 'number'
addTypeScript(5, 3);           // [OK] OK : 8

/*
[IDEE] CYCLE DE DÉVELOPPEMENT

SANS TypeScript :
1. Écrire code
2. Lancer l'application
3. Voir le bug (peut-être en production !)
4. Debugger
5. Corriger

AVEC TypeScript :
1. Écrire code
2. ERREUR IMMÉDIATE dans l'éditeur <-── Beaucoup plus tôt !
3. Corriger

[TEMPS] Coût d'un bug en production = 100x coût d'un bug au développement !
*/


// ----------------------------------------------------------------------------
// [RECHERCHE] TYPESCRIPT VS JAVASCRIPT
// ----------------------------------------------------------------------------

/*
TABLEAU COMPARATIF

┌─────────────────────┬──────────────────────┬──────────────────────┐
│                     │    JAVASCRIPT        │    TYPESCRIPT        │
├─────────────────────┼──────────────────────┼──────────────────────┤
│ Types               │ Dynamiques (runtime) │ Statiques (compile)  │
│ Extension fichier   │ .js                  │ .ts                  │
│ Exécution           │ Directe              │ Compilation -> .js    │
│ Erreurs             │ À l'exécution        │ À la compilation     │
│ IDE Support         │ Basique              │ Excellent            │
│ Autocomplétion      │ Limitée              │ Très précise         │
│ Refactoring         │ Risqué               │ Sûr                  │
│ Documentation       │ Commentaires         │ Types = docs vivantes│
│ Courbe              │ Plus facile départ   │ Plus facile long term│
└─────────────────────┴──────────────────────┴──────────────────────┘


QUAND UTILISER TYPESCRIPT ?

[OK] UTILISEZ TypeScript pour :
- Projets en équipe (collaboration)
- Applications moyennes à grandes
- APIs et backends
- Bibliothèques partagées
- Long terme / maintenabilité
- React, Angular, Vue (tous supportent TS)
- Node.js backends

[ATTENTION] PEUT-ÊTRE PAS NÉCESSAIRE pour :
- Petits scripts une fois
- Prototypes ultra-rapides
- Projets de 50 lignes max
*/


// ----------------------------------------------------------------------------
// [OUTILS] INSTALLATION ET CONFIGURATION
// ----------------------------------------------------------------------------

/*
ÉTAPE 1 : INSTALLER NODE.JS
---------------------------
https://nodejs.org/
Vérifier : node --version
           npm --version


ÉTAPE 2 : INSTALLER TYPESCRIPT
-------------------------------
*/

// Terminal
// npm install -g typescript           <- Global (disponible partout)
// npm install --save-dev typescript   <- Local au projet (recommandé)

// Vérifier l'installation
// tsc --version
// -> Version 5.x.x

/*
ÉTAPE 3 : CRÉER UN PROJET
--------------------------
*/

// Structure recommandée
/*
mon-projet/
├── src/          <- Code TypeScript source
│   ├── index.ts
│   └── utils.ts
├── dist/         <- JavaScript compilé (généré)
├── node_modules/ <- Dépendances
├── tsconfig.json <- Configuration TypeScript
└── package.json  <- Métadonnées projet
*/

/*
ÉTAPE 4 : INITIALISER TYPESCRIPT
---------------------------------
*/

// Terminal
// tsc --init
// -> Crée tsconfig.json avec options commentées

/*
TSCONFIG.JSON MINIMAL POUR DÉBUTANT
*/

// tsconfig.json
const tsconfigExample = {
  "compilerOptions": {
    "target": "ES2020",          // Version JavaScript de sortie
    "module": "commonjs",        // Système de modules
    "outDir": "./dist",          // Dossier de sortie
    "rootDir": "./src",          // Dossier source
    "strict": true,              // Mode strict (recommandé)
    "esModuleInterop": true,     // Compatibilité modules
    "skipLibCheck": true,        // Ignorer types des libs
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],       // Fichiers à compiler
  "exclude": ["node_modules"]    // Fichiers à ignorer
};

/*
ÉTAPE 5 : COMPILER
------------------
*/

// Terminal
// tsc                <- Compile tout le projet
// tsc --watch        <- Recompile automatiquement
// tsc index.ts       <- Compile un seul fichier
// ts-node index.ts   <- Exécuter sans compiler (développement)

// Installer ts-node pour dev
// npm install --save-dev ts-node


// ----------------------------------------------------------------------------
// [IDEE] PREMIÈRE PROGRAMME TYPESCRIPT
// ----------------------------------------------------------------------------

// src/index.ts

// Déclaration avec type explicite
const message: string = "Bonjour TypeScript !";
const year: number = 2024;
const isActive: boolean = true;

console.log(`${message} Année: ${year}`);

// TypeScript infère les types automatiquement aussi !
const autoMessage = "TypeScript infère que c'est une string";
// autoMessage = 42; // [X] Erreur ! Type 'number' n'est pas assignable à 'string'

/*
[IDEE] TYPE INFERENCE

TypeScript est assez intelligent pour DÉDUIRE le type
sans que vous l'écriviez explicitement !

const x = 5;        -> TypeScript sait que x est number
const y = "hello";  -> TypeScript sait que y est string
const z = true;     -> TypeScript sait que z est boolean

QUAND ÉCRIRE LES TYPES EXPLICITEMENT ?
[OK] Paramètres de fonctions (obligatoire)
[OK] Valeurs de retour de fonctions (recommandé)
[OK] Variables avec valeur initiale complexe
[OK] Quand l'inférence est ambiguë
[X] Variables simples avec valeur initiale claire (superflu)
*/


// ============================================================================
// [GUIDE] CHAPITRE 1 : TYPES DE BASE
// ============================================================================

/*
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Utiliser tous les types primitifs
[OK] Typer les tableaux et objets
[OK] Comprendre null, undefined, any, unknown, never, void
[OK] Utiliser les assertions de type
[OK] Comprendre les types littéraux
*/


// ----------------------------------------------------------------------------
// [NOMBRE] TYPES PRIMITIFS
// ----------------------------------------------------------------------------

/*
LES 8 TYPES PRIMITIFS DE TYPESCRIPT
*/

// 1. NUMBER ─────────────────────────────
const age: number = 25;
const price: number = 19.99;
const hex: number = 0xff;        // Hexadécimal
const binary: number = 0b1010;   // Binaire
const octal: number = 0o744;     // Octal
const big: number = 1_000_000;   // Séparateur (lisibilité)

/*
[IDEE] DIFFÉRENCE AVEC JAVASCRIPT :
TypeScript a UN SEUL type 'number' (pas de int, float séparés)
Pour les très grands nombres : BigInt
*/
const bigNumber: bigint = 9007199254740993n; // BigInt

// 2. STRING ─────────────────────────────
const firstName: string = "Alice";
const lastName: string = 'Martin';
const fullName: string = `${firstName} ${lastName}`; // Template literal

// 3. BOOLEAN ────────────────────────────
const isLoggedIn: boolean = true;
const hasPermission: boolean = false;

// 4. NULL ───────────────────────────────
let nullable: null = null;

/*
[IDEE] null vs undefined EN TYPESCRIPT

null    = Valeur intentionnellement VIDE (vous l'avez mis à null)
undefined = Variable déclarée mais PAS de valeur assignée

Exemple concret :
*/
let username: string | null = null;  // Pas encore de nom (intentionnel)
let score: number | undefined;       // Score pas encore calculé

// 5. UNDEFINED ──────────────────────────
let notDefined: undefined = undefined;

// 6. SYMBOL ─────────────────────────────
const uniqueKey: symbol = Symbol("key");
const anotherKey: symbol = Symbol("key");
// uniqueKey === anotherKey -> false ! Chaque Symbol est unique

// 7. BIGINT ─────────────────────────────
const maxSafe: bigint = BigInt(Number.MAX_SAFE_INTEGER);

// 8. VOID ───────────────────────────────
// Utilisé pour les fonctions qui ne retournent rien
function logMessage(msg: string): void {
  console.log(msg);
  // pas de return avec valeur
}


// ----------------------------------------------------------------------------
// [TEXTE] TYPES SPÉCIAUX TYPESCRIPT
// ----------------------------------------------------------------------------

/*
ANY : Le "joker" (à éviter !)
*/

let anything: any = "Hello";
anything = 42;          // OK (any accepte tout)
anything = true;        // OK
anything = { x: 1 };   // OK
anything.foo.bar;       // OK (pas d'erreur, mais dangereux !)
anything();             // OK (pas d'erreur, même si pas callable !)

/*
[ATTENTION] POURQUOI ÉVITER ANY ?
- Désactive entièrement la vérification de types
- Vous perdez tous les avantages de TypeScript
- C'est comme retourner au JavaScript brut

[OK] QUAND UTILISER ANY (légitimement) :
- Migration progressive de JS vers TS
- Code vraiment dynamique de bibliothèques tierces
- Prototype ultra-rapide (à remplacer ensuite)

[IDEE] ALTERNATIVE RECOMMANDÉE : unknown
*/


/*
UNKNOWN : Le "any sûr"
*/

let userInput: unknown;
userInput = 5;
userInput = "hello";
userInput = true;

// Pour utiliser, vous DEVEZ vérifier le type d'abord
if (typeof userInput === "string") {
  console.log(userInput.toUpperCase()); // [OK] OK, TypeScript sait que c'est string ici
}

// Sans vérification :
// console.log(userInput.toUpperCase()); // [X] Erreur ! Type unknown

/*
[IDEE] DIFFÉRENCE any vs unknown

any     = "Je me fous des types, laisse-moi faire n'importe quoi"
unknown = "Je ne sais pas le type POUR L'INSTANT, mais je vais vérifier avant d'utiliser"

-> Utilisez TOUJOURS unknown plutôt que any quand possible
*/


/*
NEVER : L'impossible
*/

// Une fonction qui ne retourne JAMAIS (lance toujours une erreur)
function throwError(message: string): never {
  throw new Error(message);
}

// Une boucle infinie
function infiniteLoop(): never {
  while (true) {}
}

// Utilisé dans les vérifications exhaustives
type Direction = "nord" | "sud" | "est" | "ouest";

function handleDirection(dir: Direction): string {
  switch (dir) {
    case "nord": return "Aller au nord";
    case "sud":  return "Aller au sud";
    case "est":  return "Aller à l'est";
    case "ouest": return "Aller à l'ouest";
    default:
      // Si TypeScript arrive ici, il y a un cas non géré
      const exhaustiveCheck: never = dir;
      return exhaustiveCheck;
  }
}

/*
[IDEE] QUAND UTILISE-T-ON NEVER ?
- Type de retour des fonctions qui lancent TOUJOURS des erreurs
- Type de retour des boucles infinies
- Vérification d'exhaustivité des switch/if
- Résultat de types impossibles : string & number = never
*/


// ----------------------------------------------------------------------------
// [PACKAGE] ARRAYS (TABLEAUX)
// ----------------------------------------------------------------------------

/*
DEUX SYNTAXES POUR LES TABLEAUX
*/

// Syntaxe 1 : Type[]
const numbers: number[] = [1, 2, 3, 4, 5];
const names: string[] = ["Alice", "Bob", "Charlie"];
const flags: boolean[] = [true, false, true];

// Syntaxe 2 : Array<Type> (générique)
const scores: Array<number> = [95, 87, 92];
const cities: Array<string> = ["Paris", "Lyon", "Marseille"];

/*
[IDEE] QUELLE SYNTAXE CHOISIR ?
- Type[]        -> Plus courante, plus lisible, recommandée
- Array<Type>   -> Utile avec les génériques complexes
Cohérence > tout : choisissez-en une et gardez-la !
*/

// Tableaux en lecture seule
const CONSTANTS: readonly number[] = [1, 2, 3];
const IMMUTABLE: ReadonlyArray<string> = ["a", "b", "c"];
// CONSTANTS.push(4); // [X] Erreur ! Array en lecture seule

// Tableau multidimensionnel
const matrix: number[][] = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
];

// Tableau de mixed types (avec union)
const mixed: (string | number)[] = ["Alice", 25, "Bob", 30];


// ----------------------------------------------------------------------------
// [ICE_CUBE] TUPLES
// ----------------------------------------------------------------------------

/*
TUPLE = Tableau avec longueur et types FIXES à chaque position
*/

// Déclaration d'un tuple
const person: [string, number] = ["Alice", 25];
// person[0] est string
// person[1] est number

// Accès avec types corrects
const personName: string = person[0];
const personAge: number = person[1];

// [X] Inversion de types : erreur
// const wrongPerson: [string, number] = [25, "Alice"]; // ERREUR !

// Tuple avec labels (TypeScript 4.0+)
const personNamed: [name: string, age: number] = ["Bob", 30];

// Tuple optionnel
const optionalTuple: [string, number?] = ["Alice"]; // age optionnel

// Tuple spread (rest elements)
type StringsAndNumber = [...string[], number];
const example: StringsAndNumber = ["a", "b", "c", 42];

/*
[IDEE] UTILISATION PRATIQUE DES TUPLES

Cas 1 : Retourner plusieurs valeurs d'une fonction
*/
function getCoordinates(): [number, number] {
  return [48.8566, 2.3522]; // [latitude, longitude] Paris
}

const [lat, lng] = getCoordinates(); // Destructuring !

/*
Cas 2 : Les hooks React utilisent des tuples !
useState retourne un tuple : [state, setState]
*/
// const [count, setCount] = useState(0); // [number, Dispatch<SetStateAction<number>>]


// ----------------------------------------------------------------------------
// [DOSSIER] OBJECTS
// ----------------------------------------------------------------------------

/*
TYPER UN OBJET EN LIGNE (Inline Object Type)
*/

// Mauvais (type any implicite)
// const user = { name: "Alice", age: 25 };

// Bon (type explicite inline)
const user: { name: string; age: number } = {
  name: "Alice",
  age: 25
};

// Avec propriétés optionnelles (?)
const config: { 
  host: string; 
  port: number; 
  debug?: boolean;  // <- optionnel
} = {
  host: "localhost",
  port: 3000
  // debug n'est pas requis
};

// Avec propriétés en lecture seule
const point: { readonly x: number; readonly y: number } = {
  x: 10,
  y: 20
};
// point.x = 5; // [X] Erreur ! x est readonly

/*
[IDEE] BONNE PRATIQUE :
Pour les objets complexes, utilisez une INTERFACE ou TYPE (chapitre 3)
plutôt que le type inline. Plus lisible et réutilisable !
*/


// ----------------------------------------------------------------------------
// [WATER_WAVE] TYPE ASSERTIONS
// ----------------------------------------------------------------------------

/*
Type Assertion = Dire à TypeScript "Fais-moi confiance, je sais que c'est ce type"

[ATTENTION] CE N'EST PAS UNE CONVERSION ! Juste un changement de vue pour TypeScript.
*/

// Syntaxe 1 : as (recommandée)
const input = document.getElementById("email") as HTMLInputElement;
input.value = "test@example.com"; // [OK] TypeScript sait que c'est un input

// Syntaxe 2 : <Type> (ancienne, conflits avec JSX)
// const input2 = <HTMLInputElement>document.getElementById("email");

/*
[IDEE] QUAND UTILISER AS ?

[OK] Quand TypeScript ne peut pas déduire le type précis :
   - DOM elements (getElementById retourne HTMLElement | null)
   - Données JSON parsées
   - Migrations depuis JavaScript

[X] NE PAS utiliser pour contourner des erreurs légitimes :
   - Ça cache des bugs !
   - Préférez une vraie vérification de type
*/

// Double assertion (quand les types ne sont pas compatibles)
// À utiliser avec EXTRÊME PRUDENCE
const unknownValue: unknown = "Je suis une string";
const certainlyString = unknownValue as unknown as string; // Double assertion


// ----------------------------------------------------------------------------
// [NOTE] TYPES LITTÉRAUX
// ----------------------------------------------------------------------------

/*
LITTÉRAL = Valeur EXACTE comme type (pas juste string ou number, mais UNE VALEUR PRÉCISE)
*/

// Littéral string
const direction: "nord" = "nord";
// direction = "sud"; // [X] Erreur ! Seul "nord" est autorisé

// Littéral number
const statusCode: 200 | 404 | 500 = 200;

// Littéral boolean
const alwaysTrue: true = true;

// Union de littéraux (très utile !)
type Status = "active" | "inactive" | "pending";
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
type Size = "small" | "medium" | "large";

let userStatus: Status = "active";
userStatus = "inactive"; // [OK] OK
// userStatus = "deleted"; // [X] Erreur ! "deleted" n'est pas dans Status

/*
[IDEE] AVANTAGE DES UNIONS DE LITTÉRAUX

1. DOCUMENTATION : Le type dit QUELLES valeurs sont possibles
2. AUTOCOMPLÉTION : IDE vous montre les options
3. SÉCURITÉ : Impossible d'écrire une valeur invalide
4. REFACTORING : Si vous changez les options, TypeScript trouve tous les usages
*/

// Exemple pratique
type Color = "red" | "green" | "blue" | "yellow";

function applyColor(element: HTMLElement, color: Color): void {
  element.style.color = color;
}

// applyColor(div, "purple"); // [X] Erreur immédiate ! "purple" n'est pas dans Color
// applyColor(div, "red");    // [OK] OK


// ----------------------------------------------------------------------------
// [OBJECTIF] CONST ASSERTIONS
// ----------------------------------------------------------------------------

/*
'as const' = Rendre toutes les valeurs en lecture seule ET en types littéraux
*/

// Sans as const : type inféré est large
const config1 = {
  host: "localhost",  // type: string (large)
  port: 3000          // type: number (large)
};

// Avec as const : type littéral exact
const config2 = {
  host: "localhost",  // type: "localhost" (littéral exact)
  port: 3000          // type: 3000 (littéral exact)
} as const;

// config2.host = "production.com"; // [X] Erreur ! Readonly

// Tableau avec as const
const directions = ["nord", "sud", "est", "ouest"] as const;
type Direction2 = typeof directions[number]; // "nord" | "sud" | "est" | "ouest"

/*
[IDEE] UTILISATION TYPIQUE DE AS CONST :
- Configuration qui ne change pas
- Enums sous forme d'objet
- Maps de valeurs constantes
*/

const HTTP_CODES = {
  OK: 200,
  NOT_FOUND: 404,
  SERVER_ERROR: 500
} as const;

type HttpCode = typeof HTTP_CODES[keyof typeof HTTP_CODES]; // 200 | 404 | 500


// ============================================================================
// [GUIDE] CHAPITRE 2 : FONCTIONS TYPÉES
// ============================================================================

/*
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Typer les paramètres et retours de fonctions
[OK] Utiliser paramètres optionnels et par défaut
[OK] Typer les fonctions arrow et callbacks
[OK] Utiliser la surcharge de fonctions
[OK] Comprendre les types de fonctions
*/


// ----------------------------------------------------------------------------
// [MESURE] DÉCLARATION DE FONCTIONS
// ----------------------------------------------------------------------------

/*
SYNTAXE DE BASE
*/

// Fonction déclarée (function declaration)
function greet(name: string): string {
  return `Bonjour, ${name} !`;
}

// Fonction expression
const multiply = function(a: number, b: number): number {
  return a * b;
};

// Arrow function
const divide = (a: number, b: number): number => a / b;

// Arrow function avec corps
const complexCalc = (x: number, y: number): number => {
  const sum = x + y;
  return sum * 2;
};

/*
[IDEE] ANATOMIE D'UNE FONCTION TYPÉE

function nomFonction(param1: Type1, param2: Type2): TypeRetour {
                     ─────────────────────────────  ──────────
                     Paramètres avec types           Type de retour
  return résultat;
}

[ATTENTION] RÈGLE D'OR :
Toujours typer les PARAMÈTRES !
Le type de retour peut être inféré, mais le spécifier est une bonne pratique.
*/


// ----------------------------------------------------------------------------
// [?] PARAMÈTRES OPTIONNELS ET PAR DÉFAUT
// ----------------------------------------------------------------------------

/*
PARAMÈTRE OPTIONNEL : Peut être absent (type: Type | undefined)
*/

function createUser(name: string, age?: number): string {
  //                                       ^ Optionnel avec ?
  if (age !== undefined) {
    return `${name}, ${age} ans`;
  }
  return name;
}

createUser("Alice");        // [OK] OK
createUser("Alice", 25);    // [OK] OK
// createUser("Alice", "vieux"); // [X] Erreur !

/*
[IDEE] ORDRE DES PARAMÈTRES :
Les paramètres OBLIGATOIRES doivent venir AVANT les optionnels !
*/

// [OK] Correct
function optionalLast(required: string, optional?: number): void {}

// [X] Incorrect : TypeScript erreur
// function optionalFirst(optional?: number, required: string): void {}


/*
PARAMÈTRE PAR DÉFAUT
*/

function createUserWithDefault(
  name: string, 
  role: string = "user",        // Valeur par défaut
  isActive: boolean = true      // Valeur par défaut
): string {
  return `${name} (${role}) - ${isActive ? 'actif' : 'inactif'}`;
}

createUserWithDefault("Alice");                  // "Alice (user) - actif"
createUserWithDefault("Bob", "admin");           // "Bob (admin) - actif"
createUserWithDefault("Charlie", "mod", false);  // "Charlie (mod) - inactif"

/*
[IDEE] DIFFÉRENCE optionnel vs défaut :

optionnel (?) :
-> Peut être undefined
-> À vous de gérer le cas undefined dans la fonction
-> Syntaxe : param?: Type

par défaut (= valeur) :
-> Si absent, utilise la valeur par défaut
-> Pas besoin de gérer undefined
-> Syntaxe : param: Type = valeur

RECOMMANDATION : Préférez les valeurs par défaut aux optionnels
quand vous avez une valeur raisonnable par défaut !
*/


// ----------------------------------------------------------------------------
// [PACKAGE] PARAMÈTRE REST (...args)
// ----------------------------------------------------------------------------

/*
REST PARAMETER = Capturer un nombre variable d'arguments dans un tableau
*/

function sum(...numbers: number[]): number {
  return numbers.reduce((total, n) => total + n, 0);
}

sum(1, 2, 3);           // 6
sum(1, 2, 3, 4, 5);     // 15
sum();                   // 0

// Rest avec paramètres normaux (rest doit être DERNIER)
function logWithPrefix(prefix: string, ...messages: string[]): void {
  messages.forEach(msg => console.log(`[${prefix}] ${msg}`));
}

logWithPrefix("INFO", "Serveur démarré", "Port 3000", "Prêt");


// ----------------------------------------------------------------------------
// [SYNC] OVERLOADING (SURCHARGE)
// ----------------------------------------------------------------------------

/*
SURCHARGE = Définir plusieurs SIGNATURES pour une même fonction

Pourquoi ? Pour que le type de RETOUR dépende des PARAMÈTRES.
*/

// Signatures de surcharge (sans corps)
function parseInput(input: string): string[];        // Si string -> retourne string[]
function parseInput(input: number): number[];        // Si number -> retourne number[]
function parseInput(input: boolean): boolean[];      // Si boolean -> retourne boolean[]

// Implémentation réelle (doit couvrir toutes les signatures)
function parseInput(input: string | number | boolean): string[] | number[] | boolean[] {
  if (typeof input === "string") {
    return input.split(",");
  } else if (typeof input === "number") {
    return Array(input).fill(0);
  } else {
    return [input];
  }
}

// TypeScript utilise la bonne signature selon le type passé !
const strings = parseInput("a,b,c");  // TypeScript sait que c'est string[]
const nums = parseInput(3);           // TypeScript sait que c'est number[]

/*
[IDEE] QUAND UTILISER LA SURCHARGE ?

[OK] Quand le TYPE DE RETOUR change selon le type des paramètres
[OK] Pour une meilleure documentation et autocomplétion
[X] PAS nécessaire si le retour est toujours le même type
*/

// Exemple pratique : fetch avec différents formats
function fetchData(url: string): Promise<string>;
function fetchData(url: string, format: "json"): Promise<object>;
function fetchData(url: string, format: "text"): Promise<string>;
function fetchData(url: string, format?: "json" | "text"): Promise<string | object> {
  // Implémentation...
  return fetch(url).then(r => format === "json" ? r.json() : r.text());
}


// ----------------------------------------------------------------------------
// [LISTE] TYPES DE FONCTIONS
// ----------------------------------------------------------------------------

/*
TYPER UNE VARIABLE QUI CONTIENT UNE FONCTION
*/

// Type de fonction inline
let calculator: (a: number, b: number) => number;

calculator = (x, y) => x + y;   // [OK] Compatible
calculator = (x, y) => x * y;   // [OK] Compatible
// calculator = (x) => x * 2;   // [X] Erreur ! Manque un paramètre

// Avec type alias (plus lisible)
type BinaryOperation = (a: number, b: number) => number;

const add: BinaryOperation = (a, b) => a + b;
const subtract: BinaryOperation = (a, b) => a - b;

// Passer une fonction en paramètre (callback)
function applyOperation(
  a: number, 
  b: number, 
  operation: BinaryOperation
): number {
  return operation(a, b);
}

applyOperation(10, 5, add);       // 15
applyOperation(10, 5, subtract);  // 5

// Fonctions qui acceptent des callbacks variés
function map<T, U>(array: T[], transform: (item: T) => U): U[] {
  return array.map(transform);
}

const doubled = map([1, 2, 3], x => x * 2);   // [2, 4, 6]
const strings2 = map([1, 2, 3], x => `${x}`); // ["1", "2", "3"]


// ----------------------------------------------------------------------------
// [BACK_WITH_LEFTWARDS_ARROW_ABOVE] VOID ET RETURN TYPES SPÉCIAUX
// ----------------------------------------------------------------------------

/*
VOID : Fonction qui ne retourne pas de valeur utile
*/

function printMessage(msg: string): void {
  console.log(msg);
  // return; <- Autorisé (retourne undefined)
  // return undefined; <- Autorisé
  // return 5; <- [X] Erreur !
}

/*
NEVER : Fonction qui ne retourne JAMAIS (exception ou boucle infinie)
*/

function assertNever(value: never): never {
  throw new Error(`Valeur inattendue: ${JSON.stringify(value)}`);
}

/*
[IDEE] void vs never vs undefined :

void  = "Cette fonction retourne (mais la valeur n'est pas utile)"
never = "Cette fonction ne retourne JAMAIS normalement"
undefined = "Cette fonction retourne undefined"

Subtilité :
- void accepte undefined comme valeur de retour
- never n'accepte aucune valeur de retour
*/


// ============================================================================
// [GUIDE] CHAPITRE 3 : INTERFACES ET TYPES
// ============================================================================

/*
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Créer des interfaces pour les objets
[OK] Utiliser les types alias
[OK] Comprendre interface vs type
[OK] Utiliser l'extension et l'implémentation
[OK] Index signatures et types mappés simples
*/


// ----------------------------------------------------------------------------
// [CONSTRUCTION] INTERFACES
// ----------------------------------------------------------------------------

/*
INTERFACE = Contrat qui décrit la FORME d'un objet
*/

// Déclaration d'interface
interface User {
  id: number;
  name: string;
  email: string;
  age?: number;        // Propriété optionnelle
  readonly createdAt: Date; // Propriété en lecture seule
}

// Utilisation
const alice: User = {
  id: 1,
  name: "Alice",
  email: "alice@example.com",
  createdAt: new Date()
};

// alice.createdAt = new Date(); // [X] Erreur ! readonly

// Avec propriété optionnelle
const bob: User = {
  id: 2,
  name: "Bob",
  email: "bob@example.com",
  age: 30,             // Optionnel, mais fourni
  createdAt: new Date()
};

/*
[IDEE] AVANTAGES DES INTERFACES :

1. RÉUTILISABILITÉ : Définir une fois, utiliser partout
2. DOCUMENTATION : La structure est claire
3. INTELISENSE : Autocomplétion précise
4. VALIDATION : TypeScript vérifie la conformité
5. EXTENSIBILITÉ : On peut étendre une interface
*/


// ----------------------------------------------------------------------------
// [MELANGE] INTERFACE POUR FONCTIONS
// ----------------------------------------------------------------------------

// Interface décrivant une fonction
interface StringTransformer {
  (input: string): string;
}

const toUpper: StringTransformer = (s) => s.toUpperCase();
const trim: StringTransformer = (s) => s.trim();

// Interface avec méthodes
interface Calculator {
  add(a: number, b: number): number;
  subtract(a: number, b: number): number;
  multiply: (a: number, b: number) => number; // Alternative
}

const basicCalc: Calculator = {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b,
  multiply: (a, b) => a * b
};


// ----------------------------------------------------------------------------
// [BOOKMARK_TABS] EXTENSION D'INTERFACES
// ----------------------------------------------------------------------------

/*
EXTENDS = Hériter et AJOUTER des propriétés
*/

interface Animal {
  name: string;
  age: number;
}

interface Dog extends Animal {
  breed: string;
  isGoodBoy: boolean;
}

const rex: Dog = {
  name: "Rex",
  age: 3,
  breed: "Berger Allemand",
  isGoodBoy: true
};

// Étendre PLUSIEURS interfaces
interface Flyable {
  fly(): void;
  altitude: number;
}

interface Swimmable {
  swim(): void;
  depth: number;
}

interface Duck extends Animal, Flyable, Swimmable {
  quack(): void;
}

/*
[IDEE] DÉCLARATION FUSION (Declaration Merging)

TypeScript FUSIONNE automatiquement les interfaces de même nom !
(Impossible avec les types alias)
*/

interface Window {
  myCustomProperty: string;
}

// Maintenant window.myCustomProperty existe dans TypeScript !
// Utile pour étendre des bibliothèques tierces.


// ----------------------------------------------------------------------------
// [LABEL] TYPE ALIAS
// ----------------------------------------------------------------------------

/*
TYPE ALIAS = Nommer un type pour le réutiliser

Syntaxe : type NomType = TypeExpression;
*/

// Types simples
type UserId = number;
type UserName = string;
type IsActive = boolean;

// Type objet (comme une interface)
type UserType = {
  id: UserId;
  name: UserName;
  isActive: IsActive;
};

// Union type
type StringOrNumber = string | number;
type Status = "pending" | "active" | "inactive" | "deleted";

// Intersection type
type AdminUser = UserType & {
  adminLevel: number;
  permissions: string[];
};

// Types complexes
type Callback<T> = (error: Error | null, data: T) => void;
type Matrix = number[][];
type Pair<T, U> = [T, U];


// ----------------------------------------------------------------------------
// 🆚 INTERFACE VS TYPE : QUAND UTILISER LEQUEL ?
// ----------------------------------------------------------------------------

/*
┌──────────────────────────────┬────────────────┬────────────────┐
│ Fonctionnalité               │ interface      │ type           │
├──────────────────────────────┼────────────────┼────────────────┤
│ Objets                       │ [OK]             │ [OK]             │
│ Fonctions                    │ [OK]             │ [OK]             │
│ Primitifs (string, number)   │ [X]             │ [OK]             │
│ Union types (A | B)          │ [X]             │ [OK]             │
│ Intersection (A & B)         │ via extends    │ [OK]             │
│ Tuple                        │ [X] (compliqué) │ [OK]             │
│ Déclaration fusion           │ [OK] (auto)      │ [X]             │
│ Classe implémente            │ [OK] implements  │ [OK] implements  │
│ Extension                    │ extends        │ & (intersection)│
│ Erreur messages              │ Meilleurs      │ Moins clairs   │
└──────────────────────────────┴────────────────┴────────────────┘


RÈGLE RECOMMANDÉE :

[OK] Utilisez INTERFACE quand :
- Vous décrivez la forme d'objets ou classes
- Vous voulez que d'autres puissent l'étendre
- Vous développez une API publique
- Vous utilisez des classes qui "implements"

[OK] Utilisez TYPE quand :
- Vous créez des unions ou intersections
- Vous typez des primitifs
- Vous créez des tuples nommés
- Vous mappez ou transformez des types
- Cas complexes (conditions, mapped types)

[IDEE] EN RÉSUMÉ :
Interface pour OBJETS/CLASSES
Type pour TOUT LE RESTE
*/

// Interface pour objet/classe (recommandé)
interface Product {
  id: number;
  name: string;
  price: number;
}

// Type pour union (seule option)
type ProductOrNull = Product | null;
type ProductCategory = "electronics" | "clothing" | "food";

// Type pour tuple (seule option propre)
type Coordinates = [latitude: number, longitude: number];


// ----------------------------------------------------------------------------
// [DOSSIER] INDEX SIGNATURES
// ----------------------------------------------------------------------------

/*
INDEX SIGNATURE = Définir les types pour TOUTES les propriétés dynamiques d'un objet
*/

// Objet dont les clés sont des strings et valeurs des numbers
interface StringToNumber {
  [key: string]: number;
}

const scores: StringToNumber = {
  alice: 95,
  bob: 87,
  charlie: 92
};

scores["diane"] = 88;  // [OK] OK

// Objet dont les clés sont des numbers (comme un tableau)
interface NumberIndex {
  [index: number]: string;
}

const phrases: NumberIndex = {
  0: "Bonjour",
  1: "Au revoir",
  2: "Merci"
};

// Combinaison propriétés fixes et dynamiques
interface Config {
  name: string;         // Propriété fixe
  version: number;      // Propriété fixe
  [key: string]: string | number; // Propriétés dynamiques
  // [ATTENTION] Le type des propriétés fixes doit être compatible avec l'index signature !
}


// ----------------------------------------------------------------------------
// [COURS] EXERCICE PRATIQUE 1 : SYSTÈME DE GESTION D'ÉTUDIANTS
// ----------------------------------------------------------------------------

/*
[OBJECTIF] OBJECTIF :
Créer un système de gestion d'étudiants en utilisant TypeScript.
Appliquer tous les concepts des chapitres 0-3.

[LISTE] CAHIER DES CHARGES :

1. Définir les types et interfaces :
   - Grade : type union de "A" | "B" | "C" | "D" | "F"
   - Course : interface avec id, name, credits, grade optionnelle
   - Student : interface avec id, firstName, lastName, email, 
                         courses[], enrollmentDate (readonly)
   - Department : interface avec name, code, head(nom du responsable)

2. Créer des fonctions :
   - createStudent(id, firstName, lastName, email): Student
   - enrollInCourse(student, course): Student (retourne étudiant mis à jour)
   - calculateGPA(student): number (moyenne des notes)
   - getTopStudents(students, minGPA): Student[]

3. Tester avec des données d'exemple

[TEMPS] TEMPS ESTIMÉ : 30-45 minutes
*/

// SOLUTION COMPLÈTE :

// 1. Types et interfaces
type Grade = "A" | "B" | "C" | "D" | "F";

interface Course {
  id: string;
  name: string;
  credits: number;
  grade?: Grade;
}

interface Student {
  id: number;
  firstName: string;
  lastName: string;
  email: string;
  courses: Course[];
  readonly enrollmentDate: Date;
}

interface Department {
  name: string;
  code: string;
  head: string;
}

// Mapping grade -> valeur numérique (pour GPA)
const GRADE_POINTS: Record<Grade, number> = {
  "A": 4.0,
  "B": 3.0,
  "C": 2.0,
  "D": 1.0,
  "F": 0.0
};

// 2. Fonctions

function createStudent(
  id: number,
  firstName: string,
  lastName: string,
  email: string
): Student {
  return {
    id,
    firstName,
    lastName,
    email,
    courses: [],
    enrollmentDate: new Date()
  };
}

function enrollInCourse(student: Student, course: Course): Student {
  // Vérifier que le cours n'est pas déjà ajouté
  const alreadyEnrolled = student.courses.some(c => c.id === course.id);
  
  if (alreadyEnrolled) {
    console.warn(`${student.firstName} est déjà inscrit à ${course.name}`);
    return student;
  }
  
  return {
    ...student,
    courses: [...student.courses, course]
  };
}

function assignGrade(student: Student, courseId: string, grade: Grade): Student {
  return {
    ...student,
    courses: student.courses.map(course => 
      course.id === courseId ? { ...course, grade } : course
    )
  };
}

function calculateGPA(student: Student): number {
  const gradedCourses = student.courses.filter(c => c.grade !== undefined);
  
  if (gradedCourses.length === 0) return 0;
  
  const totalPoints = gradedCourses.reduce((sum, course) => {
    const gradeValue = GRADE_POINTS[course.grade!]; // ! car on a vérifié avec filter
    return sum + gradeValue * course.credits;
  }, 0);
  
  const totalCredits = gradedCourses.reduce((sum, c) => sum + c.credits, 0);
  
  return Math.round((totalPoints / totalCredits) * 100) / 100;
}

function getTopStudents(students: Student[], minGPA: number): Student[] {
  return students
    .filter(student => calculateGPA(student) >= minGPA)
    .sort((a, b) => calculateGPA(b) - calculateGPA(a));
}

function getFullName(student: Student): string {
  return `${student.firstName} ${student.lastName}`;
}

// 3. Test

// Créer les cours
const mathCourse: Course = { id: "MATH101", name: "Mathématiques", credits: 4 };
const csCourse: Course = { id: "CS101", name: "Intro à l'Info", credits: 3 };
const physicsCourse: Course = { id: "PHYS101", name: "Physique", credits: 3 };

// Créer les étudiants
let alice = createStudent(1, "Alice", "Dupont", "alice@univ.fr");
let bob = createStudent(2, "Bob", "Martin", "bob@univ.fr");
let charlie = createStudent(3, "Charlie", "Bernard", "charlie@univ.fr");

// Inscrire aux cours
alice = enrollInCourse(alice, mathCourse);
alice = enrollInCourse(alice, csCourse);
alice = enrollInCourse(alice, physicsCourse);

bob = enrollInCourse(bob, mathCourse);
bob = enrollInCourse(bob, csCourse);

charlie = enrollInCourse(charlie, csCourse);
charlie = enrollInCourse(charlie, physicsCourse);

// Assigner des notes
alice = assignGrade(alice, "MATH101", "A");
alice = assignGrade(alice, "CS101", "B");
alice = assignGrade(alice, "PHYS101", "A");

bob = assignGrade(bob, "MATH101", "C");
bob = assignGrade(bob, "CS101", "B");

charlie = assignGrade(charlie, "CS101", "A");
charlie = assignGrade(charlie, "PHYS101", "B");

// Calculer les GPAs
console.log(`GPA de ${getFullName(alice)}: ${calculateGPA(alice)}`);      // 3.7
console.log(`GPA de ${getFullName(bob)}: ${calculateGPA(bob)}`);          // 2.57
console.log(`GPA de ${getFullName(charlie)}: ${calculateGPA(charlie)}`);  // 3.5

// Top étudiants
const students: Student[] = [alice, bob, charlie];
const topStudents = getTopStudents(students, 3.0);
console.log("Top étudiants (GPA >= 3.0):");
topStudents.forEach(s => console.log(`  ${getFullName(s)}: ${calculateGPA(s)}`));


// ----------------------------------------------------------------------------
// [DOCS] RÉCAPITULATIF PARTIE 1
// ----------------------------------------------------------------------------

/*
CE QUE VOUS AVEZ APPRIS :

[OK] Chapitre 0 : Introduction
   - TypeScript = JavaScript + Types Statiques
   - Compilation en JavaScript
   - Installation et configuration

[OK] Chapitre 1 : Types de Base
   - Types primitifs (number, string, boolean, null, undefined, symbol, bigint)
   - Types spéciaux (any, unknown, never, void)
   - Tableaux et Tuples
   - Objets typés
   - Type assertions (as)
   - Types littéraux et as const

[OK] Chapitre 2 : Fonctions
   - Typer paramètres et retours
   - Paramètres optionnels (?), par défaut (=), rest (...)
   - Surcharge de fonctions
   - Types de fonctions (callbacks)
   - void vs never vs undefined

[OK] Chapitre 3 : Interfaces et Types
   - Interfaces : forme des objets
   - Extension d'interfaces
   - Type alias : unions, intersections
   - Interface vs Type (quand utiliser quoi)
   - Index signatures


[CLE] POINTS CLÉS À RETENIR :

1. TypeScript détecte les bugs À LA COMPILATION, pas à l'exécution
2. Préférez unknown à any
3. Utilisez les types littéraux pour les valeurs limitées
4. Interface pour objets/classes, Type pour le reste
5. Paramètres de fonction TOUJOURS typés
6. readonly pour l'immutabilité
7. L'inférence de types est votre amie !


-> PROCHAINE ÉTAPE : PARTIE 2 - Classes, Génériques, Enums
*/

// ============================================================================
// [LIVRE] TYPESCRIPT - PARTIE 2 : ORIENTÉ OBJET
// ============================================================================
//
// [OBJECTIF] CETTE PARTIE COUVRE :
// - Chapitre 4 : Classes et Programmation Orientée Objet
// - Chapitre 5 : Génériques (Generics)
// - Chapitre 6 : Enums et Tuples Avancés
//
// [TEMPS] TEMPS : ~6-8 heures
// [DOCS] PRÉREQUIS : Partie 1 complétée
// ============================================================================


// ============================================================================
// [GUIDE] CHAPITRE 4 : CLASSES ET PROGRAMMATION ORIENTÉE OBJET
// ============================================================================

/*
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Créer des classes avec propriétés typées
[OK] Utiliser les modificateurs d'accès (public, private, protected)
[OK] Implémenter des interfaces avec les classes
[OK] Utiliser l'héritage en TypeScript
[OK] Comprendre les membres statiques et abstraits
[OK] Utiliser les getters/setters
*/


// ----------------------------------------------------------------------------
// [CONSTRUCTION] CLASSES DE BASE
// ----------------------------------------------------------------------------

/*
CLASSE = Modèle (blueprint) pour créer des objets

TypeScript ajoute le TYPAGE aux classes JavaScript
*/

class Person {
  // Déclaration des propriétés avec leurs types
  name: string;
  age: number;
  email: string;

  // Constructeur
  constructor(name: string, age: number, email: string) {
    this.name = name;
    this.age = age;
    this.email = email;
  }

  // Méthode
  greet(): string {
    return `Bonjour, je suis ${this.name}, ${this.age} ans.`;
  }

  // Méthode qui modifie l'état
  birthday(): void {
    this.age++;
  }
}

const alice = new Person("Alice", 25, "alice@example.com");
console.log(alice.greet());  // Bonjour, je suis Alice, 25 ans.
alice.birthday();
console.log(alice.age);      // 26

/*
[IDEE] RACCOURCI : PARAMÈTRE DE CONSTRUCTEUR

Au lieu d'écrire deux fois le nom de la propriété, TypeScript permet :
*/

class PersonShortcut {
  // Déclarer ET initialiser en une seule ligne !
  constructor(
    public name: string,       // public = accessible de partout
    public age: number,
    public email: string
  ) {
    // Pas besoin de this.name = name; etc.
    // TypeScript le fait automatiquement !
  }

  greet(): string {
    return `Bonjour, je suis ${this.name}`;
  }
}

/*
[IDEE] CE RACCOURCI (PARAMETER PROPERTIES) est très utilisé en TypeScript !
Il réduit la répétition et est considéré comme bonne pratique.
*/


// ----------------------------------------------------------------------------
// [VERROUILLE] MODIFICATEURS D'ACCÈS
// ----------------------------------------------------------------------------

/*
TypeScript a 3 (+ 1) modificateurs d'accès :

public    : Accessible partout (défaut)
private   : Accessible SEULEMENT dans la classe
protected : Accessible dans la classe ET ses sous-classes
readonly  : Ne peut pas être modifié après initialisation
*/

class BankAccount {
  public owner: string;          // Accessible partout
  private _balance: number;      // Seulement dans BankAccount
  protected accountType: string; // Dans BankAccount et sous-classes
  readonly id: string;           // Lecture seulement

  constructor(owner: string, initialBalance: number) {
    this.owner = owner;
    this._balance = initialBalance;
    this.accountType = "standard";
    this.id = `ACC-${Date.now()}`; // Assigné UNE FOIS dans le constructeur
  }

  // Méthode publique
  deposit(amount: number): void {
    if (amount <= 0) throw new Error("Montant invalide");
    this._balance += amount;
  }

  withdraw(amount: number): boolean {
    if (amount > this._balance) {
      console.log("Solde insuffisant");
      return false;
    }
    this._balance -= amount;
    return true;
  }

  // Méthode publique pour lire le solde (encapsulation !)
  getBalance(): number {
    return this._balance;
  }
}

const account = new BankAccount("Alice", 1000);
account.deposit(500);
console.log(account.getBalance()); // 1500
// account._balance = 999999; // [X] Erreur ! Private
// account.id = "HACK";        // [X] Erreur ! Readonly

/*
[IDEE] PRIVATE vs PROTECTED :

private   = Mur étanche autour de la classe
            Même les sous-classes ne peuvent pas accéder

protected = Mur avec une porte pour les enfants
            Les sous-classes peuvent accéder


[IDEE] CONVENTION DE NOMMAGE :
Propriétés privées souvent préfixées par _ (underscore)
Exemple : _balance, _id, _secret
C'est une CONVENTION, pas une règle TypeScript.


[IDEE] PRIVATE vs #private (ECMAScript) :

TypeScript private :
-> Vérifié seulement à la compilation
-> Accessible depuis JavaScript au runtime !

ECMAScript private (#) :
-> Vérifié au runtime aussi
-> Vraiment privé, même en JavaScript
*/

class ModernClass {
  #reallyPrivate: number = 0; // Vraiment privé (ES2022)
  private tsPrivate: string = ""; // Privé TypeScript seulement

  getPrivate() { return this.#reallyPrivate; }
}


// ----------------------------------------------------------------------------
// [OUTIL] GETTERS ET SETTERS
// ----------------------------------------------------------------------------

/*
GETTER = Propriété calculée (get)
SETTER = Contrôler l'assignation (set)

Syntaxe : get nomProp() / set nomProp(valeur)
*/

class Temperature {
  private _celsius: number;

  constructor(celsius: number) {
    this._celsius = celsius;
  }

  // GETTER : Lire comme une propriété
  get celsius(): number {
    return this._celsius;
  }

  // SETTER : Assigner avec validation
  set celsius(value: number) {
    if (value < -273.15) {
      throw new Error("Température inférieure au zéro absolu !");
    }
    this._celsius = value;
  }

  // Propriété calculée (get seulement)
  get fahrenheit(): number {
    return this._celsius * 9/5 + 32;
  }

  get kelvin(): number {
    return this._celsius + 273.15;
  }
}

const temp = new Temperature(100);
console.log(temp.celsius);    // 100 (comme une propriété normale !)
console.log(temp.fahrenheit); // 212
console.log(temp.kelvin);     // 373.15

temp.celsius = 0; // Utilise le setter
console.log(temp.fahrenheit); // 32

// temp.celsius = -300; // [X] Erreur à l'exécution !

/*
[IDEE] AVANTAGES GETTERS/SETTERS :

1. INTERFACE PROPRE : Accès comme une propriété, logique comme une méthode
2. VALIDATION : Contrôler les valeurs assignées
3. CALCUL : Propriétés calculées à la demande
4. ENCAPSULATION : Cache les détails d'implémentation
*/


// ----------------------------------------------------------------------------
// [LIEN] HÉRITAGE
// ----------------------------------------------------------------------------

/*
HÉRITAGE = Une classe enfant hérite des propriétés et méthodes parent
           ET peut les étendre ou les remplacer

Mot-clé : extends
*/

class Animal {
  constructor(
    public name: string,
    public age: number
  ) {}

  makeSound(): string {
    return "...";
  }

  describe(): string {
    return `${this.name} a ${this.age} ans`;
  }
}

class Dog extends Animal {
  constructor(
    name: string,
    age: number,
    public breed: string  // Propriété supplémentaire
  ) {
    super(name, age); // <- Appel OBLIGATOIRE au constructeur parent !
  }

  // Méthode OVERRIDE (remplace celle du parent)
  makeSound(): string {
    return "Wouf !";
  }

  // Nouvelle méthode
  fetch(): string {
    return `${this.name} rapporte la balle !`;
  }
}

class Cat extends Animal {
  constructor(
    name: string,
    age: number,
    public isIndoor: boolean
  ) {
    super(name, age);
  }

  makeSound(): string {
    return "Miaou !";
  }
}

const rex = new Dog("Rex", 3, "Labrador");
const luna = new Cat("Luna", 2, true);

console.log(rex.makeSound());    // Wouf !
console.log(luna.makeSound());   // Miaou !
console.log(rex.describe());     // Rex a 3 ans (héritée d'Animal)
console.log(rex.fetch());        // Rex rapporte la balle !

// Polymorphisme : Traiter différents types uniformément
const animals: Animal[] = [rex, luna];
animals.forEach(a => console.log(`${a.name}: ${a.makeSound()}`));

/*
[IDEE] OVERRIDE AVEC SUPER :
Vous pouvez appeler la méthode PARENTE depuis la méthode enfant
*/

class FriendlyDog extends Dog {
  makeSound(): string {
    const parentSound = super.makeSound(); // Appel parent : "Wouf !"
    return `${parentSound} ${parentSound} Je suis amical !`;
  }
}

/*
[IDEE] super() vs super.méthode() :

super()           = Appeler le CONSTRUCTEUR du parent (obligatoire dans le constructeur enfant)
super.méthode()   = Appeler une MÉTHODE du parent depuis la méthode enfant
*/


// ----------------------------------------------------------------------------
// [BLACK_SQUARE_BUTTON] CLASSES ABSTRAITES
// ----------------------------------------------------------------------------

/*
CLASSE ABSTRAITE = Classe qui ne peut pas être instanciée directement
                   Oblige les sous-classes à implémenter certaines méthodes

Mot-clé : abstract
*/

abstract class Shape {
  constructor(public color: string) {}

  // MÉTHODE ABSTRAITE : Signature sans corps, DOIT être implémentée
  abstract area(): number;
  abstract perimeter(): number;

  // MÉTHODE CONCRÈTE : Implémentation partagée
  describe(): string {
    return `Forme de couleur ${this.color}, surface: ${this.area().toFixed(2)}`;
  }
}

class Circle extends Shape {
  constructor(color: string, public radius: number) {
    super(color);
  }

  area(): number {
    return Math.PI * this.radius ** 2;
  }

  perimeter(): number {
    return 2 * Math.PI * this.radius;
  }
}

class Rectangle extends Shape {
  constructor(
    color: string,
    public width: number,
    public height: number
  ) {
    super(color);
  }

  area(): number {
    return this.width * this.height;
  }

  perimeter(): number {
    return 2 * (this.width + this.height);
  }
}

// const shape = new Shape("red"); // [X] Erreur ! Cannot instantiate abstract class

const circle = new Circle("rouge", 5);
const rect = new Rectangle("bleu", 4, 6);

console.log(circle.describe()); // Forme de couleur rouge, surface: 78.54
console.log(rect.describe());   // Forme de couleur bleu, surface: 24.00

/*
[IDEE] CLASSE ABSTRAITE vs INTERFACE :

Classe abstraite :
[OK] Peut avoir des méthodes CONCRÈTES (avec implémentation)
[OK] Peut avoir des propriétés avec état
[OK] Une classe ne peut hériter que d'UNE seule classe abstraite
[OK] Peut avoir des constructeurs

Interface :
[OK] Seulement des signatures (sans implémentation)
[OK] Une classe peut implémenter PLUSIEURS interfaces
[OK] Plus flexible
[OK] Léger (pas de code généré en JS)

RÈGLE : Utilisez l'interface si possible, classe abstraite si besoin d'implémentation partagée
*/


// ----------------------------------------------------------------------------
// [CONFIG] IMPLEMENTS : CLASSES ET INTERFACES
// ----------------------------------------------------------------------------

/*
IMPLEMENTS = Une classe s'engage à respecter un contrat (interface)
*/

interface Serializable {
  serialize(): string;
  deserialize(data: string): void;
}

interface Printable {
  print(): void;
}

// Une classe peut implémenter PLUSIEURS interfaces
class Document implements Serializable, Printable {
  private content: string = "";

  serialize(): string {
    return JSON.stringify({ content: this.content });
  }

  deserialize(data: string): void {
    const parsed = JSON.parse(data);
    this.content = parsed.content;
  }

  print(): void {
    console.log(`Document: ${this.content}`);
  }

  setContent(content: string): void {
    this.content = content;
  }
}

const doc = new Document();
doc.setContent("Hello TypeScript !");
doc.print(); // Document: Hello TypeScript !
const serialized = doc.serialize();
console.log(serialized); // {"content":"Hello TypeScript !"}


// ----------------------------------------------------------------------------
// [GRAPHIQUE] MEMBRES STATIQUES
// ----------------------------------------------------------------------------

/*
STATIQUE = Appartient à la CLASSE, pas aux instances
           Partagé entre toutes les instances

Mot-clé : static
*/

class Counter {
  private static _count: number = 0; // Partagé par TOUTES les instances
  public readonly id: number;

  constructor() {
    Counter._count++; // Increment le compteur partagé
    this.id = Counter._count; // Chaque instance a son ID unique
  }

  static getCount(): number {
    return Counter._count;
  }

  static reset(): void {
    Counter._count = 0;
  }
}

const c1 = new Counter();
const c2 = new Counter();
const c3 = new Counter();

console.log(Counter.getCount()); // 3
console.log(c1.id, c2.id, c3.id); // 1 2 3

Counter.reset();
console.log(Counter.getCount()); // 0

// Utilitaires statiques (pas besoin d'instance)
class MathUtils {
  static readonly PI = 3.14159265358979;

  static circleArea(radius: number): number {
    return MathUtils.PI * radius ** 2;
  }

  static clamp(value: number, min: number, max: number): number {
    return Math.min(Math.max(value, min), max);
  }
}

console.log(MathUtils.circleArea(5)); // 78.54...
console.log(MathUtils.clamp(15, 0, 10)); // 10


// ============================================================================
// [GUIDE] CHAPITRE 5 : GÉNÉRIQUES (GENERICS)
// ============================================================================

/*
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Comprendre pourquoi les génériques existent
[OK] Utiliser les génériques avec fonctions et classes
[OK] Utiliser les contraintes de génériques (extends)
[OK] Utiliser les types génériques utilitaires
[OK] Créer des structures de données génériques
*/


// ----------------------------------------------------------------------------
// [REFLEXION] POURQUOI LES GÉNÉRIQUES ?
// ----------------------------------------------------------------------------

/*
PROBLÈME : Répétition de code pour différents types
*/

// [X] SANS génériques : Répétition !
function firstNumberInArray(arr: number[]): number {
  return arr[0];
}

function firstStringInArray(arr: string[]): string {
  return arr[0];
}

function firstBooleanInArray(arr: boolean[]): boolean {
  return arr[0];
}

// La logique est la MÊME, seul le type change !

// [X] AVEC any : Perd le type !
function firstWithAny(arr: any[]): any {
  return arr[0];
}
const result = firstWithAny([1, 2, 3]);
// result est any -> on a perdu le type number !

/*
[OK] SOLUTION : GÉNÉRIQUES

Un générique est un "paramètre de type" qui permet d'écrire
du code qui fonctionne avec PLUSIEURS TYPES tout en restant typé.

Syntaxe : <T> où T est le paramètre de type (comme un placeholder)
*/

// [OK] AVEC générique : Une seule fonction, types préservés !
function first<T>(arr: T[]): T {
  //       ^ Paramètre de type (placeholder)
  return arr[0];
}

const num = first([1, 2, 3]);         // TypeScript infère T = number
const str = first(["a", "b", "c"]);   // TypeScript infère T = string
const bool = first([true, false]);     // TypeScript infère T = boolean

// num est number, str est string, bool est boolean !
// Types préservés ET code réutilisable

/*
[IDEE] ANALOGIE :

Générique = Machine distributrice polyvalente
T          = La case "type de produit"

machine.distribuer<Boisson>()  -> retourne une Boisson
machine.distribuer<Snack>()    -> retourne un Snack
machine.distribuer<Billet>()   -> retourne un Billet

Une seule machine, plusieurs produits !
*/


// ----------------------------------------------------------------------------
// [OUTIL] FONCTIONS GÉNÉRIQUES
// ----------------------------------------------------------------------------

// Fonction avec 2 paramètres de type
function pair<T, U>(first: T, second: U): [T, U] {
  return [first, second];
}

const nameAge = pair("Alice", 25);        // [string, number]
const coordPair = pair(48.8566, 2.3522);  // [number, number]

// Fonction générique avec retour différent
function identity<T>(arg: T): T {
  return arg;
}

// Fonction générique sur les objets
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  //                       ^ Contrainte : K doit être une clé de T
  return obj[key];
}

const user = { name: "Alice", age: 25, email: "alice@ex.com" };
const userName = getProperty(user, "name");  // string
const userAge = getProperty(user, "age");    // number
// getProperty(user, "unknown"); // [X] Erreur ! "unknown" n'est pas une clé de user


// ----------------------------------------------------------------------------
// [CONSTRUCTION] CLASSES GÉNÉRIQUES
// ----------------------------------------------------------------------------

/*
Structure de données générique : Stack (Pile LIFO)
*/

class Stack<T> {
  private items: T[] = [];

  push(item: T): void {
    this.items.push(item);
  }

  pop(): T | undefined {
    return this.items.pop();
  }

  peek(): T | undefined {
    return this.items[this.items.length - 1];
  }

  isEmpty(): boolean {
    return this.items.length === 0;
  }

  get size(): number {
    return this.items.length;
  }
}

// Stack de numbers
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
numberStack.push(3);
console.log(numberStack.pop()); // 3

// Stack de strings
const stringStack = new Stack<string>();
stringStack.push("hello");
stringStack.push("world");
console.log(stringStack.peek()); // "world"

// Stack d'objets complexes
interface Task {
  id: number;
  name: string;
  priority: "high" | "medium" | "low";
}

const taskStack = new Stack<Task>();
taskStack.push({ id: 1, name: "Tâche urgente", priority: "high" });

/*
File d'attente générique (Queue FIFO)
*/

class Queue<T> {
  private items: T[] = [];

  enqueue(item: T): void {
    this.items.push(item);
  }

  dequeue(): T | undefined {
    return this.items.shift();
  }

  front(): T | undefined {
    return this.items[0];
  }

  isEmpty(): boolean {
    return this.items.length === 0;
  }

  get size(): number {
    return this.items.length;
  }

  toArray(): T[] {
    return [...this.items];
  }
}


// ----------------------------------------------------------------------------
// [VERROUILLE] CONTRAINTES DE GÉNÉRIQUES (EXTENDS)
// ----------------------------------------------------------------------------

/*
CONTRAINTE = Limiter les types acceptés par un générique

Pourquoi ? Pour s'assurer que T a certaines propriétés disponibles.
*/

// [X] SANS contrainte : T pourrait ne pas avoir .length
function logLength<T>(arg: T): void {
  // console.log(arg.length); // [X] Erreur ! T n'a peut-être pas .length
}

// [OK] AVEC contrainte : T doit avoir .length
interface HasLength {
  length: number;
}

function logLengthConstrained<T extends HasLength>(arg: T): T {
  console.log(`Longueur: ${arg.length}`);
  return arg;
}

logLengthConstrained("hello");          // [OK] string a .length
logLengthConstrained([1, 2, 3]);        // [OK] array a .length
logLengthConstrained({ length: 5, data: "test" }); // [OK] objet avec .length
// logLengthConstrained(42);            // [X] Erreur ! number n'a pas .length

// Contrainte avec keyof
function copyProperty<T, K extends keyof T>(source: T, key: K): T[K] {
  return source[key];
}

// Contrainte sur le retour d'une factory function
function createFactory<T>(factory: () => T): T {
  return factory();
}

const user2 = createFactory(() => ({ name: "Alice", age: 25 }));
// TypeScript infère le type de user2 correctement

// Contraintes multiples (intersection)
interface Serializable2 {
  serialize(): string;
}

interface Identifiable {
  id: string;
}

function saveEntity<T extends Serializable2 & Identifiable>(entity: T): void {
  console.log(`Sauvegarde de ${entity.id}: ${entity.serialize()}`);
}


// ----------------------------------------------------------------------------
// [OUTIL] PARAMÈTRES PAR DÉFAUT GÉNÉRIQUES
// ----------------------------------------------------------------------------

/*
Un paramètre de type peut avoir une valeur par défaut
*/

interface PaginatedResult<T, Meta = { total: number; page: number }> {
  //                         ^ Meta a un type par défaut
  data: T[];
  meta: Meta;
}

// Avec le type Meta par défaut
type UserResult = PaginatedResult<User>;

// Avec un type Meta personnalisé
type CustomResult = PaginatedResult<User, { total: number; cursor: string }>;


// ----------------------------------------------------------------------------
// [OUTILS] TYPES GÉNÉRIQUES UTILITAIRES (BUILT-IN)
// ----------------------------------------------------------------------------

/*
TypeScript fournit des types génériques utilitaires intégrés.
Ces types permettent de transformer d'autres types.

On les couvrira plus en détail en Partie 3, voici un aperçu :
*/

interface UserProfile {
  id: number;
  name: string;
  email: string;
  age: number;
}

// Partial<T> : Toutes les propriétés deviennent optionnelles
type PartialUser = Partial<UserProfile>;
const updateData: PartialUser = { name: "Bob" }; // Seulement name, c'est OK

// Required<T> : Toutes les propriétés deviennent obligatoires
type RequiredUser = Required<PartialUser>;

// Readonly<T> : Toutes les propriétés deviennent readonly
type ReadonlyUser = Readonly<UserProfile>;

// Pick<T, K> : Garder seulement certaines propriétés
type UserPreview = Pick<UserProfile, "id" | "name">;
const preview: UserPreview = { id: 1, name: "Alice" };

// Omit<T, K> : Exclure certaines propriétés
type PublicUser = Omit<UserProfile, "email" | "age">;


// ============================================================================
// [GUIDE] CHAPITRE 6 : ENUMS ET TUPLES AVANCÉS
// ============================================================================

/*
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Créer et utiliser des enums numériques et string
[OK] Comprendre les const enums et leur optimisation
[OK] Utiliser les enums d'accès inversé
[OK] Choisir entre enum, type union et const object
[OK] Maîtriser les tuples avancés
*/


// ----------------------------------------------------------------------------
// [NOMBRE] ENUMS NUMÉRIQUES
// ----------------------------------------------------------------------------

/*
ENUM = Ensemble de CONSTANTES NOMMÉES

Les enums numériques commencent à 0 par défaut et s'incrémentent.
*/

enum Direction {
  North,  // 0
  South,  // 1
  East,   // 2
  West    // 3
}

const move = Direction.North;
console.log(move);                    // 0
console.log(Direction[0]);            // "North" (accès inversé !)
console.log(Direction.North === 0);  // true

// Valeurs personnalisées
enum StatusCode {
  OK = 200,
  Created = 201,
  BadRequest = 400,
  Unauthorized = 401,
  NotFound = 404,
  ServerError = 500
}

function handleResponse(code: StatusCode): string {
  switch (code) {
    case StatusCode.OK: return "Succès";
    case StatusCode.NotFound: return "Non trouvé";
    case StatusCode.ServerError: return "Erreur serveur";
    default: return "Statut inconnu";
  }
}

console.log(handleResponse(StatusCode.OK));        // Succès
console.log(handleResponse(StatusCode.NotFound));  // Non trouvé

/*
[IDEE] ACCÈS INVERSÉ :
Les enums numériques permettent d'accéder au nom depuis la valeur !
Direction[0] -> "North"
Direction["North"] -> 0
*/


// ----------------------------------------------------------------------------
// [TEXTE] ENUMS STRING
// ----------------------------------------------------------------------------

/*
ENUM STRING = Chaque membre a une valeur string explicite

Avantages sur les numériques :
- Plus lisible dans les logs et débogage
- Pas d'accès inversé (moins de confusion)
- Sérialisation JSON claire
*/

enum Role {
  Admin = "ADMIN",
  Moderator = "MODERATOR",
  User = "USER",
  Guest = "GUEST"
}

const userRole: Role = Role.Admin;
console.log(userRole);                // "ADMIN" (lisible !)
console.log(userRole === "ADMIN");   // true !

// Utilisation pratique
function hasPermission(userRole: Role, requiredRole: Role): boolean {
  const hierarchy = [Role.Guest, Role.User, Role.Moderator, Role.Admin];
  return hierarchy.indexOf(userRole) >= hierarchy.indexOf(requiredRole);
}

console.log(hasPermission(Role.Admin, Role.User));       // true
console.log(hasPermission(Role.User, Role.Admin));       // false

enum FilePermission {
  Read = "READ",
  Write = "WRITE",
  Execute = "EXECUTE"
}


// ----------------------------------------------------------------------------
// [RAPIDE] CONST ENUMS
// ----------------------------------------------------------------------------

/*
CONST ENUM = Enum optimisé : Les valeurs sont INLINÉES à la compilation
             Le code généré est plus léger.

Quand utiliser ?
-> Quand vous n'avez pas besoin de l'enum au runtime (accès inversé, itération)
-> Pour de meilleures performances
*/

const enum Season {
  Spring = "SPRING",
  Summer = "SUMMER",
  Autumn = "AUTUMN",
  Winter = "WINTER"
}

const currentSeason = Season.Summer;
// TypeScript compile ça en : const currentSeason = "SUMMER";
// L'objet Season n'existe PAS au runtime !

/*
JavaScript généré :
const currentSeason = "SUMMER"; // Plus d'objet Season !

VS enum normal :
const currentSeason = Season.Summer; // Référence à l'objet Season
*/

/*
[ATTENTION] LIMITATION des const enums :
- Ne peuvent pas être itérés
- Pas d'accès inversé (Direction[0])
- Ne peuvent pas être utilisés dans certains contextes dynamiques
*/


// ----------------------------------------------------------------------------
// [NOMBRE] ENUMS HÉTÉROGÈNES ET COMPUTED
// ----------------------------------------------------------------------------

/*
HÉTÉROGÈNE = Mix de valeurs numériques et string
-> Généralement déconseillé pour la clarté
*/

enum Mixed {
  No = 0,
  Yes = "YES",
}

/*
COMPUTED = Valeur calculée
*/

enum FileAccess {
  None,
  Read = 1 << 1,    // 2 (bitshift)
  Write = 1 << 2,   // 4
  ReadWrite = Read | Write, // 6
  G = "123".length  // Valeur calculée
}


// ----------------------------------------------------------------------------
// 🆚 ENUM vs UNION TYPE vs CONST OBJECT
// ----------------------------------------------------------------------------

/*
TROIS FAÇONS DE REPRÉSENTER DES VALEURS FIXES :
*/

// 1. ENUM (TypeScript)
enum ColorEnum {
  Red = "RED",
  Green = "GREEN",
  Blue = "BLUE"
}

// 2. UNION TYPE (TypeScript)
type ColorUnion = "RED" | "GREEN" | "BLUE";

// 3. CONST OBJECT (JavaScript + TypeScript)
const Colors = {
  Red: "RED",
  Green: "GREEN",
  Blue: "BLUE"
} as const;
type ColorObject = typeof Colors[keyof typeof Colors]; // "RED" | "GREEN" | "BLUE"

/*
┌─────────────────────┬─────────────┬─────────────┬─────────────────┐
│                     │ Enum        │ Union Type  │ Const Object    │
├─────────────────────┼─────────────┼─────────────┼─────────────────┤
│ Code JS généré      │ Oui (objet) │ Non (effacé)│ Oui (objet)     │
│ Itérable            │ [OK]          │ [X]          │ [OK]              │
│ Accès inversé       │ [OK] (numéric)│ [X]          │ Partiel         │
│ Extensible          │ [X]          │ [OK] (union)  │ [OK]              │
│ Lisibilité          │ [OK]          │ [OK]          │ [OK]              │
│ Tree-shaking        │ [X]          │ [OK]          │ Partiel         │
│ JSON compatible     │ [X] (besoin  │ [OK]          │ [OK]              │
│                     │  de mapping)│             │                 │
└─────────────────────┴─────────────┴─────────────┴─────────────────┘

RECOMMANDATIONS :

[OK] Utilisez UNION TYPE quand :
- Petit ensemble de valeurs string
- Pas besoin d'itération
- Intégration JSON directe
- Bibliothèque partagée

[OK] Utilisez CONST OBJECT quand :
- Besoin d'itérer les valeurs
- Besoin de valeurs et labels séparés
- Compatibilité JavaScript pure

[OK] Utilisez ENUM quand :
- Grande équipe, code très structuré
- Besoin d'accès inversé numérique
- Domaine métier fort (statuts, rôles)
- Préférence d'équipe
*/


// ----------------------------------------------------------------------------
// [OBJECTIF] TUPLES AVANCÉS
// ----------------------------------------------------------------------------

/*
On a vu les tuples de base en Partie 1.
Voici les fonctionnalités avancées.
*/

// Tuples variadic (TypeScript 4.0+)
type Strings = [string, ...string[]];        // Au moins 1 string
type NumbersThenStrings = [...number[], ...string[]]; // Non autorisé !
type NumberThenStrings = [number, ...string[]]; // 1 number, puis strings

// Labeled Tuples (TypeScript 4.0+)
type Range = [start: number, end: number];
type RGB = [red: number, green: number, blue: number];

const color: RGB = [255, 128, 0];
const [r, g, b] = color; // Destructuring avec noms sémantiques

// Tuple readonly
type ReadonlyPoint = readonly [number, number];
const point: ReadonlyPoint = [10, 20];
// point[0] = 5; // [X] Erreur !

// Exemple pratique : Résultats de fonctions
type Success<T> = [data: T, error: null];
type Failure = [data: null, error: Error];
type Result<T> = Success<T> | Failure;

function fetchUser(id: number): Result<{ name: string }> {
  if (id > 0) {
    return [{ name: "Alice" }, null];
  }
  return [null, new Error("ID invalide")];
}

const [userData, userError] = fetchUser(1);
if (userError === null) {
  console.log(userData.name); // TypeScript sait que userData n'est pas null ici !
}


// ----------------------------------------------------------------------------
// [COURS] EXERCICE PRATIQUE 2 : SYSTÈME DE BIBLIOTHÈQUE
// ----------------------------------------------------------------------------

/*
[OBJECTIF] OBJECTIF :
Créer un système de bibliothèque avec classes, génériques et enums.

[LISTE] CAHIER DES CHARGES :

1. ENUMS :
   - BookStatus : "AVAILABLE" | "BORROWED" | "RESERVED" | "LOST"
   - MemberType : "STUDENT" | "TEACHER" | "STAFF"

2. INTERFACES :
   - LibraryItem<T> : générique, id, title, type, data: T
   - BorrowRecord : memberId, itemId, borrowDate, returnDate?

3. CLASSES :
   - Library<T> : Classe générique de gestion
     -> items: Map<string, LibraryItem<T>>
     -> addItem(item)
     -> removeItem(id)
     -> findById(id)
     -> findByTitle(title)
     -> getAllAvailable()
   
   - Member : id, name, type(MemberType), borrowedItems
     -> canBorrow(): boolean (max 3 pour student, 5 pour teacher)
     -> borrow(item)
     -> return(itemId)

4. Démonstration avec livres et DVDs

[TEMPS] TEMPS ESTIMÉ : 45-60 minutes
*/

// SOLUTION :

// 1. Enums
enum BookStatus {
  Available = "AVAILABLE",
  Borrowed = "BORROWED",
  Reserved = "RESERVED",
  Lost = "LOST"
}

enum MemberType {
  Student = "STUDENT",
  Teacher = "TEACHER",
  Staff = "STAFF"
}

// Limits par type de membre
const BORROW_LIMITS: Record<MemberType, number> = {
  [MemberType.Student]: 3,
  [MemberType.Teacher]: 5,
  [MemberType.Staff]: 4
};

// 2. Interfaces
interface LibraryItem<T> {
  id: string;
  title: string;
  status: BookStatus;
  data: T;
}

interface BorrowRecord {
  memberId: string;
  itemId: string;
  borrowDate: Date;
  returnDate?: Date;
}

// Types spécifiques pour les données
interface BookData {
  author: string;
  isbn: string;
  pages: number;
  genre: string;
}

interface DVDData {
  director: string;
  duration: number; // minutes
  rating: string;
}

// 3. Classe générique Library
class Library<T> {
  private items: Map<string, LibraryItem<T>> = new Map();
  private borrowRecords: BorrowRecord[] = [];

  addItem(item: LibraryItem<T>): void {
    this.items.set(item.id, item);
    console.log(`[OK] Ajouté: "${item.title}"`);
  }

  removeItem(id: string): boolean {
    const item = this.items.get(id);
    if (!item) {
      console.log(`[X] Item ${id} non trouvé`);
      return false;
    }
    if (item.status === BookStatus.Borrowed) {
      console.log(`[X] Impossible de supprimer: item emprunté`);
      return false;
    }
    this.items.delete(id);
    return true;
  }

  findById(id: string): LibraryItem<T> | undefined {
    return this.items.get(id);
  }

  findByTitle(title: string): LibraryItem<T>[] {
    const searchLower = title.toLowerCase();
    return Array.from(this.items.values())
      .filter(item => item.title.toLowerCase().includes(searchLower));
  }

  getAllAvailable(): LibraryItem<T>[] {
    return Array.from(this.items.values())
      .filter(item => item.status === BookStatus.Available);
  }

  borrowItem(itemId: string, memberId: string): boolean {
    const item = this.items.get(itemId);
    if (!item) {
      console.log(`[X] Item ${itemId} non trouvé`);
      return false;
    }
    if (item.status !== BookStatus.Available) {
      console.log(`[X] Item "${item.title}" non disponible (${item.status})`);
      return false;
    }
    
    item.status = BookStatus.Borrowed;
    this.borrowRecords.push({
      memberId,
      itemId,
      borrowDate: new Date()
    });
    console.log(`[GUIDE] "${item.title}" emprunté par ${memberId}`);
    return true;
  }

  returnItem(itemId: string): boolean {
    const item = this.items.get(itemId);
    if (!item || item.status !== BookStatus.Borrowed) return false;
    
    item.status = BookStatus.Available;
    const record = this.borrowRecords.find(
      r => r.itemId === itemId && !r.returnDate
    );
    if (record) record.returnDate = new Date();
    
    console.log(`[OK] "${item.title}" retourné`);
    return true;
  }

  getStats(): { total: number; available: number; borrowed: number } {
    const allItems = Array.from(this.items.values());
    return {
      total: allItems.length,
      available: allItems.filter(i => i.status === BookStatus.Available).length,
      borrowed: allItems.filter(i => i.status === BookStatus.Borrowed).length
    };
  }
}

// Classe Member
class Member {
  private _borrowedItemIds: string[] = [];
  readonly id: string;

  constructor(
    public name: string,
    public type: MemberType,
    id?: string
  ) {
    this.id = id || `MBR-${Date.now()}`;
  }

  get borrowedCount(): number {
    return this._borrowedItemIds.length;
  }

  get maxBorrows(): number {
    return BORROW_LIMITS[this.type];
  }

  canBorrow(): boolean {
    return this._borrowedItemIds.length < this.maxBorrows;
  }

  borrow(itemId: string): void {
    if (!this.canBorrow()) {
      throw new Error(`${this.name} a atteint la limite d'emprunt (${this.maxBorrows})`);
    }
    this._borrowedItemIds.push(itemId);
  }

  return(itemId: string): boolean {
    const index = this._borrowedItemIds.indexOf(itemId);
    if (index === -1) return false;
    this._borrowedItemIds.splice(index, 1);
    return true;
  }

  describe(): string {
    return `${this.name} (${this.type}) - ${this.borrowedCount}/${this.maxBorrows} emprunts`;
  }
}

// 4. DÉMONSTRATION
const bookLibrary = new Library<BookData>();

// Ajouter des livres
bookLibrary.addItem({
  id: "B001",
  title: "Le Guide TypeScript",
  status: BookStatus.Available,
  data: { author: "Anders Hejlsberg", isbn: "978-0000000001", pages: 450, genre: "Informatique" }
});

bookLibrary.addItem({
  id: "B002",
  title: "Design Patterns",
  status: BookStatus.Available,
  data: { author: "Gang of Four", isbn: "978-0000000002", pages: 395, genre: "Informatique" }
});

bookLibrary.addItem({
  id: "B003",
  title: "Clean Code",
  status: BookStatus.Available,
  data: { author: "Robert C. Martin", isbn: "978-0000000003", pages: 431, genre: "Informatique" }
});

// Créer des membres
const student = new Member("Alice Dupont", MemberType.Student, "MBR-001");
const teacher = new Member("Prof. Martin", MemberType.Teacher, "MBR-002");

console.log("\n=== BIBLIOTHÈQUE STATS ===");
console.log(bookLibrary.getStats());

console.log("\n=== EMPRUNTS ===");
bookLibrary.borrowItem("B001", student.id);
student.borrow("B001");
console.log(student.describe()); // Alice Dupont (STUDENT) - 1/3 emprunts

bookLibrary.borrowItem("B002", teacher.id);
teacher.borrow("B002");

console.log("\n=== DISPONIBLES ===");
bookLibrary.getAllAvailable().forEach(b => console.log(`  - "${b.title}"`));

console.log("\n=== RETOUR ===");
bookLibrary.returnItem("B001");
student.return("B001");
console.log(student.describe()); // Alice Dupont (STUDENT) - 0/3 emprunts

console.log("\n=== STATS FINALES ===");
console.log(bookLibrary.getStats());


// ----------------------------------------------------------------------------
// [DOCS] RÉCAPITULATIF PARTIE 2
// ----------------------------------------------------------------------------

/*
CE QUE VOUS AVEZ APPRIS :

[OK] Chapitre 4 : Classes et POO
   - Déclaration de classes avec propriétés typées
   - Modificateurs d'accès (public, private, protected, readonly)
   - Raccourci paramètres de constructeur
   - Getters et Setters
   - Héritage (extends) et super()
   - Classes abstraites (abstract)
   - Implements d'interfaces
   - Membres statiques (static)

[OK] Chapitre 5 : Génériques
   - Pourquoi les génériques (réutilisabilité + type safety)
   - Fonctions génériques <T>
   - Classes génériques
   - Contraintes extends
   - Paramètres de type par défaut
   - Types utilitaires (Partial, Required, Readonly, Pick, Omit)

[OK] Chapitre 6 : Enums
   - Enums numériques (avec auto-incrément et accès inversé)
   - Enums string (recommandés)
   - Const enums (optimisation)
   - Enum vs Union Type vs Const Object
   - Tuples avancés et variadic tuples


[CLE] POINTS CLÉS :

1. Utilisez le raccourci de constructeur (public name: string)
2. private pour l'encapsulation, protected pour l'héritage
3. Les génériques = réutilisabilité SANS perdre le type
4. Contraintes extends pour limiter les types génériques
5. Les enums string sont plus lisibles que les numériques
6. Préférez souvent les union types aux enums (plus léger)


-> PROCHAINE ÉTAPE : PARTIE 3 - Types Avancés
*/

// ============================================================================
// [LIVRE] TYPESCRIPT - PARTIE 3 : TYPES AVANCÉS
// ============================================================================
//
// [OBJECTIF] CETTE PARTIE COUVRE :
// - Chapitre 7 : Union, Intersection et Type Guards
// - Chapitre 8 : Types Utilitaires (Utility Types)
// - Chapitre 9 : Types Conditionnels et Mapped Types
//
// [TEMPS] TEMPS : ~6-8 heures
// [DOCS] PRÉREQUIS : Parties 1 et 2 complétées
// ============================================================================


// ============================================================================
// [GUIDE] CHAPITRE 7 : UNION, INTERSECTION ET TYPE GUARDS
// ============================================================================

/*
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Utiliser les types union (A | B) efficacement
[OK] Utiliser les types intersection (A & B)
[OK] Discriminer les unions avec des propriétés discriminantes
[OK] Utiliser les type guards (typeof, instanceof, in)
[OK] Créer des type guards personnalisés
[OK] Comprendre le narrowing
*/


// ----------------------------------------------------------------------------
// [MELANGE] UNION TYPES (A | B)
// ----------------------------------------------------------------------------

/*
UNION TYPE = "Ce type peut être A OU B OU C"

Syntaxe : TypeA | TypeB | TypeC
*/

// Union de primitifs
type StringOrNumber = string | number;
type IdType = string | number;

function formatId(id: IdType): string {
  // TypeScript sait que id peut être string OU number
  if (typeof id === "string") {
    return id.toUpperCase(); // TypeScript sait que c'est string ici
  }
  return id.toString();     // TypeScript sait que c'est number ici
}

// Union avec objets
interface Cat {
  type: "cat";
  name: string;
  meow(): string;
}

interface Dog {
  type: "dog";
  name: string;
  bark(): string;
}

type Pet = Cat | Dog;

// Sans discrimination, seules les propriétés COMMUNES sont accessibles
function greetPet(pet: Pet): string {
  return `Bonjour ${pet.name}`; // name est commun aux deux
  // pet.meow(); // [X] Erreur ! meow n'est pas commun
}

/*
[IDEE] PROPRIÉTÉ DISCRIMINANTE (Discriminant Property)

Une propriété dont le TYPE LITTÉRAL permet de distinguer les membres d'une union.
Dans l'exemple : type: "cat" | type: "dog"

Cela s'appelle une "Discriminated Union" (Union Discriminée)
C'est un pattern FONDAMENTAL en TypeScript !
*/

function handlePet(pet: Pet): string {
  switch (pet.type) {
    case "cat":
      return pet.meow(); // TypeScript sait que c'est un Cat ici !
    case "dog":
      return pet.bark(); // TypeScript sait que c'est un Dog ici !
  }
}


// ----------------------------------------------------------------------------
// [LIEN] INTERSECTION TYPES (A & B)
// ----------------------------------------------------------------------------

/*
INTERSECTION TYPE = "Ce type doit satisfaire A ET B EN MÊME TEMPS"

Syntaxe : TypeA & TypeB

Penser : fusion / mélange de types
*/

interface HasName {
  name: string;
}

interface HasAge {
  age: number;
}

interface HasEmail {
  email: string;
}

// Intersection : doit avoir name ET age ET email
type FullPerson = HasName & HasAge & HasEmail;

const person: FullPerson = {
  name: "Alice",
  age: 25,
  email: "alice@example.com"
  // Toutes les propriétés sont OBLIGATOIRES
};

// Pattern courant : Composer des types existants
interface BaseEntity {
  id: string;
  createdAt: Date;
  updatedAt: Date;
}

interface UserData {
  name: string;
  email: string;
  role: string;
}

// User = BaseEntity + UserData
type User = BaseEntity & UserData;

// Avec les génériques
type Timestamped<T> = T & {
  createdAt: Date;
  updatedAt: Date;
};

interface Post {
  title: string;
  content: string;
}

type TimestampedPost = Timestamped<Post>;
// = { title: string; content: string; createdAt: Date; updatedAt: Date; }

/*
[IDEE] UNION vs INTERSECTION :

UNION  (A | B) :
-> Soit A, soit B (pas besoin d'avoir tout)
-> Seules les propriétés communes sont accessibles sans narrowing
-> "ou" logique

INTERSECTION (A & B) :
-> A ET B à la fois (doit avoir tout)
-> Toutes les propriétés des deux types sont accessibles
-> "et" logique
*/


// ----------------------------------------------------------------------------
// [RECHERCHE] TYPE GUARDS (GARDES DE TYPE)
// ----------------------------------------------------------------------------

/*
TYPE GUARD = Code qui PROUVE à TypeScript qu'une valeur est d'un certain type
             TypeScript rétrécit (narrow) le type dans le bloc conditionnel

5 techniques de type guards :
1. typeof
2. instanceof
3. in
4. Discriminant property
5. Type guard personnalisé (user-defined)
*/

// 1. TYPEOF ─────────────────────────────
function processValue(value: string | number | boolean): string {
  if (typeof value === "string") {
    // TypeScript sait : value est string ici
    return value.toUpperCase();
  }
  if (typeof value === "number") {
    // TypeScript sait : value est number ici
    return value.toFixed(2);
  }
  // TypeScript sait : value est boolean ici (seul cas restant)
  return value ? "vrai" : "faux";
}

/*
[IDEE] typeof fonctionne avec :
"string", "number", "boolean", "symbol", "bigint", "undefined", "function", "object"

[ATTENTION] ATTENTION : typeof null === "object" (bug historique JavaScript !)
-> Toujours vérifier !== null séparément pour les objets
*/

// 2. INSTANCEOF ──────────────────────────
class ApiError extends Error {
  constructor(message: string, public statusCode: number) {
    super(message);
    this.name = "ApiError";
  }
}

class ValidationError extends Error {
  constructor(message: string, public field: string) {
    super(message);
    this.name = "ValidationError";
  }
}

function handleError(error: Error | ApiError | ValidationError): string {
  if (error instanceof ApiError) {
    // TypeScript sait : error est ApiError ici
    return `API Error ${error.statusCode}: ${error.message}`;
  }
  if (error instanceof ValidationError) {
    // TypeScript sait : error est ValidationError ici
    return `Validation Error on field '${error.field}': ${error.message}`;
  }
  // TypeScript sait : error est Error de base ici
  return `Error: ${error.message}`;
}

/*
[IDEE] instanceof fonctionne avec les classes
   Vérifie la chaîne de prototypes
*/

// 3. IN (VÉRIFIER SI UNE PROPRIÉTÉ EXISTE) ─────────
interface Fish {
  swim(): void;
  fins: number;
}

interface Bird {
  fly(): void;
  wings: number;
}

type Animal2 = Fish | Bird;

function makeMove(animal: Animal2): void {
  if ("swim" in animal) {
    // TypeScript sait : animal est Fish ici
    animal.swim();
  } else {
    // TypeScript sait : animal est Bird ici
    animal.fly();
  }
}

// 4. PROPRIÉTÉ DISCRIMINANTE ─────────────
interface LoadingState {
  status: "loading";
}

interface SuccessState<T> {
  status: "success";
  data: T;
}

interface ErrorState {
  status: "error";
  error: string;
}

type AsyncState<T> = LoadingState | SuccessState<T> | ErrorState;

function renderState<T>(state: AsyncState<T>): string {
  switch (state.status) {
    case "loading":
      return "Chargement...";
    case "success":
      return `Données: ${JSON.stringify(state.data)}`; // data accessible !
    case "error":
      return `Erreur: ${state.error}`; // error accessible !
    // TypeScript vérifie que tous les cas sont couverts !
  }
}


// ----------------------------------------------------------------------------
// [EDIT] TYPE GUARDS PERSONNALISÉS
// ----------------------------------------------------------------------------

/*
TYPE PREDICATE = Fonction qui retourne `x is Type`
                 Quand elle retourne true, TypeScript sait que x est de ce type
*/

// Syntaxe : function isXxx(value: unknown): value is XxxType
function isString(value: unknown): value is string {
  return typeof value === "string";
}

function isNumber(value: unknown): value is number {
  return typeof value === "number" && !isNaN(value);
}

// Type guard pour interface
interface User {
  id: number;
  name: string;
  email: string;
}

function isUser(value: unknown): value is User {
  return (
    typeof value === "object" &&
    value !== null &&
    "id" in value &&
    "name" in value &&
    "email" in value &&
    typeof (value as any).id === "number" &&
    typeof (value as any).name === "string" &&
    typeof (value as any).email === "string"
  );
}

// Utilisation
function processData(data: unknown): void {
  if (isUser(data)) {
    // TypeScript sait que data est User ici !
    console.log(`User: ${data.name} (${data.email})`);
  }
}

/*
[IDEE] ASSERTION FUNCTIONS (TypeScript 3.7+)

function assert(condition: boolean): asserts condition
-> Si la fonction ne lance pas d'erreur, TypeScript sait que la condition est vraie
*/

function assertIsString(value: unknown): asserts value is string {
  if (typeof value !== "string") {
    throw new Error(`Expected string, got ${typeof value}`);
  }
}

function processString(input: unknown): void {
  assertIsString(input);
  // TypeScript sait que input est string ici !
  console.log(input.toUpperCase());
}


// ----------------------------------------------------------------------------
// [ANALYSE] NARROWING AVANCÉ
// ----------------------------------------------------------------------------

/*
NARROWING = TypeScript "rétrécit" le type en analysant le code
*/

// Equality narrowing
function example1(x: string | number, y: string | boolean): void {
  if (x === y) {
    // TypeScript sait que x et y sont string (seul type commun !)
    console.log(x.toUpperCase());
  }
}

// Truthiness narrowing
function example2(value: string | null | undefined): string {
  if (value) {
    // TypeScript sait que value est string (truthy = pas null/undefined/"")
    return value.toUpperCase();
  }
  return "valeur par défaut";
}

// Nullish coalescing avec narrowing
function getUsername(user: { name?: string } | null): string {
  return user?.name ?? "Anonyme";
  // ?. (optional chaining) + ?? (nullish coalescing)
}

// Never pour vérification exhaustive
type Shape = 
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number }
  | { kind: "triangle"; base: number; height: number };

function calculateArea(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "square":
      return shape.side ** 2;
    case "triangle":
      return (shape.base * shape.height) / 2;
    default:
      // Si on ajoute un nouveau Shape et oublie ce cas, TypeScript donne une erreur !
      const _exhaustiveCheck: never = shape;
      throw new Error(`Type de forme inconnu: ${JSON.stringify(_exhaustiveCheck)}`);
  }
}


// ============================================================================
// [GUIDE] CHAPITRE 8 : TYPES UTILITAIRES (UTILITY TYPES)
// ============================================================================

/*
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Utiliser tous les types utilitaires TypeScript
[OK] Transformer des types avec Partial, Required, Readonly
[OK] Filtrer des propriétés avec Pick, Omit
[OK] Créer des types depuis des valeurs avec Record
[OK] Utiliser les types avancés Exclude, Extract, NonNullable
*/


// ----------------------------------------------------------------------------
// [PACKAGE] TYPES DE MODIFICATION DE PROPRIÉTÉS
// ----------------------------------------------------------------------------

/*
Types qui modifient les MODIFICATEURS de propriétés existantes
*/

interface BlogPost {
  id: number;
  title: string;
  content: string;
  author: string;
  publishedAt: Date;
  tags: string[];
}

// PARTIAL<T> : Toutes propriétés optionnelles ────────────────
type PartialPost = Partial<BlogPost>;
// = { id?: number; title?: string; content?: string; ... }

// Utile pour les mises à jour partielles
function updatePost(id: number, updates: Partial<BlogPost>): BlogPost {
  // On peut passer seulement les champs à mettre à jour
  const currentPost: BlogPost = fetchPost(id); // hypothétique
  return { ...currentPost, ...updates };
}

function fetchPost(id: number): BlogPost {
  return { id, title: "Test", content: "...", author: "Alice", publishedAt: new Date(), tags: [] };
}

updatePost(1, { title: "Nouveau titre" }); // Seulement le titre !

// REQUIRED<T> : Toutes propriétés obligatoires ───────────────
interface Config {
  host?: string;
  port?: number;
  ssl?: boolean;
}

type RequiredConfig = Required<Config>;
// = { host: string; port: number; ssl: boolean; }

// READONLY<T> : Toutes propriétés en lecture seule ─────────────
type FrozenPost = Readonly<BlogPost>;
// = { readonly id: number; readonly title: string; ... }

const frozenPost: FrozenPost = {
  id: 1, title: "Test", content: "...", author: "Alice",
  publishedAt: new Date(), tags: []
};
// frozenPost.title = "Changed"; // [X] Erreur !


// ----------------------------------------------------------------------------
// [OBJECTIF] TYPES DE SÉLECTION DE PROPRIÉTÉS
// ----------------------------------------------------------------------------

// PICK<T, K> : Garder seulement les propriétés K ─────────────
type PostPreview = Pick<BlogPost, "id" | "title" | "author">;
// = { id: number; title: string; author: string; }

const preview: PostPreview = {
  id: 1,
  title: "Mon article",
  author: "Alice"
};

// OMIT<T, K> : Exclure les propriétés K ──────────────────────
type PostWithoutId = Omit<BlogPost, "id">;
// = { title: string; content: string; author: string; publishedAt: Date; tags: string[]; }

type PublicPost = Omit<BlogPost, "author" | "id">;
// Retirer infos sensibles pour l'API publique

/*
[IDEE] PICK vs OMIT :

PICK  : "Je veux SEULEMENT ces propriétés"   -> Lister ce qu'on GARDE
OMIT  : "Je veux TOUT SAUF ces propriétés"   -> Lister ce qu'on ENLÈVE

Choisir selon ce qui est plus court/lisible :
- Peu de propriétés à garder -> PICK
- Peu de propriétés à enlever -> OMIT
*/


// ----------------------------------------------------------------------------
// [WORLD_MAP] RECORD<K, V>
// ----------------------------------------------------------------------------

/*
RECORD<K, V> : Objet dont les clés sont de type K et les valeurs de type V
*/

// Record avec union de clés
type CountByDay = Record<"Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday", number>;

const weeklyCount: CountByDay = {
  Monday: 5,
  Tuesday: 3,
  Wednesday: 7,
  Thursday: 4,
  Friday: 6
};

// Record pour cache
type UserCache = Record<string, User>;
const cache: UserCache = {};

// Record pour config
type FeatureFlags = Record<string, boolean>;
const features: FeatureFlags = {
  darkMode: true,
  notifications: false,
  betaFeatures: true
};

// Pattern courant : Grouper par clé
type GroupedItems<T> = Record<string, T[]>;

function groupBy<T>(items: T[], key: keyof T): GroupedItems<T> {
  return items.reduce((groups, item) => {
    const groupKey = String(item[key]);
    return {
      ...groups,
      [groupKey]: [...(groups[groupKey] || []), item]
    };
  }, {} as GroupedItems<T>);
}


// ----------------------------------------------------------------------------
// [OUTIL] TYPES UTILITAIRES SUR LES UNIONS
// ----------------------------------------------------------------------------

// EXCLUDE<T, U> : Exclure les membres U de T ─────────────────
type AllStatuses = "pending" | "active" | "inactive" | "deleted";
type ActiveStatuses = Exclude<AllStatuses, "deleted" | "inactive">;
// = "pending" | "active"

type NonString = Exclude<string | number | boolean, string>;
// = number | boolean

// EXTRACT<T, U> : Garder seulement les membres dans T ET U ────
type ExtractedStatuses = Extract<AllStatuses, "pending" | "active" | "unknown">;
// = "pending" | "active" (seulement les membres communs)

// NONNULLABLE<T> : Exclure null et undefined ─────────────────
type MaybeString = string | null | undefined;
type DefinitelyString = NonNullable<MaybeString>;
// = string

function processNonNull<T>(value: NonNullable<T>): T {
  return value;
}


// ----------------------------------------------------------------------------
// [OUTIL] TYPES UTILITAIRES POUR FONCTIONS
// ----------------------------------------------------------------------------

// Soit ces fonctions/classes :
function fetchUserAsync(id: number): Promise<User> {
  return Promise.resolve({ id, name: "Alice", email: "alice@example.com" });
}

class HttpService {
  get(url: string): Promise<unknown> {
    return fetch(url).then(r => r.json());
  }
}

// RETURNTYPE<T> : Extraire le type de retour d'une fonction ──
type FetchResult = ReturnType<typeof fetchUserAsync>;
// = Promise<User>

type SimpleResult = ReturnType<() => { x: number; y: number }>;
// = { x: number; y: number }

// PARAMETERS<T> : Extraire les types des paramètres ──────────
type FetchParams = Parameters<typeof fetchUserAsync>;
// = [id: number]

function callFunction<T extends (...args: any[]) => any>(
  fn: T,
  args: Parameters<T>
): ReturnType<T> {
  return fn(...args);
}

// INSTANCETYPE<T> : Type de l'instance d'une classe ──────────
type ServiceInstance = InstanceType<typeof HttpService>;
// = HttpService

// CONSTRUCTORPARAMETERS<T> : Params du constructeur ──────────
class Config {
  constructor(public host: string, public port: number) {}
}

type ConfigParams = ConstructorParameters<typeof Config>;
// = [host: string, port: number]


// ----------------------------------------------------------------------------
// [SYNC] AWAITED<T> (TypeScript 4.5+)
// ----------------------------------------------------------------------------

/*
AWAITED<T> : Déroule le type d'une Promise récursive
*/

type ResolvedUser = Awaited<Promise<User>>;
// = User

type ResolvedNested = Awaited<Promise<Promise<string>>>;
// = string (déroule récursivement)

async function fetchData(): Promise<{ data: User[] }> {
  return { data: [] };
}

type FetchReturn = Awaited<ReturnType<typeof fetchData>>;
// = { data: User[] }


// ============================================================================
// [GUIDE] CHAPITRE 9 : TYPES CONDITIONNELS ET MAPPED TYPES
// ============================================================================

/*
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Écrire des types conditionnels (T extends U ? X : Y)
[OK] Utiliser infer pour extraire des types
[OK] Créer des mapped types avec [K in keyof T]
[OK] Modifier les propriétés avec +, -, ?, readonly
[OK] Utiliser les template literal types
*/


// ----------------------------------------------------------------------------
// [?] TYPES CONDITIONNELS
// ----------------------------------------------------------------------------

/*
TYPE CONDITIONNEL = Type calculé selon une condition

Syntaxe : T extends U ? TypeSiVrai : TypeSiFaux

C'est comme le ternaire JavaScript, mais pour les TYPES !
*/

// Exemple de base
type IsString<T> = T extends string ? "oui, c'est string" : "non, pas string";

type Test1 = IsString<string>;   // "oui, c'est string"
type Test2 = IsString<number>;   // "non, pas string"
type Test3 = IsString<"hello">;  // "oui, c'est string" (string literal extends string)

// Type conditionnel pratique
type ArrayElement<T> = T extends Array<infer Item> ? Item : never;

type NumberArrayElement = ArrayElement<number[]>;   // number
type StringArrayElement = ArrayElement<string[]>;   // string
type NotArray = ArrayElement<number>;              // never

// Extraire le type de Promise
type Unpack<T> = T extends Promise<infer Result> ? Result : T;

type UnpackedPromise = Unpack<Promise<string>>;  // string
type UnpackedNormal = Unpack<string>;            // string (pas de Promise)

// Conditionnel distribué (sur unions)
type Flatten<T> = T extends any[] ? T[number] : T;

type Flat1 = Flatten<string[]>;          // string
type Flat2 = Flatten<number>;            // number
type Flat3 = Flatten<string[] | number>; // string | number


// ----------------------------------------------------------------------------
// [RECHERCHE] INFER : EXTRAIRE DES TYPES
// ----------------------------------------------------------------------------

/*
INFER = Demander à TypeScript d'"inférer" (deviner) un type dans une condition

Syntaxe : T extends SomeType<infer U> ? UtiliserU : Autre
          Quand T correspond, TypeScript capture le type dans U
*/

// Extraire le type d'élément d'un tableau
type ElementType<T extends any[]> = T extends (infer E)[] ? E : never;

type Elem1 = ElementType<string[]>;   // string
type Elem2 = ElementType<number[]>;   // number
type Elem3 = ElementType<[string, number]>; // string | number

// Extraire le type de retour (comme ReturnType intégré)
type MyReturnType<T extends (...args: any[]) => any> = 
  T extends (...args: any[]) => infer R ? R : never;

type R1 = MyReturnType<() => string>;     // string
type R2 = MyReturnType<() => number[]>;   // number[]

// Extraire le premier paramètre
type FirstParameter<T extends (...args: any[]) => any> =
  T extends (first: infer F, ...rest: any[]) => any ? F : never;

type P1 = FirstParameter<(x: number, y: string) => void>; // number
type P2 = FirstParameter<(name: string) => boolean>;      // string

// Exemple complexe : décomposer un type Promise imbriqué
type DeepAwaited<T> = 
  T extends Promise<infer U> 
    ? DeepAwaited<U>  // Récursif !
    : T;

type D1 = DeepAwaited<Promise<string>>;              // string
type D2 = DeepAwaited<Promise<Promise<number>>>;     // number
type D3 = DeepAwaited<Promise<Promise<Promise<boolean>>>>;  // boolean


// ----------------------------------------------------------------------------
// [WORLD_MAP] MAPPED TYPES
// ----------------------------------------------------------------------------

/*
MAPPED TYPE = Transformer chaque propriété d'un type existant

Syntaxe : { [K in keyof T]: TransformationType }
*/

// Rendre toutes les propriétés optionnelles (comme Partial mais fait maison)
type MyPartial<T> = {
  [K in keyof T]?: T[K];
  //  ^ Pour chaque clé K dans T  ^ ? = optionnel  ^ Type original
};

// Rendre toutes les propriétés readonly
type MyReadonly<T> = {
  readonly [K in keyof T]: T[K];
  //^ readonly pour chaque propriété
};

// Transformer les types de propriétés
type Nullable<T> = {
  [K in keyof T]: T[K] | null;
  // Chaque propriété peut maintenant être null
};

type Stringify<T> = {
  [K in keyof T]: string;
  // Toutes les propriétés deviennent string
};

// Exemple pratique : Validation form
interface FormFields {
  name: string;
  email: string;
  age: number;
  newsletter: boolean;
}

// Type pour les erreurs de validation
type FormErrors = {
  [K in keyof FormFields]?: string;
  // Chaque champ peut avoir un message d'erreur (optionnel)
};

const errors: FormErrors = {
  email: "Email invalide",
  age: "Doit être >= 18"
  // name et newsletter n'ont pas d'erreur
};


// ----------------------------------------------------------------------------
// [EDIT] MODIFICATEURS DE MAPPED TYPES (+, -)
// ----------------------------------------------------------------------------

/*
On peut AJOUTER (+) ou ENLEVER (-) les modificateurs readonly et ?
*/

// Enlever tous les optional (? )
type RequiredAll<T> = {
  [K in keyof T]-?: T[K];
  //            ^ Le - enlève le ?
};

// Enlever tous les readonly
type Mutable<T> = {
  -readonly [K in keyof T]: T[K];
  //^ Le - enlève readonly
};

interface ReadonlyPartialConfig {
  readonly host?: string;
  readonly port?: number;
  readonly debug?: boolean;
}

type MutableRequired = Mutable<RequiredAll<ReadonlyPartialConfig>>;
// = { host: string; port: number; debug: boolean; }
// Plus readonly, plus optionnel !


// ----------------------------------------------------------------------------
// [SYNC] REMAPPING AVEC AS
// ----------------------------------------------------------------------------

/*
TypeScript 4.1+ : Renommer les clés dans les mapped types
*/

// Transformer les noms de propriétés
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
  //              ^ as = transformer le nom de la clé
};

interface PersonData {
  name: string;
  age: number;
}

type PersonGetters = Getters<PersonData>;
// = {
//   getName: () => string;
//   getAge: () => number;
// }

// Filtrer des propriétés avec as (using never)
type OnlyStrings<T> = {
  [K in keyof T as T[K] extends string ? K : never]: T[K];
  //                                       ^ never = exclure cette propriété
};

interface MixedData {
  name: string;
  age: number;
  email: string;
  isActive: boolean;
}

type StringOnly = OnlyStrings<MixedData>;
// = { name: string; email: string; }
// age (number) et isActive (boolean) sont exclus


// ----------------------------------------------------------------------------
// [NOTE] TEMPLATE LITERAL TYPES
// ----------------------------------------------------------------------------

/*
TEMPLATE LITERAL TYPE = Types construits à partir de strings

Syntaxe : `${TypeA}${TypeB}`
*/

// Type de base
type EventName = "click" | "focus" | "blur" | "change";
type EventHandler = `on${Capitalize<EventName>}`;
// = "onClick" | "onFocus" | "onBlur" | "onChange"

// Générer des types CSS
type CSSUnit = "px" | "em" | "rem" | "vh" | "vw" | "%";
type CSSValue = `${number}${CSSUnit}`;
// = Accepte "10px", "2em", "100vh", etc.

// Route API
type ApiVersion = "v1" | "v2";
type Resource = "users" | "posts" | "comments";
type ApiRoute = `/api/${ApiVersion}/${Resource}`;
// = "/api/v1/users" | "/api/v1/posts" | ... | "/api/v2/comments"

// Combinaison avec keyof pour génération automatique
type EntityEvent<Entity extends string> = 
  `${Lowercase<Entity>}:${"created" | "updated" | "deleted"}`;

type UserEvents = EntityEvent<"User">;
// = "user:created" | "user:updated" | "user:deleted"

type PostEvents = EntityEvent<"Post">;
// = "post:created" | "post:updated" | "post:deleted"

/*
[IDEE] TYPES UTILITAIRES DE MANIPULATION DE STRINGS (intégrés) :

Uppercase<S>    : "hello" -> "HELLO"
Lowercase<S>    : "HELLO" -> "hello"
Capitalize<S>   : "hello" -> "Hello"
Uncapitalize<S> : "Hello" -> "hello"
*/

type Upper = Uppercase<"hello">;       // "HELLO"
type Lower = Lowercase<"WORLD">;       // "world"
type Cap = Capitalize<"typescript">;   // "Typescript"
type Uncap = Uncapitalize<"TypeScript">; // "typeScript"


// ----------------------------------------------------------------------------
// [CONSTRUCTION] EXEMPLE COMPLET : BUILDER DE REQUÊTES TYPÉ
// ----------------------------------------------------------------------------

/*
Construire un query builder fortement typé en utilisant
les types avancés de ce chapitre.
*/

interface Database {
  users: {
    id: number;
    name: string;
    email: string;
    age: number;
    role: "admin" | "user";
  };
  posts: {
    id: number;
    title: string;
    content: string;
    userId: number;
    publishedAt: Date | null;
  };
}

// Type pour les opérateurs de comparaison
type ComparisonOperator = "=" | "!=" | ">" | "<" | ">=" | "<=" | "LIKE" | "IN";

// Type pour un filtre
type Filter<T> = {
  [K in keyof T]?: {
    operator: ComparisonOperator;
    value: T[K];
  };
};

// Type générique pour le query builder
class QueryBuilder<Table extends keyof Database> {
  private table: Table;
  private selectedColumns: Array<keyof Database[Table]> = [];
  private filters: Filter<Database[Table]> = {};
  private limitValue?: number;

  constructor(table: Table) {
    this.table = table;
  }

  select<K extends keyof Database[Table]>(
    ...columns: K[]
  ): QueryBuilder<Table> {
    this.selectedColumns = columns;
    return this;
  }

  where<K extends keyof Database[Table]>(
    column: K,
    operator: ComparisonOperator,
    value: Database[Table][K]
  ): QueryBuilder<Table> {
    this.filters[column] = { operator, value } as any;
    return this;
  }

  limit(n: number): QueryBuilder<Table> {
    this.limitValue = n;
    return this;
  }

  buildSQL(): string {
    const cols = this.selectedColumns.length > 0 
      ? this.selectedColumns.join(", ")
      : "*";
    
    let sql = `SELECT ${cols} FROM ${String(this.table)}`;
    
    const filterEntries = Object.entries(this.filters);
    if (filterEntries.length > 0) {
      const conditions = filterEntries.map(([col, filter]) => {
        const f = filter as { operator: string; value: unknown };
        return `${col} ${f.operator} '${f.value}'`;
      });
      sql += ` WHERE ${conditions.join(" AND ")}`;
    }
    
    if (this.limitValue) {
      sql += ` LIMIT ${this.limitValue}`;
    }
    
    return sql;
  }
}

// Utilisation typée !
const query = new QueryBuilder("users")
  .select("name", "email", "role")  // TypeScript vérifie les colonnes !
  .where("role", "=", "admin")       // TypeScript vérifie le type de la valeur !
  .where("age", ">", 18)
  .limit(10)
  .buildSQL();

console.log(query);
// SELECT name, email, role FROM users WHERE role = 'admin' AND age > '18' LIMIT 10


// ----------------------------------------------------------------------------
// [COURS] EXERCICE PRATIQUE 3 : SYSTÈME DE VALIDATION TYPÉ
// ============================================================================

/*
[OBJECTIF] OBJECTIF :
Créer un système de validation de données typé avec TypeScript avancé.

[LISTE] CAHIER DES CHARGES :

1. TYPE DEFINITIONS :
   - Validator<T> : type de fonction de validation
   - ValidationResult : { valid: boolean; errors: string[] }
   - Schema<T> : Record des validateurs pour chaque propriété de T

2. FONCTIONS UTILITAIRES :
   - required<T>(value: T | null | undefined): ValidationResult
   - minLength(min: number): Validator<string>
   - maxLength(max: number): Validator<string>
   - email(): Validator<string>
   - min(n: number): Validator<number>
   - max(n: number): Validator<number>
   - pattern(regex: RegExp, message: string): Validator<string>

3. FONCTION PRINCIPALE :
   - validate<T>(data: T, schema: Schema<T>): { 
       valid: boolean; 
       errors: Partial<Record<keyof T, string[]>> 
     }

4. TEST avec FormData :
   - Interface RegistrationForm { username, email, password, age }
   - Créer un schéma de validation
   - Tester des données valides et invalides

[TEMPS] TEMPS ESTIMÉ : 45-60 minutes
*/

// SOLUTION COMPLÈTE :

// Types
interface ValidationResult {
  valid: boolean;
  errors: string[];
}

type Validator<T> = (value: T) => ValidationResult;
type Schema<T> = {
  [K in keyof T]?: Validator<T[K]>[];
};

// Helper pour créer un résultat
function ok(): ValidationResult {
  return { valid: true, errors: [] };
}

function fail(message: string): ValidationResult {
  return { valid: false, errors: [message] };
}

function combineResults(results: ValidationResult[]): ValidationResult {
  const errors = results.flatMap(r => r.errors);
  return { valid: errors.length === 0, errors };
}

// Validateurs de base
function required<T>(
  message = "Ce champ est requis"
): Validator<T | null | undefined> {
  return (value) => {
    if (value === null || value === undefined || value === "") {
      return fail(message);
    }
    return ok();
  };
}

function minLength(min: number, message?: string): Validator<string> {
  return (value) => {
    if (value.length < min) {
      return fail(message || `Minimum ${min} caractères requis`);
    }
    return ok();
  };
}

function maxLength(max: number, message?: string): Validator<string> {
  return (value) => {
    if (value.length > max) {
      return fail(message || `Maximum ${max} caractères autorisés`);
    }
    return ok();
  };
}

function emailValidator(message = "Email invalide"): Validator<string> {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return (value) => {
    if (!emailRegex.test(value)) return fail(message);
    return ok();
  };
}

function min(n: number, message?: string): Validator<number> {
  return (value) => {
    if (value < n) return fail(message || `Minimum ${n}`);
    return ok();
  };
}

function max(n: number, message?: string): Validator<number> {
  return (value) => {
    if (value > n) return fail(message || `Maximum ${n}`);
    return ok();
  };
}

function pattern(regex: RegExp, message: string): Validator<string> {
  return (value) => {
    if (!regex.test(value)) return fail(message);
    return ok();
  };
}

// Fonction principale de validation
function validate<T extends Record<string, unknown>>(
  data: T,
  schema: Schema<T>
): { valid: boolean; errors: Partial<Record<keyof T, string[]>> } {
  const errors: Partial<Record<keyof T, string[]>> = {};
  let valid = true;

  for (const key in schema) {
    if (!Object.prototype.hasOwnProperty.call(schema, key)) continue;
    
    const validators = schema[key as keyof T] || [];
    const value = data[key as keyof T];
    
    const fieldErrors: string[] = [];
    
    for (const validator of validators) {
      const result = (validator as Validator<unknown>)(value);
      if (!result.valid) {
        fieldErrors.push(...result.errors);
      }
    }
    
    if (fieldErrors.length > 0) {
      errors[key as keyof T] = fieldErrors;
      valid = false;
    }
  }

  return { valid, errors };
}

// TEST
interface RegistrationForm {
  username: string;
  email: string;
  password: string;
  age: number;
}

// Schéma de validation
const registrationSchema: Schema<RegistrationForm> = {
  username: [
    minLength(3, "Nom d'utilisateur trop court"),
    maxLength(20, "Nom d'utilisateur trop long"),
    pattern(/^[a-zA-Z0-9_]+$/, "Seulement lettres, chiffres et _")
  ],
  email: [
    minLength(1, "Email requis"),
    emailValidator()
  ],
  password: [
    minLength(8, "Mot de passe trop court"),
    pattern(/[A-Z]/, "Doit contenir au moins une majuscule"),
    pattern(/[0-9]/, "Doit contenir au moins un chiffre")
  ],
  age: [
    min(18, "Doit être majeur (18+)"),
    max(120, "Âge invalide")
  ]
};

// Test avec données valides
const validData: RegistrationForm = {
  username: "alice_123",
  email: "alice@example.com",
  password: "SecurePass1",
  age: 25
};

const validResult = validate(validData, registrationSchema);
console.log("Données valides:", validResult.valid); // true
console.log("Erreurs:", validResult.errors); // {}

// Test avec données invalides
const invalidData: RegistrationForm = {
  username: "ab",                // Trop court
  email: "pas-un-email",         // Invalide
  password: "weakpassword",      // Pas de majuscule ni chiffre
  age: 15                        // Mineur
};

const invalidResult = validate(invalidData, registrationSchema);
console.log("\nDonnées invalides:", invalidResult.valid); // false
console.log("Erreurs:");
Object.entries(invalidResult.errors).forEach(([field, errs]) => {
  console.log(`  ${field}:`, errs);
});


// ----------------------------------------------------------------------------
// [DOCS] RÉCAPITULATIF PARTIE 3
// ----------------------------------------------------------------------------

/*
CE QUE VOUS AVEZ APPRIS :

[OK] Chapitre 7 : Union, Intersection et Type Guards
   - Union types (A | B) et discrimination
   - Intersection types (A & B) pour combiner
   - Type guards : typeof, instanceof, in, discriminant
   - Type predicates personnalisés (is Type)
   - Narrowing et vérification exhaustive avec never

[OK] Chapitre 8 : Types Utilitaires
   - Partial, Required, Readonly : modifier les modificateurs
   - Pick, Omit : sélectionner/exclure des propriétés
   - Record : créer des types d'objets uniformes
   - Exclude, Extract, NonNullable : filtrer les unions
   - ReturnType, Parameters, InstanceType : types de fonctions
   - Awaited : déroules les Promises

[OK] Chapitre 9 : Types Avancés
   - Types conditionnels (T extends U ? X : Y)
   - infer pour extraire des types
   - Mapped types ([K in keyof T])
   - Modificateurs de mapped types (+/- readonly/?)
   - Remapping avec as
   - Template literal types


[CLE] POINTS CLÉS :

1. Utilisez les unions discriminées pour les états complexes
2. never est votre ami pour la vérification exhaustive
3. Les types utilitaires évitent la répétition (DRY)
4. infer permet d'extraire des parties de types
5. Les mapped types transforment des types entiers
6. Les template literal types génèrent des strings typées


-> PROCHAINE ÉTAPE : PARTIE 4 - Modules, Décorateurs, Configuration
*/

// ============================================================================
// [LIVRE] TYPESCRIPT - PARTIE 4 : MODULES ET OUTILLAGE
// ============================================================================
//
// [OBJECTIF] CETTE PARTIE COUVRE :
// - Chapitre 10 : Modules et Namespaces
// - Chapitre 11 : Décorateurs
// - Chapitre 12 : Configuration tsconfig.json
// - Chapitre 13 : TypeScript avec React et Node.js
//
// [TEMPS] TEMPS : ~6-8 heures
// [DOCS] PRÉREQUIS : Parties 1, 2 et 3 complétées
// ============================================================================


// ============================================================================
// [GUIDE] CHAPITRE 10 : MODULES ET NAMESPACES
// ============================================================================

/*
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Comprendre les modules ES6 en TypeScript
[OK] Exporter et importer des types et des valeurs
[OK] Utiliser les imports/exports de type uniquement
[OK] Comprendre les namespaces
[OK] Augmenter des modules externes
[OK] Comprendre les fichiers de déclaration (.d.ts)
*/


// ----------------------------------------------------------------------------
// [PACKAGE] MODULES ES6 EN TYPESCRIPT
// ----------------------------------------------------------------------------

/*
Un FICHIER TypeScript avec au moins un import ou export est un MODULE.
Sans import/export, c'est un SCRIPT GLOBAL (évitez les scripts globaux !).
*/

// ─── FICHIER : src/models/user.ts ─────────────────────────────────────────

// EXPORT NOMMÉ : On peut exporter plusieurs choses
export interface UserProfile {
  id: number;
  name: string;
  email: string;
  role: "admin" | "user";
}

export type UserPreview = Pick<UserProfile, "id" | "name">;

export class UserService {
  private users: UserProfile[] = [];
  
  add(user: UserProfile): void {
    this.users.push(user);
  }
  
  findById(id: number): UserProfile | undefined {
    return this.users.find(u => u.id === id);
  }
  
  getAll(): UserProfile[] {
    return [...this.users];
  }
}

// EXPORT PAR DÉFAUT : Un seul par fichier
// export default class UserRepository { ... }

// Constante exportée
export const MAX_USERS = 1000;

// Fonction exportée
export function createUser(name: string, email: string): UserProfile {
  return {
    id: Math.floor(Math.random() * 10000),
    name,
    email,
    role: "user"
  };
}


// ─── FICHIER : src/utils/validation.ts ────────────────────────────────────

export function isValidEmail(email: string): boolean {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

export function isValidUsername(username: string): boolean {
  return /^[a-zA-Z0-9_]{3,20}$/.test(username);
}

// Re-export
// export { UserProfile } from "./models/user"; // Re-exporter depuis user.ts


// ─── FICHIER : src/index.ts ────────────────────────────────────────────────

// IMPORT NOMMÉ
// import { UserService, createUser, MAX_USERS } from "./models/user";

// IMPORT DE TYPE (n'existe qu'à la compilation)
// import type { UserProfile, UserPreview } from "./models/user";

// IMPORT PAR DÉFAUT
// import UserRepository from "./repositories/user";

// IMPORT NAMESPACE : Tout dans un objet
// import * as UserModule from "./models/user";
// UserModule.createUser("Alice", "alice@test.com");
// UserModule.MAX_USERS;

// IMPORT MIXTE
// import Repository, { UserProfile, createUser } from "./models/user";

/*
[IDEE] IMPORT TYPE VS IMPORT NORMAL

import { UserProfile } from "./models/user";   <- Peut être valeur ou type
import type { UserProfile } from "./models/user"; <- TYPE SEULEMENT

AVANTAGES d'import type :
[OK] Supprimé complètement du JavaScript généré
[OK] Impossible d'utiliser accidentellement comme valeur
[OK] Indique clairement l'intention
[OK] Améliore les performances de compilation

RÈGLE : Si vous n'utilisez le symbole que comme type, utilisez import type
*/


// ----------------------------------------------------------------------------
// [DOSSIER] EXPORTS BARREL (INDEX.TS)
// ----------------------------------------------------------------------------

/*
BARREL = Fichier index.ts qui ré-exporte tout d'un dossier
         Permet des imports plus propres
*/

// ─── FICHIER : src/models/index.ts ────────────────────────────────────────

// Re-exporter tout ce dont l'utilisateur a besoin
// export { UserProfile, UserService, createUser } from "./user";
// export type { UserPreview } from "./user";
// export { PostService, Post } from "./post";

// Maintenant on peut importer depuis le dossier :
// import { UserProfile, PostService } from "./models";
// Au lieu de :
// import { UserProfile } from "./models/user";
// import { PostService } from "./models/post";

/*
[IDEE] CONVENTIONS DE MODULES :

src/
├── models/
│   ├── index.ts        <- Barrel : exporte tout de models/
│   ├── user.ts         <- Modèle User
│   └── post.ts         <- Modèle Post
├── services/
│   ├── index.ts        <- Barrel : exporte tout de services/
│   ├── userService.ts
│   └── postService.ts
├── utils/
│   ├── index.ts
│   └── validation.ts
└── index.ts            <- Point d'entrée principal
*/


// ----------------------------------------------------------------------------
// [WEB] NAMESPACES
// ----------------------------------------------------------------------------

/*
NAMESPACE = Regrouper du code sous un nom commun
            Évite les conflits de noms (surtout dans les scripts globaux)

[ATTENTION] NOTE : En 2024, les MODULES sont préférés aux namespaces.
           Les namespaces sont surtout utiles pour organiser 
           les fichiers de déclaration (.d.ts) des bibliothèques.
*/

namespace Geometry {
  export interface Point {
    x: number;
    y: number;
  }

  export function distance(p1: Point, p2: Point): number {
    return Math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2);
  }

  export namespace ThreeD {
    export interface Point3D extends Geometry.Point {
      z: number;
    }

    export function distance3D(p1: Point3D, p2: Point3D): number {
      return Math.sqrt(
        (p2.x - p1.x) ** 2 +
        (p2.y - p1.y) ** 2 +
        (p2.z - p1.z) ** 2
      );
    }
  }
}

const p1: Geometry.Point = { x: 0, y: 0 };
const p2: Geometry.Point = { x: 3, y: 4 };
console.log(Geometry.distance(p1, p2)); // 5

const p3D: Geometry.ThreeD.Point3D = { x: 1, y: 2, z: 3 };


// ----------------------------------------------------------------------------
// [NOTE] FICHIERS DE DÉCLARATION (.d.ts)
// ----------------------------------------------------------------------------

/*
FICHIER DE DÉCLARATION = Décrit la forme d'une bibliothèque JS externe
                          Extension .d.ts (d = declaration)

Pourquoi ? Les bibliothèques JavaScript pures n'ont pas de types.
           Les fichiers .d.ts ajoutent les types SANS le code.

Exemple : src/types/my-library.d.ts
*/

// Déclaration pour une bibliothèque hypothétique
declare module "my-json-library" {
  export function parse<T>(json: string): T;
  export function stringify(value: unknown): string;
  export function clone<T>(obj: T): T;
}

// Déclaration de variable globale
declare const __VERSION__: string;
declare const __DEV__: boolean;

// Déclaration de fonction globale
declare function require(id: string): any;

// Augmenter des types existants
declare global {
  interface Window {
    myGlobalFunction: () => void;
  }
  
  interface Array<T> {
    last(): T | undefined;
  }
}

/*
[IDEE] @types/ PACKAGES

La plupart des types de bibliothèques populaires sont disponibles via :
npm install --save-dev @types/nom-de-la-bibliothèque

Exemples :
npm install --save-dev @types/node        <- Types Node.js
npm install --save-dev @types/react       <- Types React
npm install --save-dev @types/express     <- Types Express
npm install --save-dev @types/jest        <- Types Jest

Les bibliothèques modernes incluent souvent leurs propres types.
*/


// ============================================================================
// [GUIDE] CHAPITRE 11 : DÉCORATEURS
// ============================================================================

/*
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Comprendre ce qu'est un décorateur
[OK] Créer des décorateurs de classe
[OK] Créer des décorateurs de méthode et propriété
[OK] Utiliser les décorateurs pour la métaprogrammation
[OK] Connaître les décorateurs en production (Angular, NestJS)

[ATTENTION] CONFIGURATION REQUISE dans tsconfig.json :
"experimentalDecorators": true
"emitDecoratorMetadata": true (avec reflect-metadata)
*/


// ----------------------------------------------------------------------------
// [DESIGN] DÉCORATEURS DE CLASSE
// ----------------------------------------------------------------------------

/*
DÉCORATEUR DE CLASSE = Fonction appliquée à une classe
                       Reçoit le constructeur en paramètre

Syntaxe : @decorateur
*/

// Décorateur simple
function sealed(constructor: Function): void {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@sealed
class SealedClass {
  greet(): string { return "Hello"; }
}
// SealedClass.prototype.newMethod = () => {}; // Erreur au runtime !

// Décorateur avec paramètres (factory)
function Component(options: { selector: string; template: string }) {
  return function(constructor: Function): void {
    constructor.prototype.selector = options.selector;
    constructor.prototype.template = options.template;
  };
}

@Component({
  selector: "app-root",
  template: "<div>Hello</div>"
})
class AppComponent {
  title = "Mon App";
}

// Décorateur qui modifie la classe (retourne nouvelle classe)
function Singleton<T extends new (...args: any[]) => {}>(constructor: T) {
  let instance: T | null = null;
  
  return class extends constructor {
    constructor(...args: any[]) {
      if (instance) return instance;
      super(...args);
      instance = this as unknown as T;
    }
  };
}

@Singleton
class DatabaseConnection {
  private _connected = false;
  
  connect(): void {
    this._connected = true;
    console.log("Connecté à la base de données");
  }
}

const db1 = new DatabaseConnection();
const db2 = new DatabaseConnection();
// db1 === db2 -> true (même instance !)


// ----------------------------------------------------------------------------
// [OUTIL] DÉCORATEURS DE MÉTHODE
// ----------------------------------------------------------------------------

/*
DÉCORATEUR DE MÉTHODE : Reçoit target, propertyKey, descriptor
*/

// Log automatique
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  
  descriptor.value = function(...args: any[]) {
    console.log(`[TEL] Appel de ${propertyKey} avec:`, args);
    const result = originalMethod.apply(this, args);
    console.log(`[OK] ${propertyKey} a retourné:`, result);
    return result;
  };
  
  return descriptor;
}

// Mesure du temps
function timing(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  
  descriptor.value = function(...args: any[]) {
    const start = performance.now();
    const result = originalMethod.apply(this, args);
    const end = performance.now();
    console.log(`[TEMPS] ${propertyKey} a pris ${(end - start).toFixed(2)}ms`);
    return result;
  };
  
  return descriptor;
}

// Memoization
function memoize(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const cache = new Map<string, any>();
  const originalMethod = descriptor.value;
  
  descriptor.value = function(...args: any[]) {
    const key = JSON.stringify(args);
    if (cache.has(key)) {
      console.log(`[SAUVEGARDE] Cache hit pour ${propertyKey}(${key})`);
      return cache.get(key);
    }
    const result = originalMethod.apply(this, args);
    cache.set(key, result);
    return result;
  };
  
  return descriptor;
}

class MathService {
  @log
  @timing
  add(a: number, b: number): number {
    return a + b;
  }

  @memoize
  fibonacci(n: number): number {
    if (n <= 1) return n;
    return this.fibonacci(n - 1) + this.fibonacci(n - 2);
  }
}

const mathService = new MathService();
mathService.add(5, 3);
// [TEL] Appel de add avec: [5, 3]
// [TEMPS] add a pris 0.02ms
// [OK] add a retourné: 8


// ----------------------------------------------------------------------------
// [VERROUILLE] DÉCORATEURS DE PROPRIÉTÉ
// ----------------------------------------------------------------------------

// Validation de propriété
function MinLength(min: number) {
  return function(target: any, propertyKey: string): void {
    let value: string;
    
    const getter = function() { return value; };
    const setter = function(newValue: string) {
      if (newValue.length < min) {
        throw new Error(`${propertyKey} doit avoir au moins ${min} caractères`);
      }
      value = newValue;
    };
    
    Object.defineProperty(target, propertyKey, {
      get: getter,
      set: setter,
      enumerable: true,
      configurable: true
    });
  };
}

function NotEmpty() {
  return function(target: any, propertyKey: string): void {
    let value: string;
    
    const getter = function() { return value; };
    const setter = function(newValue: string) {
      if (!newValue || newValue.trim() === "") {
        throw new Error(`${propertyKey} ne peut pas être vide`);
      }
      value = newValue.trim();
    };
    
    Object.defineProperty(target, propertyKey, {
      get: getter,
      set: setter,
      enumerable: true,
      configurable: true
    });
  };
}

class UserFormData {
  @MinLength(3)
  @NotEmpty()
  username: string = "";
  
  @NotEmpty()
  email: string = "";
}

const form = new UserFormData();
// form.username = "ab"; // Erreur ! Moins de 3 caractères


// ============================================================================
// [GUIDE] CHAPITRE 12 : CONFIGURATION TSCONFIG.JSON
// ============================================================================

/*
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Comprendre toutes les options importantes de tsconfig.json
[OK] Configurer pour différents environnements
[OK] Utiliser les références de projet
[OK] Optimiser la compilation
*/


// ----------------------------------------------------------------------------
// [CONFIG] TSCONFIG.JSON COMPLET ET COMMENTÉ
// ----------------------------------------------------------------------------

/*
TSCONFIG.JSON POUR PROJET NODE.JS MODERNE
*/

const nodeConfig = {
  "compilerOptions": {
    // ── SORTIE ──────────────────────────────────────────────────
    "target": "ES2022",       // Version JavaScript de sortie
    // ES3, ES5, ES2015, ES2016, ES2017, ES2018, ES2019, ES2020, ES2021, ES2022, ESNext
    // Recommandation : ES2020 ou plus pour Node.js 14+
    
    "module": "NodeNext",     // Système de modules
    // "commonjs" -> require() (Node.js classique)
    // "es2020"   -> import/export (ESM)
    // "NodeNext" -> Support Node.js ESM et CJS automatique
    
    "outDir": "./dist",       // Où mettre les .js générés
    "rootDir": "./src",       // Dossier source
    
    // ── STRICTNESS ──────────────────────────────────────────────
    "strict": true,           // Active TOUT le mode strict (recommandé !)
    // "strict": true active automatiquement :
    // -> "noImplicitAny": true        (pas de 'any' implicite)
    // -> "noImplicitThis": true       (pas de 'this' implicite any)
    // -> "strictNullChecks": true     (null et undefined sont des types distincts)
    // -> "strictFunctionTypes": true  (typage strict des fonctions)
    // -> "strictBindCallApply": true  (typage strict de bind/call/apply)
    // -> "strictPropertyInitialization": true (initialisation des propriétés)
    // -> "alwaysStrict": true         (mode strict JS)
    
    // Options supplémentaires recommandées :
    "noUncheckedIndexedAccess": true, // arr[0] est T | undefined (pas juste T)
    "noImplicitOverride": true,        // 'override' obligatoire si override
    "exactOptionalPropertyTypes": true, // prop?: string ≠ prop?: string | undefined
    
    // ── CHEMINS ─────────────────────────────────────────────────
    "baseUrl": "./src",       // Base pour les chemins relatifs
    "paths": {                // Alias de chemins
      "@models/*": ["models/*"],
      "@services/*": ["services/*"],
      "@utils/*": ["utils/*"]
    },
    
    // ── COMPILATION ─────────────────────────────────────────────
    "declaration": true,      // Générer les fichiers .d.ts
    "declarationMap": true,   // Source maps pour .d.ts
    "sourceMap": true,        // Source maps pour le debugging
    "removeComments": false,  // Garder les commentaires
    
    // ── INTEROP ─────────────────────────────────────────────────
    "esModuleInterop": true,  // Permet import React from 'react'
    "allowSyntheticDefaultImports": true, // Comme esModuleInterop
    "resolveJsonModule": true,            // Importer des fichiers JSON
    "moduleResolution": "NodeNext",       // Stratégie de résolution
    
    // ── PERFORMANCE ─────────────────────────────────────────────
    "skipLibCheck": true,     // Ignorer les types de node_modules (rapide)
    "incremental": true,      // Cache pour compilation incrémentale
    "tsBuildInfoFile": "./dist/.tsbuildinfo",
    
    // ── VÉRIFICATIONS SUPPLÉMENTAIRES ──────────────────────────
    "noUnusedLocals": true,      // Erreur si variable locale inutilisée
    "noUnusedParameters": true,  // Erreur si paramètre inutilisé
    "noFallthroughCasesInSwitch": true, // Erreur si case sans break/return
    "noImplicitReturns": true,   // Erreur si pas de return dans tous les chemins
    
    // ── EXPÉRIMENTAL ────────────────────────────────────────────
    "experimentalDecorators": true,  // Activer les décorateurs
    "emitDecoratorMetadata": true    // Métadonnées des décorateurs
  },
  
  "include": ["src/**/*.ts"],    // Fichiers à inclure
  "exclude": [
    "node_modules",              // Toujours exclure
    "dist",                      // Dossier de sortie
    "**/*.test.ts",              // Tests (souvent dans config séparée)
    "**/*.spec.ts"
  ]
};

/*
TSCONFIG POUR REACT (FRONTEND)
*/

const reactConfig = {
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],  // <- Ajouter DOM !
    "jsx": "react-jsx",  // Pour React 17+ (pas besoin d'importer React)
    // "jsx": "react"   <- Pour React 16 et avant
    // "jsx": "preserve" <- Pour Vite/ESBuild qui gèrent le JSX eux-mêmes
    
    "strict": true,
    "moduleResolution": "bundler",  // Pour Vite, webpack
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,  // Le bundler (Vite/webpack) génère les fichiers
    
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]  // Alias courant avec Vite
    }
  },
  "include": ["src"],
  "references": [{ "path": "./tsconfig.node.json" }]
};

/*
TSCONFIG POUR LIBRAIRIE (PACKAGE NPM)
*/

const libraryConfig = {
  "compilerOptions": {
    "target": "ES2019",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "declaration": true,
    "declarationDir": "./types",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  }
};


// ----------------------------------------------------------------------------
// [IDEE] OPTIONS STRICT EXPLIQUÉES
// ----------------------------------------------------------------------------

/*
STRICTNULLCHECKS : La plus importante !

Sans strictNullChecks :
*/
function getLength(str: string): number {
  return str.length; // str pourrait être null/undefined sans erreur
}

// Avec strictNullChecks :
function getLengthSafe(str: string | null): number {
  if (str === null) return 0; // Obligé de gérer le cas null !
  return str.length;
}

/*
NOIMPLICITANY : Pas d'any implicite
*/
// [X] Sans noImplicitAny : TypeScript infère any sans erreur
// function process(data) { return data.name; }  // data est any implicitement !

// [OK] Avec noImplicitAny : Erreur ! Vous devez typer
function processTyped(data: { name: string }): string { return data.name; }

/*
NOUNCHECKEDINDEXEDACCESS : Sécurité sur les accès tableaux
*/
const arr = [1, 2, 3];

// Sans noUncheckedIndexedAccess : TypeScript pense que arr[0] est number
const first = arr[0]; // number (pourrait être undefined si arr vide !)

// Avec noUncheckedIndexedAccess : TypeScript sait que arr[0] est number | undefined
// const first2: number | undefined = arr[0]; // Vous devez vérifier !


// ============================================================================
// [GUIDE] CHAPITRE 13 : TYPESCRIPT AVEC REACT ET NODE.JS
// ============================================================================

/*
[OBJECTIF] OBJECTIFS D'APPRENTISSAGE

À la fin de ce chapitre, vous saurez :
[OK] Créer des composants React typés
[OK] Typer les props, state et hooks React
[OK] Créer des API Express typées
[OK] Utiliser les types Node.js
*/


// ----------------------------------------------------------------------------
// [SCIENCE] TYPESCRIPT + REACT
// ----------------------------------------------------------------------------

/*
Installation :
npm create vite@latest my-app -- --template react-ts
Ou :
npx create-react-app my-app --template typescript
*/

// Types de base React
// import React, { useState, useEffect, useCallback, useRef } from 'react';

// ── COMPOSANTS FONCTIONNELS ────────────────────────────────────────────────

// Props simples
interface ButtonProps {
  label: string;
  onClick: () => void;
  disabled?: boolean;
  variant?: "primary" | "secondary" | "danger";
  size?: "small" | "medium" | "large";
}

// Composant typé
const Button: React.FC<ButtonProps> = ({
  label,
  onClick,
  disabled = false,
  variant = "primary",
  size = "medium"
}) => {
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      className={`btn btn-${variant} btn-${size}`}
    >
      {label}
    </button>
  );
};

// Composant avec children
interface CardProps {
  title: string;
  children: React.ReactNode;  // Accepte tout ce qui peut être rendu
  footer?: React.ReactNode;
}

const Card: React.FC<CardProps> = ({ title, children, footer }) => {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div className="card-body">{children}</div>
      {footer && <div className="card-footer">{footer}</div>}
    </div>
  );
};

// ── HOOKS TYPÉS ────────────────────────────────────────────────────────────

// useState
const Counter: React.FC = () => {
  const [count, setCount] = useState<number>(0);
  //                                  ^ Type explicite (optionnel ici, inféré)
  
  const [user, setUser] = useState<User | null>(null);
  //                               ^ Nécessaire pour null initial
  
  const [items, setItems] = useState<string[]>([]);
  
  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(c => c + 1)}>+</button>
    </div>
  );
};

// useRef
const InputComponent: React.FC = () => {
  const inputRef = useRef<HTMLInputElement>(null);
  //                      ^ Type de l'élément DOM
  
  const focus = () => {
    inputRef.current?.focus(); // ?. car peut être null
  };
  
  return <input ref={inputRef} type="text" />;
};

// useEffect avec types
const UserProfile2: React.FC<{ userId: number }> = ({ userId }) => {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    let cancelled = false;
    
    async function loadUser() {
      try {
        setLoading(true);
        const response = await fetch(`/api/users/${userId}`);
        const data: User = await response.json();
        
        if (!cancelled) {
          setUser(data);
        }
      } finally {
        if (!cancelled) setLoading(false);
      }
    }
    
    loadUser();
    
    return () => { cancelled = true; }; // Cleanup
  }, [userId]);
  
  if (loading) return <div>Chargement...</div>;
  if (!user) return <div>Utilisateur non trouvé</div>;
  
  return <div>{user.name}</div>;
};

// ── CUSTOM HOOKS TYPÉS ────────────────────────────────────────────────────

// Hook de formulaire
interface UseFormReturn<T> {
  values: T;
  errors: Partial<Record<keyof T, string>>;
  handleChange: (field: keyof T, value: T[keyof T]) => void;
  handleSubmit: (onSubmit: (values: T) => void) => (e: React.FormEvent) => void;
  reset: () => void;
}

function useForm<T extends Record<string, unknown>>(
  initialValues: T
): UseFormReturn<T> {
  const [values, setValues] = useState<T>(initialValues);
  const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({});
  
  const handleChange = useCallback((field: keyof T, value: T[keyof T]) => {
    setValues(prev => ({ ...prev, [field]: value }));
  }, []);
  
  const handleSubmit = useCallback(
    (onSubmit: (values: T) => void) => (e: React.FormEvent) => {
      e.preventDefault();
      onSubmit(values);
    },
    [values]
  );
  
  const reset = useCallback(() => {
    setValues(initialValues);
    setErrors({});
  }, [initialValues]);
  
  return { values, errors, handleChange, handleSubmit, reset };
}

// Utilisation du hook
interface LoginForm {
  email: string;
  password: string;
}

const LoginComponent: React.FC = () => {
  const { values, handleChange, handleSubmit } = useForm<LoginForm>({
    email: "",
    password: ""
  });
  
  const onSubmit = (formValues: LoginForm) => {
    console.log("Login avec:", formValues);
  };
  
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        type="email"
        value={values.email}
        onChange={e => handleChange("email", e.target.value)}
      />
      <input
        type="password"
        value={values.password}
        onChange={e => handleChange("password", e.target.value)}
      />
      <button type="submit">Connexion</button>
    </form>
  );
};

// ── EVENTS REACT TYPÉS ────────────────────────────────────────────────────

const EventsExample: React.FC = () => {
  // React.ChangeEvent<HTMLInputElement> pour les inputs
  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
    console.log(e.target.value);
  };
  
  // React.MouseEvent<HTMLButtonElement> pour les boutons
  const handleClick = (e: React.MouseEvent<HTMLButtonElement>): void => {
    e.preventDefault();
    console.log("Cliqué !");
  };
  
  // React.FormEvent<HTMLFormElement> pour les formulaires
  const handleSubmit2 = (e: React.FormEvent<HTMLFormElement>): void => {
    e.preventDefault();
  };
  
  // React.KeyboardEvent pour le clavier
  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>): void => {
    if (e.key === "Enter") console.log("Entrée pressée");
  };
  
  return (
    <form onSubmit={handleSubmit2}>
      <input onChange={handleInputChange} onKeyDown={handleKeyDown} />
      <button onClick={handleClick}>OK</button>
    </form>
  );
};

/*
[IDEE] TYPES D'ÉVÉNEMENTS REACT COURANTS :

React.ChangeEvent<HTMLInputElement>     <- input, textarea
React.ChangeEvent<HTMLSelectElement>    <- select
React.MouseEvent<HTMLButtonElement>     <- bouton
React.MouseEvent<HTMLDivElement>        <- div
React.FormEvent<HTMLFormElement>        <- form
React.KeyboardEvent<HTMLInputElement>   <- clavier
React.FocusEvent<HTMLInputElement>      <- focus/blur
React.DragEvent<HTMLDivElement>         <- drag & drop
React.WheelEvent<HTMLDivElement>        <- scroll souris
*/


// ----------------------------------------------------------------------------
// [VERT] TYPESCRIPT + NODE.JS / EXPRESS
// ----------------------------------------------------------------------------

/*
Installation :
npm install express
npm install --save-dev @types/express @types/node
npm install --save-dev typescript ts-node-dev
*/

// app.ts
// import express, { Request, Response, NextFunction, Router } from 'express';

// Typer les paramètres de route
interface UserParams {
  id: string; // Les params URL sont toujours des strings
}

interface UserQuery {
  page?: string;
  limit?: string;
  search?: string;
}

interface CreateUserBody {
  name: string;
  email: string;
  password: string;
}

// Route typée
// const router = Router();

// GET /users/:id
/*
router.get<UserParams, User, never, UserQuery>(
  '/users/:id',
  async (req: Request<UserParams, User, never, UserQuery>, res: Response<User>) => {
    const userId = parseInt(req.params.id, 10);
    const { page = "1", limit = "10" } = req.query;
    
    // TypeScript sait que req.params.id est string
    // TypeScript sait que req.query.page est string | undefined
    
    const user = await UserService.findById(userId);
    if (!user) {
      res.status(404).json({ message: "User not found" } as any);
      return;
    }
    
    res.json(user);
  }
);
*/

// Middleware typé
/*
function authMiddleware(req: Request, res: Response, next: NextFunction): void {
  const token = req.headers.authorization?.split(" ")[1];
  
  if (!token) {
    res.status(401).json({ error: "Non autorisé" });
    return;
  }
  
  // Vérifier le token...
  next();
}
*/

// Extension des types Express pour ajouter currentUser
/*
declare global {
  namespace Express {
    interface Request {
      currentUser?: User;  // Ajouter currentUser à Request
    }
  }
}
*/

// Maintenant req.currentUser est disponible et typé !

/*
[IDEE] CONFIGURATION RECOMMANDÉE POUR NODE.JS

package.json scripts :
{
  "scripts": {
    "dev": "ts-node-dev --respawn --transpile-only src/index.ts",
    "build": "tsc",
    "start": "node dist/index.js",
    "type-check": "tsc --noEmit"
  }
}
*/


// ----------------------------------------------------------------------------
// [COURS] EXERCICE PRATIQUE 4 : API REST TYPÉE
// ============================================================================

/*
[OBJECTIF] OBJECTIF :
Créer une API REST complètement typée avec les types corrects.

[LISTE] CAHIER DES CHARGES :

1. TYPES :
   - ApiResponse<T> : { success: boolean; data?: T; error?: string; pagination?: Pagination }
   - Pagination : { page: number; limit: number; total: number; pages: number }
   - CreatePostDto : { title: string; content: string; tags?: string[] }
   - UpdatePostDto : Partial<CreatePostDto>
   - Post : extends BaseEntity, title, content, tags, authorId

2. CLASSES DE SERVICE :
   - PostService : 
     -> create(dto: CreatePostDto, authorId: number): Promise<Post>
     -> findAll(page: number, limit: number): Promise<{ data: Post[]; pagination: Pagination }>
     -> findById(id: number): Promise<Post | null>
     -> update(id: number, dto: UpdatePostDto): Promise<Post | null>
     -> delete(id: number): Promise<boolean>

3. FONCTIONS DE RÉPONSE API :
   - successResponse<T>(data: T, pagination?: Pagination): ApiResponse<T>
   - errorResponse(message: string): ApiResponse<never>

4. SIMULER 5 appels API et afficher les résultats

[TEMPS] TEMPS ESTIMÉ : 45-60 minutes
*/

// SOLUTION :

// Types de base
interface BaseEntity {
  id: number;
  createdAt: Date;
  updatedAt: Date;
}

interface Pagination {
  page: number;
  limit: number;
  total: number;
  pages: number;
}

interface ApiResponse<T> {
  success: boolean;
  data?: T;
  error?: string;
  pagination?: Pagination;
}

interface CreatePostDto {
  title: string;
  content: string;
  tags?: string[];
}

type UpdatePostDto = Partial<CreatePostDto>;

interface PostEntity extends BaseEntity {
  title: string;
  content: string;
  tags: string[];
  authorId: number;
}

// Fonctions de réponse
function successResponse<T>(data: T, pagination?: Pagination): ApiResponse<T> {
  return {
    success: true,
    data,
    pagination
  };
}

function errorResponse(message: string): ApiResponse<never> {
  return {
    success: false,
    error: message
  };
}

// Service
class PostService {
  private posts: PostEntity[] = [];
  private nextId = 1;

  async create(dto: CreatePostDto, authorId: number): Promise<PostEntity> {
    const now = new Date();
    const post: PostEntity = {
      id: this.nextId++,
      title: dto.title,
      content: dto.content,
      tags: dto.tags || [],
      authorId,
      createdAt: now,
      updatedAt: now
    };
    this.posts.push(post);
    return post;
  }

  async findAll(
    page: number = 1,
    limit: number = 10
  ): Promise<{ data: PostEntity[]; pagination: Pagination }> {
    const total = this.posts.length;
    const pages = Math.ceil(total / limit);
    const start = (page - 1) * limit;
    const data = this.posts.slice(start, start + limit);
    
    return {
      data,
      pagination: { page, limit, total, pages }
    };
  }

  async findById(id: number): Promise<PostEntity | null> {
    return this.posts.find(p => p.id === id) || null;
  }

  async update(id: number, dto: UpdatePostDto): Promise<PostEntity | null> {
    const index = this.posts.findIndex(p => p.id === id);
    if (index === -1) return null;
    
    const post = this.posts[index];
    const updated: PostEntity = {
      ...post,
      ...dto,
      tags: dto.tags || post.tags,
      updatedAt: new Date()
    };
    
    this.posts[index] = updated;
    return updated;
  }

  async delete(id: number): Promise<boolean> {
    const index = this.posts.findIndex(p => p.id === id);
    if (index === -1) return false;
    this.posts.splice(index, 1);
    return true;
  }
}

// SIMULATION
async function simulateAPI(): Promise<void> {
  const service = new PostService();
  
  console.log("=== SIMULATION API REST ===\n");

  // 1. Créer des posts
  console.log("1. Créer des posts :");
  const post1 = await service.create(
    { title: "TypeScript Intro", content: "...", tags: ["ts", "dev"] }, 1
  );
  const post2 = await service.create(
    { title: "Génériques en TypeScript", content: "...", tags: ["ts", "advanced"] }, 1
  );
  const post3 = await service.create(
    { title: "React Hooks", content: "...", tags: ["react", "hooks"] }, 2
  );
  
  console.log(JSON.stringify(successResponse(post1), null, 2));

  // 2. Lister tous les posts
  console.log("\n2. Lister les posts (page 1, limit 2) :");
  const { data: posts, pagination } = await service.findAll(1, 2);
  console.log(JSON.stringify(successResponse(posts, pagination), null, 2));

  // 3. Trouver par ID
  console.log("\n3. Trouver post #1 :");
  const foundPost = await service.findById(1);
  if (foundPost) {
    console.log(JSON.stringify(successResponse(foundPost), null, 2));
  }

  // 4. Mettre à jour
  console.log("\n4. Mettre à jour post #1 :");
  const updated = await service.update(1, { title: "TypeScript pour Débutants" });
  if (updated) {
    console.log(JSON.stringify(successResponse(updated), null, 2));
  }

  // 5. Supprimer
  console.log("\n5. Supprimer post #3 :");
  const deleted = await service.delete(3);
  if (deleted) {
    console.log(JSON.stringify(successResponse({ deleted: true }), null, 2));
  }

  // 6. Post inexistant
  console.log("\n6. Trouver post inexistant (#999) :");
  const notFound = await service.findById(999);
  if (!notFound) {
    console.log(JSON.stringify(errorResponse("Post non trouvé"), null, 2));
  }
}

simulateAPI().catch(console.error);


// ----------------------------------------------------------------------------
// [DOCS] RÉCAPITULATIF PARTIE 4
// ----------------------------------------------------------------------------

/*
CE QUE VOUS AVEZ APPRIS :

[OK] Chapitre 10 : Modules
   - Modules ES6 : export/import
   - import type pour les types seulement
   - Barrel files (index.ts)
   - Namespaces
   - Fichiers de déclaration (.d.ts)
   - @types/ packages

[OK] Chapitre 11 : Décorateurs
   - Décorateurs de classe
   - Décorateurs de méthode (log, timing, memoize)
   - Décorateurs de propriété (validation)
   - Factories de décorateurs

[OK] Chapitre 12 : Configuration
   - tsconfig.json complet
   - Mode strict et ses options
   - Configurations pour Node.js, React, librairie
   - noUncheckedIndexedAccess

[OK] Chapitre 13 : React et Node.js
   - Composants React typés
   - Hooks typés (useState, useRef, useEffect)
   - Custom hooks typés
   - Events React
   - API Express typée
   - Étendre les types Express


-> PROCHAINE ÉTAPE : PARTIE 5 - Design Patterns, Tests, Best Practices
*/