export type TimelineItem = { itemId: string; occurredAt: string; eventName: string; productId: string; title: string; summary?: string | null; artifactRefs: string[]; relatedEventIds: string[]; actorType: 'user' | 'agent' | 'system' | 'device'; visibility: 'private' | 'org' | 'shared' | 'local-only'; correlationId?: string | null; }; export type TimelineGroup = { groupId: string; correlationId: string | null; items: TimelineItem[]; productIds: string[]; latestOccurredAt: string; }; export const TIMELINE_PRODUCT_OPTIONS = [ { value: 'current', label: 'Current Admin Product' }, { value: 'lysnrai', label: 'LysnrAI' }, { value: 'notelett', label: 'NoteLett' }, { value: 'mindlyst', label: 'MindLyst' }, { value: 'flowmonk', label: 'FlowMonk' }, { value: 'chronomind', label: 'ChronoMind' }, { value: 'efforise', label: 'EffoRise' }, { value: 'actiontrail', label: 'ActionTrail' }, { value: 'cowork', label: 'Cowork' }, ] as const; function compareDescendingIso(a: string, b: string) { return new Date(b).getTime() - new Date(a).getTime(); } export function getTimelineStats(items: TimelineItem[]) { return { uniqueProducts: new Set(items.map(item => item.productId)).size, uniqueCorrelations: new Set( items .map(item => item.correlationId) .filter((value): value is string => typeof value === 'string' && value.length > 0) ).size, withArtifacts: items.filter(item => item.artifactRefs.length > 0).length, groupedChains: buildTimelineGroups(items).filter(group => group.items.length > 1).length, }; } export function buildTimelineGroups(items: TimelineItem[]): TimelineGroup[] { const groups = new Map(); for (const item of items) { const groupKey = item.correlationId?.trim() ? `corr:${item.correlationId}` : `item:${item.itemId}`; const bucket = groups.get(groupKey); if (bucket) { bucket.push(item); } else { groups.set(groupKey, [item]); } } return [...groups.entries()] .map(([groupId, groupItems]) => { const itemsSorted = [...groupItems].sort((left, right) => compareDescendingIso(left.occurredAt, right.occurredAt) ); return { groupId, correlationId: itemsSorted[0]?.correlationId?.trim() || null, items: itemsSorted, productIds: [...new Set(itemsSorted.map(item => item.productId))], latestOccurredAt: itemsSorted[0]?.occurredAt ?? new Date(0).toISOString(), }; }) .sort((left, right) => compareDescendingIso(left.latestOccurredAt, right.latestOccurredAt)); } export function getProductLabel(productId: string) { return TIMELINE_PRODUCT_OPTIONS.find(option => option.value === productId)?.label ?? productId; }