/**
 * 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 { guaranteeNonNullable, guaranteeType, UnsupportedOperationError, isNonNullable, uniq, returnUndefOnError, assertTrue, } from '@finos/legend-shared';
import { PRIMITIVE_TYPE, SUPPORTED_FUNCTIONS, } from '../../../../../../../../graph/MetaModelConst.js';
import { LambdaFunction, FunctionType, LambdaFunctionInstanceValue, } from '../../../../../../../../graph/metamodel/pure/valueSpecification/LambdaFunction.js';
import { VariableExpression } from '../../../../../../../../graph/metamodel/pure/valueSpecification/VariableExpression.js';
import { Class } from '../../../../../../../../graph/metamodel/pure/packageableElements/domain/Class.js';
import { PropertyGraphFetchTree, RootGraphFetchTree, SubTypeGraphFetchTree, GraphFetchTreeInstanceValue, } from '../../../../../../../../graph/metamodel/pure/valueSpecification/GraphFetchTree.js';
import { ValueSpecification } from '../../../../../../../../graph/metamodel/pure/valueSpecification/ValueSpecification.js';
import { SimpleFunctionExpression, AbstractPropertyExpression, FunctionExpression, } from '../../../../../../../../graph/metamodel/pure/valueSpecification/Expression.js';
import { GenericType } from '../../../../../../../../graph/metamodel/pure/packageableElements/domain/GenericType.js';
import { GenericTypeExplicitReference } from '../../../../../../../../graph/metamodel/pure/packageableElements/domain/GenericTypeReference.js';
import { InstanceValue, PrimitiveInstanceValue, EnumValueInstanceValue, CollectionInstanceValue, } from '../../../../../../../../graph/metamodel/pure/valueSpecification/InstanceValue.js';
import { PropertyExplicitReference } from '../../../../../../../../graph/metamodel/pure/packageableElements/domain/PropertyReference.js';
import { PackageableElementExplicitReference } from '../../../../../../../../graph/metamodel/pure/packageableElements/PackageableElementReference.js';
import { V1_Variable } from '../../../../model/valueSpecification/V1_Variable.js';
import { V1_ProcessingContext } from './V1_ProcessingContext.js';
import { V1_PropertyGraphFetchTree } from '../../../../model/valueSpecification/raw/classInstance/graph/V1_PropertyGraphFetchTree.js';
import { V1_Multiplicity } from '../../../../model/packageableElements/domain/V1_Multiplicity.js';
import { V1_RootGraphFetchTree } from '../../../../model/valueSpecification/raw/classInstance/graph/V1_RootGraphFetchTree.js';
import { V1_CString } from '../../../../model/valueSpecification/raw/V1_CString.js';
import { V1_getAppliedProperty } from './V1_DomainBuilderHelper.js';
import { Enumeration } from '../../../../../../../../graph/metamodel/pure/packageableElements/domain/Enumeration.js';
import { EnumValueExplicitReference } from '../../../../../../../../graph/metamodel/pure/packageableElements/domain/EnumValueReference.js';
import { INTERNAL__UnknownValueSpecification } from '../../../../../../../../graph/metamodel/pure/valueSpecification/INTERNAL__UnknownValueSpecification.js';
import { GraphBuilderError } from '../../../../../../../../graph-manager/GraphManagerUtils.js';
import { getEnumValue } from '../../../../../../../../graph/helpers/DomainHelper.js';
import { matchFunctionName } from '../../../../../../../../graph/MetaModelUtils.js';
import { V1_ClassInstanceType, V1_serializeValueSpecification, } from '../../../pureProtocol/serializationHelpers/V1_ValueSpecificationSerializer.js';
import { Multiplicity } from '../../../../../../../../graph/metamodel/pure/packageableElements/domain/Multiplicity.js';
import { KeyExpression, KeyExpressionInstanceValue, } from '../../../../../../../../graph/metamodel/pure/valueSpecification/KeyExpressionInstanceValue.js';
import { V1_SubTypeGraphFetchTree } from '../../../../model/valueSpecification/raw/classInstance/graph/V1_SubTypeGraphFetchTree.js';
import { findMappingLocalProperty } from '../../../../../../../../graph/helpers/DSL_Mapping_Helper.js';
import { getRelationTypeGenericType } from '../../../../../../../../graph/helpers/ValueSpecificationHelper.js';
import { RelationType } from '../../../../../../../../graph/metamodel/pure/packageableElements/relation/RelationType.js';
import { V1_getGenericTypeFullPath, V1_createGenericTypeWithElementPath, } from '../../../../helpers/V1_DomainHelper.js';
import { ColSpec, ColSpecArray, ColSpecArrayInstance, ColSpecInstanceValue, } from '../../../../../../../../graph/metamodel/pure/valueSpecification/RelationValueSpecification.js';
import { V1_ColSpec } from '../../../../model/valueSpecification/raw/classInstance/relation/V1_ColSpec.js';
import { V1_ColSpecArray } from '../../../../model/valueSpecification/raw/classInstance/relation/V1_ColSpecArray.js';
import { V1_RelationStoreAccessor, V1_IngestDefinitionAccessor, V1_DataProductAccessor, } from '../../../../model/valueSpecification/raw/classInstance/relation/V1_RelationStoreAccessor.js';
import { AccessorInstanceValue, DataProductAccessor, } from '../../../../../../../../graph/metamodel/pure/packageableElements/relation/Accessor.js';
import { DataProduct, LakehouseAccessPoint, } from '../../../../../../../../graph/metamodel/pure/dataProduct/DataProduct.js';
import { Database } from '../../../../../../../../graph/metamodel/pure/packageableElements/store/relational/model/Database.js';
import { IngestDefinition } from '../../../../../../../../graph/metamodel/pure/packageableElements/ingest/IngestDefinition.js';
import { V1_createAccessorFromPackageableElementWithNonFunctionSources } from '../../../../helpers/V1_AccessorHelper.js';
const buildPrimtiveInstanceValue = (type, values, context) => {
    const _genericType = context.resolveGenericType(type);
    const instance = new PrimitiveInstanceValue(_genericType);
    instance.values = values;
    return instance;
};
export class V1_ValueSpecificationBuilder {
    context;
    processingContext;
    openVariables = [];
    checkFunctionName;
    constructor(context, processingContext, openVariables, checkFunctionName) {
        this.context = context;
        this.processingContext = processingContext;
        this.openVariables = openVariables;
        this.checkFunctionName = checkFunctionName;
    }
    visit_INTERNAL__UnknownValueSpecfication(valueSpecification) {
        const metamodel = new INTERNAL__UnknownValueSpecification(valueSpecification.content);
        return metamodel;
    }
    visit_Variable(variable) {
        this.openVariables.push(variable.name);
        if (variable.genericType) {
            const multiplicity = this.context.graph.getMultiplicity(variable.multiplicity.lowerBound, variable.multiplicity.upperBound);
            const ve = new VariableExpression(variable.name, multiplicity);
            ve.genericType = this.context.resolveGenericType(V1_getGenericTypeFullPath(variable.genericType));
            this.processingContext.addInferredVariables(variable.name, ve);
            return ve;
        }
        else {
            const vs = this.processingContext.getInferredVariable(variable.name);
            if (!vs) {
                throw new GraphBuilderError(`Can't find variable '${variable.name}' in the graph`);
            }
            return vs;
        }
    }
    visit_Lambda(valueSpecification) {
        const instanceValue = new LambdaFunctionInstanceValue();
        instanceValue.values = [
            V1_buildLambdaBody(valueSpecification.body, valueSpecification.parameters, this.context, this.processingContext.clone()),
        ];
        return instanceValue;
    }
    visit_KeyExpression(valueSpecification) {
        const keyExpInstanceVal = new KeyExpressionInstanceValue();
        const processedExpression = valueSpecification.expression.accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(this.context, this.processingContext, this.openVariables));
        const processedKey = valueSpecification.key.accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(this.context, this.processingContext, this.openVariables));
        const key = guaranteeType(processedKey, PrimitiveInstanceValue, `Can't process key expression: key value must be a primitive instance value`);
        const keyExpression = new KeyExpression(key, processedExpression, valueSpecification.add);
        keyExpInstanceVal.values = [keyExpression];
        return keyExpInstanceVal;
    }
    visit_AppliedFunction(appliedFunction) {
        this.processingContext.push(`Applying function '${appliedFunction.function}'`);
        if (matchFunctionName(appliedFunction.function, SUPPORTED_FUNCTIONS.LET)) {
            // We will build the let parameters to assign the right side of the expression to the left side
            const parameters = appliedFunction.parameters.map((expression) => returnUndefOnError(() => expression.accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(this.context, this.processingContext, this.openVariables))));
            const letName = guaranteeType(appliedFunction.parameters[0], V1_CString).value;
            // right side (value spec)
            const rightSide = parameters[1];
            const variableExpression = new VariableExpression(letName, rightSide?.multiplicity ?? Multiplicity.ONE);
            variableExpression.genericType = rightSide?.genericType;
            this.processingContext.addInferredVariables(letName, variableExpression);
            this.openVariables.push(letName);
        }
        const func = V1_buildFunctionExpression(appliedFunction.function, appliedFunction.parameters, this.openVariables, this.context, this.processingContext, this.checkFunctionName);
        this.processingContext.pop();
        return func;
    }
    visit_AppliedProperty(valueSpecification) {
        return V1_processProperty(this.context, this.openVariables, this.processingContext, valueSpecification.parameters, valueSpecification.property);
    }
    visit_PackageableElementPtr(valueSpecification) {
        const instanceValue = new InstanceValue(Multiplicity.ONE, 
        // generic type not required since packageable element not required to be a type
        returnUndefOnError(() => this.context.resolveGenericType(valueSpecification.fullPath)));
        instanceValue.values = [
            this.context.resolveElement(valueSpecification.fullPath, false),
        ];
        return instanceValue;
    }
    visit_GenericTypeInstance(valueSpecification) {
        const instanceValue = new InstanceValue(Multiplicity.ONE, this.context.resolveGenericType(V1_getGenericTypeFullPath(valueSpecification.genericType)));
        return instanceValue;
    }
    visit_Collection(valueSpecification) {
        const instanceValue = new CollectionInstanceValue(this.context.graph.getMultiplicity(valueSpecification.multiplicity.lowerBound, valueSpecification.multiplicity.upperBound));
        instanceValue.values = valueSpecification.values.map((v) => v.accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(this.context, this.processingContext, this.openVariables)));
        // NOTE: Engine applies a more sophisticated `findMostCommon()` algorithm to find the collection's generic type. Here we only handle the case where the collection has one type.
        const typeValues = uniq(instanceValue.values
            .map((v) => v.genericType?.value.rawType)
            .filter(isNonNullable));
        if (typeValues.length === 1) {
            instanceValue.genericType = GenericTypeExplicitReference.create(new GenericType(guaranteeNonNullable(typeValues[0])));
        }
        return instanceValue;
    }
    visit_EnumValue(valueSpecification) {
        const instance = new EnumValueInstanceValue(this.context.resolveGenericType(valueSpecification.fullPath));
        instance.values = [
            this.context.resolveEnumValue(valueSpecification.fullPath, valueSpecification.value),
        ];
        return instance;
    }
    visit_CInteger(valueSpecification) {
        return buildPrimtiveInstanceValue(PRIMITIVE_TYPE.INTEGER, [valueSpecification.value], this.context);
    }
    visit_CDecimal(valueSpecification) {
        return buildPrimtiveInstanceValue(PRIMITIVE_TYPE.DECIMAL, [valueSpecification.value], this.context);
    }
    visit_CString(valueSpecification) {
        return buildPrimtiveInstanceValue(PRIMITIVE_TYPE.STRING, [valueSpecification.value], this.context);
    }
    visit_CBoolean(valueSpecification) {
        return buildPrimtiveInstanceValue(PRIMITIVE_TYPE.BOOLEAN, [valueSpecification.value], this.context);
    }
    visit_CByteArray(valueSpecification) {
        const res = buildPrimtiveInstanceValue(PRIMITIVE_TYPE.BYTE, [valueSpecification.value], // Store a Base64String as the value for BYTE
        this.context);
        return res;
    }
    visit_CFloat(valueSpecification) {
        return buildPrimtiveInstanceValue(PRIMITIVE_TYPE.FLOAT, [valueSpecification.value], this.context);
    }
    visit_CDateTime(valueSpecification) {
        return buildPrimtiveInstanceValue(PRIMITIVE_TYPE.DATETIME, [valueSpecification.value], this.context);
    }
    visit_CStrictDate(valueSpecification) {
        return buildPrimtiveInstanceValue(PRIMITIVE_TYPE.STRICTDATE, [valueSpecification.value], this.context);
    }
    visit_CStrictTime(valueSpecification) {
        return buildPrimtiveInstanceValue(PRIMITIVE_TYPE.STRICTTIME, [valueSpecification.value], this.context);
    }
    visit_CLatestDate(valueSpecification) {
        return buildPrimtiveInstanceValue(PRIMITIVE_TYPE.LATESTDATE, [], this.context);
    }
    visit_ClassInstance(valueSpecification) {
        switch (valueSpecification.type) {
            case V1_ClassInstanceType.ROOT_GRAPH_FETCH_TREE: {
                const instanceValue = new GraphFetchTreeInstanceValue();
                const protocol = guaranteeType(valueSpecification.value, V1_RootGraphFetchTree);
                const tree = V1_buildGraphFetchTree(protocol, this.context, this.context.resolveClass(protocol.class).value, this.openVariables, this.processingContext);
                instanceValue.values = [tree];
                return instanceValue;
            }
            case V1_ClassInstanceType.COL_SPEC: {
                const instanceValue = new ColSpecInstanceValue(Multiplicity.ONE, undefined);
                const protocol = guaranteeType(valueSpecification.value, V1_ColSpec);
                const value = new ColSpec();
                value.name = protocol.name;
                value.type = protocol.type;
                value.function1 = protocol.function1
                    ? protocol.function1.accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(this.context, this.processingContext, this.openVariables))
                    : undefined;
                value.function2 = protocol.function2
                    ? protocol.function2.accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(this.context, this.processingContext, this.openVariables))
                    : undefined;
                instanceValue.values = [value];
                return instanceValue;
            }
            case V1_ClassInstanceType.COL_SPEC_ARRAY: {
                const instanceValue = new ColSpecArrayInstance(Multiplicity.ONE, undefined);
                const protocol = guaranteeType(valueSpecification.value, V1_ColSpecArray);
                const value = new ColSpecArray();
                value.colSpecs = protocol.colSpecs.map((colSpec) => {
                    const colSpecInstance = new ColSpec();
                    colSpecInstance.name = colSpec.name;
                    colSpecInstance.type = colSpec.type;
                    colSpecInstance.function1 = colSpec.function1
                        ? colSpec.function1.accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(this.context, this.processingContext, this.openVariables))
                        : undefined;
                    colSpecInstance.function2 = colSpec.function2
                        ? colSpec.function2.accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(this.context, this.processingContext, this.openVariables))
                        : undefined;
                    return colSpecInstance;
                });
                instanceValue.values = [value];
                return instanceValue;
            }
            case V1_ClassInstanceType.RELATION_STORE_ACCESSOR: {
                const protocol = guaranteeType(valueSpecification.value, V1_RelationStoreAccessor);
                const dbPath = guaranteeNonNullable(protocol.path[0]);
                const schemaName = protocol.path[1];
                const tableName = protocol.path[2];
                const db = guaranteeType(this.context.resolveElement(dbPath, false).value, Database);
                const accessor = guaranteeNonNullable(V1_createAccessorFromPackageableElementWithNonFunctionSources(db, this.context, {
                    schemaName,
                    tableName,
                }), `Can't build accessor for database '${dbPath}'`);
                const accessorInstanceValue = new AccessorInstanceValue();
                accessorInstanceValue.values = [accessor];
                accessorInstanceValue.genericType = GenericTypeExplicitReference.create(new GenericType(accessor.relationType));
                return accessorInstanceValue;
            }
            case V1_ClassInstanceType.INGEST_ACCESSOR: {
                const protocol = guaranteeType(valueSpecification.value, V1_IngestDefinitionAccessor);
                const ingestPath = guaranteeNonNullable(protocol.path[0]);
                const datasetName = protocol.path[1];
                const ingestDef = guaranteeType(this.context.resolveElement(ingestPath, false).value, IngestDefinition);
                const accessor = guaranteeNonNullable(V1_createAccessorFromPackageableElementWithNonFunctionSources(ingestDef, this.context, {
                    tableName: datasetName,
                }), `Can't build accessor for ingest definition '${ingestPath}'`);
                const accessorInstanceValue = new AccessorInstanceValue();
                accessorInstanceValue.values = [accessor];
                accessorInstanceValue.genericType = GenericTypeExplicitReference.create(new GenericType(accessor.relationType));
                return accessorInstanceValue;
            }
            case V1_ClassInstanceType.DATA_PRODUCT_ACCESSOR: {
                const protocol = guaranteeType(valueSpecification.value, V1_DataProductAccessor);
                const dataProductPath = guaranteeNonNullable(protocol.path[0], `Can't build data product accessor: missing data product path`);
                const accessPointId = guaranteeNonNullable(protocol.path[1], `Can't build data product accessor for data product '${dataProductPath}': missing access point id`);
                const dataProduct = guaranteeType(this.context.resolveElement(dataProductPath, false).value, DataProduct, `Can't build data product accessor: element '${dataProductPath}' is not a data product`);
                const accessPoint = dataProduct.accessPointGroups
                    .flatMap((group) => group.accessPoints)
                    .find((ap) => ap instanceof LakehouseAccessPoint && ap.id === accessPointId);
                if (!accessPoint) {
                    throw new UnsupportedOperationError(`Can't build data product accessor: lakehouse access point '${accessPointId}' not found in data product '${dataProductPath}'`);
                }
                const relationType = guaranteeNonNullable(accessPoint.__internal__RelationType, `Can't build data product accessor for '${dataProductPath}.${accessPointId}': relation type has not been resolved on the access point`);
                const accessor = new DataProductAccessor(dataProductPath, accessPoint.__owner.id, accessPointId, relationType, dataProduct);
                const accessorInstanceValue = new AccessorInstanceValue();
                accessorInstanceValue.values = [accessor];
                accessorInstanceValue.genericType = GenericTypeExplicitReference.create(new GenericType(accessor.relationType));
                return accessorInstanceValue;
            }
            default: {
                const builders = this.context.extensions.plugins.flatMap((plugin) => plugin.V1_getExtraClassInstanceValueBuilders?.() ?? []);
                for (const builder of builders) {
                    const value = builder(valueSpecification.value, valueSpecification.type, this.context);
                    if (value) {
                        const instanceValue = new InstanceValue(Multiplicity.ONE);
                        instanceValue.values = [value];
                        return instanceValue;
                    }
                }
                throw new UnsupportedOperationError(`Can't build class instance value of type '${valueSpecification.type}': no compatible builder available from plugins`);
            }
        }
    }
}
// --------------------------------------------- Generic Constructs ---------------------------------------------
export function V1_buildLambdaBody(expressions, parameters, context, processingContext) {
    processingContext.push('Creating new lambda');
    const pureParameters = parameters.map((parameter) => parameter.accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(context, processingContext, [])));
    const openVariables = [];
    const _expressions = expressions.map((value) => value.accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(context, processingContext, openVariables)));
    const firstExpression = guaranteeNonNullable(_expressions[0]);
    const functionType = new FunctionType(firstExpression.genericType
        ? PackageableElementExplicitReference.create(firstExpression.genericType.value.rawType)
        : undefined, firstExpression.multiplicity);
    functionType.parameters = pureParameters;
    processingContext.pop();
    const _lambda = new LambdaFunction(functionType);
    openVariables.forEach((v) => {
        const varExp = processingContext.getInferredVariable(v);
        if (varExp instanceof VariableExpression) {
            _lambda.openVariables.set(varExp.name, varExp);
        }
    });
    _lambda.expressionSequence = _expressions;
    return _lambda;
}
export function V1_buildValueSpecification(valueSpecification, context) {
    return valueSpecification.accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(context, new V1_ProcessingContext(''), []));
}
// ------------------------------------------ Property -----------------------------------------
export function V1_processProperty(context, openVariables, processingContext, parameters, property) {
    const firstParameter = parameters[0];
    const processedParameters = parameters.map((parameter) => parameter.accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(context, processingContext, openVariables)));
    let inferredVariable;
    if (firstParameter instanceof V1_Variable) {
        inferredVariable = guaranteeType(processingContext.getInferredVariable(firstParameter.name), ValueSpecification);
    }
    else {
        inferredVariable = processedParameters[0];
    }
    const inferredType = inferredVariable instanceof AbstractPropertyExpression
        ? inferredVariable.func.value.genericType.value.rawType
        : V1_resolvePropertyExpressionTypeInference(inferredVariable, context);
    if (inferredType instanceof Class) {
        const processedProperty = new AbstractPropertyExpression('');
        processedProperty.func = PropertyExplicitReference.create(V1_getAppliedProperty(inferredType, undefined, property));
        processedProperty.parametersValues = processedParameters;
        return processedProperty;
    }
    else if (inferredType instanceof Enumeration) {
        const enumValueInstanceValue = new EnumValueInstanceValue(GenericTypeExplicitReference.create(new GenericType(inferredType)));
        enumValueInstanceValue.values = [
            EnumValueExplicitReference.create(getEnumValue(inferredType, property)),
        ];
        return enumValueInstanceValue;
    }
    else if (inferredType instanceof RelationType) {
        const col = guaranteeNonNullable(inferredType.columns.find((e) => property === e.name), `Can't find property ${property} in relation`);
        const _funcExp = new FunctionExpression(col.name);
        _funcExp.func = col;
        _funcExp.parametersValues = processedParameters;
        _funcExp.genericType = col.genericType;
        return _funcExp;
    }
    throw new UnsupportedOperationError(`Can't resolve property '${property}' of type '${inferredType?.name}'`);
}
export const V1_buildBaseSimpleFunctionExpression = (processedParameters, functionName, compileContext) => {
    const expression = new SimpleFunctionExpression(functionName);
    const func = returnUndefOnError(() => compileContext.resolveFunction(functionName));
    expression.func = func;
    if (func) {
        const val = func.value;
        expression.genericType = val.returnType;
        expression.multiplicity = val.returnMultiplicity;
    }
    expression.parametersValues = processedParameters;
    return expression;
};
/**
 * NOTE: this is a catch-all builder for all functions we support
 * This is extremely basic and will fail for any functions that needs proper
 * type-inferencing of the return and the parameters.
 */
