/**
 * 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 { isNonNullable, addUniqueEntry, guaranteeNonNullable, } from '@finos/legend-shared';
import { CORE_DND_TYPE } from './DnDUtils.js';
import { ROOT_PACKAGE_NAME, Package, Class, Enumeration, Profile, Association, ConcreteFunctionDefinition, Measure, Unit, Database, FlatData, Mapping, Service, PackageableRuntime, PackageableConnection, FileGenerationSpecification, GenerationSpecification, DataElement, generateFunctionPrettyName, getElementRootPackage, extractDependencyGACoordinateFromRootPackageName, generateDependencyRootPackageName, } from '@finos/legend-graph';
import { ExplorerTreeRootPackageLabel } from '../ExplorerTreeState.js';
const getElementProjectExplorerDnDType = (editorStore, element) => {
    if (element instanceof Package) {
        return CORE_DND_TYPE.PROJECT_EXPLORER_PACKAGE;
    }
    else if (element instanceof Class) {
        return CORE_DND_TYPE.PROJECT_EXPLORER_CLASS;
    }
    else if (element instanceof Association) {
        return CORE_DND_TYPE.PROJECT_EXPLORER_ASSOCIATION;
    }
    else if (element instanceof Enumeration) {
        return CORE_DND_TYPE.PROJECT_EXPLORER_ENUMERATION;
    }
    else if (element instanceof Profile) {
        return CORE_DND_TYPE.PROJECT_EXPLORER_PROFILE;
    }
    else if (element instanceof ConcreteFunctionDefinition) {
        return CORE_DND_TYPE.PROJECT_EXPLORER_FUNCTION;
    }
    else if (element instanceof Measure) {
        return CORE_DND_TYPE.PROJECT_EXPLORER_MEASURE;
    }
    else if (element instanceof FlatData) {
        return CORE_DND_TYPE.PROJECT_EXPLORER_FLAT_DATA;
    }
    else if (element instanceof Database) {
        return CORE_DND_TYPE.PROJECT_EXPLORER_DATABASE;
    }
    else if (element instanceof Mapping) {
        return CORE_DND_TYPE.PROJECT_EXPLORER_MAPPING;
    }
    else if (element instanceof PackageableRuntime) {
        return CORE_DND_TYPE.PROJECT_EXPLORER_RUNTIME;
    }
    else if (element instanceof PackageableConnection) {
        return CORE_DND_TYPE.PROJECT_EXPLORER_CONNECTION;
    }
    else if (element instanceof Service) {
        return CORE_DND_TYPE.PROJECT_EXPLORER_SERVICE;
    }
    else if (element instanceof GenerationSpecification) {
        return CORE_DND_TYPE.PROJECT_EXPLORER_GENERATION_TREE;
    }
    else if (element instanceof FileGenerationSpecification) {
        return CORE_DND_TYPE.PROJECT_EXPLORER_FILE_GENERATION;
    }
    else if (element instanceof DataElement) {
        return CORE_DND_TYPE.PROJECT_EXPLORER_DATA;
    }
    const extraDragElementClassifiers = editorStore.pluginManager
        .getApplicationPlugins()
        .flatMap((plugin) => plugin.getExtraDragElementClassifiers?.() ?? []);
    for (const classifier of extraDragElementClassifiers) {
        const dragType = classifier(element);
        if (dragType) {
            return dragType;
        }
    }
    return CORE_DND_TYPE.NONE;
};
export const getSelectedPackageTreeNodePackage = (node) => node
    ? node.packageableElement instanceof Package
        ? node.packageableElement
        : node.packageableElement.package
    : undefined;
export const generatePackageableElementTreeNodeDataLabel = (element) => element instanceof ConcreteFunctionDefinition
    ? generateFunctionPrettyName(element)
    : element.name;
export const getPackableElementTreeNodeData = (editorStore, element, childFilter) => ({
    id: element.path,
    dndType: getElementProjectExplorerDnDType(editorStore, element),
    label: generatePackageableElementTreeNodeDataLabel(element),
    childrenIds: element instanceof Package
        ? element.children
            .filter((child) => !(child instanceof Unit)) // remove unit from package tree
            .filter((child) => child instanceof Package || !childFilter || childFilter(child))
            .map((child) => child.path)
        : undefined,
    packageableElement: element,
});
export const getDependencyPackableElementTreeNodeData = (editorStore, element, rootName, isDependencyRoot, childFilter) => ({
    id: isDependencyRoot ? element.path : `${rootName}::${element.path}`,
    dndType: getElementProjectExplorerDnDType(editorStore, element),
    label: generatePackageableElementTreeNodeDataLabel(element),
    childrenIds: element instanceof Package
        ? element.children
            .filter((child) => !(child instanceof Unit)) // remove unit from package tree
            .filter((child) => child instanceof Package || !childFilter || childFilter(child))
            .map((child) => `${rootName}::${child.path}`)
        : undefined,
    packageableElement: element,
});
export const getPackableElementTreeData = (editorStore, _package, rootWrapperName, childFilter) => {
    const rootIds = [];
    const nodes = new Map();
    if (rootWrapperName === '') {
        _package.children
            .slice()
            .filter((child) => !(child instanceof Unit)) // remove unit from package tree
            // packages comes first, within each group, sort by name
            .sort((a, b) => a.name.localeCompare(b.name))
            .sort((a, b) => (b instanceof Package ? 1 : 0) - (a instanceof Package ? 1 : 0))
            .forEach((childPackage) => {
            const childTreeNodeData = getPackableElementTreeNodeData(editorStore, childPackage, childFilter);
            addUniqueEntry(rootIds, childTreeNodeData.id);
            nodes.set(childTreeNodeData.id, childTreeNodeData);
        });
    }
    else {
        const rootNode = getPackableElementTreeNodeData(editorStore, _package, childFilter);
        rootNode.label = rootWrapperName;
        addUniqueEntry(rootIds, rootNode.id);
        nodes.set(rootNode.id, rootNode);
    }
    return { rootIds, nodes };
};
export const getDependenciesPackableElementTreeData = (editorStore, _packages, rootWrapperName, childFilter) => {
    const rootIds = [];
    // Here we warp all the dependency roots with an new root node 'dependencies'
    // We push all the dependency roots as children nodes of this new node so that
    // we can show the tree structure of dependencies wrapped with the label 'dependencies'.
    // In future we would want to support a new tree node type to accomodate these
    // excpetions to the existing structure.
    const root = new Package(ROOT_PACKAGE_NAME.PROJECT_DEPENDENCY_ROOT);
    const nodes = new Map();
    _packages.forEach((_package) => {
        const childRootNode = getDependencyPackableElementTreeNodeData(editorStore, _package, _package.name, true, childFilter);
        const dependencyGACoordinates = extractDependencyGACoordinateFromRootPackageName(_package.name) ??
            _package.name;
        childRootNode.label = generateDependencyRootPackageName(dependencyGACoordinates);
        childRootNode.id = generateDependencyRootPackageName(dependencyGACoordinates);
        addUniqueEntry(rootIds, childRootNode.id);
        nodes.set(childRootNode.id, childRootNode);
        root.children.push(_package);
    });
    const rootNode = {
        id: rootWrapperName,
        dndType: getElementProjectExplorerDnDType(editorStore, root),
        label: rootWrapperName,
        childrenIds: rootIds,
        packageableElement: root,
    };
    rootNode.label = rootWrapperName;
    nodes.set(rootNode.id, rootNode);
    const ids = [];
    ids.push(rootNode.id);
    return { rootIds: ids, nodes };
};
/**
 * Resolve all children of a node
 */
