1import { useAtomValue } from "@effect/atom-react";
2import type { EnvironmentShellStatus } from "@t3tools/client-runtime/state/shell";
3import type { EnvironmentId, MessageId } from "@t3tools/contracts";
4import { Atom } from "effect/reactivity";
5
6import { appAtomRegistry } from "./atom-registry";
7import { environmentShell } from "./shell";
8import { threadOutboxManager } from "./thread-outbox";
9
10export const threadOutboxShellStatusesAtom = Atom.make(
11 (get): ReadonlyMap<EnvironmentId, EnvironmentShellStatus> => {
12 const statuses = new Map<EnvironmentId, EnvironmentShellStatus>();
13 for (const queue of Object.values(get(threadOutboxManager.queuedMessagesByThreadKeyAtom))) {
14 const environmentId = queue[0]?.environmentId;
15 if (environmentId !== undefined && !statuses.has(environmentId)) {
16 statuses.set(environmentId, get(environmentShell.stateValueAtom(environmentId)).status);
17 }
18 }
19 return statuses;
20 },
21).pipe(Atom.withLabel("mobile:thread-outbox:shell-statuses"));
22
23/** Do not deliver tasks still open in an editor or whose latest edits failed to save; their queued payloads are stale. */
24export const editingQueuedMessageIdsAtom = Atom.make<Readonly<Record<MessageId, true>>>({}).pipe(
25 Atom.keepAlive,
26 Atom.withLabel("mobile:thread-outbox:editing-message-ids"),
27);
28
29export const dispatchingQueuedMessageIdAtom = Atom.make<MessageId | null>(null).pipe(
30 Atom.keepAlive,
31 Atom.withLabel("mobile:thread-outbox:dispatching-message-id"),
32);
33
34/** Messages waiting out a retry backoff, which can repeat for as long as the failure lasts. */
35export const retryingQueuedMessageIdsAtom = Atom.make<Readonly<Partial<Record<MessageId, true>>>>(
36 {},
37).pipe(Atom.keepAlive, Atom.withLabel("mobile:thread-outbox:retrying-message-ids"));
38
39export function holdEditingQueuedMessage(messageId: MessageId): void {
40 const current = appAtomRegistry.get(editingQueuedMessageIdsAtom);
41 if (current[messageId]) {
42 return;
43 }
44 appAtomRegistry.set(editingQueuedMessageIdsAtom, { ...current, [messageId]: true });
45}
46
47export function releaseEditingQueuedMessage(messageId: MessageId): void {
48 const current = appAtomRegistry.get(editingQueuedMessageIdsAtom);
49 if (!current[messageId]) {
50 return;
51 }
52 const next = { ...current };
53 delete next[messageId];
54 appAtomRegistry.set(editingQueuedMessageIdsAtom, next);
55}
56
57export function useThreadOutboxMessages() {
58 return useAtomValue(threadOutboxManager.queuedMessagesByThreadKeyAtom);
59}
60
61export const queuedThreadKeysAtom = Atom.make((get): ReadonlySet<string> => {
62 const keys = new Set<string>();
63 for (const [threadKey, queue] of Object.entries(
64 get(threadOutboxManager.queuedMessagesByThreadKeyAtom),
65 )) {
66 if (queue.some((message) => message.creation === undefined)) {
67 keys.add(threadKey);
68 }
69 }
70 return keys;
71}).pipe(Atom.keepAlive, Atom.withLabel("mobile:thread-outbox:queued-thread-keys"));
72
73export function useQueuedThreadKeys(): ReadonlySet<string> {
74 return useAtomValue(queuedThreadKeysAtom);
75}