import 'package:real_estate_mobile/features/agents/data/models/agent_type.dart'; class AgentProfile { final String id; final String? userId; final String firstName; final String lastName; final String? headline; final String? bio; final String? avatar; final String? companyName; final String? phone; final String? website; final String? city; final String? state; final String? country; final double? averageRating; final int totalReviews; final bool isVerified; final String? verificationStatus; final String? verificationNote; final bool isFeatured; final bool isActive; final bool isAvailable; final String? email; final AgentType? agentType; final AgentUser? user; final List fieldValues; final String? createdAt; final List legacySpecializations; final List languages; final List serviceAreas; final int? matchPercentage; const AgentProfile({ required this.id, this.userId, required this.firstName, required this.lastName, this.headline, this.bio, this.avatar, this.companyName, this.phone, this.website, this.city, this.state, this.country, this.averageRating, this.totalReviews = 0, this.isVerified = false, this.verificationStatus, this.verificationNote, this.isFeatured = false, this.isActive = true, this.isAvailable = false, this.email, this.agentType, this.user, this.fieldValues = const [], this.createdAt, this.legacySpecializations = const [], this.languages = const [], this.serviceAreas = const [], this.matchPercentage, }); String get fullName => '$firstName $lastName'.trim(); /// Format "Member Since March 2024" from createdAt date string. String get memberSinceText { if (createdAt == null || createdAt!.isEmpty) return 'Member Since 2024'; try { final date = DateTime.parse(createdAt!); const months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December', ]; return 'Member Since ${months[date.month - 1]} ${date.year}'; } catch (_) { return 'Member Since 2024'; } } /// Get avatar URL — check agent avatar first, then user avatar String? get avatarUrl => avatar ?? user?.avatar; /// Get description — check fieldValues 'description' first, then bio (matches web) String get description { for (final fv in fieldValues) { if (fv.fieldSlug == 'description' && fv.textValue != null && fv.textValue!.isNotEmpty) { return fv.textValue!; } } return bio ?? ''; } String get location { // Direct fields first (state first, then city) — normalized if (city != null && state != null) { return '${_normalizeState(state!)}, ${_stripStatePrefix(city!)}'; } if (state != null) return _normalizeState(state!); if (city != null) return _stripStatePrefix(city!); // Fallback: extract from fieldValues (matches web getLocationString) String? fvCity; String? fvState; for (final fv in fieldValues) { final slug = fv.fieldSlug.toLowerCase(); if (slug == 'city' || slug == 'city_name') { fvCity = _extractFieldString(fv, isCity: true); } else if (slug == 'state' || slug == 'state_name') { fvState = _extractFieldString(fv, isCity: false); } } final parts = [fvState, fvCity].where((s) => s != null && s.isNotEmpty); if (parts.isNotEmpty) return parts.join(', '); if (country != null && country!.isNotEmpty) return country!; if (serviceAreas.isNotEmpty) return serviceAreas.first; return ''; } /// Uppercase 2-letter state codes (e.g. "nd" → "ND"); leave full names alone. static String _normalizeState(String raw) { final trimmed = raw.trim(); if (trimmed.length == 2) return trimmed.toUpperCase(); return trimmed; } /// Strip the 2-letter state prefix from a city value ("nd_hurley" → "Hurley", /// "Nd Hurley" → "Hurley"). Matches web's `stripCityPrefix` behavior. static String _stripStatePrefix(String raw) { var s = raw.trim(); // Snake-case prefix: nd_hurley s = s.replaceFirst(RegExp(r'^[a-z]{2}_', caseSensitive: false), ''); // Title-cased prefix token: "Nd Hurley" s = s.replaceFirst(RegExp(r'^[A-Za-z]{2}\s+'), ''); return s; } /// Extract a display string from a field value (handles jsonValue arrays). /// For city fields, strip the state-code prefix before title-casing. static String? _extractFieldString(AgentFieldValue fv, {required bool isCity}) { String format(String raw) { var s = raw; if (isCity) { // Strip slug state prefix: "nd_hurley" → "hurley" s = s.replaceFirst(RegExp(r'^[a-z]{2}_', caseSensitive: false), ''); } final titled = s .split('_') .map((w) => w.isNotEmpty ? '${w[0].toUpperCase()}${w.substring(1).toLowerCase()}' : '') .join(' '); if (!isCity && titled.length == 2) return titled.toUpperCase(); return titled; } if (fv.jsonValue is List && (fv.jsonValue as List).isNotEmpty) { final val = (fv.jsonValue as List).first; if (val is String && val.isNotEmpty) return format(val); } if (fv.textValue != null && fv.textValue!.isNotEmpty) { return format(fv.textValue!); } return null; } /// Get experience from field values String get experienceText { for (final fv in fieldValues) { if (fv.fieldSlug.contains('experience') || fv.fieldName.toLowerCase().contains('experience')) { if (fv.textValue != null) return fv.textValue!; if (fv.numberValue != null) { return '${fv.numberValue!.toInt()}+ years in real estate.'; } } } return ''; } /// Get specializations / expertise from field values. /// Strictly reads about_me_expertise only (matches web + admin filter source). List get specializations { final tags = []; for (final fv in fieldValues) { if (fv.fieldSlug.toLowerCase() != 'about_me_expertise') continue; if (fv.jsonValue is List) { for (final v in (fv.jsonValue as List)) { final s = v.toString().trim(); if (s.isEmpty) continue; final display = s .split('_') .map((w) => w.isNotEmpty ? '${w[0].toUpperCase()}${w.substring(1).toLowerCase()}' : '') .join(' '); if (!tags.contains(display)) tags.add(display); } } else if (fv.textValue != null && fv.textValue!.trim().isNotEmpty) { final display = fv.textValue!.trim(); if (!tags.contains(display)) tags.add(display); } } return tags; } AgentProfile copyWith({bool? isAvailable}) { return AgentProfile( id: id, userId: userId, firstName: firstName, lastName: lastName, headline: headline, bio: bio, avatar: avatar, companyName: companyName, phone: phone, website: website, city: city, state: state, country: country, averageRating: averageRating, totalReviews: totalReviews, isVerified: isVerified, verificationStatus: verificationStatus, verificationNote: verificationNote, isFeatured: isFeatured, isActive: isActive, isAvailable: isAvailable ?? this.isAvailable, email: email, agentType: agentType, user: user, fieldValues: fieldValues, createdAt: createdAt, legacySpecializations: legacySpecializations, languages: languages, serviceAreas: serviceAreas, ); } factory AgentProfile.fromJson(Map json) { return AgentProfile( id: json['id'] as String, userId: json['userId'] as String?, firstName: json['firstName'] as String? ?? '', lastName: json['lastName'] as String? ?? '', headline: json['headline'] as String?, bio: json['bio'] as String?, avatar: json['avatar'] as String?, companyName: json['companyName'] as String?, phone: json['phone'] as String?, website: json['website'] as String?, city: json['city'] as String?, state: json['state'] as String?, country: json['country'] as String?, averageRating: (json['averageRating'] as num?)?.toDouble(), totalReviews: json['totalReviews'] as int? ?? 0, isVerified: json['isVerified'] as bool? ?? false, verificationStatus: json['verificationStatus'] as String?, verificationNote: json['verificationNote'] as String?, isFeatured: json['isFeatured'] as bool? ?? false, isActive: json['isActive'] as bool? ?? true, isAvailable: json['isAvailable'] as bool? ?? false, email: json['email'] as String?, agentType: json['agentType'] != null ? AgentType.fromJson(json['agentType'] as Map) : null, user: json['user'] != null ? AgentUser.fromJson(json['user'] as Map) : null, fieldValues: (json['fieldValues'] as List?) ?.map((e) => AgentFieldValue.fromJson(e as Map)) .toList() ?? const [], createdAt: json['createdAt'] as String?, legacySpecializations: (json['specializations'] as List?) ?.map((e) => e.toString()) .toList() ?? const [], languages: (json['languages'] as List?) ?.map((e) => e.toString()) .toList() ?? const [], serviceAreas: (json['serviceAreas'] as List?) ?.map((e) => e.toString()) .toList() ?? const [], matchPercentage: json['matchPercentage'] as int?, ); } } class AgentUser { final String id; final String email; final String? avatar; const AgentUser({ required this.id, required this.email, this.avatar, }); factory AgentUser.fromJson(Map json) { return AgentUser( id: json['id'] as String, email: json['email'] as String? ?? '', avatar: json['avatar'] as String?, ); } } class AgentFieldValue { final String id; final String fieldSlug; final String fieldName; final String fieldType; final String? sectionSlug; final String? sectionName; final String? textValue; final num? numberValue; final bool? booleanValue; final dynamic jsonValue; final String? dateValue; const AgentFieldValue({ required this.id, required this.fieldSlug, required this.fieldName, required this.fieldType, this.sectionSlug, this.sectionName, this.textValue, this.numberValue, this.booleanValue, this.jsonValue, this.dateValue, }); /// Parse from the /agents/{id}/field-values endpoint format /// which has { fieldSlug, fieldName, fieldType, sectionSlug, value } factory AgentFieldValue.fromFieldValueResponse(Map json) { final value = json['value']; String? textValue; num? numberValue; bool? booleanValue; dynamic jsonValue; if (value is String) { textValue = value; } else if (value is num) { numberValue = value; } else if (value is bool) { booleanValue = value; } else if (value is List || value is Map) { jsonValue = value; } return AgentFieldValue( id: '', fieldSlug: json['fieldSlug'] as String? ?? '', fieldName: json['fieldName'] as String? ?? '', fieldType: json['fieldType'] as String? ?? '', sectionSlug: json['sectionSlug'] as String?, sectionName: json['sectionName'] as String?, textValue: textValue, numberValue: numberValue, booleanValue: booleanValue, jsonValue: jsonValue, ); } factory AgentFieldValue.fromJson(Map json) { // API returns field info nested: { field: { slug, name, fieldType } } // Fallback to top-level fieldSlug/fieldName/fieldType for compatibility final field = json['field'] as Map?; return AgentFieldValue( id: json['id'] as String? ?? '', fieldSlug: field?['slug'] as String? ?? json['fieldSlug'] as String? ?? '', fieldName: field?['name'] as String? ?? json['fieldName'] as String? ?? '', fieldType: field?['fieldType'] as String? ?? json['fieldType'] as String? ?? '', textValue: json['textValue'] as String?, numberValue: json['numberValue'] as num?, booleanValue: json['booleanValue'] as bool?, jsonValue: json['jsonValue'], dateValue: json['dateValue'] as String?, ); } }