import {
  isActionableRiskFlag,
  isRiskSummaryWithRisk,
  normalizeRiskFlagTone
} from '../../utils/riskFlags.js'
import {
  normalizeBusinessStage,
  resolveFlagBusinessStage,
  resolveRequestBusinessStage,
  resolveRiskTextBusinessStage
} from './travelRequestDetailBusinessStage.js'
import { cardLikeText } from './travelRequestDetailRiskCardDedupe.js'
import { resolveRouteRelatedItemIdsForRisk } from './travelRequestDetailRouteRisk.js'
import { withRiskTags } from './travelRequestDetailRiskTags.js'

export { buildAiAdviceViewModel } from './travelRequestDetailAiAdviceModel.js'
export {
  extractRiskTagsFromText,
  filterRiskCardsByBusinessStage,
  resolveRiskTags,
  resolveRiskTagTone
} from './travelRequestDetailRiskTags.js'

const DOCUMENT_TYPE_LABELS = {
  flight_itinerary: '机票/航班行程单',
  train_ticket: '火车/高铁票',
  ship_ticket: '轮船票',
  hotel_invoice: '酒店住宿票据',
  taxi_receipt: '出租车/网约车票据',
  parking_toll_receipt: '停车/通行费票据',
  meal_receipt: '餐饮票据',
  office_invoice: '办公用品票据',
  meeting_invoice: '会议/会务票据',
  training_invoice: '培训票据',
  vat_invoice: '增值税发票',
  receipt: '一般收据/凭证',
  other: '其他单据'
}

function normalizeText(value) {
  return String(value || '').trim()
}

function normalizeRiskCardTitle(value, fallback = '单据风险提示') {
  const title = normalizeText(value)
  if (!title) {
    return fallback
  }

  const normalizedTitle = title.replace(/^(?:AI\s*提示|AI\s*建议|智能提示|系统提示)\s*[：:]\s*/i, '').trim()
  if (!normalizedTitle || /^(?:AI\s*提示|AI\s*建议|智能提示|系统提示)$/i.test(normalizedTitle)) {
    return fallback
  }
  return normalizedTitle
}

function uniqueTexts(values) {
  return [...new Set(values.map((item) => normalizeText(item)).filter(Boolean))]
}

function normalizeTone(value) {
  const tone = normalizeText(value).toLowerCase()
  if (['pass', 'success', 'ok', 'normal', 'none', 'compliant', 'approved'].includes(tone)) return 'pass'
  if (tone === 'high') return 'high'
  if (tone === 'medium') return 'medium'
  if (tone === 'low') return 'low'
  return 'medium'
}

function resolveRiskLevelLabel(tone) {
  const normalizedTone = normalizeTone(tone)
  if (normalizedTone === 'high') return '高风险'
  if (normalizedTone === 'medium') return '中风险'
  if (normalizedTone === 'low') return '低风险'
  return '风险提示'
}

export function normalizeRiskTone(value) {
  return normalizeTone(value)
}

function resolveFlagTone(flag) {
  return normalizeRiskFlagTone(flag)
}

function isRiskTone(tone) {
  return ['medium', 'high'].includes(normalizeText(tone).toLowerCase())
}

function normalizeId(value) {
  return normalizeText(value)
}

function normalizeIdList(value) {
  const rawValues = Array.isArray(value)
    ? value
    : normalizeText(value)
      ? [value]
      : []
  return [...new Set(rawValues.map((item) => normalizeId(item)).filter(Boolean))]
}

function normalizeExpenseItemNote(item = {}) {
  const note = normalizeText(item.itemNote ?? item.item_note).replace(/[。；;]+$/, '')
  if (!note || ['待补充', '待补充异常说明', '暂无', '无', 'null', 'undefined'].includes(note)) {
    return ''
  }
  return note
}

