import { useCallback, useReducer, useRef } from "react"; // A generic, reusable undo/redo container for a single piece of state. // // It keeps a past/present/future snapshot stack: `set` records the current value into the // past and clears the redo stack; `undo`/`redo` walk the stack. Every value change (set, // undo, redo) calls `onApply(value)` — the side effect that makes the change real (e.g. // persist to the server). `reset` installs a fresh value and clears history WITHOUT calling // onApply (use it when the underlying data is reloaded from the source of truth). // // Not playlist-specific: any component with an undoable value (sort order, a draft, a set // of filters…) can use it. State lives in a ref so the callbacks are stable and never read // a stale snapshot; a reducer-based force-render keeps the view in sync. export interface Undoable { value: T; set: (next: T) => void; reset: (value: T) => void; undo: () => void; redo: () => void; canUndo: boolean; canRedo: boolean; } interface History { past: T[]; present: T; future: T[]; } export function useUndoable( initial: T, opts?: { onApply?: (value: T) => void; limit?: number } ): Undoable { const [, force] = useReducer((n: number) => n + 1, 0); const ref = useRef>({ past: [], present: initial, future: [] }); const onApplyRef = useRef(opts?.onApply); onApplyRef.current = opts?.onApply; const limit = opts?.limit ?? 100; const set = useCallback( (next: T) => { const s = ref.current; if (Object.is(next, s.present)) return; const past = [...s.past, s.present]; if (past.length > limit) past.shift(); ref.current = { past, present: next, future: [] }; force(); onApplyRef.current?.(next); }, [limit] ); const undo = useCallback(() => { const s = ref.current; if (!s.past.length) return; const prev = s.past[s.past.length - 1]; ref.current = { past: s.past.slice(0, -1), present: prev, future: [s.present, ...s.future], }; force(); onApplyRef.current?.(prev); }, []); const redo = useCallback(() => { const s = ref.current; if (!s.future.length) return; const [next, ...rest] = s.future; ref.current = { past: [...s.past, s.present], present: next, future: rest }; force(); onApplyRef.current?.(next); }, []); const reset = useCallback((value: T) => { ref.current = { past: [], present: value, future: [] }; force(); }, []); const h = ref.current; return { value: h.present, set, reset, undo, redo, canUndo: h.past.length > 0, canRedo: h.future.length > 0, }; }