<template>
    <template v-if="style == 'card'">
        <view class="ut-upload-card">
            <template v-for="(item, index) in fileList" :key="index">
                <view :style="{ width, height }" class="card-item">
                    <template v-if="getItemKind(item) === 'image'">
                        <image :src="item.status === 'uploading' && item.tempUrl ? item.tempUrl : item.url" :mode="mode" style="width: 100%; height: 100%" @click="onPreview(index)"></image>
                    </template>
                    <template v-else-if="getItemKind(item) === 'video'">
                        <video :src="item.status === 'uploading' && item.tempUrl ? item.tempUrl : item.url" controls style="width: 100%; height: 100%" @click="onPreview(index)"></video>
                    </template>
                    <template v-else>
                        <view class="d-flex flex-cln file-box" @click="onPreview(index)">
                            <view class="flex1">
                                <view class="f-s-28 c-primary up-line-2">{{ item.fileName || '文件' }}</view>
                                <view class="f-s-24 c-999">{{ item.fileSize ? (item.fileSize / 1024).toFixed(2) + ' KB' : '' }}</view>
                            </view>
                            <view class="d-flex j-ed">
                                <image :src="getFileIconByUrl(item.tempUrl || item.url)" mode="aspectFit" style="width: 40rpx; height: 40rpx;" />
                            </view>
                        </view>
                    </template>
                    <view v-if="item.status === 'uploading'" class="uploading-mask">
                        <view class="uploading-text">上传中...</view>
                    </view>
                    <view class="del-btn" @click.stop="onDelete(index)">
                        <up-icon name="close" color="#fff" size="32rpx"></up-icon>
                    </view>
                </view>
            </template>
            <template v-if="fileList.length < maxCount">
                <!-- 兼容旧用法：只有一种类型时，仍然只展示一个按钮，文案/样式按类型和配置对象决定 -->
                <view
                    v-if="getAcceptKinds().length === 1"
                    :style="[{ width, height }, getUploadBtnStyle(getAcceptKinds()[0])]"
                    @click="clickBtnUpload"
                    class="card-item btn-select d-flex flex-cln j-c a-c"
                    :class="getUploadBtnClass(getAcceptKinds()[0])"
                >
                    <view class="mb-10">
                        <up-icon :color="getUploadIconColor(getAcceptKinds()[0])" :name="getUploadIconName(getAcceptKinds()[0])" :iconSize="getUploadIconSize(getAcceptKinds()[0])"></up-icon>
                    </view>
                    <view class="f-s-24 c-primary" :style="getUploadBtnTextStyle(getAcceptKinds()[0])">{{ getUploadTextByKind(getAcceptKinds()[0]) }}</view>
                </view>
                <!-- 多种类型时，根据 accept 展示多个按钮，每个按钮对应一种上传类型 -->
                <template v-else>
                    <view
                        v-for="kind in getAcceptKinds()"
                        :key="kind"
                        :style="[{ width, height }, getUploadBtnStyle(kind)]"
                        @click="onClickUpload(kind)"
                        class="card-item btn-select d-flex flex-cln j-c a-c"
                        :class="getUploadBtnClass(kind)"
                    >
                        <view class="mb-10">
                            <up-icon :color="getUploadIconColor(kind)" :name="getUploadIconName(kind)" :iconSize="getUploadIconSize(kind)"></up-icon>
                        </view>
                        <view class="f-s-24 c-primary" :style="getUploadBtnTextStyle(kind)">{{ getUploadTextByKind(kind) }}</view>
                    </view>
                </template>
            </template>
        </view>
    </template>
</template>

<script setup lang="ts">
import upload from '@/utils/upload';
import { fileExt, isUrl } from '@/utils/ruoyi';
import { getFileIconByUrl } from '@/utils/common';

type AcceptKind = 'image' | 'video' | 'file';

interface UploadButtonConfigItem {
    text?: string; // 按钮文字
    bgColor?: string; // 按钮背景色
    textColor?: string; // 文字颜色
    iconName?: string; // 覆盖默认图标名
    iconColor?: string; // 覆盖默认图标颜色
    iconSize?: string | number; // 覆盖默认图标大小
    customClass?: string; // 自定义 class
}

interface FileItem {
    url: string;
    fileName?: string;
    fileSize?: number;
    // 封面图
    coverUrl?: string;
    tempUrl?: string;
    status?: 'uploading' | 'done' | 'error';
    progress?: number;
    kind?: AcceptKind;
}

interface UploadEvent {
    file: Array<{ url: string }>;
}

interface DeleteEvent {
    index: number;
}