function resolveExpenseItemLabel(item = {}, fallback = '相关明细') {
  return normalizeText(item.desc)
    || normalizeText(item.itemReason)
    || normalizeText(item.detail)
    || normalizeText(item.name)
    || normalizeText(item.category)
    || fallback
}

function resolveRelatedExpenseExplanations(itemIds = [], expenseItems = []) {
  const relatedIds = normalizeIdList(itemIds)
  if (!relatedIds.length || !Array.isArray(expenseItems)) {
    return []
  }

  return expenseItems
    .filter((item) => relatedIds.includes(normalizeId(item?.id)))
    .map((item) => {
      const note = normalizeExpenseItemNote(item)
      if (!note) {
        return null
      }
      return {
        itemId: normalizeId(item.id),
        label: resolveExpenseItemLabel(item),
        note,
        text: `${resolveExpenseItemLabel(item)}：${note}`
      }
    })
    .filter(Boolean)
}

function isHotelExpenseItem(item = {}) {
  const text = [
    item.name,
    item.category,
    item.desc,
    item.detail,
    item.itemType,
    item.item_type,
    item.documentType,
    item.document_type
  ].map((value) => normalizeText(value)).join(' ')
  return /住宿|酒店|宾馆|hotel/.test(text)
}

function isTrafficExpenseItem(item = {}) {
  const text = [
    item.name,
    item.category,
    item.desc,
    item.detail,
    item.itemType,
    item.item_type,
    item.documentType,
    item.document_type
  ].map((value) => normalizeText(value)).join(' ')
  if (/补贴|系统自动计算/.test(text)) {
    return false
  }
  return /交通|火车|高铁|机票|航班|出租车|网约车|乘车|车票|train|flight|taxi/.test(text)
}

function inferRelatedExpenseItemIdsByRiskText(flag = {}, risks = [], expenseItems = []) {
  const text = [
    cardLikeText(flag),
    ...uniqueTexts(risks)
  ].map((value) => normalizeText(value)).join(' ')
  const items = Array.isArray(expenseItems) ? expenseItems : []

  if (isHotelOverStandardRiskText(text) || /住宿|酒店|宾馆|hotel/.test(text)) {
    return items.filter(isHotelExpenseItem).map((item) => normalizeId(item?.id)).filter(Boolean)
  }
  if (/交通|火车|高铁|机票|航班|出租车|网约车|乘车|车票|train|flight|taxi/.test(text)) {
    return items.filter(isTrafficExpenseItem).map((item) => normalizeId(item?.id)).filter(Boolean)
  }
  return []
}

function resolveRelatedItemIdsForRisk({
  explicitItemIds = [],
  flag = {},
  risks = [],
  expenseItems = []
} = {}) {
  const normalizedExplicitItemIds = normalizeIdList(explicitItemIds)
  const routeRelatedItemIds = resolveRouteRelatedItemIdsForRisk({
    flagItemIds: normalizedExplicitItemIds,
    flag,
    risks,
    expenseItems
  })
  if (routeRelatedItemIds.length) {
    return routeRelatedItemIds
  }
  if (normalizedExplicitItemIds.length) {
    return normalizedExplicitItemIds
  }
  return inferRelatedExpenseItemIdsByRiskText(flag, risks, expenseItems)
}

function formatRelatedExpenseExplanations(explanations = [], limit = 3) {
  const texts = uniqueTexts(explanations.map((item) => item.text))
  if (!texts.length) {
    return ''
  }
  const visible = texts.slice(0, limit).join('；')
  return `${visible}${texts.length > limit ? ` 等 ${texts.length} 条` : ''}`
}

