1import type { EnvironmentProject } from "@t3tools/client-runtime/state/models";
2import type { EnvironmentConnectionSummary } from "@t3tools/client-runtime/state/presentation";
3
4export type PullRequestEnvironmentTarget = {
5 readonly environmentId: string;
6 readonly projectId: string;
7 readonly repository: string;
8 readonly host?: string;
9};
10
11export function pullRequestFallbackEnvironment(
12 target: PullRequestEnvironmentTarget,
13 projects: ReadonlyArray<EnvironmentProject>,
14 environments: ReadonlyArray<
15 Pick<EnvironmentConnectionSummary, "environmentId" | "connectionState" | "isEnabled">
16 >,
17) {
18 const requested = environments.find(
19 (environment) => environment.environmentId === target.environmentId,
20 );
21 // Pending connections retain the requested target until an explicit failure, even before
22 // its project hydrates. Available includes the period before connection startup.
23 if (
24 requested?.isEnabled &&
25 (requested.connectionState === "connected" ||
26 requested.connectionState === "available" ||
27 requested.connectionState === "connecting" ||
28 requested.connectionState === "reconnecting")
29 )
30 return null;
31 const connectedEnvironmentIds = new Set<string>(
32 environments
33 .filter((environment) => environment.isEnabled && environment.connectionState === "connected")
34 .map((environment) => environment.environmentId),
35 );
36 const own = projects.find(
37 (project) => project.environmentId === target.environmentId && project.id === target.projectId,
38 );
39 const host = target.host ?? own?.repositoryIdentity?.canonicalKey.split("/")[0];
40 if (!host) return null;
41 const key = `${host}/${target.repository}`.toLowerCase();
42 const copy = projects.find(
43 (project) =>
44 project.environmentId !== target.environmentId &&
45 connectedEnvironmentIds.has(project.environmentId) &&
46 project.repositoryIdentity?.canonicalKey.toLowerCase() === key,
47 );
48 return copy ? { environmentId: String(copy.environmentId), projectId: String(copy.id) } : null;
49}