<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
import { onBeforeRouteLeave, useRouter } from 'vue-router'
import { ElMessage, type UploadFile } from 'element-plus'
import { storeToRefs } from 'pinia'
import AppConfirmDialog from '@/components/AppConfirmDialog.vue'
import TaskSetupStep from './create/TaskSetupStep.vue'
import ModelSelectionStep from './create/ModelSelectionStep.vue'
import SourceUploadStep from './create/SourceUploadStep.vue'
import PreviewCompareStep from './create/PreviewCompareStep.vue'
import GenerationStep from './create/GenerationStep.vue'
import ResultEditorStep from './create/ResultEditorStep.vue'
import { DEFAULT_SOURCE_TEXT, estimateTokenCount } from './create/previewModel'
import {
  createDefaultStructuredOptions,
  createDefaultUnstructuredOptions,
} from './create/dataProcessCreateState'
import { useDataProcessGeneration } from './create/useDataProcessGeneration'
import { useDataProcessPreviewBuild } from './create/useDataProcessPreviewBuild'
import {
  mapDataProcessSourceFile,
  useDataProcessSourceUpload,
  validateSourceFileSelection,
} from './create/useDataProcessSourceUpload'
import { useModelsStore } from '@/stores/models'
import {
  createDataProcessPreview,
  createDataProcessTask,
  deleteDataProcessPreview,
  deleteDataProcessSourceFile,
  getDataProcessPreview,
  getDataProcessSourceContent,
  pullDataProcessExternalSource,
  testDataProcessExternalSource,
  updateDataProcessPreview,
  updateDataProcessTask,
  type DataProcessExternalSourcePayload,
  type DataProcessPreviewItem,
} from '@/api/modules/dataProcess'
import type { DataProcessConfig } from '@/types/dataProcess'
import type {
  ExternalDataSource,
  GenerationControlOptions,
  PreviewItem,
  ProcessType,
  StepId,
  StructuredProcessOptions,
  UnstructuredProcessOptions,
  UploadedDataFile,
} from './create/types'

const router = useRouter()
const modelsStore = useModelsStore()
const { list: modelList } = storeToRefs(modelsStore)
// 候选范围与模型管理保持一致，不在数据处理页面重复定义模型过滤规则。
const generationModels = computed(() => modelList.value)
const taskSetupRef = ref<InstanceType<typeof TaskSetupStep>>()
const modelSelectionRef = ref<InstanceType<typeof ModelSelectionStep>>()
const confirmDialogRef = ref<InstanceType<typeof AppConfirmDialog>>()
const PREVIEW_MODEL_VERSION = 'backend-pipeline-v3'
const WIZARD_STEPS = [
  { id: 'create', title: '创建任务', desc: '填写任务信息与处理配置' },
  { id: 'model', title: '大模型选择', desc: '选择生成模型并设置输出要求' },
  { id: 'upload', title: '上传文件', desc: '上传或接入待处理的源数据' },
  { id: 'preview', title: '数据预览', desc: '核对源文件与预览内容' },
  { id: 'generate', title: '开始生成', desc: '确认摘要并启动处理' },
  { id: 'results', title: '结果编辑与保存', desc: '检查、修改并保存结果' },
] as const satisfies ReadonlyArray<{ id: StepId; title: string; desc: string }>
const currentStep = ref(0)
const currentStepId = computed<StepId>(() => WIZARD_STEPS[currentStep.value]?.id ?? 'create')
const task = reactive({ name: '', description: '' })
const taskId = ref<string | null>(null)
const processType = ref<ProcessType>('structured')
const structuredOptions = ref<StructuredProcessOptions>(createDefaultStructuredOptions())
const unstructuredOptions = ref<UnstructuredProcessOptions>(createDefaultUnstructuredOptions())
const modelSelectionOptions = computed<GenerationControlOptions>(() => (
  processType.value === 'unstructured' ? unstructuredOptions.value : structuredOptions.value
))
const uploadedFiles = ref<UploadedDataFile[]>([])
const previewBuilding = ref(false)
const { buildPreviewsByFile } = useDataProcessPreviewBuild()
const externalSource = reactive<ExternalDataSource>({
  type: 'postgresql',
  url: '',
  authMode: 'none',
  username: '',
  password: '',
  limit: 1000,
  query: '',
  fileName: 'external-data.jsonl',
})
const externalPulling = ref(false)
const externalConnected = ref(false)
const fileName = computed(() => uploadedFiles.value.map(f => f.name).join(', '))
const previewSignature = ref('')
const previewItems = ref<PreviewItem[]>([])
const selectedPreviewFileId = ref<string | null>(null)
const selectedPreviewId = ref<string | null>(null)
const selectedPreviewIdsByFile = ref<Record<string, string>>({})
const dirty = ref(false)
let allowLeave = false

