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 { useState, useEffect, useRef } from 'react';
import { useDrag } from 'react-dnd';
import { PURE_DatabaseTableJoinIcon, TreeView, ChevronDownIcon, ChevronRightIcon, } from '@finos/legend-art';
import { addUniqueEntry, assertTrue, filterByType, guaranteeType, isNonNullable, } from '@finos/legend-shared';
import { renderColumnTypeIcon } from '../../connection-editor/DatabaseEditorHelper.js';
import { Column, stringifyDataType, } from '@finos/legend-graph';
export const TABLE_ELEMENT_DND_TYPE = 'TABLE_ELEMENT_DND_TYPE';
const JOIN_OPERATOR = '>';
const JOIN_AT_SYMBOL = '@';
const JOIN_PIPE_SYMBOL = '|';
const generateDatabasePointerText = (database) => `[${database}]`;
export class TableOrViewTreeNodeData {
    id;
    label;
    isSelected;
    isOpen;
    childrenIds;
    relation;
    constructor(id, label, relation) {
        this.id = id;
        this.label = label;
        this.relation = relation;
    }
}
export class ColumnNodeData extends TableOrViewTreeNodeData {
    column;
    constructor(id, label, relation, column) {
        super(id, label, relation);
        this.column = column;
    }
}
export class JoinNodeData extends TableOrViewTreeNodeData {
    join;
    constructor(id, label, relation, join) {
        super(id, label, relation);
        this.join = join;
    }
}
export class TableOrViewTreeNodeDragSource {
    data;
    constructor(data) {
        this.data = data;
    }
}
const generateColumnTreeNodeId = (column, relation, parentNode) => parentNode
    ? parentNode instanceof JoinNodeData
        ? `${parentNode.id} ${JOIN_PIPE_SYMBOL} ${relation.name}.${column.name}`
        : `${parentNode.id}.${column.name}`
    : `${generateDatabasePointerText(relation.schema._OWNER.path)}${relation.schema.name}.${relation.name}.${column.name}`;
const getColumnTreeNodeData = (column, relation, parentNode) => {
    const columnNode = new ColumnNodeData(generateColumnTreeNodeId(column, relation, parentNode), column.name, relation, column);
    return columnNode;
};
// TODO: support more complex join feature (with operation, direction, etc.)
const generateJoinTreeNodeId = (join, parentNode) => parentNode
    ? `${parentNode.id} ${JOIN_OPERATOR} ${JOIN_AT_SYMBOL}${join.name}`
    : `${generateDatabasePointerText(join.owner.path)}${JOIN_AT_SYMBOL}${join.name}`;
