﻿<template>
  <Transition name="risk-sim-dialog">
    <div v-if="open" class="risk-sim-backdrop" @click.self="handleClose">
      <section class="risk-sim-modal" role="dialog" aria-modal="true" aria-label="风险规则仿真测试">
        <header class="risk-sim-head">
          <div class="risk-sim-title">
            <span>独立仿真测试</span>
            <h3>{{ rule?.name || '风险规则' }}</h3>
            <p>临时对话只做单据识别和风险规则执行，不创建报销单，不写入主工作台会话。</p>
          </div>
          <button type="button" class="risk-sim-icon-btn" aria-label="关闭" @click="handleClose">
            <i class="mdi mdi-close"></i>
          </button>
        </header>

        <div class="risk-sim-meta">
          <span>版本：{{ testVersion }}</span>
          <span :class="severityTone">{{ rule?.riskRuleSeverityLabel || '中风险' }}</span>
          <span>{{ requiresAttachment ? '测试需附件' : '文字测试' }}</span>
          <span>{{ latestSummary?.test_passed ? '测试结论已保存' : '测试结论未保存' }}</span>
          <span>Session：{{ sessionShortId }}</span>
        </div>

        <div class="risk-sim-main">
          <section class="risk-sim-dialog-panel">
            <div ref="messageListRef" class="risk-sim-message-list" aria-live="polite">
              <article
                v-for="message in messages"
                :key="message.id"
                class="risk-sim-message-row"
                :class="message.role"
              >
                <span class="risk-sim-avatar" aria-hidden="true">
                  <i :class="message.role === 'assistant' ? 'mdi mdi-shield-search-outline' : 'mdi mdi-account-outline'"></i>
                </span>

                <div class="risk-sim-bubble">
                  <header>
                    <strong>{{ message.role === 'assistant' ? '风险仿真助手' : '我' }}</strong>
                    <time>{{ message.time }}</time>
                  </header>

                  <p v-if="message.text">{{ message.text }}</p>

                  <div v-if="message.attachments?.length" class="risk-sim-message-files">
                    <span v-for="file in message.attachments" :key="`${message.id}-${file.id}`">
                      <i class="mdi mdi-file-document-outline"></i>
                      {{ file.name }}
                    </span>
                  </div>

                  <div v-if="message.recognitionDocuments?.length" class="risk-sim-recognition-debug">
                    <span>单据识别明细</span>
                    <article
                      v-for="document in message.recognitionDocuments"
                      :key="`${message.id}-${document.filename}`"
                    >
                      <header>
                        <strong>{{ document.filename || '临时单据' }}</strong>
                        <em>{{ formatDocumentMeta(document) }}</em>
                      </header>
                      <p v-if="document.summary">摘要：{{ document.summary }}</p>
                      <div v-if="document.document_fields?.length" class="risk-sim-debug-field-list">
                        <b
                          v-for="field in document.document_fields"
                          :key="`${document.filename}-${field.key}-${field.value}`"
                        >
                          {{ field.label }}[{{ field.key }}]：{{ field.value }}
                        </b>
                      </div>
                      <p v-if="document.text" class="risk-sim-debug-ocr-text">
                        OCR原文：{{ trimDebugText(document.text, 800) }}
                      </p>
                    </article>
                  </div>

                  <div v-if="message.result" class="risk-sim-result-card" :class="message.result.severity">
                    <div class="risk-sim-result-head">
                      <div>
                        <span>{{ message.result.ready === false ? '流程状态' : '识别结果' }}</span>
                        <strong>
                          {{ message.result.ready === false ? '待补充后再判断' : (message.result.hit ? '命中风险' : '未命中风险') }}
                        </strong>
                      </div>
                      <b>{{ message.result.severity_label }}</b>
                    </div>

                    <p v-if="message.result.blocking_reason" class="risk-sim-blocking-message">
                      {{ message.result.blocking_reason }}
                    </p>

                    <p v-if="message.result.message" class="risk-sim-result-message">
                      {{ message.result.message }}
                    </p>

                    <div class="risk-sim-field-grid">
                      <div v-for="item in buildResultFields(message.result)" :key="item.key">
                        <span>{{ item.label }}</span>
                        <strong>{{ item.value }}</strong>
                      </div>
                    </div>

                    <div
                      v-if="buildFieldPipelineSections(message.result).length"
                      class="risk-sim-field-pipeline"
                    >
                      <section
                        v-for="section in buildFieldPipelineSections(message.result)"
                        :key="section.key"
                      >
                        <header>
                          <span>{{ section.title }}</span>
                          <small>{{ section.description }}</small>
                        </header>
                        <ul>
                          <li v-for="field in section.rows" :key="field.key">
                            <strong>{{ field.label }}</strong>
                            <em>{{ field.source }}</em>
                            <b>{{ field.value }}</b>
                          </li>
                        </ul>
                      </section>
                    </div>

                    <div
                      v-if="buildRecognizedFieldRows(message.result).length"
                      class="risk-sim-recognized-fields"
                    >
                      <span>规则实际取用字段</span>
                      <ul>
                        <li v-for="field in buildRecognizedFieldRows(message.result)" :key="field.key">
                          <strong>{{ field.label }}</strong>
                          <em>{{ field.source }}</em>
                          <b>{{ field.value }}</b>
                        </li>
                      </ul>
                    </div>

                    <div v-if="buildEvidenceItems(message.result).length" class="risk-sim-evidence">
                      <span>判断依据</span>
                      <ul>
                        <li v-for="item in buildEvidenceItems(message.result)" :key="item">{{ item }}</li>
                      </ul>
                    </div>

                    <div v-if="buildTraceItems(message.result).length" class="risk-sim-evidence">
                      <span>执行路径</span>
                      <ul>
                        <li v-for="item in buildTraceItems(message.result)" :key="item">{{ item }}</li>
                      </ul>
                    </div>

                    <div v-if="message.result.missing_fields?.length" class="risk-sim-missing-fields">
                      <span>待补充字段</span>
                      <b v-for="field in message.result.missing_fields" :key="field.key">
                        {{ formatFieldLabel(field) }}
                      </b>
                    </div>
                  </div>
                </div>
              </article>

              <article v-if="busyAction === 'simulate' || recognitionBusy" class="risk-sim-message-row assistant">
                <span class="risk-sim-avatar" aria-hidden="true">
                  <i class="mdi mdi-shield-search-outline"></i>
                </span>
                <div class="risk-sim-bubble">
                  <header>
                    <strong>风险仿真助手</strong>
                    <time>{{ currentTime }}</time>
                  </header>
                  <p class="risk-sim-thinking">
                    <i class="mdi mdi-loading mdi-spin"></i>
                    {{ recognitionBusy ? '正在识别临时单据...' : '正在调用规则执行器识别风险...' }}
                  </p>
                </div>
              </article>
            </div>

            <div v-if="requiresAttachment && uploadedFiles.length" class="risk-sim-file-strip">
              <span>本轮临时附件</span>
              <div>
                <button
                  v-for="file in uploadedFiles"
                  :key="file.id"
                  type="button"
                  class="risk-sim-file-chip"
                  :class="file.status"
                  :title="`${file.name} · ${formatFileSize(file.size)}`"
                  @click="removeFile(file.id)"
                >
                  <i class="mdi mdi-file-document-outline"></i>
                  <span>{{ file.name }}</span>
                  <em>{{ resolveFileStatusLabel(file) }}</em>
                  <i class="mdi mdi-close"></i>
                </button>
              </div>
            </div>

            <footer class="risk-sim-composer" :class="{ 'text-only': !requiresAttachment }">
              <input
                v-if="requiresAttachment"
                ref="fileInputRef"
                class="risk-sim-file-input"
                type="file"
                multiple
                @change="handleFileChange"
              />
              <button
                v-if="requiresAttachment"
                type="button"
                class="risk-sim-tool-btn"
                :disabled="busy"
                aria-label="上传临时单据"
                @click="triggerFilePick"
              >
                <i class="mdi mdi-paperclip"></i>
              </button>
              <div class="risk-sim-composer-shell">
                <textarea
                  ref="composerRef"
                  v-model="draft"
                  :disabled="busy"
                  rows="1"
                  :placeholder="composerPlaceholder"
                  @keydown.enter.exact.prevent="sendMessage"
                ></textarea>
              </div>
              <button
                type="button"
                class="risk-sim-send-btn"
                :disabled="busy || !canSend"
                :title="sendBlockedReason"
                aria-label="执行风险识别"
                @click="sendMessage"
              >
                <i :class="busyAction === 'simulate' ? 'mdi mdi-loading mdi-spin' : 'mdi mdi-send'"></i>
              </button>
            </footer>
          </section>

          <aside class="risk-sim-context-panel">
            <section>
              <span>仿真流程</span>
              <div class="risk-sim-step-list">
                <div
                  v-for="step in simulationSteps"
                  :key="step.key"
                  class="risk-sim-step"
                  :class="step.status"
                >
                  <i :class="step.icon"></i>
                  <div>
                    <strong>{{ step.label }}</strong>
                    <p>{{ step.description }}</p>
                  </div>
                </div>
              </div>
            </section>

            <section v-if="recognizedDocuments.length || recognitionError">
              <span>单据识别</span>
              <div class="risk-sim-recognition-list">
                <article v-for="item in recognizedDocuments" :key="item.filename">
                  <strong>{{ item.document_type_label || item.scene_label || '已识别单据' }}</strong>
                  <p>{{ item.filename }}</p>
                  <small>{{ buildDocumentBrief(item) }}</small>
                </article>
                <p v-if="recognitionError" class="risk-sim-error-text">{{ recognitionError }}</p>
              </div>
            </section>

            <section>
              <span>规则边界</span>
              <strong>{{ requiresAttachment ? '附件和文字合并判断' : '仅使用文字事实判断' }}</strong>
              <p>{{ boundaryDescription }}</p>
            </section>

            <section>
              <span>测试报告</span>
              <strong>{{ testReportTitle }}</strong>
              <p>{{ testReportDescription }}</p>
            </section>

            <section>
              <span>使用字段</span>
              <div class="risk-sim-field-list">
                <b v-for="field in displayFields" :key="field.key">{{ field.label }}</b>
                <em v-if="!displayFields.length">当前规则未声明字段</em>
              </div>
            </section>

            <section>
              <span>关闭后清理</span>
              <p>聊天记录、临时附件、识别结果会从弹窗内存中清空。</p>
            </section>
          </aside>
        </div>

        <footer class="risk-sim-foot">
          <span>{{ lastSimulationHint }}</span>
          <div>
            <button type="button" class="risk-sim-secondary-btn" :disabled="busy" @click="resetConversation">
              <i class="mdi mdi-refresh"></i>
              <span>清空本轮</span>
            </button>
            <button
              type="button"
              class="risk-sim-primary-btn"
              :disabled="busy || !activeSimulationResult?.ready"
              @click="saveSimulationConclusion"
            >
              <i :class="busyAction === 'report' ? 'mdi mdi-loading mdi-spin' : 'mdi mdi-check-decagram-outline'"></i>
              <span>{{ busyAction === 'report' ? '保存中' : '确认测试通过' }}</span>
            </button>
          </div>
        </footer>
      </section>
    </div>
  </Transition>