const {
  generation,
  results,
  selectedResultId,
  persistResultChanges,
  resetDownstream,
  restoreResult,
  startGeneration,
  stopGeneration,
  stopGenerationTimer,
  updateResultField,
  validateResults,
} = useDataProcessGeneration({
  taskId,
  dirty,
  beforeGenerate: syncPreviewChanges,
})
const { enqueueSourceUpload, sourceUploading } = useDataProcessSourceUpload({
  taskId,
  uploadedFiles,
  onUploaded(file) {
    previewSignature.value = ''
    resetDownstream()
    dirty.value = true
    ElMessage.success(`文件 ${file.name} 上传成功，等待切分`)
  },
})
const hasUnfinishedUploads = computed(() => uploadedFiles.value.some((file) => (
  file.status !== 'ready' || !file.sourceFileId
)))

const modifiedPreviewCount = computed(() => previewItems.value.filter((item) => item.status !== 'original').length)
const previewItemsByFile = computed(() => {
  const grouped = new Map<string, PreviewItem[]>()
  for (const item of previewItems.value) {
    const items = grouped.get(item.sourceFileId) || []
    items.push(item)
    grouped.set(item.sourceFileId, items)
  }
  return grouped
})
const activePreviewFile = computed(() => uploadedFiles.value.find((file) => String(file.uid) === selectedPreviewFileId.value))
const activePreviewItems = computed(() => previewItemsByFile.value.get(selectedPreviewFileId.value || '') || [])
const activeSourceText = computed(() => activePreviewFile.value?.content ?? '')
const previewFiles = computed(() => uploadedFiles.value.map((file) => {
  const items = previewItemsByFile.value.get(String(file.uid)) || []
  return {
    id: String(file.uid),
    name: file.name,
    count: items.length,
    modifiedCount: items.filter((item) => item.status !== 'original').length,
  }
}))
const primaryActionLabel = computed(() => {
  if (currentStepId.value === 'create') return '继续：选择大模型'
  if (currentStepId.value === 'model') return '继续：上传文件'
  if (currentStepId.value === 'upload') {
    if (sourceUploading.value) return '正在上传'
    return previewBuilding.value ? '正在切分' : '继续：数据预览'
  }
  if (currentStepId.value === 'preview') return '确认预览并继续'
  if (currentStepId.value === 'results') return '保存任务'
  if (generation.status === 'running') return '正在生成'
  if (generation.status === 'success') return '查看生成结果'
  if (generation.status === 'failed') return '重新生成'
  return '开始生成'
})

const primaryActionIcon = computed(() => {
  if (currentStepId.value === 'results') return 'fa-check'
  if (currentStepId.value === 'generate' && generation.status !== 'success') return 'fa-play'
  return 'fa-arrow-right'
})

const previousStepLabel = computed(() => currentStep.value > 0
  ? WIZARD_STEPS[currentStep.value - 1].title
  : '')

function goToStep(stepId: StepId) {
  const nextStepIndex = WIZARD_STEPS.findIndex((step) => step.id === stepId)
  if (nextStepIndex >= 0) currentStep.value = nextStepIndex
}

function updateModelSelectionOptions(value: GenerationControlOptions) {
  if (processType.value === 'unstructured') {
    unstructuredOptions.value = { ...unstructuredOptions.value, ...value }
    return
  }
  structuredOptions.value = { ...structuredOptions.value, ...value }
}

