datasynth_core/templates/
names.rs

1//! Multi-cultural name generation for realistic user personas.
2//!
3//! Provides name pools for various cultures to generate realistic
4//! user names, IDs, and email addresses.
5
6use rand::seq::SliceRandom;
7use rand::Rng;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11/// Cultural background for name generation.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
13#[serde(rename_all = "snake_case")]
14pub enum NameCulture {
15    /// Western US names (default)
16    #[default]
17    WesternUs,
18    /// German names
19    German,
20    /// French names
21    French,
22    /// Chinese names
23    Chinese,
24    /// Japanese names
25    Japanese,
26    /// Indian names
27    Indian,
28    /// Hispanic/Latino names
29    Hispanic,
30}
31
32impl NameCulture {
33    /// Get all available cultures.
34    pub fn all() -> &'static [NameCulture] {
35        &[
36            NameCulture::WesternUs,
37            NameCulture::German,
38            NameCulture::French,
39            NameCulture::Chinese,
40            NameCulture::Japanese,
41            NameCulture::Indian,
42            NameCulture::Hispanic,
43        ]
44    }
45}
46
47/// A generated person name with components.
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct PersonName {
50    /// First name
51    pub first_name: String,
52    /// Last name
53    pub last_name: String,
54    /// Full display name
55    pub display_name: String,
56    /// Culture of origin
57    pub culture: NameCulture,
58    /// Whether the name is typically male
59    pub is_male: bool,
60}
61
62impl PersonName {
63    /// Generate a user ID from the name (e.g., "JSMITH001").
64    pub fn to_user_id(&self, index: usize) -> String {
65        let first_initial = self.first_name.chars().next().unwrap_or('X');
66        let last_part: String = self
67            .last_name
68            .chars()
69            .filter(|c| c.is_ascii_alphabetic())
70            .take(5)
71            .collect();
72        format!("{}{}{:03}", first_initial, last_part.to_uppercase(), index)
73    }
74
75    /// Generate an email address from the name.
76    pub fn to_email(&self, domain: &str) -> String {
77        let first = self
78            .first_name
79            .chars()
80            .filter(|c| c.is_ascii_alphabetic())
81            .collect::<String>()
82            .to_lowercase();
83        let last = self
84            .last_name
85            .chars()
86            .filter(|c| c.is_ascii_alphabetic())
87            .collect::<String>()
88            .to_lowercase();
89        format!("{}.{}@{}", first, last, domain)
90    }
91}
92
93/// Pool of names for a specific culture.
94#[derive(Debug, Clone)]
95pub struct NamePool {
96    /// Male first names
97    pub first_names_male: Vec<&'static str>,
98    /// Female first names
99    pub first_names_female: Vec<&'static str>,
100    /// Last names / family names
101    pub last_names: Vec<&'static str>,
102    /// The culture this pool represents
103    pub culture: NameCulture,
104}
105
106impl NamePool {
107    /// Create a Western US name pool.
108    pub fn western_us() -> Self {
109        Self {
110            culture: NameCulture::WesternUs,
111            first_names_male: vec![
112                "James",
113                "John",
114                "Robert",
115                "Michael",
116                "William",
117                "David",
118                "Richard",
119                "Joseph",
120                "Thomas",
121                "Christopher",
122                "Charles",
123                "Daniel",
124                "Matthew",
125                "Anthony",
126                "Mark",
127                "Donald",
128                "Steven",
129                "Paul",
130                "Andrew",
131                "Joshua",
132                "Kenneth",
133                "Kevin",
134                "Brian",
135                "George",
136                "Timothy",
137                "Ronald",
138                "Edward",
139                "Jason",
140                "Jeffrey",
141                "Ryan",
142                "Jacob",
143                "Gary",
144                "Nicholas",
145                "Eric",
146                "Jonathan",
147                "Stephen",
148                "Larry",
149                "Justin",
150                "Scott",
151                "Brandon",
152                "Benjamin",
153                "Samuel",
154                "Raymond",
155                "Gregory",
156                "Frank",
157                "Alexander",
158                "Patrick",
159                "Jack",
160                "Dennis",
161                "Jerry",
162                "Tyler",
163                "Aaron",
164                "Jose",
165                "Adam",
166                "Nathan",
167            ],
168            first_names_female: vec![
169                "Mary",
170                "Patricia",
171                "Jennifer",
172                "Linda",
173                "Barbara",
174                "Elizabeth",
175                "Susan",
176                "Jessica",
177                "Sarah",
178                "Karen",
179                "Lisa",
180                "Nancy",
181                "Betty",
182                "Margaret",
183                "Sandra",
184                "Ashley",
185                "Kimberly",
186                "Emily",
187                "Donna",
188                "Michelle",
189                "Dorothy",
190                "Carol",
191                "Amanda",
192                "Melissa",
193                "Deborah",
194                "Stephanie",
195                "Rebecca",
196                "Sharon",
197                "Laura",
198                "Cynthia",
199                "Kathleen",
200                "Amy",
201                "Angela",
202                "Shirley",
203                "Anna",
204                "Brenda",
205                "Pamela",
206                "Emma",
207                "Nicole",
208                "Helen",
209                "Samantha",
210                "Katherine",
211                "Christine",
212                "Debra",
213                "Rachel",
214                "Carolyn",
215                "Janet",
216                "Catherine",
217                "Maria",
218                "Heather",
219                "Diane",
220                "Ruth",
221                "Julie",
222            ],
223            last_names: vec![
224                "Smith",
225                "Johnson",
226                "Williams",
227                "Brown",
228                "Jones",
229                "Garcia",
230                "Miller",
231                "Davis",
232                "Rodriguez",
233                "Martinez",
234                "Hernandez",
235                "Lopez",
236                "Gonzalez",
237                "Wilson",
238                "Anderson",
239                "Thomas",
240                "Taylor",
241                "Moore",
242                "Jackson",
243                "Martin",
244                "Lee",
245                "Perez",
246                "Thompson",
247                "White",
248                "Harris",
249                "Sanchez",
250                "Clark",
251                "Ramirez",
252                "Lewis",
253                "Robinson",
254                "Walker",
255                "Young",
256                "Allen",
257                "King",
258                "Wright",
259                "Scott",
260                "Torres",
261                "Nguyen",
262                "Hill",
263                "Flores",
264                "Green",
265                "Adams",
266                "Nelson",
267                "Baker",
268                "Hall",
269                "Rivera",
270                "Campbell",
271                "Mitchell",
272                "Carter",
273                "Roberts",
274                "Turner",
275                "Phillips",
276                "Evans",
277                "Parker",
278                "Edwards",
279                "Collins",
280            ],
281        }
282    }
283
284    /// Create a German name pool.
285    pub fn german() -> Self {
286        Self {
287            culture: NameCulture::German,
288            first_names_male: vec![
289                "Hans",
290                "Klaus",
291                "Wolfgang",
292                "Dieter",
293                "Jürgen",
294                "Peter",
295                "Michael",
296                "Thomas",
297                "Andreas",
298                "Stefan",
299                "Markus",
300                "Christian",
301                "Martin",
302                "Frank",
303                "Bernd",
304                "Uwe",
305                "Ralf",
306                "Werner",
307                "Heinz",
308                "Helmut",
309                "Gerhard",
310                "Manfred",
311                "Horst",
312                "Karl",
313                "Heinrich",
314                "Friedrich",
315                "Wilhelm",
316                "Otto",
317                "Matthias",
318                "Tobias",
319                "Sebastian",
320                "Florian",
321                "Alexander",
322                "Maximilian",
323                "Felix",
324                "Lukas",
325                "Jonas",
326                "Leon",
327                "Paul",
328                "Philipp",
329                "Tim",
330                "Jan",
331                "Nico",
332                "Erik",
333                "Lars",
334                "Sven",
335                "Kai",
336                "Olaf",
337                "Rainer",
338            ],
339            first_names_female: vec![
340                "Anna",
341                "Maria",
342                "Elisabeth",
343                "Ursula",
344                "Helga",
345                "Monika",
346                "Petra",
347                "Sabine",
348                "Karin",
349                "Renate",
350                "Ingrid",
351                "Brigitte",
352                "Gisela",
353                "Erika",
354                "Christa",
355                "Andrea",
356                "Claudia",
357                "Susanne",
358                "Birgit",
359                "Heike",
360                "Martina",
361                "Nicole",
362                "Stefanie",
363                "Julia",
364                "Katharina",
365                "Christina",
366                "Sandra",
367                "Melanie",
368                "Daniela",
369                "Anja",
370                "Tanja",
371                "Simone",
372                "Silke",
373                "Nadine",
374                "Yvonne",
375                "Manuela",
376                "Sonja",
377                "Michaela",
378                "Angelika",
379                "Barbara",
380                "Gabriele",
381                "Beate",
382                "Doris",
383                "Eva",
384                "Franziska",
385                "Lena",
386                "Hannah",
387                "Sophie",
388                "Leonie",
389                "Laura",
390            ],
391            last_names: vec![
392                "Müller",
393                "Schmidt",
394                "Schneider",
395                "Fischer",
396                "Weber",
397                "Meyer",
398                "Wagner",
399                "Becker",
400                "Schulz",
401                "Hoffmann",
402                "Schäfer",
403                "Koch",
404                "Bauer",
405                "Richter",
406                "Klein",
407                "Wolf",
408                "Schröder",
409                "Neumann",
410                "Schwarz",
411                "Zimmermann",
412                "Braun",
413                "Krüger",
414                "Hofmann",
415                "Hartmann",
416                "Lange",
417                "Schmitt",
418                "Werner",
419                "Schmitz",
420                "Krause",
421                "Meier",
422                "Lehmann",
423                "Schmid",
424                "Schulze",
425                "Maier",
426                "Köhler",
427                "Herrmann",
428                "König",
429                "Walter",
430                "Mayer",
431                "Huber",
432                "Kaiser",
433                "Fuchs",
434                "Peters",
435                "Lang",
436                "Scholz",
437                "Möller",
438                "Weiß",
439                "Jung",
440                "Hahn",
441                "Schubert",
442            ],
443        }
444    }
445
446    /// Create a French name pool.
447    pub fn french() -> Self {
448        Self {
449            culture: NameCulture::French,
450            first_names_male: vec![
451                "Jean",
452                "Pierre",
453                "Michel",
454                "André",
455                "Philippe",
456                "Jacques",
457                "Bernard",
458                "Alain",
459                "François",
460                "Robert",
461                "Marcel",
462                "René",
463                "Louis",
464                "Claude",
465                "Daniel",
466                "Yves",
467                "Christian",
468                "Patrick",
469                "Nicolas",
470                "Julien",
471                "Thomas",
472                "Antoine",
473                "Alexandre",
474                "Maxime",
475                "Lucas",
476                "Hugo",
477                "Théo",
478                "Mathieu",
479                "Guillaume",
480                "Laurent",
481                "Olivier",
482                "Christophe",
483                "Sébastien",
484                "Frédéric",
485                "Vincent",
486                "David",
487                "Eric",
488                "Pascal",
489                "Gilles",
490                "Thierry",
491                "Stéphane",
492                "Bruno",
493                "Dominique",
494                "Serge",
495                "Maurice",
496                "Henri",
497                "Paul",
498                "Charles",
499                "Emmanuel",
500                "Raphaël",
501            ],
502            first_names_female: vec![
503                "Marie",
504                "Jeanne",
505                "Françoise",
506                "Monique",
507                "Catherine",
508                "Nathalie",
509                "Isabelle",
510                "Sylvie",
511                "Anne",
512                "Martine",
513                "Nicole",
514                "Christine",
515                "Sophie",
516                "Valérie",
517                "Julie",
518                "Camille",
519                "Léa",
520                "Manon",
521                "Emma",
522                "Chloé",
523                "Inès",
524                "Sarah",
525                "Laura",
526                "Louise",
527                "Jade",
528                "Alice",
529                "Lola",
530                "Margot",
531                "Charlotte",
532                "Clara",
533                "Pauline",
534                "Marine",
535                "Aurélie",
536                "Céline",
537                "Sandrine",
538                "Virginie",
539                "Stéphanie",
540                "Élodie",
541                "Delphine",
542                "Laurence",
543                "Brigitte",
544                "Jacqueline",
545                "Simone",
546                "Denise",
547                "Madeleine",
548                "Thérèse",
549                "Hélène",
550                "Élise",
551                "Juliette",
552                "Marguerite",
553            ],
554            last_names: vec![
555                "Martin",
556                "Bernard",
557                "Dubois",
558                "Thomas",
559                "Robert",
560                "Richard",
561                "Petit",
562                "Durand",
563                "Leroy",
564                "Moreau",
565                "Simon",
566                "Laurent",
567                "Lefebvre",
568                "Michel",
569                "Garcia",
570                "David",
571                "Bertrand",
572                "Roux",
573                "Vincent",
574                "Fournier",
575                "Morel",
576                "Girard",
577                "André",
578                "Lefèvre",
579                "Mercier",
580                "Dupont",
581                "Lambert",
582                "Bonnet",
583                "François",
584                "Martinez",
585                "Legrand",
586                "Garnier",
587                "Faure",
588                "Rousseau",
589                "Blanc",
590                "Guérin",
591                "Muller",
592                "Henry",
593                "Roussel",
594                "Nicolas",
595                "Perrin",
596                "Morin",
597                "Mathieu",
598                "Clément",
599                "Gauthier",
600                "Dumont",
601                "Lopez",
602                "Fontaine",
603                "Chevalier",
604                "Robin",
605            ],
606        }
607    }
608
609    /// Create a Chinese name pool.
610    pub fn chinese() -> Self {
611        Self {
612            culture: NameCulture::Chinese,
613            first_names_male: vec![
614                "Wei", "Fang", "Lei", "Jun", "Jian", "Hao", "Chen", "Yang", "Ming", "Tao", "Long",
615                "Feng", "Bin", "Qiang", "Gang", "Hui", "Peng", "Xiang", "Bo", "Chao", "Dong",
616                "Liang", "Ning", "Kai", "Jie", "Yong", "Hai", "Lin", "Wen", "Zheng", "Hong", "Xin",
617                "Da", "Zhi", "Guang", "Cheng", "Yi", "Sheng", "Biao", "Ping", "Yun", "Song",
618                "Chang", "Kang", "Rui", "Nan", "Jia", "Xiao", "Yu", "Hua",
619            ],
620            first_names_female: vec![
621                "Fang", "Min", "Jing", "Li", "Yan", "Hong", "Juan", "Mei", "Ying", "Xia", "Hui",
622                "Lin", "Ling", "Ping", "Dan", "Yun", "Na", "Qian", "Xin", "Ya", "Wei", "Wen",
623                "Jie", "Qing", "Yu", "Hua", "Yue", "Xue", "Lan", "Zhen", "Rong", "Shu", "Fei",
624                "Lei", "Shan", "Ting", "Ni", "Ying", "Chen", "Huan", "Lu", "Ai", "Xiao", "Xiang",
625                "Yao", "Meng", "Qi", "Jun", "Bei", "Zhi",
626            ],
627            last_names: vec![
628                "Wang", "Li", "Zhang", "Liu", "Chen", "Yang", "Huang", "Zhao", "Wu", "Zhou", "Xu",
629                "Sun", "Ma", "Zhu", "Hu", "Guo", "He", "Lin", "Gao", "Luo", "Zheng", "Liang",
630                "Xie", "Tang", "Song", "Xu", "Han", "Deng", "Feng", "Cao", "Peng", "Xiao", "Cheng",
631                "Yuan", "Tian", "Dong", "Pan", "Cai", "Jiang", "Wei", "Yu", "Du", "Ye", "Shi",
632                "Lu", "Shen", "Su", "Jia", "Fan", "Jin",
633            ],
634        }
635    }
636
637    /// Create a Japanese name pool.
638    pub fn japanese() -> Self {
639        Self {
640            culture: NameCulture::Japanese,
641            first_names_male: vec![
642                "Hiroshi", "Takeshi", "Kenji", "Yuki", "Kazuki", "Ryota", "Daiki", "Shota", "Yuto",
643                "Kenta", "Haruto", "Sota", "Riku", "Yuma", "Kaito", "Ren", "Hayato", "Takumi",
644                "Kouki", "Ryuu", "Naoki", "Tsubasa", "Yuuki", "Akira", "Satoshi", "Makoto",
645                "Tetsuya", "Masaki", "Shin", "Kei", "Daisuke", "Shunsuke", "Tomoya", "Yusuke",
646                "Tatsuya", "Katsuki", "Shun", "Yamato", "Koji", "Hideo", "Takahiro", "Noboru",
647                "Shinji", "Osamu", "Minoru", "Hideki", "Jun", "Masaru", "Ken", "Ryo",
648            ],
649            first_names_female: vec![
650                "Yuki", "Sakura", "Hana", "Yui", "Mio", "Rin", "Aoi", "Mei", "Saki", "Miku",
651                "Nanami", "Ayaka", "Misaki", "Haruka", "Momoka", "Rina", "Yuna", "Hinata",
652                "Koharu", "Miyu", "Akari", "Hikari", "Kaede", "Natsuki", "Mai", "Ami", "Aya",
653                "Emi", "Kana", "Megumi", "Tomoko", "Yoko", "Keiko", "Naomi", "Mayumi", "Chika",
654                "Nana", "Risa", "Asuka", "Fumiko", "Kyoko", "Reiko", "Noriko", "Sachiko", "Mariko",
655                "Shiori", "Midori", "Kanako", "Minami", "Eriko",
656            ],
657            last_names: vec![
658                "Sato",
659                "Suzuki",
660                "Takahashi",
661                "Tanaka",
662                "Watanabe",
663                "Ito",
664                "Yamamoto",
665                "Nakamura",
666                "Kobayashi",
667                "Kato",
668                "Yoshida",
669                "Yamada",
670                "Sasaki",
671                "Yamaguchi",
672                "Matsumoto",
673                "Inoue",
674                "Kimura",
675                "Hayashi",
676                "Shimizu",
677                "Yamazaki",
678                "Mori",
679                "Abe",
680                "Ikeda",
681                "Hashimoto",
682                "Yamashita",
683                "Ishikawa",
684                "Nakajima",
685                "Maeda",
686                "Fujita",
687                "Ogawa",
688                "Goto",
689                "Okada",
690                "Hasegawa",
691                "Murakami",
692                "Kondo",
693                "Ishii",
694                "Saito",
695                "Sakamoto",
696                "Endo",
697                "Aoki",
698                "Fujii",
699                "Nishimura",
700                "Fukuda",
701                "Ota",
702                "Miura",
703                "Okamoto",
704                "Matsuda",
705                "Nakagawa",
706                "Fujiwara",
707                "Kawamura",
708            ],
709        }
710    }
711
712    /// Create an Indian name pool.
713    pub fn indian() -> Self {
714        Self {
715            culture: NameCulture::Indian,
716            first_names_male: vec![
717                "Raj", "Amit", "Vikram", "Rahul", "Sanjay", "Arun", "Suresh", "Rajesh", "Deepak",
718                "Vijay", "Prakash", "Manoj", "Sunil", "Anil", "Ravi", "Ashok", "Ramesh", "Mukesh",
719                "Sandeep", "Ajay", "Naveen", "Pradeep", "Sachin", "Nitin", "Vinod", "Rakesh",
720                "Srinivas", "Ganesh", "Krishna", "Mohan", "Kiran", "Venkat", "Hari", "Shankar",
721                "Dinesh", "Mahesh", "Satish", "Girish", "Naresh", "Harish", "Pavan", "Arjun",
722                "Anand", "Vivek", "Rohit", "Gaurav", "Kunal", "Vishal", "Akhil", "Dev",
723            ],
724            first_names_female: vec![
725                "Priya",
726                "Anita",
727                "Sunita",
728                "Kavita",
729                "Neha",
730                "Pooja",
731                "Anjali",
732                "Deepa",
733                "Meena",
734                "Rekha",
735                "Lakshmi",
736                "Padma",
737                "Gita",
738                "Sita",
739                "Rani",
740                "Devi",
741                "Shalini",
742                "Nisha",
743                "Swati",
744                "Preeti",
745                "Divya",
746                "Shreya",
747                "Aishwarya",
748                "Ritu",
749                "Seema",
750                "Jyoti",
751                "Shweta",
752                "Pallavi",
753                "Rashmi",
754                "Smita",
755                "Varsha",
756                "Archana",
757                "Asha",
758                "Manisha",
759                "Usha",
760                "Vandana",
761                "Geeta",
762                "Rita",
763                "Maya",
764                "Radha",
765                "Sapna",
766                "Megha",
767                "Nikita",
768                "Tanvi",
769                "Aditi",
770                "Bhavna",
771                "Chitra",
772                "Komal",
773                "Madhuri",
774                "Parul",
775            ],
776            last_names: vec![
777                "Sharma",
778                "Patel",
779                "Singh",
780                "Kumar",
781                "Gupta",
782                "Joshi",
783                "Verma",
784                "Shah",
785                "Mehta",
786                "Reddy",
787                "Rao",
788                "Iyer",
789                "Nair",
790                "Menon",
791                "Pillai",
792                "Das",
793                "Bose",
794                "Sen",
795                "Chatterjee",
796                "Banerjee",
797                "Mukherjee",
798                "Ghosh",
799                "Roy",
800                "Dutta",
801                "Kapoor",
802                "Malhotra",
803                "Agarwal",
804                "Sinha",
805                "Thakur",
806                "Saxena",
807                "Mishra",
808                "Pandey",
809                "Trivedi",
810                "Desai",
811                "Modi",
812                "Kulkarni",
813                "Patil",
814                "Kaur",
815                "Chopra",
816                "Khanna",
817                "Bhatia",
818                "Choudhury",
819                "Rajan",
820                "Subramaniam",
821                "Venkatesh",
822                "Naidu",
823                "Hegde",
824                "Shukla",
825                "Prasad",
826                "Murthy",
827            ],
828        }
829    }
830
831    /// Create a Hispanic name pool.
832    pub fn hispanic() -> Self {
833        Self {
834            culture: NameCulture::Hispanic,
835            first_names_male: vec![
836                "José",
837                "Juan",
838                "Carlos",
839                "Luis",
840                "Miguel",
841                "Francisco",
842                "Antonio",
843                "Manuel",
844                "Pedro",
845                "Javier",
846                "Fernando",
847                "Rafael",
848                "Alejandro",
849                "Ricardo",
850                "Eduardo",
851                "Roberto",
852                "Daniel",
853                "Pablo",
854                "Sergio",
855                "Jorge",
856                "Andrés",
857                "Raúl",
858                "Diego",
859                "Enrique",
860                "Óscar",
861                "Adrián",
862                "Víctor",
863                "Martín",
864                "Gabriel",
865                "Álvaro",
866                "Iván",
867                "Mario",
868                "César",
869                "Héctor",
870                "Alberto",
871                "Gustavo",
872                "Arturo",
873                "Ramón",
874                "Hugo",
875                "Salvador",
876                "Ernesto",
877                "Guillermo",
878                "Ignacio",
879                "Jaime",
880                "Felipe",
881                "Tomás",
882                "Santiago",
883                "Mateo",
884                "Sebastián",
885                "Nicolás",
886            ],
887            first_names_female: vec![
888                "María",
889                "Carmen",
890                "Ana",
891                "Laura",
892                "Rosa",
893                "Isabel",
894                "Elena",
895                "Patricia",
896                "Teresa",
897                "Lucia",
898                "Pilar",
899                "Dolores",
900                "Paula",
901                "Sara",
902                "Marta",
903                "Julia",
904                "Cristina",
905                "Claudia",
906                "Andrea",
907                "Mónica",
908                "Gloria",
909                "Beatriz",
910                "Alicia",
911                "Rocío",
912                "Victoria",
913                "Silvia",
914                "Eva",
915                "Raquel",
916                "Adriana",
917                "Lorena",
918                "Gabriela",
919                "Marina",
920                "Sandra",
921                "Verónica",
922                "Natalia",
923                "Carolina",
924                "Diana",
925                "Alejandra",
926                "Cecilia",
927                "Daniela",
928                "Sofía",
929                "Valentina",
930                "Camila",
931                "Isabella",
932                "Mariana",
933                "Fernanda",
934                "Paola",
935                "Liliana",
936                "Angela",
937                "Inés",
938            ],
939            last_names: vec![
940                "García",
941                "Rodríguez",
942                "Martínez",
943                "López",
944                "González",
945                "Hernández",
946                "Pérez",
947                "Sánchez",
948                "Ramírez",
949                "Torres",
950                "Flores",
951                "Rivera",
952                "Gómez",
953                "Díaz",
954                "Reyes",
955                "Cruz",
956                "Morales",
957                "Ortiz",
958                "Gutiérrez",
959                "Chávez",
960                "Ramos",
961                "Vargas",
962                "Castillo",
963                "Jiménez",
964                "Moreno",
965                "Romero",
966                "Herrera",
967                "Medina",
968                "Aguilar",
969                "Vega",
970                "Castro",
971                "Mendoza",
972                "Ruiz",
973                "Fernández",
974                "Álvarez",
975                "Muñoz",
976                "Rojas",
977                "Silva",
978                "Suárez",
979                "Delgado",
980                "Navarro",
981                "Santos",
982                "Molina",
983                "Espinoza",
984                "Guerrero",
985                "Cabrera",
986                "Campos",
987                "Cortés",
988                "Salazar",
989                "Luna",
990            ],
991        }
992    }
993
994    /// Get a name pool for a specific culture.
995    pub fn for_culture(culture: NameCulture) -> Self {
996        match culture {
997            NameCulture::WesternUs => Self::western_us(),
998            NameCulture::German => Self::german(),
999            NameCulture::French => Self::french(),
1000            NameCulture::Chinese => Self::chinese(),
1001            NameCulture::Japanese => Self::japanese(),
1002            NameCulture::Indian => Self::indian(),
1003            NameCulture::Hispanic => Self::hispanic(),
1004        }
1005    }
1006
1007    /// Generate a random name from this pool.
1008    pub fn generate_name(&self, rng: &mut impl Rng) -> PersonName {
1009        let is_male = rng.gen_bool(0.5);
1010
1011        let first_name = if is_male {
1012            self.first_names_male.choose(rng).unwrap()
1013        } else {
1014            self.first_names_female.choose(rng).unwrap()
1015        };
1016
1017        let last_name = self.last_names.choose(rng).unwrap();
1018
1019        PersonName {
1020            first_name: (*first_name).to_string(),
1021            last_name: (*last_name).to_string(),
1022            display_name: format!("{} {}", first_name, last_name),
1023            culture: self.culture,
1024            is_male,
1025        }
1026    }
1027}
1028
1029/// Multi-culture name generator with weighted distribution.
1030#[derive(Debug, Clone)]
1031pub struct MultiCultureNameGenerator {
1032    pools: HashMap<NameCulture, NamePool>,
1033    distribution: Vec<(NameCulture, f64)>,
1034    email_domain: String,
1035}
1036
1037impl Default for MultiCultureNameGenerator {
1038    fn default() -> Self {
1039        Self::new()
1040    }
1041}
1042
1043impl MultiCultureNameGenerator {
1044    /// Create a new generator with default distribution.
1045    pub fn new() -> Self {
1046        let mut pools = HashMap::new();
1047        for culture in NameCulture::all() {
1048            pools.insert(*culture, NamePool::for_culture(*culture));
1049        }
1050
1051        Self {
1052            pools,
1053            distribution: vec![
1054                (NameCulture::WesternUs, 0.40),
1055                (NameCulture::Hispanic, 0.20),
1056                (NameCulture::German, 0.10),
1057                (NameCulture::French, 0.05),
1058                (NameCulture::Chinese, 0.10),
1059                (NameCulture::Japanese, 0.05),
1060                (NameCulture::Indian, 0.10),
1061            ],
1062            email_domain: "company.com".to_string(),
1063        }
1064    }
1065
1066    /// Create a generator with custom distribution.
1067    pub fn with_distribution(distribution: Vec<(NameCulture, f64)>) -> Self {
1068        let mut gen = Self::new();
1069        gen.distribution = distribution;
1070        gen
1071    }
1072
1073    /// Set the email domain.
1074    pub fn with_email_domain(mut self, domain: &str) -> Self {
1075        self.email_domain = domain.to_string();
1076        self
1077    }
1078
1079    /// Set the email domain (mutable reference).
1080    pub fn set_email_domain(&mut self, domain: &str) {
1081        self.email_domain = domain.to_string();
1082    }
1083
1084    /// Get the email domain.
1085    pub fn email_domain(&self) -> &str {
1086        &self.email_domain
1087    }
1088
1089    /// Select a culture based on the distribution.
1090    fn select_culture(&self, rng: &mut impl Rng) -> NameCulture {
1091        let roll: f64 = rng.gen();
1092        let mut cumulative = 0.0;
1093
1094        for (culture, weight) in &self.distribution {
1095            cumulative += weight;
1096            if roll < cumulative {
1097                return *culture;
1098            }
1099        }
1100
1101        // Fallback to default
1102        NameCulture::WesternUs
1103    }
1104
1105    /// Generate a random name from the weighted distribution.
1106    pub fn generate_name(&self, rng: &mut impl Rng) -> PersonName {
1107        let culture = self.select_culture(rng);
1108        self.pools
1109            .get(&culture)
1110            .map(|pool| pool.generate_name(rng))
1111            .unwrap_or_else(|| NamePool::western_us().generate_name(rng))
1112    }
1113
1114    /// Generate a name with a specific culture.
1115    pub fn generate_name_for_culture(
1116        &self,
1117        culture: NameCulture,
1118        rng: &mut impl Rng,
1119    ) -> PersonName {
1120        self.pools
1121            .get(&culture)
1122            .map(|pool| pool.generate_name(rng))
1123            .unwrap_or_else(|| NamePool::western_us().generate_name(rng))
1124    }
1125
1126    /// Generate a user ID from a name.
1127    pub fn generate_user_id(&self, name: &PersonName, index: usize) -> String {
1128        name.to_user_id(index)
1129    }
1130
1131    /// Generate an email from a name.
1132    pub fn generate_email(&self, name: &PersonName) -> String {
1133        name.to_email(&self.email_domain)
1134    }
1135}
1136
1137#[cfg(test)]
1138mod tests {
1139    use super::*;
1140    use rand::SeedableRng;
1141    use rand_chacha::ChaCha8Rng;
1142
1143    #[test]
1144    fn test_name_pool_generation() {
1145        let mut rng = ChaCha8Rng::seed_from_u64(42);
1146        let pool = NamePool::western_us();
1147        let name = pool.generate_name(&mut rng);
1148
1149        assert!(!name.first_name.is_empty());
1150        assert!(!name.last_name.is_empty());
1151        assert_eq!(name.culture, NameCulture::WesternUs);
1152    }
1153
1154    #[test]
1155    fn test_multi_culture_generator() {
1156        let mut rng = ChaCha8Rng::seed_from_u64(42);
1157        let generator = MultiCultureNameGenerator::new();
1158
1159        // Generate multiple names and check distribution
1160        let mut culture_counts: HashMap<NameCulture, usize> = HashMap::new();
1161        for _ in 0..100 {
1162            let name = generator.generate_name(&mut rng);
1163            *culture_counts.entry(name.culture).or_insert(0) += 1;
1164        }
1165
1166        // Should have some diversity
1167        assert!(culture_counts.len() > 1);
1168    }
1169
1170    #[test]
1171    fn test_user_id_generation() {
1172        let name = PersonName {
1173            first_name: "John".to_string(),
1174            last_name: "Smith".to_string(),
1175            display_name: "John Smith".to_string(),
1176            culture: NameCulture::WesternUs,
1177            is_male: true,
1178        };
1179
1180        let user_id = name.to_user_id(42);
1181        assert_eq!(user_id, "JSMITH042");
1182    }
1183
1184    #[test]
1185    fn test_email_generation() {
1186        let name = PersonName {
1187            first_name: "María".to_string(),
1188            last_name: "García".to_string(),
1189            display_name: "María García".to_string(),
1190            culture: NameCulture::Hispanic,
1191            is_male: false,
1192        };
1193
1194        let email = name.to_email("acme.com");
1195        assert_eq!(email, "mara.garca@acme.com"); // Note: non-ASCII stripped
1196    }
1197
1198    #[test]
1199    fn test_all_culture_pools() {
1200        let mut rng = ChaCha8Rng::seed_from_u64(42);
1201
1202        for culture in NameCulture::all() {
1203            let pool = NamePool::for_culture(*culture);
1204            let name = pool.generate_name(&mut rng);
1205            assert!(!name.first_name.is_empty());
1206            assert!(!name.last_name.is_empty());
1207            assert_eq!(name.culture, *culture);
1208        }
1209    }
1210}