</template>

<script setup>
import { computed, nextTick, ref, watch } from 'vue'

import {
  confirmRiskRuleTestReport,
  runRiskRuleSampleTest,
  simulateRiskRuleTest
} from '../../services/agentAssets.js'
import { recognizeOcrFiles } from '../../services/ocr.js'
import { useToast } from '../../composables/useToast.js'
import {
  createId,
  documentHasMeaningfulText,
  formatFileSize,
  formatTestError,
  formatTime,
  mergeRecognizedDocuments,
  normalizeOcrDocuments,
  toAttachmentPayload
} from './riskRuleTestDialogUtils.js'
import {
  buildDocumentBrief,
  buildEvidenceItems as buildEvidenceItemsModel,
  buildFieldPipelineSections as buildFieldPipelineSectionsModel,
  buildRecognizedFieldRows as buildRecognizedFieldRowsModel,
  buildResultFields as buildResultFieldsModel,
  buildTraceItems as buildTraceItemsModel,
  formatDocumentMeta,
  formatFieldLabel,
  resolveFileStatusLabel,
  trimDebugText
} from './riskRuleTestDialogDisplay.js'

const props = defineProps({
  open: {
    type: Boolean,
    default: false
  },
  rule: {
    type: Object,
    default: null
  }
})

const emit = defineEmits(['close', 'report-saved'])
const { toast } = useToast()

