<template>
    <Tree ref="tree" :data="treeRenderData" :render="renderContent" @on-contextmenu="handleContextMenu" expand-node>
        <template #contextMenu>
            <DropdownItem v-for="item in currentMenuNode.contextMenuData" :key="item.name"
                @click="item.handler(currentMenuNode)">
                {{ item.name }}
            </DropdownItem>
        </template>
    </Tree>
</template>

<script>
import { resolveComponent } from 'vue'

export default {
    props: {
        treeData: {
            type: Object,
            required: false,
            default: {
                path: "/Users/Frankie/rootFolder",
                name: "rootFolder",
                directory: true,
                nodes: [{
                    path: "/Users/Frankie/rootFolder/file.txt",
                    name: "file.txt",
                    suffix: "txt",
                    directory: false,
                }],
                // 如果定义了 contextMenuData，则开启右键菜单
                // 需要定义 name 和 handler，分别对应显示的文本和点击事件
                contextMenuData: [
                    {
                        name: "新建目录",
                        handler: (nodeRenderData) => {
                            console.log(nodeRenderData);
                        }
                    }
                ],
            },
        },
        selectedNode: {
            type: Array,
            required: false,
            default: [],
        },
        // 文件树展开层级。-1 表示全部展开，0 表示不展开，1 表示仅展开一层
        expandLevel: {
            type: Number,
            required: false,
            default: -1,
        },
        showHiddenFile: {
            type: Boolean,
            required: false,
            default: false,
        },
        specifyFileSuffix: {
            type: Array,
            required: false,
            default: [],
        },
    },
    data() {
        return {
            root: null,
            treeRenderData: [],
            currentMenuNode: {},
        }
    },
    mounted() {
        this.renderTreeByTreeData(this.treeData)
    },
    watch: {
        treeData(newTreeData) {
            this.renderTreeByTreeData(newTreeData)
        },
    },
    methods: {
        // 以 treeData 为源数据，生成完整的文件树数据 treeRenderData
        renderTreeByTreeData(currentTreeData) {
            const renderNode = this.renderNodeByNodeData(currentTreeData, 0)
            this.treeRenderData = renderNode == null ? [] : [renderNode]
        },
        // 生成文件树节点数据。level 表示 nodeData 在文件树的层级（从 0 开始计数）
        renderNodeByNodeData(nodeData, level) {
            // 判断必须包含的字段，没有则返回 null
            if (nodeData == null || nodeData == undefined) {
                return null
            }
            if (nodeData.path == undefined ||
                nodeData.name == undefined ||
                nodeData.directory == undefined
            ) {
                return null
            }
            // 如果需要隐藏 . 开头的目录和文件
            if (!this.showHiddenFile && nodeData.name.startsWith(".")) {
                return null
            }
            // 如果要过滤文件后缀
            if (this.specifyFileSuffix.length > 0) {
                if (!nodeData.directory && nodeData.suffix != undefined &&
                    !this.specifyFileSuffix.includes(nodeData.suffix)) {
                    return null
                }
            }

            let allowExpand = this.expandLevel < 0 || level < this.expandLevel
            let nodeRenderData = {
                title: nodeData.name,
                directory: nodeData.directory,
                expand: (allowExpand && nodeData.directory) ? true : false,
                selected: this.selectedNode.includes(nodeData.path),
                // 附带数据，不直接参与渲染
                path: nodeData.path,
                level: level,
                sourceData: nodeData, // 保留源数据。重新渲染节点时需要使用
            }
            if (nodeData.contextMenuData && nodeData.contextMenuData.length > 0) {
                // contextmenu = true 用于开启右键菜单
                nodeRenderData.contextmenu = true
                // contextMenuData 是渲染右键菜单的源数据
                nodeRenderData.contextMenuData = nodeData.contextMenuData
            }
            if (nodeData.directory) {
                if (nodeData.nodes === undefined) {
                    nodeData.nodes = [];
                }
                let childrenRenderData = [];
                nodeData.nodes.forEach(node => {
                    const renderNode = this.renderNodeByNodeData(node, level + 1)
                    if (renderNode != null) {
                        if (renderNode.selected || renderNode.expand) {
                            nodeRenderData.expand = true
                        }
                        childrenRenderData.push(renderNode)
                    }
                });
                nodeRenderData.children = childrenRenderData
            }

            // 如果 input 为 true，保留源数据，然后渲染成 input 框，并绑定输入完成事件
            if (nodeData.input === true) {
                // 标识渲染为 input 框
                nodeRenderData.input = true;
                // 当输入完成后，触发的完成事件
                nodeRenderData.completer = nodeData.completer ?
                    nodeData.completer : (data, value) => {
                        this.$Message.error("Input Node have no completer.");
                    };
                // 源数据中的 input 标识需要删除。当输入取消时，用源数据进行恢复节点
                delete nodeData.input;
                delete nodeData.completer;
            }
            return nodeRenderData
        },
        // 负责文件树每个节点的渲染，决策每个节点的样式
        renderContent(h, { root, node, data }) {
            // 将 root 树存储起来，便于后续操作文件树增删节点
            this.root = root;
            // 给 data 绑定 nodeKey 和 parentKey，便于向上索引
            data.nodeKey = node.nodeKey;
            data.parentKey = node.parent;
            // 将节点渲染为输入框，并绑定输入完成事件
            if (data.input) {
                return h('span', {
                    style: {
                        display: 'inline-block',
                        width: '100%'
                    },
                }, [
                    h('span', [
                        // h(resolveComponent('Input'), {
                        // 使用 resolveComponent 时 blur 事件失效，改用原生 input 框
                        h('Input', {
                            type: 'text',
                            autofocus: true,
                            autoCorrect: "off",
                            spellCheck: "false",
                            class: 'ivu-input ivu-input-default',
                            style: { marginRight: '8px' },
                            value: data.title,
                            // 当 input 框触发 enter 或 blur 事件时，执行 completer 回调
                            onKeyup: (event) => {
                                if (event.key === "Enter") {
                                    data.completer(data, event.target.value);
                                }
                            },
                            onBlur: (event) => {
                                data.completer(data, event.target.value);
                            }
                        }),
                    ])
                ]);
            }
            // 其他正常节点渲染事件
            return h('span', {
                style: {
                    display: 'inline-block',
                    width: '100%'
                },
                onClick: () => { this.selectNode(data) }
            }, [
                h('span', [
                    h(resolveComponent('Icon'), {
                        type: data.directory ? 'ios-folder-outline' : 'ios-paper-outline',
                        style: {
                            marginRight: '8px'
                        }
                    }),
                    h('span', data.title)
                ])
            ]);
        },
        // 当点击某个节点时，设置选中状态，并触发事件
        selectNode(nodeRenderData) {
            // 文件夹不做任何动作
            if (nodeRenderData.directory) {
                return
            }

            // 清除其他选中状态，为当前节点增加选中状态
            let selectedNodes = this.$refs.tree.getSelectedNodes()
            selectedNodes.forEach(element => {
                element.selected = false
            });
            nodeRenderData.selected = true

            // 向父组件暴露当前选中的文件
            this.$emit('file-selected', nodeRenderData);
        },
        // 在父节点插入子节点
        insertTreeNode(parentRenderData, nodeData, index) {
            const renderNode = this.renderNodeByNodeData(nodeData, parentRenderData.level + 1);
            // 由于文件树有过滤规则，此处 renderNode 可能为 null
            if (renderNode == null) {
                return false;
            }
            if (index === -1) {
                parentRenderData.children.push(renderNode);
            } else {
                parentRenderData.children.splice(index, 0, renderNode);
            }
            return renderNode;
        },
        // 为一个子节点寻找父节点。返回值：父节点，以及子节点在父节点中的位置
        findNodeParent(nodeRenderData) {
            try {
                const parentKey = this.root.find(el => el.nodeKey === nodeRenderData.nodeKey).parent;
                const parent = this.root.find(el => el.nodeKey === parentKey).node;
                const index = parent.children.indexOf(nodeRenderData);
                return [parent, index];
            } catch (err) {
                console.log("find parentNode failed.");
                return [null, -1];
            }
        },
        // 给父节点增加子节点
        appendTreeNode(parentRenderData, nodeData) {
            return this.insertTreeNode(parentRenderData, nodeData, -1);
        },
        // 删除文件树中某个子节点。返回值：是否成功，父节点
        removeTreeNode(nodeRenderData) {
            const [parent, index] = this.findNodeParent(nodeRenderData);
            if (parent === null) {
                return false;
            }
            parent.children.splice(index, 1);
            return true;
        },
        // 让一个子节点在父节点下重新渲染
        reRenderTreeNode(nodeRenderData) {
            // 先找到子节点的父节点
            const [parentRenderData, indexOfParent] = this.findNodeParent(nodeRenderData);
            if (parentRenderData === null) {
                return false;
            }
            // 将节点从文件树上删去
            if (this.removeTreeNode(nodeRenderData) === false) {
                return false;
            }
            // 然后使用新的 nodeData 挂载到父节点下，以重新渲染该节点
            return this.insertTreeNode(parentRenderData, nodeRenderData.sourceData, indexOfParent);
        },
        // 对某个节点进行右键操作时，给 currentMenuNode 赋值，便于渲染右键菜单
        handleContextMenu(nodeRenderData) {
            this.currentMenuNode = nodeRenderData;
        },
    }
}
</script>
<style>
.hidden {
    display: none;
}
</style>
