/**
 * 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 { assertNonNullable, assertTrue, assertType, filterByType, guaranteeNonNullable, guaranteeType, isNonNullable, returnUndefOnError, UnsupportedOperationError, } from '@finos/legend-shared';
import { SimpleFunctionExpression, V1_ValueSpecification, V1_buildBaseSimpleFunctionExpression, V1_buildGenericFunctionExpression, extractElementNameFromPath, V1_AppliedProperty, CollectionInstanceValue, INTERNAL__UnknownValueSpecification, V1_Variable, V1_serializeValueSpecification, GenericType, GenericTypeExplicitReference, matchFunctionName, InstanceValue, V1_AppliedFunction, V1_Collection, V1_Lambda, V1_ValueSpecificationBuilder, AbstractPropertyExpression, VariableExpression, Multiplicity, PrimitiveType, V1_CString, CORE_PURE_PATH, FunctionType, LambdaFunction, LambdaFunctionInstanceValue, PackageableElementExplicitReference, V1_ClassInstance, V1_ColSpecArray, ColSpecArrayInstance, ColSpecArray, ColSpec, RelationType, RelationColumn, getValueSpecificationReturnType, Relation, } from '@finos/legend-graph';
import { QUERY_BUILDER_PURE_PATH, QUERY_BUILDER_SUPPORTED_FUNCTIONS, QUERY_BUILDER_SUPPORTED_CALENDAR_AGGREGATION_FUNCTIONS, } from '../../../../graph/QueryBuilderMetaModelConst.js';
import { QUERY_BUILDER_CALENDAR_TYPE } from '../../../QueryBuilderConst.js';
const buildProjectionColumnLambda = (valueSpecification, openVariables, compileContext, processingContext) => {
    assertType(valueSpecification, V1_Lambda, `Can't build projection column: only support lambda`);
    // lambda parameter
    assertTrue(valueSpecification.parameters.length === 1, `Can't build projection column: only support lambda with 1 parameter`);
    const columnLambdaParameter = guaranteeType(valueSpecification.parameters[0], V1_Variable, `Can't build projection column: only support lambda with 1 parameter`);
    // lambda body
    assertTrue(valueSpecification.body.length === 1, `Can't build projection column: only support lambda body with 1 expression`);
    let currentPropertyExpression = valueSpecification
        .body[0];
    //catch if wavg row mapper
    if (currentPropertyExpression instanceof V1_AppliedFunction &&
        currentPropertyExpression.parameters.length === 2 &&
        matchFunctionName(currentPropertyExpression.function, QUERY_BUILDER_SUPPORTED_FUNCTIONS.WAVG_ROW_MAPPER)) {
        return valueSpecification.accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables));
    }
    assertType(currentPropertyExpression, V1_AppliedProperty, `Can't build projection column: only support lambda body as property expression`);
    while (currentPropertyExpression instanceof V1_AppliedProperty) {
        assertTrue(currentPropertyExpression.parameters.length >= 1, `Can't build projection column: only support lambda body as property expression`);
        currentPropertyExpression = currentPropertyExpression
            .parameters[0];
        // Take care of chains of subtype (a pattern that is not useful, but we want to support and rectify)
        // $x.employees->subType(@Person)->subType(@Staff)
        while (currentPropertyExpression instanceof V1_AppliedFunction &&
            matchFunctionName(currentPropertyExpression.function, QUERY_BUILDER_SUPPORTED_FUNCTIONS.SUBTYPE)) {
            currentPropertyExpression = currentPropertyExpression
                .parameters[0];
        }
    }
    // check lambda variable and parameter match
    assertType(currentPropertyExpression, V1_Variable, `Can't build projection column: only support lambda body as property expression`);
    assertTrue(columnLambdaParameter.name === currentPropertyExpression.name, `Can't build column lambda: expects variable used in lambda body '${currentPropertyExpression.name}' to match lambda parameter '${columnLambdaParameter.name}'`);
    return valueSpecification.accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables));
};
const buildCalendarFunctionExpression = (valueSpecification, openVariables, compileContext, processingContext) => {
    assertType(valueSpecification, V1_Lambda, `Can't build calendar aggregation column: only support lambda`);
    // lambda parameter
    assertTrue(valueSpecification.parameters.length === 1, `Can't build calendar aggregation column: only support lambda with 1 parameter`);
    // lambda body
    assertTrue(valueSpecification.body.length === 1, `Can't build calendar aggregation column: only support lambda body with 1 expression`);
    const columnLambdaParameter = guaranteeType(valueSpecification.parameters[0], V1_Variable, `Can't build calendar aggregation column: only support lambda with 1 parameter`);
    const currentPropertyExpression = guaranteeType(valueSpecification.body[0], V1_AppliedFunction, `Can't build calendar aggregation column: only support applied function`);
    assertTrue(matchFunctionName(currentPropertyExpression.function, Object.values(QUERY_BUILDER_SUPPORTED_CALENDAR_AGGREGATION_FUNCTIONS)), `Can't build calendar aggregation columnn: unsupported calenddar function`);
    assertTrue(currentPropertyExpression.parameters.length === 4, `Can't build calendar aggregation column: only support calendar function with four parameters`);
    assertType(currentPropertyExpression.parameters[0], V1_AppliedProperty, `Can't build calendar aggregation column: only support first parameter of calendar function as property expression`);
    const calendarType = guaranteeType(currentPropertyExpression.parameters[1], V1_CString, `Can't build calendar aggregation column: only support second parameter of calendar function as String`);
    assertTrue(Object.values(QUERY_BUILDER_CALENDAR_TYPE).find((val) => val === calendarType.value) !== undefined, `Can't build calendar aggregation column: ${calendarType.value} is not a supported calendar type`);
    guaranteeType(currentPropertyExpression.parameters[2], V1_ValueSpecification, `Can't build calendar aggregation column: only support third parameter of calendar function as Date`);
    const dateColumn = currentPropertyExpression.parameters[0].accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables));
    const calendarTypeValueSpec = guaranteeNonNullable(currentPropertyExpression.parameters[1]).accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables));
    const endDate = guaranteeNonNullable(currentPropertyExpression.parameters[2]).accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables));
    const columnLambda = guaranteeNonNullable(currentPropertyExpression.parameters[3]);
    const targetColumn = returnUndefOnError(() => columnLambda.accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables))) ??
        new INTERNAL__UnknownValueSpecification(V1_serializeValueSpecification(columnLambda, compileContext.extensions.plugins));
    const calendarFunctionExpression = V1_buildBaseSimpleFunctionExpression([dateColumn, calendarTypeValueSpec, endDate, targetColumn], currentPropertyExpression.function, compileContext);
    const functionInstanceValue = new LambdaFunctionInstanceValue();
    const functionType = new FunctionType(PackageableElementExplicitReference.create(compileContext.graph.getType(CORE_PURE_PATH.ANY)), Multiplicity.ONE);
    functionType.parameters.push(new VariableExpression(columnLambdaParameter.name, Multiplicity.ONE));
    const lambdaFunction = new LambdaFunction(functionType);
    lambdaFunction.expressionSequence = [calendarFunctionExpression];
    functionInstanceValue.values.push(lambdaFunction);
    return functionInstanceValue;
};
const buildAggregationExpression = (valueSpecification, openVariables, compileContext, processingContext) => {
    assertType(valueSpecification, V1_AppliedFunction, `Can't build aggregation: only support function`);
    assertTrue(matchFunctionName(valueSpecification.function, QUERY_BUILDER_SUPPORTED_FUNCTIONS.TDS_AGG), `Can't build aggregation: only support agg()`);
    assertTrue(valueSpecification.parameters.length === 2, `Can't build agg() expression: agg() expects 2 arguments`);
    // column lambda
    const columnLambda = guaranteeType(valueSpecification.parameters[0], V1_Lambda, `Can't build agg() expression: agg() expects argument #1 as a lambda`);
    let processedColumnLambda;
    if (columnLambda.body[0] instanceof V1_AppliedFunction &&
        matchFunctionName(columnLambda.body[0].function, Object.values(QUERY_BUILDER_SUPPORTED_CALENDAR_AGGREGATION_FUNCTIONS))) {
        processedColumnLambda =
            returnUndefOnError(() => buildCalendarFunctionExpression(columnLambda, openVariables, compileContext, processingContext)) ??
                new INTERNAL__UnknownValueSpecification(V1_serializeValueSpecification(columnLambda, compileContext.extensions.plugins));
    }
    else {
        processedColumnLambda =
            returnUndefOnError(() => buildProjectionColumnLambda(columnLambda, openVariables, compileContext, processingContext)) ??
                new INTERNAL__UnknownValueSpecification(V1_serializeValueSpecification(columnLambda, compileContext.extensions.plugins));
    }
    // aggregate lambda
    const aggregateLambda = guaranteeType(valueSpecification.parameters[1], V1_Lambda);
    const processedAggregateLambda = aggregateLambda.accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables));
    return V1_buildBaseSimpleFunctionExpression([processedColumnLambda, processedAggregateLambda], extractElementNameFromPath(QUERY_BUILDER_SUPPORTED_FUNCTIONS.TDS_AGG), compileContext);
};
export const V1_buildGetAllFunctionExpression = (functionName, parameters, openVariables, compileContext, processingContext) => {
    const expression = V1_buildGenericFunctionExpression(functionName, parameters, openVariables, compileContext, processingContext);
    const precedingExpression = expression.parametersValues[0];
    assertType(precedingExpression, InstanceValue, `Can't build getAll() expression: only support getAll() immediately following a class`);
    expression.genericType = precedingExpression.genericType;
    expression.multiplicity = precedingExpression.multiplicity;
    return expression;
};
export const V1_buildGetAllVersionsInRangeFunctionExpression = (functionName, parameters, openVariables, compileContext, processingContext) => {
    const expression = V1_buildGenericFunctionExpression(functionName, parameters, openVariables, compileContext, processingContext);
    const precedingExpression = expression.parametersValues[0];
    assertType(precedingExpression, InstanceValue, `Can't build getAllVersionsInRange() expression: only support getAllVersionsInRange() immediately following a class`);
    const parameterValues = expression.parametersValues;
    assertTrue(parameterValues.length === 3, `Can't build getAllVersionsInRange() expression: only support getAllVersionsRange() with start and end date`);
    expression.genericType = precedingExpression.genericType;
    expression.multiplicity = precedingExpression.multiplicity;
    return expression;
};
export const V1_buildGetAllVersionsFunctionExpression = (functionName, parameters, openVariables, compileContext, processingContext) => {
    const expression = V1_buildGenericFunctionExpression(functionName, parameters, openVariables, compileContext, processingContext);
    const precedingExpression = expression.parametersValues[0];
    assertType(precedingExpression, InstanceValue, `Can't build getAllVersions() expression: only support getAllVersions() immediately following a class`);
    const parameterValues = expression.parametersValues;
    assertTrue(parameterValues.length === 1, `Can't build getAllVersions() expression: only support getAllVersions() with no paremters`);
    expression.genericType = precedingExpression.genericType;
    expression.multiplicity = precedingExpression.multiplicity;
    return expression;
};
export const V1_buildExistsFunctionExpression = (functionName, parameters, openVariables, compileContext, processingContext) => {
    assertTrue(parameters.length === 2, `Can't build exists() expression: exists() expects 1 argument`);
    const precedingExpression = parameters[0].accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables));
    precedingExpression.genericType = guaranteeType(precedingExpression, AbstractPropertyExpression).func.value.genericType;
    const lambda = parameters[1];
    if (lambda instanceof V1_Lambda) {
        lambda.parameters.forEach((variable) => {
            if (variable.name && !variable.genericType) {
                const variableExpression = new VariableExpression(variable.name, Multiplicity.ONE);
                variableExpression.genericType = precedingExpression.genericType;
                processingContext.addInferredVariables(variable.name, variableExpression);
            }
        });
    }
    return V1_buildBaseSimpleFunctionExpression([
        precedingExpression,
        parameters[1].accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables)),
    ], functionName, compileContext);
};
export const V1_buildFilterFunctionExpression = (functionName, parameters, openVariables, compileContext, processingContext) => {
    assertTrue(parameters.length === 2, `Can't build filter() expression: filter() expects 1 argument`);
    const precedingExpression = parameters[0].accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables));
    if (precedingExpression.genericType) {
        const lambda = parameters[1];
        if (lambda instanceof V1_Lambda) {
            lambda.parameters.forEach((variable) => {
                if (variable.name && !variable.genericType) {
                    const variableExpression = new VariableExpression(variable.name, precedingExpression.multiplicity);
                    variableExpression.genericType = precedingExpression.genericType;
                    processingContext.addInferredVariables(variable.name, variableExpression);
                }
            });
        }
    }
    const expression = V1_buildBaseSimpleFunctionExpression([
        precedingExpression,
        parameters[1].accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables)),
    ], functionName, compileContext);
    // return type of filter() is the same as that of the function precedes it
    expression.genericType = precedingExpression.genericType;
    expression.multiplicity = precedingExpression.multiplicity;
    return expression;
};
const V1_buildTypedProjectFunctionExpression = (functionName, parameters, openVariables, compileContext, processingContext) => {
    assertTrue(parameters.length === 2, `Can't build relation project() expression: project() expects 2 arguments`);
    let topLevelLambdaParameters = [];
    const precedingExperession = parameters[0].accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables));
    assertNonNullable(precedingExperession.genericType, `Can't build relation project() expression: preceding expression return type is missing`);
    const classInstance = parameters[1];
    assertType(classInstance, V1_ClassInstance, `Can't build relation project() expression: project() expects argument #1 to be a ClassInstance`);
    const specArray = guaranteeType(classInstance.value, V1_ColSpecArray, `Can't build relation project() expression: project() expects argument #1 to hold spec array instances value`);
    topLevelLambdaParameters = specArray.colSpecs
        .map((e) => e.function1)
        .filter(isNonNullable)
        .filter(filterByType(V1_Lambda))
        .map((lambda) => lambda.parameters)
        .flat();
    const variables = new Set();
    // Make sure top-level lambdas have their lambda parameter types set properly
    topLevelLambdaParameters.forEach((variable) => {
        if (!variables.has(variable.name) && !variable.genericType) {
            const variableExpression = new VariableExpression(variable.name, precedingExperession.multiplicity);
            variableExpression.genericType = precedingExperession.genericType;
            processingContext.addInferredVariables(variable.name, variableExpression);
        }
    });
    const processedExpression = new ColSpecArrayInstance(Multiplicity.ONE);
    const processedColSpecArray = new ColSpecArray();
    processedExpression.values = [processedColSpecArray];
    const relationType = new RelationType(RelationType.ID);
    processedColSpecArray.colSpecs = specArray.colSpecs.map((colSpec) => {
        const pColSpec = new ColSpec();
        let lambda;
        const _funct = guaranteeType(colSpec.function1, V1_ValueSpecification, `Can't build relation col spec() expression: expects function1 to be a lambda`);
        try {
            lambda = buildProjectionColumnLambda(_funct, openVariables, compileContext, processingContext);
        }
        catch {
            lambda = new INTERNAL__UnknownValueSpecification(V1_serializeValueSpecification(_funct, compileContext.extensions.plugins));
        }
        pColSpec.function1 = lambda;
        pColSpec.name = colSpec.name;
        const returnType = getValueSpecificationReturnType(lambda);
        if (returnType) {
            relationType.columns.push(new RelationColumn(colSpec.name, GenericTypeExplicitReference.create(new GenericType(returnType))));
        }
        return pColSpec;
    });
    const expression = V1_buildBaseSimpleFunctionExpression([precedingExperession, processedExpression], functionName, compileContext);
    const relationGenericType = new GenericType(Relation.INSTANCE);
    const relationTypeGenericType = new GenericType(relationType);
    relationGenericType.typeArguments = [
        GenericTypeExplicitReference.create(relationTypeGenericType),
    ];
    expression.genericType =
        GenericTypeExplicitReference.create(relationGenericType);
    return expression;
};
const V1_buildColFunctionExpression = (valueSpecification, precedingExperession, openVariables, compileContext, processingContext) => {
    const appliedFunc = guaranteeType(valueSpecification, V1_AppliedFunction, `Can't build tds col func: only support applied function`);
    assertTrue(matchFunctionName(appliedFunc.function, QUERY_BUILDER_SUPPORTED_FUNCTIONS.TDS_COL), `Can't build tds col func: unsupported func name ${appliedFunc.function}`);
    assertTrue(appliedFunc.parameters.length === 2, `Can't build tds col func: only support col function with 2 parameters`);
    const colLambda = guaranteeType(appliedFunc.parameters[0], V1_Lambda, `Can't build tds col func: only support first parameter of col function as Lambda`);
    let lambda;
    try {
        colLambda.parameters.forEach((variable) => {
            if (variable.name && !variable.genericType) {
                const variableExpression = new VariableExpression(variable.name, Multiplicity.ONE);
                variableExpression.genericType = precedingExperession.genericType;
                processingContext.addInferredVariables(variable.name, variableExpression);
            }
        });
        lambda = buildProjectionColumnLambda(colLambda, openVariables, compileContext, processingContext);
    }
    catch {
        lambda = new INTERNAL__UnknownValueSpecification(V1_serializeValueSpecification(colLambda, compileContext.extensions.plugins));
    }
    guaranteeType(appliedFunc.parameters[1], V1_CString, `Can't build tds col func: only support second parameter of col function as String`);
    const colNameValueSpec = guaranteeNonNullable(appliedFunc.parameters[1]).accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables));
    const colFunctionExpression = V1_buildBaseSimpleFunctionExpression([lambda, colNameValueSpec], appliedFunc.function, compileContext);
    return colFunctionExpression;
};
const V1_buildTDSProjectWithColFunctionExpression = (functionName, parameters, openVariables, compileContext, processingContext) => {
    assertTrue(parameters.length === 2, `Can't build tds project() expression: project() expects 2 arguments`);
    const precedingExperession = parameters[0].accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables));
    assertNonNullable(precedingExperession.genericType, `Can't build tds project() expression: preceding expression return type is missing`);
    const colFuncExpress = guaranteeNonNullable(parameters[1], `Can't build tds project() expression: second parameter is missing`);
    let secondProcessedParameter;
    if (colFuncExpress instanceof V1_Collection) {
        const processedColExpressions = new CollectionInstanceValue(compileContext.graph.getMultiplicity(colFuncExpress.multiplicity.lowerBound, colFuncExpress.multiplicity.upperBound));
        processedColExpressions.values = colFuncExpress.values.map((value) => V1_buildColFunctionExpression(value, precedingExperession, openVariables, compileContext, processingContext));
        secondProcessedParameter = processedColExpressions;
    }
    else {
        assertType(colFuncExpress, V1_AppliedFunction, `Can't build project() expression: project() expects argument #2 to be a function`);
        secondProcessedParameter = V1_buildColFunctionExpression(colFuncExpress, precedingExperession, openVariables, compileContext, processingContext);
    }
    const expression = V1_buildBaseSimpleFunctionExpression([precedingExperession, secondProcessedParameter], functionName, compileContext);
    expression.genericType = GenericTypeExplicitReference.create(new GenericType(compileContext.resolveType(QUERY_BUILDER_PURE_PATH.TDS_ROW).value));
    return expression;
};
export const V1_buildProjectFunctionExpression = (functionName, parameters, openVariables, compileContext, processingContext) => {
    if (parameters.length === 2) {
        try {
            return V1_buildTDSProjectWithColFunctionExpression(functionName, parameters, openVariables, compileContext, processingContext);
        }
        catch {
            return V1_buildTypedProjectFunctionExpression(functionName, parameters, openVariables, compileContext, processingContext);
        }
    }
    const length = parameters.length;
    assertTrue(length === 3 || length === 2, `Can't build project() expression: project() expects 2 arguments`);
    let topLevelLambdaParameters = [];
    const precedingExperession = parameters[0].accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables));
    assertNonNullable(precedingExperession.genericType, `Can't build project() expression: preceding expression return type is missing`);
    const columnExpressions = parameters[1];
    if (columnExpressions instanceof V1_Lambda) {
        topLevelLambdaParameters = columnExpressions.parameters;
    }
    else {
        assertType(columnExpressions, V1_Collection, `Can't build project() expression: project() expects argument #1 to be a collection`);
        topLevelLambdaParameters = columnExpressions.values
            .filter(filterByType(V1_Lambda))
            .map((lambda) => lambda.parameters)
            .flat();
    }
    const variables = new Set();
    // Make sure top-level lambdas have their lambda parameter types set properly
    topLevelLambdaParameters.forEach((variable) => {
        if (!variables.has(variable.name) && !variable.genericType) {
            const variableExpression = new VariableExpression(variable.name, precedingExperession.multiplicity);
            variableExpression.genericType = precedingExperession.genericType;
            processingContext.addInferredVariables(variable.name, variableExpression);
        }
    });
    // build column expressions taking into account of derivation
    const processedColumnExpressions = new CollectionInstanceValue(compileContext.graph.getMultiplicity(columnExpressions.multiplicity.lowerBound, columnExpressions.multiplicity.upperBound));
    let colExpressionValues = [];
    if (columnExpressions instanceof V1_Lambda) {
        colExpressionValues = [columnExpressions];
    }
    else {
        colExpressionValues = columnExpressions.values;
    }
    processedColumnExpressions.values = colExpressionValues.map((value) => {
        try {
            return buildProjectionColumnLambda(value, openVariables, compileContext, processingContext);
        }
        catch {
            return new INTERNAL__UnknownValueSpecification(V1_serializeValueSpecification(value, compileContext.extensions.plugins));
        }
    });
    const expression = V1_buildBaseSimpleFunctionExpression([
        precedingExperession,
        processedColumnExpressions,
        parameters[2].accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables)),
    ], functionName, compileContext);
    expression.genericType = GenericTypeExplicitReference.create(new GenericType(compileContext.resolveType(QUERY_BUILDER_PURE_PATH.TDS_ROW).value));
    return expression;
};
export const V1_buildTDSGroupByFunctionExpression = (functionName, parameters, openVariables, compileContext, processingContext) => {
    let topLevelLambdaParameters = [];
    assertTrue(parameters.length === 4, `Can't build groupBy() expression: groupBy() expects 3 arguments`);
    const precedingExperession = parameters[0].accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables));
    assertNonNullable(precedingExperession.genericType, `Can't build groupBy() expression: preceding expression return type is missing`);
    // normal columns
    const columnExpressions = parameters[1];
    assertType(columnExpressions, V1_Collection, `Can't build groupBy() expression: groupBy() expects argument #1 to be a collection`);
    topLevelLambdaParameters = columnExpressions.values
        .filter(filterByType(V1_Lambda))
        .map((lambda) => lambda.parameters)
        .flat();
    // aggregation columns
    const aggregationExpressions = parameters[2];
    assertType(aggregationExpressions, V1_Collection, `Can't build groupBy() expression: groupBy() expects argument #2 to be a collection`);
    topLevelLambdaParameters = topLevelLambdaParameters.concat(aggregationExpressions.values
        .filter((value) => value instanceof V1_AppliedFunction &&
        matchFunctionName(value.function, QUERY_BUILDER_SUPPORTED_FUNCTIONS.TDS_AGG))
        .map((value) => value.parameters)
        .flat()
        .filter(filterByType(V1_Lambda))
        .map((lambda) => lambda.parameters)
        .flat());
    // Make sure top-level lambdas have their lambda parameter types set properly
    const variables = new Set();
    topLevelLambdaParameters.forEach((variable) => {
        if (!variables.has(variable.name) && !variable.genericType) {
            const variableExpression = new VariableExpression(variable.name, precedingExperession.multiplicity);
            variableExpression.genericType = precedingExperession.genericType;
            processingContext.addInferredVariables(variable.name, variableExpression);
        }
    });
    // build column expressions taking into account of derivation
    const processedColumnExpressions = new CollectionInstanceValue(compileContext.graph.getMultiplicity(columnExpressions.multiplicity.lowerBound, columnExpressions.multiplicity.upperBound));
    processedColumnExpressions.values = columnExpressions.values.map((value) => {
        try {
            return buildProjectionColumnLambda(value, openVariables, compileContext, processingContext);
        }
        catch {
            return new INTERNAL__UnknownValueSpecification(V1_serializeValueSpecification(value, compileContext.extensions.plugins));
        }
    });
    // build aggregation expressions taking into account of derivation
    const processedAggregationExpressions = new CollectionInstanceValue(compileContext.graph.getMultiplicity(aggregationExpressions.multiplicity.lowerBound, aggregationExpressions.multiplicity.upperBound));
    processedAggregationExpressions.values = aggregationExpressions.values.map((value) => buildAggregationExpression(value, openVariables, compileContext, processingContext));
    const expression = V1_buildBaseSimpleFunctionExpression([
        precedingExperession,
        processedColumnExpressions,
        processedAggregationExpressions,
        parameters[3].accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables)),
    ], functionName, compileContext);
    expression.genericType = GenericTypeExplicitReference.create(new GenericType(compileContext.resolveType(QUERY_BUILDER_PURE_PATH.TDS_ROW).value));
    return expression;
};
export const V1_buildTypedGroupByFunctionExpression = (functionName, parameters, openVariables, compileContext, processingContext) => {
    assertTrue(parameters.length === 3, `Can't build relation groupBy() expression: groupBy() expects 2 arguments`);
    const precedingExpression = parameters[0].accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables));
    assertNonNullable(precedingExpression.genericType, `Can't build relation groupBy() expression: preceding expression return type is missing`);
    // Assert that preceding function is a project() function
    const projectFunction = guaranteeType(precedingExpression, SimpleFunctionExpression);
    if (projectFunction.functionName !==
        extractElementNameFromPath(QUERY_BUILDER_SUPPORTED_FUNCTIONS.RELATION_PROJECT)) {
        throw new UnsupportedOperationError(`Can't build relation groupBy() expression: preceding expression must be project() column expression`);
    }
    // Get normal (grouped) columns
    const columnsClassInstance = parameters[1];
    assertType(columnsClassInstance, V1_ClassInstance, `Can't build relation groupBy() expression: groupBy() expects argument #1 to be a ClassInstance`);
    const columnExpressions = guaranteeType(columnsClassInstance.value, V1_ColSpecArray, `Can't build relation groupBy() expression: groupBy() expects argument #1 to hold col spec array instance value`);
    // Get aggregation columns
    const aggregationColumnsClassInstance = parameters[2];
    assertType(aggregationColumnsClassInstance, V1_ClassInstance, `Can't build groupBy() expression: groupBy() expects argument #2 to be a ClassInstance`);
    const aggregationExpressions = guaranteeType(aggregationColumnsClassInstance.value, V1_ColSpecArray, `Can't build relation groupBy() expression: groupBy() expects argument #2 to hold col spec array instance value`);
    // Make sure top-level lambdas have their lambda parameter types set properly
    const topLevelLambdaParameters = aggregationExpressions.colSpecs
        .map((colSpec) => [colSpec.function1, colSpec.function2])
        .flat()
        .filter(isNonNullable)
        .filter(filterByType(V1_Lambda))
        .map((value) => value.parameters)
        .flat();
    topLevelLambdaParameters.forEach((variable) => {
        if (!variable.genericType) {
            const variableExpression = new VariableExpression(variable.name, precedingExpression.multiplicity);
            variableExpression.genericType = precedingExpression.genericType;
            processingContext.addInferredVariables(variable.name, variableExpression);
        }
    });
    const projectRelationReturnType = guaranteeType(projectFunction.genericType?.value.typeArguments?.[0]?.value.rawType, RelationType, `Can't build relation groupBy() expression: project() function does not return a relation`);
    // build column expressions
    const processedColumnExpressions = new ColSpecArrayInstance(Multiplicity.ONE);
    const processedColSpecArray = new ColSpecArray();
    processedColumnExpressions.values = [processedColSpecArray];
    const relationType = new RelationType(RelationType.ID);
    processedColSpecArray.colSpecs = columnExpressions.colSpecs.map((colSpec) => {
        const pColSpec = new ColSpec();
        pColSpec.name = colSpec.name;
        // Add the column using the return type of the preceding project
        const column = projectRelationReturnType.columns.find((_column) => _column.name === colSpec.name);
        if (column) {
            relationType.columns.push(column);
        }
        return pColSpec;
    });
    // build aggregation column expressions
    const processedAggregationExpressions = new ColSpecArrayInstance(Multiplicity.ONE);
    const processedAggregationColSpecArray = new ColSpecArray();
    processedAggregationExpressions.values = [processedAggregationColSpecArray];
    processedAggregationColSpecArray.colSpecs =
        aggregationExpressions.colSpecs.map((colSpec) => {
            const pColSpec = new ColSpec();
            pColSpec.name = colSpec.name;
            // Build the map lambda
            const mapFunction = guaranteeType(colSpec.function1, V1_Lambda, `Can't build relation col spec() expression: expects function1 to be a lambda`);
            const mapLambda = buildProjectionColumnLambda(mapFunction, openVariables, compileContext, processingContext);
            pColSpec.function1 = mapLambda;
            // Build the reduce lambda
            const reduceFunction = guaranteeType(colSpec.function2, V1_Lambda, `Can't build relation col spec() expression: expects function2 to be a lambda`);
            const reduceLambda = guaranteeType(reduceFunction.accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables)), LambdaFunctionInstanceValue, `Can't build relation col spec() expression: expected aggregation function to be a lambda`);
            pColSpec.function2 = reduceLambda;
            // For now, we temporarily set the return type of the column in the groupBy() expression to be
            // the same as the return type of the column in the preceding project() expression.
            // The actual return type for the groupBy() expression will be determined when we process/build the graph.
            const genericType = projectRelationReturnType.columns.find((_column) => _column.name === colSpec.name)?.genericType;
            if (genericType) {
                relationType.columns.push(new RelationColumn(colSpec.name, genericType));
            }
            else {
                throw new UnsupportedOperationError(`Unable to find projected column with name ${colSpec.name}`);
            }
            return pColSpec;
        });
    const expression = V1_buildBaseSimpleFunctionExpression([
        precedingExpression,
        processedColumnExpressions,
        processedAggregationExpressions,
    ], functionName, compileContext);
    const relationGenericType = new GenericType(Relation.INSTANCE);
    const relationTypeGenericType = new GenericType(relationType);
    relationGenericType.typeArguments = [
        GenericTypeExplicitReference.create(relationTypeGenericType),
    ];
    expression.genericType =
        GenericTypeExplicitReference.create(relationGenericType);
    return expression;
};
export const V1_buildGroupByFunctionExpression = (functionName, parameters, openVariables, compileContext, processingContext) => {
    if (functionName === QUERY_BUILDER_SUPPORTED_FUNCTIONS.RELATION_GROUP_BY ||
        parameters.length === 3) {
        return V1_buildTypedGroupByFunctionExpression(functionName, parameters, openVariables, compileContext, processingContext);
    }
    return V1_buildTDSGroupByFunctionExpression(functionName, parameters, openVariables, compileContext, processingContext);
};
export const V1_buildWatermarkFunctionExpression = (functionName, parameters, openVariables, compileContext, processingContext) => {
    assertTrue(parameters.length === 2, `Can't build forWatermark() expression: forWatermark() expects 1 argument`);
    const precedingExpression = parameters[0].accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables));
    assertNonNullable(precedingExpression.genericType, `Can't build forWatermark() expression: preceding expression return type is missing`);
    const lambda = parameters[1];
    if (lambda instanceof V1_Lambda) {
        lambda.parameters.forEach((variable) => {
            if (variable.name && !variable.genericType) {
                const variableExpression = new VariableExpression(variable.name, precedingExpression.multiplicity);
                variableExpression.genericType = precedingExpression.genericType;
                processingContext.addInferredVariables(variable.name, variableExpression);
            }
        });
    }
    const watermarkValueParam = guaranteeNonNullable(parameters[1].accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables)));
    const watermarkValueParamType = watermarkValueParam.genericType?.value.rawType;
    assertTrue(PrimitiveType.STRING === watermarkValueParamType, "Can't build forWatermark() expression: parameter is expected to be a string");
    const expression = V1_buildBaseSimpleFunctionExpression([precedingExpression, watermarkValueParam], functionName, compileContext);
    expression.genericType = precedingExpression.genericType;
    expression.multiplicity = precedingExpression.multiplicity;
    return expression;
};
export const V1_buildOLAPGroupByFunctionExpression = (functionName, parameters, openVariables, compileContext, processingContext) => {
    const processedParams = [];
    assertTrue(parameters.length === 5 || parameters.length === 4, `Can't build olapGroupBy() expression: olapGroupBy() expects 4 or 5 arguments`);
    let paramIndex = 0;
    const containsSortByExpression = parameters.length === 5;
    // preceding expression
    const precedingExperession = parameters[paramIndex].accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables));
    assertNonNullable(precedingExperession.genericType, `Can't build olapGroupBy() expression: preceding expression return type is missing`);
    processedParams.push(precedingExperession);
    paramIndex++;
    // partition (window) columns
    const columns = parameters[paramIndex].accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables));
    processedParams.push(columns);
    paramIndex++;
    // OLAP sortBy
    if (containsSortByExpression) {
        const sortBy = parameters[paramIndex].accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables));
        processedParams.push(sortBy);
        paramIndex++;
    }
    // OLAP agg operation
    const olapOperatorExp = parameters[paramIndex];
    let olapOperationLambda;
    if (olapOperatorExp instanceof V1_AppliedFunction) {
        olapOperationLambda = guaranteeType(olapOperatorExp.parameters[1], V1_Lambda, `Can't build olapGroupBy() expression: olap operation function expects argument ${paramIndex + 1} to be a lambda`);
    }
    else {
        olapOperationLambda = guaranteeType(olapOperatorExp, V1_Lambda, `Can't build olapGroupBy() expression: olapGroupBy() expects argument ${paramIndex + 1} to be a lambda`);
    }
    const olapOperationParameters = olapOperationLambda.parameters;
    const variables = new Set();
    olapOperationParameters.forEach((variable) => {
        if (!variables.has(variable.name) && !variable.genericType) {
            const variableExpression = new VariableExpression(variable.name, precedingExperession.multiplicity);
            variableExpression.genericType = precedingExperession.genericType;
            processingContext.addInferredVariables(variable.name, variableExpression);
        }
    });
    const olapOperation = olapOperatorExp.accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables));
    processedParams.push(olapOperation);
    paramIndex++;
    // OLAP column name
    const olapColumnName = parameters[paramIndex].accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables));
    processedParams.push(olapColumnName);
    paramIndex++;
    // build final expression
    const expression = V1_buildBaseSimpleFunctionExpression(processedParams, functionName, compileContext);
    expression.genericType = GenericTypeExplicitReference.create(new GenericType(compileContext.resolveType(QUERY_BUILDER_PURE_PATH.TDS_ROW).value));
    return expression;
};
export const V1_buildSubTypePropertyExpressionTypeInference = (inferredVariable) => inferredVariable.parametersValues.filter((param) => param instanceof InstanceValue)[0]?.genericType?.value.rawType;
export const V1_buildOverFunctionExpression = (functionName, parameters, openVariables, compileContext, processingContext) => {
    assertTrue(parameters.length >= 1 && parameters.length <= 2, `Can't build over() expression: over() expects 1-2 arguments, got ${parameters.length}`);
    //TODO: handle over() when first param is a ColSpec, and when only given SortInfo
    const windowColumns = parameters[0];
    assertType(windowColumns, V1_ClassInstance, `Can't build over() expression with ColSpecArray: expects argument #1 to be a ClassInstance`);
    const specArray = guaranteeType(windowColumns.value, V1_ColSpecArray, `Can't build over() expression with ColSpecArray: expects ClassInstance to hold ColSpecArray`);
    const windowColSpecArrayInst = new ColSpecArrayInstance(Multiplicity.ONE);
    const windowColSpecArray = new ColSpecArray();
    windowColSpecArrayInst.values = [windowColSpecArray];
    windowColSpecArray.colSpecs = specArray.colSpecs.map((colSpec) => {
        const windowColSpec = new ColSpec();
        windowColSpec.name = colSpec.name;
        windowColSpec.type = colSpec.type;
        return windowColSpec;
    });
    const processedParams = [windowColSpecArrayInst];
    if (parameters[1]) {
        const sortBy = parameters[1].accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables));
        processedParams.push(sortBy);
    }
    return V1_buildBaseSimpleFunctionExpression(processedParams, functionName, compileContext);
};
export const V1_buildExtendFunctionExpression = (functionName, parameters, openVariables, compileContext, processingContext) => {
    if (parameters.length === 3) {
        const precedingExpression = guaranteeNonNullable(parameters[0], `Can't build relation extend() expression: preceding expression is missing`).accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables));
        assertNonNullable(precedingExpression.genericType, `Can't build relation extend() expression: preceding expression return type is missing`);
        const overExpression = guaranteeNonNullable(parameters[1], `Can't build relation extend() expression: over() expression is missing`).accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables));
        const windowOperator = guaranteeNonNullable(parameters[2], `Can't build relation extend() expression: window operator is missing`);
        assertType(windowOperator, V1_ClassInstance, `Can't build relation extend() expression: extend() expects argument #3 to be a ClassInstance`);
        //TODO: handle extend() where the third parameter is a ColSpec
        const operatorExpression = windowOperator.value;
        const topLevelLambdaParameters = operatorExpression.colSpecs
            .flatMap((colSpec) => [colSpec.function1, colSpec.function2])
            .filter(isNonNullable)
            .flatMap((value) => value.parameters);
        topLevelLambdaParameters.forEach((variable) => {
            if (!variable.genericType) {
                const variableExpression = new VariableExpression(variable.name, precedingExpression.multiplicity);
                variableExpression.genericType = precedingExpression.genericType;
                processingContext.addInferredVariables(variable.name, variableExpression);
            }
        });
        const builtOperatorExpressions = new ColSpecArrayInstance(Multiplicity.ONE);
        const builtOperatorColSpecArray = new ColSpecArray();
        builtOperatorExpressions.values = [builtOperatorColSpecArray];
        const precedingRelationType = precedingExpression.genericType.value.typeArguments?.[0]?.value.rawType;
        const relationType = new RelationType(RelationType.ID);
        if (precedingRelationType instanceof RelationType) {
            precedingRelationType.columns.forEach((col) => {
                relationType.columns.push(col);
            });
        }
        builtOperatorColSpecArray.colSpecs = operatorExpression.colSpecs.map((colSpec) => {
            const builtColSpec = new ColSpec();
            builtColSpec.name = colSpec.name;
            if (colSpec.function1) {
                const function1 = guaranteeType(colSpec.function1, V1_Lambda, `Can't build relation extend() col spec expression: expects function1 to be a lambda`);
                const function1Lambda = guaranteeType(function1.accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables)), LambdaFunctionInstanceValue, `Can't build relation extend() col spec expression: expected function1 to be a lambda`);
                builtColSpec.function1 = function1Lambda;
            }
            if (colSpec.function2) {
                const function2 = guaranteeType(colSpec.function2, V1_Lambda, `Can't build relation extend() col spec expression: expects function2 to be a lambda`);
                const function2Lambda = guaranteeType(function2.accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables)), LambdaFunctionInstanceValue, `Can't build relation extend() col spec expression: expected aggregation function to be a lambda`);
                builtColSpec.function2 = function2Lambda;
            }
            let returnType;
            if (builtColSpec.function2) {
                returnType = getValueSpecificationReturnType(builtColSpec.function2);
            }
            else if (builtColSpec.function1) {
                returnType = getValueSpecificationReturnType(builtColSpec.function1);
            }
            if (returnType) {
                relationType.columns.push(new RelationColumn(colSpec.name, GenericTypeExplicitReference.create(new GenericType(returnType))));
            }
            return builtColSpec;
        });
        //build extend()
        const expression = V1_buildBaseSimpleFunctionExpression([precedingExpression, overExpression, builtOperatorExpressions], functionName, compileContext);
        const relationGenericType = new GenericType(Relation.INSTANCE);
        const relationTypeGenericType = new GenericType(relationType);
        relationGenericType.typeArguments = [
            GenericTypeExplicitReference.create(relationTypeGenericType),
        ];
        expression.genericType =
            GenericTypeExplicitReference.create(relationGenericType);
        return expression;
    }
    else {
        throw new UnsupportedOperationError(`Can't build relation extend() expression: extend() on non window functions is not yet supported`);
    }
};
//# sourceMappingURL=V1_QueryValueSpecificationBuilderHelper.js.map