<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import type { FormInstance, FormRules, UploadFile } from 'element-plus'
import type {
  ChunkMethod,
  DatasetSplitOptions,
  ExternalDataSource,
  GenerationContextScope,
  PreprocessOption,
  ProcessType,
  QuestionGenerationType,
  StructuredProcessOptions,
  UnstructuredPreprocessOption,
  UnstructuredProcessOptions,
} from './types'

const props = defineProps<{
  name: string
  description: string
  processType: ProcessType
  uploadedFiles: { uid: string | number; name: string; size: number; count: number }[]
  externalSource: ExternalDataSource
  externalPulling: boolean
  externalConnected: boolean
  structuredOptions: StructuredProcessOptions
  unstructuredOptions: UnstructuredProcessOptions
}>()

const emit = defineEmits<{
  'update:name': [value: string]
  'update:description': [value: string]
  'update:processType': [value: ProcessType]
  'update:externalSource': [value: ExternalDataSource]
  'update:structuredOptions': [value: StructuredProcessOptions]
  'update:unstructuredOptions': [value: UnstructuredProcessOptions]
  'file-change': [file: UploadFile]
  'remove-file': [uid: string | number]
  'use-sample': []
  'test-connection': []
  'pull-data': []
}>()

const DATA_SOURCE_TYPES = [
  { value: 'mysql', label: 'MySQL' },
  { value: 'postgresql', label: 'PostgreSQL' },
  { value: 'mongodb', label: 'MongoDB' },
  { value: 'api', label: 'REST API' },
]

const AUTH_MODES = [
  { value: 'none', label: '免鉴权' },
  { value: 'basic', label: '账号密码' },
  { value: 'token', label: 'Token' },
]

const PREPROCESS_OPTIONS: Array<{
  value: PreprocessOption
  label: string
  description: string
}> = [
  { value: 'clean_invalid', label: '清理无效数据', description: '处理空行、空列和残缺行' },
  { value: 'detect_structure', label: '识别表格结构', description: '识别表头、多级表头和合并单元格' },
  { value: 'deduplicate', label: '重复数据去重', description: '删除完全重复或关键字段重复的数据' },
  { value: 'normalize_format', label: '数据格式标准化', description: '统一日期、数字、单位和枚举值格式' },
  { value: 'filter_anomaly', label: '异常数据过滤', description: '过滤乱码、无效内容和异常记录' },
  { value: 'desensitize', label: '敏感信息脱敏', description: '处理姓名、手机号、邮箱等敏感信息' },
]

const UNSTRUCTURED_PREPROCESS_OPTIONS: Array<{
  value: UnstructuredPreprocessOption
  label: string
  description: string
}> = [
  { value: 'clean_invalid_content', label: '清理无效内容', description: '清除空段、乱码、页眉页脚和多余空白' },
  { value: 'detect_document_structure', label: '识别文档结构', description: '识别标题、章节、段落及特殊内容块' },
  { value: 'merge_short_content', label: '合并过短内容', description: '将信息不完整的过短段落并入上下文' },
  { value: 'filter_low_quality', label: '过滤低质量内容', description: '过滤广告、导航、无意义重复及信息过少内容' },
  { value: 'deduplicate_content', label: '重复内容去重', description: '识别完全重复或高度相似的段落' },
  { value: 'desensitize', label: '敏感信息脱敏', description: '处理姓名、手机号、邮箱和证件号等信息' },
  { value: 'preserve_context', label: '保留上下文信息', description: '为切片保留所属文档、章节和标题信息' },
]

const CHUNK_METHODS: Array<{ value: ChunkMethod; label: string }> = [
  { value: 'semantic', label: '自动语义切分' },
  { value: 'heading', label: '按标题和段落' },
  { value: 'fixed', label: '按固定长度' },
  { value: 'custom', label: '自定义分隔符' },
]

const CONTEXT_SCOPES: Array<{ value: GenerationContextScope; label: string }> = [
  { value: 'current', label: '当前切片' },
  { value: 'adjacent', label: '相邻切片' },
  { value: 'section', label: '当前章节' },
]

const GENERATION_TYPES: Array<{ value: QuestionGenerationType; label: string }> = [
  { value: 'factual', label: '事实问答' },
  { value: 'concept', label: '概念解释' },
  { value: 'procedure', label: '操作步骤' },
  { value: 'reasoning', label: '原因分析' },
  { value: 'comprehensive', label: '综合问答' },
]

function updateExternalField<K extends keyof ExternalDataSource>(field: K, value: ExternalDataSource[K]) {
  emit('update:externalSource', { ...props.externalSource, [field]: value })
}

function updateStructuredField<K extends keyof StructuredProcessOptions>(
  field: K,
  value: StructuredProcessOptions[K],
) {
  emit('update:structuredOptions', { ...props.structuredOptions, [field]: value })
}

function updatePreprocessOptions(value: Array<string | number | boolean>) {
  const allowedValues = new Set(PREPROCESS_OPTIONS.map((option) => option.value))
  const preprocessOptions = value.filter(
    (option): option is PreprocessOption => typeof option === 'string' && allowedValues.has(option as PreprocessOption),
  )
  updateStructuredField('preprocessOptions', preprocessOptions)
}

function updateSemanticEnrichment(value: string | number | boolean) {
  updateStructuredField('semanticEnrichment', Boolean(value))
}