function resolveRiskTextWithExplanations(risk, explanations = []) {
  const text = normalizeText(risk)
  if (!text || !explanations.length) {
    return text
  }

  if (/未说明|待说明|缺少说明|未识别到.*说明|请.*补充/.test(text)) {
    const cleaned = text
      .replace(/，?但当前[^。；;]*?(?:未说明|待说明|缺少说明|未识别到[^。；;]*说明)[^。；;]*[。；;]?/g, '')
      .replace(/当前[^。；;]*?(?:未说明|待说明|缺少说明|未识别到[^。；;]*说明)[^。；;]*[。；;]?/g, '')
      .replace(/请[^。；;]*?(?:补充|写清楚)[^。；;]*[。；;]?/g, '')
      .trim()
    const base = cleaned.replace(/[，,；;。]+$/, '') || '该风险已命中系统规则'
    return `${base}，用户已在相关费用明细补充异常说明，需审核说明是否充分。`
  }

  return text
}

function resolveClaimRiskSuggestion(flag = {}, { risk = '', summary = '', relatedExplanations = [] } = {}) {
  const explanationSummary = formatRelatedExpenseExplanations(relatedExplanations)
  if (explanationSummary) {
    return `用户已在费用明细补充异常说明：${explanationSummary}。请审核说明是否充分，并结合票据、行程和制度标准决定通过、退回或要求补充佐证。`
  }

  return resolveClaimRiskFallbackSuggestion(flag, { risk, summary })
}

function resolveItemRiskFlag(item, claimRiskFlags) {
  const itemId = normalizeId(item?.id)
  if (!itemId || !Array.isArray(claimRiskFlags)) {
    return null
  }

  return claimRiskFlags.find((flag) => {
    if (!flag || typeof flag !== 'object') {
      return false
    }

    if (!isActionableRiskFlag(flag)) {
      return false
    }

    const flagItemId = normalizeId(flag.item_id || flag.itemId)
    const flagItemIds = normalizeIdList(flag.item_ids || flag.itemIds)
    const tone = resolveFlagTone(flag)
    return (flagItemId === itemId || flagItemIds.includes(itemId)) && isRiskTone(tone)
  }) || null
}

export function buildItemClaimRiskState(item, claimRiskFlags = []) {
  const flag = resolveItemRiskFlag(item, claimRiskFlags)
  if (!flag) {
    return null
  }

  const tone = resolveFlagTone(flag)
  const label = normalizeText(flag.label) || (tone === 'high' ? '高风险' : '中风险')
  const points = Array.isArray(flag.points)
    ? flag.points.map((point) => normalizeText(point)).filter(Boolean)
    : []
  const summary = normalizeText(flag.summary || flag.message || flag.reason)

  return {
    label,
    tone,
    headline: normalizeText(flag.headline || flag.title) || label,
    summary,
    points,
    suggestion: normalizeText(flag.suggestion) || '如业务确需提交，请在附加说明中补充特殊情况原因后继续提交。'
  }
}

function resolveDocumentTypeLabel(value) {
  return DOCUMENT_TYPE_LABELS[normalizeText(value)] || DOCUMENT_TYPE_LABELS.other
}

function normalizeRuleBasis(value) {
  if (Array.isArray(value)) {
    return value.map((item) => normalizeText(item)).filter(Boolean)
  }

  const text = normalizeText(value)
  return text ? [text] : []
}

