import '../social/handle_validator.dart'; /// Result of a Club slug validation check. class ClubSlugValidationResult { final bool isValid; final String? error; final String? normalizedSlug; const ClubSlugValidationResult._({ required this.isValid, this.error, this.normalizedSlug, }); factory ClubSlugValidationResult.valid(String normalized) => ClubSlugValidationResult._(isValid: true, normalizedSlug: normalized); factory ClubSlugValidationResult.invalid(String error) => ClubSlugValidationResult._(isValid: false, error: error); } /// Normalization and validation logic for MotorSignal Club slugs. class ClubSlugValidator { static const int minLength = 3; static const int maxLength = 30; static final RegExp _formatRegex = RegExp(r'^[a-z0-9][a-z0-9-]{1,28}[a-z0-9]$'); /// Reserved words specific to clubs, chapters, and platform organizations. static const Set clubReservedWords = { 'club', 'clubs', 'motorcycle', 'motorcycles', 'mc', 'chapter', 'chapters', 'national', 'international', 'official', 'rally', 'groupride', 'grouprides', 'event', 'events', 'all', 'global', 'feed', 'roster', 'members', 'settings', 'admin', }; /// Normalizes a raw slug: trims whitespace and lowercases. static String normalize(String rawSlug) => rawSlug.trim().toLowerCase(); /// Validates a candidate slug format and checks against reserved words. static ClubSlugValidationResult validate(String rawSlug) { final normalized = normalize(rawSlug); if (normalized.isEmpty) { return ClubSlugValidationResult.invalid('Club slug cannot be empty'); } if (normalized.length < minLength) { return ClubSlugValidationResult.invalid( 'Club slug must be at least $minLength characters', ); } if (normalized.length > maxLength) { return ClubSlugValidationResult.invalid( 'Club slug cannot exceed $maxLength characters', ); } if (normalized.contains('--')) { return ClubSlugValidationResult.invalid( 'Club slug cannot contain consecutive hyphens', ); } if (!_formatRegex.hasMatch(normalized)) { return ClubSlugValidationResult.invalid( 'Club slug must start and end with a letter or number, and contain only lowercase letters, numbers, and hyphens', ); } // Check against standard platform handles (admin, support, motorsignal, etc.) if (HandleValidator.reservedHandles.contains(normalized)) { return ClubSlugValidationResult.invalid('This club slug is reserved'); } // Check against club-specific reserved words if (clubReservedWords.contains(normalized)) { return ClubSlugValidationResult.invalid('This club slug is reserved'); } return ClubSlugValidationResult.valid(normalized); } }