// ignore_for_file: sort_constructors_first import 'package:flutter/foundation.dart'; import 'package:relationship_saver/features/share_intake/domain/share_models.dart'; /// Polarity for inferred or confirmed preference signals. enum PreferenceSignalPolarity { like, dislike, neutral } /// Lifecycle state for machine-assisted preference signals. enum PreferenceSignalStatus { inferred, confirmed, dismissed } /// Primary profile object used across the relationship app. @immutable class PersonProfile { const PersonProfile({ required this.id, required this.name, required this.relationship, required this.affinityScore, required this.nextMoment, required this.tags, required this.notes, this.aliases = const [], this.location, this.lastUpdatedAt, this.lastInteractedAt, }); final String id; final String name; final String relationship; final int affinityScore; final DateTime nextMoment; final List tags; final String notes; final List aliases; final String? location; final DateTime? lastUpdatedAt; final DateTime? lastInteractedAt; PersonProfile copyWith({ String? id, String? name, String? relationship, int? affinityScore, DateTime? nextMoment, List? tags, String? notes, List? aliases, String? location, DateTime? lastUpdatedAt, DateTime? lastInteractedAt, }) { return PersonProfile( id: id ?? this.id, name: name ?? this.name, relationship: relationship ?? this.relationship, affinityScore: affinityScore ?? this.affinityScore, nextMoment: nextMoment ?? this.nextMoment, tags: tags ?? this.tags, notes: notes ?? this.notes, aliases: aliases ?? this.aliases, location: location ?? this.location, lastUpdatedAt: lastUpdatedAt ?? this.lastUpdatedAt, lastInteractedAt: lastInteractedAt ?? this.lastInteractedAt, ); } Map toJson() { return { 'id': id, 'name': name, 'relationship': relationship, 'affinityScore': affinityScore, 'nextMoment': nextMoment.toUtc().toIso8601String(), 'tags': tags, 'notes': notes, 'aliases': aliases, 'location': location, 'lastUpdatedAt': lastUpdatedAt?.toUtc().toIso8601String(), 'lastInteractedAt': lastInteractedAt?.toUtc().toIso8601String(), }; } factory PersonProfile.fromJson(Map json) { return PersonProfile( id: json['id'] as String, name: json['name'] as String, relationship: json['relationship'] as String, affinityScore: (json['affinityScore'] as num).toInt(), nextMoment: DateTime.parse(json['nextMoment'] as String).toLocal(), tags: (json['tags'] as List? ?? []) .map((dynamic tag) => '$tag') .toList(growable: false), notes: json['notes'] as String? ?? '', aliases: (json['aliases'] as List? ?? []) .map((dynamic value) => '$value') .toList(growable: false), location: json['location'] as String?, lastUpdatedAt: json['lastUpdatedAt'] == null ? null : DateTime.parse(json['lastUpdatedAt'] as String).toLocal(), lastInteractedAt: json['lastInteractedAt'] == null ? null : DateTime.parse(json['lastInteractedAt'] as String).toLocal(), ); } } /// Stable mapping between an external sender/thread identity and a person. @immutable class SourceProfileLink { const SourceProfileLink({ required this.id, required this.sourceApp, required this.normalizedDisplayName, required this.profileId, required this.firstSeenAt, required this.lastSeenAt, this.sourceUserId, this.sourceThreadId, this.sourceFingerprint, }); final String id; final String sourceApp; final String? sourceUserId; final String? sourceThreadId; final String? sourceFingerprint; final String normalizedDisplayName; final String profileId; final DateTime firstSeenAt; final DateTime lastSeenAt; SourceProfileLink copyWith({ String? id, String? sourceApp, String? sourceUserId, String? sourceThreadId, String? sourceFingerprint, String? normalizedDisplayName, String? profileId, DateTime? firstSeenAt, DateTime? lastSeenAt, }) { return SourceProfileLink( id: id ?? this.id, sourceApp: sourceApp ?? this.sourceApp, sourceUserId: sourceUserId ?? this.sourceUserId, sourceThreadId: sourceThreadId ?? this.sourceThreadId, sourceFingerprint: sourceFingerprint ?? this.sourceFingerprint, normalizedDisplayName: normalizedDisplayName ?? this.normalizedDisplayName, profileId: profileId ?? this.profileId, firstSeenAt: firstSeenAt ?? this.firstSeenAt, lastSeenAt: lastSeenAt ?? this.lastSeenAt, ); } Map toJson() { return { 'id': id, 'sourceApp': sourceApp, 'sourceUserId': sourceUserId, 'sourceThreadId': sourceThreadId, 'sourceFingerprint': sourceFingerprint, 'normalizedDisplayName': normalizedDisplayName, 'profileId': profileId, 'firstSeenAt': firstSeenAt.toUtc().toIso8601String(), 'lastSeenAt': lastSeenAt.toUtc().toIso8601String(), }; } factory SourceProfileLink.fromJson(Map json) { return SourceProfileLink( id: json['id'] as String, sourceApp: json['sourceApp'] as String, sourceUserId: json['sourceUserId'] as String?, sourceThreadId: json['sourceThreadId'] as String?, sourceFingerprint: json['sourceFingerprint'] as String?, normalizedDisplayName: json['normalizedDisplayName'] as String? ?? '', profileId: json['profileId'] as String, firstSeenAt: DateTime.parse(json['firstSeenAt'] as String).toLocal(), lastSeenAt: DateTime.parse(json['lastSeenAt'] as String).toLocal(), ); } } /// Structured fact captured about a person. @immutable class PersonFact { const PersonFact({ required this.id, required this.personId, required this.type, required this.text, required this.sourceKind, required this.createdAt, required this.updatedAt, this.label, this.sourceApp, this.sourceUrl, this.sharedMessageId, this.inboxEntryId, this.confidence, this.needsReview = false, this.isSensitive = false, }); final String id; final String personId; final CapturedFactType type; final String text; final String? label; final CaptureSourceKind sourceKind; final String? sourceApp; final String? sourceUrl; final String? sharedMessageId; final String? inboxEntryId; final double? confidence; final bool needsReview; final bool isSensitive; final DateTime createdAt; final DateTime updatedAt; PersonFact copyWith({ String? id, String? personId, CapturedFactType? type, String? text, String? label, CaptureSourceKind? sourceKind, String? sourceApp, String? sourceUrl, String? sharedMessageId, String? inboxEntryId, double? confidence, bool? needsReview, bool? isSensitive, DateTime? createdAt, DateTime? updatedAt, }) { return PersonFact( id: id ?? this.id, personId: personId ?? this.personId, type: type ?? this.type, text: text ?? this.text, label: label ?? this.label, sourceKind: sourceKind ?? this.sourceKind, sourceApp: sourceApp ?? this.sourceApp, sourceUrl: sourceUrl ?? this.sourceUrl, sharedMessageId: sharedMessageId ?? this.sharedMessageId, inboxEntryId: inboxEntryId ?? this.inboxEntryId, confidence: confidence ?? this.confidence, needsReview: needsReview ?? this.needsReview, isSensitive: isSensitive ?? this.isSensitive, createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt, ); } Map toJson() { return { 'id': id, 'personId': personId, 'type': type.name, 'text': text, 'label': label, 'sourceKind': sourceKind.name, 'sourceApp': sourceApp, 'sourceUrl': sourceUrl, 'sharedMessageId': sharedMessageId, 'inboxEntryId': inboxEntryId, 'confidence': confidence, 'needsReview': needsReview, 'isSensitive': isSensitive, 'createdAt': createdAt.toUtc().toIso8601String(), 'updatedAt': updatedAt.toUtc().toIso8601String(), }; } factory PersonFact.fromJson(Map json) { final String typeName = json['type'] as String? ?? CapturedFactType.note.name; final String sourceKindName = json['sourceKind'] as String? ?? CaptureSourceKind.manual.name; return PersonFact( id: json['id'] as String, personId: json['personId'] as String, type: CapturedFactType.values.firstWhere( (CapturedFactType value) => value.name == typeName, orElse: () => CapturedFactType.note, ), text: json['text'] as String? ?? '', label: json['label'] as String?, sourceKind: CaptureSourceKind.values.firstWhere( (CaptureSourceKind value) => value.name == sourceKindName, orElse: () => CaptureSourceKind.manual, ), sourceApp: json['sourceApp'] as String?, sourceUrl: json['sourceUrl'] as String?, sharedMessageId: json['sharedMessageId'] as String?, inboxEntryId: json['inboxEntryId'] as String?, confidence: (json['confidence'] as num?)?.toDouble(), needsReview: json['needsReview'] as bool? ?? false, isSensitive: json['isSensitive'] as bool? ?? false, createdAt: DateTime.parse(json['createdAt'] as String).toLocal(), updatedAt: DateTime.parse(json['updatedAt'] as String).toLocal(), ); } } /// Important date owned by a person profile. @immutable class PersonImportantDate { const PersonImportantDate({ required this.id, required this.personId, required this.label, required this.date, required this.classification, required this.sourceKind, required this.createdAt, required this.updatedAt, this.sourceApp, this.inboxEntryId, this.isSensitive = false, }); final String id; final String personId; final String label; final DateTime date; final String classification; final CaptureSourceKind sourceKind; final String? sourceApp; final String? inboxEntryId; final bool isSensitive; final DateTime createdAt; final DateTime updatedAt; PersonImportantDate copyWith({ String? id, String? personId, String? label, DateTime? date, String? classification, CaptureSourceKind? sourceKind, String? sourceApp, String? inboxEntryId, bool? isSensitive, DateTime? createdAt, DateTime? updatedAt, }) { return PersonImportantDate( id: id ?? this.id, personId: personId ?? this.personId, label: label ?? this.label, date: date ?? this.date, classification: classification ?? this.classification, sourceKind: sourceKind ?? this.sourceKind, sourceApp: sourceApp ?? this.sourceApp, inboxEntryId: inboxEntryId ?? this.inboxEntryId, isSensitive: isSensitive ?? this.isSensitive, createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt, ); } Map toJson() { return { 'id': id, 'personId': personId, 'label': label, 'date': date.toUtc().toIso8601String(), 'classification': classification, 'sourceKind': sourceKind.name, 'sourceApp': sourceApp, 'inboxEntryId': inboxEntryId, 'isSensitive': isSensitive, 'createdAt': createdAt.toUtc().toIso8601String(), 'updatedAt': updatedAt.toUtc().toIso8601String(), }; } factory PersonImportantDate.fromJson(Map json) { final String sourceKindName = json['sourceKind'] as String? ?? CaptureSourceKind.manual.name; return PersonImportantDate( id: json['id'] as String, personId: json['personId'] as String, label: json['label'] as String? ?? '', date: DateTime.parse(json['date'] as String).toLocal(), classification: json['classification'] as String? ?? 'important', sourceKind: CaptureSourceKind.values.firstWhere( (CaptureSourceKind value) => value.name == sourceKindName, orElse: () => CaptureSourceKind.manual, ), sourceApp: json['sourceApp'] as String?, inboxEntryId: json['inboxEntryId'] as String?, isSensitive: json['isSensitive'] as bool? ?? false, createdAt: DateTime.parse(json['createdAt'] as String).toLocal(), updatedAt: DateTime.parse(json['updatedAt'] as String).toLocal(), ); } } /// Inferred preference signal that can later drive recommendations or prompts. @immutable class PersonPreferenceSignal { const PersonPreferenceSignal({ required this.id, required this.personId, required this.key, required this.label, required this.category, required this.polarity, required this.confidence, required this.status, required this.firstSeenAt, required this.lastSeenAt, required this.occurrenceCount, this.sourceApps = const [], this.evidenceMessageIds = const [], this.evidenceSnippets = const [], }); final String id; final String personId; final String key; final String label; final String category; final PreferenceSignalPolarity polarity; final double confidence; final PreferenceSignalStatus status; final DateTime firstSeenAt; final DateTime lastSeenAt; final int occurrenceCount; final List sourceApps; final List evidenceMessageIds; final List evidenceSnippets; PersonPreferenceSignal copyWith({ String? id, String? personId, String? key, String? label, String? category, PreferenceSignalPolarity? polarity, double? confidence, PreferenceSignalStatus? status, DateTime? firstSeenAt, DateTime? lastSeenAt, int? occurrenceCount, List? sourceApps, List? evidenceMessageIds, List? evidenceSnippets, }) { return PersonPreferenceSignal( id: id ?? this.id, personId: personId ?? this.personId, key: key ?? this.key, label: label ?? this.label, category: category ?? this.category, polarity: polarity ?? this.polarity, confidence: confidence ?? this.confidence, status: status ?? this.status, firstSeenAt: firstSeenAt ?? this.firstSeenAt, lastSeenAt: lastSeenAt ?? this.lastSeenAt, occurrenceCount: occurrenceCount ?? this.occurrenceCount, sourceApps: sourceApps ?? this.sourceApps, evidenceMessageIds: evidenceMessageIds ?? this.evidenceMessageIds, evidenceSnippets: evidenceSnippets ?? this.evidenceSnippets, ); } Map toJson() { return { 'id': id, 'personId': personId, 'key': key, 'label': label, 'category': category, 'polarity': polarity.name, 'confidence': confidence, 'status': status.name, 'firstSeenAt': firstSeenAt.toUtc().toIso8601String(), 'lastSeenAt': lastSeenAt.toUtc().toIso8601String(), 'occurrenceCount': occurrenceCount, 'sourceApps': sourceApps, 'evidenceMessageIds': evidenceMessageIds, 'evidenceSnippets': evidenceSnippets, }; } factory PersonPreferenceSignal.fromJson(Map json) { final String polarityName = json['polarity'] as String? ?? PreferenceSignalPolarity.neutral.name; final String statusName = json['status'] as String? ?? PreferenceSignalStatus.inferred.name; return PersonPreferenceSignal( id: json['id'] as String, personId: json['personId'] as String, key: json['key'] as String, label: json['label'] as String? ?? '', category: json['category'] as String? ?? 'general', polarity: PreferenceSignalPolarity.values.firstWhere( (PreferenceSignalPolarity item) => item.name == polarityName, orElse: () => PreferenceSignalPolarity.neutral, ), confidence: (json['confidence'] as num?)?.toDouble() ?? 0, status: PreferenceSignalStatus.values.firstWhere( (PreferenceSignalStatus item) => item.name == statusName, orElse: () => PreferenceSignalStatus.inferred, ), firstSeenAt: DateTime.parse( json['firstSeenAt'] as String? ?? DateTime.now().toUtc().toIso8601String(), ).toLocal(), lastSeenAt: DateTime.parse( json['lastSeenAt'] as String? ?? DateTime.now().toUtc().toIso8601String(), ).toLocal(), occurrenceCount: (json['occurrenceCount'] as num?)?.toInt() ?? 1, sourceApps: (json['sourceApps'] as List? ?? []) .map((dynamic item) => '$item') .toList(growable: false), evidenceMessageIds: (json['evidenceMessageIds'] as List? ?? []) .map((dynamic item) => '$item') .toList(growable: false), evidenceSnippets: (json['evidenceSnippets'] as List? ?? []) .map((dynamic item) => '$item') .toList(growable: false), ); } }