1import {
2 isNullableProjectSettingsOverride,
3 PROJECT_SCOPED_SERVER_SETTING_KEYS,
4 type EnvironmentId,
5 type ProjectId,
6 type ProjectScopedServerSettingKey,
7 type ServerSettings,
8 type ServerSettingsPatch,
9} from "@t3tools/contracts";
10import {
11 clearProjectSettingsOverrides,
12 resolveProjectSettings,
13} from "@t3tools/shared/projectSettings";
14
15import type { SettingsTarget } from "./settings-environment-filter";
16
17export interface ScopedMobileSettingsTarget {
18 readonly environment: SettingsTarget;
19 readonly projectId: ProjectId | null;
20 readonly settings: ServerSettings;
21 readonly sources: ReturnType<typeof resolveProjectSettings>["sources"];
22}
23
24export function resolveMobileSettingsTargets(
25 environments: readonly SettingsTarget[],
26 members: readonly { readonly environmentId: EnvironmentId; readonly id: ProjectId }[] | null,
27): readonly ScopedMobileSettingsTarget[] {
28 if (members === null) {
29 return environments.map((environment) => ({
30 environment,
31 projectId: null,
32 ...resolveProjectSettings(environment.serverConfig.settings, null),
33 }));
34 }
35 const byId = new Map(environments.map((environment) => [environment.environmentId, environment]));
36 return members.flatMap((member) => {
37 const environment = byId.get(member.environmentId);
38 if (!environment) return [];
39 return [
40 {
41 environment,
42 projectId: member.id,
43 ...resolveProjectSettings(environment.serverConfig.settings, member.id),
44 },
45 ];
46 });
47}
48
49export function planMobileScopedSettingsPatch(
50 targets: readonly ScopedMobileSettingsTarget[],
51 projectSelected: boolean,
52 patch: ServerSettingsPatch,
53) {
54 if (!projectSelected) {
55 return targets.map((target) => ({ environmentId: target.environment.environmentId, patch }));
56 }
57 const keys = Object.keys(patch);
58 if (
59 keys.some(
60 (key) => !PROJECT_SCOPED_SERVER_SETTING_KEYS.includes(key as ProjectScopedServerSettingKey),
61 )
62 )
63 return [];
64 const writes = new Map<EnvironmentId, Record<string, unknown>>();
65 for (const target of targets) {
66 if (
67 target.projectId === null ||
68 target.environment.serverConfig.environment.capabilities.projectSettingsOverrides !== true
69 )
70 continue;
71 const current =
72 target.environment.serverConfig.settings.projectSettingsOverrides[target.projectId] ?? {};
73 const next: Record<string, unknown> = { ...current };
74 for (const [key, value] of Object.entries(patch)) {
75 // A picker's "Inherit" sends null; for keys whose override cannot
76 // store null that means remove the override.
77 if (
78 value === null &&
79 !isNullableProjectSettingsOverride(key as ProjectScopedServerSettingKey)
80 ) {
81 delete next[key];
82 } else {
83 next[key] = value;
84 }
85 }
86 const overrides = writes.get(target.environment.environmentId) ?? {};
87 overrides[target.projectId] = next;
88 writes.set(target.environment.environmentId, overrides);
89 }
90 return [...writes].map(([environmentId, projectSettingsOverrides]) => ({
91 environmentId,
92 patch: { projectSettingsOverrides } as ServerSettingsPatch,
93 }));
94}
95
96/** A mixed selection has no single value to display. */
97export function uniformMobileSetting<K extends keyof ServerSettings>(
98 targets: readonly Pick<ScopedMobileSettingsTarget, "settings">[],
99 key: K,
100): ServerSettings[K] | null {
101 const reference = targets[0];
102 if (!reference) return null;
103 const value = reference.settings[key];
104 return targets.every((target) => target.settings[key] === value) ? value : null;
105}
106
107export function planMobileScopedSettingsClear(
108 targets: readonly ScopedMobileSettingsTarget[],
109 keys: readonly ProjectScopedServerSettingKey[],
110) {
111 const writes = new Map<EnvironmentId, Record<string, unknown>>();
112 for (const target of targets) {
113 if (
114 target.projectId === null ||
115 target.environment.serverConfig.environment.capabilities.projectSettingsOverrides !== true
116 )
117 continue;
118 const overrides = writes.get(target.environment.environmentId) ?? {};
119 overrides[target.projectId] = clearProjectSettingsOverrides(
120 target.environment.serverConfig.settings,
121 target.projectId,
122 keys,
123 );
124 writes.set(target.environment.environmentId, overrides);
125 }
126 return [...writes].map(([environmentId, projectSettingsOverrides]) => ({
127 environmentId,
128 patch: { projectSettingsOverrides } as ServerSettingsPatch,
129 }));
130}