const messageListRef = ref(null)
const fileInputRef = ref(null)
const composerRef = ref(null)
const sessionId = ref('')
const draft = ref('')
const messages = ref([])
const uploadedFiles = ref([])
const recognizedDocuments = ref([])
const recognitionError = ref('')
const activeSimulationResult = ref(null)
const latestSummary = ref(null)
const busyAction = ref('')

const requiresAttachment = computed(() => Boolean(props.rule?.riskRuleRequiresAttachment))
const recognitionBusy = computed(() => busyAction.value === 'recognize')
const busy = computed(() => Boolean(busyAction.value))
const hasPendingFiles = computed(() => uploadedFiles.value.some((file) => file.status === 'recognizing'))
const hasRecognizedFiles = computed(() => uploadedFiles.value.some((file) => file.status === 'recognized'))
const hasFailedOnlyFiles = computed(() => uploadedFiles.value.length > 0 && uploadedFiles.value.every((file) => file.status === 'failed'))
const canSend = computed(() => {
  if (hasPendingFiles.value) return false
  const hasText = Boolean(draft.value.trim())
  if (requiresAttachment.value) {
    return hasText && uploadedFiles.value.length > 0
  }
  return hasText
})
const fields = computed(() => (Array.isArray(props.rule?.riskRuleFields) ? props.rule.riskRuleFields : []))
const displayFields = computed(() => fields.value.map((field) => ({
  key: field.key,
  label: formatFieldLabel(field)
})))
const composerPlaceholder = computed(() => requiresAttachment.value
  ? '填写测试意图并上传附件，例如：请检查这张酒店发票是否与行程城市一致'
  : '描述测试事实，例如：酒店发票城市上海，申报目的地北京，金额580元')