function updateDatasetSplit(field: keyof DatasetSplitOptions, value: number | undefined) {
  updateStructuredField('datasetSplit', {
    ...props.structuredOptions.datasetSplit,
    [field]: Math.min(100, Math.max(0, Number(value) || 0)),
  })
}

function updateUnstructuredField<K extends keyof UnstructuredProcessOptions>(
  field: K,
  value: UnstructuredProcessOptions[K],
) {
  emit('update:unstructuredOptions', { ...props.unstructuredOptions, [field]: value })
}

function updateUnstructuredPreprocessOptions(value: Array<string | number | boolean>) {
  const allowedValues = new Set(UNSTRUCTURED_PREPROCESS_OPTIONS.map((option) => option.value))
  const preprocessOptions = value.filter(
    (option): option is UnstructuredPreprocessOption => (
      typeof option === 'string' && allowedValues.has(option as UnstructuredPreprocessOption)
    ),
  )
  updateUnstructuredField('preprocessOptions', preprocessOptions)
}

function updateGenerationTypes(value: Array<string | number | boolean>) {
  const allowedValues = new Set(GENERATION_TYPES.map((option) => option.value))
  const generationTypes = value.filter(
    (option): option is QuestionGenerationType => (
      typeof option === 'string' && allowedValues.has(option as QuestionGenerationType)
    ),
  )
  updateUnstructuredField('generationTypes', generationTypes)
}

const UNSTRUCTURED_NUMBER_LIMITS = {
  chunkSize: { min: 200, max: 2000 },
  chunkOverlap: { min: 0, max: 500 },
  minChunkSize: { min: 20, max: 500 },
  qaPairsPerChunk: { min: 1, max: 3 },
} as const

type UnstructuredNumberField = keyof typeof UNSTRUCTURED_NUMBER_LIMITS

function updateUnstructuredNumber(field: UnstructuredNumberField, value: number | undefined) {
  const limits = UNSTRUCTURED_NUMBER_LIMITS[field]
  const parsedValue = Number(value)
  const nextValue = Number.isFinite(parsedValue) ? parsedValue : limits.min
  updateUnstructuredField(field, Math.min(limits.max, Math.max(limits.min, nextValue)))
}

function updateUnstructuredDatasetSplit(field: keyof DatasetSplitOptions, value: number | undefined) {
  updateUnstructuredField('datasetSplit', {
    ...props.unstructuredOptions.datasetSplit,
    [field]: Math.min(100, Math.max(0, Number(value) || 0)),
  })
}

const formRef = ref<FormInstance>()
const formModel = computed(() => ({
  name: props.name,
  processType: props.processType,
}))

const splitTotal = computed(() => {
  const { train, validation, test } = props.structuredOptions.datasetSplit
  return train + validation + test
})

const unstructuredSplitTotal = computed(() => {
  const { train, validation, test } = props.unstructuredOptions.datasetSplit
  return train + validation + test
})

const chunkValidationMessage = computed(() => {
  if (props.unstructuredOptions.chunkOverlap >= props.unstructuredOptions.chunkSize) {
    return '重叠长度必须小于切片长度'
  }
  if (props.unstructuredOptions.minChunkSize > props.unstructuredOptions.chunkSize) {
    return '最小切片长度不能大于切片长度'
  }
  if (
    props.unstructuredOptions.chunkOverlap + props.unstructuredOptions.minChunkSize
    > props.unstructuredOptions.chunkSize
  ) {
    return '重叠长度与最小切片长度之和不能大于切片长度'
  }
  if (props.unstructuredOptions.chunkMethod === 'custom' && !props.unstructuredOptions.customDelimiter.trim()) {
    return '请输入自定义分隔符'
  }
  return ''
})

const rules: FormRules = {
  name: [
    { required: true, message: '请输入任务名称', trigger: 'blur' },
    { max: 50, message: '任务名称不能超过 50 个字符', trigger: 'blur' },
  ],
  processType: [{ required: true, message: '请选择数据处理类型', trigger: 'change' }],
}

const uploadAccept = computed(() => props.processType === 'unstructured'
  ? '.txt,.md,.pdf,.docx,.doc,.json,.jsonl'
  : '.json,.jsonl,.csv,.xlsx,.xls')

const isExternal = computed(() => props.processType === 'external')

const FILE_PAGE_SIZE = 10
const currentFilePage = ref(1)
const pagedUploadedFiles = computed(() => {
  const start = (currentFilePage.value - 1) * FILE_PAGE_SIZE
  return props.uploadedFiles.slice(start, start + FILE_PAGE_SIZE)
})

watch(() => props.uploadedFiles.length, (newLength, oldLength) => {
  const totalPages = Math.max(1, Math.ceil(newLength / FILE_PAGE_SIZE))
  if (newLength > oldLength) {
    currentFilePage.value = totalPages
    return
  }
  currentFilePage.value = Math.min(currentFilePage.value, totalPages)
})

function formatSize(size: number) {
  if (!size) return '0 KB'
  if (size >= 1024 * 1024) return `${(size / 1024 / 1024).toFixed(1)} MB`
  return `${(size / 1024).toFixed(1)} KB`
}

