import {
  REIMBURSEMENT_LIST_PREVIEW_PARAMS,
  fetchExpenseClaims
} from '../../services/reimbursements.js'
import { runOrchestrator } from '../../services/orchestrator.js'
import {
  createLinkedReimbursementDraftJob,
  fetchLinkedReimbursementDraftJob
} from '../../services/linkedReimbursementDraftJobs.js'
import {
  applyAiExpenseAnswer,
  buildAiExpenseDraftPrefillValues,
  buildAiExpenseStepPrompt,
  buildAiExpenseSummary,
  createAiExpenseDraft,
  isAiExpenseDraftComplete
} from '../../utils/aiExpenseDraftModel.js'
import {
  buildExpenseSceneSelectionMessage,
  SESSION_TYPE_EXPENSE
} from '../../views/scripts/travelReimbursementConversationModel.js'
import { buildExpenseSceneSelectionActions } from '../../utils/expenseAssistantActions.js'
import {
  buildRequiredApplicationActions,
  buildRequiredApplicationMissingText,
  buildRequiredApplicationSelectionText,
  filterRequiredApplicationCandidates,
  resolveRequiredApplicationReimbursementType
} from '../../views/scripts/travelReimbursementApplicationLinkModel.js'
import {
  buildReimbursementAssociationActions,
  buildReimbursementAssociationMissingText,
  buildReimbursementAssociationSubmitOptions,
  buildReimbursementAssociationThinkingEvents,
  buildReimbursementAssociationSelectionText,
  buildReimbursementAssociationQueryFailedText,
  buildReimbursementDraftActions,
  buildReimbursementDraftContinuationText,
  buildReimbursementDraftSelectionText,
  buildStandaloneReimbursementDraftConfirmationActions,
  buildStandaloneReimbursementDraftConfirmationText,
  buildViewReimbursementDraftAction,
  fetchReimbursementAssociationClaims,
  filterReimbursementAssociationCandidates,
  filterReimbursementDraftCandidates,
  REIMBURSEMENT_ASSOCIATION_QUERY_TIMEOUT_MS
} from '../../views/scripts/travelReimbursementAssociationGateModel.js'

export { SESSION_TYPE_EXPENSE }

const AI_REIMBURSEMENT_ASSOCIATION_STEP_DELAY_MS = 320
const LINKED_DRAFT_JOB_POLL_INTERVAL_MS = 1200
const LINKED_DRAFT_JOB_MAX_POLLS = 100
const LINKED_DRAFT_JOB_PENDING_STATUSES = new Set(['queued', 'running'])
const LINKED_DRAFT_RUNNING_PHRASE = '正在后台生成报销草稿'

function waitForReimbursementAssociationStep() {
  return new Promise((resolve) => {
    globalThis.setTimeout(resolve, AI_REIMBURSEMENT_ASSOCIATION_STEP_DELAY_MS)
  })
}

export function buildLinkedDraftRunningText(job = {}, claimNo = '') {
  const statusText = String(job?.message || '').trim()
  const shouldShowStatusText = Boolean(
    statusText && !statusText.includes(LINKED_DRAFT_RUNNING_PHRASE)
  )
  return [
    `已关联申请单${claimNo ? ` ${claimNo}` : ''}，正在后台生成报销草稿...`,
    shouldShowStatusText ? '' : null,
    shouldShowStatusText ? `处理状态：${statusText}` : null,
    '',
    '您可以先离开当前会话，回来后我会继续查询任务结果。'
  ].filter((line) => line !== null).join('\n')
}

