import { TRANSPORT_KEYWORD_PATTERN } from '../../utils/reimbursementTextInference.js'
import {
  CATEGORY_CONFIDENCE_KEYWORDS,
  EXPENSE_CODE_TO_PRESET_SCENE,
  EXPENSE_TYPE_LABELS,
  REVIEW_CATEGORY_PRESET_OPTIONS,
  REVIEW_SCENE_OPTIONS,
  REVIEW_SCENE_OTHER_OPTION,
  REVIEW_SLOT_CONFIG
} from './travelReimbursementReviewConstants.js'
import {
  buildReviewDocumentCorrectionLines,
  formatConfidenceLabel,
  resolveExpenseTypeLabel
} from './travelReimbursementReviewDocuments.js'

export {
  CATEGORY_CONFIDENCE_KEYWORDS,
  DATE_INPUT_FORMAT,
  DOCUMENT_TYPE_LABELS,
  EXPENSE_CODE_TO_PRESET_SCENE,
  EXPENSE_TYPE_LABELS,
  REVIEW_CATEGORY_PRESET_OPTIONS,
  REVIEW_FALLBACK_GROUP_CODES,
  REVIEW_OTHER_CATEGORY_OPTIONS,
  REVIEW_SCENE_OPTIONS,
  REVIEW_SCENE_OTHER_OPTION,
  REVIEW_SLOT_CONFIG
} from './travelReimbursementReviewConstants.js'

export {
  buildReviewDocumentCorrectionContext,
  buildReviewDocumentCorrectionLines,
  buildReviewDocumentCorrectionMessage,
  buildReviewDocumentDrafts,
  buildReviewDocumentSummaries,
  cloneReviewDocumentDrafts,
  formatConfidenceLabel,
  normalizeReviewDocumentComparableValue,
  resolveDocumentTypeLabel,
  resolveExpenseTypeLabel
} from './travelReimbursementReviewDocuments.js'

export function cloneReviewEditFields(fields) {
  const items = Array.isArray(fields) ? fields : []
  return items.map((item) => ({
    key: String(item?.key || '').trim(),
    label: String(item?.label || '').trim(),
    value: String(item?.value || ''),
    placeholder: String(item?.placeholder || ''),
    required: Boolean(item?.required),
    field_type: String(item?.field_type || item?.fieldType || 'text').trim() || 'text',
    group: String(item?.group || 'basic').trim() || 'basic'
  }))
}

export function buildReviewFormValues(fields) {
  return cloneReviewEditFields(fields).reduce((result, item) => {
    if (!item.key) {
      return result
    }
    result[item.key] = String(item.value || '').trim()
    return result
  }, {})
}

export function buildBusinessTimeContextFromReviewValues(values = {}) {
  const timeText = String(values.time_range || values.business_time || values.occurred_date || '').trim()
  if (!timeText) {
    return null
  }

  const matchedDates = timeText.match(/\d{4}-\d{2}-\d{2}/g) || []
  if (!matchedDates.length) {
    return null
  }
  const startDate = matchedDates[0]
  const endDate = matchedDates[matchedDates.length - 1] || startDate
  if (!isValidIsoDateString(startDate) || !isValidIsoDateString(endDate) || startDate > endDate) {
    return null
  }
  const displayValue = startDate === endDate ? startDate : `${startDate} 至 ${endDate}`
  return {
    mode: startDate === endDate ? 'single' : 'range',
    start_date: startDate,
    end_date: endDate,
    display_value: displayValue
  }
}

export function buildReviewFormContextFromPayload(reviewPayload, inlineState = null) {
  if (!reviewPayload || typeof reviewPayload !== 'object') {
    return {}
  }

  const fallbackState = buildInlineReviewState(reviewPayload)
  const candidateState = inlineState || fallbackState
  const hasCandidateValue = Object.values(candidateState || {}).some((value) => {
    if (typeof value === 'number') return value > 0
    return Boolean(String(value || '').trim())
  })
  const state = hasCandidateValue ? candidateState : fallbackState
  const fields = mergeInlineReviewFields(reviewPayload.edit_fields || [], state)
  const values = buildReviewFormValues(fields)
  const slotMap = buildReviewSlotMap(reviewPayload)
  const inheritedTimeRange = String(
    slotMap.time_range?.normalized_value ||
      slotMap.time_range?.value ||
      values.time_range ||
      values.business_time ||
      values.occurred_date ||
      ''
  ).trim()
  if (inheritedTimeRange) {
    values.time_range = values.time_range || inheritedTimeRange
    values.business_time = values.business_time || inheritedTimeRange
  }

  const businessTimeContext = buildBusinessTimeContextFromReviewValues(values)
  return {
    review_form_values: values,
    ...(businessTimeContext ? { business_time_context: businessTimeContext } : {})
  }
}

export function buildReviewEditFieldMap(fields) {
  return cloneReviewEditFields(fields).reduce((result, item) => {
    if (!item.key) return result
    result[item.key] = item
    return result
  }, {})
}

export function createEmptyInlineReviewState() {
  return {
    occurred_date: '',
    amount: '',
    transport_type: '',
    scene_label: '',
    reason_value: '',
    customer_name: '',
    location: '',
    merchant_name: '',
    participants: '',
    attachment_names: '',
    attachment_count: 0,
    pending_attachment_count: 0,
    expense_type: ''
  }
}

export function isTravelReviewPayload(reviewPayload, inlineState = createEmptyInlineReviewState()) {
  const expenseType = resolveExpenseTypeCode(
    inlineState?.expense_type ||
      buildReviewSlotMap(reviewPayload).expense_type?.normalized_value ||
      buildReviewSlotMap(reviewPayload).expense_type?.value ||
      ''
  )
  if (['travel', 'hotel'].includes(expenseType)) {
    return true
  }

  return (Array.isArray(reviewPayload?.document_cards) ? reviewPayload.document_cards : []).some((item) => {
    const documentType = String(item?.document_type || '').trim().toLowerCase()
    const suggestedType = resolveExpenseTypeCode(item?.suggested_expense_type || item?.scene_label || '')
    return (
      ['flight_itinerary', 'train_ticket', 'hotel_invoice'].includes(documentType) ||
      ['travel', 'hotel'].includes(suggestedType)
    )
  })
}

export function resolveReviewTravelTransportType(reviewPayload, fallbackText = '') {
  const documents = Array.isArray(reviewPayload?.document_cards) ? reviewPayload.document_cards : []
  const labels = []

  const appendLabel = (label) => {
    if (label && !labels.includes(label)) {
      labels.push(label)
    }
  }

  for (const item of documents) {
    const documentType = String(item?.document_type || '').trim().toLowerCase()
    const text = [
      item?.filename,
      item?.summary,
      item?.scene_label,
      item?.suggested_expense_type,
      ...(Array.isArray(item?.fields) ? item.fields.map((field) => `${field?.label || ''}${field?.value || ''}`) : [])
    ].join(' ')
    const compact = text.replace(/\s+/g, '')

    if (documentType === 'flight_itinerary' || /飞机|机票|航班|登机牌/.test(compact)) {
      appendLabel('飞机')
    } else if (documentType === 'train_ticket' || /火车|高铁|动车|铁路|车票/.test(compact)) {
      appendLabel('火车/高铁')
    } else if (documentType === 'taxi_receipt' || /打车|网约车|出租车|滴滴|的士/.test(compact)) {
      appendLabel('打车/网约车')
    }
  }

  const fallback = String(fallbackText || '').replace(/\s+/g, '')
  if (!labels.length) {
    if (/飞机|机票|航班/.test(fallback)) appendLabel('飞机')
    if (/火车|高铁|动车|铁路/.test(fallback)) appendLabel('火车/高铁')
    if (/打车|网约车|出租车|滴滴|的士/.test(fallback)) appendLabel('打车/网约车')
  }

  return labels.join('、')
}