function toBackendConfig(): DataProcessConfig {
  const options = processType.value === 'unstructured'
    ? unstructuredOptions.value
    : structuredOptions.value

  const common = {
    preprocess_options: [...options.preprocessOptions],
    semantic_enrichment: options.semanticEnrichment,
    dataset_split: { ...options.datasetSplit },
    generation_model_id: options.generationModelId,
    generation_prompt: options.generationPrompt,
    temperature: options.temperature,
    max_tokens: options.maxTokens,
    json_mode: options.jsonMode,
    quality_filter_enabled: options.qualityFilterEnabled,
    filter_low_quality: options.filterLowQuality,
    filter_short_content: options.filterShortContent,
    min_output_length: options.minOutputLength,
  }

  if (processType.value === 'unstructured') {
    return {
      ...common,
      chunk_method: unstructuredOptions.value.chunkMethod,
      chunk_size: unstructuredOptions.value.chunkSize,
      chunk_overlap: unstructuredOptions.value.chunkOverlap,
      min_chunk_size: unstructuredOptions.value.minChunkSize,
      custom_delimiter: unstructuredOptions.value.customDelimiter,
      preserve_tables: unstructuredOptions.value.preserveTables,
      preserve_code_blocks: unstructuredOptions.value.preserveCodeBlocks,
      preserve_lists: unstructuredOptions.value.preserveLists,
      qa_pairs_per_chunk: unstructuredOptions.value.qaPairsPerChunk,
    }
  }

  return {
    ...common,
    qa_pairs_per_row: structuredOptions.value.qaPairsPerRow,
  }
}

function taskPayload() {
  return {
    name: task.name.trim(),
    description: task.description.trim(),
    process_type: processType.value,
    config: toBackendConfig(),
  }
}

function externalPayload(): DataProcessExternalSourcePayload {
  return {
    type: 'postgresql',
    url: externalSource.url.trim(),
    auth_mode: externalSource.authMode,
    username: externalSource.username || undefined,
    password: externalSource.password || undefined,
    limit: externalSource.limit,
    query: externalSource.query?.trim() || undefined,
    file_name: externalSource.fileName || 'external-data.jsonl',
  }
}

function mapPreviewItem(item: DataProcessPreviewItem): PreviewItem {
  return {
    id: String(item.id),
    sourceFileId: String(item.source_file_id),
    originalContent: item.original_content,
    editedContent: item.edited_content,
    sourceStart: item.source_start,
    sourceEnd: item.source_end,
    sourceStartLine: item.source_start_line,
    sourceEndLine: item.source_end_line,
    tokenCount: item.token_count,
    status: item.status,
    updatedAt: item.updated_at,
  }
}

function previewAffectingOptions() {
  if (processType.value === 'structured') {
    return {
      preprocessOptions: structuredOptions.value.preprocessOptions,
    }
  }
  if (processType.value === 'unstructured') {
    const {
      preprocessOptions,
      chunkMethod,
      chunkSize,
      chunkOverlap,
      minChunkSize,
      customDelimiter,
      preserveTables,
      preserveCodeBlocks,
      preserveLists,
    } = unstructuredOptions.value
    return {
      preprocessOptions,
      chunkMethod,
      chunkSize,
      chunkOverlap,
      minChunkSize,
      customDelimiter: chunkMethod === 'custom' ? customDelimiter : '',
      preserveTables,
      preserveCodeBlocks,
      preserveLists,
    }
  }
  return null
}

function generationAffectingOptions() {
  if (processType.value === 'structured') {
    const {
      semanticEnrichment, qaPairsPerRow, datasetSplit, generationModelId, generationPrompt,
      temperature, maxTokens, jsonMode,
      qualityFilterEnabled, filterLowQuality, filterShortContent, minOutputLength,
    } = structuredOptions.value
    return {
      semanticEnrichment, qaPairsPerRow, datasetSplit, generationModelId, generationPrompt,
      temperature, maxTokens, jsonMode,
      qualityFilterEnabled, filterLowQuality, filterShortContent, minOutputLength,
    }
  }
  if (processType.value === 'unstructured') {
    const {
      semanticEnrichment,
      qaPairsPerChunk,
      datasetSplit,
      generationModelId,
      generationPrompt,
      temperature,
      maxTokens,
      jsonMode,
      qualityFilterEnabled,
      filterLowQuality,
      filterShortContent,
      minOutputLength,
    } = unstructuredOptions.value
    return {
      semanticEnrichment,
      qaPairsPerChunk,
      datasetSplit,
      generationModelId,
      generationPrompt,
      temperature,
      maxTokens,
      jsonMode,
      qualityFilterEnabled,
      filterLowQuality,
      filterShortContent,
      minOutputLength,
    }
  }
  return null
}

