<script lang="ts" setup>
import { computed, reactive, ref, watch } from 'vue';

import {
  Button,
  Drawer,
  Empty,
  Image,
  Input,
  InputNumber,
  message,
  Progress,
  Select,
  Space,
  Switch,
  Table,
  Tag,
} from 'antdv-next';

import {
  getAccessImgUrl,
  OSS_FILE_BUCKETS,
  simpleUploadFile,
} from '#/api/legacy/oss';
import { addPropsSource } from '#/api/legacy/props';
import { normalizeResourceCode } from '#/views/_shared/resource-code';

import {
  BADGE_DISPLAY_TYPE_OPTIONS,
  getPropsTypeName,
  PROPS_RESOURCE_CONFIG_TYPES,
} from '../shared';

type AssetKind = 'cover' | 'expand' | 'source';
type RowStatus = 'error' | 'ready' | 'saved' | 'saving' | 'uploading';
type StructuredAssetKind = 'cover' | 'source';

interface BatchRow {
  adminFree: boolean;
  amount: number;
  amountTouched: boolean;
  code: string;
  cover: string;
  coverError: string;
  coverFileName: string;
  coverUploading: boolean;
  errors: string[];
  expand: string;
  expandError: string;
  expandFileName: string;
  expandUploading: boolean;
  key: string;
  name: string;
  saved: boolean;
  saving: boolean;
  sourceError: string;
  sourceFileName: string;
  sourceUploading: boolean;
  sourceUrl: string;
  status: RowStatus;
  type: string;
  typeTouched: boolean;
}

interface FindRowOptions {
  adminFree?: boolean;
  amount?: number | string;
  amountTouched?: boolean;
  code?: string;
  type?: string;
  typeTouched?: boolean;
}

interface StructuredResourceFile {
  amount: number;
  assetKind: StructuredAssetKind;
  code: string;
  name: string;
  type: string;
}

interface UploadTask {
  file: File;
  fileName: string;
  kind: AssetKind;
  row: BatchRow;
}

const props = defineProps<{
  initialSysOrigin?: number | string;
  initialType?: number | string;
  open: boolean;
  sysOriginOptions: Array<{ label: string; value: string }>;
}>();

const emit = defineEmits<{
  close: [];
  success: [];
}>();

const batchForm = reactive({
  adminFree: false,
  amount: 0,
  badgeDisplayType: 'SHORT',
  sysOrigin: '',
  type: '',
});

const rows = ref<BatchRow[]>([]);
const activeRowKey = ref('');
const dragging = ref(false);
const folderInputRef = ref<HTMLInputElement>();
const fileInputRef = ref<HTMLInputElement>();
const selectedRowKeys = ref<string[]>([]);
const sequence = ref(0);
const showOnlyErrors = ref(false);
const submitting = ref(false);
const droppedFilePathMap = new WeakMap<File, string>();
const uploadQueue: UploadTask[] = [];
const MAX_UPLOAD_CONCURRENCY = 2;
let activeUploadCount = 0;

const propsTypeOptions = PROPS_RESOURCE_CONFIG_TYPES.map((item) => ({
  label: item.name,
  value: item.value as any,
}));
const badgeDisplayTypeOptions = BADGE_DISPLAY_TYPE_OPTIONS.map((item) => ({
  label: item.name,
  value: item.value as any,
}));

const STRUCTURED_RESOURCE_TYPE_MAP: Record<string, string> = {
  勋章: 'BADGE',
  坐骑: 'RIDE',
  头像框: 'AVATAR_FRAME',
};
const STRUCTURED_ASSET_KIND_MAP: Record<string, StructuredAssetKind> = {
  animation: 'source',
  cover: 'cover',
};

function isAmountRequiredByType(type: string) {
  return !['CUSTOMIZE', 'FRAGMENTS'].includes(type);
}

function isSourceRequiredByType(type: string) {
  return !['BADGE', 'CUSTOMIZE', 'FRAGMENTS'].includes(type);
}

function showExpandUploadByType(type: string) {
  return type === 'CHAT_BUBBLE';
}

const isAmountRequired = computed(() => isAmountRequiredByType(batchForm.type));
const showExpandUpload = computed(() => showExpandUploadByType(batchForm.type));
const hasExpandColumn = computed(
  () =>
    showExpandUpload.value
    || rows.value.some((row) => showExpandUploadByType(getRowType(row))),
);

const columns = computed(() => {
  const baseColumns = [
    { dataIndex: 'cover', key: 'cover', title: '封面', width: 116 },
    { dataIndex: 'sourceUrl', key: 'sourceUrl', title: '资源', width: 180 },
  ];
  if (hasExpandColumn.value) {
    baseColumns.push({
      dataIndex: 'expand',
      key: 'expand',
      title: 'Android资源',
      width: 180,
    });
  }
  return [
    ...baseColumns,
    { dataIndex: 'type', key: 'type', title: '类型', width: 132 },
    { dataIndex: 'name', key: 'name', title: '名称', width: 180 },
    { dataIndex: 'code', key: 'code', title: '编码', width: 180 },
    { dataIndex: 'amount', key: 'amount', title: '底价', width: 116 },
    { dataIndex: 'adminFree', key: 'adminFree', title: 'Admin Free', width: 116 },
    { dataIndex: 'status', key: 'status', title: '状态', width: 120 },
    { dataIndex: 'actions', key: 'actions', title: '操作', width: 92 },
  ];
});