const boundaryDescription = computed(() => requiresAttachment.value
  ? '附件选择后不会立即识别，点击发送时才会和输入内容一起进入仿真；不会创建报销草稿或写入风险标记。'
  : '这条规则不需要上传附件，测试窗口只根据输入文字执行规则；不会创建报销草稿或影响审批流。')
const testVersion = computed(() => props.rule?.workingVersion || props.rule?.displayVersion || props.rule?.version || '-')
const sessionShortId = computed(() => sessionId.value ? sessionId.value.slice(-8).toUpperCase() : '-')
const currentTime = computed(() => formatTime())
const severityTone = computed(() => `tone-${props.rule?.riskRuleSeverity || 'medium'}`)
const sendBlockedReason = computed(() => {
  if (hasPendingFiles.value) return '单据识别中，请稍后执行风险识别。'
  if (requiresAttachment.value && !uploadedFiles.value.length) return '这条规则要求上传测试附件。'
  if (requiresAttachment.value && !draft.value.trim()) return '请填写测试意图或关键事实，附件会和文字一起判断。'
  if (!draft.value.trim()) return '请先描述测试单据或测试事实。'
  return ''
})
const simulationSteps = computed(() => [
  {
    key: 'recognize',
    label: '1. 单据识别',
    description: buildRecognitionStepDescription(),
    status: resolveRecognitionStepStatus(),
    icon: recognitionBusy.value ? 'mdi mdi-loading mdi-spin' : 'mdi mdi-text-recognition'
  },
  {
    key: 'fields',
    label: '2. 字段确认',
    description: buildFieldStepDescription(),
    status: resolveFieldStepStatus(),
    icon: 'mdi mdi-format-list-checks'
  },
  {
    key: 'execute',
    label: '3. 规则执行',
    description: activeSimulationResult.value?.ready
      ? '已使用规则执行器完成判断。'
      : '字段齐备后才会执行规则。',
    status: activeSimulationResult.value?.ready ? 'done' : 'pending',
    icon: 'mdi mdi-shield-check-outline'
  }
])
const lastSimulationHint = computed(() => {
  if (!activeSimulationResult.value) {
    return '本窗口是独立临时会话，关闭后会清空聊天记录和上传文件。'
  }
  if (activeSimulationResult.value.ready === false) {
    return `最近一次仿真：${activeSimulationResult.value.summary || '待补充字段'}`
  }
  return activeSimulationResult.value.hit
    ? `最近一次仿真：命中${activeSimulationResult.value.severity_label}`
    : '最近一次仿真：未命中风险'
})
const testReportTitle = computed(() => latestSummary.value?.test_passed ? '已确认测试通过' : '待确认测试结论')
const testReportDescription = computed(() => {
  const summary = latestSummary.value
  if (!summary) return '暂无测试报告。完成一次仿真后，可点击底部按钮确认测试通过。'
  if (summary.report?.summary) return summary.report.summary
  if (summary.sample?.summary) return `样例复核：${summary.sample.summary}`
  if (summary.scenario?.summary) return `场景试运行：${summary.scenario.summary}`
  return summary.test_passed ? '测试结论已保存。' : '暂无通过结论。'
})

