<template>
  <div class="ctms-page setup-page">
    <div class="setup-shell unified-shell">
      <div class="setup-header">
      <div class="setup-flow">
        <div
          v-for="(step, index) in steps"
          :key="step.key"
          class="flow-step"
          :class="{ active: index === activeStep, done: isStepCompleted(index), 'has-error': stepHasErrors(index) }"
          @click="goStep(index)"
        >
          <span class="flow-index">{{ index + 1 }}</span>
          <span class="flow-label">{{ step.label }}</span>
          <el-icon v-if="isStepCompleted(index) && !stepHasErrors(index)" class="flow-done-icon"><SuccessFilled /></el-icon>
          <span v-if="index < steps.length - 1" class="flow-connector" />
        </div>
      </div>

      <div class="setup-toolbar">
        <div class="setup-switcher-wrap">
          <div v-if="project?.is_locked" class="setup-switcher">
            <el-tag type="warning" effect="plain">
              <el-icon><Lock /></el-icon>
              已锁定
            </el-tag>
          </div>
          <div class="setup-save-meta">
            <span class="meta-text">{{ setupSaveMetaText }}</span>
            <el-tag size="small" :type="setupSyncTagType">{{ setupSyncTagLabel }}</el-tag>
            <el-tag size="small" :type="setupPublishStatus === 'PUBLISHED' ? 'success' : 'info'">
              {{ setupPublishStatus === "PUBLISHED" ? "已发布" : "草稿中" }}
            </el-tag>
            <el-tag v-if="setupPublishStatus === 'PUBLISHED'" size="small" type="warning">
              发布时间 {{ setupPublishedAt || "-" }}
            </el-tag>
          </div>
          <div class="setup-view-mode">
            <el-radio-group v-model="setupViewMode" size="small">
              <el-radio-button label="draft">草稿视图</el-radio-button>
              <el-radio-button label="published">发布预览</el-radio-button>
            </el-radio-group>
          </div>
        </div>

        <div class="setup-toolbar-actions">
          <el-button type="success" plain :loading="primarySaveLoading" @click="handlePrimarySaveConfig">保存配置</el-button>
          <el-button type="warning" plain :loading="publishConfirmLoading" @click="publishConfigNow">发布版本</el-button>
          <el-button
            type="info"
            plain
            @click="openRollbackDialog"
          >
            回滚版本
          </el-button>
          <el-dropdown trigger="click" class="setup-secondary-menu" @command="handleSecondaryAction">
            <el-button plain class="secondary-menu-btn">
              更多
              <el-icon class="el-icon--right"><ArrowDown /></el-icon>
            </el-button>
            <template #dropdown>
              <el-dropdown-menu>
                <el-dropdown-item command="enter">
                  <el-icon><Promotion /></el-icon>
                  进入项目
                </el-dropdown-item>
                <el-dropdown-item command="members">
                  <el-icon><User /></el-icon>
                  项目账号/成员配置
                </el-dropdown-item>
                <el-dropdown-item command="sites">
                  <el-icon><OfficeBuilding /></el-icon>
                  中心管理
                </el-dropdown-item>
              </el-dropdown-menu>
            </template>
          </el-dropdown>
        </div>
      </div>
      </div>
      <div v-if="currentStepErrors.length" class="setup-step-errors">
        <el-alert
          type="error"
          show-icon
          :closable="false"
          :title="`当前步骤有 ${currentStepErrors.length} 项需修正`"
          :description="currentStepErrors[0]?.message"
        />
      </div>
      <div
        v-if="drawerVisible"
        class="setup-edit-backdrop"
        :class="{ 'is-closing': drawerClosing }"
        @click="closeEditDrawer"
      />
      <div
        class="setup-content"
        :class="{
          'setup-content-drawer': drawerVisible,
          'is-closing': drawerClosing,
          'is-published-preview': isPublishedView,
          'is-draft-preview': !isPublishedView && !isEditing,
        }"
      >
        <div v-if="!drawerVisible" class="setup-content-step-title">
          <div class="setup-content-step-nav">
            <el-button circle :disabled="activeStep <= 0" @click="prevStep">
              <el-icon><ArrowLeft /></el-icon>
            </el-button>
            <el-button circle :disabled="activeStep >= steps.length - 1" @click="nextStep">
              <el-icon><ArrowRight /></el-icon>
            </el-button>
          </div>
          <span>{{ currentStepTitle }}</span>
          <div class="setup-content-step-actions">
            <template v-if="stepActionMode === 'edit'">
              <template v-if="(activeStep === 2 || activeStep === 4) && isEditing && !isPublishedView">
                <el-button
                  class="step-action-btn step-action-btn-cancel"
                  :disabled="drawerConfirmLoading || submitting"
                  @click="cancelEdit"
                >
                  取消
                </el-button>
                <el-button
                  class="step-action-btn"
                  type="primary"
                  :loading="drawerConfirmLoading || submitting"
                  :disabled="!canEditSetup"
                  @click="handleDrawerConfirm"
                >
                  保存
                </el-button>
              </template>
              <el-button v-else class="step-action-btn" type="primary" :disabled="!canEditSetup" @click="startEdit">编辑</el-button>
            </template>
            <template v-else-if="stepActionMode === 'project-milestone'">
              <el-dropdown trigger="click" @command="onProjectMilestoneTemplateSelect">
                <el-button class="step-action-btn" type="primary" :disabled="!canEditSetup">
                  <span>选择模板</span>
                </el-button>
                <template #dropdown>
                  <el-dropdown-menu>
                    <el-dropdown-item
                      v-for="item in projectMilestoneTemplates"
                      :key="item.key"
                      :command="item.key"
                    >
                      {{ item.label }}
                    </el-dropdown-item>
                  </el-dropdown-menu>
                </template>
              </el-dropdown>
              <el-button class="step-action-btn" type="primary" :disabled="!canEditSetup" @click="handleAddProjectMilestoneAction">新增</el-button>
            </template>
            <template v-else-if="stepActionMode === 'site-milestone'">
              <el-dropdown trigger="click" @command="onSiteMilestoneTemplateSelect">
                <el-button class="step-action-btn" type="primary" :disabled="!canEditSetup">
                  <span>选择模板</span>
                </el-button>
                <template #dropdown>
                  <el-dropdown-menu>
                    <el-dropdown-item
                      v-for="item in siteMilestoneTemplates"
                      :key="item.key"
                      :command="item.key"
                    >
                      {{ item.label }}
                    </el-dropdown-item>
                  </el-dropdown-menu>
                </template>
              </el-dropdown>
              <el-button class="step-action-btn" type="primary" :disabled="!canEditSetup" @click="handleAddSiteMilestoneAction">新增</el-button>
            </template>
            <template v-else-if="stepActionMode === 'monitoring-strategy'">
              <el-dropdown trigger="click" @command="onMonitoringStrategyTemplateSelect">
                <el-button class="step-action-btn" type="primary" :disabled="!canEditSetup">
                  <span>选择模板</span>
                </el-button>
                <template #dropdown>
                  <el-dropdown-menu>
                    <el-dropdown-item
                      v-for="item in monitoringStrategyTemplates"
                      :key="item.key"
                      :command="item.key"
                    >
                      {{ item.label }}
                    </el-dropdown-item>
                  </el-dropdown-menu>
                </template>
              </el-dropdown>
              <el-button class="step-action-btn" type="primary" :disabled="!canEditSetup" @click="addMonitoringStrategy">新增</el-button>
            </template>
          </div>
        </div>
        <div v-if="drawerVisible" class="setup-drawer-header">
          <div class="setup-drawer-title-wrap">
            <div class="setup-drawer-title">{{ currentStepTitle }}</div>
          </div>
        </div>

    <section v-show="activeStep === 0" class="setup-section">
      <template v-if="project">
        <el-tabs v-model="infoTab" class="setup-info-tabs">
          <el-tab-pane v-if="showStep1BasicTab" label="基本信息" name="basic">
            <el-form
              v-if="isEditing && !isPublishedView"
              ref="formRef"
              :model="form"
              :rules="rules"
              label-width="120px"
              class="detail-form"
              label-position="top"
            >
              <div v-if="canEditStep1BasicGroup('project')" class="basic-edit-group">
                <div class="basic-form-group-title">项目信息</div>
                <el-row :gutter="24">
                  <el-col :span="12">
                    <el-form-item :label="TEXT.common.fields.projectName" prop="name">
                      <el-input v-model="form.name" />
                    </el-form-item>
                  </el-col>
                  <el-col :span="12">
                    <el-form-item label="项目全称">
                      <el-input v-model="form.project_full_name" placeholder="项目全称" />
                    </el-form-item>
                  </el-col>
                </el-row>
                <el-row :gutter="24">
                  <el-col :span="12">
                    <el-form-item label="申办方">
                      <el-input v-model="form.sponsor" :placeholder="TEXT.modules.adminProjects.sponsorPlaceholder" />
                    </el-form-item>
                  </el-col>
                  <el-col :span="12">
                    <el-form-item :label="TEXT.common.fields.projectCode" prop="code">
                      <el-input v-model="form.code" />
                    </el-form-item>
                  </el-col>
                </el-row>
                <el-row :gutter="24">
                  <el-col :span="12">
                    <el-form-item :label="TEXT.common.fields.protocolNo" prop="protocol_no">
                      <el-input v-model="form.protocol_no" :placeholder="TEXT.modules.adminProjects.protocolPlaceholder" />
                    </el-form-item>
                  </el-col>
                  <el-col :span="12">
                    <el-form-item label="组长单位">
                      <el-input v-model="form.lead_unit" placeholder="组长单位" />
                    </el-form-item>
                  </el-col>
                </el-row>
                <el-row :gutter="24">
                  <el-col :span="12">
                    <el-form-item label="主要研究者">
                      <el-input v-model="form.principal_investigator" placeholder="主要研究者" />
                    </el-form-item>
                  </el-col>
                  <el-col :span="12">
                    <el-form-item label="主PM">
                      <el-input v-model="form.main_pm" placeholder="主PM" />
                    </el-form-item>
                  </el-col>
                </el-row>
              </div>

              <div v-if="canEditStep1BasicGroup('research')" class="basic-edit-group">
                <div class="basic-form-group-title">研究信息</div>
                <el-row :gutter="24">
                  <el-col :span="12">
                    <el-form-item label="研究分期">
                      <el-input v-model="form.research_analysis" placeholder="研究分期" />
                    </el-form-item>
                  </el-col>
                  <el-col :span="12">
                    <el-form-item label="研究产品">
                      <el-input v-model="form.research_product" placeholder="研究产品" />
                    </el-form-item>
                  </el-col>
                </el-row>
                <el-row :gutter="24">
                  <el-col :span="12">
                    <el-form-item label="对照产品">
                      <el-input v-model="form.control_product" placeholder="对照产品" />
                    </el-form-item>
                  </el-col>
                  <el-col :span="12">
                    <el-form-item label="适应症">
                      <el-input v-model="form.indication" placeholder="适应症" />
                    </el-form-item>
                  </el-col>
                </el-row>
                <el-row :gutter="24">
                  <el-col :span="12">
                    <el-form-item label="研究人群">
                      <el-input v-model="form.research_population" placeholder="研究人群" />
                    </el-form-item>
                  </el-col>
                  <el-col :span="12">
                    <el-form-item label="研究设计">
                      <el-input v-model="form.research_design" placeholder="研究设计" />
                    </el-form-item>
                  </el-col>
                </el-row>
              </div>

              <div v-if="canEditStep1BasicGroup('execution')" class="basic-edit-group">
                <div class="basic-form-group-title">执行信息</div>
                <el-row :gutter="24">
                  <el-col :span="12">
                    <el-form-item label="计划开始日期">
                      <el-date-picker v-model="form.plan_start_date" type="date" value-format="YYYY-MM-DD" class="w-full" />
                    </el-form-item>
                  </el-col>
                  <el-col :span="12">
                    <el-form-item label="计划结束日期">
                      <el-date-picker v-model="form.plan_end_date" type="date" value-format="YYYY-MM-DD" class="w-full" />
                    </el-form-item>
                  </el-col>
                </el-row>
                <el-row :gutter="24">
                  <el-col :span="12">
                    <el-form-item :label="TEXT.common.fields.status" prop="status">
                      <el-select v-model="form.status" :placeholder="TEXT.common.placeholders.select" class="w-full">
                        <el-option :label="TEXT.enums.projectStatus.DRAFT" value="DRAFT" />
                        <el-option :label="TEXT.enums.projectStatus.ACTIVE" value="ACTIVE" />
                        <el-option :label="TEXT.enums.projectStatus.CLOSED" value="CLOSED" />
                      </el-select>
                    </el-form-item>
                  </el-col>
                  <el-col :span="12">
                    <el-form-item label="计划中心数">
                      <el-input-number v-model="form.planned_site_count" :min="0" :step="1" controls-position="right" class="w-full" />
                    </el-form-item>
                  </el-col>
                </el-row>
                <el-row :gutter="24">
                  <el-col :span="12">
                    <el-form-item label="计划入组数">
                      <el-input-number v-model="form.planned_enrollment_count" :min="0" :step="1" controls-position="right" class="w-full" />
                    </el-form-item>
                  </el-col>
                </el-row>
              </div>
            </el-form>

            <div v-else class="basic-info-groups">
              <div class="info-group">
                <div class="info-group-title-row">
                  <div class="info-group-title">项目信息</div>
                  <el-button
                    v-if="!isPublishedView"
                    type="primary"
                    class="info-group-edit-btn"
                    :disabled="!canEditSetup"
                    @click="startStep1SectionEdit('project')"
                  >
                    编辑
                  </el-button>
                </div>
                <div class="info-grid">
                  <div v-for="item in projectInfoItems" :key="`project-${item.label}`" class="info-item">
                    <span class="info-label">{{ item.label }}</span>
                    <span class="info-value">{{ item.value || "-" }}</span>
                  </div>
                </div>
              </div>
              <div class="info-group">
                <div class="info-group-title-row">
                  <div class="info-group-title">研究信息</div>
                  <el-button
                    v-if="!isPublishedView"
                    type="primary"
                    class="info-group-edit-btn"
                    :disabled="!canEditSetup"
                    @click="startStep1SectionEdit('research')"
                  >
                    编辑
                  </el-button>
                </div>
                <div class="info-grid">
                  <div v-for="item in researchInfoItems" :key="`research-${item.label}`" class="info-item">
                    <span class="info-label">{{ item.label }}</span>
                    <span class="info-value">{{ item.value || "-" }}</span>
                  </div>
                </div>
              </div>
              <div class="info-group">
                <div class="info-group-title-row">
                  <div class="info-group-title">执行信息</div>
                  <el-button
                    v-if="!isPublishedView"
                    type="primary"
                    class="info-group-edit-btn"
                    :disabled="!canEditSetup"
                    @click="startStep1SectionEdit('execution')"
                  >
                    编辑
                  </el-button>
                </div>
                <div class="info-grid">
                  <div v-for="item in executionInfoItems" :key="`execution-${item.label}`" class="info-item">
                    <span class="info-label">{{ item.label }}</span>
                    <span class="info-value">{{ item.value || "-" }}</span>
                  </div>
                </div>
              </div>
            </div>
          </el-tab-pane>

          <el-tab-pane v-if="showStep1SummaryTab" label="方案摘要" name="summary">
            <el-form v-if="canEditStep1Summary" label-width="120px" class="detail-form" label-position="top">
              <el-row :gutter="24">
                <el-col :span="6">
                  <el-form-item :label="TEXT.common.fields.visitTotal">
                    <el-input-number v-model="form.visit_total" :min="1" :max="50" :step="1" controls-position="right" class="w-full" />
                  </el-form-item>
                </el-col>
                <el-col :span="6">
                  <el-form-item :label="TEXT.common.fields.visitIntervalDays">
                    <el-input-number v-model="form.visit_interval_days" :min="1" :max="365" :step="1" controls-position="right" class="w-full" />
                  </el-form-item>
                </el-col>
                <el-col :span="6">
                  <el-form-item :label="TEXT.common.fields.visitWindowStartOffset">
                    <el-input-number v-model="form.visit_window_start_offset" :min="-365" :max="365" :step="1" controls-position="right" class="w-full" />
                  </el-form-item>
                </el-col>
                <el-col :span="6">
                  <el-form-item :label="TEXT.common.fields.visitWindowEndOffset">
                    <el-input-number v-model="form.visit_window_end_offset" :min="-365" :max="365" :step="1" controls-position="right" class="w-full" />
                  </el-form-item>
                </el-col>
              </el-row>
              <el-row :gutter="24">
                <el-col :span="12">
                  <el-form-item label="方案摘要说明">
                    <el-input v-model="form.summary_note" type="textarea" :rows="3" placeholder="用于展示项目信息概览说明" />
                  </el-form-item>
                </el-col>
                <el-col :span="12">
                  <el-form-item label="研究目标摘要">
                    <el-input v-model="form.objective_note" type="textarea" :rows="3" placeholder="用于展示研究目标摘要" />
                  </el-form-item>
                </el-col>
              </el-row>
            </el-form>

            <div v-else class="info-grid">
              <div v-for="item in summaryInfoItems" :key="item.label" class="info-item">
                <span class="info-label">{{ item.label }}</span>
                <span class="info-value">{{ item.value || "-" }}</span>
              </div>
            </div>
          </el-tab-pane>
        </el-tabs>
      </template>

      <StateLoading v-else :rows="4" />
      </section>

    <section v-show="activeStep === 1" class="setup-section">
      <el-table :data="currentSetupDraft.projectMilestones" class="ctms-table setup-milestone-table">
        <el-table-column label="里程碑" min-width="180">
          <template #default="scope">
            <span class="milestone-cell-text">{{ scope.row.name || "-" }}</span>
          </template>
        </el-table-column>
        <el-table-column label="计划时间" width="240">
          <template #default="scope">
            <div class="milestone-plan-cell">
              <div class="milestone-plan-line">
                <span class="milestone-plan-dot" />
                <span class="milestone-plan-label">开始:</span>
                <span>{{ resolveProjectMilestoneStart(scope.row) || "-" }}</span>
              </div>
              <div class="milestone-plan-line">
                <span class="milestone-plan-dot" />
                <span class="milestone-plan-label">结束:</span>
                <span>{{ resolveProjectMilestoneEnd(scope.row) || "-" }}</span>
              </div>
              <div class="milestone-plan-line">
                <span class="milestone-plan-dot" />
                <span class="milestone-plan-label">耗时:</span>
                <span>{{ formatProjectMilestoneDuration(scope.row) }}</span>
              </div>
            </div>
          </template>
        </el-table-column>
        <el-table-column label="负责人" min-width="160">
          <template #default="scope">
            <span class="milestone-cell-text">{{ normalizeOwnerDisplay(scope.row.owner) }}</span>
          </template>
        </el-table-column>
        <el-table-column label="状态" width="150">
          <template #default="scope">
            <span class="milestone-cell-text">{{ scope.row.status || "-" }}</span>
          </template>
        </el-table-column>
        <el-table-column label="备注" min-width="220">
          <template #default="scope">
            <span class="milestone-cell-text">{{ scope.row.remark || "-" }}</span>
          </template>
        </el-table-column>
        <el-table-column v-if="!isPublishedView" label="操作" width="130">
          <template #default="scope">
            <div class="milestone-actions-inline">
              <el-button link type="primary" :disabled="!canEditSetup" @click="openProjectMilestoneEditor(scope.$index)">编辑</el-button>
              <el-button link type="danger" :disabled="!canEditSetup" @click="removeProjectMilestone(scope.$index)">删除</el-button>
            </div>
          </template>
        </el-table-column>
      </el-table>
      </section>

    <section v-show="activeStep === 2" class="setup-section enrollment-plan-section">
      <div class="enrollment-plan-toolbar">
        <div class="enrollment-inline-group">
          <span class="enrollment-inline-label required">计划入组日期</span>
          <div class="field-cell enrollment-date-cell" data-field-path="enrollmentPlan.startDate">
            <el-date-picker
              v-if="isEditing && !isPublishedView"
              v-model="currentSetupDraft.enrollmentPlan.startDate"
              type="date"
              value-format="YYYY-MM-DD"
              :disabled="!canEditSetup"
              class="w-full"
            />
            <span v-else class="enrollment-inline-value">{{ currentSetupDraft.enrollmentPlan.startDate || "-" }}</span>
            <div v-if="getFieldError('enrollmentPlan.startDate')" class="field-error">{{ getFieldError("enrollmentPlan.startDate") }}</div>
          </div>
          <span class="enrollment-sep">~</span>
          <div class="field-cell enrollment-date-cell" data-field-path="enrollmentPlan.endDate">
            <el-date-picker
              v-if="isEditing && !isPublishedView"
              v-model="currentSetupDraft.enrollmentPlan.endDate"
              type="date"
              value-format="YYYY-MM-DD"
              :disabled="!canEditSetup"
              class="w-full"
            />
            <span v-else class="enrollment-inline-value">{{ currentSetupDraft.enrollmentPlan.endDate || "-" }}</span>
            <div v-if="getFieldError('enrollmentPlan.endDate')" class="field-error">{{ getFieldError("enrollmentPlan.endDate") }}</div>
          </div>
        </div>
        <div class="enrollment-inline-group">
          <span class="enrollment-inline-label">入组周期</span>
          <el-radio-group
            v-if="isEditing && !isPublishedView"
            v-model="enrollmentPlanCycle"
            size="small"
            class="enrollment-cycle-group"
          >
            <el-radio-button label="month">月</el-radio-button>
            <el-radio-button label="quarter">季度</el-radio-button>
          </el-radio-group>
          <span v-else class="enrollment-inline-value">{{ enrollmentPlanCycleLabel }}</span>
        </div>
        <div class="enrollment-inline-group total-target-group">
          <span class="enrollment-inline-label">计划入组总数</span>
          <div class="field-cell enrollment-target-cell" data-field-path="enrollmentPlan.totalTarget">
            <el-input-number
              v-if="isEditing && !isPublishedView"
              v-model="currentSetupDraft.enrollmentPlan.totalTarget"
              :min="0"
              :disabled="!canEditSetup"
              controls-position="right"
              class="w-full"
            />
            <span v-else class="enrollment-inline-value">{{ toDisplayNumber(currentSetupDraft.enrollmentPlan.totalTarget) }}</span>
            <div v-if="getFieldError('enrollmentPlan.totalTarget')" class="field-error">{{ getFieldError("enrollmentPlan.totalTarget") }}</div>
          </div>
        </div>
      </div>

      <div class="enrollment-plan-summary">
        <div class="enrollment-summary-line">
          <span class="summary-title">项目计划情况:</span>
          <span>要求试验参与者数 <b>{{ toDisplayNumber(currentSetupDraft.enrollmentPlan.totalTarget) }}</b> 人</span>
          <span>已填写计划数 <b>{{ toDisplayNumber(projectMonthlyPlannedCount) }}</b> 人</span>
          <span v-if="projectMonthlyOverflowCount > 0" class="summary-danger">已超出 {{ projectMonthlyOverflowCount }} 人</span>
          <span v-else>待分配 {{ projectMonthlyPendingCount }} 人</span>
        </div>
        <div class="enrollment-summary-line">
          <span class="summary-title">中心计划情况:</span>
          <span>已配置中心 <b>{{ centerConfiguredSiteCount }}</b>/<b>{{ centerActiveSiteCount }}</b> 个</span>
          <span>中心计划总数 <b>{{ toDisplayNumber(centerPlannedCount) }}</b> 人</span>
          <span>已填写计划数 <b>{{ toDisplayNumber(centerCyclePlannedCount) }}</b> 人</span>
          <span v-if="centerCycleOverflowCount > 0" class="summary-danger">已超出 {{ centerCycleOverflowCount }} 人</span>
          <span v-else-if="centerCyclePendingCount > 0">待分配 {{ centerCyclePendingCount }} 人</span>
          <span v-else class="summary-success">已完成分配</span>
        </div>
      </div>

      <div v-if="isEditing && !isPublishedView" class="enrollment-plan-actions">
        <el-button link class="enrollment-plan-action-link" @click="clearEnrollmentPeriodPlan">清空</el-button>
        <el-button link class="enrollment-plan-action-link" @click="quickFillEnrollmentPeriodPlan">快速填写</el-button>
      </div>

      <el-table
        v-if="enrollmentPlanCycle === 'month'"
        :data="enrollmentPlanYearRows"
        class="ctms-table enrollment-month-plan-table"
        border
        empty-text="请先填写有效的计划入组日期范围"
      >
        <el-table-column label="年份" width="88">
          <template #default="scope">
            <span class="milestone-cell-text">{{ scope.row.year }}</span>
          </template>
        </el-table-column>
        <el-table-column v-for="month in 12" :key="month" :label="`${month}月`" min-width="88">
          <template #default="scope">
            <template v-if="scope.row.cells[month - 1].key">
              <el-input-number
                v-if="isEditing && !isPublishedView"
                :model-value="getEnrollmentPeriodTarget(scope.row.cells[month - 1].key)"
                :min="0"
                :step="1"
                controls-position="right"
                class="month-target-input"
                @update:model-value="setEnrollmentPeriodTarget(scope.row.cells[month - 1].key, $event as number | null)"
              />
              <span v-else class="milestone-cell-text">{{ displayEnrollmentPeriodTarget(scope.row.cells[month - 1].key) }}</span>
            </template>
            <span v-else class="month-empty">-</span>
          </template>
        </el-table-column>
      </el-table>

      <el-table
        v-else
        :data="enrollmentCycleGridRows"
        class="ctms-table enrollment-period-plan-table"
        border
        empty-text="请先填写有效的计划入组日期范围"
      >
        <el-table-column :label="enrollmentCycleRowLabel" min-width="120">
          <template #default="scope">
            <span class="milestone-cell-text">{{ scope.row.groupLabel }}</span>
          </template>
        </el-table-column>
        <el-table-column
          v-for="(column, columnIndex) in enrollmentCycleGridColumns"
          :key="column.key"
          :label="column.label"
          min-width="96"
        >
          <template #default="scope">
            <template v-if="scope.row.cells[columnIndex]?.key">
              <el-input-number
                v-if="isEditing && !isPublishedView"
                :model-value="getEnrollmentPeriodTarget(scope.row.cells[columnIndex].key)"
                :min="0"
                :step="1"
                controls-position="right"
                class="month-target-input"
                @update:model-value="setEnrollmentPeriodTarget(scope.row.cells[columnIndex].key, $event as number | null)"
              />
              <span v-else class="milestone-cell-text">{{ displayEnrollmentPeriodTarget(scope.row.cells[columnIndex].key) }}</span>
            </template>
            <span v-else class="month-empty">-</span>
          </template>
        </el-table-column>
      </el-table>
      </section>

    <section v-show="activeStep === 3" class="setup-section">
      <el-table :data="currentSetupDraft.siteMilestones" class="ctms-table">
        <el-table-column label="里程碑" min-width="220">
          <template #default="scope">
            <span class="milestone-cell-text">{{ scope.row.milestone || "-" }}</span>
          </template>
        </el-table-column>
        <el-table-column label="计划日期" width="180">
          <template #default="scope">
            <span class="milestone-cell-text">{{ scope.row.planDate || "-" }}</span>
          </template>
        </el-table-column>
        <el-table-column label="负责人" min-width="160">
          <template #default="scope">
            <span class="milestone-cell-text">{{ normalizeOwnerDisplay(scope.row.owner) }}</span>
          </template>
        </el-table-column>
        <el-table-column label="状态" width="150">
          <template #default="scope">
            <span class="milestone-cell-text">{{ scope.row.status || "未开始" }}</span>
          </template>
        </el-table-column>
        <el-table-column label="备注" min-width="180">
          <template #default="scope">
            <span class="milestone-cell-text">{{ scope.row.remark || "-" }}</span>
          </template>
        </el-table-column>
        <el-table-column v-if="!isPublishedView" label="操作" width="130">
          <template #default="scope">
            <div class="milestone-actions-inline">
              <el-button link type="primary" :disabled="!canEditSetup" @click="openSiteMilestoneEditor(scope.$index)">编辑</el-button>
              <el-button link type="danger" :disabled="!canEditSetup" @click="removeSiteMilestone(scope.$index)">删除</el-button>
            </div>
          </template>
        </el-table-column>
      </el-table>
      </section>

    <section v-show="activeStep === 4" class="setup-section enrollment-plan-section">
      <div class="enrollment-plan-actions enrollment-site-filter-row">
        <div class="site-plan-selector">
          <span class="enrollment-inline-label">中心</span>
          <el-select
            v-model="selectedSiteEnrollmentPlanSiteId"
            filterable
            clearable
            placeholder="选择中心"
            class="site-plan-site-select"
          >
            <el-option
              v-for="site in siteEnrollmentSelectableSites"
              :key="site.id"
              :label="site.name"
              :value="site.id"
            />
          </el-select>
        </div>
      </div>

      <div class="enrollment-plan-toolbar">
        <div class="enrollment-inline-group">
          <span class="enrollment-inline-label required">中心入组日期</span>
          <div class="field-cell enrollment-date-cell">
            <el-date-picker
              v-if="isEditing && !isPublishedView && selectedSiteEnrollmentPlan"
              v-model="selectedSiteEnrollmentPlan.startDate"
              type="date"
              value-format="YYYY-MM-DD"
              :disabled="!canEditSetup"
              class="w-full"
            />
            <span v-else class="enrollment-inline-value">{{ selectedSiteEnrollmentPlan?.startDate || "-" }}</span>
          </div>
          <span class="enrollment-sep">~</span>
          <div class="field-cell enrollment-date-cell">
            <el-date-picker
              v-if="isEditing && !isPublishedView && selectedSiteEnrollmentPlan"
              v-model="selectedSiteEnrollmentPlan.endDate"
              type="date"
              value-format="YYYY-MM-DD"
              :disabled="!canEditSetup"
              class="w-full"
            />
            <span v-else class="enrollment-inline-value">{{ selectedSiteEnrollmentPlan?.endDate || "-" }}</span>
          </div>
        </div>
        <div class="enrollment-inline-group">
          <span class="enrollment-inline-label">入组周期</span>
          <el-radio-group
            v-if="isEditing && !isPublishedView"
            v-model="enrollmentPlanCycle"
            size="small"
            class="enrollment-cycle-group"
          >
            <el-radio-button label="month">月</el-radio-button>
            <el-radio-button label="quarter">季度</el-radio-button>
          </el-radio-group>
          <span v-else class="enrollment-inline-value">{{ enrollmentPlanCycleLabel }}</span>
        </div>
        <div class="enrollment-inline-group total-target-group">
          <span class="enrollment-inline-label">中心计划总数</span>
          <div class="field-cell enrollment-target-cell">
            <el-input-number
              v-if="isEditing && !isPublishedView && selectedSiteEnrollmentPlan"
              v-model="selectedSiteEnrollmentPlan.target"
              :min="0"
              :disabled="!canEditSetup"
              controls-position="right"
              class="w-full"
            />
            <span v-else class="enrollment-inline-value">{{ toDisplayNumber(selectedSiteEnrollmentPlan?.target) }}</span>
          </div>
        </div>
      </div>

      <div class="enrollment-plan-summary">
        <div class="enrollment-summary-line">
          <span class="summary-title">项目计划情况:</span>
          <span>要求试验参与者数 <b>{{ toDisplayNumber(currentSetupDraft.enrollmentPlan.totalTarget) }}</b> 人</span>
          <span>已填写计划数 <b>{{ toDisplayNumber(projectMonthlyPlannedCount) }}</b> 人</span>
          <span v-if="projectMonthlyOverflowCount > 0" class="summary-danger">已超出 {{ projectMonthlyOverflowCount }} 人</span>
          <span v-else>待分配 {{ projectMonthlyPendingCount }} 人</span>
        </div>
        <div class="enrollment-summary-line">
          <span class="summary-title">中心计划情况:</span>
          <span class="summary-site-name">{{ selectedSitePlanName }}</span>
          <span>计划入组数 <b>{{ toDisplayNumber(selectedSitePlanTarget) }}</b> 人</span>
          <span>已填写计划数 <b>{{ toDisplayNumber(selectedSiteCyclePlannedCount) }}</b> 人</span>
          <span>待分配 {{ selectedSiteCyclePendingCount }} 人</span>
        </div>
      </div>

      <div class="enrollment-plan-actions">
        <template v-if="isEditing && !isPublishedView">
          <el-button
            v-if="!selectedSiteEnrollmentPlan"
            link
            class="enrollment-plan-action-link"
            @click="createSelectedSiteEnrollmentPlan"
          >
            初始化当前中心计划
          </el-button>
          <template v-else>
            <el-button link class="enrollment-plan-action-link" @click="clearSiteEnrollmentPeriodPlan(selectedSiteEnrollmentPlanIndex)">清空</el-button>
            <el-button link class="enrollment-plan-action-link" @click="quickFillSiteEnrollmentPeriodPlan(selectedSiteEnrollmentPlanIndex)">快速填写</el-button>
            <el-button link type="danger" @click="removeSiteEnrollment(selectedSiteEnrollmentPlanIndex)">删除</el-button>
          </template>
        </template>
      </div>

      <div v-if="!selectedSiteEnrollmentPlanSiteId" class="month-empty">请先选择中心</div>
      <div v-else-if="!selectedSiteEnrollmentPlan" class="month-empty">当前中心暂未配置入组计划</div>
      <div v-else>
        <el-table
          v-if="enrollmentPlanCycle === 'month'"
          :data="enrollmentPlanYearRows"
          class="ctms-table enrollment-month-plan-table"
          border
          empty-text="请先填写有效的计划入组日期范围"
        >
          <el-table-column label="年份" width="88">
            <template #default="scope">
              <span class="milestone-cell-text">{{ scope.row.year }}</span>
            </template>
          </el-table-column>
          <el-table-column v-for="month in 12" :key="month" :label="`${month}月`" min-width="88">
            <template #default="scope">
              <template v-if="scope.row.cells[month - 1].key">
                <el-input-number
                  v-if="isEditing && !isPublishedView"
                  :model-value="getSiteEnrollmentPeriodTarget(selectedSiteEnrollmentPlan, scope.row.cells[month - 1].key)"
                  :min="0"
                  :step="1"
                  controls-position="right"
                  class="month-target-input"
                  @update:model-value="setSiteEnrollmentPeriodTarget(selectedSiteEnrollmentPlanIndex, scope.row.cells[month - 1].key, $event as number | null)"
                />
                <span v-else class="milestone-cell-text">{{ displaySiteEnrollmentPeriodTarget(selectedSiteEnrollmentPlan, scope.row.cells[month - 1].key) }}</span>
              </template>
              <span v-else class="month-empty">-</span>
            </template>
          </el-table-column>
        </el-table>

        <el-table
          v-else
          :data="enrollmentCycleGridRows"
          class="ctms-table enrollment-period-plan-table"
          border
          empty-text="请先填写有效的计划入组日期范围"
        >
          <el-table-column :label="enrollmentCycleRowLabel" min-width="120">
            <template #default="scope">
              <span class="milestone-cell-text">{{ scope.row.groupLabel }}</span>
            </template>
          </el-table-column>
          <el-table-column
            v-for="(column, columnIndex) in enrollmentCycleGridColumns"
            :key="column.key"
            :label="column.label"
            min-width="96"
          >
            <template #default="scope">
              <template v-if="scope.row.cells[columnIndex]?.key">
                <el-input-number
                  v-if="isEditing && !isPublishedView"
                  :model-value="getSiteEnrollmentPeriodTarget(selectedSiteEnrollmentPlan, scope.row.cells[columnIndex].key)"
                  :min="0"
                  :step="1"
                  controls-position="right"
                  class="month-target-input"
                  @update:model-value="setSiteEnrollmentPeriodTarget(selectedSiteEnrollmentPlanIndex, scope.row.cells[columnIndex].key, $event as number | null)"
                />
                <span v-else class="milestone-cell-text">{{ displaySiteEnrollmentPeriodTarget(selectedSiteEnrollmentPlan, scope.row.cells[columnIndex].key) }}</span>
              </template>
              <span v-else class="month-empty">-</span>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </section>

    <section v-show="activeStep === 5" class="setup-section">
      <el-table :data="currentSetupDraft.monitoringStrategies" class="ctms-table">
        <el-table-column label="监查类型" min-width="180">
          <template #default="scope">
            <span class="milestone-cell-text">{{ scope.row.strategyType || "-" }}</span>
          </template>
        </el-table-column>
        <el-table-column label="策略详情" min-width="430">
          <template #default="scope">
            <span class="milestone-cell-text">{{ scope.row.detail || "-" }}</span>
          </template>
        </el-table-column>
        <el-table-column label="监查次数" width="140">
          <template #default="scope">
            <span class="milestone-cell-text">{{ scope.row.frequency || "-" }}</span>
          </template>
        </el-table-column>
        <el-table-column label="更新时间" width="180">
          <template #default="scope">
            <span class="milestone-cell-text">{{ scope.row.updatedAt || "-" }}</span>
          </template>
        </el-table-column>
        <el-table-column v-if="!isPublishedView" label="操作" width="130">
          <template #default="scope">
            <div class="milestone-actions-inline">
              <el-button link type="primary" :disabled="!canEditSetup" @click="openMonitoringStrategyEditor(scope.$index)">编辑</el-button>
              <el-button link type="danger" :disabled="!canEditSetup" @click="removeMonitoringStrategy(scope.$index)">删除</el-button>
            </div>
          </template>
        </el-table-column>
      </el-table>
      </section>

    <section v-show="activeStep === 6" class="setup-section">
      <el-table :data="centerConfirmDisplayRows" class="ctms-table">
        <el-table-column label="中心" min-width="180">
          <template #default="scope">
            <span class="milestone-cell-text">{{ scope.row.siteName || "-" }}</span>
          </template>
        </el-table-column>
        <el-table-column label="城市" min-width="120">
          <template #default="scope">
            <span class="milestone-cell-text">{{ scope.row.city || "-" }}</span>
          </template>
        </el-table-column>
        <el-table-column label="PI" min-width="120">
          <template #default="scope">
            <span class="milestone-cell-text">{{ scope.row.piName || "-" }}</span>
          </template>
        </el-table-column>
        <el-table-column label="负责人" min-width="180">
          <template #default="scope">
            <span class="milestone-cell-text">{{ scope.row.ownerDisplay || "-" }}</span>
          </template>
        </el-table-column>
        <el-table-column label="计划入组数" width="140">
          <template #default="scope">
            <span class="milestone-cell-text">{{ toDisplayNumber(scope.row.target) }}</span>
          </template>
        </el-table-column>
        <el-table-column label="状态" width="120">
          <template #default="scope">
            <el-tag :type="scope.row.isActive ? 'success' : 'danger'" effect="light">
              {{ scope.row.isActive ? "激活" : "锁定" }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="入组开始时间" width="150">
          <template #default="scope">
            <span class="milestone-cell-text">{{ scope.row.startDate || "-" }}</span>
          </template>
        </el-table-column>
        <el-table-column label="入组结束时间" width="150">
          <template #default="scope">
            <span class="milestone-cell-text">{{ scope.row.endDate || "-" }}</span>
          </template>
        </el-table-column>
      </el-table>
      </section>
      <div v-if="drawerVisible" class="setup-drawer-footer">
        <el-button @click="closeEditDrawer">取消</el-button>
        <el-button type="primary" :loading="drawerConfirmLoading || submitting" :disabled="!canEditSetup" @click="handleDrawerConfirm">
          保存
        </el-button>
      </div>
      </div>
    </div>
    <el-dialog append-to=".layout-main .content-wrapper" v-model="publishConfirmVisible" title="发布确认" width="920px" top="8vh" class="setup-publish-dialog">
      <div class="conflict-summary">
        <div>发布目标：立项配置草稿</div>
        <div>当前发布版本：{{ setupPublishedVersionText }}</div>
      </div>
      <div v-if="incompleteSteps.length" class="publish-incomplete">
        <el-alert
          type="warning"
          :closable="false"
          show-icon
          :title="`仍有 ${incompleteSteps.length} 个步骤待完善，建议补全后再发布`"
        />
        <div class="publish-incomplete-list">
          <el-tag v-for="item in incompleteSteps" :key="item.index" type="warning" effect="plain">
            {{ item.index + 1 }}. {{ item.label }}
          </el-tag>
        </div>
      </div>
      <el-alert
        v-if="isFirstPublish"
        type="info"
        :closable="false"
        show-icon
        title="当前为首次发布，将以当前草稿生成发布快照。"
      />
      <div v-else-if="publishDiffLines.length" class="conflict-diff-lines">
        <el-tag v-for="line in publishDiffLines" :key="line" effect="plain" type="warning">{{ line }}</el-tag>
      </div>
      <div v-if="publishFieldDiffRows.length" class="conflict-readable-diff">
        <div class="publish-diff-overview">
          <el-tag v-for="item in publishDiffModuleSummary" :key="item.module" type="info" effect="plain">
            {{ item.module }}：{{ item.count }} 项变更
          </el-tag>
        </div>
        <el-table :data="publishFieldDiffRows" size="small" border max-height="320">
          <el-table-column prop="moduleLabel" label="模块" width="140" />
          <el-table-column prop="path" label="变更项" min-width="300" />
          <el-table-column prop="changeType" label="变更类型" width="100">
            <template #default="scope">
              <el-tag :type="scope.row.changeType === '新增' ? 'success' : scope.row.changeType === '删除' ? 'danger' : 'warning'" effect="plain">
                {{ scope.row.changeType }}
              </el-tag>
            </template>
          </el-table-column>
          <el-table-column prop="serverValue" label="上一版本值" min-width="220" show-overflow-tooltip />
          <el-table-column prop="localValue" label="预发布版本值" min-width="220" show-overflow-tooltip />
        </el-table>
      </div>
      <template #footer>
        <el-button @click="publishConfirmVisible = false">取消</el-button>
        <el-button type="primary" :loading="publishConfirmLoading" @click="confirmPublishConfig">确认发布</el-button>
      </template>
    </el-dialog>
    <el-dialog append-to=".layout-main .content-wrapper" v-model="rollbackDialogVisible" title="选择回滚版本" width="760px" top="10vh">
        <el-table :data="setupVersionHistory" height="360" v-loading="rollbackDialogLoading">
          <el-table-column prop="version" label="发布版本" width="120">
          <template #default="scope">v{{ scope.row.display_version || scope.row.version }}</template>
        </el-table-column>
        <el-table-column prop="published_by_name" label="发布人" min-width="150" />
        <el-table-column prop="published_at" label="发布时间" min-width="200" />
        <el-table-column label="操作" width="260">
          <template #default="scope">
            <el-button link type="danger" @click="selectRollbackVersion(scope.row.version)">回滚</el-button>
            <el-button link type="primary" @click="downloadVersionRaw(scope.row)">下载</el-button>
            <el-button
              link
              type="danger"
              :disabled="isLatestPublishedVersion(scope.row.version)"
              @click="removeVersion(scope.row.version)"
            >
              删除
            </el-button>
          </template>
        </el-table-column>
      </el-table>
      <template #footer>
        <el-button @click="rollbackDialogVisible = false">关闭</el-button>
      </template>
    </el-dialog>

    <el-drawer
      v-model="projectMilestoneEditorVisible"
      direction="rtl"
      size="420px"
      :close-on-click-modal="false"
      :show-close="false"
      class="setup-milestone-editor-drawer"
    >
      <template #header>
        <div class="setup-milestone-editor-header">
          <span>编辑里程碑</span>
        </div>
      </template>
      <el-form label-position="top" class="setup-milestone-editor-form">
        <el-form-item label="里程碑">
          <el-input v-model="projectMilestoneEditorForm.name" placeholder="例如：首例入组" />
        </el-form-item>
        <el-form-item label="开始日期">
          <el-date-picker
            v-model="projectMilestoneEditorForm.startDate"
            type="date"
            value-format="YYYY-MM-DD"
            class="w-full"
            placeholder="选择开始日期"
          />
        </el-form-item>
        <el-form-item label="结束日期">
          <el-date-picker
            v-model="projectMilestoneEditorForm.endDate"
            type="date"
            value-format="YYYY-MM-DD"
            class="w-full"
            placeholder="选择结束日期"
          />
        </el-form-item>
        <el-form-item label="耗时">
          <span>{{ formatProjectMilestoneDuration(projectMilestoneEditorForm) }}</span>
        </el-form-item>
        <el-form-item label="负责人">
          <el-select
            v-model="projectMilestoneEditorForm.owner"
            filterable
            clearable
            class="w-full"
            placeholder="从项目成员中选择负责人"
          >
            <el-option
              v-for="option in projectMilestoneOwnerOptions"
              :key="option.value"
              :label="option.label"
              :value="option.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="状态">
          <el-select v-model="projectMilestoneEditorForm.status" class="w-full" placeholder="状态">
            <el-option label="未开始" value="未开始" />
            <el-option label="进行中" value="进行中" />
            <el-option label="已完成" value="已完成" />
            <el-option label="延期" value="延期" />
          </el-select>
        </el-form-item>
        <el-form-item label="备注">
          <el-input v-model="projectMilestoneEditorForm.remark" placeholder="备注" />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="setup-milestone-editor-footer">
          <el-button @click="projectMilestoneEditorVisible = false">取消</el-button>
          <el-button type="primary" @click="saveProjectMilestoneEditor">保存</el-button>
        </div>
      </template>
    </el-drawer>

    <el-drawer
      v-model="siteMilestoneEditorVisible"
      direction="rtl"
      size="420px"
      :close-on-click-modal="false"
      :show-close="false"
      class="setup-milestone-editor-drawer"
    >
      <template #header>
        <div class="setup-milestone-editor-header">
          <span>编辑中心里程碑</span>
        </div>
      </template>
      <el-form label-position="top" class="setup-milestone-editor-form">
        <el-form-item label="里程碑">
          <el-input v-model="siteMilestoneEditorForm.milestone" placeholder="中心里程碑" />
        </el-form-item>
        <el-form-item label="计划日期">
          <el-date-picker
            v-model="siteMilestoneEditorForm.planDate"
            type="date"
            value-format="YYYY-MM-DD"
            class="w-full"
            placeholder="选择日期"
          />
        </el-form-item>
        <el-form-item label="负责人">
          <el-select
            v-model="siteMilestoneEditorForm.owner"
            filterable
            clearable
            class="w-full"
            placeholder="从项目成员中选择负责人"
          >
            <el-option
              v-for="option in siteMilestoneOwnerOptions"
              :key="option.value"
              :label="option.label"
              :value="option.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="状态">
          <el-select v-model="siteMilestoneEditorForm.status" class="w-full" placeholder="状态">
            <el-option label="未开始" value="未开始" />
            <el-option label="进行中" value="进行中" />
            <el-option label="已完成" value="已完成" />
            <el-option label="延期" value="延期" />
          </el-select>
        </el-form-item>
        <el-form-item label="备注">
          <el-input v-model="siteMilestoneEditorForm.remark" placeholder="备注" />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="setup-milestone-editor-footer">
          <el-button @click="siteMilestoneEditorVisible = false">取消</el-button>
          <el-button type="primary" @click="saveSiteMilestoneEditor">保存</el-button>
        </div>
      </template>
    </el-drawer>

    <el-drawer
      v-model="siteEnrollmentEditorVisible"
      direction="rtl"
      size="420px"
      :close-on-click-modal="false"
      :show-close="false"
      class="setup-milestone-editor-drawer"
    >
      <template #header>
        <div class="setup-milestone-editor-header">
          <span>编辑中心入组计划</span>
        </div>
      </template>
      <el-form label-position="top" class="setup-milestone-editor-form">
        <el-form-item label="中心">
          <el-select v-model="siteEnrollmentEditorForm.siteId" filterable clearable placeholder="选择中心" class="w-full">
            <el-option
              v-for="site in siteOptions"
              :key="site.id"
              :label="site.name"
              :value="site.id"
              :disabled="isSiteEnrollmentSiteTaken(site.id)"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="计划例数">
          <el-input-number v-model="siteEnrollmentEditorForm.target" :min="0" controls-position="right" class="w-full" />
        </el-form-item>
        <el-form-item label="启动日期">
          <el-date-picker v-model="siteEnrollmentEditorForm.startDate" type="date" value-format="YYYY-MM-DD" class="w-full" />
        </el-form-item>
        <el-form-item label="完成日期">
          <el-date-picker v-model="siteEnrollmentEditorForm.endDate" type="date" value-format="YYYY-MM-DD" class="w-full" />
        </el-form-item>
        <el-form-item label="备注">
          <el-input v-model="siteEnrollmentEditorForm.note" placeholder="备注" />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="setup-milestone-editor-footer">
          <el-button @click="siteEnrollmentEditorVisible = false">取消</el-button>
          <el-button type="primary" @click="saveSiteEnrollmentEditor">保存</el-button>
        </div>
      </template>
    </el-drawer>

    <el-drawer
      v-model="monitoringStrategyEditorVisible"
      direction="rtl"
      size="420px"
      :close-on-click-modal="false"
      :show-close="false"
      class="setup-milestone-editor-drawer"
    >
      <template #header>
        <div class="setup-milestone-editor-header">
          <span>编辑监查策略</span>
        </div>
      </template>
      <el-form label-position="top" class="setup-milestone-editor-form">
        <el-form-item label="监查类型">
          <el-select v-model="monitoringStrategyEditorForm.strategyType" class="w-full" placeholder="选择类型">
            <el-option
              v-for="item in monitoringStrategyTypeOptions"
              :key="item"
              :label="item"
              :value="item"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="策略详情">
          <el-input v-model="monitoringStrategyEditorForm.detail" placeholder="填写监查触发规则与执行策略" />
        </el-form-item>
        <el-form-item label="监查次数">
          <el-input v-model="monitoringStrategyEditorForm.frequency" placeholder="如：不限 / 2次" />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="setup-milestone-editor-footer">
          <el-button @click="monitoringStrategyEditorVisible = false">取消</el-button>
          <el-button type="primary" @click="saveMonitoringStrategyEditor">保存</el-button>
        </div>
      </template>
    </el-drawer>

    <el-drawer
      v-model="centerConfirmEditorVisible"
      direction="rtl"
      size="420px"
      :close-on-click-modal="false"
      :show-close="false"
      class="setup-milestone-editor-drawer"
    >
      <template #header>
        <div class="setup-milestone-editor-header">
          <span>编辑中心确认</span>
        </div>
      </template>
      <el-form label-position="top" class="setup-milestone-editor-form">
        <el-form-item label="中心">
          <el-select v-model="centerConfirmEditorForm.siteId" filterable clearable placeholder="选择中心" class="w-full">
            <el-option v-for="site in siteOptions" :key="site.id" :label="site.name" :value="site.id" />
          </el-select>
        </el-form-item>
        <el-form-item label="确认人">
          <el-input v-model="centerConfirmEditorForm.confirmer" placeholder="确认人" />
        </el-form-item>
        <el-form-item label="确认状态">
          <el-select v-model="centerConfirmEditorForm.confirmStatus" class="w-full" placeholder="状态">
            <el-option label="待确认" value="待确认" />
            <el-option label="已确认" value="已确认" />
            <el-option label="退回" value="退回" />
          </el-select>
        </el-form-item>
        <el-form-item label="确认日期">
          <el-date-picker v-model="centerConfirmEditorForm.confirmDate" type="date" value-format="YYYY-MM-DD" class="w-full" />
        </el-form-item>
        <el-form-item label="备注">
          <el-input v-model="centerConfirmEditorForm.note" placeholder="备注" />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="setup-milestone-editor-footer">
          <el-button @click="centerConfirmEditorVisible = false">取消</el-button>
          <el-button type="primary" @click="saveCenterConfirmEditor">保存</el-button>
        </div>
      </template>
    </el-drawer>
  </div>
</template>

<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, reactive, ref, watch, type Ref } from "vue";
import { onBeforeRouteLeave, useRoute, useRouter } from "vue-router";
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from "element-plus";
import { ArrowLeft, ArrowRight, Promotion, User, OfficeBuilding, Lock, SuccessFilled, ArrowDown } from "@element-plus/icons-vue";
import {
  fetchStudyDetail,
  updateStudy,
} from "../../api/studies";
import { listMembers } from "../../api/members";
import { fetchSites } from "../../api/sites";
import { fetchUsers } from "../../api/users";
import type { Site, Study } from "../../types/api";
import type {
  CenterConfirmDraft,
  MonitoringStrategyDraft,
  ProjectMilestoneDraft,
  SetupConfigDraft,
  SiteEnrollmentPlanDraft,
  SiteMilestoneDraft,
  StudySetupConfigResponse,
  StudySetupConfigVersionItem,
  StudySetupConfigUpsertPayload,
} from "../../types/setupConfig";
import StateLoading from "../../components/StateLoading.vue";
import { useStudyStore } from "../../store/study";
import { useAuthStore } from "../../store/auth";
import { TEXT, requiredMessage } from "../../locales";
import { groupErrorsBySection, parseFieldPath, type SetupValidationError } from "../../utils/setupFieldLocator";
import { buildSetupReadableDiffRows, serializeDiffValue, type SetupDiffRow } from "../../utils/setupDiffRows";
import { useSetupConfig } from "../../composables/useSetupConfig";

