using Core.Entities; namespace Core.Utility; /// /// Utility class for formatting student names for teams with various formatting options. /// public static class TeamStudentNameFormatter { /// /// Options for formatting student names. /// public record FormatOptions { /// /// Style for indicating the team captain. Default is None. /// public CaptainIndicatorStyle CaptainIndicator { get; init; } = CaptainIndicatorStyle.None; /// /// How to order the students. Default is None (preserve original order). /// public OrderingStyle Ordering { get; init; } = OrderingStyle.None; /// /// Whether to add "*" suffix for students with schedule overlaps. Default is false. /// public bool MarkOverlaps { get; init; } = false; /// /// Whether to add "(absent)" suffix for absent students. Default is false. /// public bool MarkAbsent { get; init; } = false; /// /// Function to determine if a student has overlaps. Required if MarkOverlaps is true. /// public Func? HasOverlaps { get; init; } /// /// Collection of absent students. Required if MarkAbsent is true. /// public ICollection? AbsentStudents { get; init; } /// /// Whether to only show captain indicator for team events (EventFormat.Team). Default is true. /// public bool OnlyTeamEventsForCaptain { get; init; } = true; } /// /// Style for indicating the team captain. /// public enum CaptainIndicatorStyle { /// /// No captain indicator. /// None, /// /// Use asterisk (*) for captain. /// Star, /// /// Use "(Cpt)" for captain. /// Captain } /// /// Style for ordering students. /// public enum OrderingStyle { /// /// Preserve original order. /// None, /// /// Captain first, then alphabetical by first name. /// CaptainFirst, /// /// Alphabetical by first name. /// Alphabetical, /// /// By grade + TSA year descending (highest first). /// GradeDescending } /// /// Formats a single student name with the specified options. /// /// The student to format. /// The team the student belongs to. /// Formatting options. /// Formatted student name. public static string FormatStudentName(Student student, Team team, FormatOptions options) { if (student == null) return string.Empty; var name = student.FirstName; // Add captain indicator (before overlap/absent markers) if (options.CaptainIndicator != CaptainIndicatorStyle.None && team != null && team.Captain != null && team.Captain.Equals(student)) { var shouldShow = !options.OnlyTeamEventsForCaptain || (team.Event != null && team.Event.EventFormat == EventFormat.Team); if (shouldShow) { name += options.CaptainIndicator switch { CaptainIndicatorStyle.Star => "*", CaptainIndicatorStyle.Captain => "(Cpt)", _ => string.Empty }; } } // Convert collections/functions to booleans for StudentNameFormatter var hasOverlap = options.MarkOverlaps && options.HasOverlaps != null && options.HasOverlaps(student); var isAbsent = options.MarkAbsent && options.AbsentStudents != null && options.AbsentStudents.Contains(student); // Use StudentNameFormatter for overlap/absent markers (appended after captain indicator) var studentNameOptions = new StudentNameFormatter.FormatOptions { HasOverlap = hasOverlap, IsAbsent = isAbsent }; // Get the suffix from StudentNameFormatter (overlap/absent markers) var baseFormatted = StudentNameFormatter.FormatStudentName(student, studentNameOptions); var suffix = baseFormatted.Substring(student.FirstName.Length); return name + suffix; } /// /// Formats all students for a team as a comma-separated string. /// /// The team to format students for. /// Formatting options. /// Comma-separated string of formatted student names. public static string FormatStudentList(Team team, FormatOptions options) { if (team?.Students == null || !team.Students.Any()) return string.Empty; var students = ApplyOrdering(team.Students, team, options.Ordering); return string.Join(", ", students.Select(s => FormatStudentName(s, team, options))); } /// /// Formats all students for a team as a list of strings. /// /// The team to format students for. /// Formatting options. /// List of formatted student names. public static List FormatStudentListAsList(Team team, FormatOptions options) { if (team?.Students == null || !team.Students.Any()) return []; var students = ApplyOrdering(team.Students, team, options.Ordering); return students.Select(s => FormatStudentName(s, team, options)).ToList(); } /// /// Formats all unique students across multiple teams for an event definition. /// Returns a list of unique student names (by student ID). /// /// The event definition. /// The teams for this event. /// Formatting options. /// List of unique formatted student names. public static List FormatStudentListForEvent(EventDefinition eventDefinition, IEnumerable teams, FormatOptions options) { if (eventDefinition == null || teams == null) return []; var teamsList = teams.ToList(); if (!teamsList.Any()) return []; // Get all unique students from all teams for this event var allStudents = teamsList .SelectMany(t => t.Students) .DistinctBy(s => s.Id) .ToList(); if (!allStudents.Any()) return []; // Determine if each student is a captain in any team var studentsWithCaptainInfo = allStudents.Select(s => { var isCaptain = teamsList.Any(t => t.Captain != null && t.Captain.Equals(s)); // Find a team this student belongs to for formatting context var studentTeam = teamsList.FirstOrDefault(t => t.Students.Contains(s)); return new { Student = s, IsCaptain = isCaptain, Team = studentTeam }; }).ToList(); // Apply ordering var orderedStudents = options.Ordering switch { OrderingStyle.CaptainFirst => studentsWithCaptainInfo .OrderBy(x => !x.IsCaptain) .ThenBy(x => x.Student.FirstName) .Select(x => x.Student), OrderingStyle.Alphabetical => studentsWithCaptainInfo .OrderBy(x => x.Student.FirstName) .Select(x => x.Student), OrderingStyle.GradeDescending => studentsWithCaptainInfo .OrderByDescending(x => x.Student.Grade + x.Student.TsaYear) .Select(x => x.Student), _ => studentsWithCaptainInfo.Select(x => x.Student) }; // Format names - for event-level formatting, we need to handle captain indicator specially // since a student might be captain in one team but not another return orderedStudents.Select(s => { var studentInfo = studentsWithCaptainInfo.First(x => x.Student.Equals(s)); // Use the student's team if available, otherwise create a minimal team for formatting context var team = studentInfo.Team ?? new Team { Students = [s], Event = eventDefinition }; // Create options that handle captain indicator for event-level var eventOptions = options with { CaptainIndicator = studentInfo.IsCaptain && (!options.OnlyTeamEventsForCaptain || eventDefinition.EventFormat == EventFormat.Team) ? options.CaptainIndicator : CaptainIndicatorStyle.None }; return FormatStudentName(s, team, eventOptions); }).ToList(); } private static IEnumerable ApplyOrdering(IEnumerable students, Team team, OrderingStyle ordering) { return ordering switch { OrderingStyle.CaptainFirst => students .OrderBy(s => team.Captain == null || !team.Captain.Equals(s)) .ThenBy(s => s.FirstName), OrderingStyle.Alphabetical => students.OrderBy(s => s.FirstName), OrderingStyle.GradeDescending => students.OrderByDescending(s => s.Grade + s.TsaYear), _ => students }; } }