1import { scopeThreadRef, scopedThreadKey } from "@t3tools/client-runtime/environment";
2import { useEffect } from "react";
3
4import { useThreadShells } from "../state/entities";
5import { threadEnvironment } from "../state/threads";
6import { useAtomCommand } from "../state/use-atom-command";
7import { useUiStateStore } from "../uiStateStore";
8
9// Module-level so each thread is considered once per page load. The visit
10// command is idempotent server-side (the server keeps max(stored, supplied)),
11// so repeats across page loads are harmless — this only avoids re-dispatching
12// within a session.
13const migratedThreadKeys = new Set<string>();
14
15/**
16 * One-way migration of the browser-local visited watermarks into servers with
17 * visited tracking. Before tracking existed, "Done" lived in this browser's
18 * localStorage; pushing those watermarks up seeds the server value so other
19 * devices see the same read state. The server never rewinds a newer visit, so
20 * this cannot clobber progress made elsewhere.
21 */
22export function useThreadVisitedMigration(): void {
23 const threads = useThreadShells();
24 const visitThreadMutation = useAtomCommand(threadEnvironment.visit, { reportFailure: false });
25 useEffect(() => {
26 for (const thread of threads) {
27 // Field absent → the environment's server has no visited tracking; keep
28 // the local value in play and reconsider if the server upgrades.
29 if (thread.lastVisitedAt === undefined) continue;
30 const threadKey = scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id));
31 if (migratedThreadKeys.has(threadKey)) continue;
32 migratedThreadKeys.add(threadKey);
33 const local = useUiStateStore.getState().threadLastVisitedAtById[threadKey];
34 if (!local) continue;
35 const localMs = Date.parse(local);
36 if (!Number.isFinite(localMs)) continue;
37 const serverMs = thread.lastVisitedAt === null ? null : Date.parse(thread.lastVisitedAt);
38 if (serverMs !== null && Number.isFinite(serverMs) && serverMs >= localMs) continue;
39 void visitThreadMutation({
40 environmentId: thread.environmentId,
41 input: { threadId: thread.id, visitedAt: local },
42 });
43 }
44 }, [threads, visitThreadMutation]);
45}