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
};
}
}