import { computed, onBeforeUnmount, ref, watch } from "vue"; export const ORDER_METADATA_AUTOSAVE_DELAY_MS = 600; const defaultNormalizeValue = (value) => (value == null ? "" : String(value)); const resolveSourceValue = (source) => { if (typeof source === "function") { return source(); } return source?.value; }; export const useOrderMetadataAutosave = ({ source, saveValue, normalizeValue = defaultNormalizeValue, delay = ORDER_METADATA_AUTOSAVE_DELAY_MS, onSaved = null, onError = null, }) => { const draft = ref(normalizeValue(resolveSourceValue(source))); const lastSavedValue = ref(normalizeValue(resolveSourceValue(source))); const isSaving = ref(false); const isDirty = computed(() => normalizeValue(draft.value) !== normalizeValue(lastSavedValue.value)); let timerId = null; let saveAgainAfterCurrentRequest = false; let nextSequence = 0; let lastAppliedSequence = 0; const clearPendingSave = () => { if (timerId !== null) { window.clearTimeout(timerId); timerId = null; } }; const syncFromSource = (value) => { const normalizedValue = normalizeValue(value); const shouldHydrateDraft = normalizeValue(draft.value) === normalizeValue(lastSavedValue.value); lastSavedValue.value = normalizedValue; if (shouldHydrateDraft) { draft.value = normalizedValue; } }; watch(() => resolveSourceValue(source), syncFromSource, { immediate: true }); const persist = async () => { clearPendingSave(); if (!isDirty.value) { return false; } if (isSaving.value) { saveAgainAfterCurrentRequest = true; return false; } const requestedValue = normalizeValue(draft.value); const sequence = ++nextSequence; let saveCompleted = false; isSaving.value = true; try { const result = await saveValue(requestedValue); const savedValue = normalizeValue(result ?? requestedValue); if (sequence >= lastAppliedSequence) { lastAppliedSequence = sequence; lastSavedValue.value = savedValue; if (normalizeValue(draft.value) === requestedValue) { draft.value = savedValue; } } if (typeof onSaved === "function") { await onSaved(savedValue, requestedValue); } saveCompleted = true; return true; } catch (error) { if (typeof onError === "function") { onError(error); } else { console.error(error); } return false; } finally { isSaving.value = false; const shouldSaveAgain = saveAgainAfterCurrentRequest; saveAgainAfterCurrentRequest = false; if (saveCompleted && (shouldSaveAgain || isDirty.value) && isDirty.value) { void persist(); } } }; const scheduleSave = () => { clearPendingSave(); if (!isDirty.value) { return; } timerId = window.setTimeout(() => { void persist(); }, delay); }; const flush = async () => { return persist(); }; onBeforeUnmount(() => { clearPendingSave(); if (isSaving.value) { saveAgainAfterCurrentRequest = true; return; } if (isDirty.value) { void persist(); } }); return { draft, lastSavedValue, isDirty, isSaving, scheduleSave, flush, clearPendingSave, syncFromSource, }; };