async function validate() {
  if (!formRef.value) return false
  try {
    await formRef.value.validate()
    if (props.processType === 'structured' && splitTotal.value !== 100) return false
    if (props.processType === 'unstructured') {
      if (unstructuredSplitTotal.value !== 100) return false
      if (chunkValidationMessage.value) return false
      if (props.unstructuredOptions.generationTypes.length === 0) return false
    }
    return true
  } catch {
    return false
  }
}

defineExpose({ validate })
</script>

<template>
  <section class="task-setup-step">
    <el-form ref="formRef" :model="formModel" :rules="rules" label-position="top">
      <div class="form-section">
        <h3>基本信息</h3>
        <div class="basic-grid">
          <el-form-item label="任务名称" prop="name" required>
            <el-input
              :model-value="name"
              maxlength="50"
              show-word-limit
              placeholder="例如：金融问答清洗任务"
              @update:model-value="emit('update:name', $event)"
            />
          </el-form-item>
          <el-form-item label="任务描述（选填）">
            <el-input
              :model-value="description"
              type="textarea"
              :rows="3"
              maxlength="200"
              show-word-limit
              placeholder="简要说明本次数据处理目标"
              @update:model-value="emit('update:description', $event)"
            />
          </el-form-item>
        </div>
      </div>

      <div class="form-section">
        <div class="section-title-row">
          <div>
            <h3>处理类型</h3>
            <p>类型只影响后续预览方式，不会改变四步流程</p>
          </div>
        </div>
        <el-form-item prop="processType" class="type-form-item">
          <div class="type-options">
            <button
              type="button"
              class="type-option"
              :class="{ 'is-active': processType === 'structured' }"
              @click="emit('update:processType', 'structured')"
            >
              <span class="type-icon"><i class="fa fa-table" /></span>
              <span>
                <strong>结构化数据</strong>
                <small>适用于 CSV、Excel、JSONL 等固定字段数据</small>
              </span>
              <i class="fa fa-check-circle selection-mark" />
            </button>
            <button
              type="button"
              class="type-option"
              :class="{ 'is-active': processType === 'unstructured' }"
              @click="emit('update:processType', 'unstructured')"
            >
              <span class="type-icon"><i class="fa fa-file-text-o" /></span>
              <span>
                <strong>非结构化数据</strong>
                <small>适用于文档、文本、问答等需要切分的数据</small>
              </span>
              <i class="fa fa-check-circle selection-mark" />
            </button>
            <button
              type="button"
              class="type-option"
              :class="{ 'is-active': processType === 'external' }"
              @click="emit('update:processType', 'external')"
            >
              <span class="type-icon"><i class="fa fa-cloud-download" /></span>
              <span>
                <strong>外来数据源拉取</strong>
                <small>适用于数据库、API 接口等需远程拉取的外部数据</small>
              </span>
              <i class="fa fa-check-circle selection-mark" />
            </button>
          </div>
        </el-form-item>
      </div>

      <template v-if="processType === 'structured'">
        <div class="form-section structured-options-section">
          <div class="section-title-row">
            <div>
              <h3>预处理选项</h3>
              <p>选择在生成问答对之前需要执行的数据处理方式</p>
            </div>
          </div>
          <el-checkbox-group
            :model-value="structuredOptions.preprocessOptions"
            class="preprocess-option-grid"
            @update:model-value="updatePreprocessOptions"
          >
            <el-checkbox
              v-for="option in PREPROCESS_OPTIONS"
              :key="option.value"
              :value="option.value"
              class="preprocess-option"
            >
              <span class="preprocess-option-copy">
                <strong>{{ option.label }}</strong>
                <small>{{ option.description }}</small>
              </span>
            </el-checkbox>
          </el-checkbox-group>
        </div>

        <div class="form-section generation-options-section">
          <div class="section-title-row">
            <div>
              <h3>生成选项</h3>
              <p>配置每条结构化记录生成问答对的方式和数量</p>
            </div>
          </div>
          <div class="generation-option-list">
            <div class="generation-option-row">
              <div class="generation-option-copy">
                <strong>语义丰富表达</strong>
                <small>使用大模型将问答表述得更自然、柔和</small>
              </div>
              <el-switch
                :model-value="structuredOptions.semanticEnrichment"
                inline-prompt
                active-text="开"
                inactive-text="关"
                @update:model-value="updateSemanticEnrichment"
              />
            </div>
            <div class="generation-option-row">
              <div class="generation-option-copy">
                <strong>每行生成数量</strong>
                <small>每行结构化数据生成的问答对数量</small>
              </div>
              <el-input-number
                :model-value="structuredOptions.qaPairsPerRow"
                :min="1"
                :max="5"
                :step="1"
                controls-position="right"
                @update:model-value="updateStructuredField('qaPairsPerRow', Number($event) || 1)"
              />
            </div>
            <div class="generation-option-row dataset-split-row">
              <div class="generation-option-copy">
                <strong>数据集划分</strong>
                <small>按比例将生成后的问答对随机划分为训练集、验证集和测试集</small>
              </div>
              <div class="dataset-split-config">
                <div class="dataset-split-grid">
                  <label class="dataset-split-field">
                    <span>训练集</span>
                    <span class="dataset-split-input">
                      <el-input-number
                        :model-value="structuredOptions.datasetSplit.train"
                        :min="0"
                        :max="100"
                        :step="1"
                        :precision="0"
                        controls-position="right"
                        aria-label="训练集比例"
                        @update:model-value="updateDatasetSplit('train', $event)"
                      />
                      <span>%</span>
                    </span>
                  </label>
                  <label class="dataset-split-field">
                    <span>验证集</span>
                    <span class="dataset-split-input">
                      <el-input-number
                        :model-value="structuredOptions.datasetSplit.validation"
                        :min="0"
                        :max="100"
                        :step="1"
                        :precision="0"
                        controls-position="right"
                        aria-label="验证集比例"
                        @update:model-value="updateDatasetSplit('validation', $event)"
                      />
                      <span>%</span>
                    </span>
                  </label>
                  <label class="dataset-split-field">
                    <span>测试集</span>
                    <span class="dataset-split-input">
                      <el-input-number
                        :model-value="structuredOptions.datasetSplit.test"
                        :min="0"
                        :max="100"
                        :step="1"
                        :precision="0"
                        controls-position="right"
                        aria-label="测试集比例"
                        @update:model-value="updateDatasetSplit('test', $event)"
                      />
                      <span>%</span>
                    </span>
                  </label>
                </div>
                <div class="dataset-split-summary" :class="{ 'is-invalid': splitTotal !== 100 }">
                  <span>总计 {{ splitTotal }}%</span>
                  <span v-if="splitTotal !== 100" role="alert">训练集、验证集和测试集比例总和必须为 100%</span>
                </div>
              </div>
            </div>
          </div>
        </div>
      </template>

      <template v-if="processType === 'unstructured'">
        <div class="form-section unstructured-options-section">
          <div class="section-title-row">
            <div>
              <h3>预处理选项</h3>
              <p>先清理和补全文档语义，再进入切分和问答生成</p>
            </div>
          </div>
          <el-checkbox-group
            :model-value="unstructuredOptions.preprocessOptions"
            class="preprocess-option-grid"
            @update:model-value="updateUnstructuredPreprocessOptions"
          >
            <el-checkbox
              v-for="option in UNSTRUCTURED_PREPROCESS_OPTIONS"
              :key="option.value"
              :value="option.value"
              class="preprocess-option"
            >
              <span class="preprocess-option-copy">
                <strong>{{ option.label }}</strong>
                <small>{{ option.description }}</small>
              </span>
            </el-checkbox>
          </el-checkbox-group>
        </div>

        <div class="form-section chunk-options-section">
          <div class="section-title-row">
            <div>
              <h3>切分选项</h3>
              <p>以语义完整为优先，将长文档拆成可独立生成问答的内容块</p>
            </div>
          </div>

          <div class="chunk-settings-grid">
            <label class="config-field">
              <span class="config-field-label">切分方式</span>
              <el-select
                :model-value="unstructuredOptions.chunkMethod"
                aria-label="切分方式"
                @update:model-value="updateUnstructuredField('chunkMethod', $event)"
              >
                <el-option
                  v-for="method in CHUNK_METHODS"
                  :key="method.value"
                  :label="method.label"
                  :value="method.value"
                />
              </el-select>
              <small>推荐使用自动语义切分，在长度限制内优先保留完整句段</small>
            </label>

            <label class="config-field">
              <span class="config-field-label">切片长度</span>
              <span class="unit-input">
                <el-input-number
                  :model-value="unstructuredOptions.chunkSize"
                  :min="200"
                  :max="2000"
                  :step="50"
                  :precision="0"
                  controls-position="right"
                  aria-label="切片长度"
                  @update:model-value="updateUnstructuredNumber('chunkSize', $event)"
                />
                <span>Token</span>
              </span>
              <small>单个切片的目标上限，默认 800 Token</small>
            </label>

            <label class="config-field">
              <span class="config-field-label">重叠长度</span>
              <span class="unit-input">
                <el-input-number
                  :model-value="unstructuredOptions.chunkOverlap"
                  :min="0"
                  :max="500"
                  :step="10"
                  :precision="0"
                  controls-position="right"
                  aria-label="重叠长度"
                  @update:model-value="updateUnstructuredNumber('chunkOverlap', $event)"
                />
                <span>Token</span>
              </span>
              <small>在相邻切片中最多重复保留的上下文，默认 100 Token</small>
            </label>

            <label class="config-field">
              <span class="config-field-label">最小切片长度</span>
              <span class="unit-input">
                <el-input-number
                  :model-value="unstructuredOptions.minChunkSize"
                  :min="20"
                  :max="500"
                  :step="10"
                  :precision="0"
                  controls-position="right"
                  aria-label="最小切片长度"
                  @update:model-value="updateUnstructuredNumber('minChunkSize', $event)"
                />
                <span>Token</span>
              </span>
              <small>过短的尾部内容会尽量并入前一个切片</small>
            </label>

            <label
              v-if="unstructuredOptions.chunkMethod === 'custom'"
              class="config-field is-full-width"
            >
              <span class="config-field-label">自定义分隔符</span>
              <el-input
                :model-value="unstructuredOptions.customDelimiter"
                maxlength="40"
                show-word-limit
                placeholder="例如：--- 或 ###"
                aria-label="自定义分隔符"
                @update:model-value="updateUnstructuredField('customDelimiter', $event)"
              />
              <small>系统会优先在分隔符位置结束当前切片</small>
            </label>
          </div>

          <p class="chunk-estimation-note">
            Token 数为轻量估算值，实际长度以训练使用的模型分词器为准。
          </p>

          <p v-if="chunkValidationMessage" class="option-validation-message" role="alert">
            {{ chunkValidationMessage }}
          </p>

          <div class="preserve-options-panel">
            <div class="generation-option-copy">
              <strong>特殊内容保护</strong>
              <small>避免切分点破坏表格、代码或列表的完整性</small>
            </div>
            <div class="preserve-option-grid">
              <el-checkbox
                :model-value="unstructuredOptions.preserveTables"
                @update:model-value="updateUnstructuredField('preserveTables', Boolean($event))"
              >
                完整保留表格
              </el-checkbox>
              <el-checkbox
                :model-value="unstructuredOptions.preserveCodeBlocks"
                @update:model-value="updateUnstructuredField('preserveCodeBlocks', Boolean($event))"
              >
                完整保留代码块
              </el-checkbox>
              <el-checkbox
                :model-value="unstructuredOptions.preserveLists"
                @update:model-value="updateUnstructuredField('preserveLists', Boolean($event))"
              >
                完整保留列表
              </el-checkbox>
            </div>
          </div>
        </div>

        <div class="form-section generation-options-section">
          <div class="section-title-row">
            <div>
              <h3>生成选项</h3>
              <p>配置每个切片的问答生成方式、范围和输出数量</p>
            </div>
          </div>
          <div class="generation-option-list">
            <div class="generation-option-row">
              <div class="generation-option-copy">
                <strong>语义丰富表达</strong>
                <small>使用大模型将问答表述得更自然、柔和</small>
              </div>
              <el-switch
                :model-value="unstructuredOptions.semanticEnrichment"
                inline-prompt
                active-text="开"
                inactive-text="关"
                @update:model-value="updateUnstructuredField('semanticEnrichment', Boolean($event))"
              />
            </div>

            <div class="generation-option-row">
              <div class="generation-option-copy">
                <strong>每个切片生成数量</strong>
                <small>每个内容切片最多生成 3 个不同角度的问答对</small>
              </div>
              <el-input-number
                :model-value="unstructuredOptions.qaPairsPerChunk"
                :min="1"
                :max="3"
                :step="1"
                :precision="0"
                controls-position="right"
                @update:model-value="updateUnstructuredNumber('qaPairsPerChunk', $event)"
              />
            </div>

            <div class="generation-option-row">
              <div class="generation-option-copy">
                <strong>上下文范围</strong>
                <small>生成问答时可参考的文档范围</small>
              </div>
              <el-select
                :model-value="unstructuredOptions.contextScope"
                class="compact-select"
                aria-label="上下文范围"
                @update:model-value="updateUnstructuredField('contextScope', $event)"
              >
                <el-option
                  v-for="scope in CONTEXT_SCOPES"
                  :key="scope.value"
                  :label="scope.label"
                  :value="scope.value"
                />
              </el-select>
            </div>

            <div class="generation-option-row is-stacked">
              <div class="generation-option-copy">
                <strong>问题类型</strong>
                <small>按内容特点选择要生成的问答角度，至少保留一项</small>
              </div>
              <el-checkbox-group
                :model-value="unstructuredOptions.generationTypes"
                class="generation-type-grid"
                @update:model-value="updateGenerationTypes"
              >
                <el-checkbox
                  v-for="type in GENERATION_TYPES"
                  :key="type.value"
                  :value="type.value"
                >
                  {{ type.label }}
                </el-checkbox>
              </el-checkbox-group>
              <span
                v-if="unstructuredOptions.generationTypes.length === 0"
                class="option-validation-message"
                role="alert"
              >
                请至少选择一种问题类型
              </span>
            </div>

            <div class="generation-option-row">
              <div class="generation-option-copy">
                <strong>跳过无法回答的内容</strong>
                <small>当切片缺少完整信息时，不强行编造问答对</small>
              </div>
              <el-switch
                :model-value="unstructuredOptions.skipUnanswerable"
                inline-prompt
                active-text="开"
                inactive-text="关"
                @update:model-value="updateUnstructuredField('skipUnanswerable', Boolean($event))"
              />
            </div>

            <div class="generation-option-row dataset-split-row">
              <div class="generation-option-copy">
                <strong>数据集划分</strong>
                <small>按比例将生成后的问答对随机划分为训练集、验证集和测试集</small>
              </div>
              <div class="dataset-split-config">
                <div class="dataset-split-grid">
                  <label class="dataset-split-field">
                    <span>训练集</span>
                    <span class="dataset-split-input">
                      <el-input-number
                        :model-value="unstructuredOptions.datasetSplit.train"
                        :min="0"
                        :max="100"
                        :step="1"
                        :precision="0"
                        controls-position="right"
                        aria-label="非结构化训练集比例"
                        @update:model-value="updateUnstructuredDatasetSplit('train', $event)"
                      />
                      <span>%</span>
                    </span>
                  </label>
                  <label class="dataset-split-field">
                    <span>验证集</span>
                    <span class="dataset-split-input">
                      <el-input-number
                        :model-value="unstructuredOptions.datasetSplit.validation"
                        :min="0"
                        :max="100"
                        :step="1"
                        :precision="0"
                        controls-position="right"
                        aria-label="非结构化验证集比例"
                        @update:model-value="updateUnstructuredDatasetSplit('validation', $event)"
                      />
                      <span>%</span>
                    </span>
                  </label>
                  <label class="dataset-split-field">
                    <span>测试集</span>
                    <span class="dataset-split-input">
                      <el-input-number
                        :model-value="unstructuredOptions.datasetSplit.test"
                        :min="0"
                        :max="100"
                        :step="1"
                        :precision="0"
                        controls-position="right"
                        aria-label="非结构化测试集比例"
                        @update:model-value="updateUnstructuredDatasetSplit('test', $event)"
                      />
                      <span>%</span>
                    </span>
                  </label>
                </div>
                <div class="dataset-split-summary" :class="{ 'is-invalid': unstructuredSplitTotal !== 100 }">
                  <span>总计 {{ unstructuredSplitTotal }}%</span>
                  <span v-if="unstructuredSplitTotal !== 100" role="alert">
                    训练集、验证集和测试集比例总和必须为 100%
                  </span>
                </div>
              </div>
            </div>
          </div>
        </div>
      </template>

      <div v-if="isExternal" class="form-section external-section">
        <div class="section-title-row">
          <div>
            <h3>数据源配置</h3>
            <p>配置外部数据源并拉取数据，系统将在下一步生成可对照编辑的预览内容</p>
          </div>
        </div>

        <div class="external-form">
          <el-form label-position="top" class="external-grid">
            <el-form-item label="数据源类型">
              <el-select
                :model-value="externalSource.type"
                placeholder="请选择数据源类型"
                @update:model-value="updateExternalField('type', $event)"
              >
                <el-option
                  v-for="item in DATA_SOURCE_TYPES"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value"
                />
              </el-select>
            </el-form-item>
            <el-form-item label="地址 / URL">
              <el-input
                :model-value="externalSource.url"
                placeholder="例如：mysql://host:3306/db 或 https://api.example.com/data"
                @update:model-value="updateExternalField('url', $event)"
              />
            </el-form-item>
            <el-form-item label="鉴权方式">
              <el-select
                :model-value="externalSource.authMode"
                @update:model-value="updateExternalField('authMode', $event)"
              >
                <el-option
                  v-for="item in AUTH_MODES"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value"
                />
              </el-select>
            </el-form-item>
            <el-form-item v-if="externalSource.authMode === 'basic'" label="账号">
              <el-input
                :model-value="externalSource.username"
                placeholder="请输入账号"
                @update:model-value="updateExternalField('username', $event)"
              />
            </el-form-item>
            <el-form-item v-if="externalSource.authMode === 'basic'" label="密码">
              <el-input
                :model-value="externalSource.password"
                type="password"
                show-password
                placeholder="请输入密码"
                @update:model-value="updateExternalField('password', $event)"
              />
            </el-form-item>
            <el-form-item v-if="externalSource.authMode === 'token'" label="Token">
              <el-input
                :model-value="externalSource.token"
                type="password"
                show-password
                placeholder="请输入访问 Token"
                @update:model-value="updateExternalField('token', $event)"
              />
            </el-form-item>
            <el-form-item label="拉取条数">
              <el-input-number
                :model-value="externalSource.limit"
                :min="1"
                :max="100000"
                :step="100"
                controls-position="right"
                @update:model-value="updateExternalField('limit', Number($event) || 0)"
              />
            </el-form-item>
          </el-form>

          <div class="external-actions">
            <el-button :loading="externalPulling && !externalConnected" plain @click="emit('test-connection')">
              测试连接
            </el-button>
            <el-button type="primary" :loading="externalPulling" @click="emit('pull-data')">
              拉取数据
            </el-button>
            <span v-if="externalConnected" class="external-status is-connected">
              <i class="fa fa-check-circle" /> 连接正常
            </span>
          </div>

          <section v-if="uploadedFiles.length" class="uploaded-file-list" aria-label="已拉取数据列表">
            <div class="uploaded-file-list-header">
              <span>已拉取 {{ uploadedFiles.length }} 个数据集</span>
            </div>
            <div class="uploaded-file-items">
              <div v-for="file in pagedUploadedFiles" :key="file.uid" class="uploaded-file">
                <span class="file-icon"><i class="fa fa-cloud-download" /></span>
                <div class="file-main">
                  <strong :title="file.name">{{ file.name }}</strong>
                  <span>{{ formatSize(file.size) }}<template v-if="file.count"> · {{ file.count.toLocaleString() }} 条</template></span>
                </div>
                <span class="file-status"><i class="fa fa-check-circle" /> 拉取成功</span>
                <el-button link type="danger" @click="emit('remove-file', file.uid)">删除</el-button>
              </div>
            </div>
            <el-pagination
              v-if="uploadedFiles.length > FILE_PAGE_SIZE"
              v-model:current-page="currentFilePage"
              :page-size="FILE_PAGE_SIZE"
              :total="uploadedFiles.length"
              :pager-count="5"
              small
              background
              layout="prev, pager, next"
              class="uploaded-file-pagination"
            />
          </section>
        </div>
      </div>

      <div v-else class="form-section upload-section">
        <div class="section-title-row">
          <div>
            <h3>源数据上传</h3>
            <p>系统将在下一步生成可对照编辑的预览内容，支持上传多个文件</p>
          </div>
          <el-button v-if="uploadedFiles.length === 0" link type="primary" @click="emit('use-sample')">
            使用示例数据
          </el-button>
        </div>

        <el-upload
          v-if="uploadedFiles.length === 0"
          drag
          multiple
          :accept="uploadAccept"
          :auto-upload="false"
          :show-file-list="false"
          :on-change="(file: UploadFile) => emit('file-change', file)"
        >
          <i class="fa fa-cloud-upload upload-icon" />
          <div class="el-upload__text">拖拽文件到此处，或<em>点击选择文件</em></div>
          <template #tip>
            <div class="el-upload__tip">
              {{ processType === 'unstructured'
                ? '支持 TXT、Markdown、PDF、Word、JSON、JSONL，单文件不超过 200MB'
                : '支持 JSON、JSONL、CSV、Excel，单文件不超过 200MB' }}
            </div>
          </template>
        </el-upload>

        <section v-else class="uploaded-file-list" aria-label="已上传文件列表">
          <div class="uploaded-file-list-header">
            <span>已添加 {{ uploadedFiles.length }} 个文件</span>
            <div class="continue-upload">
              <el-upload
                multiple
                :accept="uploadAccept"
                :auto-upload="false"
                :show-file-list="false"
                :on-change="(file: UploadFile) => emit('file-change', file)"
              >
                <el-button size="small" type="primary">继续上传</el-button>
              </el-upload>
            </div>
          </div>
          <div class="uploaded-file-items">
            <div v-for="file in pagedUploadedFiles" :key="file.uid" class="uploaded-file">
              <span class="file-icon"><i class="fa fa-file-text-o" /></span>
              <div class="file-main">
                <strong :title="file.name">{{ file.name }}</strong>
                <span>{{ formatSize(file.size) }}<template v-if="file.count"> · {{ file.count.toLocaleString() }} 条</template></span>
              </div>
              <span class="file-status"><i class="fa fa-check-circle" /> 校验通过</span>
              <el-button link type="danger" @click="emit('remove-file', file.uid)">删除</el-button>
            </div>
          </div>
          <el-pagination
            v-if="uploadedFiles.length > FILE_PAGE_SIZE"
            v-model:current-page="currentFilePage"
            :page-size="FILE_PAGE_SIZE"
            :total="uploadedFiles.length"
            :pager-count="5"
            small
            background
            layout="prev, pager, next"
            class="uploaded-file-pagination"
          />
        </section>
      </div>
    </el-form>
  </section>