const generationOptionsSignature = computed(() => JSON.stringify(generationAffectingOptions()))

function buildPreviewConfigSignature() {
  return `${PREVIEW_MODEL_VERSION}:${processType.value}:${JSON.stringify(previewAffectingOptions())}`
}

function buildPreviewSignature() {
  const filesSignature = uploadedFiles.value
    .map((file) => `${file.uid}:${file.name}:${file.size}:${file.checksumSha256 || file.count}`)
    .join('|')
  return `${buildPreviewConfigSignature()}:${filesSignature}`
}

watch(
  [() => task.name, () => task.description, processType, structuredOptions, unstructuredOptions, externalSource],
  () => { dirty.value = true },
  { deep: true },
)

watch(processType, (nextType, previousType) => {
  if (nextType === previousType || uploadedFiles.value.length === 0) return
  resetSourceDataForProcessTypeChange()
  ElMessage.info('处理类型已变更，请重新上传或拉取匹配的源数据')
})

watch(generationOptionsSignature, (currentSignature, previousSignature) => {
  if (currentSignature === previousSignature) return
  resetDownstream()
})

function scrollToStepTop() {
  document.querySelector<HTMLElement>('.layout-content')?.scrollTo({ top: 0, behavior: 'smooth' })
}

watch(currentStep, () => nextTick(scrollToStepTop))

function handleFileChange(uploadFile: UploadFile) {
  const raw = uploadFile.raw
  if (!raw) return
  if (!taskId.value) {
    ElMessage.error('任务尚未创建，请返回模型选择步骤后重试')
    return
  }
  const validation = validateSourceFileSelection(raw, processType.value, uploadedFiles.value)
  if (!validation.valid) {
    ElMessage[validation.severity](validation.message)
    return
  }

  // 必须先同步插入文件行，再交给队列；这样选择完成后页面会立即展示全部文件。
  const localUid = `local-${uploadFile.uid}-${Date.now()}-${uploadedFiles.value.length}`
  uploadedFiles.value.push({
    uid: localUid,
    rawFile: raw,
    name: raw.name,
    size: raw.size,
    count: 0,
    content: '',
    fileFormat: validation.extension,
    status: 'queued',
    uploadProgress: 0,
    previewStatus: 'waiting',
    previewProgress: 0,
  })
  dirty.value = true
  enqueueSourceUpload({ uid: localUid, file: raw, extension: validation.extension })
}

async function useSampleFile() {
  if (!taskId.value) {
    ElMessage.error('任务尚未创建，请返回模型选择步骤后重试')
    return
  }
  const sample = new File([DEFAULT_SOURCE_TEXT], 'finance_qa.jsonl', { type: 'application/x-ndjson' })
  handleFileChange({ raw: sample, uid: Date.now(), name: sample.name } as UploadFile)
}

function updateExternalSource(value: ExternalDataSource) {
  Object.assign(externalSource, value)
  externalConnected.value = false
}

async function handleTestConnection() {
  if (!taskId.value) {
    ElMessage.error('任务尚未创建，请返回模型选择步骤后重试')
    return
  }
  if (!externalSource.url.trim()) {
    ElMessage.warning('请先填写数据源地址')
    return
  }
  externalPulling.value = true
  try {
    const result = await testDataProcessExternalSource(taskId.value, externalPayload())
    externalConnected.value = result.connected
    if (result.connected) ElMessage.success(result.message || '数据源连接测试成功')
    else ElMessage.warning(result.message || '数据源连接失败')
  } catch {
    externalConnected.value = false
  } finally {
    externalPulling.value = false
  }
}