function resolveClaimRiskRuleBasis(flag = {}, { risk = '', summary = '', tone = 'medium' } = {}) {
  const explicitBasis = normalizeRuleBasis(flag.rule_basis || flag.ruleBasis)
  if (explicitBasis.length) {
    return explicitBasis
  }

  const source = normalizeText(flag.source)
  const label = normalizeText(flag.label || flag.title || flag.name)
  const corpus = [risk, summary, label].map((item) => normalizeText(item)).join(' ')
  const basis = []

  if (/高风险|中风险/.test(corpus)) {
    basis.push(`风险文本已明确标记为${tone === 'high' ? '高风险' : '中风险'}。`)
  }
  if (source === 'attachment_analysis' || /附件|票据|OCR|识别|发票/.test(corpus)) {
    basis.push('附件识别或票据核验未完全通过，系统将该项同步为单据风险。')
  }
  if (/直属领导|审批链|审批人缺失|审批人信息|补充分配|分配/.test(corpus)) {
    basis.push('审批链校验未匹配到完整审批人信息，因此按中风险提醒。')
  }
  if (/金额|超标|阈值|住宿标准|报销标准|标准/.test(corpus)) {
    basis.push('金额或标准核算命中制度阈值，需要补充说明或人工复核。')
  }
  if (/历史|近\s*\d+\s*天|重复|多次/.test(corpus)) {
    basis.push('历史报销风险次数达到预警条件，系统提示审批人重点关注。')
  }
  if (/缺失|缺少|未识别|待补充|不一致|不匹配/.test(corpus)) {
    basis.push('单据、附件或审批信息存在缺失、不一致或待补充项。')
  }
  if (summary) {
    basis.push(`风险汇总：${summary}`)
  }

  return uniqueTexts(basis.length ? basis : [`系统预审根据“${label || '单据风险'}”将该项列为${tone === 'high' ? '高风险' : '中风险'}。`])
}

function resolveClaimRiskFallbackSuggestion(flag = {}, { risk = '', summary = '' } = {}) {
  const explicitSuggestion = normalizeText(flag.suggestion)
  if (explicitSuggestion) {
    return explicitSuggestion
  }

  const corpus = [risk, summary, flag.label, flag.title].map((item) => normalizeText(item)).join(' ')
  if (/直属领导|审批链|审批人缺失|审批人信息|补充分配|分配/.test(corpus)) {
    return '请先核对员工档案中的直属领导和审批链配置；如果信息无误，可由审批环节人工补充分配说明。'
  }
  if (/金额|超标|阈值|住宿标准|报销标准|标准/.test(corpus)) {
    return '请核对金额、天数、地点和职级标准；如确需超标，请在附加说明中写清楚业务原因和佐证材料。'
  }
  if (/附件|票据|OCR|识别|发票/.test(corpus)) {
    return '请打开对应费用明细的附件预览，核对票据类型、金额、日期和说明；识别有误时先修正明细或重新上传附件。'
  }
  if (/历史|近\s*\d+\s*天|重复|多次/.test(corpus)) {
    return '请核对近期同类报销记录，必要时补充本次费用与历史单据不同的业务背景。'
  }
  if (/缺失|缺少|未识别|待补充|不一致|不匹配/.test(corpus)) {
    return '请按风险点补齐缺失信息，并核对费用明细与附件内容是否一致。'
  }

  return '请先核对上方触发原因；如属于真实业务例外，在附加说明中写清楚原因和佐证后再继续流转。'
}

export function buildAttachmentInsightViewModel(metadata, item = {}) {
  if (!metadata) {
    return null
  }

  const documentInfo = metadata.document_info || {}
  const requirementCheck = metadata.requirement_check || null
  const analysis = metadata.analysis || null
  const documentTypeLabel =
    normalizeText(documentInfo.document_type_label) || resolveDocumentTypeLabel(documentInfo.document_type)
  const fields = Array.isArray(documentInfo.fields)
    ? documentInfo.fields
      .map((field) => ({
        label: normalizeText(field?.label),
        value: normalizeText(field?.value)
      }))
      .filter((field) => field.label && field.value)
      .map((field) => `${field.label}：${field.value}`)
    : []
  const ruleBasis = uniqueTexts([
    ...normalizeRuleBasis(analysis?.rule_basis || analysis?.ruleBasis),
    ...normalizeRuleBasis(requirementCheck?.rule_basis || requirementCheck?.ruleBasis),
    normalizeText(requirementCheck?.message),
    documentTypeLabel ? `票据识别依据：系统将附件识别为${documentTypeLabel}。` : '',
    normalizeText(item?.name) ? `费用项目依据：当前明细为${normalizeText(item.name)}。` : ''
  ])

  return {
    fileName: normalizeText(metadata.file_name || item.attachmentHint || item.invoiceId),
    mediaType: normalizeText(metadata.media_type),
    previewable: metadata.previewable !== false,
    documentTypeLabel,
    requirementLabel: requirementCheck
      ? (requirementCheck.matches ? '符合当前费用类型' : '不符合当前费用类型')
      : '待校验附件类型',
    requirementTone: requirementCheck
      ? (requirementCheck.matches ? 'pass' : 'high')
      : 'medium',
    message: normalizeText(requirementCheck?.message),
    fields: fields.slice(0, 8),
    ruleBasis,
    analysis: analysis
      ? {
        label: normalizeText(analysis.label) || 'AI提示',
        tone: normalizeTone(analysis.severity),
        headline: normalizeText(analysis.headline) || normalizeText(analysis.label) || 'AI提示',
        summary: normalizeText(analysis.summary),
        points: Array.isArray(analysis.points) ? analysis.points.map((point) => normalizeText(point)).filter(Boolean) : [],
        suggestion: normalizeText(analysis.suggestion)
      }
      : null
  }
}

