import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
/**
 * 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 { useState, useRef, useCallback } from 'react';
import { flowResult } from 'mobx';
import { Dialog, ResizablePanelGroup, ResizablePanel, ResizablePanelSplitter, createFilter, CustomSelectorInput, BlankPanelPlaceholder, PanelLoadingIndicator, TimesIcon, PlayIcon, FlaskIcon, ResizablePanelSplitterLine, compareLabelFn, ControlledDropdownMenu, MenuContent, MenuContentItem, CaretDownIcon, RefreshIcon, RobotIcon, PanelDropZone, PencilIcon, PanelContent, Modal, ModalTitle, clsx, PanelHeaderActionItem, PanelHeader, PanelHeaderActions, Panel, PauseCircleIcon, } from '@finos/legend-art';
import { observer } from 'mobx-react-lite';
import { getMappingElementSource, getMappingElementTarget, getMappingElementLabel, } from '../../../../stores/editor/editor-state/element-editor-state/mapping/MappingEditorState.js';
import { useDrop } from 'react-dnd';
import { NewServiceModal } from '../service-editor/NewServiceModal.js';
import { CORE_DND_TYPE, } from '../../../../stores/editor/utils/DnDUtils.js';
import { assertErrorThrown, guaranteeType, uniq } from '@finos/legend-shared';
import { MappingExecutionEmptyInputDataState, MappingExecutionObjectInputDataState, MappingExecutionFlatDataInputDataState, MappingExecutionRelationalInputDataState, } from '../../../../stores/editor/editor-state/element-editor-state/mapping/MappingExecutionState.js';
import { ActionAlertActionType, ActionAlertType, useApplicationStore, } from '@finos/legend-application';
import { useEditorStore } from '../../EditorStoreProvider.js';
import { Class, SetImplementation, OperationSetImplementation, getAllClassMappings, RelationalInputType, stub_RawLambda, isStubbed_RawLambda, } from '@finos/legend-graph';
import { objectInputData_setData } from '../../../../stores/graph-modifier/DSL_Mapping_GraphModifierHelper.js';
import { flatData_setData } from '../../../../stores/graph-modifier/STO_FlatData_GraphModifierHelper.js';
import { relationalInputData_setData, relationalInputData_setInputType, } from '../../../../stores/graph-modifier/STO_Relational_GraphModifierHelper.js';
import { MappingExecutionQueryBuilderState } from '../../../../stores/editor/editor-state/element-editor-state/mapping/MappingExecutionQueryBuilderState.js';
import { ExecutionPlanViewer, } from '@finos/legend-query-builder';
import { CODE_EDITOR_LANGUAGE } from '@finos/legend-code-editor';
import { CodeEditor } from '@finos/legend-lego/code-editor';
export const ClassMappingSelectorModal = observer((props) => {
    const { mappingEditorState, changeClassMapping, hideClassMappingSelectorModal, classMappingFilterFn, } = props;
    const editorStore = useEditorStore();
    const applicationStore = editorStore.applicationStore;
    // Class mapping selector
    const classMappingSelectorRef = useRef(null);
    const filterOption = createFilter({
        ignoreCase: true,
        ignoreAccents: false,
        stringify: (option) => getMappingElementLabel(option.data.value, editorStore).value,
    });
    const classMappingOptions = uniq(getAllClassMappings(mappingEditorState.mapping)
        .filter((classMapping) => !classMappingFilterFn || classMappingFilterFn(classMapping))
        .map((classMapping) => ({
        label: getMappingElementLabel(classMapping, editorStore).value,
        value: classMapping,
    }))
        .sort(compareLabelFn));
    const handleEnterClassMappingSelectorModal = () => classMappingSelectorRef.current?.focus();
    const changeClassMappingOption = (val) => changeClassMapping(val.value);
    return (_jsx(Dialog, { open: true, onClose: hideClassMappingSelectorModal, classes: { container: 'search-modal__container' }, slotProps: {
            transition: {
                onEnter: handleEnterClassMappingSelectorModal,
            },
            paper: {
                classes: { root: 'search-modal__inner-container' },
            },
        }, children: _jsxs(Modal, { className: clsx('search-modal', {
                'modal--dark': true,
            }), children: [_jsx(ModalTitle, { title: "Choose a class mapping" }), _jsx(CustomSelectorInput, { inputRef: classMappingSelectorRef, options: classMappingOptions, onChange: changeClassMappingOption, value: null, placeholder: "Choose a class mapping...", filterOption: filterOption, isClearable: true, darkMode: !applicationStore.layoutService
                        .TEMPORARY__isLightColorThemeEnabled })] }) }));
});
export const getRelationalInputTestDataEditorLanguage = (type) => {
    switch (type) {
        case RelationalInputType.SQL:
            return CODE_EDITOR_LANGUAGE.SQL;
        default:
            return CODE_EDITOR_LANGUAGE.TEXT;
    }
};
const MappingExecutionQueryEditor = observer((props) => {
    const { executionState } = props;
    const queryState = executionState.queryState;
    const mappingEditorState = executionState.mappingEditorState;
    const editorStore = useEditorStore();
    const applicationStore = useApplicationStore();
    // actions
    const editWithQueryBuilder = applicationStore.guardUnhandledError(async () => {
        const embeddedQueryBuilderState = editorStore.embeddedQueryBuilderState;
        await flowResult(embeddedQueryBuilderState.setEmbeddedQueryBuilderConfiguration({
            setupQueryBuilderState: async () => {
                const queryBuilderState = new MappingExecutionQueryBuilderState(embeddedQueryBuilderState.editorStore.applicationStore, embeddedQueryBuilderState.editorStore.graphManagerState, executionState.mappingEditorState.mapping, editorStore.applicationStore.config.options.queryBuilderConfig, editorStore.editorMode.getSourceInfo());
                queryBuilderState.initializeWithQuery(executionState.queryState.query);
                return queryBuilderState;
            },
            actionConfigs: [
                {
                    key: 'save-query-btn',
                    renderer: (queryBuilderState) => {
                        const save = applicationStore.guardUnhandledError(async () => {
                            try {
                                const rawLambda = queryBuilderState.buildQuery();
                                await flowResult(executionState.queryState.updateLamba(rawLambda));
                                applicationStore.notificationService.notifySuccess(`Mapping execution query is updated`);
                                embeddedQueryBuilderState.setEmbeddedQueryBuilderConfiguration(undefined);
                            }
                            catch (error) {
                                assertErrorThrown(error);
                                applicationStore.notificationService.notifyError(`Can't save query: ${error.message}`);
                            }
                        });
                        return (_jsx("button", { className: "query-builder__dialog__header__custom-action", tabIndex: -1, onClick: save, children: "Save Query" }));
                    },
                },
            ],
            disableCompile: isStubbed_RawLambda(executionState.queryState.query),
        }));
    });
    // Class mapping selector
    const [openClassMappingSelectorModal, setOpenClassMappingSelectorModal] = useState(false);
    const showClassMappingSelectorModal = () => setOpenClassMappingSelectorModal(true);
    const hideClassMappingSelectorModal = () => setOpenClassMappingSelectorModal(false);
    const changeClassMapping = useCallback((setImplementation) => {
        // do all the necessary updates
        executionState.setExecutionResultText(undefined);
        flowResult(queryState.updateLamba(setImplementation
            ? editorStore.graphManagerState.graphManager.createGetAllRawLambda(guaranteeType(getMappingElementTarget(setImplementation), Class))
            : stub_RawLambda())).catch(applicationStore.alertUnhandledError);
        hideClassMappingSelectorModal();
        // Attempt to generate data for input data panel as we pick the class mapping:
        // - If the source panel is empty right now, automatically try to generate input data:
        //   - We generate based on the class mapping, if it's concrete
        //   - If the class mapping is operation, output a warning message
        // - If the source panel is non-empty (show modal), show an option to keep current input data
        if (setImplementation) {
            if (executionState.inputDataState instanceof
                MappingExecutionEmptyInputDataState) {
                if (setImplementation instanceof OperationSetImplementation) {
                    applicationStore.notificationService.notifyWarning(`Can't auto-generate input data for operation class mapping. Please pick a concrete class mapping instead`);
                }
                else {
                    executionState.setInputDataStateBasedOnSource(getMappingElementSource(setImplementation, editorStore.pluginManager.getApplicationPlugins()), true);
                }
            }
            else {
                applicationStore.alertService.setActionAlertInfo({
                    message: 'Mapping execution input data is already set',
                    prompt: 'Do you want to regenerate the input data?',
                    type: ActionAlertType.CAUTION,
                    actions: [
                        {
                            label: 'Regenerate',
                            type: ActionAlertActionType.PROCEED_WITH_CAUTION,
                            handler: () => executionState.setInputDataStateBasedOnSource(getMappingElementSource(setImplementation, editorStore.pluginManager.getApplicationPlugins()), true),
                        },
                        {
                            label: 'Keep my input data',
                            type: ActionAlertActionType.PROCEED,
                            default: true,
                        },
                    ],
                });
            }
        }
        // TODO: open query builder
    }, [applicationStore, editorStore, executionState, queryState]);
    // Drag and Drop
    const handleDrop = useCallback((item) => {
        changeClassMapping(guaranteeType(item.data, SetImplementation));
    }, [changeClassMapping]);
    const [{ isDragOver, canDrop }, dropConnector] = useDrop(() => ({
        accept: CORE_DND_TYPE.MAPPING_EXPLORER_CLASS_MAPPING,
        drop: (item) => handleDrop(item),
        collect: (monitor) => ({
            isDragOver: monitor.isOver({ shallow: true }),
            canDrop: monitor.canDrop(),
        }),
    }), [handleDrop]);
    const clearQuery = applicationStore.guardUnhandledError(() => flowResult(executionState.queryState.updateLamba(stub_RawLambda())));
    return (_jsxs(Panel, { className: "mapping-execution-builder__query-panel", children: [_jsx(PanelHeader, { title: "query", children: _jsxs(PanelHeaderActions, { children: [_jsx(PanelHeaderActionItem, { onClick: editWithQueryBuilder, title: "Edit query...", children: _jsx(PencilIcon, {}) }), _jsx(PanelHeaderActionItem, { onClick: clearQuery, title: "Clear query", children: _jsx(TimesIcon, {}) })] }) }), !isStubbed_RawLambda(queryState.query) && (_jsx(PanelContent, { children: _jsx("div", { className: "mapping-execution-builder__query-panel__query", children: _jsx(CodeEditor, { inputValue: queryState.lambdaString, isReadOnly: true, language: CODE_EDITOR_LANGUAGE.PURE, hideMinimap: true }) }) })), isStubbed_RawLambda(queryState.query) && (_jsx(PanelContent, { children: _jsx(PanelDropZone, { dropTargetConnector: dropConnector, isDragOver: isDragOver, children: _jsx(BlankPanelPlaceholder, { text: "Choose a class mapping", onClick: showClassMappingSelectorModal, clickActionType: "add", tooltipText: "Drop a class mapping, or click to choose one to start building the query", isDropZoneActive: canDrop }) }) })), openClassMappingSelectorModal && (_jsx(ClassMappingSelectorModal, { mappingEditorState: mappingEditorState, hideClassMappingSelectorModal: hideClassMappingSelectorModal, changeClassMapping: changeClassMapping }))] }));
});
export const MappingExecutionObjectInputDataBuilder = observer((props) => {
    const { inputDataState } = props;
    // TODO?: handle XML/type
    // Input data
    const updateInput = (val) => objectInputData_setData(inputDataState.inputData, val);
    return (_jsx(PanelContent, { className: "mapping-execution-builder__input-data-panel__content", children: _jsx(CodeEditor, { language: CODE_EDITOR_LANGUAGE.JSON, inputValue: inputDataState.inputData.data, updateInput: updateInput }) }));
});
export const MappingExecutionFlatDataInputDataBuilder = observer((props) => {
    const { inputDataState } = props;
    // Input data
    const updateInput = (val) => flatData_setData(inputDataState.inputData, val);
    return (_jsx(PanelContent, { className: "mapping-execution-builder__input-data-panel__content", children: _jsx(CodeEditor, { language: CODE_EDITOR_LANGUAGE.TEXT, inputValue: inputDataState.inputData.data, updateInput: updateInput }) }));
});
/**
 * Right now, we always default this to use Local H2 connection.
 */
