| 1 | import type { ArchivedSnapshotEntry } from "@t3tools/client-runtime/state/threads"; |
| 2 | import { |
| 3 | scopeProject, |
| 4 | scopeThreadShell, |
| 5 | type EnvironmentProject, |
| 6 | type EnvironmentThreadShell, |
| 7 | } from "@t3tools/client-runtime/state/shell"; |
| 8 | import type { EnvironmentId } from "@t3tools/contracts"; |
| 9 | import * as Arr from "effect/Array"; |
| 10 | import * as Order from "effect/Order"; |
| 11 | |
| 12 | import { scopedProjectKey } from "../../lib/scopedEntities"; |
| 13 | |
| 14 | export type ArchivedThreadSortOrder = "newest" | "oldest"; |
| 15 | |
| 16 | export interface ArchivedThreadGroup { |
| 17 | readonly key: string; |
| 18 | readonly project: EnvironmentProject; |
| 19 | readonly threads: ReadonlyArray<EnvironmentThreadShell>; |
| 20 | } |
| 21 | |
| 22 | function archiveTimestamp(thread: EnvironmentThreadShell): number { |
| 23 | const timestamp = Date.parse(thread.archivedAt ?? thread.updatedAt); |
| 24 | return Number.isNaN(timestamp) ? 0 : timestamp; |
| 25 | } |
| 26 | |
| 27 | function matchesQuery(value: string | null, query: string): boolean { |
| 28 | return value?.toLocaleLowerCase().includes(query) ?? false; |
| 29 | } |
| 30 | |
| 31 | export function buildArchivedThreadGroups(input: { |
| 32 | readonly snapshots: ReadonlyArray<ArchivedSnapshotEntry>; |
| 33 | readonly environmentLabels: Readonly<Record<string, string>>; |
| 34 | readonly environmentId: EnvironmentId | null; |
| 35 | readonly searchQuery: string; |
| 36 | readonly sortOrder: ArchivedThreadSortOrder; |
| 37 | }): ReadonlyArray<ArchivedThreadGroup> { |
| 38 | const query = input.searchQuery.trim().toLocaleLowerCase(); |
| 39 | const groups: ArchivedThreadGroup[] = []; |
| 40 | |
| 41 | for (const entry of input.snapshots) { |
| 42 | if (input.environmentId !== null && input.environmentId !== entry.environmentId) { |
| 43 | continue; |
| 44 | } |
| 45 | |
| 46 | const environmentLabel = input.environmentLabels[entry.environmentId] ?? null; |
| 47 | const threadsByProjectId = new Map<string, EnvironmentThreadShell[]>(); |
| 48 | for (const thread of entry.snapshot.threads) { |
| 49 | if (thread.archivedAt === null) { |
| 50 | continue; |
| 51 | } |
| 52 | const threads = threadsByProjectId.get(thread.projectId) ?? []; |
| 53 | threads.push(scopeThreadShell(entry.environmentId, thread)); |
| 54 | threadsByProjectId.set(thread.projectId, threads); |
| 55 | } |
| 56 | |
| 57 | for (const rawProject of entry.snapshot.projects) { |
| 58 | const project = scopeProject(entry.environmentId, rawProject); |
| 59 | const projectThreads = threadsByProjectId.get(project.id) ?? []; |
| 60 | const groupMatches = |
| 61 | query.length === 0 || |
| 62 | matchesQuery(project.title, query) || |
| 63 | matchesQuery(project.workspaceRoot, query) || |
| 64 | matchesQuery(environmentLabel, query); |
| 65 | const matchingThreads = groupMatches |
| 66 | ? projectThreads |
| 67 | : projectThreads.filter( |
| 68 | (thread) => matchesQuery(thread.title, query) || matchesQuery(thread.branch, query), |
| 69 | ); |
| 70 | |
| 71 | if (matchingThreads.length === 0) { |
| 72 | continue; |
| 73 | } |
| 74 | |
| 75 | const timestampOrder = input.sortOrder === "newest" ? Order.flip(Order.Number) : Order.Number; |
| 76 | groups.push({ |
| 77 | key: scopedProjectKey(project.environmentId, project.id), |
| 78 | project, |
| 79 | threads: Arr.sort( |
| 80 | matchingThreads, |
| 81 | Order.mapInput( |
| 82 | Order.Struct({ timestamp: timestampOrder, title: Order.String, id: Order.String }), |
| 83 | (thread: EnvironmentThreadShell) => ({ |
| 84 | timestamp: archiveTimestamp(thread), |
| 85 | title: thread.title, |
| 86 | id: thread.id, |
| 87 | }), |
| 88 | ), |
| 89 | ), |
| 90 | }); |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | const timestampOrder = input.sortOrder === "newest" ? Order.flip(Order.Number) : Order.Number; |
| 95 | return Arr.sort( |
| 96 | groups, |
| 97 | Order.mapInput( |
| 98 | Order.Struct({ timestamp: timestampOrder, title: Order.String, key: Order.String }), |
| 99 | (group: ArchivedThreadGroup) => ({ |
| 100 | timestamp: group.threads[0] ? archiveTimestamp(group.threads[0]) : 0, |
| 101 | title: group.project.title, |
| 102 | key: group.key, |
| 103 | }), |
| 104 | ), |
| 105 | ); |
| 106 | } |