1import { useAtomValue } from "@effect/atom-react";
2import type { EnvironmentId } from "@t3tools/contracts";
3import {
4 createContext,
5 use,
6 useMemo,
7 useCallback,
8 type Dispatch,
9 type ReactNode,
10 type SetStateAction,
11} from "react";
12
13import { useProjects, useServerConfigs } from "../../state/entities";
14import {
15 createDraft,
16 scheduledTaskDefaultModel,
17 hasScheduledTaskDraftChanges,
18 type ScheduledTaskDraft,
19} from "./scheduledTaskDraft";
20import { useSettingsEnvironmentFilter } from "./settings-environment-filter";
21import { settingsTargetsForProject } from "./settings-environment-filter.logic";
22
23import {
24 scheduledTaskEditorSessionAtom,
25 startScheduledTaskEditor,
26 updateScheduledTaskEditor,
27 readScheduledTaskEditor,
28 type ScheduledTaskEditor,
29} from "./scheduled-task-editor-state";
30
31const ScheduledTaskEditorContext = createContext<{
32 readonly editor: ScheduledTaskEditor | null;
33 readonly voiceOwnerKey: string;
34 readonly readEditor: () => ScheduledTaskEditor | null;
35 readonly setEditor: Dispatch<SetStateAction<ScheduledTaskEditor | null>>;
36 readonly startEditor: (editor: ScheduledTaskEditor | null) => void;
37 readonly hasChanges: boolean;
38 readonly resetEditor: () => void;
39 readonly draftForEnvironment: (environmentId: EnvironmentId) => ScheduledTaskDraft;
40} | null>(null);
41
42/** Keeps the form draft alive while its native picker routes are on top. */
43export function ScheduledTaskEditorProvider({ children }: { readonly children: ReactNode }) {
44 const { selectedTargets, selectedProjectKey, projectGroups } = useSettingsEnvironmentFilter();
45 const selectedGroup = projectGroups.find((group) => group.key === selectedProjectKey);
46 const configs = useServerConfigs();
47 const projects = useProjects();
48 const defaultTarget = settingsTargetsForProject(
49 selectedTargets,
50 selectedProjectKey === null ? null : selectedGroup,
51 )[0];
52 const draftForEnvironment = useCallback(
53 (environmentId: EnvironmentId) => {
54 const environmentProjects = projects.filter(
55 (project) => project.environmentId === environmentId,
56 );
57 const project =
58 environmentProjects.find((project) =>
59 selectedGroup?.members.some(
60 (member) =>
61 member.project.environmentId === environmentId && member.project.id === project.id,
62 ),
63 ) ?? environmentProjects[0];
64 return createDraft(
65 project?.id ?? null,
66 scheduledTaskDefaultModel(configs.get(environmentId) ?? null, project ?? null),
67 );
68 },
69 [configs, projects, selectedGroup],
70 );
71 // Direct links can open the new-task form before environments have connected.
72 const defaultEditor = useMemo(
73 () =>
74 defaultTarget
75 ? {
76 environmentId: defaultTarget.environmentId,
77 environmentLabel: defaultTarget.label,
78 draft: draftForEnvironment(defaultTarget.environmentId),
79 }
80 : null,
81 [defaultTarget, draftForEnvironment],
82 );
83 const session = useAtomValue(scheduledTaskEditorSessionAtom);
84 const voiceOwnerKey = `scheduled-task:${session?.id ?? 0}`;
85 const editor = session ? session.current : defaultEditor;
86 const initial = session ? session.initial : defaultEditor;
87 const hasChanges = Boolean(
88 initial &&
89 editor &&
90 (initial.environmentId !== editor.environmentId ||
91 hasScheduledTaskDraftChanges(initial.draft, editor.draft)),
92 );
93 const setEditor = useCallback<Dispatch<SetStateAction<ScheduledTaskEditor | null>>>(
94 (update) => {
95 updateScheduledTaskEditor(update, defaultEditor);
96 },
97 [defaultEditor],
98 );
99 const startEditor = startScheduledTaskEditor;
100 const resetEditor = useCallback(() => startEditor(defaultEditor), [defaultEditor, startEditor]);
101 const readEditor = useCallback(
102 () => readScheduledTaskEditor(voiceOwnerKey, defaultEditor),
103 [defaultEditor, voiceOwnerKey],
104 );
105 const value = useMemo(
106 () => ({
107 editor,
108 voiceOwnerKey,
109 readEditor,
110 setEditor,
111 startEditor,
112 hasChanges,
113 resetEditor,
114 draftForEnvironment,
115 }),
116 [
117 editor,
118 voiceOwnerKey,
119 readEditor,
120 setEditor,
121 startEditor,
122 hasChanges,
123 resetEditor,
124 draftForEnvironment,
125 ],
126 );
127 return <ScheduledTaskEditorContext value={value}>{children}</ScheduledTaskEditorContext>;
128}
129
130export function useScheduledTaskEditor() {
131 const context = use(ScheduledTaskEditorContext);
132 if (!context) throw new Error("Scheduled task editor provider is missing.");
133 return context;
134}