const resolveJoinTargetRelation = (join, sourceRelation) => {
    const potentialTargetRelations = new Set();
    join.aliases.forEach((alias) => {
        if (alias.first.relation.value !== sourceRelation) {
            potentialTargetRelations.add(alias.first.relation.value);
        }
        if (alias.second.relation.value !== sourceRelation) {
            potentialTargetRelations.add(alias.second.relation.value);
        }
    });
    assertTrue(potentialTargetRelations.size < 2, `Can't resolve target relation for join`);
    return potentialTargetRelations.size === 0
        ? sourceRelation
        : Array.from(potentialTargetRelations.values())[0];
};
const getJoinTreeNodeData = (join, relation, parentNode) => {
    const joinNode = new JoinNodeData(generateJoinTreeNodeId(join, parentNode), join.name, relation, join);
    const childrenIds = [];
    // columns
    relation.columns
        .slice()
        .filter(filterByType(Column))
        .sort((a, b) => a.name.toString().localeCompare(b.name.toString()))
        .forEach((col) => {
        addUniqueEntry(childrenIds, generateColumnTreeNodeId(col, relation, joinNode));
    });
    // joins
    relation.schema._OWNER.joins
        .slice()
        .filter((_join) => _join.aliases.filter((alias) => alias.first.relation.value === relation ||
        alias.second.relation.value === relation).length > 0)
        .sort((a, b) => a.name.toString().localeCompare(b.name.toString()))
        .forEach((childJoin) => {
        addUniqueEntry(childrenIds, generateJoinTreeNodeId(childJoin, joinNode));
    });
    joinNode.childrenIds = childrenIds;
    return joinNode;
};
const getRelationTreeData = (relation) => {
    const rootIds = [];
    const nodes = new Map();
    // columns
    relation.columns
        .slice()
        .filter(filterByType(Column))
        .sort((a, b) => a.name.toString().localeCompare(b.name.toString()))
        .forEach((col) => {
        const columnNode = getColumnTreeNodeData(col, relation, undefined);
        addUniqueEntry(rootIds, columnNode.id);
        nodes.set(columnNode.id, columnNode);
    });
    // joins
    relation.schema._OWNER.joins
        .slice()
        .filter((join) => join.aliases.filter((alias) => alias.first.relation.value === relation ||
        alias.second.relation.value === relation).length > 0)
        .sort((a, b) => a.name.toString().localeCompare(b.name.toString()))
        .forEach((join) => {
        const joinNode = getJoinTreeNodeData(join, resolveJoinTargetRelation(join, relation), undefined);
        addUniqueEntry(rootIds, joinNode.id);
        nodes.set(joinNode.id, joinNode);
    });
    return { rootIds, nodes };
};
const RelationalOperationElementTreeNodeContainer = (props) => {
    const { node, level, stepPaddingInRem, onNodeSelect } = props;
    const [, dragConnector] = useDrag(() => ({
        type: TABLE_ELEMENT_DND_TYPE,
        item: new TableOrViewTreeNodeDragSource(node),
    }), [node]);
    const ref = useRef(null);
    dragConnector(ref);
    const isExpandable = Boolean(node.childrenIds?.length);
    const nodeTypeIcon = node instanceof ColumnNodeData ? (renderColumnTypeIcon(node.column.type)) : (_jsx(PURE_DatabaseTableJoinIcon, {}));
    const selectNode = () => onNodeSelect?.(node);
    const nodeExpandIcon = isExpandable ? (node.isOpen ? (_jsx(ChevronDownIcon, {})) : (_jsx(ChevronRightIcon, {}))) : (_jsx("div", {}));
    return (_jsxs("div", { className: "tree-view__node__container", onClick: selectNode, ref: ref, style: {
            paddingLeft: `${(level - 1) * (stepPaddingInRem ?? 1)}rem`,
            display: 'flex',
        }, children: [_jsxs("div", { className: "tree-view__node__icon", children: [_jsx("div", { className: "tree-view__node__expand-icon", children: nodeExpandIcon }), _jsx("div", { className: "type-tree__type-icon", children: nodeTypeIcon })] }), _jsxs("div", { className: "tree-view__node__label type-tree__node__label", children: [_jsx("button", { tabIndex: -1, title: `${node.id}`, children: node.label }), node instanceof ColumnNodeData && (_jsx("div", { className: "type-tree__node__type", children: _jsx("button", { className: "type-tree__node__type__label", 
                            // TODO: match type
                            // className={clsx('type-tree__node__type__label', {
                            //   'type-tree__node__type__label--highlighted':
                            //     primitiveType && primitiveType === selectedType,
                            // })}
                            tabIndex: -1, title: "Column Type", children: stringifyDataType(guaranteeType(node.column, Column).type) }) }))] })] }));
};
export const TableOrViewSourceTree = (props) => {
    const { relation, selectedType } = props;
    // NOTE: We only need to compute this once so we use lazy initial state syntax
    // See https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
    const [treeData, setTreeData] = useState(() => getRelationTreeData(relation));
    const onNodeSelect = (node) => {
        if (node.childrenIds?.length) {
            node.isOpen = !node.isOpen;
            // columns
            node.relation.columns.filter(filterByType(Column)).forEach((col) => {
                const columnNode = getColumnTreeNodeData(col, node.relation, node);
                treeData.nodes.set(columnNode.id, columnNode);
            });
            // joins
            node.relation.schema._OWNER.joins
                .filter((join) => join.aliases.filter((alias) => alias.first.relation.value === node.relation ||
                alias.second.relation.value === node.relation).length > 0)
                .forEach((join) => {
                const joinNode = getJoinTreeNodeData(join, resolveJoinTargetRelation(join, node.relation), node);
                treeData.nodes.set(joinNode.id, joinNode);
            });
        }
        setTreeData({ ...treeData });
    };
    const getChildNodes = (node) => {
        if (!node.childrenIds) {
            return [];
        }
        const childrenNodes = node.childrenIds
            .map((id) => treeData.nodes.get(id))
            .filter(isNonNullable)
            // sort so that column nodes come before join nodes
            .sort((a, b) => a.label.localeCompare(b.label))
            .sort((a, b) => (b instanceof ColumnNodeData ? 1 : 0) -
            (a instanceof ColumnNodeData ? 1 : 0));
        return childrenNodes;
    };
    useEffect(() => {
        setTreeData(() => getRelationTreeData(relation));
    }, [relation]);
    return (_jsx(TreeView, { components: {
            TreeNodeContainer: RelationalOperationElementTreeNodeContainer,
        }, treeData: treeData, getChildNodes: getChildNodes, onNodeSelect: onNodeSelect, innerProps: {
            selectedType,
        } }));
};
//# sourceMappingURL=TableOrViewSourceTree.js.map