/**
 * 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 { action, makeObservable, observable, flow, computed, flowResult, override, } from 'mobx';
import { LogEvent, assertErrorThrown, guaranteeNonNullable, guaranteeType, assertTrue, assertNonEmptyString, hashArray, ActionState, prettyCONSTName, } from '@finos/legend-shared';
import { buildPropertyExpressionFromExplorerTreeNodeData, } from '../../../explorer/QueryBuilderExplorerState.js';
import { getPropertyChainName, QueryBuilderPropertyExpressionState, } from '../../../QueryBuilderPropertyEditorState.js';
import { PackageableElementExplicitReference, GRAPH_MANAGER_EVENT, buildSourceInformationSourceId, ParserError, RawVariableExpression, Enumeration, RawLambda, stub_RawLambda, Multiplicity, PrimitiveType, Accessor, Class, LambdaFunction, FunctionType, CORE_PURE_PATH, AccessorInstanceValue, SimpleFunctionExpression, ColSpec, extractElementNameFromPath, ColSpecArrayInstance, ColSpecArray, INTERNAL__UnknownValueSpecification, V1_serializeRawValueSpecification, V1_transformRawLambda, V1_GraphTransformerContextBuilder, buildRawLambdaFromLambdaFunction, } from '@finos/legend-graph';
import { QueryBuilderTDSColumnState } from '../QueryBuilderTDSColumnState.js';
import { DEFAULT_LAMBDA_VARIABLE_NAME, QUERY_BUILDER_SOURCE_ID_LABEL, } from '../../../QueryBuilderConfig.js';
import { QUERY_BUILDER_STATE_HASH_STRUCTURE } from '../../../QueryBuilderStateHashUtils.js';
import { LambdaEditorState } from '../../../shared/LambdaEditorState.js';
import { isValueExpressionReferencedInValue } from '../../../QueryBuilderValueSpecificationHelper.js';
import { QUERY_BUILDER_SUPPORTED_FUNCTIONS } from '../../../../graph/QueryBuilderMetaModelConst.js';
export const _buildAccessorProjectRawLambda = (graph, graphManagerState, accessor, columns) => {
    const lambdaFunction = new LambdaFunction(new FunctionType(PackageableElementExplicitReference.create(graph.getType(CORE_PURE_PATH.ANY)), Multiplicity.ONE));
    const accessorInstanceValue = new AccessorInstanceValue();
    accessorInstanceValue.values = [accessor];
    const projectFunction = new SimpleFunctionExpression(extractElementNameFromPath(QUERY_BUILDER_SUPPORTED_FUNCTIONS.RELATION_PROJECT));
    const instanceVal = new ColSpecArrayInstance(Multiplicity.ONE, undefined);
    const colSpecArray = new ColSpecArray();
    instanceVal.values = [colSpecArray];
    const transformerContext = new V1_GraphTransformerContextBuilder(
    // TODO?: do we need to include the plugins here?
    []).build();
    columns.forEach((lambda, columnName) => {
        const colSpec = new ColSpec();
        colSpec.name = columnName;
        colSpec.function1 = new INTERNAL__UnknownValueSpecification(V1_serializeRawValueSpecification(V1_transformRawLambda(lambda, transformerContext)));
        colSpecArray.colSpecs.push(colSpec);
    });
    projectFunction.parametersValues = [accessorInstanceValue, instanceVal];
    lambdaFunction.expressionSequence = [projectFunction];
    return buildRawLambdaFromLambdaFunction(lambdaFunction, graphManagerState);
};
export const _decorateRawLambdaWithParametersAndLetExpressions = (rawLambda, parameters, letExpressions) => {
    let lambdaBody = rawLambda.body;
    if (letExpressions.length) {
        if (Array.isArray(rawLambda.body)) {
            lambdaBody = [...letExpressions, ...rawLambda.body];
        }
        else {
            lambdaBody = [...letExpressions, rawLambda.body];
        }
    }
    return new RawLambda([
        ...(Array.isArray(rawLambda.parameters)
            ? rawLambda.parameters
            : []),
        ...parameters,
    ], lambdaBody);
};
export const QUERY_BUILDER_PROJECTION_COLUMN_DND_TYPE = 'PROJECTION_COLUMN';
export class QueryBuilderProjectionColumnState extends QueryBuilderTDSColumnState {
    tdsState;
    columnName;
    wavgWeight;
    constructor(tdsState, columnName) {
        super();
        makeObservable(this, {
            uuid: false,
            tdsState: false,
            columnName: observable,
            wavgWeight: observable,
            setColumnName: action,
            setWavgWeight: action,
            hashCode: computed,
        });
        this.tdsState = tdsState;
        this.columnName = columnName;
    }
    setColumnName(val) {
        this.columnName = val;
    }
    setWavgWeight(val) {
        this.wavgWeight = val;
    }
}
export class QueryBuilderSimpleProjectionColumnState extends QueryBuilderProjectionColumnState {
    lambdaParameterName = DEFAULT_LAMBDA_VARIABLE_NAME;
    propertyExpressionState;
    constructor(tdsState, propertyExpression, humanizePropertyName) {
        super(tdsState, '');
        makeObservable(this, {
            lambdaParameterName: observable,
            propertyExpressionState: observable,
            setLambdaParameterName: action,
            changeProperty: action,
        });
        this.propertyExpressionState = new QueryBuilderPropertyExpressionState(tdsState.queryBuilderState, propertyExpression);
        this.columnName = getPropertyChainName(this.propertyExpressionState.propertyExpression, humanizePropertyName);
    }
    setLambdaParameterName(val) {
        this.lambdaParameterName = val;
    }
    isVariableUsed(variable) {
        return Boolean(this.propertyExpressionState.derivedPropertyExpressionStates.find((derived) => derived.parameterValues.find((param) => isValueExpressionReferencedInValue(variable, param))));
    }
    changeProperty(node, humanizePropertyName) {
        this.propertyExpressionState = new QueryBuilderPropertyExpressionState(this.tdsState.queryBuilderState, buildPropertyExpressionFromExplorerTreeNodeData(node, this.tdsState.queryBuilderState.explorerState));
        this.columnName = getPropertyChainName(this.propertyExpressionState.propertyExpression, humanizePropertyName);
    }
    getColumnType() {
        return this.propertyExpressionState.propertyExpression.func.value
            .genericType.value.rawType;
    }
    get hashCode() {
        return hashArray([
            QUERY_BUILDER_STATE_HASH_STRUCTURE.SIMPLE_PROJECTION_COLUMN_STATE,
            this.propertyExpressionState,
            this.columnName,
        ]);
    }
}
export class QueryBuilderRelationColumnProjectionColumnState extends QueryBuilderProjectionColumnState {
    lambdaParameterName = DEFAULT_LAMBDA_VARIABLE_NAME;
    column;
    constructor(tdsState, column, humanizePropertyName = true) {
        super(tdsState, column.name);
        makeObservable(this, {
            hashCode: override,
            lambdaParameterName: observable,
            setLambdaParameterName: action,
        });
        this.column = column;
        this.columnName = humanizePropertyName
            ? prettyCONSTName(column.name)
            : column.name;
    }
    getColumnType() {
        return this.column.genericType.value.rawType;
    }
    setLambdaParameterName(val) {
        this.lambdaParameterName = val;
    }
    isVariableUsed(_variable) {
        return false;
    }
    get hashCode() {
        return hashArray([
            QUERY_BUILDER_STATE_HASH_STRUCTURE.RELATION_COLUMN_PROJECTION_COLUMN_STATE,
            this.column.name,
            this.columnName,
        ]);
    }
}
class QueryBuilderDerivationProjectionLambdaState extends LambdaEditorState {
    queryBuilderState;
    derivationProjectionColumnState;
    /**
     * This is used to store the JSON string when viewing the query in JSON mode
     * TODO: consider moving this to another state if we need to simplify the logic of text-mode
     */
    readOnlylambdaJson = '';
    constructor(queryBuilderState, derivationProjectionColumnState) {
        super('', '');
        this.queryBuilderState = queryBuilderState;
        this.derivationProjectionColumnState = derivationProjectionColumnState;
    }
    get lambdaId() {
        return buildSourceInformationSourceId([
            // TODO: to be reworked
            // See https://github.com/finos/legend-studio/issues/1168
            QUERY_BUILDER_SOURCE_ID_LABEL.QUERY_BUILDER,
            QUERY_BUILDER_SOURCE_ID_LABEL.PROJECTION,
            this.derivationProjectionColumnState.uuid,
        ]);
    }
    setLambdaJson(lambdaJson) {
        this.readOnlylambdaJson = lambdaJson;
    }
    *convertLambdaGrammarStringToObject() {
        const emptyLambda = stub_RawLambda();
        if (this.lambdaString) {
            try {
                const lambda = (yield this.queryBuilderState.graphManagerState.graphManager.pureCodeToLambda(this.fullLambdaString, this.lambdaId));
                this.setParserError(undefined);
                this.derivationProjectionColumnState.setLambda(lambda);
            }
            catch (error) {
                assertErrorThrown(error);
                if (error instanceof ParserError) {
                    this.setParserError(error);
                }
                this.queryBuilderState.applicationStore.logService.error(LogEvent.create(GRAPH_MANAGER_EVENT.PARSING_FAILURE), error);
            }
        }
        else {
            this.clearErrors();
            this.derivationProjectionColumnState.setLambda(emptyLambda);
        }
    }
    *convertLambdaObjectToGrammarString(options) {
        if (this.derivationProjectionColumnState.lambda.body) {
            try {
                const lambdas = new Map();
                lambdas.set(this.lambdaId, new RawLambda(this.derivationProjectionColumnState.lambda.parameters, this.derivationProjectionColumnState.lambda.body));
                const isolatedLambdas = (yield this.queryBuilderState.graphManagerState.graphManager.lambdasToPureCode(lambdas, options?.pretty));
                const grammarText = isolatedLambdas.get(this.lambdaId);
                this.setLambdaString(grammarText !== undefined
                    ? this.extractLambdaString(grammarText)
                    : '');
                this.clearErrors({
                    preserveCompilationError: options?.preserveCompilationError,
                });
            }
            catch (error) {
                assertErrorThrown(error);
                this.queryBuilderState.applicationStore.logService.error(LogEvent.create(GRAPH_MANAGER_EVENT.PARSING_FAILURE), error);
            }
        }
        else {
            this.clearErrors();
            this.setLambdaString('');
        }
    }
}
export class QueryBuilderDerivationProjectionColumnState extends QueryBuilderProjectionColumnState {
    derivationLambdaEditorState;
    lambda;
    returnType;
    fetchingLambdaReturnTypeState = ActionState.create();
    constructor(tdsState, lambda) {
        super(tdsState, '(derivation)');
        makeObservable(this, {
            lambda: observable,
            returnType: observable,
            fetchingLambdaReturnTypeState: observable,
            setLambda: action,
            setResolvedLambdaReturnType: action,
            fetchDerivationLambdaReturnType: flow,
            setLambdaReturnType: action,
        });
        this.derivationLambdaEditorState =
            new QueryBuilderDerivationProjectionLambdaState(tdsState.queryBuilderState, this);
        this.lambda = lambda;
    }
    setLambda(val) {
        this.lambda = val;
    }
    setReturnType(val) {
        this.returnType = val;
    }
    /**
     * Fetches lambda return type for derivation column.
     * Throws error if unable to fetch type or if type is not primitive or an enumeration
     * as expected by a projection column
     */
    *fetchDerivationLambdaReturnType(options) {
        if (!options?.forceRefresh && this.returnType !== undefined) {
            return;
        }
        try {
            assertTrue(!this.fetchingLambdaReturnTypeState.isInProgress, 'Fetching lambda return type already in progress');
            this.fetchingLambdaReturnTypeState.inProgress();
            if (options?.isBeingDropped) {
                this.tdsState.postFilterState.setDerivedColumnBeingDropped(this);
            }
            if (options?.forceConversionStringToLambda) {
                yield flowResult(this.derivationLambdaEditorState.convertLambdaGrammarStringToObject()).catch(this.tdsState.queryBuilderState.applicationStore.alertUnhandledError);
            }
            assertTrue(Array.isArray(this.lambda.parameters));
            const graph = this.tdsState.queryBuilderState.graphManagerState.graph;
            const sourceElement = this.tdsState.queryBuilderState.sourceElement;
            if (sourceElement instanceof Class) {
                const isolatedLambda = this.getIsolatedClassRawLambda();
                const type = (yield this.tdsState.queryBuilderState.graphManagerState.graphManager.getLambdaReturnType(isolatedLambda, graph));
                this.setLambdaReturnType(type);
            }
            else if (sourceElement instanceof Accessor) {
                const isolatedLambda = this.getIsolatedAccessorRawLambda(sourceElement);
                const relationTypeMetadata = (yield this.tdsState.queryBuilderState.graphManagerState.graphManager.getLambdaRelationType(isolatedLambda, graph));
                const columnMetadata = relationTypeMetadata.columns.find((col) => col.name === this.columnName);
                if (columnMetadata) {
                    this.setLambdaReturnType(columnMetadata.type);
                }
            }
        }
        catch (error) {
            assertErrorThrown(error);
            this.tdsState.queryBuilderState.applicationStore.logService.info(LogEvent.create(GRAPH_MANAGER_EVENT.COMPILATION_FAILURE), error);
        }
        finally {
            this.fetchingLambdaReturnTypeState.complete();
            if (options?.isBeingDropped) {
                this.tdsState.postFilterState.setDerivedColumnBeingDropped(undefined);
            }
        }
    }
    getIsolatedAccessorRawLambda(_accessor) {
        const { graphManagerState } = this.tdsState.queryBuilderState;
        const isolatedLambda = _buildAccessorProjectRawLambda(graphManagerState.graph, graphManagerState, _accessor, new Map([[this.columnName, this.lambda]]));
        const enrichedLambda = _decorateRawLambdaWithParametersAndLetExpressions(isolatedLambda, this.tdsState.getParameters(), this.tdsState.getLetExpressions());
        return enrichedLambda;
    }
    getIsolatedClassRawLambda() {
        const _class = guaranteeType(this.tdsState.queryBuilderState.sourceElement, Class, 'Expected source element to be a class');
        assertTrue(Array.isArray(this.lambda.parameters));
        const projectionParameter = this.lambda.parameters;
        assertTrue(projectionParameter.length === 1);
        const variable = projectionParameter[0];
        assertNonEmptyString(variable.name);
        // assign variable to query class
        const queryBuilderState = this.tdsState.queryBuilderState;
        const rawVariableExpression = new RawVariableExpression(variable.name, Multiplicity.ONE, PackageableElementExplicitReference.create(guaranteeNonNullable(_class)));
        const _rawVariableExpression = queryBuilderState.graphManagerState.graphManager.serializeRawValueSpecification(rawVariableExpression);
        return _decorateRawLambdaWithParametersAndLetExpressions(new RawLambda([_rawVariableExpression], this.lambda.body), this.tdsState.getParameters(), this.tdsState.getLetExpressions());
    }
    setLambdaReturnType(type) {
        this.setResolvedLambdaReturnType(this.tdsState.queryBuilderState.graphManagerState.graph.getType(type));
    }
    setResolvedLambdaReturnType(resolvedType) {
        assertTrue(resolvedType instanceof PrimitiveType ||
            resolvedType instanceof Enumeration, 'Projection column must have return type of either primitve type or enumeration');
        this.setReturnType(resolvedType);
    }
    getColumnType() {
        return this.returnType;
    }
    get hashCode() {
        return hashArray([
            QUERY_BUILDER_STATE_HASH_STRUCTURE.DERIVATION_PROJECTION_COLUMN_STATE,
            this.returnType ?? '',
            this.lambda,
            this.columnName,
        ]);
    }
}
//# sourceMappingURL=QueryBuilderProjectionColumnState.js.map