watch(
  () => props.open,
  (open) => {
    if (open) {
      initializeSession()
    } else {
      destroySession()
    }
  },
  { immediate: true }
)

function initializeSession() {
  sessionId.value = createId()
  latestSummary.value = props.rule?.latestTestSummary || null
  draft.value = ''
  uploadedFiles.value = []
  recognizedDocuments.value = []
  recognitionError.value = ''
  activeSimulationResult.value = null
  busyAction.value = ''
  messages.value = [buildMessage('assistant', buildWelcomeMessage())]
  clearFileInput()
  nextTick(() => {
    scrollMessagesToBottom()
    composerRef.value?.focus()
  })
}

function destroySession() {
  draft.value = ''
  messages.value = []
  uploadedFiles.value = []
  recognizedDocuments.value = []
  recognitionError.value = ''
  activeSimulationResult.value = null
  busyAction.value = ''
  sessionId.value = ''
  clearFileInput()
}

function resetConversation() {
  initializeSession()
}

function handleClose() {
  destroySession()
  emit('close')
}

function triggerFilePick() {
  if (!requiresAttachment.value) return
  fileInputRef.value?.click()
}

function handleFileChange(event) {
  if (!requiresAttachment.value) {
    clearFileInput()
    return
  }
  const input = event.target
  const incoming = Array.from(input?.files || [])
  if (!incoming.length) return
  const nextFiles = incoming.map((file) => ({
    id: createId(),
    name: file.name,
    size: file.size,
    contentType: file.type || '',
    status: 'pending',
    statusText: '待发送',
    ocrDocument: null,
    error: '',
    file
  }))
  uploadedFiles.value = [...uploadedFiles.value, ...nextFiles].slice(0, 12)
  clearFileInput()
}

function removeFile(fileId) {
  uploadedFiles.value = uploadedFiles.value.filter((file) => file.id !== fileId)
}