</template>

<style scoped lang="scss">
.task-setup-step {
  width: 100%;
}


.form-section {
  padding: 0 0 26px;
  margin-bottom: 26px;
  border-bottom: 1px solid #edf0f5;

  &:last-child {
    margin-bottom: 0;
    border-bottom: 0;
  }

  h3 {
    margin: 0 0 16px;
    color: #2f3747;
    font-size: 15px;
    font-weight: 650;
  }
}

.basic-grid {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.preprocess-option-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  margin-top: 16px;
}

.preprocess-option {
  width: 100%;
  min-height: 64px;
  margin-right: 0;
  padding: 12px 14px;
  box-sizing: border-box;
  align-items: flex-start;
  border: 1px solid #e2e5ec;
  border-radius: 8px;
  transition: border-color 0.18s ease, background-color 0.18s ease;

  &:hover {
    border-color: #b7b2f7;
  }

  &.is-checked {
    background: #fafaff;
    border-color: #8b82f4;
  }

  :deep(.el-checkbox__input) {
    margin-top: 3px;
  }

  :deep(.el-checkbox__label) {
    min-width: 0;
    padding-left: 10px;
    white-space: normal;
  }
}

.preprocess-option-copy,
.generation-option-copy {
  display: grid;
  gap: 4px;

  strong {
    color: #344054;
    font-size: 13px;
    font-weight: 600;
  }

  small {
    color: #8a93a3;
    font-size: 12px;
    line-height: 1.5;
  }
}