const visibleRows = computed(() =>
  showOnlyErrors.value
    ? rows.value.filter((row) => row.errors.length > 0)
    : rows.value,
);

const activeRow = computed(() => {
  if (activeRowKey.value) {
    const matched = rows.value.find((row) => row.key === activeRowKey.value);
    if (matched) {
      return matched;
    }
  }
  return rows.value[0] || null;
});

const uploadAssetTotal = computed(() => {
  return rows.value.reduce((total, row) => {
    return total
      + (row.coverFileName ? 1 : 0)
      + (row.sourceFileName ? 1 : 0)
      + (row.expandFileName ? 1 : 0);
  }, 0);
});

const uploadAssetFinished = computed(() => {
  return rows.value.reduce((total, row) => {
    return total
      + (row.coverFileName && !row.coverUploading ? 1 : 0)
      + (row.sourceFileName && !row.sourceUploading ? 1 : 0)
      + (row.expandFileName && !row.expandUploading ? 1 : 0);
  }, 0);
});

const uploadPercent = computed(() => {
  if (uploadAssetTotal.value === 0) {
    return 0;
  }
  return Math.round((uploadAssetFinished.value / uploadAssetTotal.value) * 100);
});

const errorCount = computed(() =>
  rows.value.filter((row) => row.errors.length > 0).length,
);
const readyRows = computed(() =>
  rows.value.filter((row) => isRowSubmittable(row)),
);
const submitTargetCount = computed(() => readyRows.value.length);

const rowSelection = computed(() => ({
  selectedRowKeys: selectedRowKeys.value,
  onChange: (keys: Array<number | string>) => {
    selectedRowKeys.value = keys.map(String);
  },
}));

function resetBatch() {
  batchForm.sysOrigin =
    String(props.initialSysOrigin || props.sysOriginOptions[0]?.value || 'LIKEI');
  batchForm.type =
    String(props.initialType || PROPS_RESOURCE_CONFIG_TYPES[0]?.value || 'AVATAR_FRAME');
  batchForm.amount = 0;
  batchForm.adminFree = false;
  batchForm.badgeDisplayType = 'SHORT';
  rows.value = [];
  selectedRowKeys.value = [];
  activeRowKey.value = '';
  showOnlyErrors.value = false;
  sequence.value = 0;
  uploadQueue.length = 0;
}

watch(
  () => props.open,
  (open) => {
    if (open) {
      resetBatch();
    }
  },
  { immediate: true },
);

watch(
  () => batchForm.amount,
  () => {
    rows.value.forEach((row) => {
      if (!row.amountTouched) {
        row.amount = normalizeAmount(batchForm.amount);
      }
    });
    refreshAllRows();
  },
);

watch(
  () => [batchForm.type, batchForm.sysOrigin, batchForm.badgeDisplayType],
  () => {
    rows.value.forEach((row) => {
      if (!row.typeTouched) {
        row.type = batchForm.type;
      }
    });
    refreshAllRows();
  },
);

function normalizeAmount(value: unknown) {
  const amount = Number(value ?? 0);
  return Number.isFinite(amount) && amount >= 0 ? amount : 0;
}

function normalizeCode(value: string) {
  return normalizeResourceCode(value);
}

function getPathFileName(path: string) {
  const segments = path.split('/');
  return segments[segments.length - 1] || path;
}

function getFilePath(file: File) {
  return (
    droppedFilePathMap.get(file)
    || (file as any).webkitRelativePath
    || file.name
  );
}

function isImageFile(file: File) {
  return (
    file.type.startsWith('image/')
    || /\.(apng|avif|gif|jpe?g|png|svg|webp)$/i.test(file.name)
  );
}

function isResourceFile(file: File) {
  return /\.(mp4|pag|svga|zip)$/i.test(file.name);
}

function parseStructuredResourceFile(file: File): null | StructuredResourceFile {
  if (!isImageFile(file) && !isResourceFile(file)) {
    return null;
  }
  const fileName = getPathFileName(getFilePath(file));
  const baseName = fileName.replace(/\.[^.]+$/, '').trim();
  const parts = baseName
    .split('_')
    .map((part) => part.trim())
    .filter(Boolean);
  const type = STRUCTURED_RESOURCE_TYPE_MAP[parts[0] || ''];
  const assetKind =
    STRUCTURED_ASSET_KIND_MAP[String(parts[parts.length - 1] || '').toLowerCase()];
  if (!type || !assetKind) {
    return null;
  }
  if (assetKind === 'cover' && !isImageFile(file)) {
    return null;
  }
  if (type === 'BADGE') {
    const name = parts.slice(1, -1).join('_').trim();
    if (!name) {
      return null;
    }
    return {
      amount: 0,
      assetKind,
      code: normalizeCode(name),
      name,
      type,
    };
  }
  if (parts.length < 5) {
    return null;
  }
  const amount = Number(parts[parts.length - 3]);
  const duration = String(parts[parts.length - 2] || '');
  const name = parts.slice(1, -3).join('_').trim();
  if (!name || !/^\d+天$/.test(duration) || !Number.isFinite(amount) || amount < 0) {
    return null;
  }
  return {
    amount: normalizeAmount(amount),
    assetKind,
    code: normalizeCode(name),
    name,
    type,
  };
}

