<script setup lang="ts">
/**
 * 通用拖拽 + 粘贴上传：Dragger 点击/拖拽，下方外侧「开始监听粘贴」
 * v-model 为已上传 URL 字符串数组；列表支持图片/PDF 预览（blob，避免直接下载）
 */
import { computed, onBeforeUnmount, ref, watch } from 'vue';

import { preferences } from '@vben/preferences';
import { Button, Image, Modal, Upload, message } from 'ant-design-vue';
import type { UploadFile } from 'ant-design-vue';

import { Icon } from '#/components/icon';
import { uploadFile } from '#/api/core/upload';
import { uploadToOss } from '#/utils/oss-upload';
import {
  matchAccept,
  usePasteUploadListen,
} from '#/utils/use-paste-upload-listen';

defineOptions({ name: 'UploadDraggerPaste' });

interface FileItem {
  uid: string;
  name: string;
  status: 'uploading' | 'done' | 'error';
  url: string;
}

interface Props {
  modelValue?: string[];
  accept?: string;
  multiple?: boolean;
  maxCount?: number;
  /** 单文件最大 MB */
  maxSizeMb?: number;
  tip?: string;
  disabled?: boolean;
}

const props = withDefaults(defineProps<Props>(), {
  modelValue: () => [],
  accept: '.jpg,.jpeg,.png,.gif,.webp,.pdf,image/*,application/pdf',
  multiple: true,
  maxCount: 9,
  maxSizeMb: 20,
  tip: '点击或拖拽文件到此处上传',
  disabled: false,
});

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

const UploadDragger = Upload.Dragger;
const fileList = ref<FileItem[]>([]);
const uploading = ref(false);

/** 文件预览弹窗状态 */
const previewVisible = ref(false);
const previewTitle = ref('');
const previewIsImage = ref(false);
const previewImageUrl = ref('');
const previewPdfUrl = ref('');
const previewLoading = ref(false);

const maxBytes = computed(() => props.maxSizeMb * 1024 * 1024);

function syncFromValue(urls: string[]) {
  fileList.value = (urls || []).map((url) => ({
    uid: url,
    name: url.split('/').pop() || 'file',
    status: 'done' as const,
    url,
  }));
}

syncFromValue(props.modelValue);

watch(
  () => props.modelValue,
  (val) => {
    const current = fileList.value
      .filter((f) => f.status === 'done')
      .map((f) => f.url);
    if (JSON.stringify(val || []) !== JSON.stringify(current)) {
      syncFromValue(val || []);
    }
  },
  { deep: true },
);

function emitUrls() {
  const urls = fileList.value
    .filter((f) => f.status === 'done' && f.url)
    .map((f) => f.url);
  emit('update:modelValue', urls);
}

function isImageUrl(url: string) {
  return /\.(jpg|jpeg|png|gif|webp|bmp)(\?|$)/i.test(url || '');
}

function revokePdfBlob() {
  if (previewPdfUrl.value && previewPdfUrl.value.startsWith('blob:')) {
    URL.revokeObjectURL(previewPdfUrl.value);
  }
  previewPdfUrl.value = '';
}

/**
 * 预览已上传文件：图片用 Image；PDF fetch 成 blob 后 iframe，避免 OSS 直接下载
 */
async function previewItem(item: FileItem) {
  if (!item.url) return;
  revokePdfBlob();
  previewTitle.value = item.name || '预览';
  if (isImageUrl(item.url)) {
    previewIsImage.value = true;
    previewImageUrl.value = item.url;
    previewVisible.value = true;
    return;
  }
  previewIsImage.value = false;
  previewLoading.value = true;
  previewVisible.value = true;
  try {
    const resp = await fetch(item.url);
    if (!resp.ok) throw new Error('加载失败');
    const blob = await resp.blob();
    const pdfBlob =
      blob.type && blob.type !== 'application/octet-stream'
        ? blob
        : new Blob([blob], { type: 'application/pdf' });
    previewPdfUrl.value = URL.createObjectURL(pdfBlob);
  } catch {
    message.error('预览失败');
    previewVisible.value = false;
  } finally {
    previewLoading.value = false;
  }
}

function onPreviewClose() {
  previewVisible.value = false;
  revokePdfBlob();
  previewImageUrl.value = '';
}

/**
 * 上传单个文件并写入列表
 */
async function uploadOneFile(file: File) {
  if (props.disabled) return;
  const doneCount = fileList.value.filter((f) => f.status === 'done').length;
  if (doneCount >= props.maxCount) {
    message.warning(`最多上传 ${props.maxCount} 个文件`);
    return;
  }
  if (!matchAccept(file, props.accept)) {
    message.warning('文件类型不符合要求');
    return;
  }
  if (file.size > maxBytes.value) {
    message.warning(`文件不能超过 ${props.maxSizeMb}MB`);
    return;
  }

  const uid = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
  fileList.value = [
    ...fileList.value,
    { uid, name: file.name, status: 'uploading', url: '' },
  ];
  uploading.value = true;
  try {
    const uploadMethod = preferences.app.uploadMethod || 'direct';
    const res =
      uploadMethod === 'direct'
        ? await uploadToOss({ file })
        : await uploadFile({ file });
    const url = res?.url || '';
    if (!url) throw new Error('上传失败');
    fileList.value = fileList.value.map((item) =>
      item.uid === uid ? { ...item, status: 'done' as const, url } : item,
    );
    emitUrls();
  } catch (e: any) {
    fileList.value = fileList.value.filter((item) => item.uid !== uid);
    message.error(e?.message || '上传失败');
  } finally {
    uploading.value = false;
  }
}