function buildCardSuggestion(analysis, insight) {
  return (
    normalizeText(analysis?.suggestion)
    || normalizeText(insight?.message)
    || '请根据规则依据核对附件和费用明细，必要时补充说明、更换附件或调整费用项目。'
  )
}

function buildRiskCardFromPoint({ item, index, point, pointIndex, insight, analysis, businessStage = 'reimbursement' }) {
  const tone = normalizeTone(analysis?.severity)
  const title = normalizeRiskCardTitle(
    normalizeText(analysis?.headline) || normalizeText(analysis?.label) || normalizeText(item?.name),
    '附件风险'
  )
  const relatedExplanations = resolveRelatedExpenseExplanations([normalizeId(item?.id)], [item])
  const explanationSummary = formatRelatedExpenseExplanations(relatedExplanations)
  const ruleBasis = uniqueTexts([
    ...(insight?.ruleBasis?.length ? insight.ruleBasis : ['系统根据附件识别结果与费用项目规则进行比对。']),
    explanationSummary ? `用户已补充异常说明：${explanationSummary}。` : ''
  ])

  return withRiskTags({
    id: `${normalizeText(item?.id) || `expense-${index}`}-${pointIndex}`,
    itemId: normalizeId(item?.id),
    itemIndex: index + 1,
    invoiceId: normalizeText(item?.invoiceId),
    businessStage: normalizeBusinessStage(businessStage) || 'reimbursement',
    tone,
    label: resolveRiskLevelLabel(tone),
    title,
    risk: resolveRiskTextWithExplanations(
      normalizeText(point) || normalizeText(analysis?.summary) || '附件存在待核对风险。',
      relatedExplanations
    ),
    summary: normalizeText(analysis?.summary),
    ruleBasis,
    suggestion: relatedExplanations.length
      ? resolveClaimRiskSuggestion({}, {
        risk: normalizeText(point) || normalizeText(analysis?.summary),
        summary: normalizeText(analysis?.summary),
        relatedExplanations
      })
      : buildCardSuggestion(analysis, insight),
    source: 'attachment_analysis',
    itemType: normalizeText(item?.itemType),
    documentType: normalizeText(insight?.documentTypeLabel),
    relatedExplanations,
    relatedExplanationSummary: explanationSummary,
    visibility_scope: 'submitter',
    actionability: 'fixable_by_submitter'
  })
}

function parseReturnCount(flag) {
  const count = Number(flag?.return_count ?? flag?.returnCount ?? 0)
  return Number.isFinite(count) && count > 0 ? Math.floor(count) : 0
}