export const V1_buildGenericFunctionExpression = (functionName, parameters, openVariables, compileContext, processingContext) => V1_buildBaseSimpleFunctionExpression(parameters.map((parameter) => parameter.accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables))), functionName, compileContext);
export const V1_buildGenericLetFunctionExpression = (functionName, parameters, openVariables, compileContext, processingContext) => {
    try {
        return V1_buildGenericFunctionExpression(functionName, parameters, openVariables, compileContext, processingContext);
    }
    catch {
        // let statement
        assertTrue(matchFunctionName(functionName, SUPPORTED_FUNCTIONS.LET), 'Expected let() function for custom let() handling');
        assertTrue(parameters.length === 2, 'Let() function expects 2 parameters');
        const leftSide = guaranteeNonNullable(parameters[0], 'Left side of let statement expected to be defined').accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables));
        const rightSideParam = guaranteeNonNullable(parameters[1]);
        const rightSide = returnUndefOnError(() => rightSideParam.accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(compileContext, processingContext, openVariables))) ??
            new INTERNAL__UnknownValueSpecification(V1_serializeValueSpecification(rightSideParam, compileContext.extensions.plugins));
        return V1_buildBaseSimpleFunctionExpression([leftSide, rightSide], functionName, compileContext);
    }
};
/**
 * This is fairly similar to how engine does function matching in a way.
 * Notice that Studio core should not attempt to do any function inferencing/matching
 * at all as the job is meant for engine.
 *
 * On the other hand, the function handling/matching plugin mechanism is meant
 * for extensions which should try to understand/match functions such as query builder.
 */