async function sendMessage() {
  if (!props.rule?.id || !canSend.value || busy.value) return
  const activeSessionId = sessionId.value
  const text = draft.value.trim()
  const runFiles = requiresAttachment.value ? uploadedFiles.value.slice() : []
  messages.value.push(buildMessage('user', text, { attachments: runFiles }))
  draft.value = ''
  await nextTick()
  scrollMessagesToBottom()

  try {
    let attachments = []
    if (requiresAttachment.value) {
      const filesForRecognition = runFiles.filter((file) => file.status !== 'recognized')
      if (filesForRecognition.length) {
        busyAction.value = 'recognize'
        await recognizeTemporaryFiles(filesForRecognition, activeSessionId)
        if (!isActiveSession(activeSessionId)) return
      }
      attachments = runFiles.map(toAttachmentPayload)
    }
    busyAction.value = 'simulate'
    const result = await simulateRiskRuleTest(props.rule.id, {
      version: testVersion.value,
      message: text,
      attachments
    })
    if (!isActiveSession(activeSessionId)) return
    activeSimulationResult.value = result
    messages.value.push(buildMessage('assistant', result.summary, { result }))
  } catch (error) {
    if (!isActiveSession(activeSessionId)) return
    messages.value.push(buildMessage('assistant', formatTestError(error, '仿真识别失败，请稍后重试。')))
  } finally {
    if (isActiveSession(activeSessionId)) {
      busyAction.value = ''
      await nextTick()
      scrollMessagesToBottom()
      composerRef.value?.focus()
    }
  }
}

async function saveSimulationConclusion() {
  if (!props.rule?.id || !activeSimulationResult.value?.ready || busy.value) return
  const activeSessionId = sessionId.value
  busyAction.value = 'report'
  try {
    const sample = await runRiskRuleSampleTest(props.rule.id, {
      version: testVersion.value,
      cases: []
    })
    if (!sample?.passed) {
      messages.value.push(buildMessage('assistant', '系统样例复核未通过，暂不能保存测试通过结论。请调整规则后重新仿真。'))
      return
    }
    const report = await confirmRiskRuleTestReport(props.rule.id, {
      version: testVersion.value,
      confirm_passed: true,
      note: '通过独立对话仿真后确认测试通过；聊天记录和临时附件不保存。'
    })
    if (!isActiveSession(activeSessionId)) return
    latestSummary.value = {
      ...(latestSummary.value || {}),
      sample,
      report,
      test_passed: true
    }
    emit('report-saved', latestSummary.value)
    messages.value.push(buildMessage('assistant', '测试通过结论已保存。这个动作只更新规则生命周期状态，不保存本轮聊天记录或上传文件。'))
  } catch (error) {
    if (!isActiveSession(activeSessionId)) return
    messages.value.push(buildMessage('assistant', formatTestError(error, '测试结论保存失败，请稍后重试。')))
  } finally {
    if (isActiveSession(activeSessionId)) {
      busyAction.value = ''
      await nextTick()
      scrollMessagesToBottom()
    }
  }
}

async function recognizeTemporaryFiles(files, activeSessionId) {
  if (!files.length) return
  recognitionError.value = ''
  files.forEach((file) => {
    const target = uploadedFiles.value.find((item) => item.id === file.id)
    if (!target) return
    target.status = 'recognizing'
    target.statusText = '识别中'
    target.error = ''
  })
  try {
    const payload = await recognizeOcrFiles(files.map((file) => file.file), {
      timeoutMs: 90000,
      timeoutMessage: '单据 OCR 识别超时，请补充关键字段后再执行规则。'
    })
    if (!isActiveSession(activeSessionId)) return
    const documents = normalizeOcrDocuments(payload)
    recognizedDocuments.value = mergeRecognizedDocuments(recognizedDocuments.value, documents)
    files.forEach((file, index) => {
      const document = documents[index] || null
      const target = uploadedFiles.value.find((item) => item.id === file.id)
      if (!target) return
      if (document && documentHasMeaningfulText(document)) {
        target.status = 'recognized'
        target.statusText = '已识别'
        target.ocrDocument = document
        target.error = ''
      } else {
        target.status = 'failed'
        target.statusText = '识别不足'
        target.ocrDocument = document
        target.error = '未提取到足够文本'
      }
    })
    const recognizedCount = files.filter((file) => {
      const target = uploadedFiles.value.find((item) => item.id === file.id)
      return target?.status === 'recognized'
    }).length
    messages.value.push(buildMessage(
      'assistant',
      recognizedCount
        ? `已完成 ${recognizedCount} 份临时单据识别。下面会展示 OCR 结构化字段和原文片段，请先核对这些信息；字段不足时可以直接在输入框补充。`
        : '上传文件没有提取到足够字段。下面仍会展示 OCR 返回内容，方便判断是票据质量问题还是字段映射问题。请在输入框补充城市、金额、发票号等关键信息。',
      { recognitionDocuments: documents }
    ))
  } catch (error) {
    if (!isActiveSession(activeSessionId)) return
    recognitionError.value = formatTestError(error, '单据识别失败，请补充关键字段后再执行规则。')
    files.forEach((file) => {
      const target = uploadedFiles.value.find((item) => item.id === file.id)
      if (!target) return
      target.status = 'failed'
      target.statusText = '识别失败'
      target.error = recognitionError.value
    })
    messages.value.push(buildMessage('assistant', recognitionError.value))
  } finally {
    if (isActiveSession(activeSessionId)) {
      await nextTick()
      scrollMessagesToBottom()
    }
  }
}

