1import { useAtomValue } from "@effect/atom-react";
2import {
3 linkableMachines,
4 linkedEnvironmentNames,
5 offersLinkedEnvironments,
6} from "@t3tools/client-runtime/link-request";
7import { createPeerLinkEnvironmentAtoms } from "@t3tools/client-runtime/state/peerLinks";
8import type { EnvironmentId } from "@t3tools/contracts";
9import { useEffect, useEffectEvent, useMemo } from "react";
10
11import { connectionAtomRuntime } from "../connection/runtime";
12import { useEnvironments } from "./environments";
13import { relayEnvironmentDiscovery } from "./relay";
14import { useEnvironmentQuery } from "./query";
15import { useAtomCommand } from "./use-atom-command";
16
17export const peerLinkEnvironment = createPeerLinkEnvironmentAtoms(connectionAtomRuntime);
18
19/**
20 * The machines `environmentId` links to, by name, while it offers links, and none otherwise.
21 * Its agents can read threads there. Listing asks each linked machine whether it answers, so
22 * every caller shares one briefly cached list.
23 */
24export function useLinkedEnvironments(
25 environmentId: EnvironmentId | null,
26): ReadonlyMap<EnvironmentId, string> {
27 const { environments } = useEnvironments();
28 const offered = offersLinkedEnvironments(
29 environments.find((environment) => environment.environmentId === environmentId)?.serverConfig,
30 );
31 const links = useEnvironmentQuery(
32 offered && environmentId !== null
33 ? peerLinkEnvironment.list({ environmentId, input: {} })
34 : null,
35 );
36 return useMemo(
37 () => linkedEnvironmentNames({ links: offered ? (links.data?.links ?? []) : [], environments }),
38 [environments, links.data, offered],
39 );
40}
41
42/**
43 * The user's other machines, for picking one to link `environmentId` to,
44 * including those only on their T3 Connect account. Reads that environment's
45 * links only while `enabled`, since listing them asks every linked machine
46 * whether it answers.
47 */
48export function useLinkableMachines(environmentId: EnvironmentId, enabled: boolean) {
49 const { environments } = useEnvironments();
50 const discovery = useAtomValue(relayEnvironmentDiscovery.stateValueAtom);
51 const refreshDiscovery = useAtomCommand(relayEnvironmentDiscovery.refresh, {
52 reportFailure: false,
53 });
54 // Connect hosts are listed only once something asked for them, so ask on open.
55 const discoverIfUnasked = useEffectEvent(() => {
56 if (discovery.environments.size === 0 && !discovery.refreshing) void refreshDiscovery();
57 });
58 // oxlint-disable-next-line t3code/no-unreasoned-state-sync -- discovery follows the picker's visibility, which a card's mount or a dialog's open sets
59 useEffect(() => {
60 if (enabled) discoverIfUnasked();
61 }, [enabled]);
62 const links = useEnvironmentQuery(
63 enabled ? peerLinkEnvironment.list({ environmentId, input: {} }) : null,
64 );
65 return linkableMachines({
66 environments,
67 threadEnvironmentId: environmentId,
68 links: links.data?.links ?? [],
69 connect: discovery.environments,
70 });
71}