<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
import PageCard from '@/components/PageCard.vue'
import ModelSelectDialog from '@/components/ModelSelectDialog.vue'
import {
  createFineTune,
  startFineTune,
  updateFineTune,
  checkFineTuneName,
} from '@/api/modules/fineTune'
import { getModelList } from '@/api/modules/model'
import { getDatasetList } from '@/api/modules/dataset'
import { getSystemInfo } from '@/api/modules/system'
import { TEMPLATE_GROUPS, LR_SCHEDULER_OPTIONS, QUANTIZATION_BIT_OPTIONS, QUANT_METHOD_OPTIONS, GGUF_FORMAT_OPTIONS } from '@/constants'
import {
  DEFAULT_TRAINING_PARAMS,
  buildFineTuneCommand,
  buildFineTunePayload,
  createDefaultFineTuneForm,
  toCreateFineTunePayload,
} from './fineTuneFormModel'
import type { FineTuneFormModel } from './fineTuneFormModel'
import type { ModelItem, DatasetItem, GpuInfo } from '@/types'

const router = useRouter()
const formRef = ref<FormInstance>()
const submitting = ref(false)

const models = ref<ModelItem[]>([])
const datasets = ref<DatasetItem[]>([])
const gpus = ref<GpuInfo[]>([])
const selectedGpus = ref<number[]>([])
const modelDialogVisible = ref(false)

const form = reactive(createDefaultFineTuneForm())

const rules: FormRules = {
  name: [
    { required: true, message: '请输入任务名称', trigger: 'blur' },
    {
      pattern: /^[a-zA-Z0-9_]+$/,
      message: '仅支持字母、数字、下划线',
      trigger: 'blur',
    },
    { max: 50, message: '不超过 50 字符', trigger: 'blur' },
  ],
  base_model: [{ required: true, message: '请选择模型', trigger: 'change' }],
  template: [{ required: true, message: '请选择训练模板', trigger: 'change' }],
  train_dataset_id: [{ required: true, message: '请选择训练数据集', trigger: 'change' }],
}

const showLoraParams = computed(() => form.train_method === 'lora')

const selectedModel = computed(() => models.value.find((model) => model.id === form.base_model))

const modelDialogTitle = computed(() => selectedModel.value?.name || '')

/** 训练命令与提交载荷共用同一份表单模型。 */
const commandPreview = computed(() => buildFineTuneCommand(form, selectedGpus.value))

/** GPU 多选切换 */
function toggleGpu(index: number) {
  const idx = selectedGpus.value.indexOf(index)
  if (idx === -1) selectedGpus.value.push(index)
  else selectedGpus.value.splice(idx, 1)
}

function gpuUsageWidth(percent: number) {
  return `${Math.max(0, Math.min(percent, 100))}%`
}

function openModelDialog() {
  modelDialogVisible.value = true
}

/** 模型选择弹窗确认 */
function handleModelConfirm(modelId: string | number) {
  form.base_model = modelId
  formRef.value?.validateField('base_model')
}

function resetParams() {
  Object.assign(form, DEFAULT_TRAINING_PARAMS)
}

const isParamsExpanded = ref(false)

interface ParameterDefinition {
  key: keyof FineTuneFormModel
  name: string
  desc: string
  hint: string
  type: 'number' | 'select'
  min?: number
  max?: number
  step?: number
  precision?: number
  options?: Array<{ label: string; value: string | number }>
}

