<template>
  <div class="file-upload-wrapper" :style="wrapperStyle">
    <el-upload
      ref="uploadRef"
      class="avatar-uploader"
      :action="action"
      :headers="headers"
      :data="data"
      :name="name"
      :multiple="multiple"
      :limit="limit"
      :show-file-list="showFileList"
      :list-type="listType"
      :accept="accept"
      :before-upload="beforeUpload"
      :on-success="handleSuccess"
      :on-error="handleError"
      :on-preview="handlePreview"
      :on-remove="handleRemove"
      :before-remove="beforeRemove"
      :file-list="fileList"
    >
      <!-- 自定义上传触发器插槽，无插槽内容则使用默认加号 -->
      <slot v-if="!hasFile" name="trigger">
        <div class="upload-btn">
          <span>+</span>
        </div>
      </slot>

      <!-- 图片预览弹窗 -->
      <el-dialog v-model="dialogVisible" title="图片预览" width="30%" align-center>
        <img w-full :src="dialogImageUrl" alt="预览图片" style="object-fit: contain;" />
      </el-dialog>
    </el-upload>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, watch, ComputedRef } from 'vue';
import type { UploadProps, UploadUserFile, UploadFile } from 'element-plus';
import { ElMessage, ElMessageBox } from 'element-plus';

const props = defineProps({
  action: {
    type: String,
    default: '',
  },
  headers: {
    type: Object,
    default: () => ({}),
  },
  data: {
    type: Object,
    default: () => ({}),
  },
  name: {
    type: String,
    default: 'file',
  },
  multiple: {
    type: Boolean,
    default: false,
  },
  limit: {
    type: Number,
    default: Infinity,
  },
  listType: {
    type: String,
    default: 'picture-card',
  },
  accept: {
    type: String,
    default: '.jpg,.jpeg,.png,.gif,.webp',
  },
  maxSize: {
    type: Number,
    default: 5,
  },
  showFileList: {
    type: Boolean,
    default: true,
  },
  btnText: {
    type: String,
    default: '上传',
  },
  /** 上传卡片宽度 px */
  width: {
    type: Number,
    default: 100,
  },
  /** 上传卡片高度 px，不传则默认等于 width */
  height: {
    type: Number,
    default: undefined,
  },
});

const emit = defineEmits<{
  'update:modelValue': [files: UploadUserFile[]];
  success: [response: any, file: UploadFile];
  error: [error: any, file: UploadFile];
}>();

const modelValue = defineModel<any>({ default: [] });

const uploadRef = ref<InstanceType<typeof import('element-plus').ElUpload>>();
const dialogImageUrl = ref('');
const dialogVisible = ref(false);

// 内部文件列表
const fileList = ref<UploadUserFile[]>([...modelValue.value]);

const hasFile: ComputedRef<boolean> = computed(() => fileList.value.length > 0);

// 计算最终宽高，height不存在则和width保持一致
const wrapperStyle = computed(() => {
  const h = props.height ?? props.width;
  return {
    '--upload-width': `${props.width}px`,
    '--upload-height': `${h}px`,
  };
});

// 同步外部 v-model → 内部列表
watch(
  modelValue,
  (val) => {
    fileList.value = Array.isArray(val) ? [...val] : [];
  },
  { deep: true }
);

// 内部列表变更同步到父组件
watch(
  fileList,
  (val) => {
    emit('update:modelValue', [...val]);
  },
  { deep: true }
);

const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
  const ext = rawFile.name.substring(rawFile.name.lastIndexOf('.') + 1);
  const allowExts = props.accept.split(',').map((e) => e.trim().replace('.', '').toLowerCase());
  if (allowExts.length && !allowExts.includes(ext.toLowerCase())) {
    ElMessage.warning(`仅支持 ${props.accept} 格式文件`);
    return false;
  }

  const maxBytes = props.maxSize * 1024 * 1024;
  if (rawFile.size > maxBytes) {
    ElMessage.warning(`文件不能超过 ${props.maxSize}MB`);
    return false;
  }
  return true;
};

const handleSuccess: UploadProps['onSuccess'] = (response, file) => {
  if (response.code === 200) {
    file.url = response.data;
    if (!props.multiple) {
      fileList.value = [file];
    }
  }
  emit('success', response, file);
};

const handleError: UploadProps['onError'] = (error, file) => {
  ElMessage.error(`上传失败：${file.name}`);
  emit('error', error, file);
};

const handleRemove: UploadProps['onRemove'] = () => {
  // 可扩展外部事件
};

const beforeRemove: UploadProps['beforeRemove'] = async () => {
  try {
    await ElMessageBox.confirm('确定移除该文件？', '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning',
    });
    return true;
  } catch {
    return false;
  }
};

const handlePreview: UploadProps['onPreview'] = (uploadFile) => {
  dialogImageUrl.value = uploadFile.url ?? '';
  dialogVisible.value = true;
};

defineExpose({
  uploadRef,
  submit: () => uploadRef.value?.submit(),
  clearFiles: () => uploadRef.value?.clearFiles(),
});
</script>

<style scoped lang="scss">
.file-upload-wrapper {
  display: inline-block;
  --upload-width: 100px;
  --upload-height: 100px;

  .avatar-uploader {
    :deep(.el-upload--picture-card) {
      width: var(--upload-width);
      height: var(--upload-height);
      border-radius: 6px;
      border: 1px dashed #dcdfe6;
      transition: all 0.3s;

      &:hover {
        border-color: rgba(var(--main-color), 0.4);
        background-color: rgba(var(--main-color), 0.05);
      }
    }

    :deep(.el-upload-list--picture-card .el-upload-list__item) {
      width: var(--upload-width);
      height: var(--upload-height);
      border-radius: 6px;
    }
  }

  .upload-btn {
    width: var(--upload-width);
    height: var(--upload-height);
    display: flex;
    align-items: center;
    justify-content: center;
    color: #888;
    font-size: 28px;
  }
}
</style>