<script setup>
import useDialogStore from 'stores/dialog.js'
import { h, markRaw, nextTick, reactive, ref } from 'vue'
import useConnectionStore from 'stores/connections.js'
import { NIcon, NSpace, NText, useThemeVars } from 'naive-ui'
import { ConnectionType } from '@/consts/connection_type.js'
import Folder from '@/components/icons/Folder.vue'
import Server from '@/components/icons/Server.vue'
import Cluster from '@/components/icons/Cluster.vue'
import { debounce, get, includes, indexOf, isEmpty, split } from 'lodash'
import Config from '@/components/icons/Config.vue'
import Delete from '@/components/icons/Delete.vue'
import Unlink from '@/components/icons/Unlink.vue'
import CopyLink from '@/components/icons/CopyLink.vue'
import Connect from '@/components/icons/Connect.vue'
import { useI18n } from 'vue-i18n'
import useTabStore from 'stores/tab.js'
import Edit from '@/components/icons/Edit.vue'
import { hexGammaCorrection, parseHexColor, toHexColor } from '@/utils/rgb.js'
import IconButton from '@/components/common/IconButton.vue'
import usePreferencesStore from 'stores/preferences.js'
import useBrowserStore from 'stores/browser.js'
import { useRender } from '@/utils/render.js'

const themeVars = useThemeVars()
const i18n = useI18n()
const render = useRender()
const connectingServer = ref('')
const connectionStore = useConnectionStore()
const browserStore = useBrowserStore()
const tabStore = useTabStore()
const prefStore = usePreferencesStore()
const dialogStore = useDialogStore()

const expandedKeys = ref([])
const selectedKeys = ref([])

const props = defineProps({
    filterPattern: {
        type: String,
    },
})

const contextMenuParam = reactive({
    show: false,
    x: 0,
    y: 0,
    options: null,
    currentNode: null,
})

const menuOptions = {
    [ConnectionType.Group]: ({ opened }) => [
        {
            key: 'group_rename',
            label: 'interface.rename_conn_group',
            icon: Edit,
        },
        {
            key: 'group_delete',
            label: 'interface.remove_conn_group',
            icon: Delete,
        },
    ],
    [ConnectionType.Server]: ({ name }) => {
        const connected = browserStore.isConnected(name)
        if (connected) {
            return [
                {
                    key: 'server_close',
                    label: 'interface.disconnect',
                    icon: Unlink,
                },
                {
                    key: 'server_edit',
                    label: 'interface.edit_conn',
                    icon: Config,
                },
                {
                    key: 'server_dup',
                    label: 'interface.dup_conn',
                    icon: CopyLink,
                },
                {
                    type: 'divider',
                    key: 'd1',
                },
                {
                    key: 'server_remove',
                    label: 'interface.remove_conn',
                    icon: Delete,
                },
            ]
        } else {
            return [
                {
                    key: 'server_open',
                    label: 'interface.open_connection',
                    icon: Connect,
                },
                {
                    key: 'server_edit',
                    label: 'interface.edit_conn',
                    icon: Config,
                },
                {
                    key: 'server_dup',
                    label: 'interface.dup_conn',
                    icon: CopyLink,
                },
                {
                    type: 'divider',
                    key: 'd1',
                },
                {
                    key: 'server_remove',
                    label: 'interface.remove_conn',
                    icon: Delete,
                },
            ]
        }
    },
}

/**
 * get mark color of server saved in preferences
 * @param name
 * @return {null|string}
 */
const getServerMarkColor = (name) => {
    const { markColor = '' } = connectionStore.serverProfile[name] || {}
    if (!isEmpty(markColor)) {
        const rgb = parseHexColor(markColor)
        const rgb2 = hexGammaCorrection(rgb, 0.75)
        return toHexColor(rgb2)
    }
    return null
}

const renderLabel = ({ option }) => {
    if (option.type === ConnectionType.Server) {
        const color = getServerMarkColor(option.name)
        if (color != null) {
            return h(
                NText,
                {
                    style: {
                        color,
                        fontWeight: '450',
                    },
                },
                () => option.label,
            )
        }
    }
    return option.label
}

// render horizontal item
const renderIconMenu = (items) => {
    return h(
        NSpace,
        {
            align: 'center',
            inline: true,
            size: 3,
            wrapItem: false,
            wrap: false,
            style: 'margin-right: 5px',
        },
        () => items,
    )
}

const renderPrefix = ({ option }) => {
    const iconTransparency = prefStore.isDark ? 0.75 : 1
    switch (option.type) {
        case ConnectionType.Group:
            const opened = indexOf(expandedKeys.value, option.key) !== -1
            return h(
                NIcon,
                { size: 20 },
                {
                    default: () =>
                        h(Folder, {
                            open: opened,
                            fillColor: `rgba(255,206,120,${iconTransparency})`,
                        }),
                },
            )
        case ConnectionType.Server:
            const connected = browserStore.isConnected(option.name)
            const color = getServerMarkColor(option.name)
            const icon = option.cluster === true ? Cluster : Server
            return h(
                NIcon,
                { size: 20, color: !!!connected ? color : '#dc423c' },
                {
                    default: () =>
                        h(icon, {
                            inverse: !!connected,
                            fillColor: `rgba(220,66,60,${iconTransparency})`,
                        }),
                },
            )
    }
}

