import { useCallback, useRef } from 'react' import type { SetStateAction } from 'react' import { useSaveNote } from './useSaveNote' interface Tab { entry: { path: string } content: string } interface EditorSaveConfig { updateVaultContent: (path: string, content: string) => void // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Tab types vary between layers setTabs: (fn: SetStateAction) => void setToastMessage: (msg: string | null) => void onAfterSave?: () => void } /** * Hook that manages explicit save (Cmd+S) for editor content. * Tracks pending (unsaved) content and provides save + pre-rename helpers. */ const noop = () => {} export function useEditorSave({ updateVaultContent, setTabs, setToastMessage, onAfterSave = noop }: EditorSaveConfig) { const pendingContentRef = useRef<{ path: string; content: string } | null>(null) const updateTabAndContent = useCallback((path: string, content: string) => { updateVaultContent(path, content) setTabs((prev: Tab[]) => prev.map((t) => t.entry.path === path ? { ...t, content } : t) ) }, [updateVaultContent, setTabs]) const { saveNote } = useSaveNote(updateTabAndContent) /** Persist pending content matching an optional path filter; returns true if saved */ const flushPending = useCallback(async (pathFilter?: string): Promise => { const pending = pendingContentRef.current if (!pending) return false if (pathFilter && pending.path !== pathFilter) return false await saveNote(pending.path, pending.content) pendingContentRef.current = null return true }, [saveNote]) /** Called by Cmd+S — persists the current editor content to disk */ const handleSave = useCallback(async () => { try { const saved = await flushPending() setToastMessage(saved ? 'Saved' : 'Nothing to save') onAfterSave() } catch (err) { console.error('Save failed:', err) setToastMessage(`Save failed: ${err}`) } }, [flushPending, setToastMessage, onAfterSave]) /** Called by Editor onChange — buffers the latest content without saving */ const handleContentChange = useCallback((path: string, content: string) => { pendingContentRef.current = { path, content } }, []) /** Save pending content for a specific path (used before rename) */ const savePendingForPath = useCallback( (path: string): Promise => flushPending(path), [flushPending], ) /** Flush any pending content to disk silently (used before git commit). * Does NOT call onAfterSave — callers manage their own refresh. */ const savePending = useCallback((): Promise => flushPending(), [flushPending]) return { handleSave, handleContentChange, savePendingForPath, savePending } }