async function handlePullData() {
  if (!taskId.value) {
    ElMessage.error('任务尚未创建，请返回模型选择步骤后重试')
    return
  }
  if (!externalSource.url.trim()) {
    ElMessage.warning('请先填写数据源地址')
    return
  }
  if (!externalSource.query?.trim()) {
    ElMessage.warning('请先填写只读 SELECT 查询语句')
    return
  }
  externalPulling.value = true
  try {
    const response = await pullDataProcessExternalSource(taskId.value, externalPayload())
    const newFiles: UploadedDataFile[] = []
    for (const file of response.files) {
      const source = await getDataProcessSourceContent(taskId.value, file.id, {
        start_line: 1,
        line_count: 5000,
      })
      newFiles.push(mapDataProcessSourceFile(file, source.content))
    }
    uploadedFiles.value.push(...newFiles)
    externalConnected.value = true
    previewSignature.value = ''
    resetDownstream()
    dirty.value = true
    ElMessage.success(`已成功登记 ${newFiles.length} 个外部源文件`)
  } catch {
    externalConnected.value = false
  } finally {
    externalPulling.value = false
  }
}

async function handleRemoveFile(uid: string | number) {
  const index = uploadedFiles.value.findIndex(f => f.uid === uid)
  if (index < 0 || !taskId.value) return
  const file = uploadedFiles.value[index]
  if (!file) return
  if (file.status === 'uploading') {
    ElMessage.warning('当前文件正在上传，请等待上传结束后再删除')
    return
  }
  if (file.sourceFileId) {
    try {
      await deleteDataProcessSourceFile(taskId.value, file.sourceFileId)
    } catch {
      return
    }
  }
  uploadedFiles.value.splice(index, 1)
  previewSignature.value = ''
  previewItems.value = []
  selectedPreviewId.value = null
  resetDownstream()
  dirty.value = true
}

function resetSourceDataForProcessTypeChange() {
  uploadedFiles.value = []
  previewSignature.value = ''
  previewItems.value = []
  selectedPreviewFileId.value = null
  selectedPreviewId.value = null
  selectedPreviewIdsByFile.value = {}
  externalConnected.value = false
  resetDownstream()
}

async function nextFromCreate() {
  const valid = await taskSetupRef.value?.validate()
  if (!valid) return
  goToStep('model')
}

async function nextFromModel() {
  const valid = await modelSelectionRef.value?.validate()
  if (!valid) return
  try {
    const saved = taskId.value
      ? await updateDataProcessTask(taskId.value, taskPayload())
      : await createDataProcessTask(taskPayload())
    taskId.value = String(saved.id)
    dirty.value = true
    goToStep('upload')
  } catch {
    // 请求层已展示名称冲突或配置非法等具体原因。
  }
}