export const populatePackageTreeNodeChildren = (editorStore, node, treeData, isDependencyTree) => {
    if (node.childrenIds &&
        node.packageableElement instanceof Package &&
        node.id === ExplorerTreeRootPackageLabel.PROJECT_DEPENDENCY) {
        // do nothing
    }
    else if (node.childrenIds &&
        node.packageableElement instanceof Package &&
        isDependencyTree) {
        const rootNodeName = node.id.split('::')[0] ?? node.id;
        node.childrenIds = node.packageableElement.children
            .filter((child) => !(child instanceof Unit)) // remove unit from package tree
            .map((child) => `${rootNodeName}::${child.path}`);
        node.packageableElement.children
            .filter((child) => !(child instanceof Unit)) // remove unit from package tree
            .map((child) => getDependencyPackableElementTreeNodeData(editorStore, child, rootNodeName, false))
            .forEach((childNode) => {
            const currentNode = treeData.nodes.get(childNode.id);
            if (currentNode) {
                // Note here that we keep track of isSelected status using reference, we cannot swap out the currentNode to use new childNode
                currentNode.childrenIds = childNode.childrenIds;
                currentNode.label = childNode.label;
            }
            else {
                treeData.nodes.set(childNode.id, childNode);
            }
        });
    }
    else if (node.childrenIds && node.packageableElement instanceof Package) {
        node.childrenIds = node.packageableElement.children
            .filter((child) => !(child instanceof Unit)) // remove unit from package tree
            .map((child) => child.path);
        node.packageableElement.children
            .filter((child) => !(child instanceof Unit)) // remove unit from package tree
            .map((child) => getPackableElementTreeNodeData(editorStore, child))
            .forEach((childNode) => {
            const currentNode = treeData.nodes.get(childNode.id);
            if (currentNode) {
                // Note here that we keep track of isSelected status using reference, we cannot swap out the currentNode to use new childNode
                currentNode.childrenIds = childNode.childrenIds;
                currentNode.label = childNode.label;
            }
            else {
                treeData.nodes.set(childNode.id, childNode);
            }
        });
    }
};
export const addNode = (editorStore, element, treeData, childFilter, isDependencyTreeNode) => {
    const rootNodeName = getElementRootPackage(element).name;
    const newNode = isDependencyTreeNode
        ? getDependencyPackableElementTreeNodeData(editorStore, element, rootNodeName, false, childFilter)
        : getPackableElementTreeNodeData(editorStore, element, childFilter);
    treeData.nodes.set(newNode.id, newNode);
    if (!element.package || element.package.path === ROOT_PACKAGE_NAME.MAIN) {
        treeData.rootIds = Array.from(new Set(treeData.rootIds).add(newNode.id));
    }
    else if (element.package.path === ROOT_PACKAGE_NAME.SYSTEM ||
        element.package.path === ROOT_PACKAGE_NAME.MODEL_GENERATION) {
        const baseNode = treeData.nodes.get(element.package.path);
        if (baseNode) {
            baseNode.isOpen = true;
        }
    }
    else {
        const parentNode = treeData.nodes.get(element.package.path);
        if (parentNode) {
            parentNode.childrenIds = parentNode.childrenIds
                ? Array.from(new Set(parentNode.childrenIds).add(newNode.id))
                : [newNode.id];
        }
    }
    return newNode;
};
export const openNode = (editorStore, element, treeData, childFilter, isDependencyElement) => {
    let currentElement = element;
    let openingNode;
    while (currentElement.package) {
        const currentNode = treeData.nodes.get(currentElement.path) ??
            addNode(editorStore, currentElement, treeData, childFilter, isDependencyElement);
        currentNode.isOpen = currentElement instanceof Package;
        openingNode = !openingNode ? currentNode : openingNode;
        currentElement = currentElement.package;
    }
    // Open the dependency root
    const node = treeData.nodes.get(currentElement.name);
    if (node) {
        node.isOpen = currentElement instanceof Package;
        if (node.isOpen && treeData.rootIds.length) {
            const rootNode = treeData.nodes.get(guaranteeNonNullable(treeData.rootIds[0]));
            if (rootNode) {
                rootNode.isOpen = true;
            }
        }
    }
    return openingNode;
};
export const openNodes = (editorStore, parent, activeElements, treeData) => {
    if (!parent.children.filter((child) => !(child instanceof Unit)).length || // remove unit from package tree
        !activeElements.size) {
        return;
    }
    parent.children
        .filter((child) => !(child instanceof Unit)) // remove unit from package tree
        .forEach((child) => {
        if (activeElements.has(child)) {
            openNode(editorStore, child, treeData);
            activeElements.delete(child);
            if (!activeElements.size) {
                return;
            }
        }
        if (child instanceof Package) {
            openNodes(editorStore, child, activeElements, treeData);
        }
    });
};
export const openNodeById = (id, treeData) => {
    if (treeData) {
        const node = treeData.nodes.get(id);
        if (node) {
            node.isOpen = true;
        }
    }
};
export const getTreeChildNodes = (editorStore, node, treeData, isDependencyTree) => {
    if (node.childrenIds && node.packageableElement instanceof Package) {
        populatePackageTreeNodeChildren(editorStore, node, treeData, isDependencyTree);
        return (node.childrenIds
            .map((id) => treeData.nodes.get(id))
            .filter(isNonNullable)
            // packages comes first, within each group, sort by name
            .sort((a, b) => a.label.localeCompare(b.label))
            .sort((a, b) => (b.packageableElement instanceof Package ? 1 : 0) -
            (a.packageableElement instanceof Package ? 1 : 0)));
    }
    return [];
};
//# sourceMappingURL=PackageTreeUtils.js.map