function getRowType(row: BatchRow) {
  return String(row.type || batchForm.type || '');
}

function findOrCreateRow(baseName: string, options: FindRowOptions = {}) {
  const name = baseName.trim() || 'resource';
  const type = String(options.type || batchForm.type || '');
  const code = options.code || normalizeCode(name);
  const matched = rows.value.find(
    (row) =>
      getRowType(row) === type
      && (String(row.code || '') === code || String(row.name || '') === name),
  );
  if (matched) {
    if (options.adminFree !== undefined) {
      matched.adminFree = options.adminFree;
    }
    if (options.amount !== undefined) {
      matched.amount = normalizeAmount(options.amount);
      matched.amountTouched = options.amountTouched ?? true;
    }
    if (options.code) {
      matched.code = options.code;
    }
    if (options.type) {
      matched.type = options.type;
      matched.typeTouched = options.typeTouched ?? true;
    }
    matched.saved = false;
    return matched;
  }
  const row: BatchRow = {
    adminFree: options.adminFree ?? batchForm.adminFree,
    amount:
      options.amount === undefined
        ? normalizeAmount(batchForm.amount)
        : normalizeAmount(options.amount),
    amountTouched: options.amountTouched ?? false,
    code,
    cover: '',
    coverError: '',
    coverFileName: '',
    coverUploading: false,
    errors: [],
    expand: '',
    expandError: '',
    expandFileName: '',
    expandUploading: false,
    key: `batch-${Date.now()}-${sequence.value++}`,
    name,
    saved: false,
    saving: false,
    sourceError: '',
    sourceFileName: '',
    sourceUploading: false,
    sourceUrl: '',
    status: 'error',
    type,
    typeTouched: options.typeTouched ?? false,
  };
  rows.value.push(row);
  if (!activeRowKey.value) {
    activeRowKey.value = row.key;
  }
  return row;
}

function getUploadBucket(kind: AssetKind, file: File) {
  if (kind === 'cover' || kind === 'expand' || isImageFile(file)) {
    return OSS_FILE_BUCKETS.svgaCover;
  }
  return OSS_FILE_BUCKETS.svgasource;
}

function setRowAsset(row: BatchRow, kind: AssetKind, file: File) {
  const fileName = getPathFileName(getFilePath(file));
  if (kind === 'cover') {
    row.cover = '';
    row.coverError = '';
    row.coverFileName = fileName;
    row.coverUploading = true;
  } else if (kind === 'source') {
    row.sourceUrl = '';
    row.sourceError = '';
    row.sourceFileName = fileName;
    row.sourceUploading = true;
  } else {
    row.expand = '';
    row.expandError = '';
    row.expandFileName = fileName;
    row.expandUploading = true;
  }
  row.saved = false;
  refreshAllRows();
  enqueueUpload({ file, fileName, kind, row });
}

function enqueueUpload(task: UploadTask) {
  uploadQueue.push(task);
  runUploadQueue();
}

function runUploadQueue() {
  while (activeUploadCount < MAX_UPLOAD_CONCURRENCY && uploadQueue.length > 0) {
    const task = uploadQueue.shift();
    if (!task) {
      return;
    }
    activeUploadCount += 1;
    void uploadAsset(task.row, task.kind, task.file, task.fileName).finally(() => {
      activeUploadCount -= 1;
      runUploadQueue();
    });
  }
}

async function uploadAsset(
  row: BatchRow,
  kind: AssetKind,
  file: File,
  fileName: string,
) {
  try {
    const result = await simpleUploadFile(file, getUploadBucket(kind, file));
    const url = getAccessImgUrl(result.name);
    if (kind === 'cover' && row.coverFileName === fileName) {
      row.cover = url;
    } else if (kind === 'source' && row.sourceFileName === fileName) {
      row.sourceUrl = url;
    } else if (kind === 'expand' && row.expandFileName === fileName) {
      row.expand = url;
    }
  } catch (error) {
    console.error(error);
    if (kind === 'cover' && row.coverFileName === fileName) {
      row.coverError = '封面上传失败';
    } else if (kind === 'source' && row.sourceFileName === fileName) {
      row.sourceError = '资源上传失败';
    } else if (kind === 'expand' && row.expandFileName === fileName) {
      row.expandError = 'Android资源上传失败';
    }
  } finally {
    if (kind === 'cover' && row.coverFileName === fileName) {
      row.coverUploading = false;
    } else if (kind === 'source' && row.sourceFileName === fileName) {
      row.sourceUploading = false;
    } else if (kind === 'expand' && row.expandFileName === fileName) {
      row.expandUploading = false;
    }
    refreshAllRows();
  }
}

