import parseInt from 'lodash/parseInt';
import { ExpressionEvaluationError } from '../../parser/src/ExpressionEvaluationError';
import { handleColumnFunction, handleWhereFunction, } from './expressionFunctionUtils';
import { aggregatedScalarExpressionFunctions, } from './aggregatedScalarExpressionFunctions';
import { getTypedKeys } from '../Extensions/TypeExtensions';
const SUPPORTED_AGGREGATION_FNS = [
    'SUM',
    'MIN',
    'MAX',
    'AVG',
    'COUNT',
];
const aggregationScalarOperandMap = {
    SUM: ['string', 'number'],
    MIN: ['string', 'number'],
    MAX: ['string', 'number'],
    AVG: ['string', 'number'],
    COUNT: ['string', 'number'],
};
// 0. optional non-capturing block of empty spaces
// 1. block containing a numeric value (any digits) (required)
// 2. block containing a single 'K', 'M' or 'B' letter (optional) (case insensitive)
const CRITERIA_REGEX = /^(?:\s*)(\d+)(K|M|B)?$/i;
export const aggregatedBooleanExpressionFunctions = {
    WHERE: {
        handler(args, context) {
            return handleWhereFunction(args, context);
        },
        isHiddenFromMenu: true,
        description: 'Splits 2 composed queries, allowing for a main query(lhs) to have a where clause query(rhs) defined',
        signatures: ['<main_query> WHERE <boolean_query>'],
        examples: ['<main_query> WHERE <boolean_query>', '<main_query> WHERE QUERY("abc")'],
        hasEagerEvaluation: true,
        category: 'conditional',
    },
    SUM: aggregatedScalarExpressionFunctions['SUM'],
    MIN: aggregatedScalarExpressionFunctions['MIN'],
    MAX: aggregatedScalarExpressionFunctions['MAX'],
    AVG: aggregatedScalarExpressionFunctions['AVG'],
    WEIGHT: aggregatedScalarExpressionFunctions['WEIGHT'],
    COUNT: aggregatedScalarExpressionFunctions['COUNT'],
    GROUP_BY: aggregatedScalarExpressionFunctions['GROUP_BY'],
    EQ: {
        handler(args, context) {
            return buildBooleanAggregationParameter(args, context, '=', (aggregatedValue, conditionValue) => aggregatedValue === conditionValue);
        },
        isHiddenFromMenu: true,
        description: 'Evaluates if the aggregation result equals a numerical value defined either as a number or a string abbreviation for thousands(K), millions(M) or billions(B)',
        signatures: [
            'SUM() = number',
            `SUM() = '%number%(K|M|B)'`,
            'EQ(a: SUM(), b: number)',
            `EQ(a: SUM(), b: '%number%(K|M|B)')`,
        ],
        examples: [`SUM([col1]) = '5M'`, 'EQ( SUM([col1]), 250000)'],
        category: 'comparison',
    },
    NEQ: {
        handler(args, context) {
            return buildBooleanAggregationParameter(args, context, '!=', (aggregatedValue, conditionValue) => aggregatedValue !== conditionValue);
        },
        isHiddenFromMenu: true,
        description: 'Evaluates if the aggregation result is NOT equal with a numerical value defined either as a number or a string abbreviation for thousands(K), millions(M) or billions(B)',
        signatures: [
            'SUM() != number',
            `SUM() != '%number%(K|M|B)'`,
            'NEQ(a: SUM(), b: number)',
            `NEQ(a: SUM(), b: '%number%(K|M|B)')`,
        ],
        examples: [`SUM([col1]) != '5M'`, 'NEQ( SUM([col1]), 250000)'],
        category: 'comparison',
    },
    LT: {
        handler(args, context) {
            return buildBooleanAggregationParameter(args, context, '<', (aggregatedValue, conditionValue) => aggregatedValue < conditionValue);
        },
        isHiddenFromMenu: true,
        description: 'Evaluates if the aggregation result is less than a numerical value defined either as a number or a string abbreviation for thousands(K), millions(M) or billions(B)',
        signatures: [
            'SUM() < number',
            `SUM() < '%number%(K|M|B)'`,
            'LT(a: SUM(), b: number)',
            `LT(a: SUM(), b: '%number%(K|M|B)')`,
        ],
        examples: [`SUM([col1]) < '5M'`, 'LT( SUM([col1]), 250000)'],
        category: 'comparison',
    },
    LTE: {
        handler(args, context) {
            return buildBooleanAggregationParameter(args, context, '<=', (aggregatedValue, conditionValue) => aggregatedValue <= conditionValue);
        },
        isHiddenFromMenu: true,
        description: 'Evaluates if the aggregation result is less than or equals a numerical value defined either as a number or a string abbreviation for thousands(K), millions(M) or billions(B)',
        signatures: [
            'SUM() <= number',
            `SUM() <= '%number%(K|M|B)'`,
            'LTE(a: SUM(), b: number)',
            `LTE(a: SUM(), b: '%number%(K|M|B)')`,
        ],
        examples: [`SUM([col1]) <= '5M'`, 'LTE( SUM([col1]), 250000)'],
        category: 'comparison',
    },
    GT: {
        handler(args, context) {
            return buildBooleanAggregationParameter(args, context, '>', (aggregatedValue, conditionValue) => aggregatedValue > conditionValue);
        },
        isHiddenFromMenu: true,
        description: 'Evaluates if the aggregation result is greater than a numerical value defined either as a number or a string abbreviation for thousands(K), millions(M) or billions(B)',
        signatures: [
            'SUM() > number',
            `SUM() > '%number%(K|M|B)'`,
            'GT(a: SUM(), b: number)',
            `GT(a: SUM(), b: '%number%(K|M|B)')`,
        ],
        examples: [`SUM([col1]) > '5M'`, 'GT( SUM([col1]), 250000)'],
        category: 'comparison',
    },
    GTE: {
        handler(args, context) {
            return buildBooleanAggregationParameter(args, context, '>=', (aggregatedValue, conditionValue) => aggregatedValue >= conditionValue);
        },
        isHiddenFromMenu: true,
        description: 'Evaluates if the aggregation result is greater than or equals a numerical value defined either as a number or a string abbreviation for thousands(K), millions(M) or billions(B)',
        signatures: [
            'SUM() >= number',
            `SUM() >= '%number%(K|M|B)'`,
            'GTE(a: SUM(), b: number)',
            `GTE(a: SUM(), b: '%number%(K|M|B)')`,
        ],
        examples: [`SUM([col1]) >= '5M'`, 'GTE( SUM([col1]), 250000)'],
        category: 'comparison',
    },
    COL: {
        handler(args, context) {
            return handleColumnFunction(args, context);
        },
        description: 'References a column by its unique identifier',
        signatures: ['[colName]', 'COL(name: string)'],
        examples: ['[col1]', "COL('col1')"],
        category: 'special',
    },
};
const buildBooleanAggregationParameter = (args, context, comparisonOperator, conditionFn) => {
    // LHS
    const aggregationOperand = extractScalarAggregationOperand(comparisonOperator, args[0]);
    // RHS
    const scalarOperand = extractScalarOperand(comparisonOperator, args[1], aggregationScalarOperandMap[aggregationOperand.name]);
    return {
        type: 'aggregationBoolean',
        name: aggregationOperand.name,
        scalarAggregation: aggregationOperand,
        conditionValue: scalarOperand,
        conditionFn,
    };
};
const extractScalarAggregationOperand = (consumingFunctionName, argument) => {
    const result = argument;
    if (result == undefined ||
        result.type !== 'aggregationScalar' ||
        !SUPPORTED_AGGREGATION_FNS.includes(result.name)) {
        throw new ExpressionEvaluationError(consumingFunctionName, `comparison operator expects as a left-hand operand an aggregation function of type '${SUPPORTED_AGGREGATION_FNS.join(' | ')}'`);
    }
    return result;
};
const extractScalarOperand = (consumingFunctionName, value, allowedTypes) => {
    if (value == undefined || !allowedTypes.find((allowedType) => typeof value === allowedType)) {
        throw new ExpressionEvaluationError(consumingFunctionName, `comparison operator expects as a right-hand operand a scalar value of type '${allowedTypes.join(' | ')}'`);
    }
    if (typeof value === 'number') {
        return value;
    }
    // assertion is safe, we checked in the IF above
    const stringValue = value;
    const matchingResult = stringValue.match(CRITERIA_REGEX);
    const numericString = matchingResult?.[1];
    const largeNumberUnit = matchingResult?.[2] ?? 'number';
    if (!numericString) {
        throw new ExpressionEvaluationError('Numeric abbreviation', `"${stringValue}" is invalid, expecting a number with a suffix for thousands(K), millions(M) or billions(B)"`);
    }
    // unit -> number
    const numberUnitRatios = {
        number: 1,
        k: 1000,
        m: 1000000,
        b: 1000000000,
    };
    // numeric value
    return parseInt(numericString) * numberUnitRatios[largeNumberUnit.toLowerCase()];
};
export const aggregatedBooleanExpressionFunctionNames = getTypedKeys(aggregatedBooleanExpressionFunctions);
