namespace GoogleSheetsScheduleImport; /// /// Collapses duplicate site-wide rows (same event name, date, time) that appear in every location column /// (e.g. CURFEW shaded across all rooms) into a single line with no location — still under /// General Schedule, which the parser maps to . /// public static class GlobalEventDeduplicator { private static readonly HashSet BuiltinSiteWideNames = new(StringComparer.OrdinalIgnoreCase) { "CURFEW" }; /// /// First occurrence in row/column order is kept; location is cleared so the import line is not room-specific. /// public static List Deduplicate( IReadOnlyList lines, IReadOnlyCollection? extraSiteWideNames = null) { var siteWide = new HashSet(BuiltinSiteWideNames, StringComparer.OrdinalIgnoreCase); if (extraSiteWideNames != null) { foreach (var n in extraSiteWideNames) { if (!string.IsNullOrWhiteSpace(n)) siteWide.Add(n.Trim()); } } var seen = new HashSet<(string Name, string Month, int Day, string Time)>(); var ordered = lines.OrderBy(l => l.SourceRowStart).ThenBy(l => l.SourceCol).ToList(); var result = new List(ordered.Count); foreach (var line in ordered) { var name = TextNormalization.ForEmitLine(line.Name); if (string.IsNullOrEmpty(name) || !siteWide.Contains(name)) { result.Add(line); continue; } var key = (name, line.Month, line.Day, line.TimeRange); if (!seen.Add(key)) continue; result.Add(line with { Location = string.Empty, Name = name }); } return result; } }