import { jsx as _jsx, jsxs as _jsxs } 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 { useRef, useCallback } from 'react';
import { observer } from 'mobx-react-lite';
import { GenerationSpecificationEditorState, } from '../../../stores/editor/editor-state/GenerationSpecificationEditorState.js';
import { useDrag, useDrop } from 'react-dnd';
import { getElementIcon } from '../../ElementIconUtils.js';
import { clsx, BlankPanelContent, CustomSelectorInput, ResizablePanel, ResizablePanelGroup, ResizablePanelSplitter, ResizablePanelSplitterLine, PURE_FileGenerationIcon, RefreshIcon, FireIcon, TimesIcon, PlusIcon, LongArrowRightIcon, PanelDropZone, DragPreviewLayer, useDragPreviewLayer, Panel, PanelContent, PanelDnDEntry, PanelHeader, PanelHeaderActions, PanelHeaderActionItem, } from '@finos/legend-art';
import { CORE_DND_TYPE, } from '../../../stores/editor/utils/DnDUtils.js';
import { flowResult } from 'mobx';
import { useEditorStore } from '../EditorStoreProvider.js';
import { FileGenerationSpecification, PackageableElementExplicitReference, GenerationTreeNode, } from '@finos/legend-graph';
import { useApplicationStore } from '@finos/legend-application';
import { buildElementOption, } from '@finos/legend-lego/graph-editor';
import { packageableElementReference_setValue } from '../../../stores/graph-modifier/DomainGraphModifierHelper.js';
import { generationSpecification_addFileGeneration, generationSpecification_deleteFileGeneration, generationSpecification_setId, } from '../../../stores/graph-modifier/DSL_Generation_GraphModifierHelper.js';
const GENERATION_SPEC_NODE_DND_TYPE = 'GENERATION_SPEC_NODE';
const ModelGenerationItem = observer((props) => {
    const ref = useRef(null);
    const { nodeState, specState, options } = props;
    const generationTreeNode = nodeState.node;
    const editorStore = useEditorStore();
    const applicationStore = editorStore.applicationStore;
    const modelGenerationRef = generationTreeNode.generationElement;
    const modelGeneration = modelGenerationRef.value;
    const value = {
        label: modelGeneration.name,
        value: modelGeneration,
    };
    const onChange = (val) => {
        if (val !== null) {
            packageableElementReference_setValue(modelGenerationRef, val.value);
        }
    };
    const deleteNode = () => specState.deleteGenerationTreeNode(generationTreeNode);
    const visitModelGeneration = () => editorStore.graphEditorMode.openElement(modelGeneration);
    // generation id
    const isUnique = specState.spec.generationNodes.filter((n) => n.id === generationTreeNode.id).length < 2;
    const isDefault = generationTreeNode.id ===
        generationTreeNode.generationElement.value.path && isUnique;
    const changeNodeId = (event) => generationSpecification_setId(generationTreeNode, event.target.value);
    // Drag and Drop
    const handleHover = useCallback((item, monitor) => {
        const dragIndex = specState.generationTreeNodeStates.findIndex((e) => e === item.nodeState);
        const hoverIndex = specState.generationTreeNodeStates.findIndex((e) => e === nodeState);
        if (dragIndex === -1 || hoverIndex === -1 || dragIndex === hoverIndex) {
            return;
        }
        // move the item being hovered on when the dragged item position is beyond the its middle point
        const hoverBoundingReact = ref.current?.getBoundingClientRect();
        const distanceThreshold = ((hoverBoundingReact?.bottom ?? 0) - (hoverBoundingReact?.top ?? 0)) /
            2;
        const dragDistance = (monitor.getClientOffset()?.y ?? 0) - (hoverBoundingReact?.top ?? 0);
        if (dragIndex < hoverIndex && dragDistance < distanceThreshold) {
            return;
        }
        if (dragIndex > hoverIndex && dragDistance > distanceThreshold) {
            return;
        }
        specState.moveGenerationNode(dragIndex, hoverIndex);
    }, [nodeState, specState]);
    const [{ nodeBeingDragged }, dropConnector] = useDrop(() => ({
        accept: [GENERATION_SPEC_NODE_DND_TYPE],
        hover: (item, monitor) => handleHover(item, monitor),
        collect: (monitor) => ({
            /**
             * @workaround typings - https://github.com/react-dnd/react-dnd/pull/3484
             */
            nodeBeingDragged: 
            // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
            monitor.getItem()
                ?.nodeState.node,
        }),
    }), [handleHover]);
    const isBeingDragged = nodeState.node === nodeBeingDragged;
    const [, dragConnector, dragPreviewConnector] = useDrag(() => ({
        type: GENERATION_SPEC_NODE_DND_TYPE,
        item: () => ({ nodeState }),
    }), [nodeState]);
    dragConnector(dropConnector(ref));
    useDragPreviewLayer(dragPreviewConnector);
    return (_jsxs(PanelDnDEntry, { ref: ref, className: "generation-spec-model-generation-editor__item", showPlaceholder: isBeingDragged, children: [_jsx("div", { className: "btn--sm generation-spec-model-generation-editor__item__label", children: getElementIcon(modelGeneration, editorStore) }), _jsx("input", { className: clsx('generation-spec-model-generation-editor__item__id', {
                    'generation-spec-model-generation-editor__item__id--has-error': !isUnique,
                }), spellCheck: false, value: isDefault ? 'DEFAULT' : generationTreeNode.id, onChange: changeNodeId, disabled: isDefault }), _jsx(CustomSelectorInput, { className: "generation-spec-model-generation-editor__item__dropdown", options: options, onChange: onChange, value: value, darkMode: !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled }), _jsx("button", { className: "btn--dark btn--sm", onClick: visitModelGeneration, tabIndex: -1, title: "See mapping", children: _jsx(LongArrowRightIcon, {}) }), _jsx("button", { className: "generation-spec-model-generation-editor__item__remove-btn", onClick: deleteNode, tabIndex: -1, title: "Remove", children: _jsx(TimesIcon, {}) })] }));
});
const ModelGenerationSpecifications = observer((props) => {
    const { specState } = props;
    const specNodesStates = specState.generationTreeNodeStates;
    const editorStore = useEditorStore();
    const modelGenerationElementsInGraph = editorStore.pluginManager
        .getPureGraphManagerPlugins()
        .flatMap((plugin) => plugin.getExtraModelGenerationElementGetters?.() ?? [])
        .flatMap((getter) => getter(editorStore.graphManagerState.graph));
    const extraModelGenerationSpecificationElementDnDTypes = editorStore.pluginManager
        .getApplicationPlugins()
        .flatMap((plugin) => plugin.getExtraModelGenerationSpecificationElementDnDTypes?.() ?? []);
    const modelGenerationElementOptions = modelGenerationElementsInGraph.map(buildElementOption);
    const addModelGeneration = () => {
        const option = modelGenerationElementOptions[0];
        if (option) {
            specState.addGenerationTreeNode(new GenerationTreeNode(PackageableElementExplicitReference.create(option.value)));
        }
    };
    // Drag and Drop
    const handleDrop = useCallback((item) => specState.addGenerationTreeNode(new GenerationTreeNode(PackageableElementExplicitReference.create(item.data.packageableElement))), [specState]);
    const [{ isDragOver }, dropConnector] = useDrop(() => ({
        accept: extraModelGenerationSpecificationElementDnDTypes,
        drop: (item, monitor) => {
            if (!monitor.didDrop()) {
                handleDrop(item);
            } // prevent drop event propagation to accomondate for nested DnD
        },
        collect: (monitor) => ({
            isDragOver: monitor.isOver({ shallow: true }),
        }),
    }), [handleDrop]);
    return (_jsxs(Panel, { children: [_jsx(PanelHeader, { title: "Model Generations", children: _jsx(PanelHeaderActions, { children: _jsx(PanelHeaderActionItem, { onClick: addModelGeneration, disabled: !modelGenerationElementsInGraph.length, title: "Add File Generation", children: _jsx(PlusIcon, {}) }) }) }), _jsx(PanelContent, { children: _jsx(PanelDropZone, { isDragOver: isDragOver, dropTargetConnector: dropConnector, children: specNodesStates.length ? (_jsxs("div", { className: "generation-spec-model-generation-editor__items", children: [_jsx(DragPreviewLayer, { labelGetter: (item) => item.nodeState.node.generationElement.value.name, types: [GENERATION_SPEC_NODE_DND_TYPE] }), specNodesStates.map((nodeState) => (_jsx(ModelGenerationItem, { specState: specState, nodeState: nodeState, options: modelGenerationElementOptions }, nodeState.uuid)))] })) : (_jsx(BlankPanelContent, { children: modelGenerationElementsInGraph.length
                            ? 'No model generation included in spec'
                            : 'Create a model generation element to include in spec' })) }) })] }));
});
const FileGenerationItem = observer((props) => {
    const { fileGeneraitonRef, generationSpecificationEditorState, options } = props;
    const editorStore = useEditorStore();
    const applicationStore = editorStore.applicationStore;
    const fileGeneration = fileGeneraitonRef.value;
    const value = { label: fileGeneration.name, value: fileGeneration };
    const onChange = (val) => {
        if (val !== null) {
            packageableElementReference_setValue(fileGeneraitonRef, val.value);
        }
    };
    const deleteColumnSort = () => generationSpecification_deleteFileGeneration(generationSpecificationEditorState.spec, fileGeneraitonRef);
    const visitFileGen = () => editorStore.graphEditorMode.openElement(fileGeneration);
    return (_jsxs("div", { className: "generation-spec-file-generation-editor__item", children: [_jsx("div", { className: "btn--sm generation-spec-file-generation-editor__item__label", children: _jsx(PURE_FileGenerationIcon, {}) }), _jsx(CustomSelectorInput, { className: "generation-spec-file-generation-editor__item__dropdown", options: options, onChange: onChange, value: value, darkMode: !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled }), _jsx("button", { className: "btn--dark btn--sm", onClick: visitFileGen, tabIndex: -1, title: "See mapping", children: _jsx(LongArrowRightIcon, {}) }), _jsx("button", { className: "generation-spec-file-generation-editor__item__remove-btn", onClick: deleteColumnSort, tabIndex: -1, title: "Remove", children: _jsx(TimesIcon, {}) })] }));
});
const FileGenerationSpecifications = observer((props) => {
    const { generationSpecificationEditorState } = props;
    const generationSpec = generationSpecificationEditorState.spec;
    const editorStore = useEditorStore();
    const fileGenerations = generationSpecificationEditorState.spec.fileGenerations.map((f) => f.value);
    const fileGenerationInGraph = editorStore.graphManagerState.graph.ownFileGenerations;
    const fileGenerationsOptions = fileGenerationInGraph
        .filter((f) => !fileGenerations.includes(f))
        .map(buildElementOption);
    const addFileGeneration = () => {
        const option = fileGenerationsOptions[0];
        if (option) {
            generationSpecification_addFileGeneration(generationSpec, option.value);
        }
    };
    // drag and drop
    const handleDrop = useCallback((item) => {
        const element = item.data.packageableElement;
        if (element instanceof FileGenerationSpecification &&
            !fileGenerations.includes(element)) {
            generationSpecification_addFileGeneration(generationSpec, element);
        }
    }, [fileGenerations, generationSpec]);
    const [{ isDragOver }, dropConnector] = useDrop(() => ({
        accept: [CORE_DND_TYPE.PROJECT_EXPLORER_FILE_GENERATION],
        drop: (item, monitor) => {
            if (!monitor.didDrop()) {
                handleDrop(item);
            } // prevent drop event propagation to accomondate for nested DnD
        },
        collect: (monitor) => ({
            isDragOver: monitor.isOver({ shallow: true }),
        }),
    }), [handleDrop]);
    return (_jsxs(Panel, { className: "generation-spec-file-generation-editor", children: [_jsxs(PanelHeader, { children: [_jsx("div", { className: "panel__header__title", children: _jsx("div", { className: "panel__header__title__label", children: "File Generations" }) }), _jsx(PanelHeaderActions, { children: _jsx(PanelHeaderActionItem, { onClick: addFileGeneration, disabled: !fileGenerationsOptions.length, title: "Add File Generation", children: _jsx(PlusIcon, {}) }) })] }), _jsx(PanelContent, { children: _jsx(PanelDropZone, { isDragOver: isDragOver, dropTargetConnector: dropConnector, children: generationSpec.fileGenerations.length ? (_jsx("div", { className: "generation-spec-file-generation-editor__items", children: generationSpec.fileGenerations.map((fileGen) => (_jsx(FileGenerationItem, { generationSpecificationEditorState: generationSpecificationEditorState, fileGeneraitonRef: fileGen, options: fileGenerationsOptions }, fileGen.value.path))) })) : (_jsx(BlankPanelContent, { children: fileGenerationInGraph.length
                            ? 'Add file generation to spec'
                            : 'Create a file generation to include in spec' })) }) })] }));
});
export const GenerationSpecificationEditor = observer(() => {
    const editorStore = useEditorStore();
    const applicationStore = useApplicationStore();
    const generationSpecificationState = editorStore.tabManagerState.getCurrentEditorState(GenerationSpecificationEditorState);
    const modelGenerationState = editorStore.graphState.graphGenerationState;
    const generationSpec = generationSpecificationState.spec;
    const generate = applicationStore.guardUnhandledError(() => flowResult(modelGenerationState.globalGenerate()));
    const emptyGenerationEntities = applicationStore.guardUnhandledError(() => flowResult(modelGenerationState.clearGenerations()));
    return (_jsx("div", { className: "generation-spec-editor", children: _jsxs(Panel, { children: [_jsxs(PanelHeader, { children: [_jsxs("div", { className: "panel__header__title", children: [_jsx("div", { className: "panel__header__title__label", children: "Generation Specification" }), _jsx("div", { className: "panel__header__title__content", children: generationSpec.name })] }), _jsxs(PanelHeaderActions, { children: [_jsx(PanelHeaderActionItem, { className: clsx('editor__status-bar__action editor__status-bar__generate-btn', {
                                        'editor__status-bar__generate-btn--wiggling': modelGenerationState.isRunningGlobalGenerate,
                                    }), onClick: generate, title: "Generate", children: _jsx(FireIcon, {}) }), _jsx(PanelHeaderActionItem, { className: clsx('editor__status-bar__action editor__status-bar__generate-btn', {
                                        'local-changes__refresh-btn--loading': modelGenerationState.clearingGenerationEntitiesState
                                            .isInProgress,
                                    }), onClick: emptyGenerationEntities, title: "Clear generation entities", children: _jsx(RefreshIcon, {}) })] })] }), _jsx(PanelContent, { className: "generation-spec-editor__content", children: _jsxs(ResizablePanelGroup, { orientation: "horizontal", children: [_jsx(ResizablePanel, { size: 400, minSize: 25, children: _jsx(ModelGenerationSpecifications, { specState: generationSpecificationState }) }), _jsx(ResizablePanelSplitter, { children: _jsx(ResizablePanelSplitterLine, { color: "var(--color-dark-grey-200)" }) }), _jsx(ResizablePanel, { children: _jsx(FileGenerationSpecifications, { generationSpecificationEditorState: generationSpecificationState }) })] }) })] }) }));
});
//# sourceMappingURL=GenerationSpecificationEditor.js.map