import { isAfter } from 'date-fns';
import { isBefore } from 'date-fns';
import { isEqual } from 'date-fns';
import { ExpressionEvaluationError } from '../../parser/src/ExpressionEvaluationError';
import { getStringValue, getStringValues } from './expressionFunctionUtils';
import { getTypedKeys } from '../Extensions/TypeExtensions';
import { normalizeDateParam } from './dateUtils';
export const booleanExpressionFunctions = {
    TRUE: {
        handler: () => {
            return true;
        },
        description: 'Returns the boolean value TRUE',
        category: 'logical',
        returnType: 'boolean',
        signatures: ['TRUE'],
        examples: ['[column] = TRUE'],
    },
    FALSE: {
        handler: () => {
            return false;
        },
        description: 'Returns the boolean value FALSE',
        category: 'logical',
        returnType: 'boolean',
        signatures: ['FALSE'],
        examples: ['[column] = FALSE'],
    },
    AND: {
        handler(args) {
            return Boolean(args[0] && args[1]);
        },
        isHiddenFromMenu: true,
        category: 'logical',
        description: 'Returns true if both statements are true',
        signatures: ['statement AND statement'],
        examples: ['[col1] > 5 AND [col2] > 10'],
        returnType: 'boolean',
        inputs: ['boolean', 'boolean'],
    },
    OR: {
        handler(args) {
            return Boolean(args[0] || args[1]);
        },
        isHiddenFromMenu: true,
        category: 'logical',
        description: 'Returns true if either statement is true',
        signatures: ['statement OR statement'],
        examples: ['[col1] > 5 OR [col2] > 10'],
        returnType: 'boolean',
        inputs: ['boolean', 'boolean'],
    },
    NOT: {
        handler(args) {
            return Boolean(!args[0]);
        },
        isHiddenFromMenu: true,
        category: 'logical',
        description: 'Returns the negation of a statement',
        signatures: ['!statement'],
        examples: ['!([col1] > 5 AND [col2] > 10)'],
        returnType: 'boolean',
        inputs: ['boolean'],
    },
    EQ: {
        handler(args, context) {
            if (args[0] instanceof Date && args[1] instanceof Date) {
                return isEqual(args[0], args[1]);
            }
            if (typeof args[0] === 'string' || typeof args[1] === 'string') {
                const [first, second] = getStringValues(context, String(args[0]), String(args[1]));
                return first == second;
            }
            return args[0] == args[1];
        },
        isHiddenFromMenu: true,
        category: 'comparison',
        description: 'Returns true if the 2 (input) values are equal',
        signatures: ['value = value', 'EQ(a: value, b: value)'],
        examples: ['[col1] = 5', 'EQ([col1], 5)'],
        returnType: 'boolean',
        inputs: [
            ['number', 'number'],
            ['date', 'date'],
            ['text', 'text'],
            ['boolean', 'boolean'],
        ],
    },
    NEQ: {
        handler(args, context) {
            if (args[0] instanceof Date && args[1] instanceof Date) {
                return !isEqual(args[0], args[1]);
            }
            if (typeof args[0] === 'string' || typeof args[1] === 'string') {
                const [first, second] = getStringValues(context, String(args[0]), String(args[1]));
                return first != second;
            }
            return args[0] != args[1];
        },
        isHiddenFromMenu: true,
        category: 'comparison',
        description: 'Returns true if the 2 (input) values are NOT equal',
        signatures: ['value != value', 'NEQ(a: value, b: value)'],
        examples: ['[col1] != 5', 'NEQ([col1], 5)'],
        returnType: 'boolean',
        inputs: [
            ['number', 'number'],
            ['date', 'date'],
            ['text', 'text'],
            ['boolean', 'boolean'],
        ],
    },
    LT: {
        handler(args, context) {
            if (args[0] instanceof Date && args[1] instanceof Date) {
                return isBefore(args[0], args[1]);
            }
            if (typeof args[0] === 'string' || typeof args[1] === 'string') {
                const [first, second] = getStringValues(context, String(args[0]), String(args[1]));
                return first < second;
            }
            return args[0] < args[1];
        },
        isHiddenFromMenu: true,
        category: 'comparison',
        description: 'Returns true if the 1st input is less than 2nd input',
        signatures: [
            'number < number',
            'date < date',
            'LT(a: number, b: number)',
            'LT(a: date, b: date)',
        ],
        examples: ['[col1] < 5', 'LT([col1], 5)'],
        returnType: 'boolean',
        inputs: [
            ['number', 'number'],
            ['date', 'date'],
        ],
    },
    LTE: {
        handler(args, context) {
            if (args[0] instanceof Date && args[1] instanceof Date) {
                return isBefore(args[0], args[1]) || isEqual(args[0], args[1]);
            }
            if (typeof args[0] === 'string' || typeof args[1] === 'string') {
                const [first, second] = getStringValues(context, String(args[0]), String(args[1]));
                return first <= second;
            }
            return args[0] <= args[1];
        },
        isHiddenFromMenu: true,
        category: 'comparison',
        description: 'Returns true if 1st input is less than or equals 2nd input',
        signatures: [
            'number <= number',
            'date <= date',
            'LTE(a: number, b: number)',
            'LTE(a: date, b: date)',
        ],
        examples: ['[col1] <= 5', 'LTE([col1], 5)'],
        returnType: 'boolean',
        inputs: [
            ['number', 'number'],
            ['date', 'date'],
        ],
    },
    GT: {
        handler(args, context) {
            if (args[0] instanceof Date && args[1] instanceof Date) {
                return isAfter(args[0], args[1]);
            }
            if (typeof args[0] === 'string' || typeof args[1] === 'string') {
                const [first, second] = getStringValues(context, String(args[0]), String(args[1]));
                return first > second;
            }
            return args[0] > args[1];
        },
        isHiddenFromMenu: true,
        category: 'comparison',
        description: 'Returns true if the 1st input is greater than 2nd input',
        signatures: [
            'number > number',
            'date > date',
            'GT(a: number, b: number)',
            'GT(a: date, b: date)',
        ],
        examples: ['[col1] > 5', 'GT([col1], 5)'],
        returnType: 'boolean',
        inputs: [
            ['number', 'number'],
            ['date', 'date'],
        ],
    },
    GTE: {
        handler(args, context) {
            if (args[0] instanceof Date && args[1] instanceof Date) {
                return isAfter(args[0], args[1]) || isEqual(args[0], args[1]);
            }
            if (typeof args[0] === 'string' || typeof args[1] === 'string') {
                const [first, second] = getStringValues(context, String(args[0]), String(args[1]));
                return first >= second;
            }
            return args[0] >= args[1];
        },
        isHiddenFromMenu: true,
        category: 'comparison',
        description: 'Returns true if 1st input is greater than or equals 2nd input',
        signatures: [
            'number >= number',
            'date >= date',
            'GTE(a: number, b: number)',
            'GTE(a: date, b: date)',
        ],
        examples: ['[col1] >= 5', 'GTE([col1], 5)'],
        returnType: 'boolean',
        inputs: [
            ['number', 'number'],
            ['date', 'date'],
        ],
    },
    BETWEEN: {
        handler([input, lower, upper]) {
            if (typeof input !== 'number')
                throw new ExpressionEvaluationError('BETWEEN', 'arg 1 should be a number');
            return input >= lower && input <= upper;
        },
        category: 'comparison',
        description: 'Returns true if 1st input is between 2nd and 3rd inputs',
        signatures: ['BETWEEN(input: number, lower: number, upper: number)'],
        examples: ['BETWEEN([col1], 10, 30)'],
        returnType: 'boolean',
        inputs: ['number', 'number', 'number'],
    },
    IN: {
        handler([needle, haystack]) {
            return haystack.includes(needle);
        },
        category: 'comparison',
        description: 'Returns true if the left-hand side value is a constituent element of the right-hand side sequence',
        signatures: ['values IN (value, value, ...value)'],
        examples: ['[col1] IN (5, 10, AVG([col2],[col3]))'],
        returnType: 'boolean',
        inputs: [
            ['number', 'number[]'],
            // Date is currently not supported in select
            // ['date', 'date[]'],
            ['text', 'text[]'],
        ],
    },
    CONTAINS: {
        handler(args, context) {
            const [first, second] = getStringValues(context, String(args[0]), String(args[1]));
            return first.indexOf(second) !== -1;
        },
        category: 'strings',
        description: 'Returns true if 1st input contains 2nd input',
        signatures: ['CONTAINS(a: string, b: string)'],
        examples: ['CONTAINS([col1], "S")'],
        returnType: 'boolean',
        inputs: ['text', 'text'],
    },
    STARTS_WITH: {
        handler(args, context) {
            const [first, second] = getStringValues(context, String(args[0]), String(args[1]));
            return first.startsWith(second);
        },
        category: 'strings',
        description: 'Returns true if 1st input starts with the 2nd input',
        signatures: ['STARTS_WITH(a: string, b: string)'],
        examples: ['STARTS_WITH([col1], "S")'],
        returnType: 'boolean',
        inputs: ['text', 'text'],
    },
    ENDS_WITH: {
        handler(args, context) {
            const [first, second] = getStringValues(context, String(args[0]), String(args[1]));
            return first.endsWith(second);
        },
        category: 'strings',
        description: 'Returns true if 1st input ends with the 2nd input',
        signatures: ['ENDS_WITH(a: string, b: string)'],
        examples: ['ENDS_WITH([col1], "S")'],
        returnType: 'boolean',
        inputs: ['text', 'text'],
    },
    ANY_CONTAINS: {
        handler(args, context) {
            const searchTerm = getStringValue(context, String(args[0]));
            return context.adaptableApi?.columnApi.getColumns().some((column) => {
                const value = context.adaptableApi?.gridApi.getDisplayValueFromRowNode(context.node, column.columnId);
                const columnValue = getStringValue(context, String(value));
                return columnValue.indexOf(searchTerm) !== -1;
            });
        },
        category: 'strings',
        description: 'Returns true if any Column contains input',
        signatures: ['ANY_CONTAINS(value)'],
        examples: ['ANY_CONTAINS("abc")'],
        returnType: 'boolean',
    },
    IS_NUMERIC: {
        handler(args) {
            return !isNaN(Number(args[0]));
        },
        isHiddenFromMenu: false,
        description: 'Returns true if input is numeric',
        signatures: ['IS_NUMERIC(a: number)'],
        examples: ['IS_NUMERIC([columnName])'],
        category: 'maths',
        returnType: 'boolean',
        inputs: ['number'],
    },
    REGEX: {
        handler(args) {
            // dont need context as here we always want case insenstivity in the Regex
            const firstInput = String(args[0]);
            const regex = String(args[1]);
            return RegExp(regex).test(firstInput);
        },
        isHiddenFromMenu: false,
        description: 'Returns true if Regular Expression matches',
        signatures: ['REGEX(a: string, b: pattern)'],
        examples: ['REGEX([col1], "[A-Z]+")'],
        inputs: ['text', 'text'],
        category: 'strings',
        returnType: 'boolean',
    },
    IS_HOLIDAY: {
        handler(args, context) {
            const dateToCheck = normalizeDateParam(args[0]);
            return context.adaptableApi.calendarApi.isHoliday(dateToCheck);
        },
        isHiddenFromMenu: false,
        description: 'Returns true if input date is a Holiday',
        signatures: ['IS_HOLIDAY(a: date)'],
        examples: ['IS_HOLIDAY([columnName])'],
        category: 'dates',
        returnType: 'boolean',
        inputs: ['date'],
    },
    IS_WORKDAY: {
        handler(args, context) {
            const dateToCheck = normalizeDateParam(args[0]);
            return context.adaptableApi.calendarApi.isWorkingDay(dateToCheck);
        },
        isHiddenFromMenu: false,
        description: 'Returns true if input date is a Working Day',
        signatures: ['IS_WORKDAY(a: date)'],
        examples: ['IS_WORKDAY([columnName])'],
        category: 'dates',
        returnType: 'boolean',
        inputs: ['date'],
    },
};
export const booleanExpressionFunctionsNames = getTypedKeys(booleanExpressionFunctions);
export const isBooleanAdaptableQlFunction = (functionName) => {
    return booleanExpressionFunctionsNames.includes(functionName);
};
