import { CB, DynamicValue, Maybe, isFunction, dynamicValue } from 'vest-utils'; // eslint-disable-next-line max-lines-per-function export function createState( onStateChange?: (...args: unknown[]) => unknown, ): CreateStateReturn { const state: { references: unknown[]; } = { references: [], }; const registrations: [ unknown, ((currentState: S, prevState: S) => void)?, ][] = []; return { registerStateKey, reset, }; /** * Registers a new key in the state, takes the initial value (may be a function that returns the initial value), returns a function. * * @example * * const useColor = state.registerStateKey("blue"); * * let [color, setColor] = useColor(); // -> ["blue", Function] * * setColor("green"); * * useColor()[0]; -> "green" */ function registerStateKey( initialState?: Maybe>, onUpdate?: () => void, ): CB> { const key = registrations.length; registrations.push([initialState, onUpdate]); return initKey(key, initialState); } function reset(): void { const prev = current(); state.references = []; registrations.forEach(([initialValue], index) => initKey(index, initialValue, prev[index]), ); } function initKey( key: number, initialState?: Maybe>, prevState?: Maybe, ) { current().push(); set(key, dynamicValue(initialState, prevState)); return function useStateKey(): StateHandlerReturn { return [ current()[key], (nextState: SetStateInput) => set(key, dynamicValue(nextState, current()[key])), ]; }; } function current(): any[] { return state.references; } function set(index: number, value: unknown): void { const prevValue = state.references[index]; state.references[index] = value; const [, onUpdate] = registrations[index]; if (isFunction(onUpdate)) { onUpdate(value, prevValue); } if (isFunction(onStateChange)) { onStateChange(); } } } type StateInput = DynamicValue; type SetStateInput = DynamicValue; export type State = CreateStateReturn; export type StateHandlerReturn = [S, (nextState: SetStateInput) => void]; export type UseState = CB>; type CreateStateReturn = { reset: () => void; registerStateKey: ( initialState?: Maybe>, onUpdate?: () => void, ) => CB>; };