function processFiles(files: File[]) {
  let accepted = 0;
  let ignored = 0;
  files.forEach((file) => {
    const structuredFile = parseStructuredResourceFile(file);
    if (!structuredFile) {
      ignored += 1;
      return;
    }
    const row = findOrCreateRow(structuredFile.name, {
      adminFree: false,
      amount: structuredFile.amount,
      amountTouched: true,
      code: structuredFile.code,
      type: structuredFile.type,
      typeTouched: true,
    });
    setRowAsset(row, structuredFile.assetKind, file);
    accepted += 1;
  });
  if (accepted === 0 && files.length > 0) {
    message.warning('未识别到符合命名规则的资源文件');
    return;
  }
  if (accepted > 0) {
    message.success(
      ignored > 0
        ? `已导入 ${accepted} 个文件，忽略 ${ignored} 个非规则文件`
        : `已导入 ${accepted} 个文件`,
    );
  }
}

function handleFileInput(event: Event) {
  const target = event.target as HTMLInputElement;
  const files = target.files ? [...target.files] : [];
  processFiles(files);
  target.value = '';
}

async function handleDrop(event: DragEvent) {
  dragging.value = false;
  const files = await collectDroppedFiles(event.dataTransfer);
  processFiles(files);
}

async function collectDroppedFiles(dataTransfer?: DataTransfer | null) {
  if (!dataTransfer) {
    return [];
  }
  const items = [...(dataTransfer.items || [])];
  const entries = items
    .map((item) => (item as any).webkitGetAsEntry?.())
    .filter(Boolean);
  if (entries.length === 0) {
    return [...(dataTransfer.files || [])];
  }
  const files: File[] = [];
  await Promise.all(entries.map((entry) => readEntry(entry, files)));
  return files;
}

async function readEntry(entry: any, result: File[], parentPath = ''): Promise<void> {
  const currentPath = `${parentPath}${entry.name}`;
  if (entry.isFile) {
    await new Promise<void>((resolve) => {
      entry.file((file: File) => {
        droppedFilePathMap.set(file, currentPath);
        result.push(file);
        resolve();
      }, resolve);
    });
    return;
  }
  if (!entry.isDirectory) {
    return;
  }
  const reader = entry.createReader();
  while (true) {
    const entries = await new Promise<any[]>((resolve, reject) => {
      reader.readEntries(resolve, reject);
    });
    if (entries.length === 0) {
      break;
    }
    await Promise.all(
      entries.map((child) => readEntry(child, result, `${currentPath}/`)),
    );
  }
}

function getDuplicateMap(field: 'code' | 'name') {
  const map = new Map<string, number>();
  rows.value.forEach((row) => {
    const value = String(row[field] || '').trim();
    if (!value) {
      return;
    }
    map.set(value, (map.get(value) || 0) + 1);
  });
  return map;
}

function getRowErrors(
  row: BatchRow,
  duplicateCodes = getDuplicateMap('code'),
  duplicateNames = getDuplicateMap('name'),
) {
  const errors: string[] = [];
  if (!batchForm.sysOrigin) {
    errors.push('请选择系统');
  }
  const rowType = getRowType(row);
  if (!rowType) {
    errors.push('请选择类型');
  }
  if (!String(row.name || '').trim()) {
    errors.push('缺少名称');
  }
  if (!String(row.code || '').trim()) {
    errors.push('缺少编码');
  }
  if (String(row.code || '').trim() && (duplicateCodes.get(String(row.code).trim()) || 0) > 1) {
    errors.push('本批次编码重复');
  }
  if (String(row.name || '').trim() && (duplicateNames.get(String(row.name).trim()) || 0) > 1) {
    errors.push('本批次名称重复');
  }
  if (row.coverError) {
    errors.push(row.coverError);
  }
  if (row.sourceError) {
    errors.push(row.sourceError);
  }
  if (row.expandError) {
    errors.push(row.expandError);
  }
  if (!row.cover && !row.coverUploading) {
    errors.push('缺少封面');
  }
  if (isSourceRequiredByType(rowType) && !row.sourceUrl && !row.sourceUploading) {
    errors.push('缺少资源');
  }
  if (showExpandUploadByType(rowType) && !row.expand && !row.expandUploading) {
    errors.push('缺少 Android 资源');
  }
  if (
    isAmountRequiredByType(rowType)
    && (!Number.isFinite(Number(row.amount)) || Number(row.amount) < 0)
  ) {
    errors.push('底价不正确');
  }
  return errors;
}

function refreshRow(
  row: BatchRow,
  duplicateCodes = getDuplicateMap('code'),
  duplicateNames = getDuplicateMap('name'),
) {
  row.errors = getRowErrors(row, duplicateCodes, duplicateNames);
  if (row.saving) {
    row.status = 'saving';
  } else if (row.coverUploading || row.sourceUploading || row.expandUploading) {
    row.status = 'uploading';
  } else if (row.saved) {
    row.status = 'saved';
  } else if (row.errors.length > 0) {
    row.status = 'error';
  } else {
    row.status = 'ready';
  }
}

function refreshAllRows() {
  const duplicateCodes = getDuplicateMap('code');
  const duplicateNames = getDuplicateMap('name');
  rows.value.forEach((row) => refreshRow(row, duplicateCodes, duplicateNames));
}

function isRowSubmittable(row: BatchRow) {
  refreshRow(row);
  return row.status === 'ready' && !row.saved;
}

