/**
 * Copyright (c) 2020-present, Goldman Sachs
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { extractElementNameFromPath, matchFunctionName, LambdaFunctionInstanceValue, AbstractPropertyExpression, SimpleFunctionExpression, LambdaFunction, FunctionExpression, RelationColumn, Multiplicity, VariableExpression, } from '@finos/legend-graph';
import { guaranteeType, assertTrue } from '@finos/legend-shared';
import { FilterConditionState, FilterPropertyExpressionStateConditionValueState, FilterRelationColumnConditionValueState, FilterRelationColumnSourceState, FilterValueSpecConditionValueState, } from '../QueryBuilderFilterState.js';
import { QUERY_BUILDER_SUPPORTED_FUNCTIONS } from '../../../graph/QueryBuilderMetaModelConst.js';
import { simplifyValueExpression } from '../../QueryBuilderValueSpecificationHelper.js';
import { buildPropertyExpressionChain } from '../../QueryBuilderValueSpecificationBuilderHelper.js';
import { QueryBuilderPropertyExpressionState } from '../../QueryBuilderPropertyEditorState.js';
export const buildFilterConditionExpression = (filterConditionState, operatorFunctionFullPath, lambdaParameterName) => {
    const expression = new SimpleFunctionExpression(extractElementNameFromPath(operatorFunctionFullPath));
    const resolvedLambdaParameterName = lambdaParameterName ?? filterConditionState.filterState.lambdaParameterName;
    const leftExpression = filterConditionState.sourceState.buildLeftExpression(filterConditionState.filterState.queryBuilderState, resolvedLambdaParameterName);
    expression.parametersValues.push(leftExpression);
    // NOTE: there are simple operators which do not require any params (e.g. isEmpty)
    if (filterConditionState.rightConditionValue &&
        filterConditionState.rightConditionValue instanceof
            FilterValueSpecConditionValueState &&
        filterConditionState.rightConditionValue.value !== undefined) {
        expression.parametersValues.push(filterConditionState.rightConditionValue.value);
    }
    else if (filterConditionState.rightConditionValue &&
        filterConditionState.rightConditionValue instanceof
            FilterPropertyExpressionStateConditionValueState) {
        const rightConditionPropertyExpression = buildPropertyExpressionChain(filterConditionState.rightConditionValue.propertyExpressionState
            .propertyExpression, filterConditionState.filterState.queryBuilderState, resolvedLambdaParameterName);
        expression.parametersValues.push(rightConditionPropertyExpression);
    }
    else if (filterConditionState.rightConditionValue &&
        filterConditionState.rightConditionValue instanceof
            FilterRelationColumnConditionValueState) {
        const rightValue = filterConditionState.rightConditionValue;
        const relationType = filterConditionState.filterState.queryBuilderState.sourceRelationType;
        if (!relationType) {
            throw new Error(`Can't build expression for relation column '${rightValue.columnName}': no source relation type`);
        }
        const col = relationType.columns.find((c) => c.name === rightValue.columnName);
        if (!col) {
            throw new Error(`Can't find column '${rightValue.columnName}' in relation`);
        }
        const colFuncExp = new FunctionExpression(col.name);
        colFuncExp.func = col;
        colFuncExp.parametersValues = [
            new VariableExpression(resolvedLambdaParameterName, Multiplicity.ONE),
        ];
        expression.parametersValues.push(colFuncExp);
    }
    return expression;
};
/**
 * Extracts the raw type path from the left-hand side of a filter expression.
 * Handles both AbstractPropertyExpression (class properties) and
 * FunctionExpression with RelationColumn (relation column properties).
 */