async function nextFromUpload() {
  if (previewBuilding.value || sourceUploading.value) return
  if (!taskId.value) {
    ElMessage.error('任务尚未创建，请返回模型选择步骤后重试')
    return
  }
  if (uploadedFiles.value.length === 0) {
    ElMessage.warning(processType.value === 'external' ? '请先拉取至少一个数据源' : '请上传至少一个源数据文件')
    return
  }
  const failedUploads = uploadedFiles.value.filter((file) => file.status === 'failed')
  if (failedUploads.length) {
    ElMessage.warning(`${failedUploads.length} 个文件上传失败，请删除后重新选择`)
    return
  }
  if (hasUnfinishedUploads.value) {
    ElMessage.warning('请等待所有文件上传完成后再开始切分')
    return
  }

  const signature = buildPreviewSignature()
  const configSignature = buildPreviewConfigSignature()
  const allFilesSucceeded = () => uploadedFiles.value.every((file) => (
    file.previewStatus === 'success' && file.previewConfigSignature === configSignature
  ))

  if (signature === previewSignature.value && previewItems.value.length && allFilesSucceeded()) {
    goToStep('preview')
    return
  }

  for (const file of uploadedFiles.value) {
    if (file.previewConfigSignature === configSignature) continue
    file.previewStatus = 'waiting'
    file.previewProgress = 0
    file.previewError = undefined
    file.previewCount = undefined
  }

  previewBuilding.value = true
  try {
    const pendingFileIds = uploadedFiles.value
      .filter((file) => file.previewStatus !== 'success' || file.previewConfigSignature !== configSignature)
      .map((file) => file.sourceFileId)
      .filter((fileId): fileId is string => Boolean(fileId))
    // 构建接口是同步响应：请求中只展示真实的“处理中”，响应成功后才记为 100%。
    await buildPreviewsByFile(taskId.value, pendingFileIds, (progress) => {
      const file = uploadedFiles.value.find((item) => (
        String(item.sourceFileId) === String(progress.source_file_id)
      ))
      if (!file) return
      file.previewStatus = progress.status
      file.previewProgress = progress.progress
      file.previewCount = progress.preview_count
      file.previewError = progress.error
      if (progress.status === 'success') file.previewConfigSignature = configSignature
    })

    const failedCount = uploadedFiles.value.filter((file) => file.previewStatus === 'failed').length
    if (failedCount || !allFilesSucceeded()) {
      ElMessage.warning(`${failedCount || 1} 个文件切分失败；修正问题后点击“继续：数据预览”即可重试`)
      return
    }

    try {
      const first = await getDataProcessPreview(taskId.value, { page: 1, page_size: 500 })
      const items = [...first.items]
      const pages = Math.ceil(first.total / first.page_size)
      for (let page = 2; page <= pages; page += 1) {
        const next = await getDataProcessPreview(taskId.value, { page, page_size: 500 })
        items.push(...next.items)
      }
      previewItems.value = items.map(mapPreviewItem)
    } catch {
      ElMessage.warning('文件切分已经完成，但预览加载失败；请再次点击继续重试加载')
      return
    }
    if (!previewItems.value.length) {
      ElMessage.warning('源文件没有生成可用的预览条目，请检查文件内容和预处理配置')
      return
    }
    selectedPreviewFileId.value = String(uploadedFiles.value[0]?.uid ?? '') || null
    selectedPreviewId.value = activePreviewItems.value[0]?.id ?? null
    selectedPreviewIdsByFile.value = selectedPreviewId.value && selectedPreviewFileId.value
      ? { [selectedPreviewFileId.value]: selectedPreviewId.value }
      : {}
    previewSignature.value = signature
    resetDownstream()
    goToStep('preview')
  } finally {
    previewBuilding.value = false
  }
}

function selectPreviewFile(fileId: string) {
  if (selectedPreviewFileId.value && selectedPreviewId.value) {
    selectedPreviewIdsByFile.value[selectedPreviewFileId.value] = selectedPreviewId.value
  }
  selectedPreviewFileId.value = fileId
  selectedPreviewId.value = selectedPreviewIdsByFile.value[fileId]
    || activePreviewItems.value[0]?.id
    || null
}

function selectPreviewItem(id: string) {
  selectedPreviewId.value = id
  if (selectedPreviewFileId.value) selectedPreviewIdsByFile.value[selectedPreviewFileId.value] = id
}

function updatePreviewContent(id: string, value: string) {
  const item = previewItems.value.find((entry) => entry.id === id)
  if (!item) return
  item.editedContent = value
  item.tokenCount = estimateTokenCount(value)
  item.status = value === item.originalContent ? 'original' : item.sourceStart == null ? 'manual' : 'modified'
  resetDownstream()
  dirty.value = true
}

async function syncPreviewChanges() {
  if (!taskId.value) throw new Error('任务尚未创建')
  const changedItems = previewItems.value.filter((item) => item.status === 'modified' || item.status === 'manual')
  for (const item of changedItems) {
    const saved = await updateDataProcessPreview(taskId.value, item.id, {
      edited_content: item.editedContent,
      expected_updated_at: item.updatedAt,
    })
    const index = previewItems.value.findIndex((entry) => entry.id === item.id)
    if (index >= 0) previewItems.value[index] = mapPreviewItem(saved)
  }
}

function restorePreviewItem(id: string) {
  const item = previewItems.value.find((entry) => entry.id === id)
  if (!item || item.sourceStart == null) return
  item.editedContent = item.originalContent
  item.tokenCount = estimateTokenCount(item.originalContent)
  item.status = 'original'
  resetDownstream()
  dirty.value = true
}

async function addPreviewItem() {
  if (!selectedPreviewFileId.value || !taskId.value) return
  try {
    const created = await createDataProcessPreview(taskId.value, {
      source_file_id: selectedPreviewFileId.value,
      edited_content: '',
    })
    const item = mapPreviewItem(created)
    previewItems.value.push(item)
    selectPreviewItem(item.id)
    resetDownstream()
    dirty.value = true
  } catch {
    // 请求层已展示错误。
  }
}