const allParams = computed(() => {
  const params: ParameterDefinition[] = [
    { key: 'batch_size', name: 'batch_size', desc: '批次大小，代表模型训练过程中，模型更新一次参数所需要的数据样本数。', hint: '[1, 64], step:1', type: 'number', min: 1, max: 64, step: 1 },
    { key: 'learning_rate', name: 'learning_rate', desc: '学习率，代表每次更新数据的增量参数权重比例。', hint: '[0.000001, 1]', type: 'number', min: 0.000001, max: 1, step: 0.00001, precision: 6 },
    { key: 'n_epochs', name: 'n_epochs', desc: '循环次数，代表模型训练过程中模型学习数据集的次数，可理解为看几遍数据，一般建议的范围是 1-3 遍即可，可依据需求进行调整', hint: '[1, 100], step:1', type: 'number', min: 1, max: 100, step: 1 },
    { key: 'save_steps', name: 'save_steps', desc: '保存步数，训练阶段模型保存的间隔步长。', hint: '[10, 10000]', type: 'number', min: 10, max: 10000, step: 1 },
    { key: 'lr_scheduler_type', name: 'lr_scheduler_type', desc: '学习率调整策略，选择不同的学习率策略。', hint: '', type: 'select', options: LR_SCHEDULER_OPTIONS },
    { key: 'max_length', name: 'max_length', desc: '序列长度，单个训练数据样本的最大长度。', hint: '[64, 4096]', type: 'number', min: 64, max: 4096, step: 1 },
    { key: 'warmup_ratio', name: 'warmup_ratio', desc: '学习率预热比例，学习率预热阶段占总训练步数的比例。', hint: '[0, 1]', type: 'number', min: 0, max: 1, step: 0.01, precision: 2 },
    { key: 'weight_decay', name: 'weight_decay', desc: '权重衰减，用于在优化过程中对模型参数施加惩罚，防止过拟合。', hint: '[0, 1]', type: 'number', min: 0, max: 1, step: 0.01, precision: 2 },
  ]
  if (showLoraParams.value) {
    params.push(
      { key: 'lora_alpha', name: 'lora_alpha', desc: 'LoRA 缩放系数。', hint: '16/32/64/128', type: 'select', options: [{ label: '16', value: 16 }, { label: '32', value: 32 }, { label: '64', value: 64 }, { label: '128', value: 128 }] },
      { key: 'lora_rank', name: 'lora_rank', desc: 'LoRA 秩大小，控制低秩矩阵的维度。', hint: '8/16/32/64', type: 'select', options: [{ label: '8', value: 8 }, { label: '16', value: 16 }, { label: '32', value: 32 }, { label: '64', value: 64 }] },
      { key: 'lora_dropout', name: 'lora_dropout', desc: 'LoRA 层的 dropout 比例。', hint: '[0, 1]', type: 'number', min: 0, max: 1, step: 0.05, precision: 2 },
    )
  }
  return params
})

const visibleParams = computed(() => {
  return isParamsExpanded.value ? allParams.value : allParams.value.slice(0, 3)
})

function parameterValue(key: keyof FineTuneFormModel) {
  const value = form[key]
  return typeof value === 'boolean' ? Number(value) : value
}

function updateParameterValue(key: keyof FineTuneFormModel, value: string | number | undefined) {
  if (value !== undefined) Reflect.set(form, key, value)
}

async function loadModels() {
  try {
    models.value = (await getModelList()) || []
  } catch {
    models.value = []
  }
}

async function loadDatasets() {
  try {
    datasets.value = (await getDatasetList()) || []
  } catch {
    datasets.value = []
  }
}

async function loadGpus() {
  try {
    const sys = await getSystemInfo()
    gpus.value = sys?.gpu || []
    // 默认选中第一个
    if (gpus.value.length > 0) selectedGpus.value = [0]
  } catch {
    gpus.value = []
  }
}

async function handleSubmit() {
  if (!formRef.value) return
  await formRef.value.validate(async (valid) => {
    if (!valid) return
    if (selectedGpus.value.length === 0) {
      ElMessage.warning('请至少选择一个 GPU')
      return
    }
    submitting.value = true
    try {
      let check: { exists: boolean }
      try {
        check = await checkFineTuneName(form.name)
      } catch {
        ElMessage.error('任务名校验失败，请稍后重试')
        return
      }
      if (check.exists) {
        ElMessage.error('任务名称已存在，请更换')
        return
      }

      const payload = buildFineTunePayload(form, selectedGpus.value)
      const createRes = await createFineTune(toCreateFineTunePayload(payload))
      const taskId = createRes.id

      try {
        await startFineTune({ ...payload, task_id: taskId })
        ElMessage.success('训练任务已创建并启动')
      } catch {
        await updateFineTune(taskId, { status: 'failed' })
        ElMessage.error('任务已创建，但训练启动失败')
      }
      router.push('/fine-tune')
    } catch {
      ElMessage.error('训练任务创建失败，请稍后重试')
    } finally {
      submitting.value = false
    }
  })
}

function handleCancel() {
  router.back()
}

onMounted(() => {
  loadModels()
  loadDatasets()
  loadGpus()
})
</script>