type SetupStepKey =
  | "project-info"
  | "project-milestone"
  | "project-enrollment"
  | "site-milestone"
  | "site-enrollment"
  | "monitoring-strategy"
  | "site-confirm";
type Step1EditSection = "all" | "project" | "research" | "execution" | "summary";
type StepActionMode =
  | "none"
  | "edit"
  | "project-milestone"
  | "site-milestone"
  | "monitoring-strategy";

const DRAFT_VERSION = 1;

const steps: Array<{ key: SetupStepKey; label: string }> = [
  { key: "project-info", label: "项目信息" },
  { key: "project-milestone", label: "项目里程碑" },
  { key: "project-enrollment", label: "项目入组计划" },
  { key: "site-milestone", label: "中心里程碑" },
  { key: "site-enrollment", label: "中心入组计划" },
  { key: "monitoring-strategy", label: "监查计划策略" },
  { key: "site-confirm", label: TEXT.modules.adminProjects.setupStepCenterConfirm || "中心确认" },
];
const projectMilestoneTemplates: Array<{ key: string; label: string; milestones: string[] }> = [
  {
    key: "startup-default",
    label: "默认模板（立项关键节点）",
    milestones: [
      "中心可行性调研",
      "试验启动前研究者会",
      "方案定稿",
      "首家中心伦理通过",
      "首家中心启动",
      "首例参与者入组",
      "参与者入组5例",
      "参与者入组20例",
      "末例参与者出组",
      "数据库锁定",
      "临床试验总结",
    ],
  },
];
const siteMilestoneTemplates: Array<{ key: string; label: string; milestones: string[] }> = [
  {
    key: "site-default",
    label: "默认模板（中心关键节点）",
    milestones: [
      "中心调研",
      "机构立项",
      "取得伦理批件",
      "合同签署",
      "启动会",
      "首例入组",
      "末例入组",
      "末例访视",
      "数据库锁定",
      "中心关闭",
    ],
  },
];
const monitoringStrategyTypeOptions = [
  "启动访视",
  "筛选访视",
  "监查访视",
  "协同访视",
  "风险访视",
  "末次访视",
];
const monitoringStrategyTemplates: Array<{
  key: string;
  label: string;
  rows: Array<{ strategyType: string; detail: string; frequency: string }>;
}> = [
  {
    key: "by-times-default",
    label: "基础模板",
    rows: monitoringStrategyTypeOptions.map((type) => ({
      strategyType: type,
      detail: `${type}按方案执行`,
      frequency: "1次",
    })),
  },
];