async function removePreviewItem(id: string) {
  const confirmed = await confirmDialogRef.value?.open({
    title: '删除预览内容？',
    message: '删除只影响本次处理，不会修改源文件。删除后可重新从源文件生成预览。',
    confirmText: '删除',
    cancelText: '取消',
    tone: 'danger',
  })
  if (!confirmed) return

  const index = previewItems.value.findIndex((item) => item.id === id)
  if (index < 0) return
  if (!taskId.value) return
  try {
    await deleteDataProcessPreview(taskId.value, id)
  } catch {
    return
  }
  previewItems.value.splice(index, 1)
  selectedPreviewId.value = activePreviewItems.value[Math.min(index, activePreviewItems.value.length - 1)]?.id ?? null
  if (selectedPreviewFileId.value && selectedPreviewId.value) {
    selectedPreviewIdsByFile.value[selectedPreviewFileId.value] = selectedPreviewId.value
  }
  resetDownstream()
  dirty.value = true
}

async function handlePrimaryAction() {
  if (currentStepId.value === 'create') {
    await nextFromCreate()
    return
  }
  if (currentStepId.value === 'model') {
    await nextFromModel()
    return
  }
  if (currentStepId.value === 'upload') {
    await nextFromUpload()
    return
  }
  if (currentStepId.value === 'preview') {
    if (!previewItems.value.length) {
      ElMessage.warning('当前没有可生成的预览内容')
      return
    }
    goToStep('generate')
    return
  }
  if (currentStepId.value === 'generate') {
    if (generation.status === 'success') {
      goToStep('results')
    } else if (generation.status !== 'running') {
      await startGeneration()
    }
    return
  }
  await saveTask()
}

function handleBack() {
  if (sourceUploading.value) {
    ElMessage.warning('请等待当前文件上传完成')
    return
  }
  if (previewBuilding.value) {
    ElMessage.warning('请等待当前文件切分完成')
    return
  }
  if (generation.status === 'running') {
    ElMessage.warning('请先停止当前生成任务')
    return
  }
  if (currentStep.value > 0) currentStep.value -= 1
}


async function saveTask() {
  if (!validateResults()) {
    ElMessage.warning('请先修正校验失败的结果')
    return
  }
  try {
    await persistResultChanges()
  } catch {
    return
  }
  if (!validateResults()) {
    ElMessage.warning('仍有结果未通过后端质量校验，请继续修正')
    return
  }
  dirty.value = false
  allowLeave = true
  ElMessage.success('数据处理任务已保存')
  await router.push('/data-process')
}

async function handleCancel() {
  if (!dirty.value) {
    allowLeave = true
    router.back()
    return
  }
  const confirmed = await confirmDialogRef.value?.open({
    title: '确认离开当前页面？',
    message: '当前存在未保存修改，离开后这些修改将不会保留。',
    confirmText: '放弃修改',
    cancelText: '继续编辑',
    tone: 'danger',
  })
  if (!confirmed) return
  allowLeave = true
  router.back()
}

onBeforeRouteLeave(async () => {
  if (sourceUploading.value) {
    ElMessage.warning('请等待当前文件上传完成后再离开页面')
    return false
  }
  if (allowLeave || !dirty.value) return true
  const confirmed = await confirmDialogRef.value?.open({
    title: '确认离开当前页面？',
    message: '当前存在未保存修改，离开后这些修改将不会保留。',
    confirmText: '放弃修改',
    cancelText: '继续编辑',
    tone: 'danger',
  })
  if (confirmed) allowLeave = true
  return Boolean(confirmed)
})

onBeforeUnmount(() => {
  stopGenerationTimer()
})
onMounted(() => {
  localStorage.removeItem('yg-data-process-create-draft')
  void modelsStore.load(true)
})
</script>

