/**
 * 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 { VariableExpression, CollectionInstanceValue, Enumeration, EnumValueExplicitReference, EnumValueInstanceValue, GenericType, GenericTypeExplicitReference, getEnumValue, getPrimitiveTypeInstanceFromEnum, PrimitiveInstanceValue, PrecisePrimitiveType, PrimitiveType, PRIMITIVE_TYPE, INTERNAL__PropagatedValue, SimpleFunctionExpression, LambdaFunction, FunctionType, PackageableElementExplicitReference, Multiplicity, CORE_PURE_PATH, buildRawLambdaFromLambdaFunction, getValueSpecificationReturnType, isSubType, AbstractPropertyExpression, PropertyExplicitReference, } from '@finos/legend-graph';
import { Randomizer, UnsupportedOperationError, deepClone, guaranteeNonNullable, returnUndefOnError, } from '@finos/legend-shared';
import { generateDefaultValueForPrimitiveType, getStandardPrimitiveTypeEquivalent, } from '../QueryBuilderValueSpecificationHelper.js';
import { functionExpression_setParametersValues, instanceValue_setValues, propertyExpression_setFunc, valueSpecification_setGenericType, } from './ValueSpecificationModifierHelper.js';
import { QUERY_BUILDER_PURE_PATH, QUERY_BUILDER_SUPPORTED_FUNCTIONS, } from '../../graph/QueryBuilderMetaModelConst.js';
import { buildDatePickerOption } from '../../components/shared/CustomDatePickerHelper.js';
export const createSupportedFunctionExpression = (supportedFuncName, expectedReturnType) => {
    const funcExpression = new SimpleFunctionExpression(supportedFuncName);
    valueSpecification_setGenericType(funcExpression, GenericTypeExplicitReference.create(new GenericType(expectedReturnType)));
    return funcExpression;
};
export const buildPrimitiveInstanceValue = (graph, type, value, observerContext) => {
    const instance = new PrimitiveInstanceValue(GenericTypeExplicitReference.create(new GenericType(graph.getPrimitiveType(type))));
    instanceValue_setValues(instance, [value], observerContext);
    return instance;
};
export const buildEnumInstanceValue = (graph, enumPath, value, observerContext) => {
    const enumType = graph.getEnumeration(enumPath);
    const enumValue = guaranteeNonNullable(enumType.values.find((v) => v.name === value), `Can't find enum value '${value}' in enumeration '${enumType.path}'`);
    const instance = new EnumValueInstanceValue(GenericTypeExplicitReference.create(new GenericType(enumType)));
    instanceValue_setValues(instance, [EnumValueExplicitReference.create(enumValue)], observerContext);
    return instance;
};
export const buildPrimitiveCollectionInstanceValue = (graph, type, values, observerContext) => {
    const instance = new CollectionInstanceValue(Multiplicity.ZERO_MANY, GenericTypeExplicitReference.create(new GenericType(graph.getPrimitiveType(type))));
    instanceValue_setValues(instance, values.map((value) => buildPrimitiveInstanceValue(graph, type, value, observerContext)), observerContext);
    return instance;
};
export const buildEnumCollectionInstanceValue = (graph, enumPath, values, observerContext) => {
    const enumType = graph.getEnumeration(enumPath);
    const instance = new CollectionInstanceValue(Multiplicity.ZERO_MANY, GenericTypeExplicitReference.create(new GenericType(enumType)));
    instanceValue_setValues(instance, values.map((value) => buildEnumInstanceValue(graph, enumPath, value, observerContext)), observerContext);
    return instance;
};
export const cloneValueSpecification = (valueSpecification, observerContext) => {
    const copy = deepClone(valueSpecification);
    copy.genericType = valueSpecification.genericType;
    copy.multiplicity = valueSpecification.multiplicity;
    return copy;
};
export const cloneAbstractPropertyExpression = (propertyExpression, observerContext) => {
    const clonedPropertyExpression = new AbstractPropertyExpression(propertyExpression.functionName);
    propertyExpression_setFunc(clonedPropertyExpression, PropertyExplicitReference.create(guaranteeNonNullable(propertyExpression.func.value)));
    functionExpression_setParametersValues(clonedPropertyExpression, propertyExpression.parametersValues.map((param) => cloneValueSpecification(param, observerContext)), observerContext);
    return clonedPropertyExpression;
};
export const createMockPrimitiveValueSpecification = (primitiveType, graph, observerContext, options) => {
    const primitiveTypeName = primitiveType.name;
    if (options?.useCurrentDateDependentFunctions) {
        if (primitiveTypeName === PRIMITIVE_TYPE.DATE ||
            primitiveTypeName === PRIMITIVE_TYPE.DATETIME) {
            return createSupportedFunctionExpression(QUERY_BUILDER_SUPPORTED_FUNCTIONS.NOW, PrimitiveType.DATETIME);
        }
        else if (primitiveTypeName === PRIMITIVE_TYPE.STRICTDATE) {
            return createSupportedFunctionExpression(QUERY_BUILDER_SUPPORTED_FUNCTIONS.TODAY, PrimitiveType.STRICTDATE);
        }
    }
    if (primitiveTypeName === PRIMITIVE_TYPE.DATE) {
        return buildPrimitiveInstanceValue(graph, PRIMITIVE_TYPE.STRICTDATE, generateDefaultValueForPrimitiveType(primitiveTypeName), observerContext);
    }
    return buildPrimitiveInstanceValue(graph, primitiveTypeName, generateDefaultValueForPrimitiveType(primitiveTypeName), observerContext);
};
export const createMockEnumerationProperty = (enumeration) => new Randomizer().getRandomItemInCollection(enumeration.values)?.name ?? '';
export const buildDefaultInstanceValue = (graph, type, observerContext, enableInitializingDefaultValue) => {
    // Normalize precise primitive types to their standard equivalents
    const path = getStandardPrimitiveTypeEquivalent(type) ?? type.path;
    switch (path) {
        case PRIMITIVE_TYPE.STRING:
        case PRIMITIVE_TYPE.STRICTDATE:
        case PRIMITIVE_TYPE.DATETIME:
        case PRIMITIVE_TYPE.NUMBER:
        case PRIMITIVE_TYPE.DECIMAL:
        case PRIMITIVE_TYPE.FLOAT:
        case PRIMITIVE_TYPE.BINARY:
        case PRIMITIVE_TYPE.BYTE:
        case PRIMITIVE_TYPE.INTEGER: {
            return buildPrimitiveInstanceValue(graph, path, enableInitializingDefaultValue
                ? generateDefaultValueForPrimitiveType(path)
                : null, observerContext);
        }
        case PRIMITIVE_TYPE.BOOLEAN: {
            return buildPrimitiveInstanceValue(graph, path, generateDefaultValueForPrimitiveType(path), observerContext);
        }
        case PRIMITIVE_TYPE.DATE: {
            return buildPrimitiveInstanceValue(graph, PRIMITIVE_TYPE.STRICTDATE, enableInitializingDefaultValue
                ? generateDefaultValueForPrimitiveType(path)
                : null, observerContext);
        }
        default:
            if (type instanceof Enumeration) {
                const enumValueInstanceValue = new EnumValueInstanceValue(GenericTypeExplicitReference.create(new GenericType(type)));
                if (enableInitializingDefaultValue) {
                    if (type.values.length > 0) {
                        instanceValue_setValues(enumValueInstanceValue, [EnumValueExplicitReference.create(type.values[0])], observerContext);
                    }
                    else {
                        throw new UnsupportedOperationError(`Can't get default value for enumeration since enumeration '${path}' has no value`);
                    }
                }
                return enumValueInstanceValue;
            }
            throw new UnsupportedOperationError(`Can't get default value for type '${path}'`);
    }
};
export const buildDefaultEmptyStringLambda = (graph, observerContext) => {
    const lambdaFunction = new LambdaFunction(new FunctionType(PackageableElementExplicitReference.create(graph.getType(CORE_PURE_PATH.ANY)), Multiplicity.ONE));
    lambdaFunction.expressionSequence[0] = buildDefaultInstanceValue(graph, PrimitiveType.STRING, observerContext, true);
    return lambdaFunction;
};
export const buildDefaultEmptyStringRawLambda = (graphState, observerContext) => buildRawLambdaFromLambdaFunction(buildDefaultEmptyStringLambda(graphState.graph, observerContext), graphState);
export const generateVariableExpressionMockValue = (parameter, graph, observerContext, options) => {
    const varType = parameter.genericType?.value.rawType;
    const multiplicity = parameter.multiplicity;
    /**
     *  Studio doesn't handle byte[*] as a CollectionInstanceValue but as a
     *  PrimitiveValueSpecification of type PrimitiveType.BYTE.
     *  Engine sends a Base64String transformed from byte[] by Jackson to Studio, then
     *  Studio stores this Base64String as the value of V1_CByteArray.
     */
    if ((!multiplicity.upperBound || multiplicity.upperBound > 1) &&
        varType &&
        varType !== PrimitiveType.BYTE) {
        return new CollectionInstanceValue(multiplicity, GenericTypeExplicitReference.create(new GenericType(varType)));
    }
    if (varType instanceof PrimitiveType) {
        return createMockPrimitiveValueSpecification(varType, graph, observerContext, options);
    }
    else if (varType instanceof Enumeration) {
        const enumValueInstance = new EnumValueInstanceValue(GenericTypeExplicitReference.create(new GenericType(varType)));
        const mock = createMockEnumerationProperty(varType);
        if (mock !== '') {
            instanceValue_setValues(enumValueInstance, [EnumValueExplicitReference.create(getEnumValue(varType, mock))], observerContext);
        }
        return enumValueInstance;
    }
    return undefined;
};
export const getValueSpecificationStringValue = (valueSpecification, applicationStore, options) => {
    if (valueSpecification instanceof PrimitiveInstanceValue) {
        if (isSubType(valueSpecification.genericType.value.rawType, PrimitiveType.DATE)) {
            return buildDatePickerOption(valueSpecification, applicationStore).label;
        }
        if (valueSpecification.genericType.value.rawType === PrimitiveType.STRING &&
            options?.wrapStringInDoubleQuotes) {
            return `"${valueSpecification.values[0]?.toString()}"`;
        }
        return valueSpecification.values[0]?.toString();
    }
    else if (valueSpecification instanceof EnumValueInstanceValue) {
        const _enum = valueSpecification.values[0];
        if (options?.omitEnumOwnerName) {
            return _enum?.value.name;
        }
        return `${_enum?.ownerReference.value.name}.${_enum?.value.name}`;
    }
    else if (valueSpecification instanceof VariableExpression) {
        return valueSpecification.name;
    }
    else if (valueSpecification instanceof INTERNAL__PropagatedValue) {
        return getValueSpecificationStringValue(valueSpecification.getValue(), applicationStore, options);
    }
    else if (valueSpecification instanceof SimpleFunctionExpression) {
        if (valueSpecification.genericType?.value.rawType !== undefined &&
            isSubType(valueSpecification.genericType.value.rawType, PrimitiveType.DATE)) {
            return buildDatePickerOption(valueSpecification, applicationStore).label;
        }
        return valueSpecification.functionName;
    }
    else if (valueSpecification instanceof CollectionInstanceValue) {
        return valueSpecification.values
            .map((valueSpec) => getValueSpecificationStringValue(valueSpec, applicationStore, options))
            .join(',');
    }
    return undefined;
};
export const valueSpecReturnTDS = (val, graph) => {
    const retunType = returnUndefOnError(() => getValueSpecificationReturnType(val));
    const tdsType = returnUndefOnError(() => graph.getClass(QUERY_BUILDER_PURE_PATH.TDS_TABULAR_DATASET));
    const tdsRowType = returnUndefOnError(() => graph.getClass(QUERY_BUILDER_PURE_PATH.TDS_ROW));
    // FIXME: we sometimes return tds row when tds is the return type of the lambda. We do this to properly build post filters.
    // We should fix this to properly build filter functions after a tds function
    return Boolean(retunType && tdsType && (retunType === tdsType || retunType === tdsRowType));
};
export const convertTextToPrimitiveInstanceValue = (expectedType, value, observerContext) => {
    let result = null;
    // Normalize precise primitive types (e.g. Varchar, BigInt, Timestamp) to
    // their standard primitive equivalent so values entered through the editor
    // are stored as standard PrimitiveInstanceValue regardless of whether the
    // source column was declared with a precise type (e.g. accessor relation
    // columns).
    let resolvedType;
    if (expectedType instanceof PrimitiveType) {
        resolvedType = expectedType;
    }
    else if (expectedType instanceof PrecisePrimitiveType) {
        const standard = getStandardPrimitiveTypeEquivalent(expectedType);
        resolvedType = standard
            ? getPrimitiveTypeInstanceFromEnum(standard)
            : undefined;
    }
    if (resolvedType) {
        switch (resolvedType.path) {
            case PRIMITIVE_TYPE.STRING: {
                result = new PrimitiveInstanceValue(GenericTypeExplicitReference.create(new GenericType(resolvedType)));
                instanceValue_setValues(result, [value.toString()], observerContext);
                break;
            }
            case PRIMITIVE_TYPE.NUMBER:
            case PRIMITIVE_TYPE.FLOAT:
            case PRIMITIVE_TYPE.DECIMAL:
            case PRIMITIVE_TYPE.INTEGER: {
                if (isNaN(Number(value))) {
                    return null;
                }
                const formattedNumber = resolvedType.path === PRIMITIVE_TYPE.INTEGER
                    ? Number.parseInt(Number(value).toString(), 10)
                    : Number(value);
                result = new PrimitiveInstanceValue(GenericTypeExplicitReference.create(new GenericType(resolvedType)));
                instanceValue_setValues(result, [formattedNumber], observerContext);
                break;
            }
            case PRIMITIVE_TYPE.DATE:
            case PRIMITIVE_TYPE.STRICTDATE: {
                if (isNaN(Date.parse(value))) {
                    return null;
                }
                result = new PrimitiveInstanceValue(GenericTypeExplicitReference.create(new GenericType(resolvedType)));
                instanceValue_setValues(result, [value], observerContext);
                break;
            }
            case PRIMITIVE_TYPE.DATETIME: {
                if (isNaN(Date.parse(value)) ||
                    !new Date(value).getTime() ||
                    (value.includes('%') &&
                        (isNaN(Date.parse(value.slice(1))) ||
                            !new Date(value.slice(1)).getTime()))) {
                    return null;
                }
                result = new PrimitiveInstanceValue(GenericTypeExplicitReference.create(new GenericType(resolvedType)));
                instanceValue_setValues(result, [value], observerContext);
                break;
            }
            default:
                // unsupported expected type, just escape
                return null;
        }
    }
    return result;
};
export const convertTextToEnum = (value, enumType) => enumType.values.find((enumValue) => enumValue.name === value);
//# sourceMappingURL=ValueSpecificationEditorHelper.js.map