const route = useRoute();
const router = useRouter();
const studyStore = useStudyStore();
const authStore = useAuthStore();
const {
  getConfig: fetchSetupConfig,
  saveConfig: upsertSetupConfig,
  publishConfig,
  listVersions: fetchSetupVersions,
  rollbackConfig,
  deleteVersion: deleteSetupVersion,
} = useSetupConfig();

const project = ref<Study | null>(null);
const siteOptions = ref<Site[]>([]);
const memberDisplayMap = ref<Record<string, string>>({});
const projectMemberOptions = ref<Array<{ value: string; label: string }>>([]);
const activeStep = ref(0);
const infoTab = ref<"basic" | "summary">("basic");
const step1EditSection = ref<Step1EditSection>("all");
const isEditing = ref(false);
const drawerClosing = ref(false);
const drawerVisible = computed(() => {
  if (activeStep.value === 2 || activeStep.value === 4) return false;
  return isEditing.value || drawerClosing.value;
});
const drawerCloseTimer = ref<number | undefined>(undefined);
const submitting = ref(false);
const formRef = ref<FormInstance>();
const draftReady = ref(false);
const suppressDraftWatch = ref(false);
const autoSaveTimer = ref<number | undefined>(undefined);
const setupDirtySinceLastPersist = ref(false);
const projectDirtySinceLastPersist = ref(false);
const suppressEnrollmentFieldSync = ref(false);
const enrollmentPlanCycle = ref<EnrollmentCycle>("month");
const enrollmentTargetsByCycle = ref<Record<EnrollmentCycle, Record<string, number>>>({
  month: {},
  quarter: {},
});
const enrollmentStageSyncingFromModel = ref(false);
const enrollmentStageSyncingToModel = ref(false);
const setupRevision = ref<number>(1);
const setupPublishedVersion = ref<number | null>(null);
const setupPublishStatus = ref<"DRAFT" | "PUBLISHED" | string>("DRAFT");
const setupPublishedAt = ref<string>("");
const setupViewMode = ref<"draft" | "published">("draft");
const publishedSetupDraft = ref<SetupConfigDraft | null>(null);
const publishConfirmVisible = ref(false);
const publishConfirmLoading = ref(false);
const primarySaveLoading = ref(false);
const rollbackDialogVisible = ref(false);
const rollbackDialogLoading = ref(false);
const setupVersionHistory = ref<StudySetupConfigVersionItem[]>([]);
const drawerConfirmLoading = ref(false);
const projectMilestoneEditorVisible = ref(false);
const projectMilestoneEditingIndex = ref<number>(-1);
const siteMilestoneEditorVisible = ref(false);
const siteMilestoneEditingIndex = ref<number>(-1);
const siteEnrollmentEditorVisible = ref(false);
const siteEnrollmentEditingIndex = ref<number>(-1);
const monitoringStrategyEditorVisible = ref(false);
const monitoringStrategyEditingIndex = ref<number>(-1);
const centerConfirmEditorVisible = ref(false);
const centerConfirmEditingIndex = ref<number>(-1);
type IndexedEditorController = {
  visible: Ref<boolean>;
  index: Ref<number>;
};
const projectMilestoneEditor: IndexedEditorController = {
  visible: projectMilestoneEditorVisible,
  index: projectMilestoneEditingIndex,
};
const siteMilestoneEditor: IndexedEditorController = {
  visible: siteMilestoneEditorVisible,
  index: siteMilestoneEditingIndex,
};
const siteEnrollmentEditor: IndexedEditorController = {
  visible: siteEnrollmentEditorVisible,
  index: siteEnrollmentEditingIndex,
};
const monitoringStrategyEditor: IndexedEditorController = {
  visible: monitoringStrategyEditorVisible,
  index: monitoringStrategyEditingIndex,
};
const centerConfirmEditor: IndexedEditorController = {
  visible: centerConfirmEditorVisible,
  index: centerConfirmEditingIndex,
};
const projectMilestoneEditorForm = reactive<ProjectMilestoneDraft>({
  id: "",
  name: "",
  planDate: "",
  startDate: "",
  endDate: "",
  durationDays: 1,
  owner: "",
  remark: "",
  status: "未开始",
});
const siteMilestoneEditorForm = reactive<SiteMilestoneDraft>({
  id: "",
  milestone: "",
  planDate: "",
  owner: "",
  remark: "",
  status: "未开始",
});
const siteEnrollmentEditorForm = reactive<SiteEnrollmentPlanDraft>({
  id: "",
  siteId: "",
  siteName: "",
  target: 0,
  startDate: "",
  endDate: "",
  note: "",
  stageBreakdown: "",
});
const monitoringStrategyEditorForm = reactive<MonitoringStrategyDraft>({
  id: "",
  strategyType: "",
  detail: "",
  frequency: "",
  updatedAt: "",
  enabled: true,
});
const centerConfirmEditorForm = reactive<CenterConfirmDraft>({
  id: "",
  siteId: "",
  siteName: "",
  confirmer: "",
  confirmStatus: "待确认",
  confirmDate: "",
  note: "",
});
type ProjectPublishSnapshot = {
  code: string;
  name: string;
  project_full_name: string;
  sponsor: string;
  protocol_no: string;
  lead_unit: string;
  principal_investigator: string;
  main_pm: string;
  research_analysis: string;
  research_product: string;
  control_product: string;
  indication: string;
  research_population: string;
  research_design: string;
  plan_start_date: string;
  plan_end_date: string;
  planned_site_count: number | null;
  planned_enrollment_count: number | null;
  summary_note: string;
  objective_note: string;
  status: string;
  visit_interval_days: number | null;
  visit_total: number | null;
  visit_window_start_offset: number | null;
  visit_window_end_offset: number | null;
};

type EnrollmentCycle = "month" | "quarter";
type EnrollmentYearRow = {
  year: number;
  cells: Array<{ key: string | null }>;
};
type EnrollmentCycleGridRow = {
  groupLabel: string;
  cells: Array<{ key: string | null }>;
};
type EnrollmentCycleGridColumn = {
  key: string;
  label: string;
};
type EnrollmentPeriodSlot = {
  key: string;
  label: string;
  groupLabel: string;
};

const publishedProjectSnapshot = ref<ProjectPublishSnapshot | null>(null);
const setupServerSnapshot = ref("");
const projectServerSnapshot = ref("");
const setupSaveMeta = ref<{ updatedAt: string; savedBy: string | null; serverSynced: boolean }>({
  updatedAt: "",
  savedBy: null,
  serverSynced: true,
});
const serverFieldErrors = ref<Record<string, string>>({});

const form = ref({
  code: "",
  name: "",
  project_full_name: "",
  short_name: "",
  sponsor: "",
  protocol_no: "",
  lead_unit: "",
  principal_investigator: "",
  main_pm: "",
  research_analysis: "",
  research_product: "",
  control_product: "",
  indication: "",
  research_population: "",
  research_design: "",
  plan_start_date: "",
  plan_end_date: "",
  planned_site_count: null as number | null,
  planned_enrollment_count: null as number | null,
  phase: "",
  status: "DRAFT",
  scope: "国内",
  summary_note: "",
  objective_note: "",
  visit_interval_days: null as number | null,
  visit_total: null as number | null,
  visit_window_start_offset: null as number | null,
  visit_window_end_offset: null as number | null,
});
const formBaselineSnapshot = ref("");

const setupDraft = reactive<SetupConfigDraft>({
  projectMilestones: [],
  enrollmentPlan: {
    totalTarget: 0,
    startDate: "",
    endDate: "",
    monthlyGoalNote: "",
    stageBreakdown: "",
  },
  siteMilestones: [],
  siteEnrollmentPlans: [],
  monitoringStrategies: [],
  centerConfirm: [],
});

const storageKey = computed(() => `ctms_setup_config_draft_${String(route.params.projectId || "")}`);
const projectDraftStorageKey = computed(() => `ctms_setup_project_draft_${String(route.params.projectId || "")}`);
const publishedProjectSignatureKey = computed(() => `ctms_setup_published_project_sig_${String(route.params.projectId || "")}`);
const setupRole = computed(() => String(project.value?.role_in_study || authStore.user?.role || "").toUpperCase());
const canManageSetup = computed(() => ["ADMIN", "PM"].includes(setupRole.value));
const isPublishedView = computed(() => setupViewMode.value === "published");
const currentSetupDraft = computed<SetupConfigDraft>(() => {
  return setupDraft;
});
const canEditSetup = computed(
  () => Boolean(project.value && !project.value?.is_locked && canManageSetup.value && !isPublishedView.value)
);
const canMutateDraft = () => canEditSetup.value && !isPublishedView.value;
const canEditStep1BasicGroup = (group: Exclude<Step1EditSection, "all" | "summary">) =>
  activeStep.value === 0 && isEditing.value && (step1EditSection.value === "all" || step1EditSection.value === group);
const canEditStep1Summary = computed(
  () => activeStep.value === 0 && isEditing.value && (step1EditSection.value === "all" || step1EditSection.value === "summary")
);
const isStep1BasicScopedEdit = computed(
  () => activeStep.value === 0 && isEditing.value && ["project", "research", "execution"].includes(step1EditSection.value)
);
const showStep1BasicTab = computed(
  () => !(activeStep.value === 0 && isEditing.value && step1EditSection.value === "summary")
);
const showStep1SummaryTab = computed(() => !isStep1BasicScopedEdit.value);
const stepActionMode = computed<StepActionMode>(() => {
  if (isPublishedView.value) return "none";
  switch (activeStep.value) {
    case 2:
      return "edit";
    case 0:
      return infoTab.value === "summary" ? "edit" : "none";
    case 1:
      return "project-milestone";
    case 3:
      return "site-milestone";
    case 4:
      return "edit";
    case 5:
      return "monitoring-strategy";
    default:
      return "none";
  }
});
const openIndexedEditor = <T>(
  rows: T[],
  rowIndex: number,
  controller: IndexedEditorController,
  fillForm: (row: T) => void
) => {
  if (!canMutateDraft()) return false;
  const row = rows[rowIndex];
  if (!row) return false;
  controller.index.value = rowIndex;
  fillForm(row);
  controller.visible.value = true;
  return true;
};
const getEditingIndex = (controller: IndexedEditorController, length: number) => {
  const idx = controller.index.value;
  if (idx < 0 || idx >= length) return -1;
  return idx;
};
const closeIndexedEditor = (controller: IndexedEditorController, successMessage: string) => {
  controller.visible.value = false;
  controller.index.value = -1;
  ElMessage.success(successMessage);
};
const canSaveDraftAction = computed(() => Boolean(project.value && canManageSetup.value && !project.value?.is_locked && !isPublishedView.value));
const canPublishAction = computed(() => Boolean(project.value && canManageSetup.value && !project.value?.is_locked));
const currentStepTitle = computed(() => `第${activeStep.value + 1}步：${steps[activeStep.value]?.label || ""}`);
const setupPublishedVersionText = computed(() => {
  return `v${setupPublishedVersion.value ?? 0}`;
});
const setupSaveMetaText = computed(() => {
  const when = setupSaveMeta.value.updatedAt || "-";
  return `最近保存：${when}｜发布版本：${setupPublishedVersionText.value}`;
});
const setupSyncTagLabel = computed(() => {
  if (setupDirtySinceLastPersist.value || projectDirtySinceLastPersist.value || autoSaveTimer.value) return "编辑中未保存";
  return setupSaveMeta.value.serverSynced ? "已保存到服务端" : "仅本地暂存";
});
const setupSyncTagType = computed<"success" | "warning" | "danger">(() => {
  if (setupDirtySinceLastPersist.value || projectDirtySinceLastPersist.value || autoSaveTimer.value) return "warning";
  return setupSaveMeta.value.serverSynced ? "success" : "danger";
});
const serializeFormForCompare = (): string =>
  JSON.stringify({
    code: form.value.code || "",
    name: form.value.name || "",
    project_full_name: form.value.project_full_name || "",
    sponsor: form.value.sponsor || "",
    protocol_no: form.value.protocol_no || "",
    lead_unit: form.value.lead_unit || "",
    principal_investigator: form.value.principal_investigator || "",
    main_pm: form.value.main_pm || "",
    research_analysis: form.value.research_analysis || "",
    research_product: form.value.research_product || "",
    control_product: form.value.control_product || "",
    indication: form.value.indication || "",
    research_population: form.value.research_population || "",
    research_design: form.value.research_design || "",
    planned_site_count: form.value.planned_site_count,
    // These three fields are setup-linked and persisted by setup draft/publish flow.
    // Excluding them avoids false "project unsaved" prompts after saving setup config.
    plan_start_date: "",
    plan_end_date: "",
    planned_enrollment_count: null,
    summary_note: form.value.summary_note || "",
    objective_note: form.value.objective_note || "",
    phase: form.value.phase || "",
    status: form.value.status || "",
    visit_interval_days: form.value.visit_interval_days,
    visit_total: form.value.visit_total,
    visit_window_start_offset: form.value.visit_window_start_offset,
    visit_window_end_offset: form.value.visit_window_end_offset,
  });
const serializeSetupForCompare = (): string => JSON.stringify(clone(setupDraft));
const buildProjectPublishSnapshot = (): ProjectPublishSnapshot => ({
    code: form.value.code || "",
    name: form.value.name || "",
    project_full_name: form.value.project_full_name || "",
    sponsor: form.value.sponsor || "",
    protocol_no: form.value.protocol_no || "",
    lead_unit: form.value.lead_unit || "",
    principal_investigator: form.value.principal_investigator || "",
    main_pm: form.value.main_pm || "",
    research_analysis: form.value.research_analysis || "",
    research_product: form.value.research_product || "",
    control_product: form.value.control_product || "",
    indication: form.value.indication || "",
    research_population: form.value.research_population || "",
    research_design: form.value.research_design || "",
    plan_start_date: form.value.plan_start_date || "",
    plan_end_date: form.value.plan_end_date || "",
    planned_site_count: form.value.planned_site_count,
    planned_enrollment_count: form.value.planned_enrollment_count,
    summary_note: form.value.summary_note || "",
    objective_note: form.value.objective_note || "",
    status: form.value.status || "",
    visit_interval_days: form.value.visit_interval_days,
    visit_total: form.value.visit_total,
    visit_window_start_offset: form.value.visit_window_start_offset,
    visit_window_end_offset: form.value.visit_window_end_offset,
  });
const serializeProjectForPublishCompare = (): string => JSON.stringify(buildProjectPublishSnapshot());
const hasFormUnsavedChanges = computed(() => Boolean(formBaselineSnapshot.value && serializeFormForCompare() !== formBaselineSnapshot.value));
const hasUnsavedChanges = computed(
  () => hasFormUnsavedChanges.value || projectDirtySinceLastPersist.value || setupDirtySinceLastPersist.value || Boolean(autoSaveTimer.value)
);
const hasSetupDraftUnsavedChanges = computed(() => setupDirtySinceLastPersist.value || Boolean(autoSaveTimer.value));
const hasLeaveGuardChanges = computed(() => hasFormUnsavedChanges.value || hasSetupDraftUnsavedChanges.value);
const setupSectionStepMap: Record<string, number> = {
  projectMilestones: 1,
  enrollmentPlan: 2,
  siteMilestones: 3,
  siteEnrollmentPlans: 4,
  monitoringStrategies: 5,
  centerConfirm: 6,
};
const currentStepErrors = computed(() => {
  const grouped = groupErrorsBySection(
    Object.entries(serverFieldErrors.value).map(([field, message]) => ({ field, message }))
  );
  const section = Object.keys(setupSectionStepMap).find((key) => setupSectionStepMap[key] === activeStep.value);
  if (!section) return [];
  return grouped[section] || [];
});

