import type { GroupFilter, QueryFilter, SingleFilter, } from "@brevity-builder/models"; import { isGroupFilter, isLegacyFilter, isSingleFilter, } from "@brevity-builder/models"; import * as React from "react"; import { nanoid } from "../utils"; const EMPTY_GROUP: GroupFilter = { __type: "filterGroup", id: "empty", children: [], logicalOperator: "AND", }; export function useDynamicFilter( initialValue: GroupFilter | SingleFilter | QueryFilter = EMPTY_GROUP, onChange?: (state?: GroupFilter) => void, ) { const prepare = React.useCallback( (value: GroupFilter | SingleFilter | QueryFilter) => { if (isLegacyFilter(value)) { value = filterToGroup(value); } if (isGroupFilter(value)) { return value; } if (!isSingleFilter(value)) { console.error("Invalid initialValue for filter group:", value); return EMPTY_GROUP; } return { __type: "filterGroup", id: nanoid(), children: [value], logicalOperator: "AND", } as GroupFilter; }, [], ); const defaultProp = React.useMemo(() => { if (!initialValue) return EMPTY_GROUP; let value = initialValue; return prepare(value); }, []); const [group, _setGroup] = React.useState(defaultProp); const setGroup = React.useCallback( ((valueOrCallback) => { if (typeof valueOrCallback === "function") { _setGroup((prev) => { const value = valueOrCallback(prev); onChange?.(value); return value; }); return; } _setGroup(valueOrCallback); onChange?.(valueOrCallback); }) as typeof _setGroup, [_setGroup], ); const clear = React.useCallback(() => setGroup(EMPTY_GROUP), []); const add = React.useCallback((filter: SingleFilter | GroupFilter) => { if (!filter) return; setGroup((group = EMPTY_GROUP) => { return { ...group, children: [...group.children, maybeReplaceIds(filter)], } as GroupFilter; }); }, []); const remove = React.useCallback((old: SingleFilter | GroupFilter) => { if (!old) return; setGroup((group = EMPTY_GROUP) => { return { ...group, children: group.children.filter((f) => f.id !== old.id), }; }); }, []); const replace = React.useCallback( (old: SingleFilter | GroupFilter, filter: SingleFilter | GroupFilter) => { if (!filter) return; setGroup((group = EMPTY_GROUP) => { const index = group.children.findIndex((f) => f.id === old.id); if (index === -1) return group; const newFilters = [...group.children]; newFilters.splice( index, 1, filter.id ? filter : (maybeReplaceIds(filter) as SingleFilter), ); return { ...group, children: newFilters, }; }); }, [], ); const setAnd = React.useCallback(() => { setGroup((group = EMPTY_GROUP) => { return { ...group, logicalOperator: "AND", }; }); }, []); const setOr = React.useCallback(() => { setGroup((group = EMPTY_GROUP) => { return { ...group, logicalOperator: "OR", }; }); }, []); const toggleOperator = React.useCallback(() => { setGroup((group = EMPTY_GROUP) => { return { ...group, logicalOperator: group.logicalOperator === "OR" ? "AND" : "OR", }; }); }, []); const imp = React.useCallback((children: any, logicalOperator: any) => { _setGroup({ children, logicalOperator, } as GroupFilter); }, []); const methods = React.useMemo( () => ({ clear, add, remove, replace, setAnd, setOr, toggleOperator, import: imp, }), [clear, add, remove, replace, setAnd, setOr, toggleOperator, imp], ); return [ group?.children || [], group?.logicalOperator || "AND", methods, ] as const; } export function maybeReplaceIds( filter: PartialBy, ): T { if (!filter) return filter; if (isSingleFilter(filter as unknown as SingleFilter)) { return { ...filter, id: filter.id ?? nanoid(), } as T; } if (isGroupFilter(filter as unknown as GroupFilter)) { return { ...filter, id: filter.id ?? nanoid(), children: (filter as unknown as GroupFilter).children.map((c) => maybeReplaceIds(c), ), } as unknown as T; } return { ...filter, id: filter.id ?? nanoid(), } as T; } export function filterToGroup( filter?: QueryFilter, ): GroupFilter | SingleFilter { if (!filter) { return EMPTY_GROUP; } const { children, logicalOperator, ...newFilter } = filter; if (children?.length) { return { __type: "filterGroup", id: nanoid(), children: [newFilter, ...children.map(filterToGroup)], logicalOperator: logicalOperator ?? "AND", } as GroupFilter; } return { ...(newFilter as unknown as SingleFilter), __type: "filter", } as SingleFilter; } type Omit = Pick>; type PartialBy = Omit & Partial>; type RequiredBy = Omit & Required>;