main
dart 154 lines 4.54 KB
Raw
1 import '../data/models/message.dart';
2
3 class MessageVariants {
4 static String groupId(Message message) =>
5 message.variantGroupId?.isNotEmpty == true
6 ? message.variantGroupId!
7 : message.id;
8
9 static List<Message> resolveActiveTimeline(List<Message> allMessages) {
10 if (allMessages.isEmpty) return [];
11
12 final normalized = allMessages.map(_normalizeLegacy).toList();
13 final withParents = _ensureParentLinks(normalized);
14
15 final childrenOf = <String, List<Message>>{};
16 final roots = <Message>[];
17 for (final message in withParents) {
18 final parentId = message.parentMessageId;
19 if (parentId == null || parentId.isEmpty) {
20 roots.add(message);
21 } else {
22 childrenOf.putIfAbsent(parentId, () => []).add(message);
23 }
24 }
25
26 if (roots.isEmpty) {
27 return _legacyFlatResolve(normalized);
28 }
29
30 roots.sort((a, b) {
31 final order = a.threadOrder.compareTo(b.threadOrder);
32 if (order != 0) return order;
33 return a.createdAt.compareTo(b.createdAt);
34 });
35
36 final timeline = <Message>[];
37 var current = _pickActiveSibling(roots);
38 while (true) {
39 timeline.add(current);
40 final children = childrenOf[current.id];
41 if (children == null || children.isEmpty) break;
42 current = _pickActiveSibling(children);
43 }
44 return timeline;
45 }
46
47 static List<Message> _legacyFlatResolve(List<Message> normalized) {
48 final byGroup = <String, List<Message>>{};
49 for (final message in normalized) {
50 byGroup.putIfAbsent(groupId(message), () => []).add(message);
51 }
52
53 final active = <Message>[];
54 for (final group in byGroup.values) {
55 group.sort((a, b) => a.variantIndex.compareTo(b.variantIndex));
56 final selected = group.firstWhere(
57 (m) => m.isActiveVariant,
58 orElse: () => group.last,
59 );
60 active.add(selected);
61 }
62
63 active.sort((a, b) {
64 final orderCompare = a.threadOrder.compareTo(b.threadOrder);
65 if (orderCompare != 0) return orderCompare;
66 return a.createdAt.compareTo(b.createdAt);
67 });
68 return active;
69 }
70
71 static List<Message> _ensureParentLinks(List<Message> messages) {
72 if (messages.any((m) => m.parentMessageId?.isNotEmpty == true)) {
73 return messages;
74 }
75
76 final byThreadOrder = <int, List<Message>>{};
77 for (final message in messages) {
78 byThreadOrder.putIfAbsent(message.threadOrder, () => []).add(message);
79 }
80 final orders = byThreadOrder.keys.toList()..sort();
81
82 String? previousActiveId;
83 final updated = <Message>[];
84 for (final order in orders) {
85 final group = byThreadOrder[order]!;
86 for (final message in group) {
87 updated.add(message.copyWith(parentMessageId: previousActiveId));
88 }
89 final active = group.firstWhere(
90 (m) => m.isActiveVariant,
91 orElse: () => group.last,
92 );
93 previousActiveId = active.id;
94 }
95 return updated;
96 }
97
98 static Message _pickActiveSibling(List<Message> siblings) {
99 final byGroup = <String, List<Message>>{};
100 for (final message in siblings) {
101 byGroup.putIfAbsent(groupId(message), () => []).add(message);
102 }
103
104 if (byGroup.length == 1) {
105 final group = byGroup.values.first;
106 group.sort((a, b) => a.variantIndex.compareTo(b.variantIndex));
107 return group.firstWhere(
108 (m) => m.isActiveVariant,
109 orElse: () => group.last,
110 );
111 }
112
113 siblings.sort((a, b) {
114 final order = a.threadOrder.compareTo(b.threadOrder);
115 if (order != 0) return order;
116 return a.createdAt.compareTo(b.createdAt);
117 });
118 return siblings.firstWhere(
119 (m) => m.isActiveVariant,
120 orElse: () => siblings.first,
121 );
122 }
123
124 static List<Message> variantsForMessage(
125 List<Message> allMessages,
126 Message message,
127 ) {
128 final gid = groupId(message);
129 return allMessages.where((m) => groupId(m) == gid).toList()
130 ..sort((a, b) => a.variantIndex.compareTo(b.variantIndex));
131 }
132
133 static int activeVariantIndex(List<Message> variants) {
134 final index = variants.indexWhere((m) => m.isActiveVariant);
135 return index >= 0 ? index : variants.length - 1;
136 }
137
138 static Message _normalizeLegacy(Message message) {
139 if (message.variantGroupId?.isNotEmpty == true) return message;
140 return message.copyWith(
141 variantGroupId: message.id,
142 variantIndex: 0,
143 isActiveVariant: true,
144 );
145 }
146
147 static int nextThreadOrder(List<Message> activeTimeline) {
148 if (activeTimeline.isEmpty) return 0;
149 return activeTimeline
150 .map((m) => m.threadOrder)
151 .reduce((a, b) => a > b ? a : b) +
152 1;
153 }
154 }