<template>
    <div class="file-picker-main">
        <div class="file-picker-body">
            <div class="file-picker-toolbar">
                <ElUpload
                    action=""
                    :accept="accept"
                    :showFileList="false"
                    :beforeUpload="handleUpload"
                >
                    <ElButton
                        type="primary"
                        class="ele-btn-icon"
                        :icon="UploadOutlined"
                    >
                        上传
                    </ElButton>
                </ElUpload>
                <div class="file-picker-search">
                    <ElInput
                        :clearable="true"
                        v-model="searchKeyword"
                        placeholder="请输入文件名"
                        @clear="handleSearch"
                        @change="handleSearch"
                    />
                    <ElButton type="primary" @click="handleSearch"
                        >搜索</ElButton
                    >
                </div>
                <EleSegmented
                    v-model="isGridMode"
                    :items="modeSegmentedItems"
                />
            </div>
            <template v-if="fileData.length">
                <div
                    class="file-picker-file-list"
                    @scroll="handleFileListScroll"
                >
                    <EleFileList
                        :boxChoose="true"
                        :icons="localIcons"
                        :smallIcons="localSmallIcons"
                        :contextMenuProps="contextMenuProps"
                        v-bind="fileListProps || {}"
                        :data="fileData"
                        :grid="isGridMode === 1"
                        :selectionType="limit === 1 ? 'radio' : 'checkbox'"
                        :selections="fileSelections"
                        v-model:current="fileCurrent"
                        :contextMenus="fileContextMenus"
                        :class="[{ 'is-ping-top': isPingTop }]"
                        @itemClick="handleFileItemClick"
                        @itemContextMenu="handleFileCtxMenuClick"
                        @update:selections="updateSelections"
                        @itemContextOpen="handleFileCtxMenuOpen"
                    />
                </div>
                <ElePagination
                    size="small"
                    :teleported="false"
                    :pageSizes="[18, 24, 30, 36, 42]"
                    layout="total,prev,pager,next,sizes"
                    v-bind="paginationProps || {}"
                    :currentPage="currentPage"
                    :pageSize="pageSize"
                    :total="total"
                    @update:currentPage="handleCurrentPageChange"
                    @update:pageSize="handlePageSizeChange"
                />
            </template>
            <ElEmpty
                v-else
                :imageSize="80"
                description="无数据"
                v-bind="emptyProps || {}"
            />
        </div>
        <FileSelections
            ref="fileSelectionsRef"
            :fileSelections="
                limit === 1
                    ? fileCurrent
                        ? [fileCurrent]
                        : []
                    : fileSelections
            "
            :limit="limit"
            :selectionListProps="selectionListProps"
            :baseIndex="baseIndex"
            @clearSelections="clearSelections"
            @removeItem="removeItem"
        />
    </div>
</template>