const projectInfoItems = computed(() => [
  { label: "项目名称", value: form.value.name || project.value?.name || "-" },
  { label: "项目全称", value: form.value.project_full_name || project.value?.project_full_name || "-" },
  { label: "申办方", value: form.value.sponsor || project.value?.sponsor || "-" },
  { label: "项目编号", value: form.value.code || project.value?.code || "-" },
  { label: "方案号", value: form.value.protocol_no || project.value?.protocol_no || "-" },
  { label: "组长单位", value: form.value.lead_unit || project.value?.lead_unit || "-" },
  { label: "主要研究者", value: form.value.principal_investigator || project.value?.principal_investigator || "-" },
  { label: "主PM", value: form.value.main_pm || project.value?.main_pm || "-" },
]);

const researchInfoItems = computed(() => [
  { label: "研究分期", value: form.value.research_analysis || project.value?.research_analysis || "-" },
  { label: "研究产品", value: form.value.research_product || project.value?.research_product || "-" },
  { label: "对照产品", value: form.value.control_product || project.value?.control_product || "-" },
  { label: "适应症", value: form.value.indication || project.value?.indication || "-" },
  { label: "研究人群", value: form.value.research_population || project.value?.research_population || "-" },
  { label: "研究设计", value: form.value.research_design || project.value?.research_design || "-" },
]);

const executionInfoItems = computed(() => [
  { label: "计划开始日期", value: form.value.plan_start_date || project.value?.plan_start_date || "-" },
  { label: "计划结束日期", value: form.value.plan_end_date || project.value?.plan_end_date || "-" },
  { label: "项目状态", value: statusLabel(form.value.status || project.value?.status || "DRAFT") },
  { label: "计划中心数", value: toDisplayNumber(form.value.planned_site_count) },
  { label: "计划入组数", value: toDisplayNumber(setupDraft.enrollmentPlan.totalTarget) },
]);

const summaryInfoItems = computed(() => [
  { label: "访视总数", value: toDisplayNumber(form.value.visit_total) },
  { label: "访视间隔（天）", value: toDisplayNumber(form.value.visit_interval_days) },
  { label: "窗口期起始偏移", value: toDisplayNumber(form.value.visit_window_start_offset) },
  { label: "窗口期结束偏移", value: toDisplayNumber(form.value.visit_window_end_offset) },
  { label: "方案摘要说明", value: form.value.summary_note || "-" },
  { label: "研究目标摘要", value: form.value.objective_note || "-" },
]);

const rules = ref<FormRules>({
  code: [{ required: true, message: requiredMessage(TEXT.common.fields.projectCode), trigger: "blur" }],
  name: [{ required: true, message: requiredMessage(TEXT.common.fields.projectName), trigger: "blur" }],
  status: [{ required: true, message: requiredMessage(TEXT.common.fields.status), trigger: "change" }],
});

const makeId = (): string => `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
const nowString = (): string => new Date().toLocaleString("zh-CN", { hour12: false });
const clone = <T>(value: T): T => JSON.parse(JSON.stringify(value));
const formatDisplayTime = (value?: string | null): string => {
  if (!value) return "";
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return value;
  return date.toLocaleString("zh-CN", { hour12: false });
};
const clearSetupValidationErrors = () => {
  serverFieldErrors.value = {};
};
const markSetupSynced = () => {
  setupDirtySinceLastPersist.value = false;
};

const normalizeEnrollmentTarget = (value: number | null | undefined): number => {
  if (value === null || value === undefined) return 0;
  if (!Number.isFinite(value)) return 0;
  if (value < 0) return 0;
  return Math.floor(value);
};

const normalizePlanDate = (value: string | null | undefined): string => {
  if (!value) return "";
  return String(value);
};
const parseDateOnlyValue = (value: string | null | undefined): Date | null => {
  const normalized = normalizePlanDate(value);
  if (!normalized) return null;
  const d = new Date(`${normalized}T00:00:00`);
  if (Number.isNaN(d.getTime())) return null;
  return d;
};
const calculateDurationDays = (startDate: string | null | undefined, endDate: string | null | undefined): number => {
  const start = parseDateOnlyValue(startDate);
  const end = parseDateOnlyValue(endDate);
  if (!start || !end) return 0;
  if (end < start) return 0;
  const diffMs = end.getTime() - start.getTime();
  return Math.floor(diffMs / 86400000) + 1;
};
const resolveProjectMilestoneStart = (row: Partial<ProjectMilestoneDraft> | null | undefined): string =>
  normalizePlanDate(row?.startDate || row?.planDate || "");
const resolveProjectMilestoneEnd = (row: Partial<ProjectMilestoneDraft> | null | undefined): string =>
  normalizePlanDate(row?.endDate || row?.planDate || "");
const resolveProjectMilestoneDurationDays = (row: Partial<ProjectMilestoneDraft> | null | undefined): number => {
  const rawDays = Number((row as any)?.durationDays);
  if (Number.isFinite(rawDays) && rawDays > 0) return Math.floor(rawDays);
  const startDate = resolveProjectMilestoneStart(row);
  const endDate = resolveProjectMilestoneEnd(row);
  if (!startDate && !endDate) return 0;
  return calculateDurationDays(startDate, endDate) || 1;
};
const formatProjectMilestoneDuration = (row: Partial<ProjectMilestoneDraft> | null | undefined): string => {
  const days = resolveProjectMilestoneDurationDays(row);
  return days > 0 ? `${days}天` : "-";
};
const normalizeProjectMilestoneRows = (rows: ProjectMilestoneDraft[]): ProjectMilestoneDraft[] =>
  rows.map((row) => {
    const startDate = resolveProjectMilestoneStart(row);
    const endDate = resolveProjectMilestoneEnd(row);
    const durationDays = resolveProjectMilestoneDurationDays({ ...row, startDate, endDate });
    return {
      ...row,
      planDate: startDate,
      startDate,
      endDate,
      durationDays: durationDays > 0 ? durationDays : 1,
    };
  });
const getProjectPlanWindow = (): { start: string; end: string } => ({
  start: normalizePlanDate(form.value.plan_start_date || project.value?.plan_start_date || ""),
  end: normalizePlanDate(form.value.plan_end_date || project.value?.plan_end_date || ""),
});

const ENROLLMENT_STAGE_PAYLOAD_TYPE = "enrollment_plan_v2";
const SITE_ENROLLMENT_STAGE_PAYLOAD_TYPE = "site_enrollment_plan_v2";
const enrollmentCycleLabelMap: Record<EnrollmentCycle, string> = {
  month: "月",
  quarter: "季度",
};
const enrollmentKeyPatternMap: Record<EnrollmentCycle, RegExp> = {
  month: /^\d{4}-(0[1-9]|1[0-2])$/,
  quarter: /^\d{4}-Q[1-4]$/,
};
const normalizeEnrollmentCycle = (value: unknown): EnrollmentCycle => {
  const normalized = String(value || "").trim().toLowerCase();
  if (normalized === "quarter") return "quarter";
  return "month";
};
const createEmptyEnrollmentTargetsByCycle = (): Record<EnrollmentCycle, Record<string, number>> => ({
  month: {},
  quarter: {},
});
const normalizeCycleTargets = (cycle: EnrollmentCycle, raw: unknown): Record<string, number> => {
  if (!raw || typeof raw !== "object") return {};
  const pattern = enrollmentKeyPatternMap[cycle];
  const normalized: Record<string, number> = {};
  Object.entries(raw as Record<string, unknown>).forEach(([key, value]) => {
    if (!pattern.test(key)) return;
    const numeric = Number(value);
    if (!Number.isFinite(numeric)) return;
    normalized[key] = normalizeEnrollmentTarget(numeric);
  });
  return normalized;
};
const parseEnrollmentStageBreakdown = (
  raw: string | null | undefined
): { cycle: EnrollmentCycle; targetsByCycle: Record<EnrollmentCycle, Record<string, number>> } => {
  const empty = {
    cycle: "month" as EnrollmentCycle,
    targetsByCycle: createEmptyEnrollmentTargetsByCycle(),
  };
  if (!raw || !String(raw).trim()) return empty;
  try {
    const parsed = JSON.parse(String(raw));
    if (!parsed || typeof parsed !== "object") return empty;
    const data = parsed as Record<string, unknown>;
    const cycle = normalizeEnrollmentCycle(data.cycle);
    const byCycle = createEmptyEnrollmentTargetsByCycle();
    const valuesByCycleRaw = data.valuesByCycle;
    if (valuesByCycleRaw && typeof valuesByCycleRaw === "object") {
      byCycle.month = normalizeCycleTargets("month", (valuesByCycleRaw as Record<string, unknown>).month);
      byCycle.quarter = normalizeCycleTargets("quarter", (valuesByCycleRaw as Record<string, unknown>).quarter);
      return { cycle, targetsByCycle: byCycle };
    }
    const legacyTargetsRaw = data.monthlyTargets || data.values;
    const fallbackCycle = data.monthlyTargets ? "month" : cycle;
    byCycle[fallbackCycle] = normalizeCycleTargets(fallbackCycle, legacyTargetsRaw);
    return { cycle, targetsByCycle: byCycle };
  } catch {
    return empty;
  }
};
const buildEnrollmentStageBreakdown = (
  payload: { cycle: EnrollmentCycle; targetsByCycle: Record<EnrollmentCycle, Record<string, number>> }
): string => {
  const byCycle: Record<EnrollmentCycle, Record<string, number>> = {
    month: normalizeCycleTargets("month", payload.targetsByCycle?.month),
    quarter: normalizeCycleTargets("quarter", payload.targetsByCycle?.quarter),
  };
  return JSON.stringify({
    type: ENROLLMENT_STAGE_PAYLOAD_TYPE,
    cycle: normalizeEnrollmentCycle(payload.cycle),
    valuesByCycle: byCycle,
    values: byCycle[normalizeEnrollmentCycle(payload.cycle)] || {},
  });
};
const parseSiteEnrollmentStageBreakdown = (raw: string | null | undefined): Record<EnrollmentCycle, Record<string, number>> => {
  if (!raw || !String(raw).trim()) return createEmptyEnrollmentTargetsByCycle();
  try {
    const parsed = JSON.parse(String(raw));
    if (!parsed || typeof parsed !== "object") return createEmptyEnrollmentTargetsByCycle();
    const data = parsed as Record<string, unknown>;
    const byCycle = createEmptyEnrollmentTargetsByCycle();
    const valuesByCycleRaw = data.valuesByCycle;
    if (valuesByCycleRaw && typeof valuesByCycleRaw === "object") {
      byCycle.month = normalizeCycleTargets("month", (valuesByCycleRaw as Record<string, unknown>).month);
      byCycle.quarter = normalizeCycleTargets("quarter", (valuesByCycleRaw as Record<string, unknown>).quarter);
      return byCycle;
    }
    byCycle.month = normalizeCycleTargets("month", data.values || data.monthlyTargets);
    byCycle.quarter = normalizeCycleTargets("quarter", data.quarterlyTargets);
    return byCycle;
  } catch {
    return createEmptyEnrollmentTargetsByCycle();
  }
};
const buildSiteEnrollmentStageBreakdown = (payload: Record<EnrollmentCycle, Record<string, number>>): string => {
  const byCycle: Record<EnrollmentCycle, Record<string, number>> = {
    month: normalizeCycleTargets("month", payload.month),
    quarter: normalizeCycleTargets("quarter", payload.quarter),
  };
  return JSON.stringify({
    type: SITE_ENROLLMENT_STAGE_PAYLOAD_TYPE,
    valuesByCycle: byCycle,
  });
};
const parseDateParts = (value: string | null | undefined): { year: number; month: number; day: number } | null => {
  const text = String(value || "").trim();
  const matched = /^(\d{4})-(\d{2})-(\d{2})$/.exec(text);
  if (!matched) return null;
  const year = Number(matched[1]);
  const month = Number(matched[2]);
  const day = Number(matched[3]);
  if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day)) return null;
  if (month < 1 || month > 12 || day < 1 || day > 31) return null;
  return { year, month, day };
};
const toDateOnly = (value: string | null | undefined): Date | null => {
  const parts = parseDateParts(value);
  if (!parts) return null;
  const date = new Date(parts.year, parts.month - 1, parts.day);
  if (date.getFullYear() !== parts.year || date.getMonth() !== parts.month - 1 || date.getDate() !== parts.day) {
    return null;
  }
  return date;
};
const pad2 = (value: number): string => String(value).padStart(2, "0");
const getQuarter = (month: number): number => Math.floor((month - 1) / 3) + 1;
const toYearMonthIndex = (year: number, month: number): number => year * 12 + (month - 1);
const buildYearMonthKey = (year: number, month: number): string => `${year}-${String(month).padStart(2, "0")}`;
const buildEnrollmentSlotsByCycle = (
  cycle: EnrollmentCycle,
  startDateText: string | null | undefined,
  endDateText: string | null | undefined
): EnrollmentPeriodSlot[] => {
  const start = parseDateParts(startDateText);
  const end = parseDateParts(endDateText);
  if (!start || !end) return [];
  if (toYearMonthIndex(start.year, start.month) > toYearMonthIndex(end.year, end.month)) return [];
  if (cycle === "month") {
    const result: EnrollmentPeriodSlot[] = [];
    let year = start.year;
    let month = start.month;
    while (toYearMonthIndex(year, month) <= toYearMonthIndex(end.year, end.month)) {
      result.push({
        key: buildYearMonthKey(year, month),
        label: `${month}月`,
        groupLabel: `${year}`,
      });
      month += 1;
      if (month > 12) {
        month = 1;
        year += 1;
      }
    }
    return result;
  }
  const startDate = toDateOnly(startDateText);
  const endDate = toDateOnly(endDateText);
  if (!startDate || !endDate || startDate.getTime() > endDate.getTime()) return [];
  const result: EnrollmentPeriodSlot[] = [];
  let year = start.year;
  let quarter = getQuarter(start.month);
  const endQuarter = getQuarter(end.month);
  while (year < end.year || (year === end.year && quarter <= endQuarter)) {
    result.push({
      key: `${year}-Q${quarter}`,
      label: `Q${quarter}`,
      groupLabel: `${year}`,
    });
    quarter += 1;
    if (quarter > 4) {
      quarter = 1;
      year += 1;
    }
  }
  return result;
};
const currentEnrollmentSlots = computed(() =>
  buildEnrollmentSlotsByCycle(enrollmentPlanCycle.value, setupDraft.enrollmentPlan.startDate, setupDraft.enrollmentPlan.endDate)
);
const enrollmentPlanMonthSlots = computed(() => {
  const slots = buildEnrollmentSlotsByCycle("month", setupDraft.enrollmentPlan.startDate, setupDraft.enrollmentPlan.endDate);
  return slots.map((slot) => ({
    year: Number(slot.key.slice(0, 4)),
    month: Number(slot.key.slice(5, 7)),
    key: slot.key,
  }));
});
const enrollmentPlanYearRows = computed<EnrollmentYearRow[]>(() => {
  const slots = enrollmentPlanMonthSlots.value;
  if (!slots.length) return [];
  const keySet = new Set(slots.map((slot) => slot.key));
  const firstYear = slots[0].year;
  const lastYear = slots[slots.length - 1].year;
  const rows: EnrollmentYearRow[] = [];
  for (let year = firstYear; year <= lastYear; year += 1) {
    rows.push({
      year,
      cells: Array.from({ length: 12 }, (_, idx) => {
        const key = buildYearMonthKey(year, idx + 1);
        return { key: keySet.has(key) ? key : null };
      }),
    });
  }
  return rows;
});
const enrollmentPlanCycleLabel = computed(() => enrollmentCycleLabelMap[enrollmentPlanCycle.value] || "月");
const buildEnrollmentCycleGridColumns = (): EnrollmentCycleGridColumn[] => {
  return Array.from({ length: 4 }, (_, idx) => {
    const quarter = idx + 1;
    const key = `Q${quarter}`;
    return { key, label: key };
  });
};
const resolveEnrollmentCycleColumnKey = (periodKey: string): string | null => {
  const matched = /^(\d{4})-Q([1-4])$/.exec(periodKey);
  return matched ? `Q${matched[2]}` : null;
};
const enrollmentCycleGridColumns = computed<EnrollmentCycleGridColumn[]>(() => {
  if (enrollmentPlanCycle.value === "month") return [];
  return buildEnrollmentCycleGridColumns();
});
const enrollmentCycleRowLabel = computed(() => "年份");
const enrollmentCycleGridRows = computed<EnrollmentCycleGridRow[]>(() => {
  if (enrollmentPlanCycle.value === "month") return [];
  const columns = enrollmentCycleGridColumns.value;
  if (!columns.length) return [];
  const columnIndexMap = new Map<string, number>();
  columns.forEach((column, idx) => columnIndexMap.set(column.key, idx));
  const rowMap = new Map<string, EnrollmentCycleGridRow>();
  currentEnrollmentSlots.value.forEach((slot) => {
    const columnKey = resolveEnrollmentCycleColumnKey(slot.key);
    if (!columnKey) return;
    const columnIndex = columnIndexMap.get(columnKey);
    if (columnIndex === undefined) return;
    let row = rowMap.get(slot.groupLabel);
    if (!row) {
      row = {
        groupLabel: slot.groupLabel,
        cells: columns.map(() => ({ key: null })),
      };
      rowMap.set(slot.groupLabel, row);
    }
    row.cells[columnIndex] = { key: slot.key };
  });
  return Array.from(rowMap.values());
});
const getEnrollmentPeriodTarget = (key: string | null): number => {
  if (!key) return 0;
  const value = enrollmentTargetsByCycle.value[enrollmentPlanCycle.value]?.[key];
  if (value === null || value === undefined) return 0;
  return normalizeEnrollmentTarget(value);
};
const displayEnrollmentPeriodTarget = (key: string | null): string => {
  if (!key) return "-";
  const value = enrollmentTargetsByCycle.value[enrollmentPlanCycle.value]?.[key];
  if (value === null || value === undefined) return "-";
  return String(normalizeEnrollmentTarget(value));
};
const setEnrollmentPeriodTarget = (key: string | null, value: number | null) => {
  if (!key) return;
  const cycle = enrollmentPlanCycle.value;
  const nextTargets = { ...(enrollmentTargetsByCycle.value[cycle] || {}) };
  if (value === null || value === undefined) {
    delete nextTargets[key];
  } else {
    nextTargets[key] = normalizeEnrollmentTarget(value);
  }
  enrollmentTargetsByCycle.value = {
    ...enrollmentTargetsByCycle.value,
    [cycle]: nextTargets,
  };
};
const clearEnrollmentPeriodPlan = () => {
  if (!isEditing.value || isPublishedView.value || !canEditSetup.value) return;
  const cycle = enrollmentPlanCycle.value;
  const scopedKeys = new Set(currentEnrollmentSlots.value.map((item) => item.key));
  if (!scopedKeys.size) {
    ElMessage.warning("请先填写有效的计划入组日期范围");
    return;
  }
  const nextTargets = { ...(enrollmentTargetsByCycle.value[cycle] || {}) };
  scopedKeys.forEach((key) => {
    delete nextTargets[key];
  });
  enrollmentTargetsByCycle.value = {
    ...enrollmentTargetsByCycle.value,
    [cycle]: nextTargets,
  };
  ElMessage.success(`已清空当前日期范围内的${enrollmentPlanCycleLabel.value}周期计划`);
};
const quickFillEnrollmentPeriodPlan = () => {
  if (!isEditing.value || isPublishedView.value || !canEditSetup.value) return;
  const cycle = enrollmentPlanCycle.value;
  const slots = currentEnrollmentSlots.value;
  if (!slots.length) {
    ElMessage.warning("请先填写有效的计划入组日期范围");
    return;
  }
  const totalTarget = normalizeEnrollmentTarget(setupDraft.enrollmentPlan.totalTarget);
  const base = Math.floor(totalTarget / slots.length);
  let remainder = totalTarget - base * slots.length;
  const nextTargets = { ...(enrollmentTargetsByCycle.value[cycle] || {}) };
  slots.forEach((slot) => {
    const extra = remainder > 0 ? 1 : 0;
    nextTargets[slot.key] = base + extra;
    if (remainder > 0) remainder -= 1;
  });
  enrollmentTargetsByCycle.value = {
    ...enrollmentTargetsByCycle.value,
    [cycle]: nextTargets,
  };
  ElMessage.success(`已按${enrollmentPlanCycleLabel.value}周期快速均分入组计划`);
};
const projectMonthlyPlannedCount = computed(() => {
  return currentEnrollmentSlots.value.reduce((sum, slot) => sum + getEnrollmentPeriodTarget(slot.key), 0);
});
const projectMonthlyPendingCount = computed(() => {
  const totalTarget = normalizeEnrollmentTarget(setupDraft.enrollmentPlan.totalTarget);
  return Math.max(totalTarget - projectMonthlyPlannedCount.value, 0);
});
const projectMonthlyOverflowCount = computed(() => {
  const totalTarget = normalizeEnrollmentTarget(setupDraft.enrollmentPlan.totalTarget);
  return Math.max(projectMonthlyPlannedCount.value - totalTarget, 0);
});
const centerEnrollmentTargetMap = computed(() => {
  const map = new Map<string, number>();
  setupDraft.siteEnrollmentPlans.forEach((row) => {
    const siteId = String(row.siteId || "").trim();
    if (!siteId) return;
    map.set(siteId, normalizeEnrollmentTarget(row.target));
  });
  return map;
});
const centerActiveSiteCount = computed(() => siteOptions.value.filter((site) => site.is_active !== false).length);
const centerConfiguredSiteCount = computed(() => {
  const plannedSiteIds = centerEnrollmentTargetMap.value;
  return siteOptions.value.filter((site) => site.is_active !== false && plannedSiteIds.has(String(site.id || "").trim())).length;
});
const centerPlannedCount = computed(() => {
  return Array.from(centerEnrollmentTargetMap.value.values()).reduce((sum, target) => sum + target, 0);
});
const centerPendingCount = computed(() => {
  const totalTarget = normalizeEnrollmentTarget(setupDraft.enrollmentPlan.totalTarget);
  return Math.max(totalTarget - centerPlannedCount.value, 0);
});
const centerOverflowCount = computed(() => {
  const totalTarget = normalizeEnrollmentTarget(setupDraft.enrollmentPlan.totalTarget);
  return Math.max(centerPlannedCount.value - totalTarget, 0);
});
const centerUnplannedSiteCount = computed(() => {
  const activeSites = siteOptions.value.filter((site) => site.is_active !== false);
  if (!activeSites.length) return 0;
  const plannedSiteIds = centerEnrollmentTargetMap.value;
  return activeSites.filter((site) => !plannedSiteIds.has(site.id)).length;
});
const getSiteEnrollmentTargetsByCycle = (row: SiteEnrollmentPlanDraft): Record<EnrollmentCycle, Record<string, number>> => {
  const parsed = parseSiteEnrollmentStageBreakdown(row.stageBreakdown);
  return {
    month: { ...parsed.month },
    quarter: { ...parsed.quarter },
  };
};
const sumSiteEnrollmentTargetForCurrentCycle = (targets: Record<EnrollmentCycle, Record<string, number>>): number => {
  const slotKeys = new Set(currentEnrollmentSlots.value.map((item) => item.key));
  const activeTargets = targets[enrollmentPlanCycle.value] || {};
  let sum = 0;
  Object.entries(activeTargets).forEach(([key, value]) => {
    if (!slotKeys.has(key)) return;
    sum += normalizeEnrollmentTarget(value);
  });
  return sum;
};
const centerCyclePlannedCount = computed(() => {
  return setupDraft.siteEnrollmentPlans.reduce((sum, row) => {
    return sum + sumSiteEnrollmentTargetForCurrentCycle(getSiteEnrollmentTargetsByCycle(row));
  }, 0);
});
const centerCyclePendingCount = computed(() => Math.max(centerPlannedCount.value - centerCyclePlannedCount.value, 0));
const centerCycleOverflowCount = computed(() => Math.max(centerCyclePlannedCount.value - centerPlannedCount.value, 0));
const getSiteEnrollmentPeriodTarget = (row: SiteEnrollmentPlanDraft, key: string | null): number => {
  if (!key) return 0;
  const targets = getSiteEnrollmentTargetsByCycle(row);
  const value = targets[enrollmentPlanCycle.value]?.[key];
  if (value === null || value === undefined) return 0;
  return normalizeEnrollmentTarget(value);
};
const displaySiteEnrollmentPeriodTarget = (row: SiteEnrollmentPlanDraft, key: string | null): string => {
  if (!key) return "-";
  const targets = getSiteEnrollmentTargetsByCycle(row);
  const value = targets[enrollmentPlanCycle.value]?.[key];
  if (value === null || value === undefined) return "-";
  return String(normalizeEnrollmentTarget(value));
};
const setSiteEnrollmentPeriodTarget = (rowIndex: number, key: string | null, value: number | null) => {
  if (!key) return;
  const row = setupDraft.siteEnrollmentPlans[rowIndex];
  if (!row) return;
  const targets = getSiteEnrollmentTargetsByCycle(row);
  const nextCycleTargets = { ...(targets[enrollmentPlanCycle.value] || {}) };
  if (value === null || value === undefined) {
    delete nextCycleTargets[key];
  } else {
    nextCycleTargets[key] = normalizeEnrollmentTarget(value);
  }
  targets[enrollmentPlanCycle.value] = nextCycleTargets;
  row.stageBreakdown = buildSiteEnrollmentStageBreakdown(targets);
  row.target = sumSiteEnrollmentTargetForCurrentCycle(targets);
};
const clearSiteEnrollmentPeriodPlan = (rowIndex: number) => {
  const row = setupDraft.siteEnrollmentPlans[rowIndex];
  if (!row) return;
  const targets = getSiteEnrollmentTargetsByCycle(row);
  const scopedKeys = new Set(currentEnrollmentSlots.value.map((item) => item.key));
  const nextCycleTargets = { ...(targets[enrollmentPlanCycle.value] || {}) };
  scopedKeys.forEach((key) => {
    delete nextCycleTargets[key];
  });
  targets[enrollmentPlanCycle.value] = nextCycleTargets;
  row.stageBreakdown = buildSiteEnrollmentStageBreakdown(targets);
  row.target = sumSiteEnrollmentTargetForCurrentCycle(targets);
};
const quickFillSiteEnrollmentPeriodPlan = (rowIndex: number) => {
  const row = setupDraft.siteEnrollmentPlans[rowIndex];
  if (!row) return;
  const slots = currentEnrollmentSlots.value;
  if (!slots.length) {
    ElMessage.warning("请先填写有效的计划入组日期范围");
    return;
  }
  const rowTarget = normalizeEnrollmentTarget(row.target);
  const base = Math.floor(rowTarget / slots.length);
  let remainder = rowTarget - base * slots.length;
  const targets = getSiteEnrollmentTargetsByCycle(row);
  const nextCycleTargets = { ...(targets[enrollmentPlanCycle.value] || {}) };
  slots.forEach((slot) => {
    const extra = remainder > 0 ? 1 : 0;
    nextCycleTargets[slot.key] = base + extra;
    if (remainder > 0) remainder -= 1;
  });
  targets[enrollmentPlanCycle.value] = nextCycleTargets;
  row.stageBreakdown = buildSiteEnrollmentStageBreakdown(targets);
  row.target = sumSiteEnrollmentTargetForCurrentCycle(targets);
};
const applyEnrollmentStageFromDraft = () => {
  const parsed = parseEnrollmentStageBreakdown(setupDraft.enrollmentPlan.stageBreakdown);
  enrollmentStageSyncingFromModel.value = true;
  enrollmentPlanCycle.value = parsed.cycle;
  enrollmentTargetsByCycle.value = {
    ...createEmptyEnrollmentTargetsByCycle(),
    ...parsed.targetsByCycle,
  };
  enrollmentStageSyncingFromModel.value = false;
};

const getFieldError = (fieldPath: string): string => {
  return serverFieldErrors.value[fieldPath] || "";
};

const extractValidationErrors = (error: any): SetupValidationError[] => {
  const detail = error?.response?.data?.detail;
  const list = detail?.errors || error?.response?.data?.errors;
  if (!Array.isArray(list)) return [];
  return list
    .map((item: any) => ({
      field: String(item?.field || ""),
      message: String(item?.message || ""),
    }))
    .filter((item: SetupValidationError) => item.field && item.message);
};

const markValidationErrors = (errors: SetupValidationError[]) => {
  const next: Record<string, string> = {};
  errors.forEach((item) => {
    next[item.field] = item.message;
  });
  serverFieldErrors.value = next;
};

const getApiErrorMessage = (error: any, fallback: string): string => {
  const detail = error?.response?.data?.detail;
  if (typeof detail === "string") return detail;
  if (typeof detail?.message === "string") return detail.message;
  if (typeof error?.response?.data?.message === "string") return error.response.data.message;
  return fallback;
};

const focusFirstValidationField = (errors: SetupValidationError[]) => {
  const first = errors[0];
  if (!first) return;
  const parsed = parseFieldPath(first.field);
  if (parsed && setupSectionStepMap[parsed.section] !== undefined) {
    activeStep.value = setupSectionStepMap[parsed.section];
  } else if (first.field.startsWith("enrollmentPlan.")) {
    activeStep.value = 2;
  }
  requestAnimationFrame(() => {
    const target = document.querySelector(`[data-field-path="${first.field}"]`) as HTMLElement | null;
    target?.scrollIntoView({ behavior: "smooth", block: "center" });
  });
};

const handleValidationFailure = (error: any, fallback: string): boolean => {
  const validationErrors = extractValidationErrors(error);
  if (!validationErrors.length) return false;
  markValidationErrors(validationErrors);
  focusFirstValidationField(validationErrors);
  ElMessage.error(getApiErrorMessage(error, fallback));
  return true;
};

const stepHasErrors = (stepIndex: number): boolean => {
  return Object.keys(serverFieldErrors.value).some((path) => {
    const parsed = parseFieldPath(path);
    if (parsed) return setupSectionStepMap[parsed.section] === stepIndex;
    if (path.startsWith("enrollmentPlan.")) return stepIndex === 2;
    if (path.startsWith("projectMilestones")) return stepIndex === 1;
    if (path.startsWith("siteMilestones")) return stepIndex === 3;
    if (path.startsWith("siteEnrollmentPlans")) return stepIndex === 4;
    if (path.startsWith("monitoringStrategies")) return stepIndex === 5;
    if (path.startsWith("centerConfirm")) return stepIndex === 6;
    return false;
  });
};

const isStepCompleted = (stepIndex: number): boolean => {
  if (stepHasErrors(stepIndex)) return false;
  if (stepIndex === 0) {
    return Boolean((form.value.name || "").trim() && (form.value.code || "").trim());
  }
  if (stepIndex === 1) {
    return (
      setupDraft.projectMilestones.length > 0 &&
      setupDraft.projectMilestones.every((item) => {
        const startDate = resolveProjectMilestoneStart(item);
        const endDate = resolveProjectMilestoneEnd(item);
        return (item.name || "").trim().length > 0 && !!startDate && !!endDate && startDate <= endDate;
      })
    );
  }
  if (stepIndex === 2) {
    const plan = setupDraft.enrollmentPlan;
    const { start: projectPlanStart, end: projectPlanEnd } = getProjectPlanWindow();
    const enrollmentStart = normalizePlanDate(plan.startDate);
    const enrollmentEnd = normalizePlanDate(plan.endDate);
    if (!(plan.totalTarget >= 0 && enrollmentStart && enrollmentEnd && enrollmentStart <= enrollmentEnd)) return false;
    if (projectPlanStart && enrollmentStart < projectPlanStart) return false;
    if (projectPlanEnd && enrollmentStart > projectPlanEnd) return false;
    if (projectPlanStart && enrollmentEnd < projectPlanStart) return false;
    if (projectPlanEnd && enrollmentEnd > projectPlanEnd) return false;
    return true;
  }
  if (stepIndex === 3) {
    const { start: planStart, end: planEnd } = getProjectPlanWindow();
    return (
      setupDraft.siteMilestones.length > 0 &&
      setupDraft.siteMilestones.every((item) => {
        const planDate = normalizePlanDate(item.planDate);
        if (!((item.milestone || "").trim().length > 0 && planDate)) return false;
        if (planStart && planDate < planStart) return false;
        if (planEnd && planDate > planEnd) return false;
        return true;
      })
    );
  }
  if (stepIndex === 4) {
    const { start: planStart, end: planEnd } = getProjectPlanWindow();
    const siteIdSet = new Set<string>();
    return (
      setupDraft.siteEnrollmentPlans.length > 0 &&
      setupDraft.siteEnrollmentPlans.every((item) => {
        const siteId = normalizeSiteId(item.siteId);
        if (!siteId) return false;
        if (siteIdSet.has(siteId)) return false;
        siteIdSet.add(siteId);
        const startDate = normalizePlanDate(item.startDate);
        const endDate = normalizePlanDate(item.endDate);
        if (!(item.target >= 0 && startDate && endDate && startDate <= endDate)) return false;
        if (planStart && startDate < planStart) return false;
        if (planEnd && startDate > planEnd) return false;
        if (planStart && endDate < planStart) return false;
        if (planEnd && endDate > planEnd) return false;
        return true;
      })
    );
  }
  if (stepIndex === 5) {
    return (
      setupDraft.monitoringStrategies.length > 0 &&
      setupDraft.monitoringStrategies.every(
        (item) => (item.strategyType || "").trim().length > 0 && (item.detail || "").trim().length > 0 && (item.frequency || "").trim().length > 0
      )
    );
  }
  if (stepIndex === 6) {
    return true;
  }
  return false;
};
const incompleteSteps = computed(() => steps.map((step, index) => ({ index, label: step.label })).filter((item) => !isStepCompleted(item.index)));

const statusLabel = (status: string): string => {
  if (status === "ACTIVE") return TEXT.enums.projectStatus.ACTIVE;
  if (status === "CLOSED") return TEXT.enums.projectStatus.CLOSED;
  return TEXT.enums.projectStatus.DRAFT;
};

const toDisplayNumber = (value: number | null | undefined): string => {
  if (value === null || value === undefined) return "-";
  return String(value);
};

const getSiteName = (siteId: string): string => {
  return siteOptions.value.find((s) => s.id === siteId)?.name || "";
};
const normalizeSiteId = (siteId: string | null | undefined): string => String(siteId || "").trim();
const normalizeOwnerDisplay = (value: string | null | undefined): string => {
  if (!value) return "-";
  return value
    .split(/[,\uFF0C;\uFF1B]/)
    .map((item) => item.trim().replace(/^[\[\]"'`]+|[\[\]"'`]+$/g, ""))
    .filter(Boolean)
    .map((item) => memberDisplayMap.value[item] || item)
    .join("、") || "-";
};
const projectMilestoneOwnerOptions = computed(() => {
  const options = [...projectMemberOptions.value];
  const currentOwner = String(projectMilestoneEditorForm.owner || "").trim();
  if (currentOwner && !options.some((option) => option.value === currentOwner)) {
    options.unshift({
      value: currentOwner,
      label: normalizeOwnerDisplay(currentOwner),
    });
  }
  return options;
});
const siteMilestoneOwnerOptions = computed(() => {
  const options = [...projectMemberOptions.value];
  const currentOwner = String(siteMilestoneEditorForm.owner || "").trim();
  if (currentOwner && !options.some((option) => option.value === currentOwner)) {
    options.unshift({
      value: currentOwner,
      label: normalizeOwnerDisplay(currentOwner),
    });
  }
  return options;
});
const siteEnrollmentPlanMap = computed(() => {
  const map = new Map<string, SiteEnrollmentPlanDraft>();
  setupDraft.siteEnrollmentPlans.forEach((row) => {
    const siteId = normalizeSiteId(row.siteId);
    if (!siteId) return;
    map.set(siteId, row);
  });
  return map;
});
const centerConfirmDisplayRows = computed(() => {
  return siteOptions.value.map((site) => {
    const siteId = normalizeSiteId(site.id);
    const plan = siteEnrollmentPlanMap.value.get(siteId);
    return {
      siteId,
      siteName: site.name || "",
      city: site.city || "",
      piName: site.pi_name || "",
      ownerDisplay: normalizeOwnerDisplay(site.contact),
      target: normalizeEnrollmentTarget(plan?.target),
      isActive: site.is_active !== false,
      startDate: plan?.startDate || "",
      endDate: plan?.endDate || "",
    };
  });
});
const selectedSiteEnrollmentPlanSiteId = ref("");
const siteEnrollmentSelectableSites = computed(() => {
  const map = new Map<string, Site>();
  siteOptions.value.forEach((site) => {
    map.set(normalizeSiteId(site.id), site);
  });
  setupDraft.siteEnrollmentPlans.forEach((row) => {
    const siteId = normalizeSiteId(row.siteId);
    if (!siteId || map.has(siteId)) return;
    map.set(siteId, {
      id: siteId,
      study_id: project.value?.id || "",
      name: row.siteName || siteId,
      city: "",
      pi_name: "",
      contact: "",
      enrollment_target: row.target || 0,
      is_active: true,
    });
  });
  return Array.from(map.values());
});
const selectedSiteEnrollmentPlanIndex = computed(() => {
  const selectedSiteId = normalizeSiteId(selectedSiteEnrollmentPlanSiteId.value);
  if (!selectedSiteId) return -1;
  return setupDraft.siteEnrollmentPlans.findIndex((row) => normalizeSiteId(row.siteId) === selectedSiteId);
});
const selectedSiteEnrollmentPlan = computed(() => {
  const index = selectedSiteEnrollmentPlanIndex.value;
  if (index < 0) return null;
  return setupDraft.siteEnrollmentPlans[index] || null;
});
const selectedSitePlanName = computed(() => {
  const nameFromPlan = (selectedSiteEnrollmentPlan.value?.siteName || "").trim();
  if (nameFromPlan) return nameFromPlan;
  const selectedSiteId = normalizeSiteId(selectedSiteEnrollmentPlanSiteId.value);
  if (!selectedSiteId) return "当前中心";
  const matched = siteEnrollmentSelectableSites.value.find((site) => normalizeSiteId(site.id) === selectedSiteId);
  const name = (matched?.name || "").trim();
  return name || "当前中心";
});
const selectedSitePlanTarget = computed(() => normalizeEnrollmentTarget(selectedSiteEnrollmentPlan.value?.target));
const selectedSiteCyclePlannedCount = computed(() => {
  const row = selectedSiteEnrollmentPlan.value;
  if (!row) return 0;
  return sumSiteEnrollmentTargetForCurrentCycle(getSiteEnrollmentTargetsByCycle(row));
});
const selectedSiteCyclePendingCount = computed(() =>
  Math.max(selectedSitePlanTarget.value - selectedSiteCyclePlannedCount.value, 0)
);
const syncSelectedSiteEnrollmentPlanSiteId = () => {
  const selectedSiteId = normalizeSiteId(selectedSiteEnrollmentPlanSiteId.value);
  if (selectedSiteId && siteEnrollmentSelectableSites.value.some((site) => normalizeSiteId(site.id) === selectedSiteId)) {
    return;
  }
  const firstConfiguredSiteId =
    setupDraft.siteEnrollmentPlans.map((row) => normalizeSiteId(row.siteId)).find((siteId) => Boolean(siteId)) || "";
  if (firstConfiguredSiteId) {
    selectedSiteEnrollmentPlanSiteId.value = firstConfiguredSiteId;
    return;
  }
  selectedSiteEnrollmentPlanSiteId.value = normalizeSiteId(siteEnrollmentSelectableSites.value[0]?.id || "");
};
const createSelectedSiteEnrollmentPlan = () => {
  if (!canMutateDraft()) return;
  const selectedSiteId = normalizeSiteId(selectedSiteEnrollmentPlanSiteId.value);
  if (!selectedSiteId) {
    ElMessage.warning("请先选择中心");
    return;
  }
  if (selectedSiteEnrollmentPlanIndex.value >= 0) return;
  setupDraft.siteEnrollmentPlans.push({
    id: makeId(),
    siteId: selectedSiteId,
    siteName: getSiteName(selectedSiteId),
    target: 0,
    startDate: setupDraft.enrollmentPlan.startDate || "",
    endDate: setupDraft.enrollmentPlan.endDate || "",
    note: "",
    stageBreakdown: "",
  });
};
const getSiteEnrollmentEditorIndex = () => getEditingIndex(siteEnrollmentEditor, setupDraft.siteEnrollmentPlans.length);
const isSiteEnrollmentDuplicate = (siteId: string, excludeIndex: number): boolean => {
  const normalizedSiteId = normalizeSiteId(siteId);
  if (!normalizedSiteId) return false;
  return setupDraft.siteEnrollmentPlans.some(
    (row, rowIndex) => rowIndex !== excludeIndex && normalizeSiteId(row.siteId) === normalizedSiteId
  );
};
const isSiteEnrollmentSiteTaken = (siteId: string): boolean => {
  const editingIndex = getSiteEnrollmentEditorIndex();
  return isSiteEnrollmentDuplicate(siteId, editingIndex);
};
watch(
  [siteOptions, () => setupDraft.siteEnrollmentPlans.map((row) => normalizeSiteId(row.siteId)).join("|")],
  syncSelectedSiteEnrollmentPlanSiteId,
  { immediate: true }
);

