import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import type { ApiSettings } from "../../../types"; import { autosaveCampaignVersion, type CampaignVersionDetail, type CampaignVersionUpdatePayload } from "../../../api/campaigns"; import { formatDateTime, getCampaignJson } from "../utils/campaignView"; import { ensureCampaignDraft, updateNested } from "../utils/draftEditor"; import { useRegisterCampaignUnsavedChanges } from "../context/UnsavedChangesContext"; type StepValue = string | (() => string | null | undefined); type UseCampaignDraftEditorOptions = { settings: ApiSettings; campaignId: string; version: CampaignVersionDetail | null; locked: boolean; reload: () => Promise; setError: (message: string) => void; currentStep: StepValue; currentFlow?: string; workflowState?: string; isComplete?: boolean; unsavedTitle: string; unsavedMessage: string; loadedLabel?: (version: CampaignVersionDetail) => string; transformLoadedDraft?: (version: CampaignVersionDetail, draft: Record) => Record; transformDraftBeforeSave?: (draft: Record) => Record; extraPayload?: () => Partial; onLoaded?: (version: CampaignVersionDetail, draft: Record) => void; onSaved?: (version: CampaignVersionDetail) => void; }; function resolveStep(value: StepValue): string | null | undefined { return typeof value === "function" ? value() : value; } function defaultLoadedLabel(version: CampaignVersionDetail): string { return version.autosaved_at ? `Loaded saved draft ${formatDateTime(version.autosaved_at)}` : "i18n:govoplan-campaign.loaded.6db90a0a"; } export function useCampaignDraftEditor({ settings, campaignId, version, locked, reload, setError, currentStep, currentFlow = "manual", workflowState = "editing", isComplete = false, unsavedTitle, unsavedMessage, loadedLabel = defaultLoadedLabel, transformLoadedDraft, transformDraftBeforeSave, extraPayload, onLoaded, onSaved }: UseCampaignDraftEditorOptions) { const loadedLabelRef = useRef(loadedLabel); const transformLoadedDraftRef = useRef(transformLoadedDraft); const transformDraftBeforeSaveRef = useRef(transformDraftBeforeSave); const onLoadedRef = useRef(onLoaded); useEffect(() => { loadedLabelRef.current = loadedLabel; transformLoadedDraftRef.current = transformLoadedDraft; transformDraftBeforeSaveRef.current = transformDraftBeforeSave; onLoadedRef.current = onLoaded; }, [loadedLabel, transformLoadedDraft, transformDraftBeforeSave, onLoaded]); const [draft, setDraft] = useState | null>(null); const [dirty, setDirty] = useState(false); const [saveState, setSaveState] = useState("i18n:govoplan-campaign.loaded.6db90a0a"); const [localError, setLocalError] = useState(""); useEffect(() => { if (!version) return; const initialDraft = ensureCampaignDraft(version); const loadedDraft = transformLoadedDraftRef.current?.(version, initialDraft) ?? initialDraft; setDraft(loadedDraft); setDirty(false); setLocalError(""); setSaveState(loadedLabelRef.current(version)); onLoadedRef.current?.(version, loadedDraft); }, [version]); const markDirty = useCallback(() => { setDirty(true); setLocalError(""); }, []); const patch = useCallback((path: string[], value: unknown) => { if (locked) return; setDraft((current) => updateNested(current ?? {}, path, value)); markDirty(); }, [locked, markDirty]); const saveDraft = useCallback(async (_mode: "auto" | "manual" = "manual"): Promise => { if (!draft || !version || locked) return false; setSaveState("i18n:govoplan-campaign.saving.56a2285c"); setError(""); setLocalError(""); try { const draftToSave = transformDraftBeforeSaveRef.current?.(draft) ?? draft; const saved = await autosaveCampaignVersion(settings, campaignId, version.id, { campaign_json: draftToSave, current_flow: currentFlow, current_step: resolveStep(currentStep), workflow_state: workflowState, is_complete: isComplete, ...(extraPayload?.() ?? {}) }); setDraft(getCampaignJson(saved)); setDirty(false); setSaveState(`Saved ${formatDateTime(saved.autosaved_at ?? saved.updated_at)}`); onSaved?.(saved); await reload(); return true; } catch (err) { const text = err instanceof Error ? err.message : String(err); setLocalError(text); setSaveState("i18n:govoplan-campaign.save_failed.0a444467"); return false; } }, [campaignId, currentFlow, currentStep, draft, extraPayload, isComplete, locked, onSaved, reload, setError, settings, version, workflowState]); const unsavedRegistration = useMemo(() => dirty && !locked ? { title: unsavedTitle, message: unsavedMessage, onSave: () => saveDraft("manual"), onDiscard: () => setDirty(false) } : null, [dirty, locked, saveDraft, unsavedMessage, unsavedTitle]); useRegisterCampaignUnsavedChanges(unsavedRegistration); return { draft, setDraft, displayDraft: draft ?? ensureCampaignDraft(null), dirty, setDirty, saveState, setSaveState, localError, setLocalError, patch, markDirty, saveDraft }; }