<template>
  <div class="create-wizard-layout">
    <main class="wizard-main">
      <div class="wizard-main-inner">
        <div class="wizard-steps-container">
          <div class="custom-wizard-steps">
            <template v-for="(step, index) in WIZARD_STEPS" :key="step.id">
              <div 
                v-if="index !== 0" 
                class="step-connector"
                :class="{ 'is-active': currentStep >= index }"
              ></div>
              <div 
                class="step-item"
                :class="{ 
                  'is-active': currentStep === index, 
                  'is-completed': currentStep > index 
                }"
                :aria-current="currentStep === index ? 'step' : undefined"
              >
                <div class="step-node">
                  <div class="step-icon">
                    <i v-if="currentStep > index" class="fa fa-check" />
                    <span v-else>{{ index + 1 }}</span>
                  </div>
                  <div class="step-text">
                    <div class="step-title">{{ step.title }}</div>
                  </div>
                </div>
              </div>
            </template>
          </div>
        </div>

        <div class="wizard-content">
          <TaskSetupStep
            v-if="currentStepId === 'create'"
            ref="taskSetupRef"
            v-model:name="task.name"
            v-model:description="task.description"
            v-model:process-type="processType"
            v-model:structured-options="structuredOptions"
            v-model:unstructured-options="unstructuredOptions"
          />

          <ModelSelectionStep
            v-else-if="currentStepId === 'model'"
            ref="modelSelectionRef"
            :options="modelSelectionOptions"
            :models="generationModels"
            @update:options="updateModelSelectionOptions"
          />

          <SourceUploadStep
            v-else-if="currentStepId === 'upload'"
            :process-type="processType"
            :uploaded-files="uploadedFiles"
            :external-source="externalSource"
            :external-pulling="externalPulling"
            :external-connected="externalConnected"
            :preview-building="previewBuilding"
            :source-uploading="sourceUploading"
            @update:external-source="updateExternalSource"
            @file-change="handleFileChange"
            @remove-file="handleRemoveFile"
            @use-sample="useSampleFile"
            @test-connection="handleTestConnection"
            @pull-data="handlePullData"
          />

          <PreviewCompareStep
            v-else-if="currentStepId === 'preview'"
            :selected-id="selectedPreviewId"
            :selected-file-id="selectedPreviewFileId"
            :source-text="activeSourceText"
            :items="activePreviewItems"
            :process-type="processType"
            :file-name="activePreviewFile?.name ?? ''"
            :task-id="taskId"
            :source-file-id="activePreviewFile?.sourceFileId ?? activePreviewFile?.uid ?? null"
            :files="previewFiles"
            @update:selected-id="selectPreviewItem"
            @update:selected-file-id="selectPreviewFile"
            @update:item-content="updatePreviewContent"
            @restore:item="restorePreviewItem"
            @add:item="addPreviewItem"
            @remove:item="removePreviewItem"
          />

          <GenerationStep
            v-else-if="currentStepId === 'generate'"
            :task-name="task.name"
            :process-type="processType"
            :file-name="fileName"
            :preview-count="previewItems.length"
            :modified-count="modifiedPreviewCount"
            :generation="generation"
            @stop="stopGeneration"
            @retry="startGeneration"
          />

          <ResultEditorStep
            v-else-if="currentStepId === 'results'"
            v-model:selected-id="selectedResultId"
            :items="results"
            @update:field="updateResultField"
            @restore:item="restoreResult"
          />
        </div>
      </div>
    </main>

    <footer class="wizard-footer">
      <div class="footer-left">
        <el-button v-if="currentStep > 0" :disabled="previewBuilding || sourceUploading" @click="handleBack">
          <i class="fa fa-arrow-left" style="margin-right: 6px;" /> 返回：{{ previousStepLabel }}
        </el-button>
        <el-button v-else @click="handleCancel">取消</el-button>
      </div>
      <div class="footer-center">
      </div>
      <div class="footer-right">
        <el-button
          class="wizard-primary-action"
          type="primary"
          :loading="generation.status === 'running' || (currentStepId === 'upload' && (sourceUploading || previewBuilding))"
          :disabled="(currentStepId === 'generate' && generation.status === 'running') || previewBuilding || sourceUploading || (currentStepId === 'upload' && hasUnfinishedUploads)"
          @click="handlePrimaryAction"
        >
          {{ primaryActionLabel }} <i class="fa" :class="primaryActionIcon" style="margin-left: 6px;" />
        </el-button>
      </div>
    </footer>
  </div>

  <AppConfirmDialog ref="confirmDialogRef" />
</template>

<style scoped lang="scss" src="./create/data-process-create.scss"></style>