<template>
  <div class="fine-tune-create page-card-host has-fixed-footer">
    <PageCard title="创建训练任务">
      <el-form ref="formRef" :model="form" :rules="rules" label-width="140px" label-position="left">
        <!-- 基本信息 -->
        <el-divider content-position="left">基本信息</el-divider>
        <el-form-item label="任务名称" prop="name">
          <el-input v-model="form.name" placeholder="字母、数字、下划线" maxlength="50" show-word-limit style="width: 420px;" />
        </el-form-item>
        <el-form-item label="任务描述">
          <el-input v-model="form.description" type="textarea" :rows="4" maxlength="200" show-word-limit style="width: 600px;" />
        </el-form-item>

        <!-- 训练配置 -->
        <el-divider content-position="left">训练配置</el-divider>
        <el-form-item label="GPU 硬件">
          <div class="gpu-list">
            <div
              v-for="(gpu, idx) in gpus"
              :key="idx"
              class="gpu-card"
              :class="{ active: selectedGpus.includes(idx), 'is-busy': gpu.gpu_percent > 80 }"
              @click="toggleGpu(idx)"
            >
              <div class="gpu-card-top">
                <div class="gpu-title">
                  <span class="gpu-index">GPU-{{ idx }}</span>
                  <span class="gpu-name">{{ gpu.name }}</span>
                </div>
                <span class="gpu-usage">{{ gpu.gpu_percent }}%</span>
              </div>
              <div class="gpu-usage-bar">
                <span :style="{ width: gpuUsageWidth(gpu.gpu_percent) }" />
              </div>
              <div class="gpu-meta">
                <span>显存 {{ gpu.memory_used_gb }}/{{ gpu.memory_total_gb }}GB</span>
                <span>{{ gpu.temperature }}°C</span>
                <span>{{ gpu.power_w }}W</span>
              </div>
            </div>
            <div v-if="!gpus.length" class="gpu-empty">暂无 GPU 信息</div>
          </div>
        </el-form-item>

        <el-form-item label="训练方式">
          <el-radio-group v-model="form.train_type">
            <el-radio-button value="SFT">SFT 微调训练</el-radio-button>
            <el-radio-button value="DPO">DPO 偏好训练</el-radio-button>
            <el-radio-button value="CPT">CPT 继续预训练</el-radio-button>
          </el-radio-group>
        </el-form-item>

        <el-form-item label="选择模型" prop="base_model">
          <el-input
            class="model-picker-input"
            :model-value="modelDialogTitle"
            placeholder="请选择基座模型"
            readonly
            @click="openModelDialog"
            style="width: 420px;"
          >
            <template #suffix>
              <i class="fa fa-angle-right" />
            </template>
          </el-input>
        </el-form-item>

        <el-form-item label="训练模板" prop="template">
          <el-select v-model="form.template" placeholder="请选择训练模板" filterable style="width: 420px;">
            <el-option-group v-for="group in TEMPLATE_GROUPS" :key="group.label" :label="group.label">
              <el-option v-for="opt in group.options" :key="opt.value" :label="opt.label" :value="opt.value" />
            </el-option-group>
          </el-select>
        </el-form-item>

        <el-form-item label="训练方法">
          <el-radio-group v-model="form.train_method">
            <el-radio value="lora">LoRA（高效微调）</el-radio>
            <el-radio value="full">全参微调</el-radio>
          </el-radio-group>
        </el-form-item>

        <!-- 超参配置 -->
        <el-divider content-position="left">
          <span style="font-size: 15px; font-weight: 600; color: #1f2937; margin-right: 12px;">超参配置</span>
          <el-button link type="primary" size="small" @click="resetParams">
            <i class="fa fa-refresh" style="margin-right: 4px;" /> 恢复默认配置
          </el-button>
        </el-divider>

        <div class="hyperparam-section">
          <div class="param-table-wrapper">
            <div class="param-table">
              <div class="param-header">
                <div class="param-col name">参数名称</div>
                <div class="param-col config">配置</div>
                <div class="param-col desc">说明</div>
              </div>
              <div class="param-row" v-for="param in visibleParams" :key="param.key">
                <div class="param-col name">{{ param.name }}</div>
                <div class="param-col config">
                  <el-input-number 
                    v-if="param.type === 'number'" 
                    :model-value="Number(parameterValue(param.key))"
                    :min="param.min" :max="param.max" :step="param.step" :precision="param.precision" 
                    controls-position="right"
                    style="width: 200px"
                    @update:model-value="updateParameterValue(param.key, $event)"
                  />
                  <el-select 
                    v-else-if="param.type === 'select'" 
                    :model-value="parameterValue(param.key)"
                    style="width: 200px"
                    @update:model-value="updateParameterValue(param.key, $event)"
                  >
                    <el-option v-for="o in param.options" :key="o.value" :label="o.label" :value="o.value" />
                  </el-select>
                  <span class="param-hint" v-if="param.hint">{{ param.hint }}</span>
                </div>
                <div class="param-col desc">
                  <el-tooltip :content="param.desc" placement="top" effect="dark" :show-after="200">
                    <span class="desc-text">{{ param.desc }}</span>
                  </el-tooltip>
                </div>
              </div>
            </div>
            <div class="param-footer">
              <el-button link type="primary" @click="isParamsExpanded = !isParamsExpanded">
                <i :class="isParamsExpanded ? 'fa fa-angle-up' : 'fa fa-angle-down'" style="margin-right: 4px;" />
                {{ isParamsExpanded ? '收起配置' : '展开配置' }}
              </el-button>
            </div>
          </div>
        </div>

        <!-- 数据配置 -->
        <el-divider content-position="left">数据配置</el-divider>
        <el-form-item label="训练数据集" prop="train_dataset_id">
          <el-select v-model="form.train_dataset_id" placeholder="请选择训练数据集" filterable style="width: 420px;">
            <el-option v-for="d in datasets" :key="d.id" :label="d.name" :value="d.id" />
          </el-select>
        </el-form-item>

        <template v-if="form.train_type === 'SFT'">
          <el-divider content-position="left">合并模型</el-divider>
          <el-form-item label="自动合并权重并保存">
            <el-select v-model="form.auto_merge" style="width: 420px;">
              <el-option label="否" :value="false" />
              <el-option label="是" :value="true" />
            </el-select>
          </el-form-item>
        </template>

        <!-- 模型量化 -->
        <el-divider content-position="left">模型量化</el-divider>
        <!-- 训练时量化（QLoRA）：仅 LoRA 训练时可用 -->
        <el-form-item v-if="form.train_method === 'lora'" label="训练时量化">
          <el-select v-model="form.quantization_bit" style="width: 420px;">
            <el-option
              v-for="opt in QUANTIZATION_BIT_OPTIONS"
              :key="opt.value"
              :label="opt.label"
              :value="opt.value"
            />
          </el-select>
        </el-form-item>

        <!-- 训练后导出量化模型 -->
        <el-form-item label="导出量化模型">
          <el-select v-model="form.export_quantized" style="width: 420px;">
            <el-option label="否" :value="false" />
            <el-option label="是" :value="true" />
          </el-select>
        </el-form-item>

        <template v-if="form.export_quantized">
          <el-form-item label="量化方法">
            <el-select v-model="form.quant_method" style="width: 420px;">
              <el-option
                v-for="opt in QUANT_METHOD_OPTIONS"
                :key="opt.value"
                :label="opt.label"
                :value="opt.value"
              />
            </el-select>
          </el-form-item>

          <el-form-item label="量化位数">
            <el-input-number
              v-model="form.quant_bits"
              :min="form.quant_method === 'bnb' ? 4 : 2"
              :max="form.quant_method === 'bnb' ? 8 : 16"
              :step="1"
              controls-position="right"
              style="width: 200px"
            />
          </el-form-item>

          <el-form-item v-if="form.quant_method === 'gptq' || form.quant_method === 'awq'" label="分组大小">
            <el-input-number
              v-model="form.quant_group_size"
              :min="32"
              :max="1024"
              :step="32"
              controls-position="right"
              style="width: 200px"
            />
          </el-form-item>

          <el-form-item v-if="form.quant_method === 'gguf'" label="导出格式">
            <el-select v-model="form.export_format" style="width: 420px;">
              <el-option
                v-for="opt in GGUF_FORMAT_OPTIONS"
                :key="opt.value"
                :label="opt.label"
                :value="opt.value"
              />
            </el-select>
          </el-form-item>
        </template>

        <!-- 训练命令预览 -->
        <el-divider content-position="left">训练命令预览</el-divider>
        <div class="command-preview-wrapper">
          <pre class="command-preview">{{ commandPreview }}</pre>
        </div>

        <div class="form-actions-wrapper">
          <el-button type="primary" :loading="submitting" @click="handleSubmit">创建并启动训练</el-button>
          <el-button @click="handleCancel">取消</el-button>
        </div>
      </el-form>
    </PageCard>

    <ModelSelectDialog
      v-model="modelDialogVisible"
      :models="models"
      :current-model-id="form.base_model"
      @confirm="handleModelConfirm"
    />
  </div>