export function resolveReviewRecognizedSlotCards(reviewPayload) {
  return Array.isArray(reviewPayload?.slot_cards)
    ? reviewPayload.slot_cards.filter((item) => item.status !== 'missing')
    : []
}

export function resolveReviewMissingSlotCards(reviewPayload) {
  return Array.isArray(reviewPayload?.slot_cards)
    ? reviewPayload.slot_cards.filter((item) => item.status === 'missing')
    : []
}

export function resolveReviewExtraMissingLabels(reviewPayload) {
  const labels = Array.isArray(reviewPayload?.missing_slots)
    ? reviewPayload.missing_slots
      .map((item) => {
        if (item && typeof item === 'object') {
          return String(item.label || item.title || item.key || '').trim()
        }
        return String(item || '').trim()
      })
      .filter(Boolean)
    : []
  if (!labels.length) return []

  const slotLabels = new Set(
    (Array.isArray(reviewPayload?.slot_cards) ? reviewPayload.slot_cards : []).flatMap((item) => [
      String(item?.label || '').trim(),
      String(item?.key || '').trim()
    ]).filter(Boolean)
  )
  return labels.filter((label) => !slotLabels.has(label))
}

export function buildReviewRecognizedLines(reviewPayload) {
  return resolveReviewRecognizedSlotCards(reviewPayload)
    .filter((item) => String(item?.value || '').trim())
    .map((item) => `${item.label}：${item.value}`)
}

export function buildReviewSlotMap(reviewPayload) {
  return Object.fromEntries(
    (Array.isArray(reviewPayload?.slot_cards) ? reviewPayload.slot_cards : []).map((item) => [item.key, item])
  )
}

export function resolveExpenseTypeCode(value) {
  const normalized = String(value || '').trim()
  if (!normalized) return 'other'
  if (EXPENSE_TYPE_LABELS[normalized]) return normalized
  const matched = Object.entries(EXPENSE_TYPE_LABELS).find(([, label]) => label === normalized)
  return matched?.[0] || 'other'
}

export function isValidIsoDateString(value) {
  const normalized = String(value || '').trim()
  if (!/^\d{4}-\d{2}-\d{2}$/.test(normalized)) {
    return false
  }

  const [yearText, monthText, dayText] = normalized.split('-')
  const year = Number(yearText)
  const month = Number(monthText)
  const day = Number(dayText)

  if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day)) {
    return false
  }

  const candidate = new Date(Date.UTC(year, month - 1, day))
  return (
    candidate.getUTCFullYear() === year &&
    candidate.getUTCMonth() === month - 1 &&
    candidate.getUTCDate() === day
  )
}

export function parseAmountNumber(value) {
  const normalized = String(value || '')
    .replace(/[，,\s]/g, '')
    .replace(/[¥￥]/g, '')
    .replace(/元/g, '')
    .trim()

  if (!normalized || !/^\d+(?:\.\d+)?$/.test(normalized)) {
    return null
  }

  const amount = Number(normalized)
  return Number.isFinite(amount) ? amount : null
}

export function normalizeAmountValue(value) {
  const amount = parseAmountNumber(value)
  if (amount === null) {
    return ''
  }
  return Number.isInteger(amount) ? `${amount}元` : `${amount.toFixed(2).replace(/\.?0+$/, '')}元`
}

export function extractAmountInputValue(value) {
  const amount = parseAmountNumber(value)
  if (amount === null) {
    return String(value || '').trim()
  }
  return Number.isInteger(amount) ? String(amount) : amount.toFixed(2).replace(/\.?0+$/, '')
}

export function formatAmountDisplay(value) {
  const amount = parseAmountNumber(value)
  if (amount === null) {
    return String(value || '').trim()
  }

  return new Intl.NumberFormat('zh-CN', {
    style: 'currency',
    currency: 'CNY',
    minimumFractionDigits: Number.isInteger(amount) ? 0 : 2,
    maximumFractionDigits: Number.isInteger(amount) ? 0 : 2
  }).format(amount)
}

export function matchPresetSceneFromReason(reason) {
  const compactReason = String(reason || '').trim().replace(/\s+/g, '')
  if (!compactReason) {
    return ''
  }
  if (/请客户.*吃饭|客户.*吃饭|招待|宴请|接待|客户接待/.test(compactReason)) {
    return '请客户吃饭'
  }
  if (/出差行程|住宿报销|交通出行|会务活动/.test(compactReason)) {
    const matchedPreset = REVIEW_SCENE_OPTIONS.find((option) => compactReason.includes(option.replace(/\s+/g, '')))
    if (matchedPreset && matchedPreset !== REVIEW_SCENE_OTHER_OPTION) {
      return matchedPreset
    }
  }
  if (/出差|差旅/.test(compactReason)) {
    return '出差行程'
  }
  if (/酒店|住宿/.test(compactReason)) {
    return '住宿报销'
  }
  if (TRANSPORT_KEYWORD_PATTERN.test(compactReason)) {
    return '交通出行'
  }
  if (/会务|会议|参会|论坛|展会/.test(compactReason)) {
    return '会务活动'
  }
  return ''
}

export function mapOcrSceneLabelToPresetScene(sceneLabel, suggestedExpenseType = '') {
  const fromCode = EXPENSE_CODE_TO_PRESET_SCENE[resolveExpenseTypeCode(suggestedExpenseType)]
  if (fromCode) {
    return fromCode
  }

  const compactLabel = String(sceneLabel || '').trim().replace(/\s+/g, '')
  if (!compactLabel) {
    return ''
  }
  if (/差旅|出差/.test(compactLabel)) {
    return '出差行程'
  }
  if (/住宿|酒店/.test(compactLabel)) {
    return '住宿报销'
  }
  if (/交通/.test(compactLabel)) {
    return '交通出行'
  }
  if (/招待|餐饮|餐费|伙食/.test(compactLabel)) {
    return '请客户吃饭'
  }
  if (/会务|会议/.test(compactLabel)) {
    return '会务活动'
  }
  return ''
}

export function mapExpenseTypeLabelToPresetScene(expenseType) {
  const code = resolveExpenseTypeCode(expenseType)
  if (EXPENSE_CODE_TO_PRESET_SCENE[code]) {
    return EXPENSE_CODE_TO_PRESET_SCENE[code]
  }

  const compactLabel = String(expenseType || '').trim().replace(/\s+/g, '')
  if (!compactLabel) {
    return ''
  }
  if (compactLabel.includes('差旅') || compactLabel.includes('出差')) {
    return '出差行程'
  }
  if (compactLabel.includes('住宿') || compactLabel.includes('酒店')) {
    return '住宿报销'
  }
  if (compactLabel.includes('交通')) {
    return '交通出行'
  }
  if (compactLabel.includes('招待') || compactLabel.includes('餐饮') || compactLabel.includes('伙食')) {
    return '请客户吃饭'
  }
  if (compactLabel.includes('会务') || compactLabel.includes('会议')) {
    return '会务活动'
  }
  return matchPresetSceneFromReason(expenseType)
}

export function inferPresetSceneFromReview(reviewPayload, reasonValue = '', expenseType = '') {
  const documents = Array.isArray(reviewPayload?.document_cards) ? reviewPayload.document_cards : []
  if (documents.length) {
    const votes = new Map()
    for (const document of documents) {
      const preset =
        mapOcrSceneLabelToPresetScene(document.scene_label, document.suggested_expense_type)
        || mapExpenseTypeLabelToPresetScene(document.suggested_expense_type)
      if (!preset) {
        continue
      }
      votes.set(preset, (votes.get(preset) || 0) + 1)
    }
    if (votes.size) {
      return [...votes.entries()].sort((left, right) => right[1] - left[1])[0][0]
    }
  }

  const claimGroups = Array.isArray(reviewPayload?.claim_groups) ? reviewPayload.claim_groups : []
  if (claimGroups.length === 1) {
    const group = claimGroups[0]
    const preset =
      mapExpenseTypeLabelToPresetScene(group.expense_type)
      || mapOcrSceneLabelToPresetScene(group.scene_label, group.expense_type)
    if (preset) {
      return preset
    }
  }

  const fromReason = matchPresetSceneFromReason(reasonValue)
  if (fromReason) {
    return fromReason
  }

  const fromExpenseType = mapExpenseTypeLabelToPresetScene(expenseType)
  if (fromExpenseType) {
    return fromExpenseType
  }

  if (String(reasonValue || '').trim()) {
    return REVIEW_SCENE_OTHER_OPTION
  }
  return '待补充'
}