function resolveLatestManualReturnFlag(flags) {
  const manualReturnFlags = flags.filter(
    (flag) => flag && typeof flag === 'object' && normalizeText(flag.source) === 'manual_return'
  )
  if (!manualReturnFlags.length) {
    return null
  }

  return manualReturnFlags.reduce((latest, flag) => {
    const latestCount = parseReturnCount(latest)
    const nextCount = parseReturnCount(flag)
    if (nextCount !== latestCount) {
      return nextCount > latestCount ? flag : latest
    }

    const latestTime = Date.parse(normalizeText(latest?.created_at || latest?.createdAt))
    const nextTime = Date.parse(normalizeText(flag?.created_at || flag?.createdAt))
    if (Number.isFinite(nextTime) && (!Number.isFinite(latestTime) || nextTime >= latestTime)) {
      return flag
    }

    return latest
  }, manualReturnFlags[0])
}

function buildManualReturnRiskCard(flag, businessStage = 'reimbursement') {
  if (!flag) {
    return null
  }

  const returnCount = parseReturnCount(flag)
  const stageReturnCount = Number(flag.stage_return_count ?? flag.stageReturnCount ?? 0)
  const returnStage = normalizeText(flag.return_stage || flag.returnStage || flag.previous_approval_stage)
  const riskPoints = Array.isArray(flag.risk_points || flag.riskPoints)
    ? (flag.risk_points || flag.riskPoints).map((item) => normalizeText(item)).filter(Boolean)
    : []
  const risk = normalizeText(flag.message || flag.reason || flag.summary) || '审批人退回该单据，请补充后重新提交。'
  const ruleBasis = uniqueTexts([
    returnCount ? `累计退回 ${returnCount} 次。` : '',
    returnStage ? `本次退回环节：${returnStage}。` : '',
    stageReturnCount > 0 ? `该环节累计退回 ${Math.floor(stageReturnCount)} 次。` : '',
    ...riskPoints.map((item) => `退回风险点：${item}。`)
  ])

  return withRiskTags({
    id: `manual-return-${returnCount || 'latest'}`,
    businessStage: resolveFlagBusinessStage(flag, normalizeBusinessStage(businessStage) || 'reimbursement'),
    tone: 'medium',
    label: '退回原因',
    title: returnCount ? `第 ${returnCount} 次退回` : '审批退回',
    risk,
    summary: normalizeText(flag.reason),
    ruleBasis: ruleBasis.length ? ruleBasis : ['审批人已退回该单据。'],
    suggestion: '请按退回原因补充材料、修正明细或完善说明后重新提交。',
    risk_domain: flag.risk_domain || flag.riskDomain || 'workflow',
    visibility_scope: flag.visibility_scope || flag.visibilityScope || 'submitter',
    actionability: flag.actionability || 'fixable_by_submitter'
  })
}

function isGenericAutoReviewSummaryFlag(flag) {
  if (!flag || typeof flag !== 'object') {
    return false
  }

  const source = normalizeText(flag.source)
  const label = normalizeText(flag.label || flag.title || flag.name)
  if (source !== 'submission_review' || !['自动检测重点复核', '自动检测提醒'].includes(label)) {
    return false
  }

  const hasConcreteRule = normalizeText(flag.rule_code || flag.ruleCode || flag.hit_source || flag.hitSource)
  const hasConcreteItem = (
    normalizeText(flag.item_id || flag.itemId || flag.invoice_id || flag.invoiceId)
    || normalizeIdList(flag.item_ids || flag.itemIds).length
  )
  if (hasConcreteRule || hasConcreteItem) {
    return false
  }

  return /^自动检测发现/.test(normalizeText(flag.message || flag.summary || flag.reason))
}

function isHotelOverStandardRiskText(value) {
  const text = normalizeText(value)
  return /住宿|酒店|宾馆/.test(text) && /超标|超出|报销标准|住宿标准|差标/.test(text)
}

