using MeetingAssistant.Calendar; namespace MeetingAssistant.MeetingNotes; internal static class CalendarAppointmentMetadata { public static MeetingMetadata Create( string? subject, IEnumerable attendees, string? body, DateTimeOffset end) { return new MeetingMetadata( ReadSubject(subject), NormalizeAttendees(attendees), ExtractAgenda(body ?? ""), end); } public static string ReadSubject(string? subject) { var trimmed = subject?.Trim(); return string.IsNullOrWhiteSpace(trimmed) ? "Teams meeting" : trimmed; } public static bool IsTeamsAppointment( string? subject, string? location, string? body, string? url = null) { return TeamsMeetingMarkerDetector.IsTeamsAppointment( subject ?? "", location ?? "", string.Join(Environment.NewLine, body, url)); } public static string ExtractAgenda(string body) { if (string.IsNullOrWhiteSpace(body)) { return ""; } var lines = body .Replace("\r\n", "\n", StringComparison.Ordinal) .Replace('\r', '\n') .Split('\n'); var agendaLines = new List(); foreach (var line in lines) { if (IsTeamsSeparator(line) || TeamsMeetingMarkerDetector.ContainsTeamsMarker(line)) { break; } agendaLines.Add(line); } return string.Join(Environment.NewLine, agendaLines).Trim(); } public static IReadOnlyList NormalizeAttendees(IEnumerable attendees) { return attendees .Select(NormalizeAttendee) .Where(attendee => !string.IsNullOrWhiteSpace(attendee)) .GroupBy(GetAttendeeDeduplicationKey, StringComparer.OrdinalIgnoreCase) .Select(group => group .OrderByDescending(attendee => attendee.Contains('<', StringComparison.Ordinal)) .ThenBy(attendee => attendee.Length) .First()) .ToList(); } private static bool IsTeamsSeparator(string line) { var trimmed = line.Trim(); return trimmed.Length >= 8 && trimmed.All(character => character is '_' or '-' or '*' or ' '); } private static string NormalizeAttendee(string attendee) { var normalized = attendee.Trim() .Replace("mailto:", "", StringComparison.OrdinalIgnoreCase); while (normalized.Contains(" ", StringComparison.Ordinal)) { normalized = normalized.Replace(" ", " ", StringComparison.Ordinal); } var emailStart = normalized.IndexOf('<', StringComparison.Ordinal); var emailEnd = normalized.LastIndexOf('>'); if (emailStart > 0 && emailEnd > emailStart) { var name = normalized[..emailStart].Trim(); var email = normalized[(emailStart + 1)..emailEnd].Trim(); if (name.Equals(email, StringComparison.OrdinalIgnoreCase)) { return email; } return $"{name} <{email}>"; } return normalized; } private static string GetAttendeeDeduplicationKey(string attendee) { var emailStart = attendee.IndexOf('<', StringComparison.Ordinal); var emailEnd = attendee.LastIndexOf('>'); return emailStart > 0 && emailEnd > emailStart ? attendee[(emailStart + 1)..emailEnd].Trim() : attendee.Trim(); } }