export function formatReviewSceneDisplayValue(inlineState) {
  const scene = String(inlineState?.scene_label || '').trim()
  if (!scene || scene === '待补充') {
    return '待补充'
  }
  if (scene === REVIEW_SCENE_OTHER_OPTION) {
    const detail = String(inlineState?.reason_value || '').trim()
    if (!detail) {
      return REVIEW_SCENE_OTHER_OPTION
    }
    return detail.length > 18 ? `${REVIEW_SCENE_OTHER_OPTION}：${detail.slice(0, 18)}...` : `${REVIEW_SCENE_OTHER_OPTION}：${detail}`
  }
  return scene
}

export function summarizeReviewScene(reason, expenseType = '', reviewPayload = null) {
  return inferPresetSceneFromReview(reviewPayload, reason, expenseType)
}

export function buildInlineReviewState(reviewPayload) {
  const slotMap = buildReviewSlotMap(reviewPayload)
  const editFieldMap = buildReviewEditFieldMap(reviewPayload?.edit_fields)
  const attachmentNames = String(
    editFieldMap.attachment_names?.value ||
      slotMap.attachments?.value ||
      (Array.isArray(reviewPayload?.document_cards) ? reviewPayload.document_cards.map((item) => item.filename).join('、') : '')
  ).trim()
  const attachmentCount = Array.isArray(reviewPayload?.document_cards)
    ? reviewPayload.document_cards.length
    : attachmentNames
      ? attachmentNames.split('、').filter(Boolean).length
      : 0
  const expenseType = String(editFieldMap.expense_type?.value || slotMap.expense_type?.value || '').trim()
  const reasonValue = String(
    editFieldMap.reason?.value || slotMap.reason?.raw_value || slotMap.reason?.value || ''
  ).trim()
  const sceneLabel = inferPresetSceneFromReview(reviewPayload, reasonValue, expenseType)
  const transportType = String(
    editFieldMap.transport_type?.value || resolveReviewTravelTransportType(reviewPayload, reasonValue)
  ).trim()

  return {
    occurred_date: String(
      editFieldMap.occurred_date?.value || slotMap.time_range?.normalized_value || slotMap.time_range?.value || ''
    ).trim(),
    amount: normalizeAmountValue(
      String(editFieldMap.amount?.value || slotMap.amount?.normalized_value || slotMap.amount?.value || '').trim()
    ),
    transport_type: transportType,
    scene_label: sceneLabel,
    reason_value:
      sceneLabel === REVIEW_SCENE_OTHER_OPTION
        ? reasonValue
        : String(slotMap.reason?.raw_value || '').trim() || reasonValue,
    customer_name: String(editFieldMap.customer_name?.value || slotMap.customer_name?.value || '').trim(),
    location: String(
      editFieldMap.business_location?.value ||
        editFieldMap.location?.value ||
        slotMap.location?.normalized_value ||
        slotMap.location?.value ||
        ''
    ).trim(),
    merchant_name: String(editFieldMap.merchant_name?.value || slotMap.merchant_name?.value || '').trim(),
    participants: String(editFieldMap.participants?.value || slotMap.participants?.value || '').trim(),
    attachment_names: attachmentNames,
    attachment_count: attachmentCount,
    pending_attachment_count: 0,
    expense_type: expenseType
  }
}

export function buildReviewAttachmentStatus(reviewPayload) {
  const documents = Array.isArray(reviewPayload?.document_cards) ? reviewPayload.document_cards : []
  if (!documents.length) return '未上传'
  return documents.length === 1 ? '已上传 1 份' : `已上传 ${documents.length} 份`
}

export function shouldShowReviewFactCard(reviewPayload, slotKey, value = '') {
  const slotMap = buildReviewSlotMap(reviewPayload)
  const slot = slotMap[slotKey]
  return Boolean(String(value || slot?.normalized_value || slot?.value || '').trim()) || slot?.status === 'missing'
}

export function buildReviewEvidenceText(reviewPayload, inlineState = createEmptyInlineReviewState()) {
  const slotMap = buildReviewSlotMap(reviewPayload)
  const documents = Array.isArray(reviewPayload?.document_cards) ? reviewPayload.document_cards : []

  return [
    String(inlineState.reason_value || '').trim(),
    String(inlineState.scene_label || '').trim(),
    String(slotMap.reason?.value || slotMap.reason?.raw_value || '').trim(),
    ...documents.map((item) =>
      [item.scene_label, item.summary, item.filename, ...(Array.isArray(item.warnings) ? item.warnings : [])]
        .filter(Boolean)
        .join(' ')
    )
  ]
    .filter(Boolean)
    .join(' ')
    .toLowerCase()
}

export function resolveReviewCategoryTextScore(text, categoryCode) {
  const patterns = CATEGORY_CONFIDENCE_KEYWORDS[categoryCode]
  if (!patterns?.length || !text) {
    return 0
  }
  return patterns.some((pattern) => pattern.test(text))
    ? {
        travel: 0.84,
        hotel: 0.82,
        transport: 0.8,
        meal: 0.76,
        meeting: 0.78,
        entertainment: 0.88,
        office: 0.74,
        training: 0.77,
        communication: 0.7,
        welfare: 0.72
      }[categoryCode] || 0
    : 0
}

export function resolveReviewCategoryDocumentScore(reviewPayload, categoryCode) {
  const documents = Array.isArray(reviewPayload?.document_cards) ? reviewPayload.document_cards : []
  const matchedScores = documents
    .filter((item) => resolveExpenseTypeCode(item?.suggested_expense_type) === categoryCode)
    .map((item) => Number(item?.avg_score || 0))
    .filter((score) => Number.isFinite(score) && score > 0)

  if (!matchedScores.length) {
    return 0
  }

  return matchedScores.reduce((sum, score) => sum + score, 0) / matchedScores.length
}

export function resolveReviewCategoryConfidenceScore(reviewPayload, selectedLabel = '', inlineState = createEmptyInlineReviewState()) {
  const normalizedLabel = String(selectedLabel || '').trim()
  if (!normalizedLabel) {
    return 0
  }

  const selectedCode = resolveExpenseTypeCode(normalizedLabel)
  const slotMap = buildReviewSlotMap(reviewPayload)
  const expenseSlot = slotMap.expense_type
  const recognizedCode = resolveExpenseTypeCode(expenseSlot?.normalized_value || expenseSlot?.value || '')
  let score = 0

  if (recognizedCode === selectedCode) {
    score = Math.max(score, Number(expenseSlot?.confidence || 0))
  }

  score = Math.max(score, resolveReviewCategoryDocumentScore(reviewPayload, selectedCode))
  score = Math.max(score, resolveReviewCategoryTextScore(buildReviewEvidenceText(reviewPayload, inlineState), selectedCode))

  if (!score && normalizedLabel) {
    score = selectedCode === 'other' ? 0.52 : 0.58
  }

  return Math.max(0, Math.min(0.98, Number(score.toFixed(2))))
}

