1import type { EnvironmentPresentation } from "../../state/environments";
2import type { ResolvedSettingsScope, SettingsScopeSearch } from "./settingsScope";
3
4type ScopeEnvironment = Pick<EnvironmentPresentation, "environmentId" | "label" | "displayUrl">;
5
6export function settingsScopeEnvironmentLabel(
7 environment: ScopeEnvironment,
8 environments: readonly ScopeEnvironment[],
9) {
10 const duplicate = environments.some(
11 (other) =>
12 other.environmentId !== environment.environmentId && other.label === environment.label,
13 );
14 return duplicate
15 ? `${environment.label} · ${environment.displayUrl ?? environment.environmentId}`
16 : environment.label;
17}
18
19export const ALL_ENVIRONMENTS_VALUE = "all";
20export const ALL_PROJECTS_VALUE = "all";
21
22/**
23 * The environment axis: `all` or an environment id. A legacy checkout link
24 * without `machine` still names one environment, which the resolver supplies.
25 */
26export function environmentAxisValue(
27 search: SettingsScopeSearch,
28 resolvedEnvironmentId?: string | null,
29): string {
30 return search.machine ?? resolvedEnvironmentId ?? ALL_ENVIRONMENTS_VALUE;
31}
32
33/** The project axis: `all` or a project key. */
34export function projectAxisValue(search: SettingsScopeSearch): string {
35 return search.project ?? ALL_PROJECTS_VALUE;
36}
37
38/** Choosing an environment keeps the project; a pre-existing checkout narrowing is dropped. */
39export function selectEnvironmentAxis(
40 search: SettingsScopeSearch,
41 value: string,
42): SettingsScopeSearch {
43 const next: SettingsScopeSearch = {};
44 if (search.project) next.project = search.project;
45 if (value !== ALL_ENVIRONMENTS_VALUE) next.machine = value;
46 return next;
47}
48
49/** Choosing a project keeps the environment axis. */
50export function selectProjectAxis(search: SettingsScopeSearch, value: string): SettingsScopeSearch {
51 const next: SettingsScopeSearch = {};
52 if (value !== ALL_PROJECTS_VALUE) next.project = value;
53 if (search.machine) next.machine = search.machine;
54 return next;
55}
56
57/** Provider configuration always belongs to one environment, including project scopes. */
58export function selectSingleEnvironmentScope(
59 search: SettingsScopeSearch,
60 scope: ResolvedSettingsScope,
61 environments: readonly {
62 readonly environmentId: EnvironmentPresentation["environmentId"];
63 readonly connection: Pick<EnvironmentPresentation["connection"], "phase">;
64 }[],
65 primaryEnvironmentId: string | null,
66): SettingsScopeSearch {
67 if (search.machine || scope.kind === "unavailable") return search;
68 const selectedIds = new Set(scope.environmentIds);
69 const candidates = environments.filter((environment) =>
70 selectedIds.has(environment.environmentId),
71 );
72 const selected =
73 candidates.find((environment) => environment.environmentId === primaryEnvironmentId) ??
74 candidates.find((environment) => environment.connection.phase === "connected") ??
75 candidates[0];
76 return selected ? { ...search, machine: selected.environmentId } : search;
77}