/**
 * 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, computed, makeObservable, observable } from 'mobx';
import { PackageableElementExplicitReference, RootGraphFetchTree, getAllSuperclasses, PropertyGraphFetchTree, } from '@finos/legend-graph';
import { addQueryBuilderPropertyNode, buildGraphFetchTreeData, } from './QueryBuilderGraphFetchTreeUtil.js';
import { generateExplorerTreePropertyNodeID, generateExplorerTreeSubtypeNodeID, } from '../../explorer/QueryBuilderExplorerState.js';
import { FETCH_STRUCTURE_IMPLEMENTATION, QueryBuilderFetchStructureImplementationState, } from '../QueryBuilderFetchStructureImplementationState.js';
import { ActionAlertActionType, ActionAlertType, } from '@finos/legend-application';
import { appendGraphFetch } from './QueryBuilderGraphFetchTreeValueSpecificationBuilder.js';
import { deepClone, guaranteeNonNullable, hashArray, UnsupportedOperationError, ContentType, } from '@finos/legend-shared';
import { QUERY_BUILDER_STATE_HASH_STRUCTURE } from '../../QueryBuilderStateHashUtils.js';
import { isValueExpressionReferencedInValue } from '../../QueryBuilderValueSpecificationHelper.js';
export var SERIALIZATION_TYPE;
(function (SERIALIZATION_TYPE) {
    SERIALIZATION_TYPE["PURE"] = "PURE";
    SERIALIZATION_TYPE["EXTERNAL_FORMAT"] = "EXTERNAL_FORMAT";
})(SERIALIZATION_TYPE || (SERIALIZATION_TYPE = {}));
export var GRAPH_FETCH_EXPORT_DATA_FORMAT;
(function (GRAPH_FETCH_EXPORT_DATA_FORMAT) {
    GRAPH_FETCH_EXPORT_DATA_FORMAT["RESULT"] = "RESULT";
})(GRAPH_FETCH_EXPORT_DATA_FORMAT || (GRAPH_FETCH_EXPORT_DATA_FORMAT = {}));
const DEFAULT_PURE_CONFIG_TYPE_NAME = '@type';
export class PureSerializationConfig {
    typeKeyName;
    dateTimeFormat;
    includeType;
    includeEnumType;
    removePropertiesWithNullValues;
    removePropertiesWithEmptySets;
    fullyQualifiedTypePath;
    includeObjectReference;
    constructor() {
        this.typeKeyName = DEFAULT_PURE_CONFIG_TYPE_NAME;
        makeObservable(this, {
            typeKeyName: observable,
            includeType: observable,
            includeEnumType: observable,
            dateTimeFormat: observable,
            removePropertiesWithNullValues: observable,
            removePropertiesWithEmptySets: observable,
            fullyQualifiedTypePath: observable,
            includeObjectReference: observable,
            setTypeName: action,
            setIncludeObjectReference: action,
            setDateTimeFormat: action,
            setIncludeType: action,
            setFullyQualifiedTypePath: action,
            setRemovePropertiesWithEmptySets: action,
            setInclueEnumType: action,
            setRemovePropertiesWithNullValues: action,
        });
    }
    static createDefault() {
        const config = new PureSerializationConfig();
        config.typeKeyName = DEFAULT_PURE_CONFIG_TYPE_NAME;
        config.includeType = true;
        config.includeEnumType = true;
        config.removePropertiesWithNullValues = true;
        config.removePropertiesWithEmptySets = false;
        config.fullyQualifiedTypePath = true;
        return config;
    }
    setDateTimeFormat(val) {
        this.dateTimeFormat = val;
    }
    setTypeName(val) {
        this.typeKeyName = val;
    }
    setIncludeType(val) {
        this.includeType = val;
    }
    setInclueEnumType(val) {
        this.includeEnumType = val;
    }
    setRemovePropertiesWithNullValues(val) {
        this.removePropertiesWithNullValues = val;
    }
    setRemovePropertiesWithEmptySets(val) {
        this.removePropertiesWithEmptySets = val;
    }
    setFullyQualifiedTypePath(val) {
        this.fullyQualifiedTypePath = val;
    }
    setIncludeObjectReference(val) {
        this.includeObjectReference = val;
    }
}
export class GraphFetchSerializationState {
    queryBuilderGraphFetchTreeState;
    constructor(graphFetchTreeState) {
        this.queryBuilderGraphFetchTreeState = graphFetchTreeState;
    }
}
export class GraphFetchPureSerializationState extends GraphFetchSerializationState {
    config;
    configModal = false;
    constructor(graphFetchTreeState) {
        super(graphFetchTreeState);
        makeObservable(this, {
            config: observable,
            configModal: observable,
            setConfigModal: action,
        });
    }
    setConfig(value) {
        this.config = value;
    }
    setConfigModal(val) {
        this.configModal = val;
    }
    getLabel() {
        return SERIALIZATION_TYPE.PURE;
    }
    get serializationContentType() {
        return ContentType.APPLICATION_JSON;
    }
}
export class GraphFetchExternalFormatSerializationState extends GraphFetchSerializationState {
    targetBinding;
    treeData;
    constructor(graphFetchTreeState, targetBinding, treeData) {
        super(graphFetchTreeState);
        makeObservable(this, {
            targetBinding: observable,
            treeData: observable.ref,
            serializationContentType: computed,
            setGraphFetchTree: action,
        });
        this.targetBinding = targetBinding;
        this.treeData = treeData;
    }
    setBinding(value) {
        this.targetBinding = value;
    }
    setGraphFetchTree(val) {
        this.treeData = val;
    }
    addProperty(node, options) {
        if (!this.treeData) {
            this.queryBuilderGraphFetchTreeState.queryBuilderState.applicationStore.notificationService.notifyWarning(`Can't add property: graph-fetch tree has not been properly initialized`);
            return;
        }
        addQueryBuilderPropertyNode(this.treeData, this.queryBuilderGraphFetchTreeState.queryBuilderState.explorerState
            .nonNullableTreeData, node, this.queryBuilderGraphFetchTreeState.queryBuilderState);
        if (options?.refreshTreeData) {
            this.setGraphFetchTree({ ...this.treeData });
        }
    }
    getLabel() {
        return SERIALIZATION_TYPE.EXTERNAL_FORMAT;
    }
    get serializationContentType() {
        const contentType = this.targetBinding.contentType;
        if (Object.values(ContentType).includes(contentType)) {
            return contentType;
        }
        else {
            // TEMP: need to investigate if flatdata should be returned as content type
            // for now we will assume all flatdata is csv
            if (contentType === 'application/x.flatdata') {
                return ContentType.TEXT_CSV;
            }
            return ContentType.TEXT_PLAIN;
        }
    }
}
export class QueryBuilderGraphFetchTreeState extends QueryBuilderFetchStructureImplementationState {
    treeData;
    /**
     * If set to `true` we will use `graphFetchChecked` function instead of `graphFetch`.
     * `graphFetchChecked` will do extra checks on constraints and only work on M2M use case for now.
     * Hence we default this to `false` for graph fetch to work universally.
     */
    isChecked = false;
    /**
     * Used to describe how the graph fetch tree is serialized to the final result set
     */
    serializationState;
    constructor(queryBuilderState, fetchStructureState) {
        super(queryBuilderState, fetchStructureState);
        makeObservable(this, {
            treeData: observable.ref,
            isChecked: observable,
            serializationState: observable,
            TEMPORARY__showPostFetchStructurePanel: computed,
            setGraphFetchTree: action,
            setSerializationState: action,
            setChecked: action,
            initialize: action,
            checkBeforeChangingImplementation: action,
        });
        // try to initialize the graph-fetch tree data using the setup class
        this.updateTreeData(this.queryBuilderState.class);
        // we will default to standard pure serialization with no config
        this.serializationState = new GraphFetchPureSerializationState(this);
    }
    get type() {
        return FETCH_STRUCTURE_IMPLEMENTATION.GRAPH_FETCH;
    }
    get fetchLabel() {
        return 'Class Properties';
    }
    get canBeExportedToCube() {
        return false;
    }
    get usedExplorerTreePropertyNodeIDs() {
        if (!this.treeData) {
            return [];
        }
        const explorerTreeNodeIDIndex = new Map();
        const ids = [];
        // traverse in breadth-first fashion
        const nodesToProcess = this.treeData.rootIds.slice();
        while (nodesToProcess.length) {
            const currentNodeID = guaranteeNonNullable(nodesToProcess[0]);
            const node = this.treeData.nodes.get(currentNodeID);
            if (!node) {
                continue;
            }
            let nodeID;
            const parentNodeID = node.parentId
                ? // since we traverse the nodes in order, parent node ID should already been computed
                    guaranteeNonNullable(explorerTreeNodeIDIndex.get(node.parentId))
                : '';
            let generateID = '';
            if (node.tree instanceof RootGraphFetchTree) {
                generateID = `root.${node.tree.class.valueForSerialization ?? ''}`;
            }
            else if (node.tree instanceof PropertyGraphFetchTree) {
                generateID = node.tree.property.value.name;
            }
            const propertyNodeID = generateExplorerTreePropertyNodeID(parentNodeID, generateID);
            ids.push(propertyNodeID);
            if (node.tree instanceof PropertyGraphFetchTree && node.tree.subType) {
                nodeID = generateExplorerTreeSubtypeNodeID(propertyNodeID, node.tree.subType.value.path);
                getAllSuperclasses(node.tree.subType.value)
                    .concat(node.tree.subType.value)
                    .forEach((_class) => ids.push(generateExplorerTreeSubtypeNodeID(propertyNodeID, _class.path)));
            }
            else {
                nodeID = propertyNodeID;
            }
            explorerTreeNodeIDIndex.set(node.id, nodeID);
            // update list of nodes to process
            nodesToProcess.shift();
            node.childrenIds.forEach((childId) => nodesToProcess.push(childId));
        }
        // de-duplicate
        return Array.from(new Set(ids).values());
    }
    get fetchStructureValidationIssues() {
        return [];
    }
    get allValidationIssues() {
        return [];
    }
    initialize() {
        this.queryBuilderState.filterState.setShowPanel(true);
    }
    setSerializationState(val) {
        this.serializationState = val;
    }
    setGraphFetchTree(val) {
        this.treeData = val;
    }
    setChecked(val) {
        this.isChecked = val;
    }
    updateTreeData(_class) {
        this.setGraphFetchTree(_class
            ? buildGraphFetchTreeData(new RootGraphFetchTree(PackageableElementExplicitReference.create(_class)))
            : undefined);
    }
    get exportDataFormatOptions() {
        return [GRAPH_FETCH_EXPORT_DATA_FORMAT.RESULT];
    }
    getExportDataInfo(format) {
        if (format === GRAPH_FETCH_EXPORT_DATA_FORMAT.RESULT) {
            return {
                contentType: this.serializationState.serializationContentType,
            };
        }
        else {
            throw new UnsupportedOperationError(`Unsupported Graph Fetch export type ${format}`);
        }
    }
    initializeWithQuery() {
        return;
    }
    onClassChange(_class) {
        this.updateTreeData(_class);
    }
    appendFetchStructure(lambdaFunction, options) {
        appendGraphFetch(this, lambdaFunction, options);
    }
    addProperty(node, options) {
        if (!this.treeData) {
            this.queryBuilderState.applicationStore.notificationService.notifyWarning(`Can't add property: graph-fetch tree has not been properly initialized`);
            return;
        }
        addQueryBuilderPropertyNode(this.treeData, this.queryBuilderState.explorerState.nonNullableTreeData, node, this.queryBuilderState);
        if (options?.refreshTreeData) {
            this.setGraphFetchTree({ ...this.treeData });
        }
    }
    revealCompilationError(compilationError) {
        return false;
    }
    clearCompilationError() {
        return;
    }
    fetchProperty(node) {
        this.addProperty(node, { refreshTreeData: true });
        if (this.serializationState instanceof
            GraphFetchExternalFormatSerializationState) {
            this.serializationState.addProperty(deepClone(node), {
                refreshTreeData: true,
            });
        }
    }
    fetchProperties(nodes) {
        if (!this.treeData) {
            this.queryBuilderState.applicationStore.notificationService.notifyWarning(`Can't add property: graph-fetch tree has not been properly initialized`);
            return;
        }
        nodes.forEach((nodeToAdd) => this.addProperty(nodeToAdd));
        this.setGraphFetchTree({
            ...this.treeData,
        });
    }
    checkBeforeChangingImplementation(onChange) {
        if (this.treeData?.rootIds.length) {
            this.queryBuilderState.applicationStore.alertService.setActionAlertInfo({
                message: 'Current graph-fetch will be lost when switching to projection mode. Do you still want to proceed?',
                type: ActionAlertType.CAUTION,
                actions: [
                    {
                        label: 'Proceed',
                        type: ActionAlertActionType.PROCEED_WITH_CAUTION,
                        handler: this.queryBuilderState.applicationStore.guardUnhandledError(async () => onChange()),
                    },
                    {
                        label: 'Cancel',
                        type: ActionAlertActionType.PROCEED,
                        default: true,
                    },
                ],
            });
        }
        else {
            onChange();
        }
    }
    isVariableUsed(variable) {
        return Boolean(Array.from(this.treeData?.nodes.values() ?? []).find((node) => {
            if (node.tree instanceof PropertyGraphFetchTree) {
                return node.tree.parameters.find((p) => isValueExpressionReferencedInValue(variable, p));
            }
            return undefined;
        }));
    }
    get hasInvalidFilterValues() {
        return false;
    }
    get hasInvalidDerivedPropertyParameters() {
        return false;
    }
    get hashCode() {
        return hashArray([
            QUERY_BUILDER_STATE_HASH_STRUCTURE.GRAPH_FETCH_STATE,
            this.isChecked.toString(),
            this.treeData?.tree ?? '',
        ]);
    }
}
//# sourceMappingURL=QueryBuilderGraphFetchTreeState.js.map