export function buildReviewCategoryOptions(reviewPayload, selectedLabel = '', inlineState = createEmptyInlineReviewState()) {
  const presetLabels = REVIEW_CATEGORY_PRESET_OPTIONS.filter((item) => !item.is_other).map((item) => item.label)
  return REVIEW_CATEGORY_PRESET_OPTIONS.map((item, index) => ({
    ...item,
    active: item.is_other ? Boolean(selectedLabel) && !presetLabels.includes(selectedLabel) : item.label === selectedLabel,
    confidenceLabel: item.is_other
      ? formatConfidenceLabel(resolveReviewCategoryConfidenceScore(reviewPayload, selectedLabel, inlineState))
      : formatConfidenceLabel(resolveReviewCategoryConfidenceScore(reviewPayload, item.label, inlineState)),
    caption: item.is_other
      ? selectedLabel && !presetLabels.includes(selectedLabel)
        ? `${selectedLabel} · ${formatConfidenceLabel(resolveReviewCategoryConfidenceScore(reviewPayload, selectedLabel, inlineState))}`
        : '点击选择更多类型'
      : `置信度 ${formatConfidenceLabel(resolveReviewCategoryConfidenceScore(reviewPayload, item.label, inlineState))}`,
    groupLabel: index === 0 ? '常用' : index < 5 ? '常用' : '更多'
  }))
}

export function buildReviewPanelConfidence(reviewPayload, inlineState = createEmptyInlineReviewState()) {
  return formatConfidenceLabel(
    resolveReviewCategoryConfidenceScore(reviewPayload, inlineState.expense_type, inlineState)
  )
}

export function resolveInlineReviewSlotValue(slotKey, inlineState = createEmptyInlineReviewState()) {
  const state = inlineState || createEmptyInlineReviewState()
  if (slotKey === 'expense_type') return String(state.expense_type || '').trim()
  if (slotKey === 'customer_name') return String(state.customer_name || '').trim()
  if (slotKey === 'time_range') return String(state.occurred_date || '').trim()
  if (slotKey === 'location') return String(state.location || '').trim()
  if (slotKey === 'merchant_name') return String(state.merchant_name || '').trim()
  if (slotKey === 'amount') return String(state.amount || '').trim()
  if (slotKey === 'reason') return String(state.reason_value || state.scene_label || '').trim()
  if (slotKey === 'participants') return String(state.participants || '').trim()
  if (slotKey === 'attachments') {
    return String(state.attachment_names || '').trim()
      || (Number(state.attachment_count || 0) > 0 ? `${Number(state.attachment_count)} 份附件` : '')
      || (Number(state.pending_attachment_count || 0) > 0 ? `${Number(state.pending_attachment_count)} 份待上传附件` : '')
  }
  return ''
}

export function buildLocallySyncedReviewActions(reviewPayload, canProceed) {
  const actions = Array.isArray(reviewPayload?.confirmation_actions)
    ? reviewPayload.confirmation_actions.map((item) => ({ ...item }))
    : []
  const actionTypes = new Set(actions.map((item) => String(item?.action_type || '').trim()))
  const associationPending = actionTypes.has('link_to_existing_draft') || actionTypes.has('create_new_claim_from_documents')

  if (!canProceed || associationPending) {
    return actions
  }

  const syncedActions = actions.filter((item) => String(item?.action_type || '').trim() !== 'next_step')
  if (!syncedActions.some((item) => String(item?.action_type || '').trim() === 'save_draft')) {
    syncedActions.push({
      label: '保存为草稿',
      action_type: 'save_draft',
      description: '先暂存当前已识别信息，稍后仍可继续补充或提交。',
      emphasis: 'secondary'
    })
  }

  return [
    ...syncedActions,
    {
      label: '继续下一步',
      action_type: 'next_step',
      description: '当前信息已齐全，进入 AI 预审、风险校验和审批路径确认。',
      emphasis: 'primary'
    }
  ]
}

export function buildLocallySyncedReviewPayload(reviewPayload, inlineState = createEmptyInlineReviewState()) {
  if (!reviewPayload || typeof reviewPayload !== 'object') {
    return reviewPayload
  }

  const nextSlotCards = (Array.isArray(reviewPayload.slot_cards) ? reviewPayload.slot_cards : []).map((slot) => {
    const value = resolveInlineReviewSlotValue(slot.key, inlineState)
    const required = Boolean(slot.required)
    const filled = Boolean(value)
    return {
      ...slot,
      value: value || slot.value || '',
      normalized_value: value || slot.normalized_value || '',
      raw_value: value || slot.raw_value || '',
      source: filled ? 'user_form' : slot.source,
      source_label: filled ? '用户修改' : slot.source_label,
      confidence: filled ? Math.max(Number(slot.confidence || 0), 0.98) : Number(slot.confidence || 0),
      confirmed: filled || Boolean(slot.confirmed),
      status: required && !filled ? 'missing' : filled ? 'identified' : slot.status,
      hint: required && !filled ? slot.hint : ''
    }
  })
  const missingSlots = nextSlotCards
    .filter((slot) => slot.required && slot.status === 'missing')
    .map((slot) => slot.label || slot.key)
  const extraMissingSlots = resolveReviewExtraMissingLabels({
    ...reviewPayload,
    slot_cards: nextSlotCards
  })
  const allMissingSlots = [...missingSlots, ...extraMissingSlots]
  const canProceed = allMissingSlots.length === 0 && (Array.isArray(reviewPayload.claim_groups) ? reviewPayload.claim_groups.length > 0 : true)

  return {
    ...reviewPayload,
    can_proceed: canProceed,
    missing_slots: allMissingSlots,
    slot_cards: nextSlotCards,
    edit_fields: mergeInlineReviewFields(reviewPayload.edit_fields || [], inlineState),
    confirmation_actions: buildLocallySyncedReviewActions(reviewPayload, canProceed)
  }
}

export function normalizeInlineReviewComparableState(state) {
  const source = state && typeof state === 'object' ? state : {}
  return {
    occurred_date: String(source.occurred_date || '').trim(),
    amount: String(source.amount || '').trim(),
    transport_type: String(source.transport_type || '').trim(),
    scene_label: String(source.scene_label || '').trim(),
    reason_value: String(source.reason_value || '').trim(),
    customer_name: String(source.customer_name || '').trim(),
    location: String(source.location || '').trim(),
    merchant_name: String(source.merchant_name || '').trim(),
    participants: String(source.participants || '').trim(),
    attachment_names: String(source.attachment_names || '').trim(),
    pending_attachment_count: Math.max(0, Number(source.pending_attachment_count || 0)),
    expense_type: String(source.expense_type || '').trim()
  }
}

export function buildInlineReviewChangedLines(baseState, nextState, pendingFiles = []) {
  const base = normalizeInlineReviewComparableState(baseState)
  const next = normalizeInlineReviewComparableState(nextState)
  const lines = []

  if (base.occurred_date !== next.occurred_date) {
    lines.push(`发生时间 ${next.occurred_date || '待补充'}`)
  }
  if (base.amount !== next.amount) {
    lines.push(`金额 ${formatAmountDisplay(next.amount) || '待补充'}`)
  }
  if (base.transport_type !== next.transport_type) {
    lines.push(`交通类型 ${next.transport_type || '待确认'}`)
  }
  if (base.scene_label !== next.scene_label) {
    lines.push(`场景 ${next.scene_label || '待补充'}`)
  }
  if (base.customer_name !== next.customer_name) {
    lines.push(`关联客户 ${next.customer_name || '待补充'}`)
  }
  if (base.location !== next.location) {
    lines.push(`业务地点 ${next.location || '待补充'}`)
  }
  if (base.merchant_name !== next.merchant_name) {
    lines.push(`酒店/商户 ${next.merchant_name || '待补充'}`)
  }
  if (base.participants !== next.participants) {
    lines.push(`同行人员 ${next.participants || '待补充'}`)
  }
  if (base.expense_type !== next.expense_type) {
    lines.push(`报销分类 ${next.expense_type || '待补充'}`)
  }
  if (base.attachment_names !== next.attachment_names || pendingFiles.length) {
    lines.push(`票据 ${next.attachment_names || (pendingFiles.length ? `已选择 ${pendingFiles.length} 份附件` : '待上传')}`)
  }

  return lines
}