const createDefaultSetupDraft = (_sites: Site[]): SetupConfigDraft => {
  return {
    projectMilestones: [],
    enrollmentPlan: {
      totalTarget: 0,
      startDate: "",
      endDate: "",
      monthlyGoalNote: "",
      stageBreakdown: "",
    },
    siteMilestones: [],
    siteEnrollmentPlans: [],
    monitoringStrategies: [],
    centerConfirm: [],
  };
};

const normalizeSiteEnrollmentPlans = (rows: SiteEnrollmentPlanDraft[]): SiteEnrollmentPlanDraft[] =>
  rows.map((row) => ({
    ...row,
    stageBreakdown: row.stageBreakdown || "",
  }));

const isSetupDraftShape = (value: unknown): value is SetupConfigDraft => {
  if (!value || typeof value !== "object") return false;
  const data = value as Record<string, unknown>;
  return (
    Array.isArray(data.projectMilestones) &&
    !!data.enrollmentPlan &&
    Array.isArray(data.siteMilestones) &&
    Array.isArray(data.siteEnrollmentPlans) &&
    Array.isArray(data.monitoringStrategies) &&
    Array.isArray(data.centerConfirm)
  );
};

const applySetupDraft = (draft: SetupConfigDraft) => {
  suppressDraftWatch.value = true;
  setupDraft.projectMilestones = normalizeProjectMilestoneRows(clone(draft.projectMilestones));
  setupDraft.enrollmentPlan = clone(draft.enrollmentPlan);
  setupDraft.siteMilestones = clone(draft.siteMilestones);
  setupDraft.siteEnrollmentPlans = normalizeSiteEnrollmentPlans(clone(draft.siteEnrollmentPlans));
  setupDraft.monitoringStrategies = clone(draft.monitoringStrategies);
  setupDraft.centerConfirm = clone(draft.centerConfirm);
  suppressDraftWatch.value = false;
};

const loadLocalSetupDraft = (): SetupConfigDraft | null => {
  try {
    const raw = localStorage.getItem(storageKey.value);
    if (!raw) return null;
    const parsed = JSON.parse(raw) as { version?: number; data?: unknown };
    if (parsed.version !== DRAFT_VERSION || !isSetupDraftShape(parsed.data)) return null;
    return clone(parsed.data);
  } catch {
    return null;
  }
};

const persistSetupDraftToLocal = () => {
  try {
    localStorage.setItem(
      storageKey.value,
      JSON.stringify({
        version: DRAFT_VERSION,
        data: clone(setupDraft),
        savedAt: nowString(),
      })
    );
    setupSaveMeta.value = {
      updatedAt: nowString(),
      savedBy: authStore.user?.full_name || authStore.user?.username || authStore.user?.email || "当前用户",
      serverSynced: false,
    };
  } catch {
    // ignore local storage errors
  }
};

const cacheSetupDraftToLocal = () => {
  try {
    localStorage.setItem(
      storageKey.value,
      JSON.stringify({
        version: DRAFT_VERSION,
        data: clone(setupDraft),
        savedAt: nowString(),
      })
    );
  } catch {
    // ignore local storage errors
  }
};

const loadLocalProjectDraft = (): Record<string, unknown> | null => {
  try {
    const raw = localStorage.getItem(projectDraftStorageKey.value);
    if (!raw) return null;
    const parsed = JSON.parse(raw) as { version?: number; data?: unknown };
    if (parsed.version !== DRAFT_VERSION || !parsed.data || typeof parsed.data !== "object") return null;
    return parsed.data as Record<string, unknown>;
  } catch {
    return null;
  }
};

const applyProjectDraft = (draft: Record<string, unknown>) => {
  const keys = Object.keys(form.value) as Array<keyof typeof form.value>;
  keys.forEach((key) => {
    if (Object.prototype.hasOwnProperty.call(draft, key)) {
      (form.value[key] as any) = draft[key as string] as any;
    }
  });
};

const persistProjectDraftToLocal = () => {
  try {
    localStorage.setItem(
      projectDraftStorageKey.value,
      JSON.stringify({
        version: DRAFT_VERSION,
        data: clone(form.value),
        savedAt: nowString(),
      })
    );
    projectDirtySinceLastPersist.value = Boolean(
      projectServerSnapshot.value && serializeFormForCompare() !== projectServerSnapshot.value
    );
    setupSaveMeta.value = {
      updatedAt: nowString(),
      savedBy: authStore.user?.full_name || authStore.user?.username || authStore.user?.email || "当前用户",
      serverSynced: false,
    };
  } catch {
    // ignore local storage errors
  }
};

const cacheProjectDraftToLocal = () => {
  try {
    localStorage.setItem(
      projectDraftStorageKey.value,
      JSON.stringify({
        version: DRAFT_VERSION,
        data: clone(form.value),
        savedAt: nowString(),
      })
    );
  } catch {
    // ignore local storage errors
  }
};

const clearLocalProjectDraft = () => {
  try {
    localStorage.removeItem(projectDraftStorageKey.value);
  } catch {
    // ignore local storage errors
  }
};

const loadSetupDraftFromServer = async (): Promise<SetupConfigDraft | null> => {
  if (!project.value) return null;
  try {
    const { data } = await fetchSetupConfig(project.value.id);
    if (!isSetupDraftShape(data?.data)) return null;
    applySetupResponseMeta(data);
    setupSaveMeta.value = {
      updatedAt: formatDisplayTime(data.updated_at),
      savedBy: data.saved_by_name || resolveSavedByDisplay(data.saved_by),
      serverSynced: true,
    };
    return clone(data.data);
  } catch {
    return null;
  }
};

const resolveSavedByDisplay = (savedBy?: string | null): string => {
  if (!savedBy) return "-";
  if (savedBy === authStore.user?.id) {
    return authStore.user?.full_name || authStore.user?.username || authStore.user?.email || "当前用户";
  }
  return savedBy;
};