export const MappingExecutionRelationalInputDataBuilder = observer((props) => {
    const { inputDataState } = props;
    // Input data
    const updateInput = (val) => relationalInputData_setData(inputDataState.inputData, val);
    return (_jsx(PanelContent, { className: "mapping-execution-builder__input-data-panel__content", children: _jsx(CodeEditor, { language: getRelationalInputTestDataEditorLanguage(inputDataState.inputData.inputType), inputValue: inputDataState.inputData.data, updateInput: updateInput }) }));
});
export const MappingExecutionEmptyInputDataBuilder = observer((props) => {
    const { changeClassMapping, showClassMappingSelectorModal } = props;
    // Drag and Drop
    const handleDrop = useCallback((item) => {
        changeClassMapping(guaranteeType(item.data, SetImplementation));
    }, [changeClassMapping]);
    const [{ isDragOver, canDrop }, dropConnector] = useDrop(() => ({
        accept: CORE_DND_TYPE.MAPPING_EXPLORER_CLASS_MAPPING,
        drop: (item) => handleDrop(item),
        collect: (monitor) => ({
            isDragOver: monitor.isOver({ shallow: true }),
            canDrop: monitor.canDrop(),
        }),
    }), [handleDrop]);
    return (_jsx(PanelContent, { children: _jsx(PanelDropZone, { dropTargetConnector: dropConnector, isDragOver: isDragOver, children: _jsx(BlankPanelPlaceholder, { text: "Choose a class mapping", onClick: showClassMappingSelectorModal, clickActionType: "add", tooltipText: "Drop a class mapping, or click to choose one to generate input data", isDropZoneActive: canDrop }) }) }));
});
const RelationalMappingExecutionInputDataTypeSelector = observer((props) => {
    const { inputDataState } = props;
    const changeInputType = (val) => () => {
        relationalInputData_setInputType(inputDataState.inputData, val);
    };
    return (_jsx(ControlledDropdownMenu, { className: "mapping-execution-builder__input-data-panel__type-selector", title: "Choose input data type...", content: _jsx(MenuContent, { children: Object.keys(RelationalInputType).map((mode) => (_jsx(MenuContentItem, { className: "mapping-execution-builder__input-data-panel__type-selector__option", onClick: changeInputType(mode), children: mode }, mode))) }), children: _jsxs("div", { className: "mapping-execution-builder__input-data-panel__type-selector__value", children: [_jsx("div", { className: "mapping-execution-builder__input-data-panel__type-selector__value__label", children: inputDataState.inputData.inputType }), _jsx(CaretDownIcon, {})] }) }));
});
export const MappingExecutionInputDataBuilder = observer((props) => {
    const { executionState } = props;
    const editorStore = useEditorStore();
    const mappingEditorState = executionState.mappingEditorState;
    const inputDataState = executionState.inputDataState;
    // Class mapping selector
    const [openClassMappingSelectorModal, setOpenClassMappingSelectorModal] = useState(false);
    const showClassMappingSelectorModal = () => setOpenClassMappingSelectorModal(true);
    const hideClassMappingSelectorModal = () => setOpenClassMappingSelectorModal(false);
    const changeClassMapping = useCallback((setImplementation) => {
        executionState.setInputDataStateBasedOnSource(setImplementation
            ? getMappingElementSource(setImplementation, editorStore.pluginManager.getApplicationPlugins())
            : undefined, true);
        executionState.setExecutionResultText(undefined);
        hideClassMappingSelectorModal();
    }, [executionState, editorStore]);
    const classMappingFilterFn = (setImp) => !(setImp instanceof OperationSetImplementation);
    // Input data builder
    let inputDataBuilder;
    if (inputDataState instanceof MappingExecutionEmptyInputDataState) {
        inputDataBuilder = (_jsx(MappingExecutionEmptyInputDataBuilder, { inputDataState: inputDataState, showClassMappingSelectorModal: showClassMappingSelectorModal, changeClassMapping: changeClassMapping }));
    }
    else if (inputDataState instanceof MappingExecutionObjectInputDataState) {
        inputDataBuilder = (_jsx(MappingExecutionObjectInputDataBuilder, { inputDataState: inputDataState }));
    }
    else if (inputDataState instanceof MappingExecutionFlatDataInputDataState) {
        inputDataBuilder = (_jsx(MappingExecutionFlatDataInputDataBuilder, { inputDataState: inputDataState }));
    }
    else if (inputDataState instanceof MappingExecutionRelationalInputDataState) {
        inputDataBuilder = (_jsx(MappingExecutionRelationalInputDataBuilder, { inputDataState: inputDataState }));
    }
    else {
        inputDataBuilder = null;
    }
    // input type builder
    let inputTypeSelector;
    if (inputDataState instanceof MappingExecutionRelationalInputDataState) {
        inputTypeSelector = (_jsx(RelationalMappingExecutionInputDataTypeSelector, { inputDataState: inputDataState }));
    }
    else {
        inputTypeSelector = null;
    }
    const clearInputData = () => executionState.setInputDataState(new MappingExecutionEmptyInputDataState(mappingEditorState.editorStore, mappingEditorState.mapping, undefined));
    return (_jsxs("div", { className: "panel mapping-execution-builder__input-data-panel", children: [_jsxs("div", { className: "panel__header", children: [_jsx("div", { className: "panel__header__title", children: _jsx("div", { className: "panel__header__title__label", children: "input data" }) }), _jsxs("div", { className: "panel__header__actions", children: [inputTypeSelector, _jsx("button", { className: "panel__header__action", tabIndex: -1, onClick: showClassMappingSelectorModal, title: "Regenerate...", children: _jsx(RefreshIcon, { className: "mapping-execution-builder__icon--refresh" }) }), _jsx("button", { className: "panel__header__action", tabIndex: -1, onClick: clearInputData, title: "Clear input data", children: _jsx(TimesIcon, {}) })] })] }), inputDataBuilder, openClassMappingSelectorModal && (_jsx(ClassMappingSelectorModal, { mappingEditorState: mappingEditorState, hideClassMappingSelectorModal: hideClassMappingSelectorModal, changeClassMapping: changeClassMapping, classMappingFilterFn: classMappingFilterFn }))] }));
});
export const MappingExecutionBuilder = observer((props) => {
    const { executionState } = props;
    const mappingEditorState = executionState.mappingEditorState;
    const applicationStore = useApplicationStore();
    const { queryState, inputDataState } = executionState;
    // execute
    const cancelExecution = applicationStore.guardUnhandledError(() => flowResult(executionState.cancelExecution()));
    const generatePlan = applicationStore.guardUnhandledError(() => flowResult(executionState.generatePlan(false)));
    const debugPlanGeneration = applicationStore.guardUnhandledError(() => flowResult(executionState.generatePlan(true)));
    const execute = applicationStore.guardUnhandledError(() => flowResult(executionState.executeMapping()));
    const executionResultText = executionState.executionResultText;
    // actions
    const promote = applicationStore.guardUnhandledError(() => flowResult(executionState.promoteToTest()));
    const promoteToService = () => executionState.setShowServicePathModal(true);
    return (_jsxs("div", { className: "mapping-execution-builder", children: [_jsx(PanelLoadingIndicator, { isLoading: executionState.isExecuting || executionState.isGeneratingPlan }), _jsxs("div", { className: "mapping-execution-builder__header", children: [_jsx("div", {}), _jsxs("div", { className: "mapping-execution-builder__header__actions", children: [!mappingEditorState.isReadOnly && (_jsx("button", { className: "mapping-execution-builder__header__action", disabled: isStubbed_RawLambda(queryState.query) ||
                                    !inputDataState.isValid ||
                                    executionState.isExecuting ||
                                    !executionState.executionResultText, onClick: promoteToService, tabIndex: -1, title: "Promote to Service...", children: _jsx(RobotIcon, {}) })), !mappingEditorState.isReadOnly && (_jsx("button", { className: "mapping-execution-builder__header__action", disabled: isStubbed_RawLambda(queryState.query) ||
                                    !inputDataState.isValid ||
                                    executionState.isExecuting ||
                                    !executionState.executionResultText, onClick: promote, tabIndex: -1, title: "Promote to Test", children: _jsx(FlaskIcon, {}) })), _jsx("div", { className: "mapping-execution-builder__action-btn btn__dropdown-combo btn__dropdown-combo--primary", children: executionState.isExecuting ? (_jsx("button", { className: "btn__dropdown-combo__canceler", onClick: cancelExecution, tabIndex: -1, children: _jsxs("div", { className: "btn--dark btn--caution btn__dropdown-combo__canceler__label", children: [_jsx(PauseCircleIcon, { className: "btn__dropdown-combo__canceler__label__icon" }), _jsx("div", { className: "btn__dropdown-combo__canceler__label__title", children: "Stop" })] }) })) : (_jsxs(_Fragment, { children: [_jsxs("button", { className: "btn__dropdown-combo__label", onClick: execute, disabled: isStubbed_RawLambda(queryState.query) ||
                                                !inputDataState.isValid ||
                                                executionState.isGeneratingPlan ||
                                                executionState.isExecuting, tabIndex: -1, children: [_jsx(PlayIcon, { className: "btn__dropdown-combo__label__icon" }), _jsx("div", { className: "btn__dropdown-combo__label__title", children: "Run Query" })] }), _jsx(ControlledDropdownMenu, { className: "btn__dropdown-combo__dropdown-btn", disabled: isStubbed_RawLambda(queryState.query) ||
                                                !inputDataState.isValid ||
                                                executionState.isGeneratingPlan ||
                                                executionState.isExecuting, content: _jsxs(MenuContent, { children: [_jsx(MenuContentItem, { className: "btn__dropdown-combo__option", onClick: generatePlan, children: "Generate Plan" }), _jsx(MenuContentItem, { className: "btn__dropdown-combo__option", onClick: debugPlanGeneration, children: "Debug" })] }), menuProps: {
                                                anchorOrigin: { vertical: 'bottom', horizontal: 'right' },
                                                transformOrigin: { vertical: 'top', horizontal: 'right' },
                                            }, children: _jsx(CaretDownIcon, {}) })] })) })] })] }), _jsx("div", { className: "mapping-execution-builder__content", children: _jsxs(ResizablePanelGroup, { orientation: "horizontal", children: [_jsx(ResizablePanel, { size: 250, minSize: 28, children: _jsx(MappingExecutionQueryEditor, { executionState: executionState }, executionState.queryState.uuid) }), _jsx(ResizablePanelSplitter, { children: _jsx(ResizablePanelSplitterLine, { color: "var(--color-dark-grey-50)" }) }), _jsx(ResizablePanel, { size: 250, minSize: 28, children: _jsx(MappingExecutionInputDataBuilder, { executionState: executionState }, executionState.inputDataState.uuid) }), _jsx(ResizablePanelSplitter, { children: _jsx(ResizablePanelSplitterLine, { color: "var(--color-dark-grey-50)" }) }), _jsx(ResizablePanel, { minSize: 28, children: _jsxs(Panel, { className: "mapping-execution-builder__result-panel", children: [_jsx(PanelHeader, { title: "result" }), _jsx(PanelContent, { className: "mapping-execution-builder__result-panel__content", children: _jsx(CodeEditor, { inputValue: executionResultText ?? '', isReadOnly: true, language: CODE_EDITOR_LANGUAGE.JSON }) })] }) })] }) }), _jsx(ExecutionPlanViewer, { executionPlanState: executionState.executionPlanState }), _jsx(NewServiceModal, { mapping: mappingEditorState.mapping, close: () => executionState.setShowServicePathModal(false), showModal: executionState.showServicePathModal, promoteToService: (packagePath, name) => flowResult(executionState.promoteToService(packagePath, name)), isReadOnly: mappingEditorState.isReadOnly })] }));
});
//# sourceMappingURL=MappingExecutionBuilder.js.map