namespace PlotLine.Services; public static class CharacterSexHintService { public const string Unknown = "Unknown"; public const string Female = "Female"; public const string Male = "Male"; public const string NonBinaryOrOther = "NonBinaryOrOther"; private static readonly HashSet FemaleNames = new(StringComparer.OrdinalIgnoreCase) { "abby", "abigail", "ada", "adele", "alice", "alison", "allison", "amanda", "amelia", "amy", "angela", "anna", "anne", "annie", "barbara", "becky", "belinda", "beth", "bethany", "brenda", "carol", "caroline", "catherine", "charlotte", "chloe", "chrissy", "christine", "cindy", "claire", "clara", "daisy", "debbie", "debby", "deborah", "diana", "diane", "donna", "dorothy", "elaine", "elizabeth", "ella", "ellie", "emily", "emma", "eva", "eve", "fiona", "gemma", "georgina", "grace", "hannah", "harriet", "heather", "helen", "isabel", "isabella", "jacqueline", "jane", "janet", "janice", "jean", "jennifer", "jenny", "jessica", "joanne", "judy", "julia", "julie", "karen", "kate", "katie", "katherine", "kathryn", "kelly", "laura", "lesley", "linda", "lisa", "lily", "louise", "lucy", "maggie", "margaret", "maria", "marie", "marion", "mary", "michelle", "molly", "nicola", "olivia", "pamela", "patricia", "paula", "rachel", "rebecca", "rose", "sally", "samantha", "sandra", "sarah", "shirley", "sophie", "stephanie", "susan", "suzanne", "tracey", "tracy", "valerie", "victoria", "wendy", "zoe" }; private static readonly HashSet MaleNames = new(StringComparer.OrdinalIgnoreCase) { "adam", "alan", "albert", "alexander", "andrew", "anthony", "arthur", "barry", "ben", "benjamin", "bernard", "brian", "bruce", "charles", "chris", "christopher", "colin", "craig", "daniel", "darren", "david", "dennis", "douglas", "edward", "eric", "frank", "gary", "geoff", "geoffrey", "george", "graham", "grant", "greg", "harry", "henry", "howard", "ian", "jack", "james", "john", "jonathan", "joseph", "keith", "kenneth", "kevin", "liam", "martin", "mark", "matthew", "michael", "neil", "nick", "nicolas", "nicholas", "nigel", "oliver", "paul", "peter", "phil", "philip", "raymond", "richard", "robert", "roger", "ronald", "samuel", "simon", "stephen", "steven", "stuart", "terence", "thomas", "tim", "timothy", "tom", "tony", "william" }; private static readonly HashSet AmbiguousNames = new(StringComparer.OrdinalIgnoreCase) { "alex", "andy", "ashley", "billie", "blair", "bobbie", "casey", "charlie", "drew", "frankie", "jamie", "jessie", "jo", "jordan", "lee", "leslie", "morgan", "pat", "riley", "robyn", "robin", "sam", "sandy", "shannon", "taylor", "terry" }; private static readonly Dictionary TitleHints = new(StringComparer.OrdinalIgnoreCase) { ["mr"] = Male, ["mister"] = Male, ["sir"] = Male, ["mrs"] = Female, ["miss"] = Female, ["ms"] = Female, ["madam"] = Female, ["lady"] = Female }; public static string Suggest(string? name) { var token = FirstNameToken(name, out var titleHint); if (!string.IsNullOrWhiteSpace(titleHint)) { return titleHint; } if (string.IsNullOrWhiteSpace(token) || AmbiguousNames.Contains(token)) { return Unknown; } if (FemaleNames.Contains(token)) { return Female; } return MaleNames.Contains(token) ? Male : Unknown; } public static string Normalise(string? value, string? fallbackName = null) => value?.Trim() switch { Female => Female, Male => Male, NonBinaryOrOther => NonBinaryOrOther, Unknown => Unknown, _ => Suggest(fallbackName) }; private static string FirstNameToken(string? name, out string? titleHint) { titleHint = null; var clean = name?.Trim(); if (string.IsNullOrWhiteSpace(clean)) { return string.Empty; } var tokens = clean .Split([' ', '-', '_'], StringSplitOptions.RemoveEmptyEntries) .Select(CleanToken) .Where(token => !string.IsNullOrWhiteSpace(token)) .ToList(); if (tokens.Count == 0) { return string.Empty; } if (TitleHints.TryGetValue(tokens[0], out titleHint)) { return tokens.Skip(1).FirstOrDefault() ?? string.Empty; } return tokens[0]; } private static string CleanToken(string token) { return token.Trim('.', ',', ';', ':', '\'', '"', '(', ')', '[', ']').ToLowerInvariant(); } }