export function buildInlineReviewChangePhrases(baseState, nextState, pendingFiles = []) {
  const base = normalizeInlineReviewComparableState(baseState)
  const next = normalizeInlineReviewComparableState(nextState)
  const fieldConfigs = [
    { key: 'occurred_date', label: '发生时间', format: (value) => value || '待补充' },
    { key: 'amount', label: '金额', format: (value) => formatAmountDisplay(value) || '待补充' },
    { key: 'transport_type', label: '交通类型', format: (value) => value || '待确认' },
    { key: 'scene_label', label: '场景', format: (value) => value || '待补充' },
    { key: 'customer_name', label: '关联客户', format: (value) => value || '待补充' },
    { key: 'location', label: '业务地点', format: (value) => value || '待补充' },
    { key: 'merchant_name', label: '酒店/商户', format: (value) => value || '待补充' },
    { key: 'participants', label: '同行人员', format: (value) => value || '待补充' },
    { key: 'expense_type', label: '报销分类', format: (value) => value || '待补充' }
  ]

  const phrases = fieldConfigs.reduce((result, item) => {
    if (base[item.key] !== next[item.key]) {
      result.push(`${item.label}修改为 ${item.format(next[item.key])}`)
    }
    return result
  }, [])

  if (base.attachment_names !== next.attachment_names || pendingFiles.length) {
    phrases.push(`票据修改为 ${next.attachment_names || (pendingFiles.length ? `已选择 ${pendingFiles.length} 份附件` : '待上传')}`)
  }

  return phrases
}

export function buildLocalReviewSavedMessage(baseState, nextState, pendingFiles = [], baseDrafts = [], nextDrafts = []) {
  const phrases = buildInlineReviewChangePhrases(baseState, nextState, pendingFiles)
  const documentLines = buildReviewDocumentCorrectionLines(baseDrafts, nextDrafts)

  if (documentLines.length) {
    phrases.push(`${documentLines.length} 张票据识别信息更新为最新修改`)
  }

  if (!phrases.length) {
    return '右侧核对信息已保存。'
  }

  return `已将${phrases.join('，')}。`
}

export function buildInlineReviewUserText(baseState, nextState, pendingFiles = []) {
  const lines = buildInlineReviewChangedLines(baseState, nextState, pendingFiles)
  if (!lines.length) {
    return '我已校正核对信息，请按最新内容更新。'
  }
  return `我已校正核对信息：${lines.join('，')}。请按最新内容更新。`
}

export function buildReviewSubmitUserText(baseState, nextState, pendingFiles = [], baseDrafts = [], nextDrafts = []) {
  const inlineLines = buildInlineReviewChangedLines(baseState, nextState, pendingFiles)
  const documentLines = buildReviewDocumentCorrectionLines(baseDrafts, nextDrafts)

  if (!inlineLines.length && !documentLines.length) {
    return '我已校正核对信息，请按最新内容更新。'
  }

  const parts = []
  if (inlineLines.length) {
    parts.push(inlineLines.join('，'))
  }
  if (documentLines.length) {
    parts.push(`修正了 ${documentLines.length} 张票据识别信息`)
  }

  return `我已校正核对信息：${parts.join('；')}。请按最新内容更新。`
}

export function mergeInlineReviewFields(baseFields, inlineState) {
  const merged = cloneReviewEditFields(baseFields)
  const updateMap = {
    expense_type: inlineState.expense_type,
    transport_type: inlineState.transport_type,
    occurred_date: inlineState.occurred_date,
    amount: inlineState.amount,
    customer_name: inlineState.customer_name,
    business_location: inlineState.location,
    merchant_name: inlineState.merchant_name,
    participants: inlineState.participants,
    reason: inlineState.reason_value || inlineState.scene_label,
    attachment_names: inlineState.attachment_names
  }

  for (const item of merged) {
    if (!(item.key in updateMap)) continue
    item.value = String(updateMap[item.key] || '').trim()
  }

  return merged
}


function resolvePresentationRiskBriefs(reviewPayload, riskBriefResolver = null) {
  if (typeof riskBriefResolver === 'function') {
    return riskBriefResolver(reviewPayload)
  }
  return Array.isArray(reviewPayload?.risk_briefs) ? reviewPayload.risk_briefs : []
}

export function buildClientTimeContext() {
  const now = new Date()
  const locale =
    typeof navigator !== 'undefined' && typeof navigator.language === 'string'
      ? navigator.language
      : 'zh-CN'

  return {
    client_now_iso: now.toISOString(),
    client_timezone_offset_minutes: now.getTimezoneOffset(),
    client_locale: locale
  }
}



export function formatDraftApplyTime(date = new Date()) {
  const year = date.getFullYear()
  const month = String(date.getMonth() + 1).padStart(2, '0')
  const day = String(date.getDate()).padStart(2, '0')
  const hours = String(date.getHours()).padStart(2, '0')
  const minutes = String(date.getMinutes()).padStart(2, '0')
  return `${year}-${month}-${day} ${hours}:${minutes}`
}



export function formatDateInputValue(date = new Date()) {
  const year = date.getFullYear()
  const month = String(date.getMonth() + 1).padStart(2, '0')
  const day = String(date.getDate()).padStart(2, '0')
  return `${year}-${month}-${day}`
}



export function buildDraftSavedPayload({
  draftPayload,
  reviewPayload,
  inlineState,
  linkedRequest,
  currentUser,
  riskItems = []
}) {
  const documents = Array.isArray(reviewPayload?.document_cards) ? reviewPayload.document_cards : []
  const missingItems = resolveReviewMissingSlotCards(reviewPayload)
  const typeCode = resolveExpenseTypeCode(inlineState?.expense_type)
  const amountNumber = parseAmountNumber(inlineState?.amount)
  const location = String(inlineState?.location || linkedRequest?.city || '').trim()
  const customerName = String(inlineState?.customer_name || '').trim()
  const typeLabel = String(inlineState?.expense_type || linkedRequest?.typeLabel || resolveExpenseTypeLabel(typeCode)).trim()
  const title =
    String(inlineState?.reason_value || '').trim()
    || String(inlineState?.scene_label || '').trim()
    || String(draftPayload?.title || '').trim()
    || `${typeLabel}报销草稿`
  const sceneLabel =
    String(inlineState?.scene_label || summarizeReviewScene(title, typeLabel, reviewPayload)).trim() || typeLabel
  const attachmentSummary = documents.length
    ? `${documents.length} 条识别票据 / ${documents.length} 份材料`
    : String(inlineState?.attachment_names || '').trim()
      ? '1 条识别票据 / 1 份材料'
      : '待上传票据'

  return {
    claimId: String(draftPayload?.claim_id || '').trim(),
    claimNo: String(draftPayload?.claim_no || '').trim(),
    status: String(draftPayload?.status || '').trim(),
    approvalStage: String(draftPayload?.approval_stage || '').trim(),
    person: String(currentUser?.name || '').trim() || '当前用户',
    dept: String(currentUser?.department || currentUser?.departmentName || '').trim() || '待补充部门',
    entity: String(linkedRequest?.entity || '').trim() || 'Northstar China Ltd.',
    typeCode,
    typeLabel,
    detailVariant: typeCode === 'travel' ? 'travel' : 'general',
    title,
    sceneLabel,
    sceneTarget: location || customerName || '待补充',
    location,
    relatedCustomer: customerName,
    occurredDisplay: String(inlineState?.occurred_date || '').trim() || '待补充',
    applyTime: formatDraftApplyTime(),
    amount: amountNumber === null ? 0 : amountNumber,
    secondaryStatusLabel: typeCode === 'travel' ? '行程状态' : '票据状态',
    secondaryStatusValue: documents.length ? '待继续完善' : '待上传票据',
    secondaryStatusTone: documents.length ? 'warning' : 'neutral',
    riskSummary: riskItems[0]?.summary || (missingItems.length ? '仍有识别字段待补充，请继续完善。' : 'AI 已生成草稿，可继续补充后提交。'),
    attachmentSummary,
    expenseTableSummary: documents.length
      ? `已关联 ${documents.length} 份票据，请继续在报销页补充和确认`
      : '当前尚未上传票据，请在报销页继续补充附件',
    note: String(draftPayload?.status || '').trim() === 'submitted'
      ? '该报销单已由 AI 工作台提交审批，可在个人报销页面持续跟踪进度。'
      : '该草稿由 AI 工作台根据当前识别结果生成，可在个人报销页面继续补充明细、票据与说明。'
  }
}