const setupModuleLabels: Array<{ key: keyof SetupConfigDraft; label: string }> = [
  { key: "projectMilestones", label: "项目里程碑" },
  { key: "enrollmentPlan", label: "项目入组计划" },
  { key: "siteMilestones", label: "中心里程碑" },
  { key: "siteEnrollmentPlans", label: "中心入组计划" },
  { key: "monitoringStrategies", label: "监查计划策略" },
  { key: "centerConfirm", label: "中心确认" },
];
const emptySetupDraft: SetupConfigDraft = {
  projectMilestones: [],
  enrollmentPlan: {
    totalTarget: 0,
    startDate: "",
    endDate: "",
    monthlyGoalNote: "",
    stageBreakdown: "",
  },
  siteMilestones: [],
  siteEnrollmentPlans: [],
  monitoringStrategies: [],
  centerConfirm: [],
};

const buildConflictDiffLines = (localDraft: SetupConfigDraft | null, serverDraft: SetupConfigDraft | null): string[] => {
  if (!localDraft || !serverDraft) return [];
  const lines: string[] = [];
  setupModuleLabels.forEach((item) => {
    const localChunk = JSON.stringify(localDraft[item.key] ?? null);
    const serverChunk = JSON.stringify(serverDraft[item.key] ?? null);
    if (localChunk !== serverChunk) {
      lines.push(`${item.label} 存在差异`);
    }
  });
  return lines;
};

const publishCompareBase = computed<SetupConfigDraft>(() => clone(publishedSetupDraft.value || emptySetupDraft));
const isFirstPublish = computed(() => !publishedSetupDraft.value);
const hasProjectPublishDiff = computed(() => {
  if (!publishedProjectSnapshot.value) return false;
  return serializeProjectForPublishCompare() !== JSON.stringify(publishedProjectSnapshot.value);
});
const canStrictlyDetectNoDiff = computed(() => Boolean(!isFirstPublish.value && publishedProjectSnapshot.value));
const hasPublishableDiff = computed(() => publishDiffLines.value.length > 0 || hasProjectPublishDiff.value);
const publishDiffLines = computed(() => {
  const lines = buildConflictDiffLines(setupDraft, publishCompareBase.value);
  if (hasProjectPublishDiff.value) {
    lines.unshift("项目信息存在差异");
  }
  return lines;
});
const publishFieldDiffRows = computed(() => [...buildProjectDiffRows(), ...buildReadableDiffRows(setupDraft, publishCompareBase.value)]);
const publishDiffModuleSummary = computed(() => {
  const grouped = new Map<string, number>();
  publishFieldDiffRows.value.forEach((row) => {
    grouped.set(row.moduleLabel, (grouped.get(row.moduleLabel) || 0) + 1);
  });
  return Array.from(grouped.entries()).map(([module, count]) => ({ module, count }));
});

const loadPublishedProjectSignatureFromLocal = () => {
  try {
    const raw = localStorage.getItem(publishedProjectSignatureKey.value);
    if (!raw) {
      publishedProjectSnapshot.value = null;
      return;
    }
    const parsed = JSON.parse(raw) as ProjectPublishSnapshot;
    if (!parsed || typeof parsed !== "object") {
      publishedProjectSnapshot.value = null;
      return;
    }
    publishedProjectSnapshot.value = parsed;
  } catch {
    publishedProjectSnapshot.value = null;
  }
};

const persistPublishedProjectSignatureToLocal = () => {
  try {
    const snapshot = buildProjectPublishSnapshot();
    publishedProjectSnapshot.value = snapshot;
    localStorage.setItem(publishedProjectSignatureKey.value, JSON.stringify(snapshot));
  } catch {
    // ignore local storage errors
  }
};

type DiffRow = SetupDiffRow;

const projectFieldLabelMap: Record<keyof ProjectPublishSnapshot, string> = {
  code: "项目编号",
  name: "项目名称",
  project_full_name: "项目全称",
  sponsor: "申办方",
  protocol_no: "方案号",
  lead_unit: "组长单位",
  principal_investigator: "主要研究者",
  main_pm: "主PM",
  research_analysis: "研究分期",
  research_product: "研究产品",
  control_product: "对照产品",
  indication: "适应症",
  research_population: "研究人群",
  research_design: "研究设计",
  plan_start_date: "计划开始日期",
  plan_end_date: "计划结束日期",
  planned_site_count: "计划中心数",
  planned_enrollment_count: "计划入组数",
  summary_note: "方案摘要说明",
  objective_note: "研究目标摘要",
  status: "项目状态",
  visit_interval_days: "访视间隔（天）",
  visit_total: "访视总数",
  visit_window_start_offset: "窗口期起始偏移",
  visit_window_end_offset: "窗口期结束偏移",
};

const buildProjectDiffRows = (): DiffRow[] => {
  if (!publishedProjectSnapshot.value) return [];
  const current = buildProjectPublishSnapshot();
  const previous = publishedProjectSnapshot.value;
  const rows: DiffRow[] = [];
  (Object.keys(projectFieldLabelMap) as Array<keyof ProjectPublishSnapshot>).forEach((key) => {
    if (current[key] === previous[key]) return;
    rows.push({
      moduleLabel: "项目信息",
      path: projectFieldLabelMap[key],
      changeType: "修改",
      localValue: serializeDiffValue(current[key]),
      serverValue: serializeDiffValue(previous[key]),
    });
  });
  return rows;
};
const normalizeDiffRowForBusiness = (row: DiffRow): DiffRow => {
  if (row.path.includes("负责人")) {
    return {
      ...row,
      localValue: row.localValue === "未填写" ? row.localValue : normalizeOwnerDisplay(row.localValue),
      serverValue: row.serverValue === "未填写" ? row.serverValue : normalizeOwnerDisplay(row.serverValue),
    };
  }
  return row;
};

const buildReadableDiffRows = (localDraft: SetupConfigDraft | null, serverDraft: SetupConfigDraft | null): DiffRow[] =>
  buildSetupReadableDiffRows(localDraft, serverDraft, setupModuleLabels).map(normalizeDiffRowForBusiness);

const applySetupResponseMeta = (data: StudySetupConfigResponse) => {
  setupRevision.value = typeof data.version === "number" ? data.version : setupRevision.value;
  setupPublishStatus.value = data.publish_status || "DRAFT";
  setupPublishedAt.value = formatDisplayTime(data.published_at);
  publishedSetupDraft.value = isSetupDraftShape(data?.published_data) ? clone(data.published_data) : null;
};

const refreshSetupDraftFromServer = async () => {
  const latest = await loadSetupDraftFromServer();
  if (!latest) return false;
  applySetupDraft(latest);
  clearSetupValidationErrors();
  markSetupSynced();
  ElMessage.info("已刷新服务端最新配置");
  return true;
};

const persistSetupDraft = async (showMessage = false): Promise<{ serverSynced: boolean }> => {
  if (!project.value) return { serverSynced: false };
  const payload: StudySetupConfigUpsertPayload = {
    expected_version: setupRevision.value,
    data: clone(setupDraft),
  };
  try {
    const { data } = await upsertSetupConfig(project.value.id, payload);
    applySetupResponseMeta(data);
    clearSetupValidationErrors();
    markSetupSynced();
    setupServerSnapshot.value = serializeSetupForCompare();
    setupSaveMeta.value = {
      updatedAt: formatDisplayTime(data.updated_at),
      savedBy: data.saved_by_name || resolveSavedByDisplay(data.saved_by),
      serverSynced: true,
    };
    if (autoSaveTimer.value) {
      window.clearTimeout(autoSaveTimer.value);
      autoSaveTimer.value = undefined;
    }
    localStorage.setItem(
      storageKey.value,
      JSON.stringify({
        version: DRAFT_VERSION,
        data: clone(setupDraft),
        savedAt: nowString(),
      })
    );
    return { serverSynced: true };
  } catch (e: any) {
    if (handleValidationFailure(e, "配置保存失败，请修正后重试")) {
      persistSetupDraftToLocal();
      setupDirtySinceLastPersist.value = true;
      return { serverSynced: false };
    }
    persistSetupDraftToLocal();
    setupDirtySinceLastPersist.value = true;
    if (e?.response?.status === 409) {
      ElMessage.warning("配置已更新，正在刷新最新版本");
      await refreshSetupDraftFromServer();
      return { serverSynced: false };
    }
    if (e?.response?.status === 403) {
      if (project.value) {
        project.value.is_locked = true;
      }
      isEditing.value = false;
    }
    if (showMessage) {
      if (e?.response?.status === 403) {
        ElMessage.warning("项目已锁定，配置未同步到服务器，仅保留本地草稿");
      } else {
        ElMessage.warning(getApiErrorMessage(e, "服务器同步失败，已保存为本地草稿"));
      }
    }
    return { serverSynced: false };
  }
};

const schedulePersistDraft = () => {
  if (autoSaveTimer.value) {
    window.clearTimeout(autoSaveTimer.value);
  }
  autoSaveTimer.value = window.setTimeout(() => {
    autoSaveTimer.value = undefined;
    persistSetupDraftToLocal();
  }, 500);
};

watch(
  () => setupDraft.enrollmentPlan.stageBreakdown,
  () => {
    if (enrollmentStageSyncingToModel.value) return;
    applyEnrollmentStageFromDraft();
  },
  { immediate: true }
);

watch(
  [enrollmentPlanCycle, enrollmentTargetsByCycle],
  () => {
    if (enrollmentStageSyncingFromModel.value) return;
    if (!draftReady.value || !isEditing.value || isPublishedView.value || !canEditSetup.value) return;
    const nextStageBreakdown = buildEnrollmentStageBreakdown({
      cycle: enrollmentPlanCycle.value,
      targetsByCycle: enrollmentTargetsByCycle.value,
    });
    if (nextStageBreakdown === setupDraft.enrollmentPlan.stageBreakdown) return;
    enrollmentStageSyncingToModel.value = true;
    setupDraft.enrollmentPlan.stageBreakdown = nextStageBreakdown;
    enrollmentStageSyncingToModel.value = false;
  },
  { deep: true }
);

watch(
  setupDraft,
  () => {
    if (!draftReady.value || suppressDraftWatch.value || !canEditSetup.value) return;
    const changed = setupServerSnapshot.value
      ? serializeSetupForCompare() !== setupServerSnapshot.value
      : true;
    setupDirtySinceLastPersist.value = changed;
    if (!changed) return;
    schedulePersistDraft();
  },
  { deep: true }
);

watch(
  () => form.value.planned_enrollment_count,
  (next) => {
    if (!draftReady.value || suppressEnrollmentFieldSync.value) return;
    const normalized = normalizeEnrollmentTarget(next);
    if (
      setupDraft.enrollmentPlan.totalTarget === normalized &&
      form.value.planned_enrollment_count === normalized
    ) {
      return;
    }
    suppressEnrollmentFieldSync.value = true;
    form.value.planned_enrollment_count = normalized;
    setupDraft.enrollmentPlan.totalTarget = normalized;
    suppressEnrollmentFieldSync.value = false;
  }
);

watch(
  () => setupDraft.enrollmentPlan.totalTarget,
  (next) => {
    if (!draftReady.value || suppressEnrollmentFieldSync.value) return;
    const normalized = normalizeEnrollmentTarget(next);
    if (
      form.value.planned_enrollment_count === normalized &&
      setupDraft.enrollmentPlan.totalTarget === normalized
    ) {
      return;
    }
    suppressEnrollmentFieldSync.value = true;
    setupDraft.enrollmentPlan.totalTarget = normalized;
    form.value.planned_enrollment_count = normalized;
    suppressEnrollmentFieldSync.value = false;
  }
);

watch(setupViewMode, (mode) => {
  if (mode === "published") {
    isEditing.value = false;
    step1EditSection.value = "all";
  }
});

const syncForm = (data: Study | null) => {
  form.value.code = data?.code || "";
  form.value.name = data?.name || "";
  form.value.project_full_name = data?.project_full_name || "";
  form.value.short_name = "";
  form.value.sponsor = data?.sponsor || "";
  form.value.protocol_no = data?.protocol_no || "";
  form.value.lead_unit = data?.lead_unit || "";
  form.value.principal_investigator = data?.principal_investigator || "";
  form.value.main_pm = data?.main_pm || "";
  form.value.research_analysis = data?.research_analysis || "";
  form.value.research_product = data?.research_product || "";
  form.value.control_product = data?.control_product || "";
  form.value.indication = data?.indication || "";
  form.value.research_population = data?.research_population || "";
  form.value.research_design = data?.research_design || "";
  form.value.plan_start_date = data?.plan_start_date || "";
  form.value.plan_end_date = data?.plan_end_date || "";
  form.value.planned_site_count = data?.planned_site_count ?? null;
  form.value.planned_enrollment_count = data?.planned_enrollment_count ?? null;
  form.value.phase = data?.phase || "";
  form.value.status = data?.status || "DRAFT";
  form.value.scope = "国内";
  form.value.summary_note = data?.summary_note || "";
  form.value.objective_note = data?.objective_note || "";
  form.value.visit_interval_days = data?.visit_interval_days ?? null;
  form.value.visit_total = data?.visit_total ?? null;
  form.value.visit_window_start_offset = data?.visit_window_start_offset ?? null;
  form.value.visit_window_end_offset = data?.visit_window_end_offset ?? null;
  formBaselineSnapshot.value = serializeFormForCompare();
};

const syncFormFromProjectWithoutSetupLink = (data: Study | null) => {
  suppressEnrollmentFieldSync.value = true;
  syncForm(data);
  suppressEnrollmentFieldSync.value = false;
};

const initializeSetupDraft = async () => {
  clearSetupValidationErrors();
  const serverDraft = await loadSetupDraftFromServer();
  if (serverDraft) {
    applySetupDraft(serverDraft);
    setupServerSnapshot.value = serializeSetupForCompare();
    markSetupSynced();
    cacheSetupDraftToLocal();
    draftReady.value = true;
    return;
  }

  const localDraft = loadLocalSetupDraft();
  if (localDraft) {
    applySetupDraft(localDraft);
    setupDirtySinceLastPersist.value = true;
    setupSaveMeta.value = {
      updatedAt: nowString(),
      savedBy: authStore.user?.full_name || authStore.user?.username || authStore.user?.email || "当前用户",
      serverSynced: false,
    };
    draftReady.value = true;
    return;
  }

  applySetupDraft(createDefaultSetupDraft(siteOptions.value));
  setupServerSnapshot.value = "";
  markSetupSynced();
  draftReady.value = true;
  persistSetupDraftToLocal();
};

const loadMemberDisplayMap = async (studyId: string) => {
  const nextMap: Record<string, string> = {};
  const memberOptionMap = new Map<string, string>();
  try {
    const { data } = await listMembers(studyId, { limit: 500 });
    const rows = Array.isArray(data) ? data : (data as any).items || [];
    rows.forEach((row: any) => {
      if (row?.is_active === false) return;
      const userId = String(row?.user_id || row?.userId || "").trim();
      const memberId = String(row?.id || "").trim();
      if (!userId) return;
      const nestedUser = row?.user || {};
      const displayName =
        nestedUser?.full_name ||
        nestedUser?.username ||
        nestedUser?.email ||
        row?.full_name ||
        row?.username ||
        row?.email ||
        userId;
      const roleInStudy = String(row?.role_in_study || row?.roleInStudy || "").trim();
      const optionLabel = roleInStudy ? `${displayName}（${roleInStudy}）` : displayName;
      nextMap[userId] = displayName;
      if (memberId) nextMap[memberId] = displayName;
      if (!memberOptionMap.has(userId)) {
        memberOptionMap.set(userId, optionLabel);
      }
    });
  } catch {
    // Ignore member API errors and fallback to users API below.
  }
  try {
    const { data } = await fetchUsers({ limit: 1000 });
    const users = (data as any)?.items || [];
    users.forEach((user: any) => {
      const userId = String(user?.id || "").trim();
      if (!userId || nextMap[userId]) return;
      nextMap[userId] = user?.full_name || user?.username || user?.email || userId;
    });
  } catch {
    // ignore
  }
  memberDisplayMap.value = nextMap;
  projectMemberOptions.value = Array.from(memberOptionMap.entries())
    .map(([value, label]) => ({ value, label }))
    .sort((a, b) => a.label.localeCompare(b.label, "zh-CN"));
};

const loadProject = async () => {
  try {
    const projectId = route.params.projectId as string;
    const { data } = await fetchStudyDetail(projectId);
    project.value = data as Study;
    syncFormFromProjectWithoutSetupLink(project.value);
    projectServerSnapshot.value = serializeFormForCompare();
    const serverProjectSnapshot = serializeFormForCompare();
    const localProjectDraft = loadLocalProjectDraft();
    if (localProjectDraft) {
      applyProjectDraft(localProjectDraft);
      const localSnapshot = serializeFormForCompare();
      if (localSnapshot !== serverProjectSnapshot) {
        projectDirtySinceLastPersist.value = true;
        setupSaveMeta.value = {
          updatedAt: nowString(),
          savedBy: authStore.user?.full_name || authStore.user?.username || authStore.user?.email || "当前用户",
          serverSynced: false,
        };
      } else {
        clearLocalProjectDraft();
        projectDirtySinceLastPersist.value = false;
      }
    } else {
      projectDirtySinceLastPersist.value = false;
    }
    formBaselineSnapshot.value = serializeFormForCompare();
    if (setupSaveMeta.value.serverSynced && !projectDirtySinceLastPersist.value) {
      setupDirtySinceLastPersist.value = false;
      if (autoSaveTimer.value) {
        window.clearTimeout(autoSaveTimer.value);
        autoSaveTimer.value = undefined;
      }
    }
    loadPublishedProjectSignatureFromLocal();
    isEditing.value = false;

    const siteRes = await fetchSites(projectId, { limit: 500 });
    const list = Array.isArray(siteRes.data) ? siteRes.data : (siteRes.data as any).items || [];
    siteOptions.value = list;
    await loadMemberDisplayMap(projectId);
    await initializeSetupDraft();
    await loadSetupVersionHistory();
  } catch (e: any) {
    ElMessage.error(getApiErrorMessage(e, TEXT.modules.adminProjects.loadFailed));
  }
};

const refreshProjectDisplayAfterPublish = async () => {
  if (!project.value) return;
  try {
    const { data } = await fetchStudyDetail(project.value.id);
    project.value = data as Study;
    syncFormFromProjectWithoutSetupLink(project.value);
    projectServerSnapshot.value = serializeFormForCompare();
    formBaselineSnapshot.value = serializeFormForCompare();
    if (studyStore.currentStudy?.id === project.value.id) {
      studyStore.setCurrentStudy({ ...(studyStore.currentStudy as Study), ...project.value } as Study);
    }
  } catch {
    // Ignore refresh errors to avoid breaking publish success flow.
  }
};

const startEdit = () => {
  if (!project.value) return;
  if (activeStep.value !== 0 && activeStep.value !== 2 && activeStep.value !== 4) return;
  if (isPublishedView.value) {
    ElMessage.info("当前为发布预览，请先切换到草稿视图后再编辑");
    return;
  }
  if (!canManageSetup.value) {
    ElMessage.warning("当前角色无权编辑立项配置");
    return;
  }
  if (project.value.is_locked) {
    ElMessage.warning("项目已锁定，无法编辑。");
    return;
  }
  clearSetupValidationErrors();
  if (activeStep.value === 0) {
    step1EditSection.value = infoTab.value === "summary" ? "summary" : "all";
  }
  formBaselineSnapshot.value = serializeFormForCompare();
  if (drawerCloseTimer.value) {
    window.clearTimeout(drawerCloseTimer.value);
    drawerCloseTimer.value = undefined;
  }
  drawerClosing.value = false;
  isEditing.value = true;
};

const startStep1SectionEdit = (section: Exclude<Step1EditSection, "all">) => {
  if (activeStep.value !== 0) return;
  if (section !== "summary") {
    infoTab.value = "basic";
  } else {
    infoTab.value = "summary";
  }
  startEdit();
  if (isEditing.value) {
    step1EditSection.value = section;
  }
};

const cancelEdit = () => {
  syncFormFromProjectWithoutSetupLink(project.value);
  if (projectDirtySinceLastPersist.value) {
    const localProjectDraft = loadLocalProjectDraft();
    if (localProjectDraft) {
      applyProjectDraft(localProjectDraft);
      formBaselineSnapshot.value = serializeFormForCompare();
    }
  }
  isEditing.value = false;
  step1EditSection.value = "all";
  clearSetupValidationErrors();
  formBaselineSnapshot.value = serializeFormForCompare();
};

const closeEditDrawer = () => {
  if (!isEditing.value || drawerClosing.value) return;
  drawerClosing.value = true;
  drawerCloseTimer.value = window.setTimeout(() => {
    cancelEdit();
    drawerClosing.value = false;
    drawerCloseTimer.value = undefined;
  }, 220);
};

const closeEditDrawerAfterSave = (onClosed?: () => void) => {
  if (!isEditing.value || drawerClosing.value) return;
  drawerClosing.value = true;
  drawerCloseTimer.value = window.setTimeout(() => {
    isEditing.value = false;
    step1EditSection.value = "all";
    clearSetupValidationErrors();
    formBaselineSnapshot.value = serializeFormForCompare();
    drawerClosing.value = false;
    drawerCloseTimer.value = undefined;
    onClosed?.();
  }, 220);
};

const handleDrawerConfirm = async () => {
  if (!canEditSetup.value) return;
  drawerConfirmLoading.value = true;
  try {
    let saved = false;
    if (activeStep.value === 0) {
      if (formRef.value) {
        await formRef.value.validate();
      }
      persistProjectDraftToLocal();
      formBaselineSnapshot.value = serializeFormForCompare();
      saved = true;
    } else {
      persistSetupDraftToLocal();
      setupDirtySinceLastPersist.value = true;
      saved = true;
    }
    if (!saved) return;
    closeEditDrawerAfterSave();
    ElMessage.success("当前步骤已保存");
  } finally {
    drawerConfirmLoading.value = false;
  }
};

const saveEdit = async (closeAfterSave = true, showSuccessMessage = true): Promise<boolean> => {
  if (!project.value) return false;
  if (formRef.value) {
    await formRef.value.validate();
  }
  submitting.value = true;
  try {
    const { data } = await updateStudy(project.value.id, {
      code: form.value.code.trim(),
      name: form.value.name.trim(),
      project_full_name: form.value.project_full_name?.trim() || null,
      sponsor: form.value.sponsor?.trim() || null,
      protocol_no: form.value.protocol_no?.trim() || null,
      lead_unit: form.value.lead_unit?.trim() || null,
      principal_investigator: form.value.principal_investigator?.trim() || null,
      main_pm: form.value.main_pm?.trim() || null,
      research_analysis: form.value.research_analysis?.trim() || null,
      research_product: form.value.research_product?.trim() || null,
      control_product: form.value.control_product?.trim() || null,
      indication: form.value.indication?.trim() || null,
      research_population: form.value.research_population?.trim() || null,
      research_design: form.value.research_design?.trim() || null,
      plan_start_date: form.value.plan_start_date || null,
      plan_end_date: form.value.plan_end_date || null,
      planned_site_count: form.value.planned_site_count,
      planned_enrollment_count: form.value.planned_enrollment_count,
      summary_note: form.value.summary_note?.trim() || null,
      objective_note: form.value.objective_note?.trim() || null,
      phase: form.value.phase?.trim() || null,
      status: form.value.status,
      visit_interval_days: form.value.visit_interval_days,
      visit_total: form.value.visit_total,
      visit_window_start_offset: form.value.visit_window_start_offset,
      visit_window_end_offset: form.value.visit_window_end_offset,
    });
    project.value = data as Study;
    syncFormFromProjectWithoutSetupLink(project.value);
    projectServerSnapshot.value = serializeFormForCompare();
    projectDirtySinceLastPersist.value = false;
    clearLocalProjectDraft();
    formBaselineSnapshot.value = serializeFormForCompare();
    if (closeAfterSave) {
      isEditing.value = false;
      step1EditSection.value = "all";
    }
    if (showSuccessMessage) {
      ElMessage.success("项目信息已保存");
    }
    return true;
  } catch (e: any) {
    ElMessage.error(getApiErrorMessage(e, TEXT.common.messages.saveFailed));
    return false;
  } finally {
    submitting.value = false;
  }
};

