import { DataObject } from './types'; export const createArrayHelpers = < NodelessOps extends Record, NodeOps extends Record, >( props: { nodelessOperations?: NodelessOps; nodeOperations?: NodeOps; } = {}, ) => { const { nodelessOperations, nodeOperations } = props; const baseHelpers = { remove: >( node: N, arrayType: string, ): N => ({ ...node, __remove: Array.isArray(node.__remove) ? [...(node.__remove as string[]), arrayType] : [arrayType], }), append: >( node: N, arrayType: string, ): N => ({ ...node, __append: Array.isArray(node.__append) ? [...(node.__append as string[]), arrayType] : [arrayType], }), prepend: >( node: N, arrayType: string, ): N => ({ ...node, __prepend: Array.isArray(node.__prepend) ? [...(node.__prepend as string[]), arrayType] : [arrayType], }), insert: >( node: N, arrayType: string, config: { index: number }, ): N => ({ ...node, __insert: Array.isArray(node.__insert) ? [...(node.__insert as unknown[]), { arrayType, index: config.index }] : [{ arrayType, index: config.index }], }), replace: >( node: N, arrayType: string, config: { index: number }, ): N => ({ ...node, __replace: Array.isArray(node.__replace) ? [...(node.__replace as unknown[]), { arrayType, index: config.index }] : [{ arrayType, index: config.index }], }), move: >( node: N, arrayType: string, config: { toIndex: number }, ): N => ({ ...node, __move: Array.isArray(node.__move) ? [ ...(node.__move as unknown[]), { arrayType, toIndex: config.toIndex }, ] : [{ arrayType, toIndex: config.toIndex }], }), swap: >( node: N, arrayType: string, config: { toIndex: number }, ): N => ({ ...node, __swap: Array.isArray(node.__swap) ? [ ...(node.__swap as unknown[]), { arrayType, toIndex: config.toIndex }, ] : [{ arrayType, toIndex: config.toIndex }], }), clear: (arrayType: string) => ({ __clear: arrayType, }), replaceAll: (arrayType: string, config: { value: DataObject[] }) => ({ __replaceAll: { arrayTypes: arrayType, value: config.value }, }), } as const; const helpers = { ...baseHelpers, ...(nodelessOperations || ({} as NodelessOps)), ...(nodeOperations || ({} as NodeOps)), } as typeof baseHelpers & NodelessOps & NodeOps; // Extract parameter types from chainable operations, excluding the first 'node' parameter type ChainOpParams = T extends ( node: Record, ...args: infer P ) => Record ? P : never; type ChainApi = { apply: () => N; // Base chainable operations (exclude clear and replaceAll which don't take node) remove: (arrayType: string) => ChainApi; append: (arrayType: string) => ChainApi; prepend: (arrayType: string) => ChainApi; insert: (arrayType: string, config: { index: number }) => ChainApi; replace: (arrayType: string, config: { index: number }) => ChainApi; move: (arrayType: string, config: { toIndex: number }) => ChainApi; swap: (arrayType: string, config: { toIndex: number }) => ChainApi; } & { [K in keyof NodeOps]: (...args: ChainOpParams) => ChainApi; }; const callHelper = ( fn: ( node: Record, ...args: ReadonlyArray ) => Record, current: Record, args: ReadonlyArray, ): Record => fn(current, ...args); const chain = >(node: N): ChainApi => { const create = (current: N): ChainApi => { const api: Record & { apply: () => N } = { apply: () => current, }; ( [ 'remove', 'append', 'prepend', 'insert', 'replace', 'move', 'swap', ] as const ).forEach(key => { const fn = helpers[key] as unknown as ( n: Record, ...a: ReadonlyArray ) => Record; (api as Record)[key] = ( ...args: ReadonlyArray ) => create(callHelper(fn, current, args) as N); }); Object.keys(nodeOperations || {}).forEach(key => { (api as Record)[key] = ( ...args: ReadonlyArray ) => { const fn = helpers[key] as ( ...params: unknown[] ) => Record; const result = fn(current, ...args); return create(result as N); }; }); return api as ChainApi; }; return create(node); }; return { ...helpers, chain, } as typeof baseHelpers & NodelessOps & NodeOps & { chain: typeof chain }; }; export const arrayHelpers = createArrayHelpers({});