export function countReviewPendingItems(reviewPayload) {
  return resolveReviewMissingSlotCards(reviewPayload).length + resolveReviewExtraMissingLabels(reviewPayload).length
}



export function countReviewRiskItems(reviewPayload, riskBriefResolver = null) {
  return resolvePresentationRiskBriefs(reviewPayload, riskBriefResolver).length
}



export function buildReviewHeadline(reviewPayload) {
  if (countReviewPendingItems(reviewPayload)) {
    return '待补充信息'
  }
  if (reviewPayload?.can_proceed) {
    return '识别结果已整理完成'
  }
  return '识别结果摘要'
}



export function buildReviewSubline(reviewPayload) {
  const pendingCount = countReviewPendingItems(reviewPayload)

  if (pendingCount) {
    return `我已把 ${pendingCount} 项待补充内容整理成文字说明，请先核查。`
  }
  if (reviewPayload?.can_proceed) {
    return '当前关键信息已基本齐全，确认无误后可以继续下一步。'
  }
  return '已为您整理本轮识别结果，请核查当前识别摘要。'
}



export function buildReviewStateLabel(reviewPayload) {
  const pendingCount = countReviewPendingItems(reviewPayload)
  if (pendingCount) return `待补充 ${pendingCount} 项`
  if (reviewPayload?.can_proceed) return '可继续处理'
  return '已识别'
}



export function buildReviewStateTone(reviewPayload) {
  return reviewPayload?.can_proceed && !countReviewPendingItems(reviewPayload)
    ? 'ready'
    : 'pending'
}



export function buildReviewDisclosureTitle(reviewPayload) {
  const pendingCount = countReviewPendingItems(reviewPayload)
  if (pendingCount) {
    return `当前有 ${pendingCount} 项待补充，点击展开查看`
  }
  return '当前信息已齐全，可展开查看识别摘要'
}



export function buildReviewDisclosureHint(reviewPayload) {
  const pendingCount = countReviewPendingItems(reviewPayload)
  if (pendingCount) {
    return '展开后可查看待补充字段和处理建议'
  }
  return '展开后可查看本轮已识别的关键信息'
}



export function shouldOpenReviewDisclosure(reviewPayload) {
  return !countReviewPendingItems(reviewPayload)
}



export function buildReviewTodoSectionTitle(reviewPayload) {
  return countReviewPendingItems(reviewPayload) ? '待补充内容' : '已识别信息'
}



export function buildReviewTodoSectionMeta(reviewPayload) {
  const count = buildReviewTodoItems(reviewPayload).length
  if (countReviewPendingItems(reviewPayload)) {
    return count ? `${count} 项` : '待确认'
  }
  return count ? `${count} 项` : '已齐全'
}



export function buildReviewAlertLabel(slotKey, expenseTypeLabel = '') {
  if (slotKey === 'customer_name') {
    return expenseTypeLabel === '业务招待费' ? '业务招待费需补充关联客户' : '缺少关联客户'
  }
  if (slotKey === 'participants') return '缺少同行人员'
  if (slotKey === 'attachments') return '票据状态待补充'
  if (slotKey === 'amount') return '金额待确认'
  if (slotKey === 'time_range') return '发生时间待确认'
  if (slotKey === 'reason') return '场景 / 事由待补充'
  if (slotKey === 'expense_type') return '报销类型待确认'
  if (slotKey === 'location') return '业务地点待补充'
  if (slotKey === 'merchant_name') return '酒店/商户待补充'
  return '仍有信息待补充'
}



export function buildReviewAlertChips(reviewPayload, riskBriefResolver = null) {
  const slotMap = buildReviewSlotMap(reviewPayload)
  const expenseTypeLabel = String(slotMap.expense_type?.value || '').trim()
  const chips = []

  for (const item of resolveReviewMissingSlotCards(reviewPayload).slice(0, 3)) {
    chips.push({
      key: item.key,
      label: buildReviewAlertLabel(item.key, expenseTypeLabel),
      tone: 'warning'
    })
  }

  if (chips.length < 3) {
    for (const label of resolveReviewExtraMissingLabels(reviewPayload)) {
      chips.push({
        key: label,
        label,
        tone: 'warning'
      })
      if (chips.length >= 3) break
    }
  }

  if (chips.length < 3) {
    for (const risk of resolvePresentationRiskBriefs(reviewPayload, riskBriefResolver)) {
      if (chips.some((item) => item.label === risk.title)) continue
      chips.push({
        key: risk.title,
        label: risk.title,
        tone: risk.level === 'high' ? 'danger' : 'warning'
      })
      if (chips.length >= 3) break
    }
  }

  if (!chips.length && reviewPayload?.can_proceed) {
    chips.push({
      key: 'ready',
      label: '当前识别信息已可继续处理',
      tone: 'success'
    })
  }

  return chips
}



export function buildReviewTodoItems(reviewPayload) {
  const missingItems = resolveReviewMissingSlotCards(reviewPayload)
  const extraMissingLabels = resolveReviewExtraMissingLabels(reviewPayload)
  if (missingItems.length || extraMissingLabels.length) {
    return [
      ...missingItems.map((item) => {
      const config = REVIEW_SLOT_CONFIG[item.key] || {}
      return {
        key: item.key,
        icon: config.icon || 'mdi mdi-form-select',
        title: config.title || item.label,
        hint: item.hint || config.hint || `请补充${item.label}`,
        status: config.status || '待补充',
        tone: 'warning'
      }
    }),
      ...extraMissingLabels.map((label, index) => ({
        key: `extra-missing-${index}-${label}`,
        icon: label.includes('酒店') || label.includes('住宿') ? 'mdi mdi-bed-outline' : 'mdi mdi-file-alert-outline',
        title: label,
        hint: label.includes('必须')
          ? '该票据属于当前差旅提交的必备材料，补齐后才能继续下一步。'
          : '可以继续补充该材料；如暂时没有，也可以按当前信息处理。',
        status: label.includes('必须') ? '必须补齐' : '可选补充',
        tone: 'warning'
      }))
    ]
  }

  return resolveReviewRecognizedSlotCards(reviewPayload)
    .filter((item) => String(item?.value || '').trim())
    .slice(0, 3)
    .map((item) => {
      const config = REVIEW_SLOT_CONFIG[item.key] || {}
      return {
        key: item.key,
        icon: config.icon || 'mdi mdi-check-circle-outline',
        title: config.title || item.label,
        hint: `已识别：${item.value}`,
        status: '已识别',
        tone: 'ready'
      }
    })
}



const REVIEW_PENDING_HINT_COPY = {
  expense_type: '请选择本次报销分类，后续票据会按这个分类继续核对。',
  customer_name: '请补充客户单位全称。',
  time_range: '请补充业务发生日期或时间范围。',
  location: '请补充业务发生地点。',
  merchant_name: '请补充酒店或商户名称。',
  amount: '请补充本次费用金额。',
  reason: '请补充本次费用场景或事由。',
  participants: '请至少填写 1 名同行人员。',
  attachments: '请上传或关联对应票据附件。'
}