const saveConfigNow = async (): Promise<boolean> => {
  if (!canSaveDraftAction.value) return false;
  const result = await persistSetupDraft(false);
  if (!result.serverSynced) return false;
  if (hasFormUnsavedChanges.value || projectDirtySinceLastPersist.value) {
    cacheProjectDraftToLocal();
    projectDirtySinceLastPersist.value = Boolean(
      projectServerSnapshot.value && serializeFormForCompare() !== projectServerSnapshot.value
    );
  }
  formBaselineSnapshot.value = serializeFormForCompare();
  return true;
};

const doPublishConfigNow = async (): Promise<boolean> => {
  if (!project.value || !canPublishAction.value) return false;
  try {
    if (hasFormUnsavedChanges.value || projectDirtySinceLastPersist.value) {
      const basicSaved = await saveEdit(false, false);
      if (!basicSaved) return false;
    }
    const { data } = await publishConfig(project.value.id, { expected_version: setupRevision.value });
    applySetupResponseMeta(data);
    clearSetupValidationErrors();
    markSetupSynced();
    setupViewMode.value = "published";
    setupSaveMeta.value = {
      updatedAt: formatDisplayTime(data.updated_at),
      savedBy: data.saved_by_name || resolveSavedByDisplay(data.saved_by),
      serverSynced: true,
    };
    persistPublishedProjectSignatureToLocal();
    const projectionStatus = String(data?.projection_status || "").toLowerCase();
    const projectionSummary = data?.projection_summary || {};
    const updatedCount = Number(projectionSummary.site_updated_count || 0);
    const skippedCount = Number(projectionSummary.site_skipped_count || 0);
    if (projectionStatus === "partial_success") {
      ElMessage.success(`配置已发布，已同步${updatedCount}个中心，跳过${skippedCount}个中心`);
    } else {
      ElMessage.success("配置已发布，已同步项目计划与中心入组目标");
    }
    const warnings = Array.isArray(projectionSummary.warnings) ? projectionSummary.warnings : [];
    if (warnings.length > 0) {
      await ElMessageBox.alert(warnings.slice(0, 3).join("\n"), "发布联动提示", {
        confirmButtonText: "我知道了",
      });
    }
    await refreshProjectDisplayAfterPublish();
    return true;
  } catch (e: any) {
    if (handleValidationFailure(e, "发布失败，请先修正配置")) {
      return false;
    }
    if (e?.response?.status === 409) {
      ElMessage.warning("发布失败：配置已更新，正在刷新最新版本");
      await refreshSetupDraftFromServer();
      return false;
    }
    if (e?.response?.status === 403) {
      ElMessage.warning("项目已锁定，无法发布");
      if (project.value) project.value.is_locked = true;
      return false;
    }
    ElMessage.error(getApiErrorMessage(e, "发布失败"));
    return false;
  }
};

const publishConfigNow = async () => {
  if (!project.value || !canManageSetup.value) return;
  if (hasSetupDraftUnsavedChanges.value) {
    ElMessage.warning("当前存在未上传的本地草稿，请先点击“保存配置”");
    return;
  }
  if (canStrictlyDetectNoDiff.value && !hasPublishableDiff.value) {
    ElMessage.info("当前草稿与已发布快照一致，无需重复发布");
    return;
  }
  publishConfirmVisible.value = true;
};

const loadSetupVersionHistory = async () => {
  if (!project.value) return;
  rollbackDialogLoading.value = true;
  try {
    const { data } = await fetchSetupVersions(project.value.id);
    const history = (data || []).map((item) => ({
      ...item,
      published_at: formatDisplayTime(item.published_at),
    }));
    setupVersionHistory.value = history;
    setupPublishedVersion.value = history.length ? (history[0].display_version || history[0].version) : 0;
  } catch (e: any) {
    ElMessage.error(getApiErrorMessage(e, "获取版本历史失败"));
  } finally {
    rollbackDialogLoading.value = false;
  }
};

const getDisplayVersion = (rawVersion: number): number => {
  const found = setupVersionHistory.value.find((item) => item.version === rawVersion);
  if (!found) return rawVersion;
  return found.display_version || found.version;
};

const isLatestPublishedVersion = (rawVersion: number): boolean => {
  return setupVersionHistory.value.length > 0 && setupVersionHistory.value[0].version === rawVersion;
};

const openRollbackDialog = async () => {
  if (!project.value || !canManageSetup.value) return;
  await loadSetupVersionHistory();
  rollbackDialogVisible.value = true;
};

const selectRollbackVersion = async (targetVersion: number) => {
  if (!project.value) return;
  const targetDisplayVersion = getDisplayVersion(targetVersion);
  try {
    await ElMessageBox.confirm(`将当前草稿回滚到发布版本 v${targetDisplayVersion}，是否继续？`, "回滚确认", {
      type: "warning",
      confirmButtonText: "确认回滚",
      cancelButtonText: "取消",
    });
  } catch {
    return;
  }
  rollbackDialogLoading.value = true;
  try {
    const { data } = await rollbackConfig(project.value.id, {
      expected_version: setupRevision.value,
      target_version: targetVersion,
    });
    applySetupResponseMeta(data);
    applySetupDraft(clone(data.data));
    clearSetupValidationErrors();
    markSetupSynced();
    setupViewMode.value = "draft";
    setupSaveMeta.value = {
      updatedAt: formatDisplayTime(data.updated_at),
      savedBy: data.saved_by_name || resolveSavedByDisplay(data.saved_by),
      serverSynced: true,
    };
    rollbackDialogVisible.value = false;
    ElMessage.success(`已回滚到版本 v${targetDisplayVersion}`);
  } catch (e: any) {
    if (e?.response?.status === 409) {
      ElMessage.warning("回滚失败：版本冲突，正在刷新最新配置");
      await refreshSetupDraftFromServer();
      await loadSetupVersionHistory();
      return;
    }
    ElMessage.error(getApiErrorMessage(e, "版本回滚失败"));
  } finally {
    rollbackDialogLoading.value = false;
  }
};

const downloadVersionRaw = (versionItem: StudySetupConfigVersionItem) => {
  if (!project.value) return;
  const displayVersion = getDisplayVersion(versionItem.version);
  const payload = {
    study_id: versionItem.study_id,
    version: versionItem.version,
    display_version: displayVersion,
    published_by: versionItem.published_by || null,
    published_by_name: versionItem.published_by_name || null,
    published_at: versionItem.published_at || null,
    config: versionItem.config,
  };
  const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json;charset=utf-8" });
  const url = URL.createObjectURL(blob);
  const filename = `setup-config-raw-v${displayVersion}-${project.value.code || project.value.id}.json`;
  const link = document.createElement("a");
  link.href = url;
  link.download = filename;
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
  URL.revokeObjectURL(url);
};

const removeVersion = async (targetVersion: number) => {
  if (!project.value) return;
  if (isLatestPublishedVersion(targetVersion)) {
    ElMessage.warning("最新发布版本不能删除");
    return;
  }
  const displayVersion = getDisplayVersion(targetVersion);
  try {
    await ElMessageBox.confirm(`确认删除发布版本 v${displayVersion} 的快照？此操作不可恢复。`, "删除确认", {
      type: "warning",
      confirmButtonText: "确认删除",
      cancelButtonText: "取消",
    });
  } catch {
    return;
  }
  rollbackDialogLoading.value = true;
  try {
    await deleteSetupVersion(project.value.id, targetVersion);
    await loadSetupVersionHistory();
    ElMessage.success(`已删除发布版本 v${displayVersion}`);
  } catch (e: any) {
    ElMessage.error(getApiErrorMessage(e, "删除版本失败"));
  } finally {
    rollbackDialogLoading.value = false;
  }
};

const handlePrimarySaveConfig = async () => {
  if (!canSaveDraftAction.value) return;
  if (incompleteSteps.value.length > 0) {
    try {
      await ElMessageBox.confirm(
        `当前仍有 ${incompleteSteps.value.length} 个步骤未完成，确认后将继续保存草稿。`,
        "步骤未完成",
        {
          type: "warning",
          confirmButtonText: "确认保存",
          cancelButtonText: "继续编辑",
        }
      );
    } catch {
      return;
    }
  }
  primarySaveLoading.value = true;
  try {
    const saved = await saveConfigNow();
    if (!saved) return;
    closeEditDrawerAfterSave();
    ElMessage.success("配置草稿已保存");
  } finally {
    primarySaveLoading.value = false;
  }
};

const confirmPublishConfig = async () => {
  publishConfirmLoading.value = true;
  try {
    const success = await doPublishConfigNow();
    if (success) {
      publishConfirmVisible.value = false;
      await loadSetupVersionHistory();
    }
  } finally {
    publishConfirmLoading.value = false;
  }
};

const enterProject = () => {
  if (!project.value) return;
  studyStore.setCurrentStudy({ ...project.value, role_in_study: project.value.role_in_study || "PM" } as Study);
  router.push("/project/overview");
};
const goMembers = () => {
  if (!project.value) return;
  router.push(`/admin/projects/${project.value.id}/members`);
};
const goSites = () => {
  if (!project.value) return;
  router.push(`/admin/projects/${project.value.id}/sites`);
};
const handleSecondaryAction = (command: string) => {
  if (command === "enter") {
    enterProject();
    return;
  }
  if (command === "members") {
    goMembers();
    return;
  }
  if (command === "sites") {
    goSites();
    return;
  }
};

const prevStep = () => {
  if (isEditing.value) return;
  if (activeStep.value <= 0) return;
  activeStep.value -= 1;
};
const nextStep = () => {
  if (isEditing.value) return;
  if (activeStep.value >= steps.length - 1) return;
  activeStep.value += 1;
};
const goStep = (index: number) => {
  if (isEditing.value) return;
  activeStep.value = index;
};

const addProjectMilestone = () => {
  if (!canMutateDraft()) return;
  setupDraft.projectMilestones.push({
    id: makeId(),
    name: "",
    planDate: "",
    startDate: "",
    endDate: "",
    durationDays: 1,
    owner: "",
    remark: "",
    status: "未开始",
  });
};
const ensureStep2Editable = (): boolean => {
  if (!project.value) return false;
  if (isPublishedView.value) {
    ElMessage.info("当前为发布预览，请先切换到草稿视图后再操作");
    return false;
  }
  if (!canManageSetup.value) {
    ElMessage.warning("当前角色无权编辑立项配置");
    return false;
  }
  if (project.value.is_locked) {
    ElMessage.warning("项目已锁定，无法编辑。");
    return false;
  }
  return true;
};
const handleAddProjectMilestoneAction = () => {
  if (!ensureStep2Editable()) return;
  addProjectMilestone();
};
const onProjectMilestoneTemplateSelect = async (templateKey: string) => {
  if (!ensureStep2Editable()) return;
  const template = projectMilestoneTemplates.find((item) => item.key === templateKey);
  if (!template) return;

  if (setupDraft.projectMilestones.length > 0) {
    try {
      await ElMessageBox.confirm("应用模板将覆盖当前第2步里程碑列表，是否继续？", "应用模板", {
        type: "warning",
        confirmButtonText: "覆盖并应用",
        cancelButtonText: "取消",
      });
    } catch {
      return;
    }
  }

  setupDraft.projectMilestones = template.milestones.map((name) => ({
    id: makeId(),
    name,
    planDate: "",
    startDate: "",
    endDate: "",
    durationDays: 1,
    owner: "",
    remark: "",
    status: "未开始",
  }));
  ElMessage.success(`已应用模板：${template.label}`);
};
const removeProjectMilestone = (index: number) => {
  if (!canMutateDraft()) return;
  setupDraft.projectMilestones.splice(index, 1);
};
const openProjectMilestoneEditor = (index: number) => {
  openIndexedEditor(setupDraft.projectMilestones, index, projectMilestoneEditor, (row) => {
    projectMilestoneEditorForm.id = row.id || "";
    projectMilestoneEditorForm.name = row.name || "";
    projectMilestoneEditorForm.startDate = resolveProjectMilestoneStart(row);
    projectMilestoneEditorForm.endDate = resolveProjectMilestoneEnd(row);
    projectMilestoneEditorForm.durationDays = resolveProjectMilestoneDurationDays(row) || 1;
    projectMilestoneEditorForm.planDate = resolveProjectMilestoneStart(row);
    projectMilestoneEditorForm.owner = row.owner || "";
    projectMilestoneEditorForm.remark = row.remark || "";
    projectMilestoneEditorForm.status = row.status || "未开始";
  });
};
const saveProjectMilestoneEditor = () => {
  if (!canMutateDraft()) return;
  const index = getEditingIndex(projectMilestoneEditor, setupDraft.projectMilestones.length);
  if (index < 0) return;
  const name = (projectMilestoneEditorForm.name || "").trim();
  if (!name) {
    ElMessage.warning("请填写里程碑名称");
    return;
  }
  const startDate = normalizePlanDate(projectMilestoneEditorForm.startDate || projectMilestoneEditorForm.planDate);
  const endDate = normalizePlanDate(projectMilestoneEditorForm.endDate || projectMilestoneEditorForm.planDate);
  if (!startDate) {
    ElMessage.warning("请选择开始日期");
    return;
  }
  if (!endDate) {
    ElMessage.warning("请选择结束日期");
    return;
  }
  if (startDate > endDate) {
    ElMessage.warning("结束日期不能早于开始日期");
    return;
  }
  const durationDays = calculateDurationDays(startDate, endDate);
  setupDraft.projectMilestones[index] = {
    id: projectMilestoneEditorForm.id || makeId(),
    name,
    planDate: startDate,
    startDate,
    endDate,
    durationDays: durationDays > 0 ? durationDays : 1,
    owner: (projectMilestoneEditorForm.owner || "").trim(),
    remark: (projectMilestoneEditorForm.remark || "").trim(),
    status: projectMilestoneEditorForm.status || "未开始",
  };
  closeIndexedEditor(projectMilestoneEditor, "里程碑已更新");
};

const addSiteMilestone = () => {
  if (!canMutateDraft()) return;
  setupDraft.siteMilestones.push({ id: makeId(), milestone: "", planDate: "", owner: "", remark: "", status: "未开始" });
};
const handleAddSiteMilestoneAction = () => {
  if (!ensureStep2Editable()) return;
  addSiteMilestone();
};
const onSiteMilestoneTemplateSelect = async (templateKey: string) => {
  if (!ensureStep2Editable()) return;
  const template = siteMilestoneTemplates.find((item) => item.key === templateKey);
  if (!template) return;

  if (setupDraft.siteMilestones.length > 0) {
    try {
      await ElMessageBox.confirm("应用模板将覆盖当前第4步中心里程碑列表，是否继续？", "应用模板", {
        type: "warning",
        confirmButtonText: "覆盖并应用",
        cancelButtonText: "取消",
      });
    } catch {
      return;
    }
  }

  setupDraft.siteMilestones = template.milestones.map((milestone) => ({
    id: makeId(),
    milestone,
    planDate: "",
    owner: "",
    remark: "",
    status: "未开始",
  }));
  ElMessage.success(`已应用模板：${template.label}`);
};
const removeSiteMilestone = (index: number) => {
  if (!canMutateDraft()) return;
  setupDraft.siteMilestones.splice(index, 1);
};
const openSiteMilestoneEditor = (index: number) => {
  openIndexedEditor(setupDraft.siteMilestones, index, siteMilestoneEditor, (row) => {
    siteMilestoneEditorForm.id = row.id || "";
    siteMilestoneEditorForm.milestone = row.milestone || "";
    siteMilestoneEditorForm.planDate = row.planDate || "";
    siteMilestoneEditorForm.owner = row.owner || "";
    siteMilestoneEditorForm.remark = row.remark || "";
    siteMilestoneEditorForm.status = row.status || "未开始";
  });
};
const saveSiteMilestoneEditor = () => {
  if (!canMutateDraft()) return;
  const index = getEditingIndex(siteMilestoneEditor, setupDraft.siteMilestones.length);
  if (index < 0) return;
  const milestone = (siteMilestoneEditorForm.milestone || "").trim();
  if (!milestone) {
    ElMessage.warning("请填写里程碑");
    return;
  }
  if (!siteMilestoneEditorForm.planDate) {
    ElMessage.warning("请选择计划日期");
    return;
  }
  const { start: planStart, end: planEnd } = getProjectPlanWindow();
  const planDate = normalizePlanDate(siteMilestoneEditorForm.planDate);
  if (planStart && planDate < planStart) {
    ElMessage.warning("中心里程碑日期不能早于项目计划开始日期");
    return;
  }
  if (planEnd && planDate > planEnd) {
    ElMessage.warning("中心里程碑日期不能晚于项目计划结束日期");
    return;
  }
  setupDraft.siteMilestones[index] = {
    id: siteMilestoneEditorForm.id || makeId(),
    milestone,
    planDate: siteMilestoneEditorForm.planDate,
    owner: (siteMilestoneEditorForm.owner || "").trim(),
    remark: (siteMilestoneEditorForm.remark || "").trim(),
    status: siteMilestoneEditorForm.status || "未开始",
  };
  closeIndexedEditor(siteMilestoneEditor, "中心里程碑已更新");
};

const removeSiteEnrollment = (index: number) => {
  if (!canMutateDraft()) return;
  setupDraft.siteEnrollmentPlans.splice(index, 1);
};
const openSiteEnrollmentEditor = (index: number) => {
  openIndexedEditor(setupDraft.siteEnrollmentPlans, index, siteEnrollmentEditor, (row) => {
    siteEnrollmentEditorForm.id = row.id || "";
    siteEnrollmentEditorForm.siteId = row.siteId || "";
    siteEnrollmentEditorForm.siteName = row.siteName || "";
    siteEnrollmentEditorForm.target = row.target ?? 0;
    siteEnrollmentEditorForm.startDate = row.startDate || "";
    siteEnrollmentEditorForm.endDate = row.endDate || "";
    siteEnrollmentEditorForm.note = row.note || "";
    siteEnrollmentEditorForm.stageBreakdown = row.stageBreakdown || "";
  });
};
const saveSiteEnrollmentEditor = () => {
  if (!canMutateDraft()) return;
  const index = getEditingIndex(siteEnrollmentEditor, setupDraft.siteEnrollmentPlans.length);
  if (index < 0) return;
  if (!siteEnrollmentEditorForm.siteId) {
    ElMessage.warning("请选择中心");
    return;
  }
  if (isSiteEnrollmentDuplicate(siteEnrollmentEditorForm.siteId, index)) {
    ElMessage.warning("该中心已配置入组计划，请勿重复配置");
    return;
  }
  if (!siteEnrollmentEditorForm.startDate || !siteEnrollmentEditorForm.endDate) {
    ElMessage.warning("请填写启动日期和完成日期");
    return;
  }
  if (siteEnrollmentEditorForm.startDate > siteEnrollmentEditorForm.endDate) {
    ElMessage.warning("完成日期不能早于启动日期");
    return;
  }
  const { start: planStart, end: planEnd } = getProjectPlanWindow();
  const startDate = normalizePlanDate(siteEnrollmentEditorForm.startDate);
  const endDate = normalizePlanDate(siteEnrollmentEditorForm.endDate);
  if (planStart && startDate < planStart) {
    ElMessage.warning("中心启动日期不能早于项目计划开始日期");
    return;
  }
  if (planEnd && startDate > planEnd) {
    ElMessage.warning("中心启动日期不能晚于项目计划结束日期");
    return;
  }
  if (planStart && endDate < planStart) {
    ElMessage.warning("中心完成日期不能早于项目计划开始日期");
    return;
  }
  if (planEnd && endDate > planEnd) {
    ElMessage.warning("中心完成日期不能晚于项目计划结束日期");
    return;
  }
  setupDraft.siteEnrollmentPlans[index] = {
    id: siteEnrollmentEditorForm.id || makeId(),
    siteId: siteEnrollmentEditorForm.siteId,
    siteName: getSiteName(siteEnrollmentEditorForm.siteId),
    target: Math.max(0, Number(siteEnrollmentEditorForm.target || 0)),
    startDate: siteEnrollmentEditorForm.startDate,
    endDate: siteEnrollmentEditorForm.endDate,
    note: (siteEnrollmentEditorForm.note || "").trim(),
    stageBreakdown: siteEnrollmentEditorForm.stageBreakdown || "",
  };
  closeIndexedEditor(siteEnrollmentEditor, "中心入组计划已更新");
};

const addMonitoringStrategy = () => {
  if (!canMutateDraft()) return;
  setupDraft.monitoringStrategies.push({ id: makeId(), strategyType: "", detail: "", frequency: "", updatedAt: nowString(), enabled: true });
};
const applyMonitoringStrategyTemplate = (templateKey: string) => {
  if (!canMutateDraft()) return;
  const template = monitoringStrategyTemplates.find((item) => item.key === templateKey);
  if (!template) {
    ElMessage.warning("未找到对应模板");
    return;
  }
  setupDraft.monitoringStrategies = template.rows.map((item) => ({
    id: makeId(),
    strategyType: item.strategyType,
    detail: item.detail,
    frequency: item.frequency,
    updatedAt: nowString(),
    enabled: true,
  }));
  ElMessage.success(`已应用模板：${template.label}`);
};
const onMonitoringStrategyTemplateSelect = (command: string | number | object) => {
  applyMonitoringStrategyTemplate(String(command));
};
const removeMonitoringStrategy = (index: number) => {
  if (!canMutateDraft()) return;
  setupDraft.monitoringStrategies.splice(index, 1);
};
const openMonitoringStrategyEditor = (index: number) => {
  openIndexedEditor(setupDraft.monitoringStrategies, index, monitoringStrategyEditor, (row) => {
    monitoringStrategyEditorForm.id = row.id || "";
    monitoringStrategyEditorForm.strategyType = row.strategyType || "";
    monitoringStrategyEditorForm.detail = row.detail || "";
    monitoringStrategyEditorForm.frequency = row.frequency || "";
    monitoringStrategyEditorForm.updatedAt = row.updatedAt || "";
    monitoringStrategyEditorForm.enabled = row.enabled ?? true;
  });
};
const saveMonitoringStrategyEditor = () => {
  if (!canMutateDraft()) return;
  const index = getEditingIndex(monitoringStrategyEditor, setupDraft.monitoringStrategies.length);
  if (index < 0) return;
  if (!(monitoringStrategyEditorForm.strategyType || "").trim()) {
    ElMessage.warning("请选择监查类型");
    return;
  }
  if (!(monitoringStrategyEditorForm.detail || "").trim()) {
    ElMessage.warning("请填写策略详情");
    return;
  }
  if (!(monitoringStrategyEditorForm.frequency || "").trim()) {
    ElMessage.warning("请填写监查次数");
    return;
  }
  setupDraft.monitoringStrategies[index] = {
    id: monitoringStrategyEditorForm.id || makeId(),
    strategyType: monitoringStrategyEditorForm.strategyType.trim(),
    detail: monitoringStrategyEditorForm.detail.trim(),
    frequency: monitoringStrategyEditorForm.frequency.trim(),
    updatedAt: nowString(),
    enabled: monitoringStrategyEditorForm.enabled ?? true,
  };
  closeIndexedEditor(monitoringStrategyEditor, "监查策略已更新");
};

