Skip to main content

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
1013                .choose(rng)
1014                .expect("non-empty name list")
1015        } else {
1016            self.first_names_female
1017                .choose(rng)
1018                .expect("non-empty name list")
1019        };
1020
1021        let last_name = self.last_names.choose(rng).expect("non-empty name list");
1022
1023        PersonName {
1024            first_name: (*first_name).to_string(),
1025            last_name: (*last_name).to_string(),
1026            display_name: format!("{} {}", first_name, last_name),
1027            culture: self.culture,
1028            is_male,
1029        }
1030    }
1031}
1032
1033/// Multi-culture name generator with weighted distribution.
1034#[derive(Debug, Clone)]
1035pub struct MultiCultureNameGenerator {
1036    pools: HashMap<NameCulture, NamePool>,
1037    distribution: Vec<(NameCulture, f64)>,
1038    email_domain: String,
1039}
1040
1041impl Default for MultiCultureNameGenerator {
1042    fn default() -> Self {
1043        Self::new()
1044    }
1045}
1046
1047impl MultiCultureNameGenerator {
1048    /// Create a new generator with default distribution.
1049    pub fn new() -> Self {
1050        let mut pools = HashMap::new();
1051        for culture in NameCulture::all() {
1052            pools.insert(*culture, NamePool::for_culture(*culture));
1053        }
1054
1055        Self {
1056            pools,
1057            distribution: vec![
1058                (NameCulture::WesternUs, 0.40),
1059                (NameCulture::Hispanic, 0.20),
1060                (NameCulture::German, 0.10),
1061                (NameCulture::French, 0.05),
1062                (NameCulture::Chinese, 0.10),
1063                (NameCulture::Japanese, 0.05),
1064                (NameCulture::Indian, 0.10),
1065            ],
1066            email_domain: "company.com".to_string(),
1067        }
1068    }
1069
1070    /// Create a generator with custom distribution.
1071    pub fn with_distribution(distribution: Vec<(NameCulture, f64)>) -> Self {
1072        let mut gen = Self::new();
1073        gen.distribution = distribution;
1074        gen
1075    }
1076
1077    /// Set the email domain.
1078    pub fn with_email_domain(mut self, domain: &str) -> Self {
1079        self.email_domain = domain.to_string();
1080        self
1081    }
1082
1083    /// Set the email domain (mutable reference).
1084    pub fn set_email_domain(&mut self, domain: &str) {
1085        self.email_domain = domain.to_string();
1086    }
1087
1088    /// Get the email domain.
1089    pub fn email_domain(&self) -> &str {
1090        &self.email_domain
1091    }
1092
1093    /// Select a culture based on the distribution.
1094    fn select_culture(&self, rng: &mut impl Rng) -> NameCulture {
1095        let roll: f64 = rng.gen();
1096        let mut cumulative = 0.0;
1097
1098        for (culture, weight) in &self.distribution {
1099            cumulative += weight;
1100            if roll < cumulative {
1101                return *culture;
1102            }
1103        }
1104
1105        // Fallback to default
1106        NameCulture::WesternUs
1107    }
1108
1109    /// Generate a random name from the weighted distribution.
1110    pub fn generate_name(&self, rng: &mut impl Rng) -> PersonName {
1111        let culture = self.select_culture(rng);
1112        self.pools
1113            .get(&culture)
1114            .map(|pool| pool.generate_name(rng))
1115            .unwrap_or_else(|| NamePool::western_us().generate_name(rng))
1116    }
1117
1118    /// Generate a name with a specific culture.
1119    pub fn generate_name_for_culture(
1120        &self,
1121        culture: NameCulture,
1122        rng: &mut impl Rng,
1123    ) -> PersonName {
1124        self.pools
1125            .get(&culture)
1126            .map(|pool| pool.generate_name(rng))
1127            .unwrap_or_else(|| NamePool::western_us().generate_name(rng))
1128    }
1129
1130    /// Generate a user ID from a name.
1131    pub fn generate_user_id(&self, name: &PersonName, index: usize) -> String {
1132        name.to_user_id(index)
1133    }
1134
1135    /// Generate an email from a name.
1136    pub fn generate_email(&self, name: &PersonName) -> String {
1137        name.to_email(&self.email_domain)
1138    }
1139}
1140
1141#[cfg(test)]
1142#[allow(clippy::unwrap_used)]
1143mod tests {
1144    use super::*;
1145    use rand::SeedableRng;
1146    use rand_chacha::ChaCha8Rng;
1147
1148    #[test]
1149    fn test_name_pool_generation() {
1150        let mut rng = ChaCha8Rng::seed_from_u64(42);
1151        let pool = NamePool::western_us();
1152        let name = pool.generate_name(&mut rng);
1153
1154        assert!(!name.first_name.is_empty());
1155        assert!(!name.last_name.is_empty());
1156        assert_eq!(name.culture, NameCulture::WesternUs);
1157    }
1158
1159    #[test]
1160    fn test_multi_culture_generator() {
1161        let mut rng = ChaCha8Rng::seed_from_u64(42);
1162        let generator = MultiCultureNameGenerator::new();
1163
1164        // Generate multiple names and check distribution
1165        let mut culture_counts: HashMap<NameCulture, usize> = HashMap::new();
1166        for _ in 0..100 {
1167            let name = generator.generate_name(&mut rng);
1168            *culture_counts.entry(name.culture).or_insert(0) += 1;
1169        }
1170
1171        // Should have some diversity
1172        assert!(culture_counts.len() > 1);
1173    }
1174
1175    #[test]
1176    fn test_user_id_generation() {
1177        let name = PersonName {
1178            first_name: "John".to_string(),
1179            last_name: "Smith".to_string(),
1180            display_name: "John Smith".to_string(),
1181            culture: NameCulture::WesternUs,
1182            is_male: true,
1183        };
1184
1185        let user_id = name.to_user_id(42);
1186        assert_eq!(user_id, "JSMITH042");
1187    }
1188
1189    #[test]
1190    fn test_email_generation() {
1191        let name = PersonName {
1192            first_name: "María".to_string(),
1193            last_name: "García".to_string(),
1194            display_name: "María García".to_string(),
1195            culture: NameCulture::Hispanic,
1196            is_male: false,
1197        };
1198
1199        let email = name.to_email("acme.com");
1200        assert_eq!(email, "mara.garca@acme.com"); // Note: non-ASCII stripped
1201    }
1202
1203    #[test]
1204    fn test_all_culture_pools() {
1205        let mut rng = ChaCha8Rng::seed_from_u64(42);
1206
1207        for culture in NameCulture::all() {
1208            let pool = NamePool::for_culture(*culture);
1209            let name = pool.generate_name(&mut rng);
1210            assert!(!name.first_name.is_empty());
1211            assert!(!name.last_name.is_empty());
1212            assert_eq!(name.culture, *culture);
1213        }
1214    }
1215}