.generation-option-list {
  display: grid;
  gap: 12px;
  margin-top: 16px;
}

.chunk-settings-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  margin-top: 16px;
}

.config-field {
  display: grid;
  align-content: start;
  gap: 8px;
  min-width: 0;
  padding: 14px;
  color: #344054;
  background: #fbfcfe;
  border: 1px solid #e2e5ec;
  border-radius: 8px;

  &.is-full-width {
    grid-column: 1 / -1;
  }

  > small {
    color: #8a93a3;
    font-size: 12px;
    line-height: 1.5;
  }

  :deep(.el-select),
  :deep(.el-input) {
    width: 100%;
  }
}

.config-field-label {
  font-size: 13px;
  font-weight: 600;
}

.unit-input {
  display: flex;
  align-items: center;
  gap: 8px;
  color: #7b8495;
  font-size: 12px;

  :deep(.el-input-number) {
    width: 100%;
  }
}

.option-validation-message {
  display: block;
  margin: 8px 0 0;
  color: #dc2626;
  font-size: 12px;
  line-height: 1.5;
}

.chunk-estimation-note {
  margin: 9px 0 0;
  color: #8a93a3;
  font-size: 12px;
  line-height: 1.5;
}

.preserve-options-panel {
  display: grid;
  gap: 12px;
  margin-top: 12px;
  padding: 14px;
  border: 1px solid #e2e5ec;
  border-radius: 8px;
}

