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, useEffect, useCallback } from 'react';
import { observer } from 'mobx-react-lite';
import { clsx, BlankPanelPlaceholder, ResizablePanelGroup, ResizablePanel, ResizablePanelSplitter, AsteriskIcon, LongArrowAltDownIcon, PencilEditIcon, PanelDropZone, PanelContent, PanelHeaderActionItem, PanelHeaderActions, PanelHeader, } from '@finos/legend-art';
import { CORE_DND_TYPE, } from '../../../../stores/editor/utils/DnDUtils.js';
import { LEGEND_STUDIO_TEST_ID } from '../../../../__lib__/LegendStudioTesting.js';
import { InstanceSetImplementationState, MappingElementState, } from '../../../../stores/editor/editor-state/element-editor-state/mapping/MappingElementState.js';
import { PureInstanceSetImplementationState, } from '../../../../stores/editor/editor-state/element-editor-state/mapping/PureInstanceSetImplementationState.js';
import { guaranteeNonNullable, noop } from '@finos/legend-shared';
import { getMappingElementSource, MappingEditorState, getEmbeddedSetImplementations, } from '../../../../stores/editor/editor-state/element-editor-state/mapping/MappingEditorState.js';
import { TypeTree } from './TypeTree.js';
import { FlatDataRecordTypeTree } from './FlatDataRecordTypeTree.js';
import { PropertyMappingEditor } from './PropertyMappingsEditor.js';
import { useDrop } from 'react-dnd';
import { FlatDataInstanceSetImplementationState } from '../../../../stores/editor/editor-state/element-editor-state/mapping/FlatDataInstanceSetImplementationState.js';
import { MappingElementDecorationCleaner } from '../../../../stores/editor/editor-state/element-editor-state/mapping/MappingElementDecorator.js';
import { UnsupportedInstanceSetImplementationState } from '../../../../stores/editor/editor-state/element-editor-state/mapping/UnsupportedInstanceSetImplementationState.js';
import { UnsupportedEditorPanel } from '../UnsupportedElementEditor.js';
import { TableOrViewSourceTree } from './relational/TableOrViewSourceTree.js';
import { getSourceElementLabel, InstanceSetImplementationSourceSelectorModal, } from './InstanceSetImplementationSourceSelectorModal.js';
import { flowResult } from 'mobx';
import { useEditorStore } from '../../EditorStoreProvider.js';
import { ActionAlertActionType, useApplicationStore, } from '@finos/legend-application';
import { Class, Type, FlatData, RootFlatDataRecordType, Table, Database, TableAlias, TableExplicitReference, ViewExplicitReference, getAllRecordTypes, getAllClassProperties, PrimitiveType, ConcreteFunctionDefinition, } from '@finos/legend-graph';
import { InlineLambdaEditor } from '@finos/legend-query-builder';
import { RelationTypeTree } from './RelationTypeTree.js';
export const InstanceSetImplementationSourceExplorer = observer((props) => {
    const { setImplementation, isReadOnly } = props;
    const editorStore = useEditorStore();
    const applicationStore = useApplicationStore();
    const [sourceElementToFilter, setSourceElementToFilter] = useState(undefined);
    const mappingEditorState = editorStore.tabManagerState.getCurrentEditorState(MappingEditorState);
    const instanceSetImplementationState = mappingEditorState.currentTabState instanceof MappingElementState
        ? mappingEditorState.currentTabState
        : undefined;
    const srcElement = getMappingElementSource(setImplementation, editorStore.pluginManager.getApplicationPlugins());
    const sourceLabel = getSourceElementLabel(srcElement);
    // `null` is when we want to open the modal using the existing source
    // `undefined` is to close the source modal
    // any other value to open the source modal using that value as the initial state of the modal
    const [sourceElementForSourceSelectorModal, setSourceElementForSourceSelectorModal,] = useState();
    const CHANGING_SOURCE_ON_EMBEDDED = 'Changing source on mapping with embedded children will delete all its children';
    const showSourceSelectorModal = () => {
        setSourceElementToFilter(undefined);
        if (!isReadOnly) {
            const embeddedSetImpls = getEmbeddedSetImplementations(setImplementation);
            if (!embeddedSetImpls.length) {
                setSourceElementForSourceSelectorModal(null);
            }
            else {
                applicationStore.alertService.setActionAlertInfo({
                    message: CHANGING_SOURCE_ON_EMBEDDED,
                    actions: [
                        {
                            label: 'Continue',
                            handler: () => setSourceElementForSourceSelectorModal(null),
                            type: ActionAlertActionType.PROCEED,
                        },
                        {
                            label: 'Cancel',
                        },
                    ],
                });
            }
        }
    };
    const hideSourceSelectorModal = () => setSourceElementForSourceSelectorModal(undefined);
    // Drag and Drop
    const dndType = [
        CORE_DND_TYPE.PROJECT_EXPLORER_CLASS,
        CORE_DND_TYPE.PROJECT_EXPLORER_FLAT_DATA,
        CORE_DND_TYPE.PROJECT_EXPLORER_DATABASE,
        CORE_DND_TYPE.PROJECT_EXPLORER_FUNCTION,
    ];
    // smartly analyze the content of the source and automatically assign it or its sub-part
    // as class mapping source when possible
    const changeClassMappingSourceDriver = useCallback((droppedPackagableElement) => {
        if (droppedPackagableElement instanceof Class) {
            flowResult(mappingEditorState.changeClassMappingSourceDriver(setImplementation, droppedPackagableElement)).catch(applicationStore.alertUnhandledError);
        }
        else if (droppedPackagableElement instanceof FlatData) {
            const allRecordTypes = getAllRecordTypes(droppedPackagableElement);
            if (allRecordTypes.length === 0) {
                applicationStore.notificationService.notifyWarning(`Source flat-data store '${droppedPackagableElement.path}' must have at least one action`);
                return;
            }
            if (allRecordTypes.length === 1) {
                flowResult(mappingEditorState.changeClassMappingSourceDriver(setImplementation, allRecordTypes[0])).catch(applicationStore.alertUnhandledError);
            }
            else {
                setSourceElementForSourceSelectorModal(allRecordTypes[0]);
            }
        }
        else if (droppedPackagableElement instanceof Database) {
            setSourceElementToFilter(droppedPackagableElement);
            const relations = droppedPackagableElement.schemas.flatMap((schema) => schema.tables.concat(schema.views));
            if (relations.length === 0) {
                applicationStore.notificationService.notifyWarning(`Source database '${droppedPackagableElement.path}' must have at least one table or view`);
                return;
            }
            const mainTableAlias = new TableAlias();
            mainTableAlias.relation =
                relations[0] instanceof Table
                    ? TableExplicitReference.create(relations[0])
                    : ViewExplicitReference.create(relations[0]);
            mainTableAlias.name = mainTableAlias.relation.value.name;
            if (relations.length === 1) {
                flowResult(mappingEditorState.changeClassMappingSourceDriver(setImplementation, mainTableAlias)).catch(applicationStore.alertUnhandledError);
            }
            else {
                setSourceElementForSourceSelectorModal(mainTableAlias);
            }
        }
        else if (droppedPackagableElement instanceof ConcreteFunctionDefinition) {
            flowResult(mappingEditorState.changeClassMappingSourceDriver(setImplementation, droppedPackagableElement)).catch(applicationStore.alertUnhandledError);
        }
    }, [applicationStore, mappingEditorState, setImplementation]);
    const handleDrop = useCallback((item) => {
        if (!setImplementation._isEmbedded && !isReadOnly) {
            const embeddedSetImpls = getEmbeddedSetImplementations(setImplementation);
            const droppedPackagableElement = item.data.packageableElement;
            if (!embeddedSetImpls.length) {
                changeClassMappingSourceDriver(droppedPackagableElement);
            }
            else {
                applicationStore.alertService.setActionAlertInfo({
                    message: CHANGING_SOURCE_ON_EMBEDDED,
                    actions: [
                        {
                            label: 'Continue',
                            handler: () => changeClassMappingSourceDriver(droppedPackagableElement),
                            type: ActionAlertActionType.PROCEED,
                        },
                        {
                            label: 'Cancel',
                        },
                    ],
                });
            }
        }
    }, [
        changeClassMappingSourceDriver,
        applicationStore,
        isReadOnly,
        setImplementation,
    ]);
    const [{ isDragOver, canDrop }, dropConnector] = useDrop(() => ({
        accept: dndType,
        drop: (item) => handleDrop(item),
        collect: (monitor) => ({
            isDragOver: monitor.isOver({ shallow: true }),
            canDrop: monitor.canDrop(),
        }),
    }), [handleDrop]);
    const isUnsupported = instanceSetImplementationState instanceof
        UnsupportedInstanceSetImplementationState;
    if (!(instanceSetImplementationState instanceof InstanceSetImplementationState)) {
        return null;
    }
    const extraInstanceSetImplementationBlockingErrorCheckers = editorStore.pluginManager
        .getApplicationPlugins()
        .flatMap((plugin) => plugin.getExtraInstanceSetImplementationBlockingErrorCheckers?.() ?? []);
    let hasParseError = false;
    for (const checker of extraInstanceSetImplementationBlockingErrorCheckers) {
        const instanceSetImplementationBlockingErrorChecker = checker(instanceSetImplementationState);
        if (instanceSetImplementationBlockingErrorChecker) {
            hasParseError = instanceSetImplementationBlockingErrorChecker;
        }
    }
    return (_jsxs("div", { "data-testid": LEGEND_STUDIO_TEST_ID.SOURCE_PANEL, className: clsx('panel source-panel', {
            backdrop__element: (instanceSetImplementationState instanceof
                PureInstanceSetImplementationState
                ? instanceSetImplementationState.hasParserError
                : false) ||
                (instanceSetImplementationState instanceof
                    FlatDataInstanceSetImplementationState
                    ? instanceSetImplementationState.hasParserError
                    : false) ||
                hasParseError,
        }), children: [_jsxs(PanelHeader, { children: [_jsxs("div", { className: "panel__header__title source-panel__header__title", children: [_jsx("div", { className: "panel__header__title__label", children: "source" }), _jsx("div", { className: "panel__header__title__content", children: sourceLabel })] }), _jsx(PanelHeaderActions, { children: _jsx(PanelHeaderActionItem, { onClick: showSourceSelectorModal, disabled: isReadOnly || setImplementation._isEmbedded || isUnsupported, title: "Select Source...", children: _jsx(PencilEditIcon, {}) }) })] }), _jsx(PanelContent, { children: _jsxs(PanelDropZone, { dropTargetConnector: dropConnector, isDragOver: isDragOver && !isReadOnly, children: [!isUnsupported && (_jsx(_Fragment, { children: srcElement ? (_jsxs("div", { className: "source-panel__explorer", children: [srcElement instanceof Type && (_jsx(TypeTree, { type: srcElement, selectedType: instanceSetImplementationState.selectedType })), srcElement instanceof RootFlatDataRecordType && (_jsx(FlatDataRecordTypeTree, { recordType: srcElement, selectedType: instanceSetImplementationState.selectedType })), srcElement instanceof TableAlias && (_jsx(TableOrViewSourceTree, { relation: srcElement.relation.value, selectedType: instanceSetImplementationState.selectedType })), srcElement instanceof ConcreteFunctionDefinition && (_jsx(RelationTypeTree, { relation: srcElement, selectedType: instanceSetImplementationState.selectedType, editorStore: editorStore }))] })) : (_jsx(BlankPanelPlaceholder, { text: "Choose a source", onClick: showSourceSelectorModal, clickActionType: "add", tooltipText: "Drop a class mapping source, or click to choose one", isDropZoneActive: canDrop, disabled: isReadOnly, previewText: "No source" })) })), isUnsupported && (_jsx(UnsupportedEditorPanel, { isReadOnly: isReadOnly, text: "Can't display class mapping source in form mode" })), sourceElementForSourceSelectorModal !== undefined && (_jsx(InstanceSetImplementationSourceSelectorModal, { mappingEditorState: mappingEditorState, setImplementation: setImplementation, sourceElementToSelect: sourceElementForSourceSelectorModal, closeModal: hideSourceSelectorModal, sourceElementToFilter: sourceElementToFilter }))] }) })] }));
});
const MappingFilterEditor = observer(({ editorStore, filterState, instanceSetImplementationState, isReadOnly, }) => (_jsxs("div", { className: "panel class-mapping-editor__filter-panel", children: [_jsx("div", { className: "panel__header", children: _jsx("div", { className: "panel__header__title", children: _jsx("div", { className: "panel__header__title__content", children: "FILTER" }) }) }), _jsx("div", { className: clsx('property-mapping-editor', {
                backdrop__element: Boolean(filterState.parserError),
            }), children: _jsx("div", { className: "class-mapping-filter-editor__content", children: _jsx(InlineLambdaEditor, { className: "class-mapping-filter-editor__element__lambda-editor", disabled: isReadOnly ||
                        instanceSetImplementationState.isConvertingTransformLambdaObjects, forceBackdrop: Boolean(filterState.parserError), lambdaEditorState: filterState, expectedType: PrimitiveType.BOOLEAN }) }) })] })));