</template>

<style scoped lang="scss">
.fine-tune-create {
  :deep(.page-card) {
    margin-bottom: 0;
  }
}

.gpu-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 8px;
  width: 100%;
}

.gpu-card {
  border: 1px solid #e5e7eb;
  border-radius: 6px;
  padding: 9px 12px;
  cursor: pointer;
  transition: border-color 0.2s, box-shadow 0.2s, background 0.2s;
  background: #fff;

  &:hover {
    border-color: #94a3b8;
    box-shadow: 0 4px 12px rgba(15, 23, 42, 0.06);
  }

  &.active {
    border-color: #2563eb;
    background: #f8fbff;
    box-shadow: inset 0 0 0 1px #2563eb;
  }

  &.is-busy {
    border-color: #f59e0b;
    background: #fffaf0;

    .gpu-usage {
      color: #b45309;
      background: #fffbeb;
      border-color: #fcd34d;
    }

    .gpu-usage-bar span {
      background: #f59e0b;
    }
  }

  &.is-busy.active {
    border-color: #dc2626;
    background: #fff7f7;
    box-shadow: inset 0 0 0 1px #dc2626;

    .gpu-usage {
      color: #dc2626;
      background: #fef2f2;
      border-color: #fecaca;
    }

    .gpu-usage-bar span {
      background: #dc2626;
    }
  }
}