function markAmountTouched(record: Record<string, any>) {
  const row = record as BatchRow;
  row.amountTouched = true;
  row.saved = false;
  refreshAllRows();
}

function markTypeTouched(record: Record<string, any>) {
  const row = record as BatchRow;
  row.typeTouched = true;
  row.saved = false;
  refreshAllRows();
}

function markRowEdited(record: Record<string, any>) {
  const row = record as BatchRow;
  row.saved = false;
  refreshAllRows();
}

function getTargetRows() {
  if (selectedRowKeys.value.length > 0) {
    const selected = new Set(selectedRowKeys.value);
    return rows.value.filter((row) => selected.has(row.key));
  }
  return visibleRows.value;
}

function applyDefaultAmount() {
  const targetRows = getTargetRows();
  targetRows.forEach((row) => {
    row.amount = normalizeAmount(batchForm.amount);
    row.amountTouched = true;
    row.saved = false;
  });
  refreshAllRows();
}

function applyDefaultAdminFree() {
  const targetRows = getTargetRows();
  targetRows.forEach((row) => {
    row.adminFree = batchForm.adminFree;
    row.saved = false;
  });
  refreshAllRows();
}

function deleteRow(record: Record<string, any>) {
  const row = record as BatchRow;
  rows.value = rows.value.filter((item) => item.key !== row.key);
  selectedRowKeys.value = selectedRowKeys.value.filter((key) => key !== row.key);
  if (activeRowKey.value === row.key) {
    activeRowKey.value = rows.value[0]?.key || '';
  }
  refreshAllRows();
}

function deleteSelectedRows() {
  if (selectedRowKeys.value.length === 0) {
    message.warning('请先选择资源');
    return;
  }
  const selected = new Set(selectedRowKeys.value);
  rows.value = rows.value.filter((row) => !selected.has(row.key));
  selectedRowKeys.value = [];
  if (!rows.value.some((row) => row.key === activeRowKey.value)) {
    activeRowKey.value = rows.value[0]?.key || '';
  }
  refreshAllRows();
}

function clearRows() {
  rows.value = [];
  selectedRowKeys.value = [];
  activeRowKey.value = '';
}

function parseBoolean(value: string) {
  return /^(1|true|yes|y|是|开启|打开|free)$/i.test(value.trim());
}

function importRowsFromText(text: string) {
  const lines = text
    .split(/\r?\n/)
    .map((line) => line.trim())
    .filter(Boolean);
  let imported = 0;
  lines.forEach((line) => {
    const cells = line.split(/\t|,/).map((cell) => cell.trim());
    if (cells.length === 0 || !cells[0]) {
      return;
    }
    const row = findOrCreateRow(cells[0]);
    row.name = cells[0];
    row.code = cells[1] || normalizeCode(cells[0]);
    if (cells[2] !== undefined && cells[2] !== '') {
      row.amount = normalizeAmount(cells[2]);
      row.amountTouched = true;
    }
    if (cells[3] !== undefined && cells[3] !== '') {
      row.adminFree = parseBoolean(cells[3]);
    }
    row.saved = false;
    imported += 1;
  });
  refreshAllRows();
  if (imported > 0) {
    message.success(`已导入 ${imported} 行数据`);
  } else {
    message.warning('未识别到可导入的数据');
  }
}

async function importClipboard() {
  try {
    const text = await navigator.clipboard.readText();
    importRowsFromText(text);
  } catch {
    message.warning('请复制 Excel 数据后，在表格区域粘贴');
  }
}

function handlePaste(event: ClipboardEvent) {
  const target = event.target as HTMLElement;
  if (['INPUT', 'TEXTAREA'].includes(target.tagName) || target.isContentEditable) {
    return;
  }
  const text = event.clipboardData?.getData('text/plain') || '';
  if (!text.trim()) {
    return;
  }
  event.preventDefault();
  importRowsFromText(text);
}

function buildBadgeExpand() {
  return JSON.stringify({
    badgeDisplayType: batchForm.badgeDisplayType || 'SHORT',
  });
}

function buildPayload(row: BatchRow) {
  const rowType = getRowType(row);
  return {
    adminFree: row.adminFree,
    amount: isAmountRequiredByType(rowType) ? normalizeAmount(row.amount) : 0,
    code: String(row.code || '').trim(),
    cover: row.cover,
    expand: rowType === 'BADGE' ? buildBadgeExpand() : row.expand,
    id: '',
    name: String(row.name || '').trim(),
    sourceUrl: row.sourceUrl,
    sysOrigin: batchForm.sysOrigin,
    type: rowType,
  };
}

async function submitBatch() {
  refreshAllRows();
  if (rows.value.some((row) => row.status === 'uploading')) {
    message.warning('还有文件正在上传，请稍后提交');
    return;
  }
  const targetRows = rows.value.filter((row) => row.status === 'ready');
  if (targetRows.length === 0) {
    message.warning('暂无可提交资源');
    return;
  }
  submitting.value = true;
  let successCount = 0;
  let failedCount = 0;
  for (const row of targetRows) {
    row.saving = true;
    refreshRow(row);
    try {
      await addPropsSource(buildPayload(row));
      row.saved = true;
      row.errors = [];
      successCount += 1;
    } catch (error) {
      console.error(error);
      row.saved = false;
      row.errors = ['保存失败'];
      failedCount += 1;
    } finally {
      row.saving = false;
      refreshRow(row);
    }
  }
  submitting.value = false;
  if (failedCount === 0) {
    message.success(`已提交 ${successCount} 个资源`);
    emit('success');
    emit('close');
    return;
  }
  message.error(`已提交 ${successCount} 个资源，${failedCount} 个失败`);
}