/** Ant Design Upload customRequest */
const customRequest = async (options: any) => {
  const { file, onSuccess, onError } = options;
  try {
    await uploadOneFile(file as File);
    onSuccess?.({});
  } catch (e) {
    onError?.(e);
  }
};

function beforeUpload(file: File) {
  if (props.disabled) return Upload.LIST_IGNORE;
  const doneCount = fileList.value.filter((f) => f.status === 'done').length;
  if (doneCount >= props.maxCount) {
    message.warning(`最多上传 ${props.maxCount} 个文件`);
    return Upload.LIST_IGNORE;
  }
  if (!matchAccept(file, props.accept)) {
    message.warning('文件类型不符合要求');
    return Upload.LIST_IGNORE;
  }
  if (file.size > maxBytes.value) {
    message.warning(`文件不能超过 ${props.maxSizeMb}MB`);
    return Upload.LIST_IGNORE;
  }
  return true;
}

function handleRemove(file: UploadFile) {
  fileList.value = fileList.value.filter((item) => item.uid !== file.uid);
  emitUrls();
  return true;
}

const {
  listening: pasteListening,
  toggle: togglePaste,
} = usePasteUploadListen({
  get accept() {
    return props.accept;
  },
  get maxSize() {
    return maxBytes.value;
  },
  onFiles: async (files) => {
    for (const file of files) {
      const doneCount = fileList.value.filter((f) => f.status === 'done').length;
      if (doneCount >= props.maxCount) {
        message.warning(`最多上传 ${props.maxCount} 个文件`);
        break;
      }
      await uploadOneFile(file);
    }
  },
});

function onTogglePaste() {
  if (props.disabled) return;
  togglePaste();
  if (pasteListening.value) {
    message.success('已开启粘贴监听，可 Ctrl+V 粘贴文件/截图');
  } else {
    message.info('已停止粘贴监听');
  }
}

const showDragger = computed(() => {
  if (!props.multiple) {
    return fileList.value.filter((f) => f.status === 'done').length === 0;
  }
  return fileList.value.filter((f) => f.status === 'done').length < props.maxCount;
});

onBeforeUnmount(() => {
  revokePdfBlob();
});
</script>

<template>
  <div class="upload-dragger-paste">
    <UploadDragger
      v-if="showDragger"
      :file-list="[]"
      :accept="accept"
      :multiple="multiple"
      :disabled="disabled || uploading"
      :before-upload="beforeUpload"
      :custom-request="customRequest"
      :show-upload-list="false"
      :max-count="maxCount"
    >
      <p class="ant-upload-drag-icon">
        <Icon icon="ant-design:inbox-outlined" class="text-4xl text-blue-500" />
      </p>
      <p class="ant-upload-text">{{ tip }}</p>
      <p class="ant-upload-hint text-muted-foreground">
        支持拖拽或点击选择，单文件不超过 {{ maxSizeMb }}MB
      </p>
    </UploadDragger>

    <div v-if="fileList.length" class="mt-3 space-y-2">
      <div
        v-for="item in fileList"
        :key="item.uid"
        class="flex items-center gap-2 rounded border border-border bg-muted/20 px-3 py-2 text-sm text-foreground"
      >
        <Icon
          :icon="
            item.status === 'uploading'
              ? 'ant-design:loading-outlined'
              : item.status === 'error'
                ? 'ant-design:close-circle-outlined'
                : 'ant-design:paper-clip-outlined'
          "
          class="shrink-0"
        />
        <button
          v-if="item.url"
          type="button"
          class="min-w-0 flex-1 truncate text-left text-primary"
          @click="previewItem(item)"
        >
          {{ item.name }}
        </button>
        <span v-else class="min-w-0 flex-1 truncate text-muted-foreground">{{ item.name }}</span>
        <Button
          type="link"
          size="small"
          danger
          :disabled="disabled"
          @click="handleRemove(item as any)"
        >
          删除
        </Button>
      </div>
    </div>

    <!-- 粘贴监听按钮放在拖拽区域下方外侧 -->
    <div class="mt-3">
      <Button
        :type="pasteListening ? 'primary' : 'default'"
        :danger="pasteListening"
        :disabled="disabled"
        @click="onTogglePaste"
      >
        <Icon
          :icon="pasteListening ? 'ant-design:stop-outlined' : 'ant-design:snippets-outlined'"
          class="mr-1"
        />
        {{ pasteListening ? '停止监听粘贴' : '开始监听粘贴' }}
      </Button>
      <span v-if="pasteListening" class="ml-2 text-xs text-orange-500 dark:text-orange-400">
        监听中：请将文件或截图 Ctrl+V 粘贴
      </span>
    </div>

    <Modal
      :open="previewVisible"
      :title="previewTitle"
      :footer="null"
      width="80%"
      destroy-on-close
      @cancel="onPreviewClose"
    >
      <div v-if="previewLoading" class="py-16 text-center text-muted-foreground">加载中…</div>
      <div v-else-if="previewIsImage" class="flex justify-center rounded border border-border bg-muted/25 p-2">
        <Image :src="previewImageUrl" :preview="true" style="max-height: 70vh" />
      </div>
      <iframe
        v-else-if="previewPdfUrl"
        :src="previewPdfUrl"
        class="h-[70vh] w-full rounded border border-border bg-background"
        title="文件预览"
      />
    </Modal>
  </div>
</template>