const getServerMenu = (connected) => {
    const btns = []
    if (connected) {
        btns.push(
            h(IconButton, {
                tTooltip: 'interface.disconnect',
                icon: Unlink,
                onClick: () => handleSelectContextMenu('server_close'),
            }),
            h(IconButton, {
                tTooltip: 'interface.edit_conn',
                icon: Config,
                onClick: () => handleSelectContextMenu('server_edit'),
            }),
        )
    } else {
        btns.push(
            h(IconButton, {
                tTooltip: 'interface.open_connection',
                icon: Connect,
                onClick: () => handleSelectContextMenu('server_open'),
            }),
            h(IconButton, {
                tTooltip: 'interface.edit_conn',
                icon: Config,
                onClick: () => handleSelectContextMenu('server_edit'),
            }),
            h(IconButton, {
                tTooltip: 'interface.remove_conn',
                icon: Delete,
                onClick: () => handleSelectContextMenu('server_remove'),
            }),
        )
    }
    return btns
}

const getGroupMenu = () => {
    return [
        h(IconButton, {
            tTooltip: 'interface.rename_conn_group',
            icon: Config,
            onClick: () => handleSelectContextMenu('group_rename'),
        }),
        h(IconButton, {
            tTooltip: 'interface.remove_conn_group',
            icon: Delete,
            onClick: () => handleSelectContextMenu('group_delete'),
        }),
    ]
}

const renderSuffix = ({ option }) => {
    if (includes(selectedKeys.value, option.key)) {
        switch (option.type) {
            case ConnectionType.Server:
                const connected = browserStore.isConnected(option.name)
                return renderIconMenu(getServerMenu(connected))
            case ConnectionType.Group:
                return renderIconMenu(getGroupMenu())
        }
    }
    return null
}

const onUpdateExpandedKeys = (keys, option) => {
    expandedKeys.value = keys
}

const onUpdateSelectedKeys = (keys, option) => {
    selectedKeys.value = keys
}

/**
 * Open connection
 * @param name
 * @returns {Promise<void>}
 */
const openConnection = async (name) => {
    try {
        connectingServer.value = name
        if (!browserStore.isConnected(name)) {
            await browserStore.openConnection(name)
        }
        // check if connection already canceled before finish open
        if (!isEmpty(connectingServer.value)) {
            tabStore.upsertTab({
                server: name,
                db: browserStore.getSelectedDB(name),
            })
        }
    } catch (e) {
        $message.error(e.message)
        // node.isLeaf = undefined
    } finally {
        connectingServer.value = ''
    }
}

const removeConnection = (name) => {
    $dialog.warning(
        i18n.t('dialogue.remove_tip', { type: i18n.t('dialogue.connection.conn_name'), name }),
        async () => {
            connectionStore.deleteConnection(name).then(({ success, msg }) => {
                if (!success) {
                    $message.error(msg)
                }
            })
        },
    )
}

const removeGroup = async (name) => {
    $dialog.warning(i18n.t('dialogue.remove_group_tip', { name }), async () => {
        connectionStore.deleteGroup(name).then(({ success, msg }) => {
            if (!success) {
                $message.error(msg)
            }
        })
    })
}

const expandKey = (key) => {
    const idx = indexOf(expandedKeys.value, key)
    if (idx === -1) {
        expandedKeys.value.push(key)
    } else {
        expandedKeys.value.splice(idx, 1)
    }
}

const nodeProps = ({ option }) => {
    return {
        onDblclick: async () => {
            if (option.type === ConnectionType.Server) {
                openConnection(option.name).then(() => {})
            } else if (option.type === ConnectionType.Group) {
                // toggle expand
                nextTick().then(() => expandKey(option.key))
            }
        },
        onContextmenu(e) {
            e.preventDefault()
            const mop = menuOptions[option.type]
            if (mop == null) {
                return
            }
            contextMenuParam.show = false
            nextTick().then(() => {
                contextMenuParam.options = markRaw(mop(option))
                contextMenuParam.currentNode = option
                contextMenuParam.x = e.clientX
                contextMenuParam.y = e.clientY
                contextMenuParam.show = true
                selectedKeys.value = [option.key]
            })
        },
    }
}