// Sort by property type/complexity (asc)
const typeSorter = (a, b) => (a.genericType.value.rawType instanceof Class ? 1 : 0) -
    (b.genericType.value.rawType instanceof Class ? 1 : 0);
// Sort by requiredness/multiplicity (desc)
const requiredStatusSorter = (a, b) => (a.multiplicity.lowerBound > 0 ? 0 : 1) -
    (b.multiplicity.lowerBound > 0 ? 0 : 1);
export const InstanceSetImplementationEditor = observer((props) => {
    const { setImplementation, isReadOnly } = props;
    const editorStore = useEditorStore();
    const applicationStore = useApplicationStore();
    const mappingEditorState = editorStore.tabManagerState.getCurrentEditorState(MappingEditorState);
    const [sortByRequired, setSortByRequired] = useState(true);
    const instanceSetImplementationState = guaranteeNonNullable(mappingEditorState.currentTabState instanceof
        InstanceSetImplementationState
        ? mappingEditorState.currentTabState
        : undefined, 'Mapping element state for instance set implementation must be instance set implementation state');
    const handleSortChange = () => setSortByRequired(!sortByRequired);
    // Get properties of supertypes
    const sortedProperties = getAllClassProperties(setImplementation.class.value)
        // LEVEL 1: sort properties by name
        .sort((a, b) => a.name.localeCompare(b.name))
        // LEVEL 2: sort by properties by required/type (which ever is not chosen to be the primary sort)
        .sort(sortByRequired ? typeSorter : requiredStatusSorter)
        // LEVEL 3: sort by properties by required/type (primary sort)
        .sort(sortByRequired ? requiredStatusSorter : typeSorter);
    const isUnsupported = instanceSetImplementationState instanceof
        UnsupportedInstanceSetImplementationState;
    const renderFilterEditor = instanceSetImplementationState instanceof
        PureInstanceSetImplementationState &&
        instanceSetImplementationState.mappingElement.filter;
    useEffect(() => {
        if (!isReadOnly) {
            instanceSetImplementationState.decorate();
        }
        flowResult(instanceSetImplementationState.convertPropertyMappingTransformObjects()).catch(applicationStore.alertUnhandledError);
        if (instanceSetImplementationState instanceof
            PureInstanceSetImplementationState &&
            instanceSetImplementationState.mappingElement.filter) {
            flowResult(instanceSetImplementationState.convertFilter()).catch(applicationStore.alertUnhandledError);
        }
        return isReadOnly
            ? noop()
            : () => setImplementation.accept_SetImplementationVisitor(new MappingElementDecorationCleaner(editorStore));
    }, [
        applicationStore,
        setImplementation,
        isReadOnly,
        instanceSetImplementationState,
        editorStore,
    ]);
    useEffect(() => {
        instanceSetImplementationState.setSelectedType(undefined);
    }, [instanceSetImplementationState]);
    return (_jsx("div", { className: "mapping-element-editor__content", children: _jsxs(ResizablePanelGroup, { orientation: "vertical", children: [_jsx(ResizablePanel, { minSize: 300, children: _jsxs(ResizablePanelGroup, { orientation: "horizontal", children: [_jsx(ResizablePanel, { minSize: 300, children: _jsxs("div", { className: "panel class-mapping-editor__property-panel", children: [_jsxs(PanelHeader, { children: [_jsx("div", { className: "panel__header__title", children: _jsx("div", { className: "panel__header__title__content", children: "PROPERTIES" }) }), _jsx(PanelHeaderActions, { children: _jsx("div", { className: "panel__header__action", children: _jsxs("div", { className: `class-mapping-editor__sort-by-required-btn ${sortByRequired
                                                                ? 'class-mapping-editor__sort-by-required-btn--enabled'
                                                                : ''}`, onClick: handleSortChange, children: [_jsx(LongArrowAltDownIcon, {}), _jsx(AsteriskIcon, {})] }) }) })] }), _jsxs(PanelContent, { children: [!isReadOnly &&
                                                    !isUnsupported &&
                                                    sortedProperties.map((property) => (_jsx(PropertyMappingEditor, { property: property, instanceSetImplementationState: instanceSetImplementationState, isReadOnly: isReadOnly }, property.name))), isReadOnly &&
                                                    !isUnsupported &&
                                                    sortedProperties
                                                        // for property without any property mapping in readonly mode, we won't show it
                                                        .filter((p) => instanceSetImplementationState.propertyMappingStates.filter((pm) => pm.propertyMapping.property.value.name ===
                                                        p.name).length)
                                                        .map((property) => (_jsx(PropertyMappingEditor, { property: property, instanceSetImplementationState: instanceSetImplementationState, isReadOnly: isReadOnly }, property.name))), isUnsupported && (_jsx(UnsupportedEditorPanel, { isReadOnly: isReadOnly, text: "Can't display class mapping in form mode" }))] })] }) }), _jsx(ResizablePanelSplitter, {}), renderFilterEditor &&
                                instanceSetImplementationState.mappingFilterState && (_jsx(ResizablePanel, { size: 330, minSize: 80, children: _jsx(MappingFilterEditor, { editorStore: editorStore, instanceSetImplementationState: instanceSetImplementationState, filterState: instanceSetImplementationState.mappingFilterState, isReadOnly: isReadOnly }) }))] }) }), _jsx(ResizablePanelSplitter, {}), _jsx(ResizablePanel, { size: 300, minSize: 300, children: _jsx(InstanceSetImplementationSourceExplorer, { setImplementation: setImplementation, isReadOnly: isReadOnly }) })] }) }));
});
//# sourceMappingURL=InstanceSetImplementationEditor.js.map