using JobTrackerApi.Services; namespace JobTrackerApi.Controllers; // Pure parsing/formatting helpers for GmailController, split out for readability (Wave 2 safe // refactor -- no behaviour change). All are static and side-effect free. public sealed partial class GmailController { private static string ApplySyncBoundary(string query, int lookbackDays, bool includeSpamTrash) { var bounded = (query ?? string.Empty).Trim(); if (!bounded.Contains("newer_than:", StringComparison.OrdinalIgnoreCase)) { bounded = string.IsNullOrWhiteSpace(bounded) ? $"newer_than:{lookbackDays}d" : $"{bounded} newer_than:{lookbackDays}d"; } if (!includeSpamTrash) { if (!bounded.Contains("in:spam", StringComparison.OrdinalIgnoreCase)) bounded += " -in:spam"; if (!bounded.Contains("in:trash", StringComparison.OrdinalIgnoreCase)) bounded += " -in:trash"; } return bounded.Trim(); } private static bool LooksLikeJobRelatedThread(IReadOnlyList orderedMessages) { var sample = string.Join("\n", orderedMessages.Select(item => string.Join(" ", new[] { item.Message.Subject, item.Message.From, item.Message.Snippet }.Where(value => !string.IsNullOrWhiteSpace(value))))); if (string.IsNullOrWhiteSpace(sample)) return false; return sample.Contains("interview", StringComparison.OrdinalIgnoreCase) || sample.Contains("application", StringComparison.OrdinalIgnoreCase) || sample.Contains("recruit", StringComparison.OrdinalIgnoreCase) || sample.Contains("role", StringComparison.OrdinalIgnoreCase) || sample.Contains("position", StringComparison.OrdinalIgnoreCase) || sample.Contains("offer", StringComparison.OrdinalIgnoreCase) || sample.Contains("follow up", StringComparison.OrdinalIgnoreCase) || sample.Contains("follow-up", StringComparison.OrdinalIgnoreCase) || sample.Contains("rejection", StringComparison.OrdinalIgnoreCase); } private static string ToConfidence(int score) { return score switch { >= 30 => "high", >= 16 => "medium", _ => "low" }; } private static string? ExtractFirstEmail(string? value) { if (string.IsNullOrWhiteSpace(value)) return null; var match = System.Text.RegularExpressions.Regex.Match(value, @"[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}", System.Text.RegularExpressions.RegexOptions.IgnoreCase); return match.Success ? match.Value : null; } private static string? ExtractRecruiterName(string? value) { if (string.IsNullOrWhiteSpace(value)) return null; var trimmed = value.Split('<')[0].Trim().Trim('"'); return string.IsNullOrWhiteSpace(trimmed) || trimmed.Contains('@') ? null : trimmed; } private static string? ExtractCompanyName(string? from, string? subject) { var subjectText = (subject ?? string.Empty).Trim(); if (!string.IsNullOrWhiteSpace(subjectText)) { var parts = subjectText.Split(new[] { '-', '–', '|' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); if (parts.Length >= 2) return parts[0]; } var recruiterName = ExtractRecruiterName(from); return recruiterName is { Length: > 0 } && recruiterName.Contains(' ') ? recruiterName.Split(' ').Last() : null; } private static string? ExtractRoleFromSubject(string? subject) { if (string.IsNullOrWhiteSpace(subject)) return null; var trimmed = subject.Trim(); if (trimmed.Contains("interview", StringComparison.OrdinalIgnoreCase)) { return trimmed.Replace("interview", string.Empty, StringComparison.OrdinalIgnoreCase).Trim(' ', '-', ':'); } return trimmed.Length <= 120 ? trimmed : trimmed[..120]; } private static string BuildPopupHtml(bool success, string message) { var escaped = System.Net.WebUtility.HtmlEncode(message); var status = success ? "connected" : "error"; var title = success ? "Gmail connected" : "Gmail connection failed"; var serializedMessage = System.Text.Json.JsonSerializer.Serialize(message); return $@" Gmail connection

{title}

{escaped}

You can close this window.

"; } }