const handleSelectContextMenu = (key) => {
    contextMenuParam.show = false
    const selectedKey = get(selectedKeys.value, 0)
    if (selectedKey == null) {
        return
    }
    const [group, name] = split(selectedKey, '/')
    if (isEmpty(group) && isEmpty(name)) {
        return
    }
    switch (key) {
        case 'server_open':
            openConnection(name).then(() => {})
            break
        case 'server_edit':
            // ask for close relevant connections before edit
            if (browserStore.isConnected(name)) {
                $dialog.warning(i18n.t('dialogue.edit_close_confirm'), () => {
                    browserStore.closeConnection(name)
                    dialogStore.openEditDialog(name)
                })
            } else {
                dialogStore.openEditDialog(name)
            }
            break
        case 'server_dup':
            dialogStore.openDuplicateDialog(name)
            break
        case 'server_remove':
            removeConnection(name)
            break
        case 'server_close':
            browserStore.closeConnection(name).then((closed) => {
                if (closed) {
                    $message.success(i18n.t('dialogue.handle_succ'))
                }
            })
            break
        case 'group_rename':
            if (!isEmpty(group)) {
                dialogStore.openRenameGroupDialog(group)
            }
            break
        case 'group_delete':
            if (!isEmpty(group)) {
                removeGroup(group)
            }
            break
        default:
            console.warn('TODO: handle context menu:' + key)
    }
}

const findSiblingsAndIndex = (node, nodes) => {
    if (!nodes) {
        return [null, null]
    }
    for (let i = 0; i < nodes.length; ++i) {
        const siblingNode = nodes[i]
        if (siblingNode.key === node.key) {
            return [nodes, i]
        }
        const [siblings, index] = findSiblingsAndIndex(node, siblingNode.children)
        if (siblings && index !== null) {
            return [siblings, index]
        }
    }
    return [null, null]
}

// delay save until drop stopped after 2 seconds
const saveSort = debounce(connectionStore.saveConnectionSorted, 2000, { trailing: true })
const handleDrop = ({ node, dragNode, dropPosition }) => {
    const [dragNodeSiblings, dragNodeIndex] = findSiblingsAndIndex(dragNode, connectionStore.connections)
    if (dragNodeSiblings === null || dragNodeIndex === null) {
        return
    }
    if (node.type === ConnectionType.Group && dragNode.type === ConnectionType.Group) {
        return
    }
    dragNodeSiblings.splice(dragNodeIndex, 1)
    if (dropPosition === 'inside') {
        if (node.children) {
            node.children.unshift(dragNode)
        } else {
            node.children = [dragNode]
        }
    } else if (dropPosition === 'before') {
        const [nodeSiblings, nodeIndex] = findSiblingsAndIndex(node, connectionStore.connections)
        if (nodeSiblings === null || nodeIndex === null) {
            return
        }
        nodeSiblings.splice(nodeIndex, 0, dragNode)
    } else if (dropPosition === 'after') {
        const [nodeSiblings, nodeIndex] = findSiblingsAndIndex(node, connectionStore.connections)
        if (nodeSiblings === null || nodeIndex === null) {
            return
        }
        nodeSiblings.splice(nodeIndex + 1, 0, dragNode)
    }
    connectionStore.connections = Array.from(connectionStore.connections)
    saveSort()
}

const onCancelOpen = () => {
    if (!isEmpty(connectingServer.value)) {
        browserStore.closeConnection(connectingServer.value)
        connectingServer.value = ''
    }
}
</script>

<template>
    <div class="connection-tree-wrapper" @keydown.esc="contextMenuParam.show = false">
        <n-tree
            :animated="false"
            :block-line="true"
            :block-node="true"
            :cancelable="false"
            :data="connectionStore.connections"
            :draggable="true"
            :expanded-keys="expandedKeys"
            :node-props="nodeProps"
            :pattern="props.filterPattern"
            :render-label="renderLabel"
            :render-prefix="renderPrefix"
            :render-suffix="renderSuffix"
            :selected-keys="selectedKeys"
            class="fill-height"
            virtual-scroll
            @drop="handleDrop"
            @update:selected-keys="onUpdateSelectedKeys"
            @update:expanded-keys="onUpdateExpandedKeys">
            <template #empty>
                <n-empty :description="$t('interface.empty_server_list')" class="empty-content" />
            </template>
        </n-tree>

        <!-- status display modal -->
        <n-modal :show="connectingServer !== ''" transform-origin="center">
            <n-card
                :bordered="false"
                :content-style="{ textAlign: 'center' }"
                aria-model="true"
                role="dialog"
                style="width: 400px">
                <n-spin>
                    <template #description>
                        <n-space vertical>
                            <n-text strong>{{ $t('dialogue.opening_connection') }}</n-text>
                            <n-button :focusable="false" secondary size="small" @click="onCancelOpen">
                                {{ $t('dialogue.interrupt_connection') }}
                            </n-button>
                        </n-space>
                    </template>
                </n-spin>
            </n-card>
        </n-modal>

        <!-- context menu -->
        <n-dropdown
            :keyboard="true"
            :options="contextMenuParam.options"
            :render-icon="({ icon }) => render.renderIcon(icon)"
            :render-label="({ label }) => render.renderLabel($t(label), { class: 'context-menu-item' })"
            :show="contextMenuParam.show"
            :x="contextMenuParam.x"
            :y="contextMenuParam.y"
            placement="bottom-start"
            trigger="manual"
            @clickoutside="contextMenuParam.show = false"
            @select="handleSelectContextMenu" />
    </div>
</template>

<style lang="scss" scoped>
@import '@/styles/content';

.connection-tree-wrapper {
    height: 100%;
    overflow: hidden;
}
</style>
