// ignore_for_file: sort_constructors_first import 'package:flutter/foundation.dart'; /// Supported raw payload shapes entering the app from the share pipeline. enum SharedPayloadType { text, url, textWithUrl } /// User-facing assignment outcome for a shared payload. enum ShareAssignmentStatus { needsPersonSelection, assignedDirectly, savedToInbox, discarded, } /// Structured capture types that shared or manual content can resolve into. enum CapturedFactType { note, like, dislike, importantDate, giftIdea, placeIdea, activityIdea, misc, } /// Tracks how a fact or date entered local storage. enum CaptureSourceKind { manual, shareSheet, shareInbox } /// Normalized input payload shared from another app. @immutable class SharedPayload { const SharedPayload({ required this.sourceApp, required this.payloadType, required this.rawText, required this.createdAt, required this.receivedAt, required this.platform, this.sharedMessageDateTime, this.url, this.sourceDisplayName, this.sourceUserId, this.sourceThreadId, this.parsingHints = const [], this.metadata = const {}, this.dedupeKey, }); final String sourceApp; final SharedPayloadType payloadType; final String rawText; final String? url; final DateTime createdAt; final DateTime receivedAt; final DateTime? sharedMessageDateTime; final String platform; final String? sourceDisplayName; final String? sourceUserId; final String? sourceThreadId; final List parsingHints; final Map metadata; final String? dedupeKey; SharedPayload copyWith({ String? sourceApp, SharedPayloadType? payloadType, String? rawText, String? url, DateTime? createdAt, DateTime? receivedAt, DateTime? sharedMessageDateTime, String? platform, String? sourceDisplayName, String? sourceUserId, String? sourceThreadId, List? parsingHints, Map? metadata, String? dedupeKey, }) { return SharedPayload( sourceApp: sourceApp ?? this.sourceApp, payloadType: payloadType ?? this.payloadType, rawText: rawText ?? this.rawText, url: url ?? this.url, createdAt: createdAt ?? this.createdAt, receivedAt: receivedAt ?? this.receivedAt, sharedMessageDateTime: sharedMessageDateTime ?? this.sharedMessageDateTime, platform: platform ?? this.platform, sourceDisplayName: sourceDisplayName ?? this.sourceDisplayName, sourceUserId: sourceUserId ?? this.sourceUserId, sourceThreadId: sourceThreadId ?? this.sourceThreadId, parsingHints: parsingHints ?? this.parsingHints, metadata: metadata ?? this.metadata, dedupeKey: dedupeKey ?? this.dedupeKey, ); } Map toJson() { return { 'sourceApp': sourceApp, 'payloadType': payloadType.name, 'rawText': rawText, 'url': url, 'createdAt': createdAt.toUtc().toIso8601String(), 'receivedAt': receivedAt.toUtc().toIso8601String(), 'sharedMessageDateTime': sharedMessageDateTime?.toUtc().toIso8601String(), 'platform': platform, 'sourceDisplayName': sourceDisplayName, 'sourceUserId': sourceUserId, 'sourceThreadId': sourceThreadId, 'parsingHints': parsingHints, 'metadata': metadata, 'dedupeKey': dedupeKey, }; } factory SharedPayload.fromJson(Map json) { final String payloadTypeName = json['payloadType'] as String? ?? SharedPayloadType.text.name; return SharedPayload( sourceApp: json['sourceApp'] as String? ?? 'share_sheet', payloadType: SharedPayloadType.values.firstWhere( (SharedPayloadType value) => value.name == payloadTypeName, orElse: () => SharedPayloadType.text, ), rawText: json['rawText'] as String? ?? '', url: json['url'] as String?, createdAt: DateTime.parse( json['createdAt'] as String? ?? DateTime.now().toUtc().toIso8601String(), ).toLocal(), receivedAt: DateTime.parse( json['receivedAt'] as String? ?? DateTime.now().toUtc().toIso8601String(), ).toLocal(), sharedMessageDateTime: json['sharedMessageDateTime'] == null ? null : DateTime.parse(json['sharedMessageDateTime'] as String).toLocal(), platform: json['platform'] as String? ?? 'unknown', sourceDisplayName: json['sourceDisplayName'] as String?, sourceUserId: json['sourceUserId'] as String?, sourceThreadId: json['sourceThreadId'] as String?, parsingHints: (json['parsingHints'] as List? ?? []) .map((dynamic item) => '$item') .toList(growable: false), metadata: (json['metadata'] as Map? ?? {}) .map( (dynamic key, dynamic value) => MapEntry('$key', '$value'), ), dedupeKey: json['dedupeKey'] as String?, ); } } /// Draft extracted from a shared payload before it becomes a persistent fact. @immutable class CapturedFactDraft { const CapturedFactDraft({ required this.type, required this.text, this.label, this.dateValue, this.confidence, this.isSensitive = false, this.needsReview = false, }); final CapturedFactType type; final String text; final String? label; final DateTime? dateValue; final double? confidence; final bool isSensitive; final bool needsReview; CapturedFactDraft copyWith({ CapturedFactType? type, String? text, String? label, DateTime? dateValue, double? confidence, bool? isSensitive, bool? needsReview, }) { return CapturedFactDraft( type: type ?? this.type, text: text ?? this.text, label: label ?? this.label, dateValue: dateValue ?? this.dateValue, confidence: confidence ?? this.confidence, isSensitive: isSensitive ?? this.isSensitive, needsReview: needsReview ?? this.needsReview, ); } Map toJson() { return { 'type': type.name, 'text': text, 'label': label, 'dateValue': dateValue?.toUtc().toIso8601String(), 'confidence': confidence, 'isSensitive': isSensitive, 'needsReview': needsReview, }; } factory CapturedFactDraft.fromJson(Map json) { final String typeName = json['type'] as String? ?? CapturedFactType.note.name; return CapturedFactDraft( type: CapturedFactType.values.firstWhere( (CapturedFactType value) => value.name == typeName, orElse: () => CapturedFactType.note, ), text: json['text'] as String? ?? '', label: json['label'] as String?, dateValue: json['dateValue'] == null ? null : DateTime.parse(json['dateValue'] as String).toLocal(), confidence: (json['confidence'] as num?)?.toDouble(), isSensitive: json['isSensitive'] as bool? ?? false, needsReview: json['needsReview'] as bool? ?? false, ); } } /// Durable record of a share that was already attached to a person. @immutable class SharedMessageEntry { const SharedMessageEntry({ required this.id, required this.profileId, required this.payload, required this.importedAt, required this.resolvedAutomatically, this.assignmentStatus = ShareAssignmentStatus.assignedDirectly, this.draft, }); final String id; final String profileId; final SharedPayload payload; final DateTime importedAt; final bool resolvedAutomatically; final ShareAssignmentStatus assignmentStatus; final CapturedFactDraft? draft; String get sourceApp => payload.sourceApp; String get messageText => payload.rawText; DateTime get sharedAt => payload.sharedMessageDateTime ?? payload.createdAt; DateTime? get sharedMessageDateTime => payload.sharedMessageDateTime; SharedPayloadType get payloadType => payload.payloadType; String? get url => payload.url; String? get sourceDisplayName => payload.sourceDisplayName; String? get sourceUserId => payload.sourceUserId; String? get sourceThreadId => payload.sourceThreadId; SharedMessageEntry copyWith({ String? id, String? profileId, SharedPayload? payload, DateTime? importedAt, bool? resolvedAutomatically, ShareAssignmentStatus? assignmentStatus, CapturedFactDraft? draft, }) { return SharedMessageEntry( id: id ?? this.id, profileId: profileId ?? this.profileId, payload: payload ?? this.payload, importedAt: importedAt ?? this.importedAt, resolvedAutomatically: resolvedAutomatically ?? this.resolvedAutomatically, assignmentStatus: assignmentStatus ?? this.assignmentStatus, draft: draft ?? this.draft, ); } Map toJson() { return { 'id': id, 'profileId': profileId, 'payload': payload.toJson(), 'importedAt': importedAt.toUtc().toIso8601String(), 'resolvedAutomatically': resolvedAutomatically, 'assignmentStatus': assignmentStatus.name, 'draft': draft?.toJson(), }; } factory SharedMessageEntry.fromJson(Map json) { final String assignmentStatusName = json['assignmentStatus'] as String? ?? ShareAssignmentStatus.assignedDirectly.name; final SharedPayload payload = json['payload'] is Map ? SharedPayload.fromJson(json['payload'] as Map) : SharedPayload( sourceApp: json['sourceApp'] as String? ?? 'share_sheet', payloadType: SharedPayloadType.text, rawText: json['messageText'] as String? ?? '', createdAt: DateTime.parse( json['sharedAt'] as String? ?? DateTime.now().toUtc().toIso8601String(), ).toLocal(), receivedAt: DateTime.parse( json['importedAt'] as String? ?? DateTime.now().toUtc().toIso8601String(), ).toLocal(), platform: 'legacy', sourceDisplayName: json['sourceDisplayName'] as String?, sourceUserId: json['sourceUserId'] as String?, sourceThreadId: json['sourceThreadId'] as String?, ); return SharedMessageEntry( id: json['id'] as String, profileId: json['profileId'] as String, payload: payload, importedAt: DateTime.parse(json['importedAt'] as String).toLocal(), resolvedAutomatically: json['resolvedAutomatically'] as bool? ?? true, assignmentStatus: ShareAssignmentStatus.values.firstWhere( (ShareAssignmentStatus value) => value.name == assignmentStatusName, orElse: () => ShareAssignmentStatus.assignedDirectly, ), draft: json['draft'] is Map ? CapturedFactDraft.fromJson(json['draft'] as Map) : null, ); } } /// Why the share flow parked a payload in the inbox instead of saving directly. enum SharedInboxReason { ambiguousProfileMatch, nearProfileConflict, missingIdentity, } /// Unresolved share payload waiting for manual triage. @immutable class SharedInboxEntry { const SharedInboxEntry({ required this.id, required this.payload, required this.reason, required this.candidateProfileIds, required this.normalizedDisplayName, this.assignmentStatus = ShareAssignmentStatus.needsPersonSelection, this.targetPersonId, this.sourceFingerprint, this.draft, }); final String id; final SharedPayload payload; final SharedInboxReason reason; final List candidateProfileIds; final String normalizedDisplayName; final ShareAssignmentStatus assignmentStatus; final String? targetPersonId; final String? sourceFingerprint; final CapturedFactDraft? draft; String get sourceApp => payload.sourceApp; String get messageText => payload.rawText; DateTime get sharedAt => payload.createdAt; DateTime get receivedAt => payload.receivedAt; SharedPayloadType get payloadType => payload.payloadType; String? get url => payload.url; String? get sourceDisplayName => payload.sourceDisplayName; String? get sourceUserId => payload.sourceUserId; String? get sourceThreadId => payload.sourceThreadId; SharedInboxEntry copyWith({ String? id, SharedPayload? payload, SharedInboxReason? reason, List? candidateProfileIds, String? normalizedDisplayName, ShareAssignmentStatus? assignmentStatus, String? targetPersonId, String? sourceFingerprint, CapturedFactDraft? draft, }) { return SharedInboxEntry( id: id ?? this.id, payload: payload ?? this.payload, reason: reason ?? this.reason, candidateProfileIds: candidateProfileIds ?? this.candidateProfileIds, normalizedDisplayName: normalizedDisplayName ?? this.normalizedDisplayName, assignmentStatus: assignmentStatus ?? this.assignmentStatus, targetPersonId: targetPersonId ?? this.targetPersonId, sourceFingerprint: sourceFingerprint ?? this.sourceFingerprint, draft: draft ?? this.draft, ); } Map toJson() { return { 'id': id, 'payload': payload.toJson(), 'reason': reason.name, 'candidateProfileIds': candidateProfileIds, 'normalizedDisplayName': normalizedDisplayName, 'assignmentStatus': assignmentStatus.name, 'targetPersonId': targetPersonId, 'sourceFingerprint': sourceFingerprint, 'draft': draft?.toJson(), }; } factory SharedInboxEntry.fromJson(Map json) { final String reasonName = json['reason'] as String? ?? SharedInboxReason.missingIdentity.name; final String assignmentStatusName = json['assignmentStatus'] as String? ?? ShareAssignmentStatus.needsPersonSelection.name; final SharedPayload payload = json['payload'] is Map ? SharedPayload.fromJson(json['payload'] as Map) : SharedPayload( sourceApp: json['sourceApp'] as String? ?? 'share_sheet', payloadType: SharedPayloadType.text, rawText: json['messageText'] as String? ?? '', createdAt: DateTime.parse( json['sharedAt'] as String? ?? DateTime.now().toUtc().toIso8601String(), ).toLocal(), receivedAt: DateTime.parse( json['receivedAt'] as String? ?? DateTime.now().toUtc().toIso8601String(), ).toLocal(), platform: 'legacy', sourceDisplayName: json['sourceDisplayName'] as String?, sourceUserId: json['sourceUserId'] as String?, sourceThreadId: json['sourceThreadId'] as String?, ); return SharedInboxEntry( id: json['id'] as String, payload: payload, reason: SharedInboxReason.values.firstWhere( (SharedInboxReason item) => item.name == reasonName, orElse: () => SharedInboxReason.missingIdentity, ), candidateProfileIds: (json['candidateProfileIds'] as List? ?? []) .map((dynamic id) => '$id') .toList(growable: false), normalizedDisplayName: json['normalizedDisplayName'] as String? ?? '', assignmentStatus: ShareAssignmentStatus.values.firstWhere( (ShareAssignmentStatus value) => value.name == assignmentStatusName, orElse: () => ShareAssignmentStatus.needsPersonSelection, ), targetPersonId: json['targetPersonId'] as String?, sourceFingerprint: json['sourceFingerprint'] as String?, draft: json['draft'] is Map ? CapturedFactDraft.fromJson(json['draft'] as Map) : null, ); } }