function isCoveredByAttachmentHotelOverStandardRisk(flag, attachmentCards = []) {
  if (!isHotelOverStandardRiskText(cardLikeText(flag))) {
    return false
  }
  return attachmentCards.some((card) => isHotelOverStandardRiskText(cardLikeText(card)))
}

export function buildAttachmentRiskCards({
  expenseItems = [],
  attachmentMetaByItemId = {},
  claimRiskFlags = [],
  businessStage = 'reimbursement'
} = {}) {
  const normalizedBusinessStage = normalizeBusinessStage(businessStage) || 'reimbursement'
  const attachmentRiskItemIds = new Set()
  const attachmentCards = expenseItems.flatMap((item, index) => {
    if (!item?.invoiceId) {
      return []
    }

    const metadata = attachmentMetaByItemId[item.id]
    const insight = buildAttachmentInsightViewModel(metadata, item)
    const analysis = metadata?.analysis
    const tone = normalizeTone(analysis?.severity)
    if (!analysis || !['medium', 'high'].includes(tone)) {
      return []
    }
    const itemId = normalizeId(item.id)
    if (itemId) {
      attachmentRiskItemIds.add(itemId)
    }

    const points = Array.isArray(analysis.points) && analysis.points.length
      ? analysis.points
      : [analysis.summary || analysis.headline || analysis.label]

    return points
      .map((point, pointIndex) => buildRiskCardFromPoint({
        item,
        index,
        point,
        pointIndex,
        insight,
        analysis,
        businessStage: normalizedBusinessStage
      }))
      .filter((card) => card.risk)
  })

  const normalizedClaimRiskFlags = Array.isArray(claimRiskFlags) ? claimRiskFlags : []
  const latestManualReturnCard = buildManualReturnRiskCard(
    resolveLatestManualReturnFlag(normalizedClaimRiskFlags),
    normalizedBusinessStage
  )
  const claimCards = normalizedClaimRiskFlags
      .flatMap((flag, index) => {
        if (flag && typeof flag === 'object' && normalizeText(flag.source) === 'manual_return') {
          return []
        }
        if (flag && typeof flag === 'object' && normalizeText(flag.source) === 'ai_pre_review') {
          return []
        }
        if (isGenericAutoReviewSummaryFlag(flag)) {
          return []
        }
        if (isCoveredByAttachmentHotelOverStandardRisk(flag, attachmentCards)) {
          return []
        }

        if (!flag || typeof flag !== 'object') {
          if (!isActionableRiskFlag(flag)) {
            return []
          }

          const risk = normalizeText(flag)
          return risk
            ? [withRiskTags({
              id: `claim-risk-${index}`,
              businessStage: resolveRiskTextBusinessStage(risk, normalizedBusinessStage),
              tone: 'medium',
              label: '单据风险',
              title: '单据风险提示',
              risk,
              summary: '',
              ruleBasis: resolveClaimRiskRuleBasis({}, { risk, tone: 'medium' }),
              suggestion: resolveClaimRiskSuggestion({}, { risk })
            })]
            : []
        }

        if (!isActionableRiskFlag(flag)) {
          return []
        }

        const source = normalizeText(flag.source)
        const flagItemId = normalizeId(flag.item_id || flag.itemId)
        const flagItemIds = normalizeIdList(flag.item_ids || flag.itemIds)
        if (source === 'attachment_analysis' && flagItemId && attachmentRiskItemIds.has(flagItemId)) {
          return []
        }

        const tone = resolveFlagTone(flag)
        if (!isRiskTone(tone)) {
          return []
        }

        const flagPoints = Array.isArray(flag.points)
          ? flag.points.map((point) => normalizeText(point)).filter(Boolean)
          : []
        const primaryRisk = normalizeText(flag.message || flag.reason || flag.summary)
        const fallbackRisk = normalizeText(flag.description || flag.detail || flag.title || flag.label || flag.name)
        const risks = flagPoints.length
          ? flagPoints
          : [primaryRisk || fallbackRisk].filter(Boolean)
        const relatedItemIds = resolveRelatedItemIdsForRisk({
          explicitItemIds: [flagItemId, ...flagItemIds],
          flag,
          risks,
          expenseItems
        })
        const itemIndex = Number(flag.item_index ?? flag.itemIndex ?? 0) || null
        const title = normalizeRiskCardTitle(
          flag.title || flag.label || flag.name || flag.rule_name || flag.ruleCode || flag.rule_code,
          '单据风险提示'
        )
        const summary = normalizeText(flag.summary || flag.message || flag.reason)
        const ruleBasis = resolveClaimRiskRuleBasis(flag, {
          risk: risks[0] || primaryRisk || fallbackRisk,
          summary,
          tone
        })
        const relatedExplanations = resolveRelatedExpenseExplanations(relatedItemIds, expenseItems)
        const explanationSummary = formatRelatedExpenseExplanations(relatedExplanations)
        const mergedRuleBasis = uniqueTexts([
          ...ruleBasis,
          explanationSummary ? `用户已补充异常说明：${explanationSummary}。` : ''
        ])

        return risks.map((risk, pointIndex) => withRiskTags({
          id: `claim-risk-${index}-${pointIndex}`,
          itemId: flagItemId || (relatedItemIds.length === 1 ? relatedItemIds[0] : ''),
          itemIds: relatedItemIds,
          itemIndex,
          invoiceId: normalizeText(flag.invoice_id || flag.invoiceId),
          businessStage: resolveFlagBusinessStage(flag, normalizedBusinessStage),
          tone,
          label: resolveRiskLevelLabel(tone),
          title,
          risk: resolveRiskTextWithExplanations(risk, relatedExplanations),
          summary,
          ruleBasis: mergedRuleBasis,
          suggestion: resolveClaimRiskSuggestion(flag, { risk, summary, relatedExplanations }),
          relatedExplanations,
          relatedExplanationSummary: explanationSummary,
          source,
          risk_domain: flag.risk_domain || flag.riskDomain,
          visibility_scope: flag.visibility_scope || flag.visibilityScope,
          actionability: flag.actionability
        }))
      })
      .filter(Boolean)
  if (latestManualReturnCard) {
    claimCards.unshift(latestManualReturnCard)
  }

  return [...attachmentCards, ...claimCards]
}