function normalizeReviewFollowupSentence(text) {
  const normalized = String(text || '')
    .replace(/^已识别[：:]\s*/, '')
    .replace(/^建议补充\s*/, '请补充')
    .replace(/\s+/g, ' ')
    .trim()
  if (!normalized) return ''
  return /[。！？.!?]$/.test(normalized) ? normalized : `${normalized}。`
}

function buildReviewPlainFollowupItem(item, pendingMode) {
  const key = String(item?.key || '').trim()
  const label = String(item?.title || item?.label || '').trim() || '待核查信息'
  if (pendingMode) {
    return {
      key: key || label,
      label,
      text: normalizeReviewFollowupSentence(REVIEW_PENDING_HINT_COPY[key] || item?.hint || `请补充${label}`)
    }
  }

  const value = normalizeReviewFollowupSentence(item?.hint || '')
  return {
    key: key || label,
    label,
    text: value || '已识别，请核查是否准确。'
  }
}

const REVIEW_PENDING_SUMMARY_TEMPLATES = [
  ({ issueSummary }) => `当前还有 ${issueSummary}。请核查对话中的文字说明；如果想先暂存，也可以点击对话文字中的“草稿”。`,
  ({ issueSummary }) => `我这边看到还有 ${issueSummary}，建议先把下方内容核对一下；暂时不处理也没关系，可以点击“草稿”先保存。`,
  ({ issueSummary }) => `下方还有 ${issueSummary}，需要你确认。信息没补齐前可以先核查说明，后续需要暂存时点“草稿”。`,
  ({ issueSummary }) => `这笔报销还有 ${issueSummary}，尚未完全确认。请先看一下下面的补充项；需要中途保存时，可以点“草稿”。`,
  ({ issueSummary }) => `目前还有 ${issueSummary}。你可以先按下面的提示补充，也可以稍后再处理，点击“草稿”即可暂存当前信息。`,
  ({ issueSummary }) => `还有 ${issueSummary}，建议先核对下面说明；如果票据或金额暂时不全，可以通过“草稿”保留当前进度。`,
  ({ issueSummary }) => `这次识别结果里还有 ${issueSummary}。请重点看下面几项，暂不提交时可以点“草稿”保存。`,
  ({ issueSummary }) => `我还需要你确认 ${issueSummary}。下面列出了具体内容；如果现在不方便补齐，可以先点“草稿”。`,
  ({ issueSummary }) => `当前还有 ${issueSummary}，需要进一步处理。请根据下面提示核查，待补充完再继续；临时保存可点击“草稿”。`,
  ({ issueSummary }) => `本次报销还有 ${issueSummary}，请先检查下面的补充项；想先留存当前识别结果时可以点“草稿”。`
]

const REVIEW_SAVED_DRAFT_PENDING_SUMMARY_TEMPLATES = [
  ({ issueSummary }) => `当前还有 ${issueSummary}。草稿已保存，后续上传票据时请关联这张草稿，补齐后再继续提交审批。`,
  ({ issueSummary }) => `这张草稿仍有 ${issueSummary} 需要补充。您可以继续上传或关联票据，系统会归集到已保存草稿中。`,
  ({ issueSummary }) => `草稿已生成，当前还差 ${issueSummary}。请按下方提示补充字段或票据，完整后再进入下一步。`,
  ({ issueSummary }) => `草稿已经留存，下面还有 ${issueSummary} 待处理。新增附件请关联当前草稿，避免重复建单。`,
  ({ issueSummary }) => `当前草稿还有 ${issueSummary}。建议先补齐金额、票据等信息，再从草稿详情继续提交审批。`,
  ({ issueSummary }) => `已保留当前进度，这笔草稿还需要 ${issueSummary}。后续补充内容会作为该草稿的更新处理。`,
  ({ issueSummary }) => `这张单据已进入草稿状态，仍有 ${issueSummary}。请继续补充必要信息，补齐后再发起正式提交。`,
  ({ issueSummary }) => `草稿保存完成后，当前还剩 ${issueSummary}。上传附件时请选择关联这张草稿，系统会继续合并识别结果。`,
  ({ issueSummary }) => `当前草稿待完善：${issueSummary}。请先处理下方项目，确认完整后再继续下一步。`,
  ({ issueSummary }) => `这笔草稿还存在 ${issueSummary}。可以继续补充票据和字段，系统会围绕已保存草稿继续更新。`
]

function buildStableTemplateIndex(signature, total) {
  const source = String(signature || '')
  let hash = 0
  for (let index = 0; index < source.length; index += 1) {
    hash = ((hash << 5) - hash + source.charCodeAt(index)) >>> 0
  }
  return total ? hash % total : 0
}

function buildReviewPendingSummary(pendingCount, riskCount, signature = '', options = {}) {
  const issueParts = []
  if (pendingCount) {
    issueParts.push(`${pendingCount} 项信息待补充`)
  }
  if (riskCount) {
    issueParts.push(`${riskCount} 条风险提醒`)
  }
  const issueSummary = issueParts.length ? issueParts.join('、') : '一些细节还需要进一步确认'
  const templates = options.savedDraft
    ? REVIEW_SAVED_DRAFT_PENDING_SUMMARY_TEMPLATES
    : REVIEW_PENDING_SUMMARY_TEMPLATES
  const templateIndex = buildStableTemplateIndex(signature || issueSummary, templates.length)
  return templates[templateIndex]({ issueSummary })
}

export function buildReviewPlainFollowupCopy(reviewPayload, options = {}) {
  const savedDraft = Boolean(options?.savedDraft)
  const todoItems = buildReviewTodoItems(reviewPayload)
  const pendingCount = countReviewPendingItems(reviewPayload)
  const riskBriefs = resolvePresentationRiskBriefs(reviewPayload)
  const extraMissingCount = resolveReviewExtraMissingLabels(reviewPayload).length

  if (pendingCount || extraMissingCount) {
    const summarySignature = [
      pendingCount || extraMissingCount,
      riskBriefs.length,
      ...todoItems.map((item) => `${item.key}:${item.title}:${item.status}`)
    ].join('|')
    return {
      lead: '补充信息：',
      tone: 'danger',
      summary: buildReviewPendingSummary(pendingCount || extraMissingCount, riskBriefs.length, summarySignature, {
        savedDraft
      }),
      items: todoItems.map((item) => buildReviewPlainFollowupItem(item, true)),
      notes: []
    }
  }

  return {
    lead: todoItems.length ? '我已整理出当前识别到的关键信息：' : '当前关键信息已基本整理完成。',
    tone: 'neutral',
    summary: '',
    items: todoItems.map((item) => buildReviewPlainFollowupItem(item, false)),
    notes: [
      reviewPayload?.can_proceed ? '确认无误后，可以继续下一步。' : '',
      riskBriefs.length ? `系统同时保留了 ${riskBriefs.length} 条风险提醒，请在提交前核查。` : ''
    ].filter(Boolean)
  }
}



export function resolveReviewPrimaryAction(reviewPayload) {
  return (
    (Array.isArray(reviewPayload?.confirmation_actions) ? reviewPayload.confirmation_actions : []).find(
      (item) => item.emphasis === 'primary' || ['save_draft', 'next_step'].includes(String(item?.action_type || ''))
    ) || null
  )
}



export function resolveReviewSaveDraftAction(reviewPayload) {
  return (
    (Array.isArray(reviewPayload?.confirmation_actions) ? reviewPayload.confirmation_actions : []).find(
      (item) => String(item?.action_type || '') === 'save_draft'
    ) || null
  )
}



export function resolveReviewFooterActions(reviewPayload) {
  return (Array.isArray(reviewPayload?.confirmation_actions) ? reviewPayload.confirmation_actions : []).filter((item) => {
    const actionType = String(item?.action_type || '').trim()
    return ['link_to_existing_draft', 'create_new_claim_from_documents'].includes(actionType)
  })
}



