1import { useAtomValue } from "@effect/atom-react";
2import { parseScopedProjectKey, scopedProjectKey } from "@t3tools/client-runtime/environment";
3import type { EnvironmentId, ProjectCloneSnapshot, ScopedProjectRef } from "@t3tools/contracts";
4import { AsyncResult, Atom } from "effect/reactivity";
5
6import { serverEnvironment } from "./server";
7import { sourceControlEnvironment } from "./sourceControl";
8
9/** pending means the clone stream has not loaded; treating it as absent would briefly enable Start. */
10export type ProjectCloneState = ProjectCloneSnapshot | "pending" | null;
11
12const EMPTY_CLONES: ReadonlyArray<ProjectCloneSnapshot> = [];
13const EMPTY_CLONE_ATOM = Atom.make<ProjectCloneState>(null).pipe(
14 Atom.withLabel("mobile-project-clone:empty"),
15);
16
17const environmentProjectClonesAtom = Atom.family((environmentId: EnvironmentId) =>
18 Atom.make((get): ReadonlyArray<ProjectCloneSnapshot> | "pending" => {
19 const config = get(serverEnvironment.configValueAtom(environmentId));
20 if (!config) return EMPTY_CLONES;
21 const result = get(sourceControlEnvironment.projectClones({ environmentId, input: {} }));
22 // On subscription failure, let server dispatch guards decide rather than blocking Start until reconnect.
23 if (result._tag === "Failure") return EMPTY_CLONES;
24 return AsyncResult.getOrElse(result, () => "pending" as const);
25 }).pipe(Atom.withLabel(`mobile-project-clones:${environmentId}`)),
26);
27
28export const environmentProjectCloneListAtom = Atom.family((environmentId: EnvironmentId) =>
29 Atom.make((get): ReadonlyArray<ProjectCloneSnapshot> => {
30 const clones = get(environmentProjectClonesAtom(environmentId));
31 return clones === "pending" ? EMPTY_CLONES : clones;
32 }).pipe(Atom.withLabel(`mobile-project-clone-list:${environmentId}`)),
33);
34
35const projectCloneAtom = Atom.family((key: string) => {
36 const ref = parseScopedProjectKey(key);
37 return Atom.make((get): ProjectCloneState => {
38 if (ref === null) return null;
39 const clones = get(environmentProjectClonesAtom(ref.environmentId));
40 if (clones === "pending") return "pending";
41 return clones.find((clone) => clone.projectId === ref.projectId) ?? null;
42 }).pipe(Atom.withLabel(`mobile-project-clone:${key}`));
43});
44
45export function useProjectClone(ref: ScopedProjectRef | null): ProjectCloneState {
46 return useAtomValue(ref === null ? EMPTY_CLONE_ATOM : projectCloneAtom(scopedProjectKey(ref)));
47}