using PlotLine.Models; namespace PlotLine.Services; public sealed class CharacterAgeDisplayResult { public bool HasExactAge { get; init; } public string? Label { get; init; } public string? DisplayText { get; init; } } public static class CharacterAgeDisplayService { public static CharacterAgeDisplayResult GetAgeForScene(SceneCharacter character, string? sceneTimeModeName, DateTime? sceneStartDateTime) { var effectiveDate = ResolveEffectiveSceneDate(sceneTimeModeName, sceneStartDateTime); if (character.BirthDate.HasValue && effectiveDate.HasValue) { return new CharacterAgeDisplayResult { HasExactAge = true, Label = "Age in scene", DisplayText = FormatExactAge(character.BirthDate.Value.Date, effectiveDate.Value.Date) }; } if (character.AgeAtSeriesStart.HasValue) { return new CharacterAgeDisplayResult { Label = "Approximate age", DisplayText = $"About {character.AgeAtSeriesStart.Value}" }; } return new CharacterAgeDisplayResult(); } public static CharacterAgeDisplayResult GetAgeForScene(Character character, string? sceneTimeModeName, DateTime? sceneStartDateTime) { var effectiveDate = ResolveEffectiveSceneDate(sceneTimeModeName, sceneStartDateTime); if (character.BirthDate.HasValue && effectiveDate.HasValue) { return new CharacterAgeDisplayResult { HasExactAge = true, Label = "Age in scene", DisplayText = FormatExactAge(character.BirthDate.Value.Date, effectiveDate.Value.Date) }; } if (character.AgeAtSeriesStart.HasValue) { return new CharacterAgeDisplayResult { Label = "Approximate age", DisplayText = $"About {character.AgeAtSeriesStart.Value}" }; } return new CharacterAgeDisplayResult(); } public static DateTime? ResolveEffectiveSceneDate(string? timeModeName, DateTime? startDateTime) { if (!startDateTime.HasValue) { return null; } return string.Equals(timeModeName, "Exact Date", StringComparison.OrdinalIgnoreCase) || string.Equals(timeModeName, "Exact DateTime", StringComparison.OrdinalIgnoreCase) || string.Equals(timeModeName, "Exact Date and Time", StringComparison.OrdinalIgnoreCase) ? startDateTime.Value.Date : null; } public static string FormatExactAge(DateTime birthDate, DateTime sceneDate) { var (years, months) = CalculateExactAge(birthDate, sceneDate); return months == 0 ? $"{years} year{Pluralise(years)}" : $"{years} year{Pluralise(years)}, {months} month{Pluralise(months)}"; } public static (int Years, int Months) CalculateExactAge(DateTime birthDate, DateTime sceneDate) { var years = sceneDate.Year - birthDate.Year; if (sceneDate < birthDate.AddYears(years)) { years--; } if (years < 0) { years = 0; } var lastBirthday = birthDate.AddYears(years); var months = ((sceneDate.Year - lastBirthday.Year) * 12) + sceneDate.Month - lastBirthday.Month; if (sceneDate.Day < lastBirthday.Day) { months--; } if (months < 0) { months = 0; } return (years, months); } private static string Pluralise(int value) => value == 1 ? string.Empty : "s"; }