.gpu-card-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
}

.gpu-title {
  min-width: 0;
  display: grid;
  gap: 2px;
}

.gpu-index {
  font-size: 11px;
  line-height: 1;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: #64748b;
}

.gpu-name {
  font-size: 13px;
  font-weight: 600;
  color: #1f2937;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.gpu-usage {
  height: 20px;
  min-width: 40px;
  padding: 0 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid #bfdbfe;
  border-radius: 4px;
  background: #eff6ff;
  color: #2563eb;
  font-size: 11px;
  font-weight: 700;
  white-space: nowrap;
}

.gpu-usage-bar {
  height: 3px;
  margin: 7px 0 6px;
  overflow: hidden;
  border-radius: 999px;
  background: #eef2f7;

  span {
    display: block;
    height: 100%;
    border-radius: inherit;
    background: #2563eb;
  }
}

.gpu-meta {
  display: flex;
  flex-wrap: nowrap;
  gap: 10px;
  font-size: 11px;
  color: #64748b;
  line-height: 1.4;
  white-space: nowrap;
  overflow: hidden;
}

.gpu-empty {
  color: #909399;
  font-size: 13px;
}

.command-preview-wrapper {
  margin-left: 80px;
  margin-bottom: 22px;
}

.field-tip {
  color: #909399;
  font-size: 12px;
  line-height: 20px;
  margin-top: 4px;
}

.form-actions-wrapper {
  position: fixed;
  bottom: 0;
  left: 240px;
  right: 0;
  height: 56px;
  background: #fff;
  border-top: 1px solid #eef0f5;
  display: flex;
  align-items: center;
  padding-left: 112px;
  z-index: 1000;
  box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.05);
}

.command-preview {
  background: #1e1e1e;
  color: #d4d4d4;
  padding: 12px 16px;
  border-radius: 6px;
  font-family: 'SFMono-Regular', Consolas, monospace;
  font-size: 12px;
  line-height: 1.6;
  overflow-x: auto;
  width: 100%;
  margin: 0;
}

.model-picker-input {
  cursor: pointer;

  :deep(.el-input__wrapper) {
    cursor: pointer;
  }

  :deep(.el-input__inner) {
    cursor: pointer;
  }
}


/* 训练参数表格样式 */
.hyperparam-section {
  margin: 32px 0 20px;
}
.hyperparam-title {
  font-size: 16px;
  font-weight: 700;
  color: #1f2937;
  margin-bottom: 24px;
}
.param-config-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}
.param-config-title {
  font-size: 14px;
  color: #475569;
  font-weight: 600;
}
.param-table-wrapper {
  margin-left: 80px;
  border: 1px solid #eef0f5;
  border-radius: 6px;
  overflow: hidden;
  background: #fff;
}
:deep(.el-form-item) {
  margin-left: 80px;

  .el-form-item__label {
    position: relative;
    padding-left: 0; /* ensure it starts at 0 */
  }

  &.is-required:not(.is-no-asterisk) > .el-form-item__label::before {
    position: absolute;
    left: -10px;
    margin-right: 0;
  }
}
.param-header, .param-row {
  display: grid;
  grid-template-columns: 140px 420px 1fr;
  align-items: center;
}
.param-header {
  background: #f8fafc;
  border-bottom: 1px solid #eef0f5;
  color: #64748b;
  font-size: 13px;
  font-weight: 600;
}
.param-row {
  border-bottom: 1px solid #eef0f5;
}
.param-col {
  padding: 16px 20px;
}
.param-col.name {
  font-weight: 600;
  color: #475569;
  font-size: 14px;
  text-align: left;
}
.param-col.config {
  display: flex;
  align-items: center;
  gap: 12px;
}
.param-col.desc {
  color: #64748b;
  font-size: 13px;
  min-width: 0;
}
.desc-text {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: default;
}
.param-hint {
  color: #94a3b8;
  font-size: 13px;
}
.param-footer {
  padding: 12px 20px;
}

@media (min-width: 1280px) {
  .gpu-list {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}
</style>