export function buildReviewRiskLevelCounts(reviewPayload) {
  return (Array.isArray(reviewPayload?.risk_briefs) ? reviewPayload.risk_briefs : []).reduce(
    (counts, item) => {
      const level = normalizeReviewRiskLevel(item?.level)
      if (level === 'high' || level === 'medium' || level === 'low') {
        counts[level] += 1
      }
      return counts
    },
    { low: 0, medium: 0, high: 0 }
  )
}



export function resolveReviewNextStepAction(reviewPayload) {
  return (
    (Array.isArray(reviewPayload?.confirmation_actions) ? reviewPayload.confirmation_actions : []).find(
      (item) => String(item?.action_type || '').trim() === 'next_step'
    ) || null
  )
}



export function buildReviewNextStepRichCopy(reviewPayload, { detailHref = '' } = {}) {
  const nextStepAction = resolveReviewNextStepAction(reviewPayload)
  if (!nextStepAction) {
    return ''
  }

  const counts = buildReviewRiskLevelCounts(reviewPayload)
  const riskSummary = `现存在 ${counts.low} 条低风险，${counts.medium} 条中风险，${counts.high} 条高风险，具体情况请看 [右侧](#review-risk-panel) 风险信息提示窗。`
  const lines = [`系统识别您的单据已经填写完所有已知信息，${riskSummary}`]

  if (reviewPayload?.can_proceed && counts.medium === 0 && counts.high === 0) {
    const editHref = String(detailHref || '').trim() || '#review-quick-edit'
    lines.push(
      `系统确认您可以 [继续下一步](#review-next-step) 进行单据的提交，如果您确认信息无误，请点击富文本按钮；如果你还需要继续修改信息，请点击 [快速修改单据信息](${editHref})。`
    )
  }
  return lines.join('\n\n')
}



export function buildReviewPrimaryButtonLabel(reviewPayload, draftPayload) {
  const action = resolveReviewPrimaryAction(reviewPayload)
  if (!action) return '确认'
  if (action.action_type === 'save_draft') {
    return draftPayload?.claim_no ? '保存为草稿' : '保存为草稿'
  }
  if (action.action_type === 'next_step') {
    return '继续下一步'
  }
  if (action.action_type === 'link_to_existing_draft') {
    return action.label || '关联到现有草稿'
  }
  if (action.action_type === 'create_new_claim_from_documents') {
    return action.label || '单独建立报销单'
  }
  return action.label || '确认'
}



export function buildReviewIntentText(reviewPayload) {
  const slotMap = buildReviewSlotMap(reviewPayload)
  const expenseType = String(slotMap.expense_type?.value || '').trim()
  if (expenseType) {
    return `报销一笔${expenseType}`
  }
  return '发起一笔报销'
}



export function buildReviewSceneValue(reviewPayload) {
  const slotMap = buildReviewSlotMap(reviewPayload)
  const reason = String(slotMap.reason?.raw_value || slotMap.reason?.value || '').trim()
  const expenseType = String(slotMap.expense_type?.value || slotMap.expense_type?.normalized_value || '').trim()
  return inferPresetSceneFromReview(reviewPayload, reason, expenseType)
}



export function buildMissingRiskLine(slotKey, expenseTypeLabel = '') {
  if (slotKey === 'customer_name') {
    return expenseTypeLabel === '业务招待费'
      ? '业务招待费需补充客户单位名称，以便进行合规校验。'
      : '当前仍缺少客户单位名称，建议补充后再提交。'
  }
  if (slotKey === 'participants') {
    return '缺少同行人员信息，建议补充至少 1 名。'
  }
  if (slotKey === 'attachments') {
    return '尚未上传票据附件，当前无法完成票据核对。'
  }
  if (slotKey === 'amount') {
    return '报销金额仍待确认，提交前需补齐金额信息。'
  }
  if (slotKey === 'time_range') {
    return '业务发生时间仍待确认，建议补充准确日期。'
  }
  if (slotKey === 'reason') {
    return '报销事由说明仍不完整，建议补充业务背景。'
  }
  return '当前仍有识别信息待补充，建议先核对后再处理。'
}



export function buildReviewRiskSummary(reviewPayload, riskBriefResolver = null) {
  if (resolvePresentationRiskBriefs(reviewPayload, riskBriefResolver).length) {
    return '当前识别到了风险提示，点击任一风险点会在主对话中展开规则依据和整改建议。'
  }
  return '当前没有需要额外处理的结构化风险点。'
}



export function normalizeReviewRiskLevel(level) {
  const normalized = String(level || '').trim().toLowerCase()
  if (normalized === 'danger' || normalized === 'error' || normalized === 'critical') return 'high'
  if (normalized === 'warn' || normalized === 'warning' || normalized === 'medium') return 'medium'
  if (normalized === 'info' || normalized === 'notice') return 'info'
  if (normalized === 'low') return 'low'
  if (normalized === 'high') return normalized
  return 'low'
}



export function buildLocalReviewCompletionMessage(reviewPayload) {
  const missingSlots = Array.isArray(reviewPayload?.missing_slots) ? reviewPayload.missing_slots : []
  if (reviewPayload?.can_proceed && !missingSlots.length) {
    return '当前所有必填信息已处理完成，可以点击“继续下一步”进入 AI 预审。'
  }
  if (missingSlots.length) {
    return `当前还剩 ${missingSlots.length} 项待补充：${missingSlots.join('、')}。`
  }
  return '当前信息已保存，可以继续核对右侧状态。'
}



export function buildReviewRecognitionNotes(reviewPayload) {
  const recognized = resolveReviewRecognizedSlotCards(reviewPayload)
  const notes = []
  const timeSlot = recognized.find((item) => item.key === 'time_range')
  const sourceLabels = [...new Set(recognized.map((item) => String(item?.source_label || '').trim()).filter(Boolean))]

  if (timeSlot?.raw_value && timeSlot.raw_value !== timeSlot.value && timeSlot.value) {
    notes.push(`时间已按你的本地日期换算：${timeSlot.raw_value} -> ${timeSlot.value}`)
  }

  if (sourceLabels.length) {
    notes.push(`本轮主要依据：${sourceLabels.join('、')}`)
  }

  const documentCards = Array.isArray(reviewPayload?.document_cards) ? reviewPayload.document_cards : []
  if (documentCards.length) {
    notes.push(`已关联 ${documentCards.length} 份附件，逐张识别结果已整理在下方`)
  } else {
    notes.push('当前还没有上传票据，这一轮主要依据你的文字描述完成初步识别')
  }

  return notes
}



export function buildReviewMissingHint(reviewPayload) {
  if (!countReviewPendingItems(reviewPayload)) {
    return ''
  }
  if (reviewPayload?.can_proceed) {
    return '当前关键信息已经齐全，这里无需再补充。'
  }
  return '下面这些字段还需要你再确认或补齐，补完后我就能继续往下处理。'
}



export function buildReviewRiskHint(reviewPayload, riskBriefResolver = null) {
  const riskBriefs = resolvePresentationRiskBriefs(reviewPayload, riskBriefResolver)
  if (!riskBriefs.length) {
    return ''
  }
  return '这些是我根据当前单据信息、票据识别结果和规则口径给出的风险提示，提交前建议顺手核对一下。'
}



export function buildReviewActionHint(reviewPayload) {
  if (reviewPayload?.can_proceed) {
    return '如果识别无误，可以继续下一步；如果有偏差，请直接在右侧核对信息中修改。'
  }
  return '如果现在信息还不完整，可以先保存草稿；识别错了请直接在右侧核对信息中修改。'
}



export function buildReviewStatusTag(reviewPayload) {
  const missingCount = countReviewPendingItems(reviewPayload)
  if (reviewPayload?.can_proceed) {
    return '可继续处理'
  }
  if (missingCount > 0) {
    return `待补充 ${missingCount} 项`
  }
  return '待确认'
}