function isNoRiskSummary(value) {
  return !isRiskSummaryWithRisk(value)
}

function resolveClaimSummaryTone(request) {
  const explicitTone = normalizeText(request?.riskTone || request?.risk_tone || request?.severity)
  if (explicitTone) {
    return normalizeTone(explicitTone)
  }

  const summary = normalizeText(request?.riskSummary || request?.risk || request?.riskText)
  if (/高风险|重大风险|严重|超标|违规/.test(summary)) {
    return 'high'
  }
  if (/中风险|待关注|待复核|提醒|异常|风险/.test(summary)) {
    return 'medium'
  }
  return 'medium'
}

export function buildClaimSummaryRiskCards(request = {}) {
  const summary = normalizeText(request?.riskSummary || request?.risk || request?.riskText)
  if (isNoRiskSummary(summary)) {
    return []
  }

  const tone = resolveClaimSummaryTone(request)
  if (!isRiskTone(tone)) {
    return []
  }
  const businessStage = resolveRiskTextBusinessStage(summary, resolveRequestBusinessStage(request))

  return [withRiskTags({
    id: 'claim-risk-summary',
    businessStage,
    tone,
    label: resolveRiskLevelLabel(tone),
    title: '单据风险提示',
    risk: summary,
    summary,
    ruleBasis: resolveClaimRiskRuleBasis(
      { source: 'risk_summary', message: summary, severity: tone },
      { risk: summary, summary, tone }
    ),
    suggestion: resolveClaimRiskSuggestion({ source: 'risk_summary' }, { risk: summary, summary })
  })]
}
