import 'package:flutter/services.dart'; /// Utilitaires NIR (Numéro d'Inscription au Répertoire) – INSEE, 15 caractères. /// Corse : 2A (2A) et 2B (2B) au lieu de 19/20. Clé de contrôle : 97 - (NIR13 mod 97). /// Normalise le NIR : 15 caractères, sans espaces ni séparateurs. Corse conservée (2A/2B). String normalizeNir(String input) { if (input.isEmpty) return ''; final cleaned = input.replaceAll(' ', '').replaceAll('-', '').replaceAll('/', '').toUpperCase(); final buf = StringBuffer(); int i = 0; while (i < cleaned.length && buf.length < 15) { final c = cleaned[i]; if (buf.length < 5) { if (c.compareTo('0') >= 0 && c.compareTo('9') <= 0) buf.write(c); i++; } else if (buf.length == 5) { if (c == '2' && i + 1 < cleaned.length && (cleaned[i + 1] == 'A' || cleaned[i + 1] == 'B')) { buf.write('2'); buf.write(cleaned[i + 1]); i += 2; } else if ((c == 'A' || c == 'B')) { buf.write('2'); buf.write(c); i++; } else if (c.compareTo('0') >= 0 && c.compareTo('9') <= 0) { buf.write(c); if (i + 1 < cleaned.length && cleaned[i + 1].compareTo('0') >= 0 && cleaned[i + 1].compareTo('9') <= 0) { buf.write(cleaned[i + 1]); i += 2; } else { i++; } } else { i++; } } else { if (c.compareTo('0') >= 0 && c.compareTo('9') <= 0) buf.write(c); i++; } } return buf.toString().length > 15 ? buf.toString().substring(0, 15) : buf.toString(); } /// Retourne la chaîne brute à 15 caractères (chiffres + 2A ou 2B). String nirToRaw(String normalized) { String s = normalized.replaceAll(' ', '').replaceAll('-', '').replaceAll('/', ''); if (s.length > 15) s = s.substring(0, 15); return s; } /// Formate pour affichage : 1 12 34 56 789 012 - 34 ou 1 12 34 2A 789 012 - 34 (Corse). String formatNir(String raw) { final r = nirToRaw(raw); if (r.length < 15) return r; // Même structure pour tous : sexe + année + mois + département + commune + ordre-clé. return '${r.substring(0, 1)} ${r.substring(1, 3)} ${r.substring(3, 5)} ${r.substring(5, 7)} ${r.substring(7, 10)} ${r.substring(10, 13)} - ${r.substring(13, 15)}'; } /// Aligné sur le backend (NIR côté API / nir.util.ts) : sexe 1–3, département 2A ou 2B pour la Corse. bool _isFormatValid(String raw) { final r = raw.toUpperCase(); if (r.length != 15) return false; return RegExp(r'^[1-3]\d{4}(?:2A|2B|\d{2})\d{6}\d{2}$').hasMatch(r); } /// Clé INSEE : 97 - (NIR13 mod 97). Corse : 2A→19, 2B→20 (aligné backend). int _controlKey(String raw13) { String n; if (raw13.length >= 7 && raw13.substring(5, 7) == '2A') { n = '${raw13.substring(0, 5)}19${raw13.substring(7)}'; } else if (raw13.length >= 7 && raw13.substring(5, 7) == '2B') { n = '${raw13.substring(0, 5)}20${raw13.substring(7)}'; } else { n = '${raw13.substring(0, 5)}${raw13.substring(5, 7)}${raw13.substring(7)}'; } final big = int.tryParse(n); if (big == null) return -1; return 97 - (big % 97); } /// Valide le NIR (format + clé). Retourne null si valide, message d'erreur sinon. String? validateNir(String? value) { if (value == null || value.isEmpty) return 'NIR requis'; final raw = nirToRaw(value).toUpperCase(); if (raw.length != 15) return 'Le NIR doit contenir 15 caractères (chiffres, ou 2A/2B pour la Corse)'; if (!_isFormatValid(raw)) return 'Format NIR invalide (ex. 1 12 34 56 789 012 - 34 ou 2A pour la Corse)'; final key = _controlKey(raw.substring(0, 13)); final keyStr = key >= 0 && key <= 99 ? key.toString().padLeft(2, '0') : ''; final expectedKey = raw.substring(13, 15); if (key < 0 || keyStr != expectedKey) return 'Clé de contrôle NIR invalide'; return null; } /// Formateur de saisie : affiche le NIR formaté (1 12 34 56 789 012 - 34) et limite à 15 caractères utiles. class NirInputFormatter extends TextInputFormatter { @override TextEditingValue formatEditUpdate( TextEditingValue oldValue, TextEditingValue newValue, ) { final raw = normalizeNir(newValue.text); if (raw.isEmpty) return newValue; final formatted = formatNir(raw); final offset = formatted.length; return TextEditingValue( text: formatted, selection: TextSelection.collapsed(offset: offset), ); } }