function buildMessage(role, text, extra = {}) {
  return {
    id: createId(),
    role,
    text,
    time: formatTime(),
    ...extra
  }
}

function buildResultFields(result) {
  return buildResultFieldsModel(result, fields.value)
}

function buildRecognizedFieldRows(result) {
  return buildRecognizedFieldRowsModel(result, fields.value)
}

function buildFieldPipelineSections(result) {
  return buildFieldPipelineSectionsModel(result, fields.value)
}

function buildEvidenceItems(result) {
  return buildEvidenceItemsModel(result, fields.value)
}

function buildTraceItems(result) {
  return buildTraceItemsModel(result, fields.value)
}

function buildRecognitionStepDescription() {
  if (!requiresAttachment.value) return '当前规则不需要附件，直接根据文字测试事实抽取字段。'
  if (recognitionBusy.value) return '正在读取临时附件并提取 OCR 字段。'
  if (hasRecognizedFiles.value) return `已识别 ${recognizedDocuments.value.length} 份临时单据。`
  if (hasFailedOnlyFiles.value) return '识别不足，请在对话中补充字段。'
  if (uploadedFiles.value.length) return '附件已加入本轮，点击发送后会和文字一起识别。'
  return '需要上传测试附件，并填写测试意图后执行。'
}

function resolveRecognitionStepStatus() {
  if (!requiresAttachment.value) return 'done'
  if (recognitionBusy.value) return 'running'
  if (hasRecognizedFiles.value) return 'done'
  if (hasFailedOnlyFiles.value || recognitionError.value) return 'warning'
  return 'pending'
}

function buildFieldStepDescription() {
  if (activeSimulationResult.value?.recognized_fields?.length) {
    return `已确认 ${activeSimulationResult.value.recognized_fields.length} 个字段。`
  }
  if (draft.value.trim()) return '将使用你输入的文字抽取测试字段。'
  return '识别完成或补充字段后进入确认。'
}

function resolveFieldStepStatus() {
  if (activeSimulationResult.value?.ready) return 'done'
  if (activeSimulationResult.value?.missing_fields?.length) return 'warning'
  if (hasRecognizedFiles.value || draft.value.trim()) return 'running'
  return 'pending'
}

function buildWelcomeMessage() {
  if (requiresAttachment.value) {
    return '这条规则要求测试附件。请先上传临时票据并填写测试意图，点击发送后我会统一识别附件和文字，再交给规则执行器判断。'
  }
  return '这条规则不需要上传附件。你可以直接输入测试事实，我只会执行风险识别，不创建单据、不写入主工作台会话。'
}

function clearFileInput() {
  if (fileInputRef.value) {
    fileInputRef.value.value = ''
  }
}

function scrollMessagesToBottom() {
  const target = messageListRef.value
  if (target) {
    target.scrollTop = target.scrollHeight
  }
}

function isActiveSession(activeSessionId) {
  return props.open && activeSessionId && activeSessionId === sessionId.value
}

</script>

<style src="../../assets/styles/components/risk-rule-test-dialog.css"></style>