.preserve-option-grid,
.generation-type-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 24px;

  :deep(.el-checkbox) {
    margin-right: 0;
  }
}

.generation-option-row {
  display: flex;
  min-height: 64px;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: 12px 14px;
  box-sizing: border-box;
  border: 1px solid #e2e5ec;
  border-radius: 8px;
}

.generation-option-row.is-stacked {
  align-items: stretch;
  flex-direction: column;
  gap: 12px;
}

.compact-select {
  width: 220px;
  max-width: 100%;
}

.dataset-split-row {
  align-items: stretch;
  flex-direction: column;
}

.dataset-split-config {
  display: grid;
  gap: 10px;
}

.dataset-split-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
}

.dataset-split-field {
  display: grid;
  gap: 7px;
  color: #5f6878;
  font-size: 12px;
}

.dataset-split-input {
  display: flex;
  align-items: center;
  gap: 8px;

  :deep(.el-input-number) {
    width: 100%;
  }
}

.dataset-split-summary {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  color: #2ca66a;
  font-size: 12px;

  &.is-invalid {
    color: #dc2626;
  }
}

.section-title-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;

  h3 {
    margin-bottom: 5px;
  }

  p {
    margin: 0;
    color: #8a93a3;
    font-size: 12px;
  }
}

.type-form-item {
  margin-top: 16px;
}

