import { isAfter } from 'date-fns';
import { isBefore } from 'date-fns';
import { isEqual } from 'date-fns';
import { isFuture } from 'date-fns';
import { isPast } from 'date-fns';
import { isSameDay } from 'date-fns';
import { isThisMonth } from 'date-fns';
import { isThisQuarter } from 'date-fns';
import { isThisWeek } from 'date-fns';
import { isThisYear } from 'date-fns';
import { isToday } from 'date-fns';
import { isTomorrow } from 'date-fns';
import { isYesterday } from 'date-fns';
import StringExtensions from '../../Utilities/Extensions/StringExtensions';
import { parseDateValue } from '../../Utilities/Helpers/DateHelper';
import Helper from '../../Utilities/Helpers/Helper';
/**
 * Array of Predicate Defs which are shipped by AdapTable
 */
export const SystemPredicateDefs = [
    {
        id: 'In',
        label: 'In',
        icon: { text: 'IN' },
        columnScope: { DataTypes: ['text', 'number', 'date', 'textArray', 'numberArray'] },
        moduleScope: ['columnFilter', 'flashingcell', 'formatColumn', 'alert', 'badgeStyle'],
        handler: (context) => {
            const { inputs = [], column, value, adaptableApi } = context;
            const predicateInputs = (context.inputs || [])
                .map((input) => {
                const predicateInput = adaptableApi.predicateApi.getPredicateDefById(input);
                if (!predicateInput) {
                    return;
                }
                if (adaptableApi.columnScopeApi.isScopeInScope(predicateInput.columnScope, 
                // 'In' ColumnScope
                {
                    DataTypes: ['text', 'number', 'date', 'textArray', 'numberArray'],
                })) {
                    return predicateInput;
                }
            })
                .filter(Boolean);
            if (predicateInputs.length) {
                const nestedContext = { ...context };
                delete nestedContext.inputs;
                const predicateResult = predicateInputs.some((predicate) => {
                    return adaptableApi.predicateApi.handlePredicate({
                        PredicateId: predicate.id,
                    }, nestedContext, false);
                });
                if (predicateResult) {
                    // We want to use the only true values, to allow for multiple predicates to be used
                    return predicateResult;
                }
            }
            if (inputs.length === 0) {
                return true;
            }
            if (column.dataType === 'date') {
                return inputs.some((input) => {
                    if (adaptableApi.optionsApi.getPredicateOptions().evaluateInPredicateUsingTime) {
                        return isEqual(input, value);
                    }
                    else {
                        return isSameDay(parseDateValue(input), parseDateValue(value));
                    }
                });
            }
            if (column.dataType === 'number') {
                return inputs.includes(value);
            }
            if (column.dataType === 'numberArray') {
                const arrayValue = Array.isArray(value) ? value : [value];
                return inputs.some((input) => {
                    return arrayValue.includes(input);
                });
            }
            const useCaseSensitivity = adaptableApi.predicateApi.useCaseSensitivity();
            if (column.dataType === 'text') {
                return useCaseSensitivity
                    ? inputs
                        .map((i) => {
                        return i?.toLocaleLowerCase();
                    })
                        .includes(value?.toLocaleLowerCase())
                    : inputs.includes(value);
            }
            if (column.dataType === 'textArray') {
                const arrayValue = Array.isArray(value) ? value : [value];
                return inputs.some((input) => {
                    return useCaseSensitivity
                        ? arrayValue.map((v) => v?.toLocaleLowerCase()).includes(input?.toLocaleLowerCase())
                        : arrayValue.includes(input);
                });
            }
            return true;
        },
        toString: ({ inputs }) => `IN (${inputs.join(', ')})`,
        shortcuts: ['#', '['],
    },
    {
        id: 'NotIn',
        label: 'Not In',
        icon: { text: '!IN' },
        columnScope: { DataTypes: ['text', 'number', 'date', 'textArray', 'numberArray'] },
        moduleScope: ['columnFilter', 'flashingcell', 'formatColumn', 'alert', 'badgeStyle'],
        handler: (context) => {
            const { inputs, column, value, adaptableApi } = context;
            const predicateInputs = (context.inputs || [])
                .map((input) => {
                const predicateInput = adaptableApi.predicateApi.getPredicateDefById(input);
                if (!predicateInput) {
                    return;
                }
                if (adaptableApi.columnScopeApi.isScopeInScope(predicateInput.columnScope, 
                // 'NotIn' ColumnScope
                {
                    DataTypes: ['text', 'number', 'date', 'textArray', 'numberArray'],
                })) {
                    return predicateInput;
                }
            })
                .filter(Boolean);
            if (predicateInputs.length) {
                const nestedContext = { ...context };
                delete nestedContext.inputs;
                const predicateResult = predicateInputs.every((predicate) => {
                    const res = adaptableApi.predicateApi.handlePredicate({
                        PredicateId: predicate.id,
                    }, nestedContext, false);
                    return res === false;
                });
                if (!predicateResult) {
                    // We want to use the only true values, to allow for multiple predicates to be used
                    return false;
                }
            }
            // basically negation of IN
            if (inputs.length === 0) {
                return true;
            }
            if (column.dataType === 'date') {
                if (adaptableApi.optionsApi.getPredicateOptions().evaluateInPredicateUsingTime) {
                    return inputs.every((input) => {
                        return !isEqual(input, value);
                    });
                }
                else {
                    return inputs.every((input) => {
                        return !isSameDay(parseDateValue(input), parseDateValue(value));
                    });
                }
            }
            if (column.dataType === 'number') {
                return !inputs.includes(value);
            }
            if (column.dataType === 'numberArray') {
                const arrayValue = Array.isArray(value) ? value : [value];
                return inputs.every((input) => {
                    return !arrayValue.includes(input);
                });
            }
            if (column.dataType === 'text') {
                return adaptableApi.predicateApi.useCaseSensitivity()
                    ? !inputs
                        .map((i) => {
                        return i?.toLocaleLowerCase();
                    })
                        .includes(value?.toLocaleLowerCase())
                    : !inputs.includes(value);
            }
            if (column.dataType === 'textArray') {
                const arrayValue = Array.isArray(value) ? value : [value];
                return inputs.every((input) => {
                    return adaptableApi.predicateApi.useCaseSensitivity()
                        ? !arrayValue.map((v) => v?.toLocaleLowerCase()).includes(input?.toLocaleLowerCase())
                        : !arrayValue.includes(input);
                });
            }
            return true;
        },
        toString: ({ inputs }) => `Exclude (${inputs.join(', ')})`,
    },
    {
        id: 'Blanks',
        label: 'Blanks',
        icon: { name: 'unfilled-circle' },
        columnScope: { All: true },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn', 'badgeStyle'],
        handler: ({ value, column, adaptableApi }) => {
            if (adaptableApi.columnApi.internalApi.hasArrayDataType(column)) {
                return value == undefined || value.length === 0;
            }
            return Helper.isInputNullOrEmpty(value);
        },
    },
    {
        id: 'NonBlanks',
        label: 'Non Blanks',
        icon: { name: 'filled-circle' },
        columnScope: { All: true },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn', 'badgeStyle'],
        handler: ({ value, column, adaptableApi }) => {
            if (adaptableApi.columnApi.internalApi.hasArrayDataType(column)) {
                return value == undefined || value.length === 0;
            }
            return Helper.isInputNotNullOrEmpty(value);
        },
    },
    // Numeric System Filters
    {
        id: 'GreaterThan',
        label: 'Greater Than',
        icon: { name: 'greater-than' },
        columnScope: { DataTypes: ['number'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn', 'badgeStyle'],
        inputs: [{ type: 'number' }],
        handler: ({ value, inputs }) => Number(value) > Number(inputs[0]),
        toString: ({ inputs }) => `> ${inputs[0] ?? ''}`,
        shortcuts: ['>'],
    },
    {
        id: 'LessThan',
        label: 'Less Than',
        icon: { name: 'less-than' },
        columnScope: { DataTypes: ['number'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn', 'badgeStyle'],
        inputs: [{ type: 'number' }],
        handler: ({ value, inputs }) => Number(value) < Number(inputs[0]),
        toString: ({ inputs }) => `< ${inputs[0] ?? ''}`,
        shortcuts: ['<'],
    },
    {
        id: 'Positive',
        label: 'Positive',
        icon: { text: '>0' },
        columnScope: { DataTypes: ['number'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn', 'badgeStyle'],
        handler: ({ value }) => Number(value) > 0,
    },
    {
        id: 'Negative',
        label: 'Negative',
        icon: { text: '<0' },
        columnScope: { DataTypes: ['number'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn', 'badgeStyle'],
        handler: ({ value }) => Number(value) < 0,
    },
    {
        id: 'Zero',
        label: 'Zero',
        icon: { text: '=0' },
        columnScope: { DataTypes: ['number'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn', 'badgeStyle'],
        handler: ({ value }) => {
            if (typeof value === 'string' && !StringExtensions.IsNumeric(value)) {
                return false;
            }
            if (Helper.objectNotExists(value)) {
                return false;
            }
            return Number(value) === 0;
        },
    },
    {
        id: 'Equals',
        label: 'Equals',
        icon: { name: 'equals' },
        columnScope: { DataTypes: ['number'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn', 'badgeStyle'],
        inputs: [{ type: 'number' }],
        handler: ({ value, inputs }) => {
            const input = inputs[0];
            if ((typeof value === 'string' && !StringExtensions.IsNumeric(value)) ||
                (typeof input === 'string' && !StringExtensions.IsNumeric(input))) {
                return false;
            }
            return Number(value) === Number(inputs[0]);
        },
        toString: ({ inputs }) => `= ${inputs[0] ?? ''}`,
        shortcuts: ['='],
    },
    {
        id: 'NotEquals',
        label: 'Not Equals',
        icon: { name: 'not-equal' },
        columnScope: { DataTypes: ['number'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn', 'badgeStyle'],
        inputs: [{ type: 'number' }],
        handler: ({ value, inputs }) => {
            const input = inputs[0];
            if ((typeof value === 'string' && !StringExtensions.IsNumeric(value)) ||
                (typeof input === 'string' && !StringExtensions.IsNumeric(input))) {
                return false;
            }
            return Number(value) !== Number(inputs[0]);
        },
        toString: ({ inputs }) => `!= ${inputs[0] ?? ''}`,
        shortcuts: ['!='],
    },
    {
        id: 'Between',
        label: 'Between',
        icon: { text: 'BE' },
        columnScope: { DataTypes: ['number'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn', 'badgeStyle'],
        inputs: [{ type: 'number' }, { type: 'number' }],
        handler: ({ value, inputs }) => Number(value) >= Number(inputs[0]) && Number(value) <= Number(inputs[1]),
        toString: ({ inputs }) => `Between ${inputs[0] ?? ''}:${inputs[1]}`,
        shortcuts: [':'],
    },
    {
        id: 'NotBetween',
        label: 'Not Between',
        icon: { text: '!BE' },
        columnScope: { DataTypes: ['number'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn', 'badgeStyle'],
        inputs: [{ type: 'number' }, { type: 'number' }],
        handler: ({ value, inputs }) => Number(value) < Number(inputs[0]) || Number(value) > Number(inputs[1]),
        toString: ({ inputs }) => `Not Between ${inputs[0] ?? ''}:${inputs[1]}`,
        shortcuts: ['!:'],
    },
    // String System Filters
    {
        id: 'Is',
        label: 'Equals',
        icon: { name: 'equals' },
        columnScope: { DataTypes: ['text'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn', 'badgeStyle'],
        inputs: [{ type: 'text' }],
        handler: ({ value, inputs, adaptableApi }) => {
            if (!value) {
                return false;
            }
            const ignoreCase = !adaptableApi.predicateApi.useCaseSensitivity();
            const v = ignoreCase ? String(value).toLocaleLowerCase() : String(value);
            const i = ignoreCase ? String(inputs[0]).toLocaleLowerCase() : String(inputs[0]);
            return v == i;
        },
        toString: ({ inputs }) => `= ${inputs[0] ?? ''}`,
        shortcuts: ['='],
    },
    {
        id: 'IsNot',
        label: 'Not Equals',
        icon: { name: 'not-equal' },
        columnScope: { DataTypes: ['text'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn', 'badgeStyle'],
        inputs: [{ type: 'text' }],
        handler: ({ value, inputs, adaptableApi }) => {
            if (!value) {
                return true;
            }
            const ignoreCase = !adaptableApi.predicateApi.useCaseSensitivity();
            const v = ignoreCase ? String(value).toLocaleLowerCase() : String(value);
            const i = ignoreCase ? String(inputs[0]).toLocaleLowerCase() : String(inputs[0]);
            return v != i;
        },
        toString: ({ inputs }) => `!= ${inputs[0] ?? ''}`,
        shortcuts: ['!='],
    },
    {
        id: 'Contains',
        label: 'Contains',
        icon: { name: 'contains' },
        columnScope: { DataTypes: ['text'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn', 'badgeStyle'],
        inputs: [{ type: 'text' }],
        handler: ({ value, inputs, adaptableApi }) => {
            if (!value) {
                return false;
            }
            const ignoreCase = !adaptableApi.predicateApi.useCaseSensitivity();
            const v = ignoreCase ? String(value).toLocaleLowerCase() : String(value);
            const i = ignoreCase ? String(inputs[0]).toLocaleLowerCase() : String(inputs[0]);
            return v.indexOf(i) !== -1;
        },
        toString: ({ inputs }) => `Contains ${inputs[0] ?? ''}`,
    },
    {
        id: 'NotContains',
        label: 'Not Contains',
        icon: { name: 'not-contains' },
        columnScope: { DataTypes: ['text'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn', 'badgeStyle'],
        inputs: [{ type: 'text' }],
        handler: ({ value, inputs, adaptableApi }) => {
            if (!value) {
                return true;
            }
            const ignoreCase = !adaptableApi.predicateApi.useCaseSensitivity();
            const v = ignoreCase ? String(value).toLocaleLowerCase() : String(value);
            const i = ignoreCase ? String(inputs[0]).toLocaleLowerCase() : String(inputs[0]);
            return v.indexOf(i) === -1;
        },
        toString: ({ inputs }) => `Not Contains ${inputs[0] ?? ''}`,
    },
    {
        id: 'StartsWith',
        label: 'Starts With',
        icon: { name: 'starts-with' },
        columnScope: { DataTypes: ['text'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn', 'badgeStyle'],
        inputs: [{ type: 'text' }],
        handler: ({ value, inputs, adaptableApi }) => {
            if (!value) {
                return false;
            }
            const ignoreCase = !adaptableApi.predicateApi.useCaseSensitivity();
            const v = ignoreCase ? String(value).toLocaleLowerCase() : String(value);
            const i = ignoreCase ? String(inputs[0]).toLocaleLowerCase() : String(inputs[0]);
            return v.startsWith(i);
        },
        toString: ({ inputs }) => `Starts With ${inputs[0] ?? ''}`,
    },
    {
        id: 'EndsWith',
        label: 'Ends With',
        icon: { name: 'ends-with' },
        columnScope: { DataTypes: ['text'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn', 'badgeStyle'],
        inputs: [{ type: 'text' }],
        handler: ({ value, inputs, adaptableApi }) => {
            if (!value) {
                return false;
            }
            const ignoreCase = !adaptableApi.predicateApi.useCaseSensitivity();
            const v = ignoreCase ? String(value).toLocaleLowerCase() : String(value);
            const i = ignoreCase ? String(inputs[0]).toLocaleLowerCase() : String(inputs[0]);
            return v.endsWith(i);
        },
        toString: ({ inputs }) => `Ends With ${inputs[0] ?? ''}`,
    },
    {
        id: 'Regex',
        label: 'Regex',
        icon: { name: 'regex' },
        columnScope: { DataTypes: ['text'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn', 'badgeStyle'],
        inputs: [{ type: 'text' }],
        handler: ({ value, inputs }) => new RegExp(inputs[0]).test(value),
        toString: ({ inputs }) => `Regex ${inputs[0] ?? ''}`,
    },
    // Date System Filters
    {
        id: 'Today',
        label: 'Today',
        icon: { name: 'calendar' },
        columnScope: { DataTypes: ['date'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn'],
        handler: ({ value }) => isToday(parseDateValue(value)),
    },
    {
        id: 'Yesterday',
        label: 'Yesterday',
        icon: { name: 'calendar' },
        columnScope: { DataTypes: ['date'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn'],
        handler: ({ value }) => isYesterday(parseDateValue(value)),
    },
    {
        id: 'Tomorrow',
        label: 'Tomorrow',
        icon: { name: 'calendar' },
        columnScope: { DataTypes: ['date'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn'],
        handler: ({ value }) => isTomorrow(parseDateValue(value)),
    },
    {
        id: 'ThisWeek',
        label: 'This Week',
        icon: { name: 'calendar' },
        columnScope: { DataTypes: ['date'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn'],
        handler: ({ value }) => isThisWeek(parseDateValue(value)),
    },
    {
        id: 'ThisMonth',
        label: 'This Month',
        icon: { name: 'calendar' },
        columnScope: { DataTypes: ['date'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn'],
        handler: ({ value }) => isThisMonth(parseDateValue(value)),
    },
    {
        id: 'ThisQuarter',
        label: 'This Quarter',
        icon: { name: 'calendar' },
        columnScope: { DataTypes: ['date'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn'],
        handler: ({ value }) => isThisQuarter(parseDateValue(value)),
    },
    {
        id: 'ThisYear',
        label: 'This Year',
        icon: { name: 'calendar' },
        columnScope: { DataTypes: ['date'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn'],
        handler: ({ value }) => isThisYear(parseDateValue(value)),
    },
    {
        id: 'InPast',
        label: 'In Past',
        icon: { name: 'calendar' },
        columnScope: { DataTypes: ['date'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn'],
        handler: ({ value }) => isPast(parseDateValue(value)),
    },
    {
        id: 'InFuture',
        label: 'In Future',
        icon: { name: 'calendar' },
        columnScope: { DataTypes: ['date'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn'],
        handler: ({ value }) => isFuture(parseDateValue(value)),
    },
    {
        id: 'After',
        label: 'After',
        icon: { name: 'greater-than' },
        columnScope: { DataTypes: ['date'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn'],
        inputs: [{ type: 'date' }],
        handler: ({ value, inputs }) => isAfter(parseDateValue(value), parseDateValue(inputs[0])),
        toString: ({ inputs }) => `> ${inputs[0] ?? ''}`,
    },
    {
        id: 'Before',
        label: 'Before',
        icon: { name: 'less-than' },
        columnScope: { DataTypes: ['date'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn'],
        inputs: [{ type: 'date' }],
        handler: ({ value, inputs }) => isBefore(parseDateValue(value), parseDateValue(inputs[0])),
        toString: ({ inputs }) => `< ${inputs[0] ?? ''}`,
    },
    {
        id: 'On',
        label: 'Equals',
        icon: { name: 'equals' },
        columnScope: { DataTypes: ['date'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn'],
        inputs: [{ type: 'date' }],
        handler: ({ value, inputs }) => isSameDay(parseDateValue(value), parseDateValue(inputs[0])),
        toString: ({ inputs }) => `= ${inputs[0] ?? ''}`,
    },
    {
        id: 'NotOn',
        label: 'NotEquals',
        icon: { name: 'not-equal' },
        columnScope: { DataTypes: ['date'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn'],
        inputs: [{ type: 'date' }],
        handler: ({ value, inputs }) => !isSameDay(parseDateValue(value), parseDateValue(inputs[0])),
        toString: ({ inputs }) => `!= ${inputs[0] ?? ''}`,
    },
    {
        id: 'NextWorkDay',
        label: 'Next Work Day',
        icon: { name: 'calendar' },
        columnScope: { DataTypes: ['date'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn'],
        handler: ({ value, adaptableApi }) => isSameDay(parseDateValue(value), adaptableApi.calendarApi.getNextWorkingDay()),
    },
    {
        id: 'LastWorkDay',
        label: 'Last Work Day',
        icon: { name: 'calendar' },
        columnScope: { DataTypes: ['date'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn'],
        handler: ({ value, adaptableApi }) => isSameDay(parseDateValue(value), adaptableApi.calendarApi.getPreviousWorkingDay()),
    },
    {
        id: 'WorkDay',
        label: 'Working Day',
        icon: { name: 'calendar' },
        columnScope: { DataTypes: ['date'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn'],
        handler: ({ value, adaptableApi }) => adaptableApi.calendarApi.isWorkingDay(value),
    },
    {
        id: 'Holiday',
        label: 'Holiday',
        icon: { name: 'calendar' },
        columnScope: { DataTypes: ['date'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn'],
        handler: ({ value, adaptableApi }) => adaptableApi.calendarApi.isHoliday(value),
    },
    {
        id: 'InRange',
        label: 'Range',
        icon: { name: 'date-range' },
        columnScope: { DataTypes: ['date'] },
        moduleScope: ['columnFilter'],
        inputs: [{ type: 'date' }, { type: 'date' }],
        handler: ({ value, inputs }) => new Date(value) >= new Date(inputs[0]) && new Date(value) <= new Date(inputs[1]),
    },
    // Boolean System Filters
    {
        id: 'True',
        label: 'True',
        icon: { text: 'T' },
        columnScope: { DataTypes: ['boolean'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn'],
        handler: ({ value }) => Boolean(value) === true,
    },
    {
        id: 'False',
        label: 'False',
        icon: { text: 'F' },
        columnScope: { DataTypes: ['boolean'] },
        moduleScope: ['columnFilter', 'alert', 'flashingcell', 'formatColumn'],
        handler: ({ value }) => Boolean(value) === false,
    },
    {
        id: 'BooleanToggle',
        label: 'BooleanToggle',
        icon: { name: 'boolean-list' },
        columnScope: { DataTypes: ['boolean'] },
        moduleScope: ['columnFilter'],
        inputs: [{ type: 'boolean' }],
        // working with string aliases instead of booleans because the ColumnFilterAPI cannot handle falsy filter values (it ignores them)
        handler: ({ value, inputs }) => {
            if (inputs[0] == 'all') {
                // if it's indeterminate, all values (true&false) are matched
                return true;
            }
            const booleanInputValue = inputs[0] === 'checked' ? true : false;
            return Boolean(value) === Boolean(booleanInputValue);
        },
    },
    // Other System Filters
    {
        id: 'AnyChange',
        label: 'Any Change',
        columnScope: { All: true },
        moduleScope: ['alert', 'flashingcell'],
        handler: ({ value, oldValue }) => value !== oldValue,
    },
    {
        id: 'PercentChange',
        label: 'Percent Change',
        columnScope: { DataTypes: ['number'] },
        moduleScope: ['alert', 'flashingcell'],
        inputs: [{ type: 'number' }],
        handler: ({ value, oldValue, inputs }) => {
            const change = Math.abs(Number(value) - Number(oldValue));
            const base = Math.min(Number(value), Number(oldValue));
            const threshold = Number(inputs[0]);
            return (change / base) * 100 > threshold;
        },
    },
];
export const SystemFilterPredicateIds = SystemPredicateDefs.filter((p) => p.moduleScope.includes('columnFilter')).map((p) => p.id);
export const SystemAlertPredicateIds = SystemPredicateDefs.filter((p) => p.moduleScope.includes('alert')).map((p) => p.id);
export const SystemFormatColumnPredicateIds = SystemPredicateDefs.filter((p) => p.moduleScope.includes('formatColumn')).map((p) => p.id);
export const SystemFlashingCellPredicateIds = SystemPredicateDefs.filter((p) => p.moduleScope.includes('flashingcell')).map((p) => p.id);
export const SystemBadgeStylePredicateIds = SystemPredicateDefs.filter((p) => p.moduleScope.includes('badgeStyle')).map((p) => p.id);