function getStatusText(status: RowStatus) {
  const map: Record<RowStatus, string> = {
    error: '待处理',
    ready: '可提交',
    saved: '已保存',
    saving: '保存中',
    uploading: '上传中',
  };
  return map[status];
}

function getStatusColor(status: RowStatus) {
  const map: Record<RowStatus, string> = {
    error: 'error',
    ready: 'success',
    saved: 'default',
    saving: 'processing',
    uploading: 'processing',
  };
  return map[status];
}

function setActiveRow(row: BatchRow) {
  activeRowKey.value = row.key;
}

function getRowClassName(record: Record<string, any>) {
  const row = record as BatchRow;
  if (row.key === activeRowKey.value) {
    return row.errors.length > 0
      ? 'batch-row batch-row--active batch-row--error'
      : 'batch-row batch-row--active';
  }
  return row.errors.length > 0 ? 'batch-row batch-row--error' : 'batch-row';
}

function customRow(record: Record<string, any>) {
  const row = record as BatchRow;
  return {
    onClick: () => setActiveRow(row),
  };
}
</script>

<template>
  <Drawer
    destroy-on-hidden
    :open="open"
    root-class="resource-batch-drawer-root"
    size="100vw"
    :styles="{
      body: { padding: '0', overflow: 'hidden' },
      footer: { padding: '0' },
    }"
    title="批量导入资源"
    @close="emit('close')"
  >
    <div class="batch-drawer" tabindex="0" @paste="handlePaste">
      <div class="batch-common">
        <div class="common-field">
          <span class="common-label">系统</span>
          <SysOriginSelect
            v-model:value="batchForm.sysOrigin"
            :options="sysOriginOptions"
          />
        </div>
        <div class="common-field">
          <span class="common-label">类型</span>
          <Select
            v-model:value="batchForm.type"
            option-label-prop="label"
            :options="propsTypeOptions"
          />
        </div>
        <div v-if="batchForm.type === 'BADGE'" class="common-field">
          <span class="common-label">展示形态</span>
          <Select
            v-model:value="batchForm.badgeDisplayType"
            option-label-prop="label"
            :options="badgeDisplayTypeOptions"
          />
        </div>
        <div v-if="isAmountRequired" class="common-field common-field--short">
          <span class="common-label">默认底价</span>
          <InputNumber
            v-model:value="batchForm.amount"
            :min="0"
            style="width: 100%"
          />
        </div>
        <div class="common-switch">
          <span class="common-label">Admin Free</span>
          <Switch
            v-model:checked="batchForm.adminFree"
            checked-children="是"
            un-checked-children="否"
          />
        </div>
      </div>

      <div class="batch-workspace">
        <aside class="import-panel">
          <input
            ref="fileInputRef"
            accept="image/*,.svga,.pag,.mp4,.zip"
            class="hidden-input"
            multiple
            type="file"
            @change="handleFileInput"
          />
          <input
            ref="folderInputRef"
            accept="image/*,.svga,.pag,.mp4,.zip"
            class="hidden-input"
            multiple
            type="file"
            webkitdirectory
            @change="handleFileInput"
          />
          <div
            class="dropzone"
            :class="{ 'dropzone--active': dragging }"
            @click="fileInputRef?.click()"
            @dragenter.prevent="dragging = true"
            @dragleave.prevent="dragging = false"
            @dragover.prevent="dragging = true"
            @drop.prevent="handleDrop"
          >
            <div class="dropzone-title">拖拽文件到这里</div>
            <div class="dropzone-desc">选择文件夹后按命名规则自动配对</div>
            <Space wrap>
              <Button type="primary" @click.stop="fileInputRef?.click()">
                选择文件
              </Button>
              <Button @click.stop="folderInputRef?.click()">选择文件夹</Button>
            </Space>
          </div>

          <div class="import-rules">
            <div class="side-title">识别规则</div>
            <div class="rule-item">坐骑_物品名_价格_1天_cover/animation</div>
            <div class="rule-item">头像框_物品名_价格_1天_cover/animation</div>
            <div class="rule-item">勋章_物品名_cover/animation</div>
            <div class="rule-item">cover 写入封面，animation 写入动态图资源</div>
          </div>

          <div class="import-progress">
            <div class="progress-head">
              <span>导入进度</span>
              <span>{{ uploadAssetFinished }}/{{ uploadAssetTotal }}</span>
            </div>
            <Progress :percent="uploadPercent" size="small" />
            <div class="progress-meta">
              已识别 {{ rows.length }} 个，错误 {{ errorCount }} 个
            </div>
          </div>
        </aside>

        <main class="table-panel">
          <div class="table-toolbar">
            <Space wrap>
              <Button @click="importClipboard">粘贴 Excel</Button>
              <Button
                :disabled="rows.length === 0"
                @click="showOnlyErrors = !showOnlyErrors"
              >
                {{ showOnlyErrors ? '查看全部' : '只看错误' }}
              </Button>
              <Button
                :disabled="rows.length === 0"
                @click="applyDefaultAmount"
              >
                批量设置底价
              </Button>
              <Button
                :disabled="rows.length === 0"
                @click="applyDefaultAdminFree"
              >
                批量设置 Admin Free
              </Button>
              <Button
                danger
                :disabled="selectedRowKeys.length === 0"
                @click="deleteSelectedRows"
              >
                删除选中
              </Button>
            </Space>
            <span class="selection-text">
              已选 {{ selectedRowKeys.length }} 项
            </span>
          </div>

          <Table
            bordered
            :columns="columns"
            :custom-row="customRow"
            :data-source="visibleRows"
            :pagination="false"
            :row-class-name="getRowClassName"
            row-key="key"
            :row-selection="rowSelection"
            :scroll="{ x: 1120, y: 'calc(100vh - 360px)' }"
            size="small"
          >
            <template #bodyCell="{ column, record }">
              <template v-if="column.key === 'cover'">
                <div class="asset-cell">
                  <Image
                    v-if="record.cover"
                    class="asset-cover"
                    :src="record.cover"
                  />
                  <div v-else class="asset-empty">
                    {{ record.coverUploading ? '上传中' : '缺少封面' }}
                  </div>
                  <div class="file-name">{{ record.coverFileName || '-' }}</div>
                </div>
              </template>
              <template v-else-if="column.key === 'sourceUrl'">
                <div class="file-cell">
                  <Tag :color="record.sourceUrl ? 'success' : 'default'">
                    {{ record.sourceUploading ? '上传中' : record.sourceUrl ? '已上传' : '未上传' }}
                  </Tag>
                  <div class="file-name">{{ record.sourceFileName || '-' }}</div>
                </div>
              </template>
              <template v-else-if="column.key === 'expand'">
                <div class="file-cell">
                  <Tag :color="record.expand ? 'success' : 'default'">
                    {{ record.expandUploading ? '上传中' : record.expand ? '已上传' : '未上传' }}
                  </Tag>
                  <div class="file-name">{{ record.expandFileName || '-' }}</div>
                </div>
              </template>
              <template v-else-if="column.key === 'type'">
                <Select
                  v-model:value="record.type"
                  option-label-prop="label"
                  :options="propsTypeOptions"
                  size="small"
                  style="width: 118px"
                  @change="markTypeTouched(record)"
                />
              </template>
              <template v-else-if="column.key === 'name'">
                <Input
                  v-model:value="record.name"
                  size="small"
                  @blur="markRowEdited(record)"
                />
              </template>
              <template v-else-if="column.key === 'code'">
                <Input
                  v-model:value="record.code"
                  size="small"
                  @blur="markRowEdited(record)"
                />
              </template>
              <template v-else-if="column.key === 'amount'">
                <InputNumber
                  v-model:value="record.amount"
                  :disabled="!isAmountRequiredByType(record.type)"
                  :min="0"
                  size="small"
                  style="width: 96px"
                  @change="markAmountTouched(record)"
                />
              </template>
              <template v-else-if="column.key === 'adminFree'">
                <Switch
                  v-model:checked="record.adminFree"
                  checked-children="是"
                  un-checked-children="否"
                  @change="markRowEdited(record)"
                />
              </template>
              <template v-else-if="column.key === 'status'">
                <Tag :color="getStatusColor(record.status)">
                  {{ getStatusText(record.status) }}
                </Tag>
              </template>
              <template v-else-if="column.key === 'actions'">
                <Button danger size="small" type="link" @click.stop="deleteRow(record)">
                  删除
                </Button>
              </template>
            </template>
          </Table>
        </main>

        <aside class="preview-panel">
          <template v-if="activeRow">
            <div class="side-title">资源预览</div>
            <div class="preview-cover">
              <Image
                v-if="activeRow.cover"
                class="preview-image"
                :src="activeRow.cover"
              />
              <div v-else class="preview-empty">未上传封面</div>
            </div>
            <div class="preview-name">{{ activeRow.name || '-' }}</div>
            <div class="preview-meta">
              类型：{{ getPropsTypeName(getRowType(activeRow)) }}
            </div>
            <div class="preview-meta">编码：{{ activeRow.code || '-' }}</div>
            <div class="preview-meta">封面：{{ activeRow.coverFileName || '-' }}</div>
            <div class="preview-meta">资源：{{ activeRow.sourceFileName || '-' }}</div>
            <div v-if="showExpandUploadByType(getRowType(activeRow))" class="preview-meta">
              Android：{{ activeRow.expandFileName || '-' }}
            </div>
            <div class="side-title side-title--spaced">校验结果</div>
            <div v-if="activeRow.errors.length === 0" class="success-text">
              可提交
            </div>
            <div v-else class="error-list">
              <Tag
                v-for="error in activeRow.errors"
                :key="error"
                color="error"
              >
                {{ error }}
              </Tag>
            </div>
          </template>
          <Empty v-else description="暂无资源" />
        </aside>
      </div>
    </div>

    <template #footer>
      <div class="batch-footer">
        <div class="footer-stats">
          <span>已识别 {{ rows.length }} 个</span>
          <span>错误 {{ errorCount }} 个</span>
          <span>可提交 {{ submitTargetCount }} 个</span>
        </div>
        <Space>
          <Button :disabled="rows.length === 0 || submitting" @click="clearRows">
            清空
          </Button>
          <Button :disabled="submitting" @click="emit('close')">取消</Button>
          <Button
            :disabled="submitTargetCount === 0"
            :loading="submitting"
            type="primary"
            @click="submitBatch"
          >
            提交 {{ submitTargetCount }} 个
          </Button>
        </Space>
      </div>
    </template>
  </Drawer>