.type-options {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
  width: 100%;
}

.type-option {
  position: relative;
  display: flex;
  align-items: center;
  gap: 14px;
  min-height: 96px;
  padding: 18px;
  color: #4b5563;
  text-align: left;
  background: #fff;
  border: 1px solid #dfe3ea;
  border-radius: 9px;
  cursor: pointer;
  transition: border-color 0.18s ease, background-color 0.18s ease;

  &:hover {
    border-color: #a8a3ff;
  }

  &.is-active {
    background: #fafaff;
    border-color: #5b50f2;
    box-shadow: 0 0 0 1px rgba(91, 80, 242, 0.08);
  }

  strong,
  small {
    display: block;
  }

  strong {
    margin-bottom: 6px;
    color: #262d3d;
    font-size: 14px;
  }

  small {
    color: #7b8495;
    font-size: 12px;
    line-height: 1.6;
  }
}

.type-icon,
.file-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  color: #5b50f2;
  font-size: 18px;
  background: #f0efff;
  border-radius: 9px;
}

.selection-mark {
  position: absolute;
  top: 12px;
  right: 12px;
  color: #5b50f2;
  opacity: 0;
}

.type-option.is-active .selection-mark {
  opacity: 1;
}

.external-section {
  .external-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 18px 20px;
    margin-top: 16px;
  }

  :deep(.el-form-item) {
    margin-bottom: 0;
  }

  :deep(.el-select),
  :deep(.el-input),
  :deep(.el-input-number) {
    width: 100%;
  }
}