interface Props {
    modelValue: string | string[] | Record<string, any> | null;
    maxCount: number;
    width: string;
    height: string;
    multiple: boolean;
    uploadText: string;
    uploadImageText?: string;
    uploadVideoText?: string;
    uploadFileText?: string;
    uploadButtonConfig?: Partial<Record<AcceptKind, UploadButtonConfigItem>>; // 统一配置 image/file/video 按钮
    uploadIcon: string;
    accept: string; // image/video/file image,file,video
    uploadUrl?: string;
    uploadTimeout?: number;
    extension?: string | string[]; // 限制选择的文件扩展名（仅在 accept==='file' 生效）
    valueType: 'string' | 'array' | 'object';
    style: 'card' | 'list';
    iconName: string;
    iconColor: string;
    iconSize: string | number;
    mode: 'aspectFill' | 'aspectFit' | 'widthFix' | 'heightFix' | 'top' | 'bottom' | 'center' | 'left' | 'right' | 'top left' | 'top right' | 'bottom left' | 'bottom right';
}

const props = withDefaults(defineProps<Props>(), {
    modelValue: () => [],
    maxCount: 1,
    width: '210rpx',
    height: '210rpx',
    multiple: true,
    uploadText: '点击上传',
    uploadImageText: '上传图片',
    uploadVideoText: '上传视频',
    uploadFileText: '上传文件',
    uploadIcon: 'plus',
    accept: 'image', // image/video/file   image,file,video
    uploadUrl: '/resource/oss/upload',
    uploadTimeout: 600000,
    extension: () => ['pdf'],
    valueType: 'string',
    style: 'card',
    iconName: 'plus', // plus/camera
    iconColor: '#37a954',
    iconSize: '30rpx',
    mode: 'aspectFill',
    uploadButtonConfig: () => ({}),
});

const emit = defineEmits<{
    change: [value: any];
    'update:modelValue': [value: any];
}>();

const fileList = ref<FileItem[]>([]);
const buildFileName = (path: string, kind: AcceptKind) => {
    const ext = fileExt(path) || 'dat';
    const prefix = kind === 'image' ? 'img' : kind === 'video' ? 'video' : 'file';
    return `${prefix}_${Date.now()}.${ext}`;
};
const getAcceptKinds = (): AcceptKind[] => {
    if (!props.accept) return ['image'];
    const parts = String(props.accept)
        .split(',')
        .map((s) => s.trim())
        .filter(Boolean) as AcceptKind[];
    const all: AcceptKind[] = ['image', 'video', 'file'];
    const result = parts.filter((p) => all.includes(p));
    return result.length ? result : ['image'];
};

const getBtnConfig = (kind: AcceptKind): UploadButtonConfigItem => {
    return (props.uploadButtonConfig && props.uploadButtonConfig[kind]) || {};
};

const detectKindByUrl = (path?: string | null): AcceptKind => {
    if (!path) return 'file';
    const ext = (fileExt(path) || '').toLowerCase();
    const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'];
    const videoExts = ['mp4', 'mov', 'avi', 'wmv', 'flv', 'mkv', 'webm', '3gp'];
    if (imageExts.includes(ext)) return 'image';
    if (videoExts.includes(ext)) return 'video';
    return 'file';
};

const getItemKind = (item: FileItem): AcceptKind => {
    if (item.kind) return item.kind;
    const path = item.tempUrl || item.url;
    return detectKindByUrl(path);
};