</template>

<style scoped>
.batch-drawer {
  background: #f5f7fb;
  display: flex;
  flex-direction: column;
  height: calc(100vh - 55px);
  min-width: 0;
  outline: none;
}

.batch-common {
  align-items: flex-end;
  background: #fff;
  border-bottom: 1px solid #e5e7eb;
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  padding: 14px 20px;
}

.common-field {
  display: grid;
  gap: 6px;
  min-width: 180px;
}

.common-field--short {
  min-width: 140px;
}

.common-label {
  color: #475569;
  font-size: 12px;
  line-height: 18px;
}

.common-switch {
  align-items: center;
  display: flex;
  gap: 10px;
  min-height: 32px;
}

.batch-workspace {
  display: grid;
  flex: 1;
  gap: 12px;
  grid-template-columns: 260px minmax(620px, 1fr) 300px;
  min-height: 0;
  padding: 12px;
}

.import-panel,
.preview-panel,
.table-panel {
  background: #fff;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  min-height: 0;
}

.import-panel,
.preview-panel {
  display: flex;
  flex-direction: column;
  gap: 14px;
  overflow: auto;
  padding: 14px;
}

.table-panel {
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.hidden-input {
  display: none;
}

.dropzone {
  align-items: center;
  background: #f8fafc;
  border: 1px dashed #94a3b8;
  border-radius: 8px;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-height: 180px;
  padding: 20px 14px;
  text-align: center;
  transition:
    background 0.2s ease,
    border-color 0.2s ease;
}

.dropzone:hover,
.dropzone--active {
  background: #eef6ff;
  border-color: #1677ff;
}

.dropzone-title {
  color: #111827;
  font-size: 16px;
  font-weight: 600;
}

.dropzone-desc {
  color: #64748b;
  font-size: 13px;
}

.side-title {
  color: #111827;
  font-weight: 600;
}

.side-title--spaced {
  margin-top: 6px;
}

.import-rules {
  display: grid;
  gap: 8px;
}

.rule-item,
.progress-meta,
.preview-meta {
  color: #64748b;
  font-size: 12px;
  line-height: 20px;
}

.progress-head {
  align-items: center;
  color: #334155;
  display: flex;
  font-size: 13px;
  justify-content: space-between;
  margin-bottom: 4px;
}

.table-toolbar {
  align-items: center;
  border-bottom: 1px solid #e5e7eb;
  display: flex;
  gap: 12px;
  justify-content: space-between;
  padding: 12px;
}

.selection-text {
  color: #64748b;
  flex-shrink: 0;
  font-size: 12px;
}

.asset-cell,
.file-cell {
  display: grid;
  gap: 6px;
  min-width: 0;
}

.asset-cover,
.asset-empty {
  height: 52px;
  width: 52px;
}

.asset-cover {
  border-radius: 6px;
  display: block;
  overflow: hidden;
}

.asset-cover :deep(.ant-image-img) {
  height: 52px;
  object-fit: contain;
  width: 52px;
}

.asset-empty {
  align-items: center;
  background: #f8fafc;
  border: 1px dashed #cbd5e1;
  border-radius: 6px;
  color: #94a3b8;
  display: flex;
  font-size: 12px;
  justify-content: center;
  text-align: center;
}

.file-name {
  color: #64748b;
  font-size: 12px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.preview-cover,
.preview-empty {
  align-items: center;
  background: #f8fafc;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  display: flex;
  justify-content: center;
  min-height: 180px;
  overflow: hidden;
}

.preview-empty {
  color: #94a3b8;
}

.preview-image {
  max-height: 220px;
  object-fit: contain;
  width: 100%;
}

.preview-name {
  color: #111827;
  font-size: 16px;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.success-text {
  color: #16a34a;
}

.error-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.batch-footer {
  align-items: center;
  background: #fff;
  border-top: 1px solid #e5e7eb;
  display: flex;
  justify-content: space-between;
  padding: 12px 20px;
}

.footer-stats {
  color: #334155;
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  font-size: 13px;
}

:deep(.batch-row--error > td) {
  background: #fff7f7;
}

:deep(.batch-row--active > td) {
  background: #eef6ff;
}

@media (width <= 1180px) {
  .batch-workspace {
    grid-template-columns: 240px minmax(520px, 1fr);
  }

  .preview-panel {
    display: none;
  }
}
</style>
