function normalizeAttachmentMatchName(value) {
  const fileName = String(value || '')
    .trim()
    .split(/[\\/]/)
    .filter(Boolean)
    .pop() || ''
  return fileName
    .toLowerCase()
    .replace(/[^\w.\-\u4e00-\u9fff]+/g, '_')
    .replace(/^[_\.]+|[_\.]+$/g, '')
}

function isSystemGeneratedExpenseItem(item = {}) {
  const itemType = String(item?.itemType || item?.item_type || '').trim()
  return Boolean(
    item?.isSystemGenerated ||
      item?.is_system_generated ||
      itemType === 'travel_allowance'
  )
}

function findCreatedAttachmentItem(items = [], usedItemIds = new Set()) {
  return (Array.isArray(items) ? items : []).find((item) => {
    const itemId = String(item?.id || '').trim()
    const invoiceId = String(item?.invoiceId || item?.invoice_id || '').trim()
    const itemType = String(item?.itemType || item?.item_type || '').trim()
    return (
      itemId &&
      !usedItemIds.has(itemId) &&
      !invoiceId &&
      itemType !== 'travel_allowance' &&
      !item?.isSystemGenerated &&
      !item?.is_system_generated
    )
  }) || null
}

export async function syncExpenseClaimFilesToDraft({
  claimId = '',
  files = [],
  fetchExpenseClaimDetail,
  createExpenseClaimItem,
  uploadExpenseClaimItemAttachment
} = {}) {
  const normalizedClaimId = String(claimId || '').trim()
  const safeFiles = Array.isArray(files) ? files : []
  if (!normalizedClaimId || !safeFiles.length || typeof uploadExpenseClaimItemAttachment !== 'function') {
    return { uploadedCount: 0, skippedCount: safeFiles.length, uploadedFileNames: [], skippedFileNames: safeFiles.map((file) => file?.name || '') }
  }
  if (typeof fetchExpenseClaimDetail !== 'function') {
    throw new Error('缺少单据详情查询服务，暂时无法自动归集附件。')
  }

  const claim = await fetchExpenseClaimDetail(normalizedClaimId)
  const items = Array.isArray(claim?.items) ? claim.items : []
  const exactMatchBuckets = new Map()
  const normalizedMatchBuckets = new Map()
  const placeholderQueue = []
  const emptyAttachmentQueue = []
  const usedItemIds = new Set()
  const uploadedFileNames = []

  for (const item of items) {
    const itemId = String(item?.id || '').trim()
    const invoiceId = String(item?.invoiceId || item?.invoice_id || '').trim()
    if (!itemId) continue

    if (!invoiceId && !isSystemGeneratedExpenseItem(item)) {
      emptyAttachmentQueue.push(item)
      continue
    }
    if (!invoiceId || invoiceId.includes('/')) {
      continue
    }

    placeholderQueue.push(item)
    const exactBucket = exactMatchBuckets.get(invoiceId) || []
    exactBucket.push(item)
    exactMatchBuckets.set(invoiceId, exactBucket)

    const normalizedInvoiceName = normalizeAttachmentMatchName(invoiceId)
    if (normalizedInvoiceName) {
      const normalizedBucket = normalizedMatchBuckets.get(normalizedInvoiceName) || []
      normalizedBucket.push(item)
      normalizedMatchBuckets.set(normalizedInvoiceName, normalizedBucket)
    }
  }

  for (const file of safeFiles) {
    const exactBucket = exactMatchBuckets.get(file.name) || []
    const nextExactMatch = exactBucket.find((item) => !usedItemIds.has(String(item?.id || '').trim()))
    const normalizedBucket = normalizedMatchBuckets.get(normalizeAttachmentMatchName(file.name)) || []
    const nextNormalizedMatch = normalizedBucket.find((item) => !usedItemIds.has(String(item?.id || '').trim()))
    const fallbackMatch = placeholderQueue.find((item) => !usedItemIds.has(String(item?.id || '').trim()))
    const emptyFallbackMatch = emptyAttachmentQueue.find((item) => !usedItemIds.has(String(item?.id || '').trim()))
    let targetItem = nextExactMatch || nextNormalizedMatch || fallbackMatch || emptyFallbackMatch
    let targetItemId = String(targetItem?.id || '').trim()

    if (!targetItemId && typeof createExpenseClaimItem === 'function') {
      const updatedClaim = await createExpenseClaimItem(normalizedClaimId, {})
      targetItem = findCreatedAttachmentItem(updatedClaim?.items, usedItemIds)
      targetItemId = String(targetItem?.id || '').trim()
    }
    if (!targetItemId) {
      continue
    }

    usedItemIds.add(targetItemId)
    await uploadExpenseClaimItemAttachment(normalizedClaimId, targetItemId, file)
    uploadedFileNames.push(String(file?.name || '').trim())
  }

  const uploadedSet = new Set(uploadedFileNames)
  const skippedFileNames = safeFiles
    .map((file) => String(file?.name || '').trim())
    .filter((name) => !uploadedSet.has(name))

  return {
    uploadedCount: uploadedFileNames.length,
    skippedCount: Math.max(0, safeFiles.length - uploadedFileNames.length),
    uploadedFileNames,
    skippedFileNames
  }
}