const doUploadByKind = async (kind: AcceptKind) => {
    try {
        const remain = props.maxCount - fileList.value.length;
        if (remain <= 0) {
            uni.showToast({ title: '已达最大上传数量', icon: 'none' });
            return;
        }

        if (kind === 'file') {
            const res: any = await uni.chooseMessageFile({
                count: props.multiple ? remain : 1,
                type: 'file',
                extension: Array.isArray(props.extension) ? props.extension : [props.extension]
            });
            const files = (res?.tempFiles || []) as Array<{ name?: string; size: number; path: string }>;
            for (const f of files) {
                const name = f.name || buildFileName(f.path, 'file');
                const placeholder: FileItem = { url: '', fileName: name, fileSize: f.size, tempUrl: f.path, status: 'uploading', kind: 'file' };
                const idx = fileList.value.push(placeholder) - 1;
                try {
                    const upRes = await upload({ url: props.uploadUrl!, filePath: f.path, name: 'file', timeout: props.uploadTimeout });
                    const serverUrl = (upRes as any)?.data?.url || (upRes as any)?.data?.fileUrl || (upRes as any)?.data?.path || (upRes as any)?.data?.uri;
                    if (upRes.code === 200) {
                        const current = fileList.value[idx] || placeholder;
                        fileList.value.splice(idx, 1, { ...current, url: serverUrl || current.tempUrl || '', status: 'done' });
                        uni.showToast({ title: '上传成功', icon: 'success' });
                        emitCurrentValue();
                    } else {
                        const current = fileList.value[idx] || placeholder;
                        fileList.value.splice(idx, 1, { ...current, status: 'error' });
                        uni.showToast({ title: '上传失败', icon: 'none' });
                    }
                } catch (err) {
                    console.error('upload file error:', err);
                    const current = fileList.value[idx] || placeholder;
                    fileList.value.splice(idx, 1, { ...current, status: 'error' });
                }
            }
        } else {
            const res: any = await uni.chooseMedia({
                count: props.multiple ? remain : 1,
                mediaType: kind === 'image' ? ['image'] : ['video'],
                sourceType: ['album', 'camera'],
            });
            const files = (res?.tempFiles || []) as Array<{ tempFilePath: string; size: number; thumbTempFilePath?: string }>;
            for (const f of files) {
                const name = buildFileName(f.tempFilePath, kind);
                const placeholder: FileItem = {
                    url: '',
                    fileName: name,
                    fileSize: f.size,
                    tempUrl: f.tempFilePath,
                    coverUrl: f.thumbTempFilePath,
                    status: 'uploading',
                    kind
                };
                const idx = fileList.value.push(placeholder) - 1;
                try {
                    const upRes = await upload({ url: props.uploadUrl!, filePath: f.tempFilePath, name: 'file', timeout: props.uploadTimeout });
                    const serverUrl = (upRes as any)?.data?.url || (upRes as any)?.data?.fileUrl || (upRes as any)?.data?.path || (upRes as any)?.data?.uri;
                    if (upRes.code === 200) {
                        const current = fileList.value[idx] || placeholder;
                        fileList.value.splice(idx, 1, { ...current, url: serverUrl || current.tempUrl || '', status: 'done' });
                        uni.showToast({ title: '上传成功', icon: 'success' });
                        emitCurrentValue();
                    } else {
                        const current = fileList.value[idx] || placeholder;
                        fileList.value.splice(idx, 1, { ...current, status: 'error' });
                        uni.showToast({ title: '上传失败', icon: 'none' });
                    }
                } catch (err) {
                    console.error('upload media error:', err);
                    const current = fileList.value[idx] || placeholder;
                    fileList.value.splice(idx, 1, { ...current, status: 'error' });
                }
            }
        }
        emitCurrentValue();
    } catch (e) {
        console.log('upload error:', e);
    }
};

// 兼容旧用法：当模板中只用一个上传按钮时，默认使用 accept 中的第一种类型
const clickBtnUpload = async () => {
    const kinds = getAcceptKinds();
    await doUploadByKind(kinds[0]);
};

const onClickUpload = async (kind: AcceptKind) => {
    await doUploadByKind(kind);
};

const getUploadTextByKind = (kind: AcceptKind) => {
    const cfg = getBtnConfig(kind);
    if (cfg.text) return cfg.text;
    // 兼容旧用法：如果外部显式传入了 uploadText（且不是默认值），统一用这个文案
    if (props.uploadText && props.uploadText !== '点击上传') {
        return props.uploadText;
    }
    if (kind === 'image') return props.uploadImageText || '上传图片';
    if (kind === 'video') return props.uploadVideoText || '上传视频';
    if (kind === 'file') return props.uploadFileText || '上传文件';
    return props.uploadText || '点击上传';
};

const getUploadBtnStyle = (kind: AcceptKind) => {
    const cfg = getBtnConfig(kind);
    const style: Record<string, string> = {};
    if (cfg.bgColor) style.backgroundColor = cfg.bgColor;
    return style;
};

const getUploadBtnTextStyle = (kind: AcceptKind) => {
    const cfg = getBtnConfig(kind);
    const style: Record<string, string> = {};
    if (cfg.textColor) style.color = cfg.textColor;
    return style;
};

const getUploadBtnClass = (kind: AcceptKind) => {
    const cfg = getBtnConfig(kind);
    return cfg.customClass || '';
};

const getUploadIconName = (kind: AcceptKind) => {
    const cfg = getBtnConfig(kind);
    return cfg.iconName || props.iconName;
};

const getUploadIconColor = (kind: AcceptKind) => {
    const cfg = getBtnConfig(kind);
    return cfg.iconColor || props.iconColor;
};

const getUploadIconSize = (kind: AcceptKind) => {
    const cfg = getBtnConfig(kind);
    return cfg.iconSize || props.iconSize;
};