<script setup>
    import { ref, reactive, watch, markRaw } from 'vue';
    import {
        localIcons,
        localSmallIcons
    } from 'ele-admin-plus/es/ele-file-list/icons';
    import {
        UploadOutlined,
        MenuOutlined,
        AppstoreOutlined,
        EditOutlined,
        DragOutlined,
        DeleteOutlined
    } from '@/components/icons';
    import { uploadFile, pageUserFiles } from '@/api/system/file';
    import FileSelections from './file-selections.vue';

    const props = defineProps({
        /** 最大选择数量 */
        limit: Number,
        /** 文件大小限制, 单位MB */
        fileLimit: Number,
        /** 接受上传的文件类型 */
        accept: String,
        /** 接口查询参数 */
        params: Object,
        /** 文件列表自定义属性 */
        fileListProps: Object,
        /** 已选列表自定义属性 */
        selectionListProps: Object,
        /** 空组件属性 */
        emptyProps: Object,
        /** 分页组件属性 */
        paginationProps: Object,
        /** 统一设置层级 */
        baseIndex: Number,
        /** 消息提示组件 */
        messageIns: [Object, Function]
    });

    const emit = defineEmits([
        'queryStart',
        'queryDone',
        'renameFile',
        'moveFile',
        'removeFile',
        'fileItemContextOpen'
    ]);

    /** 当前分组id */
    const fileParentId = ref();

    /** 文件数据 */
    const fileData = ref([]);

    /** 选中的文件数据 */
    const fileSelections = ref([]);

    /** 单选选中的文件数据 */
    const fileCurrent = ref();

    /** 是否网格模式 */
    const isGridMode = ref(1);

    /** 搜索关键字 */
    const searchKeyword = ref('');

    /** 当前页码 */
    const currentPage = ref(1);

    /** 每页显示数量 */
    const pageSize = ref(24);

    /** 总数量 */
    const total = ref(0);

    /** 文件列表是否固定表头 */
    const isPingTop = ref(false);

    /** 视图模式分段器数据 */
    const modeSegmentedItems = [
        {
            icon: MenuOutlined,
            value: 0,
            iconStyle: { transform: 'scale(0.9)' }
        },
        { icon: AppstoreOutlined, value: 1 }
    ];

    /** 已选择列表组件 */
    const fileSelectionsRef = ref();

    /** 文件列表右键菜单属性 */
    const contextMenuProps = reactive({
        menuStyle: { minWidth: '120px' },
        iconProps: { size: 15 },
        popperOptions: { strategy: 'fixed' },
        zIndex: props.baseIndex
    });

    /** 校验最大选择数量 */
    const checkLimit = (selections, isAdd) => {
        if (
            props.limit &&
            props.limit > 1 &&
            (isAdd
                ? selections.length >= props.limit
                : selections.length > props.limit)
        ) {
            props.messageIns?.error?.(`最多只能选择 ${props.limit} 个`);
            return false;
        }
    };

    /** 更新选中数据 */
    const updateSelections = (selections) => {
        if (checkLimit(selections) !== false) {
            fileSelections.value = selections;
            return;
        }
        if (props.limit && props.limit > 1) {
            fileSelections.value = selections.slice(0, props.limit);
        }
    };

    /** 清空选中 */
    const clearSelections = () => {
        fileSelections.value = [];
        fileCurrent.value = void 0;
    };

    /** 获取选中数据 */
    const getSelections = () => {
        if (fileSelectionsRef.value) {
            const selections = fileSelectionsRef.value.getSelections() || [];
            return selections.map((d) => d.userFile);
        }
        return [];
    };

    /** 移除选中 */
    const removeItem = (item) => {
        if (props.limit === 1) {
            clearSelections();
            return;
        }
        const index = fileSelections.value.findIndex((d) => d.key === item.key);
        if (index !== -1) {
            fileSelections.value.splice(index, 1);
        }
    };

    /** 搜索 */
    const handleSearch = () => {
        queryData({ page: 1 });
    };

    /** 切换当前页码 */
    const handleCurrentPageChange = (page) => {
        if (currentPage.value !== page) {
            currentPage.value = page;
            queryData();
        }
    };

    /** 切换每页显示数量 */
    const handlePageSizeChange = (limit) => {
        if (pageSize.value !== limit) {
            currentPage.value = 1;
            pageSize.value = limit;
            const maxPage = Math.ceil(total.value / limit);
            if (maxPage && currentPage.value > maxPage) {
                currentPage.value = maxPage;
            }
            queryData();
        }
    };

    /** 文件列表点击事件 */
    const handleFileItemClick = (item) => {
        if (props.limit === 1) {
            fileCurrent.value = item;
            return;
        }
        const index = fileSelections.value.findIndex((d) => d.key === item.key);
        if (index !== -1) {
            fileSelections.value.splice(index, 1);
            return;
        }
        if (checkLimit(fileSelections.value, true) !== false) {
            fileSelections.value.push(item);
        }
    };

    /** 文件列表右键菜单点击事件 */
    const handleFileCtxMenuClick = (option) => {
        const { key, item } = option;
        const userFileItem = item.userFile;
        if (key === 'preview') {
            if (!item.thumbnail) {
                window.open(item.url);
                return;
            }
            const data = fileData.value.filter((d) => !!d.thumbnail);
            const urls = data.map((d) => d.url);
            const index = data.indexOf(item);
            if (index !== -1) {
                fileSelectionsRef.value &&
                    fileSelectionsRef.value.openImagePreview(urls, index);
            }
        } else if (key === 'rename') {
            emit('renameFile', userFileItem, true);
        } else if (key === 'move') {
            emit('moveFile', userFileItem, true);
        } else if (key === 'remove') {
            emit('removeFile', userFileItem, true);
        }
    };

    /** 文件列表右键菜单数据 */
    const fileContextMenus = (item) => {
        const menus = [
            {
                title: '重命名',
                command: 'rename',
                icon: markRaw(EditOutlined)
            },
            {
                title: '移动到',
                command: 'move',
                icon: markRaw(DragOutlined)
            },
            {
                title: '删除',
                command: 'remove',
                icon: markRaw(DeleteOutlined),
                divided: true,
                danger: true
            }
        ];
        if (item.thumbnail) {
            menus[0].divided = true;
            menus.unshift({ title: '预览', command: 'preview' });
        } else {
            menus[0].divided = true;
            menus.unshift({ title: '打开', command: 'preview' });
        }
        return menus;
    };

    /** 文件列表右键菜单打开事件 */
    const handleFileCtxMenuOpen = () => {
        emit('fileItemContextOpen');
    };

    /** 校验选择的文件 */
    const checkFile = (file) => {
        if (!file) {
            return;
        }
        if (props.accept === 'image/*') {
            if (!file.type.startsWith('image')) {
                props.messageIns?.error?.('只能选择图片');
                return;
            }
        } else if (props.accept === '.xls,.xlsx') {
            if (
                ![
                    'application/vnd.ms-excel',
                    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
                ].includes(file.type)
            ) {
                props.messageIns?.error?.('只能选择 excel 文件');
                return;
            }
        }
        if (props.fileLimit && file.size / 1024 / 1024 > props.fileLimit) {
            props.messageIns?.error?.(`大小不能超过 ${props.fileLimit}MB`);
            return;
        }
        return true;
    };

    /** 文件上传事件 */
    const handleUpload = (file) => {
        if (checkFile(file)) {
            const loading = props.messageIns?.loading?.({
                message: '上传中..',
                plain: true,
                mask: true
            });
            uploadFile(file)
                .then(() => {
                    loading?.close?.();
                    props.messageIns?.success?.('上传成功');
                    queryData();
                })
                .catch((e) => {
                    loading?.close?.();
                    props.messageIns?.error?.(e.message);
                });
        }
        return false;
    };

    /** 文件列表滚动事件 */
    const handleFileListScroll = (e) => {
        const wrapEl = e.currentTarget;
        const scrollTop = wrapEl.scrollTop;
        isPingTop.value = scrollTop > 1;
    };

    /** 触发文件数据请求完成 */
    const handleQueryDone = () => {
        emit('queryDone');
    };

    /** 判断是否是图片文件 */
    const isImageFile = (item) => {
        return (
            typeof item.contentType === 'string' &&
            item.contentType.startsWith('image/') &&
            item.url
        );
    };

    /** 格式化文件大小 */
    const formatLength = (length) => {
        if (length == null) {
            return '-';
        }
        if (length < 1024) {
            return length + 'B';
        } else if (length < 1024 * 1024) {
            return (length / 1024).toFixed(1) + 'KB';
        } else if (length < 1024 * 1024 * 1024) {
            return (length / 1024 / 1024).toFixed(1) + 'M';
        } else {
            return (length / 1024 / 1024 / 1024).toFixed(1) + 'G';
        }
    };

    /** 查询文件数据 */
    const queryData = (params) => {
        emit('queryStart');
        if (params) {
            if (params.page != null) {
                currentPage.value = params.page;
            }
            if (params.parentId != null) {
                fileParentId.value = params.parentId;
            }
            if (params.name != null) {
                searchKeyword.value = params.name;
            }
        }
        const parentId = fileParentId.value;
        pageUserFiles({
            page: currentPage.value,
            limit: pageSize.value,
            parentId: parentId === -1 ? void 0 : parentId,
            name: searchKeyword.value,
            isDirectory: 0,
            ...(props.params || {})
        })
            .then((result) => {
                if (!result?.list?.length && result?.count) {
                    const maxPage = Math.ceil(result.count / pageSize.value);
                    if (maxPage && currentPage.value > maxPage) {
                        queryData({ page: maxPage });
                        return;
                    }
                }
                total.value = result?.count || 0;
                fileData.value =
                    result?.list?.map?.((d) => {
                        return {
                            key: d.id,
                            name: d.name,
                            url: d.url,
                            thumbnail: isImageFile(d) ? d.url : void 0,
                            length: formatLength(d.length),
                            updateTime: d.updateTime,
                            isDirectory: d.isDirectory === 1,
                            userFile: d
                        };
                    }) || [];
                handleQueryDone();
            })
            .catch((e) => {
                fileData.value = [];
                props.messageIns?.error?.(e.message);
                handleQueryDone();
            });
    };

    watch(
        () => props.limit,
        () => {
            clearSelections();
        }
    );

    watch(
        () => props.baseIndex,
        (baseIndex) => {
            contextMenuProps.zIndex = baseIndex;
        }
    );

    defineExpose({ queryData, clearSelections, getSelections });
</script>