export function useWorkbenchAiExpenseFlow({
  activateInlineConversation,
  aiExpenseDraft,
  assistantDraft,
  clearAiModeFiles,
  closeWorkbenchDatePicker,
  conversationMessages,
  conversationStarted,
  createInlineMessage,
  currentUser,
  persistCurrentConversation,
  pushInlineUserMessage,
  replaceInlineMessage = (id, nextMessage) => {
    const index = conversationMessages.value.findIndex((item) => item.id === id)
    if (index === -1) {
      conversationMessages.value.push(nextMessage)
      return
    }
    conversationMessages.value.splice(index, 1, nextMessage)
  },
  removeWorkbenchDateTag,
  resolveLatestInlineUserPrompt,
  scrollInlineConversationToBottom,
  startAiApplicationPreview,
  fetchExpenseClaimsForAi = fetchExpenseClaims,
  runOrchestratorForAi = runOrchestrator,
  createLinkedReimbursementDraftJobForAi = createLinkedReimbursementDraftJob,
  fetchLinkedReimbursementDraftJobForAi = fetchLinkedReimbursementDraftJob,
  linkedDraftJobPollIntervalMs = LINKED_DRAFT_JOB_POLL_INTERVAL_MS,
  linkedDraftJobMaxPolls = LINKED_DRAFT_JOB_MAX_POLLS,
  associationQueryTimeoutMs = REIMBURSEMENT_ASSOCIATION_QUERY_TIMEOUT_MS
}) {
  function replaceInlineAssistantMessage(messageId, content = '', options = {}) {
    const nextMessage = createInlineMessage('assistant', content, {
      id: messageId,
      pending: Boolean(options.pending),
      stewardPlan: options.stewardPlan || null,
      suggestedActions: Array.isArray(options.suggestedActions) ? options.suggestedActions : [],
      draftPayload: options.draftPayload || null,
      linkedReimbursementDraftJob: options.linkedReimbursementDraftJob || null,
      stewardRemainingTasks: Array.isArray(options.stewardRemainingTasks) ? options.stewardRemainingTasks : [],
      text: options.text || content
    })
    replaceInlineMessage(messageId, nextMessage)
    return nextMessage
  }

  function pushInlineExpenseSceneSelectionPrompt(originalMessage, selectedLabel = '') {
    const sourceText = String(originalMessage || '我要报销').trim()
    if (!conversationStarted.value) {
      activateInlineConversation({
        title: String(selectedLabel || sourceText || '报销').trim().slice(0, 18) || '报销'
      })
    }
    assistantDraft.value = ''
    removeWorkbenchDateTag()
    closeWorkbenchDatePicker()
    conversationMessages.value.push(createInlineMessage('user', String(selectedLabel || sourceText).trim()))
    conversationMessages.value.push(createInlineMessage('assistant', buildExpenseSceneSelectionMessage(sourceText), {
      suggestedActions: buildExpenseSceneSelectionActions(sourceText)
    }))
    persistCurrentConversation()
    scrollInlineConversationToBottom()
  }

  function startAiApplicationPreviewFromAction(payload = {}, fallbackLabel = '') {
    const expenseType = String(payload.expense_type || '').trim()
    const expenseTypeLabel = String(payload.expense_type_label || fallbackLabel || '').trim()
    return startAiApplicationPreview(
      expenseType,
      expenseTypeLabel,
      payload.carry_text || resolveLatestInlineUserPrompt(),
      {
        stewardRemainingTasks: Array.isArray(payload.steward_remaining_tasks)
          ? payload.steward_remaining_tasks
          : []
      }
    )
  }

  function normalizeDraftActionPayload(payload = {}) {
    return {
      id: String(payload.id || payload.claim_id || payload.claimId || '').trim(),
      claim_no: String(payload.claim_no || payload.claimNo || '').trim(),
      original_message: String(payload.original_message || payload.originalMessage || '我要报销').trim() || '我要报销'
    }
  }

  function pushPromptConversationUserMessage(text = '') {
    const normalizedText = String(text || '').trim()
    if (normalizedText) {
      pushInlineUserMessage(normalizedText)
    }
  }

  function promptAiReimbursementDraftContinuation(payload = {}) {
    const draft = normalizeDraftActionPayload(payload)
    const claimNo = draft.claim_no || '当前草稿'
    if (!conversationStarted.value) {
      activateInlineConversation({
        title: `继续草稿 ${claimNo}`.trim().slice(0, 18) || '继续草稿'
      })
    }
    assistantDraft.value = ''
    closeWorkbenchDatePicker()
    pushPromptConversationUserMessage(`继续关联草稿 ${claimNo}`)
    conversationMessages.value.push(createInlineMessage('assistant', buildReimbursementDraftContinuationText(draft), {
      meta: ['等待上传附件或说明'],
      suggestedActions: [buildViewReimbursementDraftAction(draft, draft.original_message)]
    }))
    persistCurrentConversation()
    scrollInlineConversationToBottom()
  }

  function promptStandaloneReimbursementDraftCreation(originalMessage = '我要报销', selectedLabel = '独立新建报销单') {
    const sourceText = String(originalMessage || '我要报销').trim() || '我要报销'
    const userText = String(selectedLabel || '独立新建报销单').trim() || '独立新建报销单'
    if (!conversationStarted.value) {
      activateInlineConversation({
        title: userText.slice(0, 18) || '新建报销'
      })
    }
    assistantDraft.value = ''
    closeWorkbenchDatePicker()
    pushPromptConversationUserMessage(userText)
    conversationMessages.value.push(createInlineMessage('assistant', buildStandaloneReimbursementDraftConfirmationText(), {
      meta: ['等待确认新建草稿'],
      suggestedActions: buildStandaloneReimbursementDraftConfirmationActions(sourceText)
    }))
    persistCurrentConversation()
    scrollInlineConversationToBottom()
  }

  function cancelStandaloneReimbursementDraftCreation() {
    conversationMessages.value.push(createInlineMessage('assistant', '好的，本次先不新建报销草稿。您可以继续查看已有草稿，或补充新的报销说明。', {
      meta: ['已取消新建']
    }))
    persistCurrentConversation()
    scrollInlineConversationToBottom()
  }

  async function startAiReimbursementAssociationGate(originalMessage = '我要报销', selectedLabel = '', options = {}) {
    const sourceText = String(originalMessage || '我要报销').trim() || '我要报销'
    if (!conversationStarted.value) {
      activateInlineConversation({
        title: String(selectedLabel || sourceText || '报销').trim().slice(0, 18) || '报销'
      })
    }
    assistantDraft.value = ''
    removeWorkbenchDateTag()
    closeWorkbenchDatePicker()
    clearAiModeFiles()
    aiExpenseDraft.value = null
    pushInlineUserMessage(String(selectedLabel || sourceText).trim())
    const pendingMessage = createInlineMessage('assistant', '', {
      pending: true,
      stewardPlan: {
        streamStatus: 'streaming',
        thinkingEvents: buildReimbursementAssociationThinkingEvents('intent')
      },
      suggestedActions: []
    })
    conversationMessages.value.push(pendingMessage)
    const pendingMessageId = pendingMessage.id
    persistCurrentConversation()
    scrollInlineConversationToBottom()
    await waitForReimbursementAssociationStep()

    replaceInlineAssistantMessage(pendingMessageId, '', {
      pending: true,
      stewardPlan: {
        streamStatus: 'streaming',
        thinkingEvents: buildReimbursementAssociationThinkingEvents('query')
      },
      suggestedActions: []
    })
    scrollInlineConversationToBottom()

    let claims = null
    try {
      claims = await fetchReimbursementAssociationClaims({
        fetchExpenseClaims: fetchExpenseClaimsForAi,
        timeoutMs: associationQueryTimeoutMs
      })
    } catch (error) {
      replaceInlineAssistantMessage(pendingMessageId, buildReimbursementAssociationQueryFailedText(error), {
        stewardPlan: {
          streamStatus: 'failed',
          thinkingEvents: buildReimbursementAssociationThinkingEvents('failed')
        },
        suggestedActions: buildReimbursementAssociationActions([], sourceText)
      })
      persistCurrentConversation()
      scrollInlineConversationToBottom()
      return
    }

    const draftCandidates = options.skipDraftCheck
      ? []
      : filterReimbursementDraftCandidates(claims, currentUser.value || {})
    if (draftCandidates.length) {
      replaceInlineAssistantMessage(pendingMessageId, '', {
        pending: true,
        stewardPlan: {
          streamStatus: 'streaming',
          thinkingEvents: buildReimbursementAssociationThinkingEvents('filter', { candidateCount: draftCandidates.length })
        },
        suggestedActions: []
      })
      scrollInlineConversationToBottom()
      await waitForReimbursementAssociationStep()

      const content = buildReimbursementDraftSelectionText(draftCandidates)
      replaceInlineAssistantMessage(pendingMessageId, content, {
        stewardPlan: {
          streamStatus: 'completed',
          thinkingEvents: buildReimbursementAssociationThinkingEvents('completed', { candidateCount: draftCandidates.length })
        },
        suggestedActions: buildReimbursementDraftActions(draftCandidates, sourceText)
      })
      persistCurrentConversation()
      scrollInlineConversationToBottom()
      return
    }

    const candidates = filterReimbursementAssociationCandidates(claims, currentUser.value || {})
    replaceInlineAssistantMessage(pendingMessageId, '', {
      pending: true,
      stewardPlan: {
        streamStatus: 'streaming',
        thinkingEvents: buildReimbursementAssociationThinkingEvents('filter', { candidateCount: candidates.length })
      },
      suggestedActions: []
    })
    scrollInlineConversationToBottom()
    await waitForReimbursementAssociationStep()

    const content = candidates.length
      ? buildReimbursementAssociationSelectionText(candidates)
      : buildReimbursementAssociationMissingText()
    replaceInlineAssistantMessage(pendingMessageId, content, {
      stewardPlan: {
        streamStatus: 'completed',
        thinkingEvents: buildReimbursementAssociationThinkingEvents('completed', { candidateCount: candidates.length })
      },
      suggestedActions: buildReimbursementAssociationActions(candidates, sourceText)
    })
    persistCurrentConversation()
    scrollInlineConversationToBottom()
  }

  // 多 task 推进时,把当前报销流程后续要处理的剩余 task 挂在 draft 上,
  // 这样关联申请单、发起申请单、生成报销草稿等子流程都能把 remaining tasks 透传下去,
  // 保证 task2 完成后能继续 task3。draft 被清空时上下文也随之消失。
  function attachStewardRemainingTasks(draft, stewardRemainingTasks) {
    if (!draft) {
      return draft
    }
    const tasks = Array.isArray(stewardRemainingTasks) ? stewardRemainingTasks : []
    draft.stewardRemainingTasks = tasks
    return draft
  }

  function resolveStewardRemainingTasks(draft) {
    const draftTasks = Array.isArray(draft?.stewardRemainingTasks) ? draft.stewardRemainingTasks : []
    return draftTasks.length ? draftTasks : null
  }

  // 把 expenseType 解析成"发起 XX 申请"按钮里的 XX,避免对招待费也显示"出差申请"。
  function resolveRequiredApplicationLabel(expenseType, fallbackLabel = '') {
    const normalized = String(expenseType || '').trim().toLowerCase()
    if (normalized === 'meal' || normalized === 'entertainment') {
      return '业务招待'
    }
    if (normalized === 'travel') {
      return '出差'
    }
    const label = String(fallbackLabel || '').trim()
    if (label) {
      return label.replace(/费$/, '')
    }
    return '费用'
  }

  function startAiExpenseDraft(expenseType, expenseTypeLabel, requiresApplicationBeforeReimbursement, options = {}) {
    if (!conversationStarted.value) {
      activateInlineConversation({ title: String(expenseTypeLabel || '报销').trim().slice(0, 18) || '报销' })
    }
    assistantDraft.value = ''
    removeWorkbenchDateTag()
    closeWorkbenchDatePicker()
    clearAiModeFiles()
    pushInlineUserMessage(`选择${expenseTypeLabel || expenseType || '报销'}`)

    const prefillValues = options.prefillValues && typeof options.prefillValues === 'object'
      ? options.prefillValues
      : null
    const stewardRemainingTasks = Array.isArray(options.stewardRemainingTasks)
      ? options.stewardRemainingTasks
      : []

    if (requiresApplicationBeforeReimbursement) {
      void resolveAiExpenseApplicationLink(expenseType, expenseTypeLabel, {
        prefillValues,
        stewardRemainingTasks
      })
      return
    }

    const draft = attachStewardRemainingTasks(
      createAiExpenseDraft(expenseType, expenseTypeLabel, prefillValues),
      stewardRemainingTasks
    )
    aiExpenseDraft.value = draft
    conversationMessages.value.push(createInlineMessage('assistant', buildAiExpenseStepPrompt(draft)))
    persistCurrentConversation()
    scrollInlineConversationToBottom()
  }

  function advanceAiExpenseDraft(answer, files = []) {
    const fileNames = Array.from(files || [])
    pushInlineUserMessage(answer || (fileNames.length ? `上传 ${fileNames.length} 份附件` : ''))
    assistantDraft.value = ''
    clearAiModeFiles()

    const currentDraft = aiExpenseDraft.value
    const next = applyAiExpenseAnswer(currentDraft, answer, fileNames)
    // applyAiExpenseAnswer 不会保留 draft 上的运行时上下文,这里手动透传 remaining tasks,
    // 保证报销草稿收集完所有字段后,仍能拿到后续 task 列表用于推进 task3。
    attachStewardRemainingTasks(next, resolveStewardRemainingTasks(currentDraft))
    aiExpenseDraft.value = next

    if (isAiExpenseDraftComplete(next)) {
      conversationMessages.value.push(createInlineMessage('assistant', `${buildAiExpenseSummary(next)}\n\n如果哪一项需要修改，请直接告诉我；确认无误后我再帮您生成报销草稿。`))
      aiExpenseDraft.value = null
    } else {
      conversationMessages.value.push(createInlineMessage('assistant', buildAiExpenseStepPrompt(next)))
    }
    persistCurrentConversation()
    scrollInlineConversationToBottom()
  }

  async function resolveAiExpenseApplicationLink(expenseType, expenseTypeLabel, options = {}) {
    const prefillValues = options.prefillValues && typeof options.prefillValues === 'object'
      ? options.prefillValues
      : null
    const stewardRemainingTasks = Array.isArray(options.stewardRemainingTasks)
      ? options.stewardRemainingTasks
      : []

    let claims = null
    try {
      claims = await fetchExpenseClaimsForAi(REIMBURSEMENT_LIST_PREVIEW_PARAMS)
    } catch {
      aiExpenseDraft.value = null
      conversationMessages.value.push(createInlineMessage('assistant', '查询可关联申请单时出现异常，请稍后再试，我先暂停这次报销流程。'))
      persistCurrentConversation()
      scrollInlineConversationToBottom()
      return
    }

    const candidates = filterRequiredApplicationCandidates(claims, expenseType, currentUser.value || {})
    // 即使后续可能被清空,也先把报销语义 + remaining tasks 上下文挂到 draft 上,
    // 这样查不到申请单时仍能透传给"发起申请单"按钮,保证 task2 不丢失语义。
    const draft = attachStewardRemainingTasks(
      createAiExpenseDraft(expenseType, expenseTypeLabel, prefillValues),
      stewardRemainingTasks
    )
    aiExpenseDraft.value = draft

    if (!candidates.length) {
      // 查不到可关联申请单:不要让 task2 语义丢失。生成"发起申请单"按钮时,
      // 按费用类型动态生成 label,带上 ontology 上下文 + remaining tasks,
      // 让用户发起申请单后能回到 task2 报销流程(见 ai_application_start_inline 分支)。
      const applicationLabel = resolveRequiredApplicationLabel(expenseType, expenseTypeLabel)
      conversationMessages.value.push(createInlineMessage('assistant', buildRequiredApplicationMissingText(expenseType), {
        suggestedActions: [{
          label: `确认发起${applicationLabel}申请`,
          description: '生成完整申请表，并预填已识别的时间、地点和事由',
          icon: 'mdi mdi-file-plus-outline',
          action_type: 'ai_application_start_inline',
          payload: {
            expense_type: expenseType,
            expense_type_label: expenseTypeLabel,
            prefill_values: prefillValues || {},
            steward_remaining_tasks: stewardRemainingTasks
          }
        }]
      }))
      persistCurrentConversation()
      scrollInlineConversationToBottom()
      return
    }

    conversationMessages.value.push(createInlineMessage('assistant', buildRequiredApplicationSelectionText(expenseType, candidates), {
      suggestedActions: buildRequiredApplicationActions(candidates, 'select_required_application')
    }))
    persistCurrentConversation()
    scrollInlineConversationToBottom()
  }

  function buildWorkbenchUserContext() {
    const user = currentUser.value || {}
    return {
      role_codes: Array.isArray(user.roleCodes) ? user.roleCodes : [],
      is_admin: Boolean(user.isAdmin),
      name: user.name || '',
      role: user.role || '',
      department: user.department || user.departmentName || '',
      department_name: user.department || user.departmentName || '',
      position: user.position || '',
      employee_position: user.position || user.employeePosition || user.employee_position || '',
      employee_no: user.employeeNo || user.employee_no || '',
      employeeNo: user.employeeNo || user.employee_no || '',
      session_type: SESSION_TYPE_EXPENSE,
      entry_source: 'workbench-ai'
    }
  }

  function waitForLinkedDraftJobPoll() {
    return new Promise((resolve) => {
      globalThis.setTimeout(resolve, linkedDraftJobPollIntervalMs)
    })
  }

  function normalizeLinkedDraftJob(job = {}) {
    const jobId = String(job?.job_id || job?.jobId || '').trim()
    if (!jobId) {
      return null
    }
    return {
      jobId,
      status: String(job?.status || 'queued').trim() || 'queued',
      message: String(job?.message || '').trim(),
      error: String(job?.error || '').trim(),
      runId: String(job?.run_id || job?.runId || '').trim(),
      applicationClaimNo: String(job?.application_claim_no || job?.applicationClaimNo || '').trim(),
      draftPayload: job?.draft_payload && typeof job.draft_payload === 'object'
        ? job.draft_payload
        : job?.draftPayload && typeof job.draftPayload === 'object'
          ? job.draftPayload
          : null
    }
  }

  function buildLinkedDraftFailedText(job = {}) {
    return String(job?.message || job?.error || '').trim()
      || '生成报销草稿时出现异常。申请单关联信息我先保留在当前会话里，您可以稍后重试，或单独新建报销单。'
  }

  const activeLinkedDraftJobPolls = new Set()

  async function pollLinkedDraftJob({
    jobId,
    pendingMessageId,
    claimNo = '',
    initialJob = null,
    stewardRemainingTasks = []
  }) {
    const normalizedJobId = String(jobId || '').trim()
    if (!normalizedJobId || activeLinkedDraftJobPolls.has(normalizedJobId)) {
      return
    }
    activeLinkedDraftJobPolls.add(normalizedJobId)
    let currentJob = initialJob ? normalizeLinkedDraftJob(initialJob) : null
    try {
      for (let index = 0; index <= linkedDraftJobMaxPolls; index += 1) {
        if (!currentJob && index > 0) {
          currentJob = normalizeLinkedDraftJob(await fetchLinkedReimbursementDraftJobForAi(normalizedJobId))
        }
        if (currentJob && !LINKED_DRAFT_JOB_PENDING_STATUSES.has(currentJob.status)) {
          if (currentJob.status === 'succeeded') {
            const draftPayload = currentJob.draftPayload || null
            const draftClaimNo = String(draftPayload?.claim_no || draftPayload?.claimNo || '').trim()
            const content = draftClaimNo
              ? `报销草稿 ${draftClaimNo} 已生成，并已关联申请单 ${claimNo || '所选申请单'}。后续可以在草稿详情中上传票据或补充信息。`
              : `报销草稿已生成，并已关联申请单 ${claimNo || '所选申请单'}。后续可以在草稿详情中上传票据或补充信息。`
            // 多 task 推进:报销草稿生成成功后,若还有剩余 task,补一个"继续处理"按钮。
            const nextTaskAction = buildExpenseDraftNextTaskAction(stewardRemainingTasks)
            replaceInlineAssistantMessage(pendingMessageId, content, {
              draftPayload,
              linkedReimbursementDraftJob: {
                ...currentJob,
                applicationClaimNo: claimNo
              },
              suggestedActions: nextTaskAction
                ? [...buildLinkedDraftAction(draftPayload), nextTaskAction]
                : buildLinkedDraftAction(draftPayload)
            })
            aiExpenseDraft.value = null
            persistCurrentConversation()
            scrollInlineConversationToBottom()
            return
          }
          throw new Error(buildLinkedDraftFailedText(currentJob))
        }
        if (currentJob) {
          replaceInlineAssistantMessage(pendingMessageId, buildLinkedDraftRunningText(currentJob, claimNo), {
            pending: true,
            linkedReimbursementDraftJob: {
              ...currentJob,
              applicationClaimNo: claimNo
            },
            suggestedActions: []
          })
          persistCurrentConversation()
        }
        await waitForLinkedDraftJobPoll()
        currentJob = normalizeLinkedDraftJob(await fetchLinkedReimbursementDraftJobForAi(normalizedJobId))
      }
      throw new Error('报销草稿仍在后台生成中，稍后回到会话会继续刷新结果。')
    } finally {
      activeLinkedDraftJobPolls.delete(normalizedJobId)
    }
  }

  function resumePendingLinkedReimbursementDraftJobs() {
    conversationMessages.value.forEach((message) => {
      const job = normalizeLinkedDraftJob(message.linkedReimbursementDraftJob || null)
      if (!job || !LINKED_DRAFT_JOB_PENDING_STATUSES.has(job.status)) {
        return
      }
      void pollLinkedDraftJob({
        jobId: job.jobId,
        pendingMessageId: message.id,
        claimNo: job.applicationClaimNo,
        initialJob: job,
        // 刷新恢复时从消息上读回 remaining tasks,保证报销完成后仍能补出"继续处理"按钮。
        stewardRemainingTasks: Array.isArray(message.stewardRemainingTasks) ? message.stewardRemainingTasks : []
      }).catch((error) => {
        replaceInlineAssistantMessage(message.id, buildLinkedDraftFailedText(error), {
          linkedReimbursementDraftJob: {
            ...job,
            status: 'failed',
            message: error?.message || '报销草稿生成状态查询失败。'
          }
        })
        persistCurrentConversation()
      })
    })
  }

  function buildLinkedDraftAction(draftPayload = {}) {
    const claimNo = String(draftPayload.claim_no || draftPayload.claimNo || '').trim()
    const claimId = String(draftPayload.claim_id || draftPayload.claimId || '').trim()
    if (!claimNo && !claimId) {
      return []
    }
    return [{
      label: '查看报销草稿',
      description: '打开草稿详情继续上传票据或补充信息。',
      icon: 'mdi mdi-file-document-outline',
      action_type: 'open_application_detail',
      payload: {
        claim_id: claimId,
        claim_no: claimNo
      }
    }]
  }

  // 报销草稿生成成功后,若有剩余 task,生成"继续处理下一个任务"按钮。
  // 与 useWorkbenchAiApplicationPreviewFlow.buildApplicationPreviewNextTaskAction 同构,
  // 但数据源是 draft 上透传过来的 stewardRemainingTasks,保证报销完成后 task3 也能推进。
  function buildExpenseDraftNextTaskAction(remainingTasks = []) {
    const tasks = Array.isArray(remainingTasks) ? remainingTasks : []
    const nextTask = tasks[0]
    if (!nextTask || !nextTask.task_type) {
      return null
    }
    const taskType = String(nextTask.task_type || '').trim()
    const isApplication = taskType === 'expense_application'
    const flowId = isApplication ? 'travel_application' : 'travel_reimbursement'
    const taskLabel = isApplication ? '出差申请' : '费用报销'
    const ontologyFields = nextTask.ontology_fields || nextTask.ontologyFields || {}
    return {
      label: `继续处理${taskLabel}`,
      description: `接下来处理${taskLabel}：${String(nextTask.summary || nextTask.title || '').slice(0, 40)}`,
      icon: isApplication ? 'mdi mdi-file-plus-outline' : 'mdi mdi-receipt-text-plus-outline',
      action_type: 'steward_continue_next_task',
      payload: {
        steward_confirm_flow: true,
        flow_id: flowId,
        steward_current_task: nextTask,
        expense_type: String(ontologyFields.expense_type || 'travel').trim() || 'travel',
        expense_type_label: String(ontologyFields.expense_type_label || '差旅费').trim() || '差旅费',
        ontology_fields: ontologyFields,
        original_message: String(nextTask.summary || nextTask.title || `继续处理${taskLabel}`).trim(),
        steward_remaining_tasks: tasks.slice(1)
      }
    }
  }

  async function linkAiExpenseApplication(application = {}) {
    const draft = aiExpenseDraft.value || (() => {
      const resolved = resolveRequiredApplicationReimbursementType(application)
      return createAiExpenseDraft(resolved.expenseType, resolved.expenseTypeLabel)
    })()
    const claimNo = String(application.application_claim_no || '').trim()
    pushInlineUserMessage(`关联申请单 ${claimNo}`.trim())

    const linked = {
      ...draft,
      applicationClaim: application,
      values: {
        ...draft.values,
        reason: String(application.application_reason || '').trim(),
        location: String(application.application_location || '').trim(),
        time_range: String(application.application_business_time || '').trim(),
        amount: String(application.application_amount_label || application.application_amount || '').trim()
      },
      stepKey: 'attachments'
    }
    aiExpenseDraft.value = linked
    // 关联申请单时,保留 draft 上的 remaining tasks 上下文,透传给后续轮询,
    // 这样报销草稿生成成功后能补出"继续处理 task3"按钮。
    const stewardRemainingTasks = resolveStewardRemainingTasks(linked) || []
    const pendingMessage = createInlineMessage('assistant', `已关联申请单${claimNo ? ` ${claimNo}` : ''}，正在生成报销草稿...`, {
      pending: true,
      suggestedActions: [],
      // 把 remaining tasks 挂到 pending 消息上,刷新后 resume 轮询能读回并透传给 poll 成功分支。
      stewardRemainingTasks
    })
    conversationMessages.value.push(pendingMessage)
    const pendingMessageId = pendingMessage.id
    persistCurrentConversation()
    scrollInlineConversationToBottom()

    try {
      const submitOptions = buildReimbursementAssociationSubmitOptions(
        application,
        application.original_message || resolveLatestInlineUserPrompt() || '我要报销'
      )
      const job = await createLinkedReimbursementDraftJobForAi({
        message: submitOptions.rawText,
        conversation_id: '',
        context_json: {
          ...buildWorkbenchUserContext(),
          ...submitOptions.extraContext
        }
      })
      const normalizedJob = normalizeLinkedDraftJob(job)
      if (!normalizedJob) {
        throw new Error('报销草稿生成任务创建失败，请稍后重试。')
      }
      await pollLinkedDraftJob({
        jobId: normalizedJob.jobId,
        pendingMessageId,
        claimNo,
        initialJob: normalizedJob,
        stewardRemainingTasks
      })
    } catch (error) {
      replaceInlineAssistantMessage(
        pendingMessageId,
        buildLinkedDraftFailedText(error),
        {
          suggestedActions: []
        }
      )
      persistCurrentConversation()
      scrollInlineConversationToBottom()
    }
  }

  return {
    advanceAiExpenseDraft,
    cancelStandaloneReimbursementDraftCreation,
    linkAiExpenseApplication,
    promptAiReimbursementDraftContinuation,
    promptStandaloneReimbursementDraftCreation,
    pushInlineExpenseSceneSelectionPrompt,
    resumePendingLinkedReimbursementDraftJobs,
    startAiApplicationPreviewFromAction,
    startAiReimbursementAssociationGate,
    startAiExpenseDraft
  }
}