const onPreview = (index: number) => {
    const item = fileList.value[index];
    if (!item) return;
    const kind = getItemKind(item);
    // 文件类型：上传中也允许预览本地临时文件
    if (kind === 'file') {
        const localPath = item.status === 'uploading' && item.tempUrl
            ? item.tempUrl
            : (!isUrl(item.url) ? item.url : '');
        if (localPath) {
            uni.openDocument({
                filePath: localPath,
                showMenu: true,
                fail: () => {
                    uni.showToast({ title: '打开文件失败', icon: 'none' });
                }
            });
            return;
        }
        if (item.url && isUrl(item.url)) {
            uni.showLoading({ title: '打开中...' });
            uni.downloadFile({
                url: item.url,
                success: (res) => {
                    uni.openDocument({
                        filePath: res.tempFilePath,
                        showMenu: true,
                        success: () => {
                            uni.hideLoading();
                        },
                        fail: () => {
                            uni.hideLoading();
                            uni.showToast({ title: '打开文件失败', icon: 'none' });
                        }
                    });
                },
                fail: () => {
                    uni.hideLoading();
                    uni.showToast({ title: '文件下载失败', icon: 'none' });
                }
            });
        }
        return;
    }
    // 图片/视频：上传中不允许预览
    if (item?.status === 'uploading') return;
    if (kind === 'image') {
        const images = fileList.value.filter((i) => getItemKind(i) === 'image');
        const urls = images.map((i) => i.url);
        const currentUrl = item.url;
        const current = urls.indexOf(currentUrl);
        uni.previewImage({
            urls,
            current: current >= 0 ? urls[current] : urls[0]
        });
        return;
    }
    if (kind === 'video') {
        // #ifdef MP-WEIXIN
        const videos = fileList.value.filter((i) => getItemKind(i) === 'video');
        const sources = videos.map((i) => ({ url: i.url, type: 'video', poster: i.coverUrl || '' }));
        const current = videos.findIndex((v) => v === item);
        // @ts-ignore
        wx.previewMedia({
            sources,
            current: current >= 0 ? current : 0
        });
        // #endif
        // #ifndef MP-WEIXIN
        uni.showToast({ title: '当前平台暂不支持视频预览', icon: 'none' });
        // #endif
    }
};

const onDelete = (index: number) => {
    fileList.value.splice(index, 1);
    const urls = fileList.value.map((i) => i.url);
    let out: any;
    if (props.valueType === 'string') {
        out = urls[0] || '';
    } else if (props.valueType === 'array') {
        out = fileList.value;
    } else {
        out = props.multiple ? fileList.value : fileList.value[0] || null;
    }
    emit('update:modelValue', out);
    emit('change', out);
};
function emitCurrentValue() {
    const validList = fileList.value;
    const urls = validList.map((i) => i.url).filter(Boolean);
    let out: any;
    if (props.valueType === 'string') {
        out = props.multiple ? urls.join(',') : (urls[0] || '');
    } else if (props.valueType === 'array') {
        out = validList;
    } else {
        out = props.multiple ? validList : validList[0] || null;
    }
    emit('update:modelValue', out);
    emit('change', out);
}
watch(
    () => props.modelValue,
    (val) => {
        if (!val) {
            fileList.value = [];
            return;
        }
        if (props.valueType === 'string') {
            if (typeof val === 'string') {
                const parts = val
                    .split(',')
                    .map((s) => String(s).trim().replace(/^['"]+|['"]+$/g, ''))
                    .filter((s) => !!s);
                fileList.value = parts.map((u) => ({ url: u }));
            }
        } else if (props.valueType === 'array') {
            if (Array.isArray(val)) {
                if (val.length && typeof val[0] === 'string') {
                    fileList.value = (val as string[]).map((u) => ({ url: u }));
                } else {
                    fileList.value = val as FileItem[];
                }
            }
        } else {
            if (Array.isArray(val)) {
                fileList.value = val as FileItem[];
            } else if (typeof val === 'object' && (val as any).url) {
                fileList.value = [val as FileItem];
            }
        }
    },
    { immediate: true }
);
</script>

<style lang="scss" scoped>
.ut-upload-card {
    display: flex;
    gap: 10rpx;
    flex-wrap: wrap;
    .card-item {
        width: 210rpx;
        height: 210rpx;
        background-color: #FAFAFA;
        border-radius: 8rpx;
        overflow: hidden;
        border: 1rpx solid #ccc;
        position: relative;
    }
    .file-box {
        width: 100%;
        height: 100%;
        box-sizing: border-box;
        padding: 20rpx;
    }
    .btn-select {
        border-style: dashed;
    }
}
.del-btn {
    position: absolute;
    top: 0rpx;
    right: 0rpx;
    width: 48rpx;
    height: 48rpx;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.35);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 2;
}
.uploading-mask {
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.4);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1;
}
.uploading-text {
    font-size: 24rpx;
}
</style>