export function V1_buildFunctionExpression(functionName, parameters, openVariables, compileContext, processingContext, 
// We don't want to throw error when we build valuespecification outside
// query context (for ex: while building execution plan)
checkFunctionName) {
    if (checkFunctionName === false) {
        return V1_buildGenericFunctionExpression(functionName, parameters, openVariables, compileContext, processingContext);
    }
    if (matchFunctionName(functionName, Object.values(SUPPORTED_FUNCTIONS))) {
        if (matchFunctionName(functionName, SUPPORTED_FUNCTIONS.LET)) {
            return V1_buildGenericLetFunctionExpression(functionName, parameters, openVariables, compileContext, processingContext);
        }
        // NOTE: this is a catch-all builder that is only meant for basic function expression
        // such as and(), or(), etc. It will fail when type-inferencing/function-matching is required
        // such as for project(), filter(), getAll(), etc.
        return V1_buildGenericFunctionExpression(functionName, parameters, openVariables, compileContext, processingContext);
    }
    const extraFunctionExpressionBuilders = compileContext.extensions.plugins.flatMap((plugin) => plugin.V1_getExtraFunctionExpressionBuilders?.() ?? []);
    for (const builder of extraFunctionExpressionBuilders) {
        const metamodel = builder(functionName, parameters, openVariables, compileContext, processingContext);
        if (metamodel) {
            return metamodel;
        }
    }
    throw new UnsupportedOperationError(`Can't find expression builder for function '${functionName}': no compatible function expression builder available from plugins`);
}
export function V1_resolvePropertyExpressionTypeInference(inferredVariable, compileContext) {
    const inferrers = compileContext.extensions.plugins.flatMap((plugin) => plugin.V1_getExtraPropertyExpressionTypeInferrers?.() ?? []);
    for (const inferrer of inferrers) {
        const inferredType = inferrer(inferredVariable);
        if (inferredType) {
            return inferredType;
        }
    }
    const relationType = inferredVariable
        ? getRelationTypeGenericType(inferredVariable)
        : undefined;
    if (relationType) {
        return relationType;
    }
    return inferredVariable?.genericType?.value.rawType;
}
// --------------------------------------------- Graph Fetch Tree ---------------------------------------------
export function V1_buildGraphFetchTree(graphFetchTree, context, parentClass, openVariables, processingContext, canResolveLocalProperty) {
    if (graphFetchTree instanceof V1_PropertyGraphFetchTree) {
        return V1_buildPropertyGraphFetchTree(graphFetchTree, context, guaranteeNonNullable(parentClass), openVariables, processingContext, canResolveLocalProperty);
    }
    else if (graphFetchTree instanceof V1_RootGraphFetchTree) {
        return V1_buildRootGraphFetchTree(graphFetchTree, context, openVariables, processingContext, canResolveLocalProperty);
    }
    else if (graphFetchTree instanceof V1_SubTypeGraphFetchTree) {
        return buildSubTypeGraphFetchTree(graphFetchTree, context, openVariables, processingContext, canResolveLocalProperty);
    }
    throw new UnsupportedOperationError(`Can't build graph fetch tree`, graphFetchTree);
}
const createThisVariableForClass = (context, classPackageString) => {
    const _classGenericType = context.resolveGenericType(classPackageString);
    const _var = new VariableExpression('this', Multiplicity.ONE);
    _var.genericType = _classGenericType;
    return _var;
};
export function V1_buildPropertyGraphFetchTree(propertyGraphFetchTree, context, parentClass, openVariables, processingContext, canResolveLocalProperty) {
    let property;
    let pureParameters = [];
    if (propertyGraphFetchTree.parameters.length) {
        const thisVariable = new V1_Variable();
        thisVariable.name = 'this';
        thisVariable.genericType = V1_createGenericTypeWithElementPath(parentClass.path);
        thisVariable.multiplicity = V1_Multiplicity.ONE;
        const parameters = propertyGraphFetchTree.parameters.concat([thisVariable]);
        property = V1_getAppliedProperty(parentClass, parameters, propertyGraphFetchTree.property);
        processingContext.push('Creating graph-fetch property tree');
        processingContext.addInferredVariables('this', createThisVariableForClass(context, parentClass.path));
        pureParameters = propertyGraphFetchTree.parameters.map((x) => x.accept_ValueSpecificationVisitor(new V1_ValueSpecificationBuilder(context, processingContext, openVariables)));
        processingContext.flushVariable('this');
        processingContext.pop();
    }
    else {
        // While building property graph fetch tree, the Class of root graph
        // fetch tree is passed as the parentClass of property graph fetch tree.
        // This Class contains the properties of property graph fetch trees.
        // But sometimes, a local property is missing in the parentClass and
        // causing error in V1_getAppliedProperty.
        // To fix this, we get the local property from PureModel context.graph
        // mappings. The function findProperty() is used for this purpose.
        if (canResolveLocalProperty) {
            try {
                property = V1_getAppliedProperty(parentClass, undefined, propertyGraphFetchTree.property);
            }
            catch (error) {
                const newProperty = findMappingLocalProperty(context.graph.mappings, propertyGraphFetchTree.property);
                if (newProperty) {
                    property = newProperty;
                }
                else {
                    throw error;
                }
            }
        }
        else {
            property = V1_getAppliedProperty(parentClass, undefined, propertyGraphFetchTree.property);
        }
    }
    const _subType = propertyGraphFetchTree.subType
        ? context.resolveClass(propertyGraphFetchTree.subType)
        : undefined;
    const _returnType = _subType?.value ?? property.genericType.value.rawType;
    let children = [];
    if (propertyGraphFetchTree.subTrees.length) {
        const _returnTypeClasss = guaranteeType(_returnType, Class, 'To have subtrees the type of the property must be complex');
        children = propertyGraphFetchTree.subTrees.map((subTree) => V1_buildGraphFetchTree(subTree, context, _returnTypeClasss, openVariables, processingContext, canResolveLocalProperty));
    }
    const _propertyGraphFetchTree = new PropertyGraphFetchTree(PropertyExplicitReference.create(property), undefined);
    _propertyGraphFetchTree.parameters = pureParameters;
    _propertyGraphFetchTree.alias = propertyGraphFetchTree.alias;
    _propertyGraphFetchTree.subType = _subType;
    _propertyGraphFetchTree.subTrees = children;
    return _propertyGraphFetchTree;
}
export function V1_buildRootGraphFetchTree(rootGraphFetchTree, context, openVariables, processingContext, canResolveLocalProperty) {
    const _class = context.resolveClass(rootGraphFetchTree.class);
    const subTreeChildren = rootGraphFetchTree.subTrees.map((subTree) => V1_buildGraphFetchTree(subTree, context, _class.value, openVariables, processingContext, canResolveLocalProperty));
    const subTypeTreeChildren = rootGraphFetchTree.subTypeTrees.map((subTypeTree) => buildSubTypeGraphFetchTree(subTypeTree, context, openVariables, processingContext, canResolveLocalProperty));
    const _rootGraphFetchTree = new RootGraphFetchTree(PackageableElementExplicitReference.create(_class.value));
    _rootGraphFetchTree.subTrees = subTreeChildren;
    _rootGraphFetchTree.subTypeTrees = subTypeTreeChildren;
    return _rootGraphFetchTree;
}
function buildSubTypeGraphFetchTree(subTypeGraphFetchTree, context, openVariables, processingContext, canResolveLocalProperty) {
    const subTypeClass = context.resolveClass(subTypeGraphFetchTree.subTypeClass);
    const children = subTypeGraphFetchTree.subTrees.map((subTree) => V1_buildGraphFetchTree(subTree, context, subTypeClass.value, openVariables, processingContext, canResolveLocalProperty));
    const _subTypeGraphFetchTree = new SubTypeGraphFetchTree(PackageableElementExplicitReference.create(subTypeClass.value));
    _subTypeGraphFetchTree.subTrees = children;
    return _subTypeGraphFetchTree;
}
//# sourceMappingURL=V1_ValueSpecificationBuilderHelper.js.map