.external-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin-top: 22px;
}

.external-status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;

  &.is-connected {
    color: #2ca66a;
  }
}

.upload-section :deep(.el-upload) {
  width: 100%;
  margin-top: 16px;
}

.upload-section :deep(.el-upload-dragger) {
  width: 100%;
  min-height: 154px;
  padding: 32px 20px;
  background: #fbfcfe;
  border-color: #dfe3ea;
}

.upload-icon {
  margin-bottom: 12px;
  color: #5b50f2;
  font-size: 30px;
}

.uploaded-file-list {
  margin-top: 20px;
  overflow: hidden;
  background: #fff;
  border: 1px solid #dfe3ea;
  border-radius: 8px;
}

.uploaded-file-list-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 14px;
  color: #5f6878;
  font-size: 12px;
  background: #fbfcfe;
  border-bottom: 1px solid #edf0f5;
}

.continue-upload {
  flex: 0 0 auto;
}

.continue-upload :deep(.el-upload) {
  width: auto;
  margin-top: 0;
}

.uploaded-file-pagination {
  display: flex;
  justify-content: flex-end;
  padding: 10px 14px;
  border-top: 1px solid #edf0f5;
}

.uploaded-file {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 48px;
  padding: 8px 14px;
  border-bottom: 1px solid #edf0f5;

  &:last-child {
    border-bottom: 0;
  }
}

.file-icon {
  width: 28px;
  height: 28px;
  font-size: 14px;
  border-radius: 7px;
}

.file-main {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 5px;
  min-width: 0;

  strong {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: #273142;
    font-size: 14px;
  }

  span {
    color: #8a93a3;
    font-size: 12px;
  }
}

.file-status {
  color: #2ca66a;
  font-size: 12px;
}

.uploaded-file :deep(.el-button) {
  flex: 0 0 auto;
}

@media (max-width: 900px) {
  .type-options {
    grid-template-columns: minmax(0, 1fr);
  }

  .preprocess-option-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .chunk-settings-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .config-field.is-full-width {
    grid-column: auto;
  }

  .dataset-split-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .dataset-split-summary {
    align-items: flex-start;
    flex-direction: column;
  }

  .generation-option-row:not(.dataset-split-row):not(.is-stacked) {
    align-items: flex-start;
    flex-direction: column;
  }

  .external-section .external-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .uploaded-file {
    gap: 8px;
    padding: 8px 10px;
  }

  .file-status {
    flex: 0 1 auto;
    line-height: 1.4;
    white-space: normal;
  }

  .uploaded-file-pagination {
    justify-content: center;
  }
}
</style>