const addCenterConfirm = () => {
  if (!canMutateDraft()) return;
  setupDraft.centerConfirm.push({ id: makeId(), siteId: "", siteName: "", confirmer: "", confirmStatus: "待确认", confirmDate: "", note: "" });
};
const removeCenterConfirm = (index: number) => {
  if (!canMutateDraft()) return;
  setupDraft.centerConfirm.splice(index, 1);
};
const openCenterConfirmEditor = (index: number) => {
  openIndexedEditor(setupDraft.centerConfirm, index, centerConfirmEditor, (row) => {
    centerConfirmEditorForm.id = row.id || "";
    centerConfirmEditorForm.siteId = row.siteId || "";
    centerConfirmEditorForm.siteName = row.siteName || "";
    centerConfirmEditorForm.confirmer = row.confirmer || "";
    centerConfirmEditorForm.confirmStatus = row.confirmStatus || "待确认";
    centerConfirmEditorForm.confirmDate = row.confirmDate || "";
    centerConfirmEditorForm.note = row.note || "";
  });
};
const saveCenterConfirmEditor = () => {
  if (!canMutateDraft()) return;
  const index = getEditingIndex(centerConfirmEditor, setupDraft.centerConfirm.length);
  if (index < 0) return;
  if (!centerConfirmEditorForm.siteId) {
    ElMessage.warning("请选择中心");
    return;
  }
  if (!(centerConfirmEditorForm.confirmStatus || "").trim()) {
    ElMessage.warning("请选择确认状态");
    return;
  }
  if (centerConfirmEditorForm.confirmStatus === "已确认" && !centerConfirmEditorForm.confirmDate) {
    ElMessage.warning("已确认状态需要填写确认日期");
    return;
  }
  if (centerConfirmEditorForm.confirmStatus === "退回" && !(centerConfirmEditorForm.note || "").trim()) {
    ElMessage.warning("退回状态请填写备注");
    return;
  }
  const { start: planStart, end: planEnd } = getProjectPlanWindow();
  const confirmDate = normalizePlanDate(centerConfirmEditorForm.confirmDate);
  if (confirmDate && planStart && confirmDate < planStart) {
    ElMessage.warning("中心确认日期不能早于项目计划开始日期");
    return;
  }
  if (confirmDate && planEnd && confirmDate > planEnd) {
    ElMessage.warning("中心确认日期不能晚于项目计划结束日期");
    return;
  }
  setupDraft.centerConfirm[index] = {
    id: centerConfirmEditorForm.id || makeId(),
    siteId: centerConfirmEditorForm.siteId,
    siteName: getSiteName(centerConfirmEditorForm.siteId),
    confirmer: (centerConfirmEditorForm.confirmer || "").trim(),
    confirmStatus: centerConfirmEditorForm.confirmStatus,
    confirmDate: centerConfirmEditorForm.confirmDate || "",
    note: (centerConfirmEditorForm.note || "").trim(),
  };
  closeIndexedEditor(centerConfirmEditor, "中心确认已更新");
};
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
  if (!hasLeaveGuardChanges.value) return;
  event.preventDefault();
  event.returnValue = "";
};

onBeforeRouteLeave(async () => {
  if (!hasLeaveGuardChanges.value) return true;
  try {
    await ElMessageBox.confirm("当前有未保存或未同步到服务端的修改，确定离开当前页面？", "离开确认", {
      type: "warning",
      confirmButtonText: "确定离开",
      cancelButtonText: "继续编辑",
    });
    return true;
  } catch {
    return false;
  }
});

onMounted(() => {
  void loadProject();
  window.addEventListener("beforeunload", handleBeforeUnload);
});
onBeforeUnmount(() => {
  window.removeEventListener("beforeunload", handleBeforeUnload);
  if (autoSaveTimer.value) {
    window.clearTimeout(autoSaveTimer.value);
  }
  if (drawerCloseTimer.value) {
    window.clearTimeout(drawerCloseTimer.value);
  }
});
</script>

<style scoped>
.setup-page {
  display: flex;
  flex-direction: column;
  gap: 0;
  background: #f3f6fb;
}

.setup-shell {
  border: 1px solid #d8e2ef;
  border-radius: 16px;
  background: #ffffff;
  overflow: hidden;
  box-shadow: 0 6px 20px rgba(17, 42, 88, 0.05);
}

.setup-header {
  position: relative;
  z-index: 1250;
  background: linear-gradient(105deg, #091633 0%, #163d9a 52%, #2f8ce8 100%);
  border-bottom: 1px solid rgba(199, 218, 255, 0.2);
  padding: 12px 14px;
}

.setup-flow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  overflow: hidden;
  padding-bottom: 2px;
  gap: 0;
}

.flow-step {
  display: flex;
  align-items: center;
  flex: 1 1 0;
  min-width: 0;
  white-space: nowrap;
  cursor: pointer;
  color: rgba(226, 240, 255, 0.86);
}

.flow-index {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: 1.5px solid rgba(220, 236, 255, 0.92);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-right: 6px;
  font-size: 12px;
  font-weight: 700;
  color: #ffffff;
  flex: 0 0 auto;
}

.flow-label {
  font-size: 13px;
  line-height: 1;
  font-weight: 600;
  color: inherit;
  flex: 0 1 auto;
}

.flow-done-icon {
  margin-left: 4px;
  color: #22c55e;
  font-size: 13px;
  flex: 0 0 auto;
}

.flow-connector {
  width: auto;
  min-width: 8px;
  flex: 1 1 auto;
  height: 2px;
  background: rgba(206, 227, 255, 0.55);
  margin: 0 6px;
  border-radius: 999px;
}

.flow-step.active {
  color: #ffffff;
}

.flow-step.has-error .flow-index {
  border-color: #ff8d8d;
  color: #ffd1d1;
}

.flow-step.active .flow-index {
  background: rgba(255, 255, 255, 0.2);
  border-color: #ffffff;
  color: #ffffff;
}

.flow-step.done {
  color: #ffffff;
}

.flow-step.done .flow-index {
  border-color: #d8e9ff;
  color: #d8e9ff;
}

.setup-toolbar {
  margin-top: 4px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}

.setup-step-errors {
  padding: 8px 12px 0;
  background: #ffffff;
}

.setup-import-errors {
  margin-top: 10px;
}

.setup-import-error-list {
  margin-top: 8px;
  border: 1px solid #f2c5c5;
  background: #fff7f7;
  border-radius: 8px;
  padding: 8px 10px;
  max-height: 220px;
  overflow: auto;
}

.setup-import-error-item {
  display: grid;
  grid-template-columns: minmax(220px, 1fr) 2fr;
  gap: 10px;
  padding: 4px 0;
  border-bottom: 1px dashed #f3d9d9;
}

.setup-import-error-item:last-child {
  border-bottom: none;
}

.setup-import-error-field {
  font-size: 12px;
  color: #9f4646;
}

.setup-import-error-message {
  font-size: 12px;
  color: #c23b3b;
}

.setup-import-error-toggle {
  margin-top: 6px;
}

.setup-switcher {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  border-radius: 14px;
  background: rgba(11, 30, 75, 0.45);
  border: 1px solid rgba(187, 217, 255, 0.28);
}

.setup-switcher-wrap {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.setup-save-meta {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  font-size: 12px;
  color: rgba(226, 240, 255, 0.9);
  padding-left: 4px;
}

.setup-save-meta .meta-text {
  color: rgba(226, 240, 255, 0.9);
}

.setup-view-mode {
  padding-left: 2px;
}

.setup-view-mode :deep(.el-radio-group) {
  background: rgba(11, 30, 75, 0.24);
  border-radius: 8px;
}

.setup-view-mode :deep(.el-radio-button__inner) {
  border-color: rgba(187, 217, 255, 0.32);
  color: rgba(237, 247, 255, 0.92);
  background: transparent;
}

.setup-view-mode :deep(.el-radio-button:first-child .el-radio-button__inner) {
  border-left-color: rgba(187, 217, 255, 0.32);
}

.setup-view-mode :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) {
  background: rgba(255, 255, 255, 0.18);
  border-color: rgba(227, 240, 255, 0.9);
  color: #ffffff;
  box-shadow: none;
}

.hidden-file-input {
  display: none;
}

.switcher-title {
  font-size: 18px;
  line-height: 1;
  font-weight: 600;
  color: #ffffff;
}

.setup-switcher :deep(.el-button) {
  border-color: rgba(255, 255, 255, 0.2);
  background: rgba(255, 255, 255, 0.92);
}

.setup-toolbar-actions {
  display: flex;
  align-items: center;
  gap: 7px;
  flex-wrap: wrap;
}

.setup-toolbar-actions :deep(.el-button) {
  border-radius: 10px;
}

.setup-toolbar-actions :deep(.el-button--primary),
.setup-toolbar-actions :deep(.el-button--success.is-plain),
.setup-toolbar-actions :deep(.el-button--warning.is-plain),
.setup-toolbar-actions :deep(.el-button--info.is-plain) {
  background: rgba(11, 30, 75, 0.42);
  border-color: rgba(167, 199, 255, 0.52);
  color: #ffffff;
}

.setup-toolbar-actions :deep(.el-button--primary:hover),
.setup-toolbar-actions :deep(.el-button--success.is-plain:hover),
.setup-toolbar-actions :deep(.el-button--warning.is-plain:hover),
.setup-toolbar-actions :deep(.el-button--info.is-plain:hover) {
  background: rgba(26, 62, 147, 0.66);
  color: #ffffff;
}

.secondary-menu-btn {
  border-color: rgba(167, 199, 255, 0.52);
  background: rgba(12, 36, 92, 0.25);
  color: #ffffff;
}

.setup-section {
  padding: 8px 12px 12px;
  border-bottom: 1px solid #edf2f8;
  background: #ffffff;
}

.enrollment-plan-section {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.enrollment-plan-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 18px;
}

.enrollment-inline-group {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.enrollment-inline-label {
  font-size: 13px;
  font-weight: 600;
  color: #4a6283;
  background: #f3f6fb;
  padding: 4px 10px;
  border-radius: 6px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  border: 1px solid #e6edf7;
}

.enrollment-inline-label.required::before {
  content: "*";
  color: #d14141;
  margin-right: 4px;
}

.enrollment-date-cell {
  min-width: auto;
}

.enrollment-date-cell :deep(.el-date-editor) {
  min-width: 148px;
}

.enrollment-target-cell {
  min-width: 112px;
}

.enrollment-inline-value {
  font-size: 14px;
  font-weight: 600;
  color: #11264d;
}

.enrollment-sep {
  color: #8da3c7;
  font-weight: 600;
  align-self: center;
  display: inline-flex;
  align-items: center;
  height: 32px;
  line-height: 32px;
  flex-shrink: 0;
}

.enrollment-plan-summary {
  border: 1px solid #e2ebf8;
  border-radius: 10px;
  background: #f8fbff;
  padding: 8px 10px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.enrollment-summary-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  font-size: 13px;
  color: #38537b;
}

.summary-site-name {
  font-weight: 700;
  color: #11264d;
}

.summary-title {
  font-weight: 700;
  color: #284a7b;
}

.summary-danger {
  color: #d14141;
  font-weight: 600;
}

.summary-success {
  color: #2f7a4d;
  font-weight: 600;
}

.enrollment-plan-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
}

.site-plan-selector {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.enrollment-site-filter-row {
  justify-content: flex-start;
}

.enrollment-plan-action-link {
  color: #5f7499;
  font-weight: 600;
}

.enrollment-plan-action-link:hover {
  color: #3f617d;
}

.enrollment-month-plan-table :deep(th.el-table__cell .cell) {
  text-align: center;
}

.enrollment-month-plan-table :deep(td.el-table__cell .cell) {
  text-align: center;
  padding-top: 5px;
  padding-bottom: 5px;
}

.enrollment-period-plan-table :deep(th.el-table__cell .cell) {
  text-align: center;
}

.enrollment-period-plan-table :deep(td.el-table__cell .cell) {
  text-align: center;
  padding-top: 6px;
  padding-bottom: 6px;
}

.month-target-input {
  width: 82px;
}

.month-empty {
  color: #b3bfd5;
}

.site-plan-site-select {
  width: 320px;
  max-width: 100%;
}

.setup-content {
  position: relative;
}

.setup-content-step-title {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  font-size: 18px;
  font-weight: 700;
  color: #162f57;
  border-bottom: 1px solid #e8eff8;
  background: #ffffff;
}

.setup-content-step-nav {
  display: flex;
  align-items: center;
  gap: 8px;
}

.setup-content-step-actions {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 8px;
}

.setup-content-step-actions :deep(.step-action-btn) {
  min-width: 88px;
  border-radius: 10px;
  font-weight: 600;
}

.setup-content-step-actions :deep(.step-action-btn.el-button--primary) {
  background: #3f617d;
  border-color: #3f617d;
  color: #ffffff;
}

.setup-content-step-actions :deep(.step-action-btn.el-button--primary:hover) {
  background: #355472;
  border-color: #355472;
  color: #ffffff;
}

.setup-content-step-actions :deep(.step-action-btn-cancel) {
  background: #f3f6fb;
  border-color: #d7e1ee;
  color: #4a6283;
}

.setup-content-step-actions :deep(.step-action-btn-cancel:hover) {
  background: #e8eff8;
  border-color: #c7d5e8;
  color: #3b5476;
}

.setup-content-step-actions :deep(.step-action-btn.el-button--primary .el-button__text),
.setup-content-step-actions :deep(.step-action-btn.el-button--primary > span),
.setup-content-step-actions :deep(.step-action-btn.el-button--primary .el-button__inner) {
  color: #ffffff !important;
  opacity: 1 !important;
}

.setup-edit-backdrop {
  position: fixed;
  inset: 0;
  z-index: 1200;
  background: rgba(10, 20, 40, 0.45);
  backdrop-filter: blur(2px);
  animation: setup-backdrop-fade-in 0.2s ease-out;
}

.setup-content.setup-content-drawer {
  position: fixed;
  top: 0;
  right: 0;
  z-index: 1300;
  width: min(380px, 100vw);
  height: 100vh;
  overflow-y: auto;
  background: #ffffff;
  box-shadow: -12px 0 36px rgba(7, 22, 50, 0.22);
  display: flex;
  flex-direction: column;
  animation: setup-drawer-slide-in 0.24s cubic-bezier(0.2, 0.8, 0.2, 1);
  will-change: transform, opacity;
}

.setup-edit-backdrop.is-closing {
  animation: setup-backdrop-fade-out 0.2s ease-in forwards;
}

.setup-content.setup-content-drawer.is-closing {
  animation: setup-drawer-slide-out 0.22s ease-in forwards;
}

.setup-content.setup-content-drawer .setup-section {
  border-bottom: 1px solid #edf2f8;
  border-radius: 0;
  padding: 12px 16px;
}

.setup-drawer-header {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 12px;
  padding: 12px 16px;
  border-bottom: 1px solid #e6edf7;
  background: #ffffff;
}

.setup-drawer-title-wrap {
  display: flex;
  align-items: center;
  min-height: 32px;
}

.setup-drawer-title {
  font-size: 18px;
  font-weight: 700;
  color: #11264d;
}

.setup-drawer-footer {
  position: sticky;
  bottom: 0;
  z-index: 2;
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  padding: 12px 16px;
  border-top: 1px solid #e6edf7;
  background: #ffffff;
}

.drawer-form-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.drawer-form-card {
  padding: 10px;
  border: 1px solid #e6edf7;
  border-radius: 10px;
  background: #fcfdff;
}

.drawer-form-card :deep(.el-form-item) {
  margin-bottom: 10px;
}

.drawer-form-card :deep(.el-form-item:last-child) {
  margin-bottom: 0;
}

.drawer-form-card-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding-top: 4px;
}

@keyframes setup-drawer-slide-in {
  from {
    transform: translateX(100%);
    opacity: 0.9;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

@keyframes setup-backdrop-fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes setup-drawer-slide-out {
  from {
    transform: translateX(0);
    opacity: 1;
  }
  to {
    transform: translateX(100%);
    opacity: 0.94;
  }
}

@keyframes setup-backdrop-fade-out {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}

.field-cell {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.field-error {
  font-size: 12px;
  line-height: 1.3;
  color: #d14141;
}

.setup-section:last-of-type {
  border-bottom: 0;
}

.setup-section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding-bottom: 8px;
  margin-bottom: 10px;
}

.setup-content.setup-content-drawer .setup-section-header {
  padding-bottom: 0;
  margin-bottom: 0;
  min-height: 0;
}

.published-view-tip {
  margin-bottom: 8px;
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid #cfe1ff;
  background: #f4f8ff;
  color: #35527d;
  font-size: 12px;
}

.section-title {
  font-size: 15px;
  font-weight: 700;
  color: var(--ctms-text-main);
}

.w-full {
  width: 100%;
}

.info-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px 24px;
}

.basic-info-groups {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.info-group {
  padding: 16px 20px;
  background: #ffffff;
  border: 1px solid #eef3fb;
  border-radius: 12px;
  box-shadow: 0 2px 12px rgba(22, 47, 86, 0.03);
}

.info-group + .info-group {
  margin-top: 14px;
}

.info-group-title {
  font-size: 14px;
  font-weight: 700;
  color: #284a7a;
  margin-bottom: 12px;
}

.info-group-title-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.info-group-edit-btn {
  min-width: 72px;
  border-radius: 10px;
  background: #3f617d;
  border-color: #3f617d;
  color: #ffffff;
  font-size: 14px;
  font-weight: 600;
}

.info-group-edit-btn:deep(span) {
  font-size: 14px;
  line-height: 1;
  color: #ffffff !important;
  opacity: 1 !important;
}

.info-group-edit-btn:hover,
.info-group-edit-btn:focus {
  background: #355472;
  border-color: #355472;
  color: #ffffff;
}

.info-group-edit-btn.is-disabled,
.info-group-edit-btn.is-disabled:hover {
  background: #94a3b8;
  border-color: #94a3b8;
  color: #ffffff;
}

.basic-form-group-title {
  margin: 0 0 14px;
  font-size: 14px;
  font-weight: 700;
  color: #284a7a;
}

.basic-edit-group {
  padding: 18px 20px 4px;
  background: #f8fbff;
  border: 1px solid #e6edf7;
  border-radius: 12px;
  margin-bottom: 16px;
}

.basic-edit-group + .basic-edit-group {
  margin-top: 0;
  padding-top: 18px;
  border-top: 1px solid #e6edf7;
}

.info-item {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  min-height: 28px;
  padding: 6px 0;
}

.info-label {
  color: #5f7499;
  min-width: 112px;
  font-size: 14px;
  background: #f4f8ff;
  padding: 4px 10px;
  border-radius: 6px;
  display: inline-flex;
  align-items: center;
}

.info-value {
  color: #11264d;
  font-weight: 600;
  font-size: 15px;
  word-break: break-word;
  padding-top: 4px;
}

.setup-info-tabs {
  margin-top: -8px;
}

.setup-section :deep(.el-tabs__item) {
  font-size: 15px;
  font-weight: 600;
}

.setup-section :deep(.el-tabs__nav-wrap::after) {
  background-color: #edf2f8;
}

.setup-section :deep(.el-table) {
  border-radius: 10px;
  overflow: hidden;
  border: 1px solid #eef3fb;
}

.setup-section :deep(.el-table th.el-table__cell) {
  background: #f8fbff;
  color: #35527d;
  font-weight: 600;
}

.setup-section :deep(.el-table td.el-table__cell) {
  border-bottom-color: #edf2f8;
}

.setup-milestone-table :deep(th.el-table__cell),
.setup-milestone-table :deep(td.el-table__cell) {
  height: 36px;
}

.setup-milestone-table :deep(th.el-table__cell .cell),
.setup-milestone-table :deep(td.el-table__cell .cell) {
  padding-top: 4px;
  padding-bottom: 4px;
}

.setup-milestone-table :deep(th.el-table__cell .cell) {
  font-size: 12px;
  font-weight: 700;
  color: #000;
}

.milestone-cell-text {
  display: inline-block;
  font-size: 12px;
  font-weight: 400;
  color: #000;
  line-height: 16px;
}

.milestone-plan-cell {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.milestone-plan-line {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  line-height: 16px;
  color: #000;
}

.milestone-plan-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  border: 3px solid #2f84c6;
  box-sizing: border-box;
}

.milestone-plan-label {
  color: #4b5563;
}

.milestone-actions-inline {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  white-space: nowrap;
}

.setup-milestone-editor-header {
  font-size: 22px;
  font-weight: 700;
  color: #11264d;
}

.setup-milestone-editor-form {
  padding-right: 4px;
}

.setup-milestone-editor-footer {
  display: flex;
  justify-content: flex-end;
  gap: 12px;
}

.setup-content.is-published-preview .drawer-form-card-actions {
  display: none;
}

.setup-content.is-published-preview :deep(.el-input.is-disabled .el-input__wrapper),
.setup-content.is-published-preview :deep(.el-textarea.is-disabled .el-textarea__inner),
.setup-content.is-published-preview :deep(.el-select .el-select__wrapper.is-disabled),
.setup-content.is-published-preview :deep(.el-input-number.is-disabled .el-input__wrapper),
.setup-content.is-draft-preview :deep(.el-input.is-disabled .el-input__wrapper),
.setup-content.is-draft-preview :deep(.el-textarea.is-disabled .el-textarea__inner),
.setup-content.is-draft-preview :deep(.el-select .el-select__wrapper.is-disabled),
.setup-content.is-draft-preview :deep(.el-input-number.is-disabled .el-input__wrapper) {
  background: transparent;
  box-shadow: none;
  border: 0;
  padding-left: 0;
  padding-right: 0;
}

.setup-content.is-published-preview :deep(.el-input.is-disabled .el-input__inner),
.setup-content.is-published-preview :deep(.el-textarea.is-disabled .el-textarea__inner),
.setup-content.is-published-preview :deep(.el-select .el-select__selected-item),
.setup-content.is-published-preview :deep(.el-input-number.is-disabled .el-input__inner),
.setup-content.is-draft-preview :deep(.el-input.is-disabled .el-input__inner),
.setup-content.is-draft-preview :deep(.el-textarea.is-disabled .el-textarea__inner),
.setup-content.is-draft-preview :deep(.el-select .el-select__selected-item),
.setup-content.is-draft-preview :deep(.el-input-number.is-disabled .el-input__inner) {
  color: #11264d;
  -webkit-text-fill-color: #11264d;
  font-weight: 600;
}

.setup-content.is-published-preview :deep(.el-input.is-disabled .el-input__suffix),
.setup-content.is-published-preview :deep(.el-select .el-select__caret),
.setup-content.is-published-preview :deep(.el-input-number__decrease),
.setup-content.is-published-preview :deep(.el-input-number__increase),
.setup-content.is-draft-preview :deep(.el-input.is-disabled .el-input__suffix),
.setup-content.is-draft-preview :deep(.el-select .el-select__caret),
.setup-content.is-draft-preview :deep(.el-input-number__decrease),
.setup-content.is-draft-preview :deep(.el-input-number__increase) {
  display: none;
}

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

.setup-content.setup-content-drawer :deep(.el-row) {
  margin-left: 0 !important;
  margin-right: 0 !important;
  display: block;
}

.setup-content.setup-content-drawer :deep(.el-row > .el-col) {
  max-width: 100% !important;
  flex: 0 0 100% !important;
  width: 100% !important;
  padding-left: 0 !important;
  padding-right: 0 !important;
}

.publish-incomplete {
  margin-bottom: 12px;
}

.publish-incomplete-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 8px;
}

.conflict-summary {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 12px;
  color: #334a75;
  font-size: 13px;
}

.conflict-diff-lines {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 12px;
}

.conflict-readable-diff {
  margin-bottom: 12px;
}

.publish-diff-overview {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 8px;
}

.conflict-readable-diff :deep(.el-table th.el-table__cell) {
  background: #f7faff;
  color: #2f4672;
}

@media (max-width: 960px) {
  .setup-switcher {
    width: 100%;
  }

  .setup-toolbar-actions {
    width: 100%;
  }

  .info-grid {
    grid-template-columns: 1fr;
  }

  .flow-label {
    font-size: 14px;
  }

  .switcher-title {
    font-size: 16px;
  }

}
</style>