export const getFilterExpressionLeftSideTypePath = (expression) => {
    const leftSide = expression.parametersValues[0];
    if (leftSide instanceof AbstractPropertyExpression) {
        return leftSide.func.value.genericType.value.rawType.path;
    }
    if (leftSide instanceof FunctionExpression &&
        leftSide.func instanceof RelationColumn) {
        return leftSide.func.genericType.value.rawType.path;
    }
    return undefined;
};
export const buildFilterConditionState = (filterState, expression, operatorFunctionFullPath, operator, 
/**
 * Use this flag for operator that does not require any param (e.g. isEmpty)
 * NOTE: this is not the cleanest way to do this, if we find ourselves adding more and more customization
 * to this utility function, we should just create a bunch of different methods
 */
hasNoValue = false) => {
    let filterConditionState;
    // This is the simple expression of form `{property} {operator} {value}`
    // This is used for post-build checks (useful when this expression is nested inside a longer
    // chain of expression, e.g. ->exists($x|x == 'something'), etc.)
    let mainExpressionWithOperator;
    if (matchFunctionName(expression.functionName, operatorFunctionFullPath)) {
        assertTrue(expression.parametersValues.length === (hasNoValue ? 1 : 2), `Can't process ${extractElementNameFromPath(operatorFunctionFullPath)}() expression: ${extractElementNameFromPath(operatorFunctionFullPath)}() expects ${hasNoValue ? 'no argument' : '1 argument'}`);
        const leftSide = expression.parametersValues[0];
        if (leftSide instanceof AbstractPropertyExpression) {
            filterConditionState = new FilterConditionState(filterState, leftSide);
        }
        else if (leftSide instanceof FunctionExpression &&
            leftSide.func instanceof RelationColumn) {
            const col = leftSide.func;
            filterConditionState = new FilterConditionState(filterState, new FilterRelationColumnSourceState(col.name, col.genericType.value.rawType, col.multiplicity));
        }
        else {
            throw new Error(`Can't process ${extractElementNameFromPath(operatorFunctionFullPath)}() expression: expects property expression or relation column in lambda body`);
        }
        mainExpressionWithOperator = expression;
    }
    else if (matchFunctionName(expression.functionName, QUERY_BUILDER_SUPPORTED_FUNCTIONS.EXISTS)) {
        const lambdaFunctionInstance = guaranteeType(expression.parametersValues[1], LambdaFunctionInstanceValue);
        const lambdaFunction = guaranteeType(lambdaFunctionInstance.values[0], LambdaFunction);
        const filterExpression = guaranteeType(lambdaFunction.expressionSequence[0], SimpleFunctionExpression);
        assertTrue(filterExpression.parametersValues.length === (hasNoValue ? 1 : 2), `Can't process ${extractElementNameFromPath(operatorFunctionFullPath)}() expression: ${extractElementNameFromPath(operatorFunctionFullPath)}() expects ${hasNoValue ? 'no argument' : '1 argument'}`);
        const propertyExpression = guaranteeType(filterExpression.parametersValues[0], AbstractPropertyExpression, `Can't process ${extractElementNameFromPath(operatorFunctionFullPath)}() expression: expects property expression in lambda body`);
        filterConditionState = new FilterConditionState(filterState, propertyExpression);
        mainExpressionWithOperator = filterExpression;
    }
    // Post-build check: make sure the simple filter condition LHS, RHS, and operator are compatible
    // and set the value of the condition in the state accordingly.
    if (filterConditionState && mainExpressionWithOperator) {
        assertTrue(operator.isCompatibleWithFilterConditionProperty(filterConditionState), `Can't process ${extractElementNameFromPath(operatorFunctionFullPath)}() expression: property is not compatible with operator`);
        filterConditionState.setOperator(operator);
        // value
        const value = mainExpressionWithOperator.parametersValues[1];
        if (hasNoValue || !value) {
            filterConditionState.setRightConditionValue(undefined);
        }
        else if (value instanceof AbstractPropertyExpression) {
            filterConditionState.setRightConditionValue(new FilterPropertyExpressionStateConditionValueState(filterConditionState, new QueryBuilderPropertyExpressionState(filterState.queryBuilderState, value)));
        }
        else if (value instanceof FunctionExpression &&
            value.func instanceof RelationColumn) {
            const col = value.func;
            filterConditionState.setRightConditionValue(new FilterRelationColumnConditionValueState(filterConditionState, col.name, col.genericType.value.rawType, col.multiplicity));
        }
        else {
            filterConditionState.setRightConditionValue(new FilterValueSpecConditionValueState(filterConditionState, simplifyValueExpression(value, filterConditionState.filterState.queryBuilderState.observerContext)));
        }
        if (!operator.isCompatibleWithFilterConditionValue(filterConditionState)) {
            filterConditionState.setRightConditionValue(new FilterValueSpecConditionValueState(filterConditionState, operator.getDefaultFilterConditionValue(filterConditionState)));
        }
        return filterConditionState;
    }
    return undefined;
};
//# sourceMappingURL=QueryBuilderFilterOperatorValueSpecificationBuilder.js.map