<template>
  <div class="paper-management-panel">
    <PageHeader title="智能组卷" description="从知识库文件中选题，一键生成、保存与发布试卷" />

    <!-- 标签导航 -->
    <div class="tab-nav">
      <button
        v-for="tab in tabs"
        :key="tab.key"
        :class="['tab-btn', { active: activeTab === tab.key }]"
        @click="switchTab(tab.key)"
      >
        <span class="tab-icon" v-html="tab.icon"></span>
        <span class="tab-label">{{ tab.label }}</span>
        <span v-if="tab.key === 'drafts' && drafts.length" class="tab-badge">{{ drafts.length }}</span>
        <span v-if="tab.key === 'published' && publishedPapers.length" class="tab-badge published">{{ publishedPapers.length }}</span>
      </button>
    </div>

    <!-- ================================================================ -->
    <!-- 智能组卷 -->
    <!-- ================================================================ -->
    <div v-if="activeTab === 'compose'" class="compose-layout">
      <!-- 左侧配置 -->
      <div class="compose-sidebar">
        <!-- Step 1: 数据源 -->
        <div class="config-section">
          <div class="section-header">
            <span class="step-badge">1</span>
            <h4>选择数据源（可选）</h4>
            <button
              v-if="!showFilePicker"
              class="link-btn"
              @click="openFilePicker"
            >{{ selectedFiles.length ? `已选 ${selectedFiles.length} 个文件` : '从知识库选择文件' }}</button>
            <button v-else class="link-btn muted" @click="showFilePicker = false">收起</button>
          </div>
          <p class="section-desc">不选则从全部可用题库中随机出题</p>

          <!-- 已选文件标签 -->
          <div v-if="selectedFiles.length > 0 && !showFilePicker" class="selected-files">
            <span v-for="f in selectedFiles" :key="f.id" class="file-chip">
              {{ f.name || f.file_name || f.fileName }}
              <button class="chip-remove" @click.stop="removeFile(f.id)">&times;</button>
            </span>
          </div>

          <!-- 文件选择面板 -->
          <div v-if="showFilePicker" class="file-picker">
            <div class="file-select-bar">
              <select class="file-select-dropdown" @change="handleFileSelect($event)">
                <option value="">-- 选择文件添加到数据源 --</option>
                <option v-for="f in allFiles" :key="f.id" :value="f.id" :disabled="isFileSelected(f.id)">
                  {{ f.name || f.file_name || f.fileName || ('文件-' + f.id) }}
                </option>
              </select>
            </div>
            <div class="file-list" v-if="selectedFiles.length > 0">
              <div
                v-for="f in selectedFiles"
                :key="f.id"
                class="file-item checked"
              >
                <span class="file-name">{{ f.name || f.file_name || f.fileName }}</span>
                <button class="chip-remove" @click.stop="removeFile(f.id)">&times;</button>
              </div>
            </div>
            <div v-else-if="loadingFiles" class="file-list-loading">加载知识库文档...</div>
            <div v-else-if="allFiles.length === 0 && !loadingFiles" class="file-list-empty">暂无可选文档</div>
            <div v-else class="file-list-hint">请从上方下拉框中选择要组卷的文档</div>
          </div>
        </div>

        <!-- Step 2: 难度与范围 -->
        <div class="config-section">
          <div class="section-header">
            <span class="step-badge">2</span>
            <h4>难度与范围</h4>
          </div>
          <div class="form-row">
            <label class="mini-label">难度</label>
            <div class="difficulty-bar">
              <button
                v-for="lv in difficulties"
                :key="lv.value"
                :class="['diff-btn', { active: composeForm.difficulty === lv.value }]"
                @click="composeForm.difficulty = lv.value"
              >{{ lv.label }}</button>
            </div>
          </div>
          <div class="form-row">
            <label class="mini-label">包含个人题库</label>
            <label class="toggle-switch">
              <input v-model="composeForm.include_personal" type="checkbox" />
              <span class="toggle-track"><span class="toggle-thumb"></span></span>
              <span class="toggle-text">{{ composeForm.include_personal ? '是' : '否' }}</span>
            </label>
          </div>
        </div>

        <!-- Step 3: 题型数量 -->
        <div class="config-section">
          <div class="section-header">
            <span class="step-badge">3</span>
            <h4>题型数量</h4>
          </div>
          <div class="type-grid">
            <div v-for="opt in questionTypes" :key="opt.key" class="type-cell">
              <span class="type-name">{{ opt.label }}</span>
              <div class="count-row">
                <button class="ctrl" @click="decrement(opt.key)">-</button>
                <span class="count">{{ composeForm[opt.key] }}</span>
                <button class="ctrl" @click="increment(opt.key)">+</button>
              </div>
            </div>
          </div>
          <div class="total-row">
            共 <strong>{{ totalCount }}</strong> 题
          </div>
        </div>

        <!-- 操作按钮 -->
        <button
          class="generate-btn"
          :disabled="totalCount === 0 || composing"
          @click="handleGeneratePaper"
        >
          <span v-if="composing" class="spinner-sm"></span>
          {{ composing ? '生成中...' : '生成试卷' }}
        </button>
      </div>

      <!-- 右侧结果 -->
      <div class="compose-result">
        <!-- 空态 -->
        <div v-if="!composeResult && !composing" class="result-empty">
          <svg width="56" height="56" viewBox="0 0 24 24" fill="none" stroke="#DEE2E6" stroke-width="1.2">
            <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
            <polyline points="14 2 14 8 20 8"/>
            <line x1="16" y1="13" x2="8" y2="13"/>
            <line x1="16" y1="17" x2="8" y2="17"/>
          </svg>
          <p>选择文件（可选）、设置题型数量<br/>点击「生成试卷」即可预览</p>
        </div>

        <!-- 加载 -->
        <div v-if="composing" class="result-loading">
          <div class="loading-spinner"></div>
          <span>正在从题库中选题组卷...</span>
        </div>

        <!-- 结果 -->
        <div v-if="composeResult && !composing" class="result-content">
          <div class="paper-preview-card">
            <div class="paper-preview-header">
              <h3 class="paper-preview-title">{{ composeResult.paperTitle || '试卷预览' }}</h3>
              <div class="paper-preview-stats">
                <span class="paper-stat">{{ composeResult.questions?.length || 0 }} 道题目</span>
                <span class="paper-stat">共 {{ totalPaperScore }} 分</span>
              </div>
            </div>
            <div class="question-list">
              <div v-for="(q, i) in composeResult.questions" :key="i" class="q-card">
                <div class="q-header">
                  <span class="q-num">{{ i + 1 }}</span>
                  <span class="q-type">{{ formatQType(q.question_type) }}</span>
                  <span class="q-diff">难度 {{ q.difficulty || '-' }}</span>
                </div>
                <div class="q-stem">{{ q.content?.stem || '无题干' }}</div>
              </div>
            </div>
          </div>

          <div v-if="composeResult && composeResult.questions?.length > 0" class="save-area">
            <button class="save-btn" @click="handleSavePaper">保存为草稿</button>
            <p class="save-note">保存后可在「我的草稿」中查看、发布</p>
          </div>
        </div>
      </div>
    </div>

    <!-- ================================================================ -->
    <!-- 我的草稿 -->
    <!-- ================================================================ -->
    <div v-if="activeTab === 'drafts'" class="tab-body">
      <div v-if="loadingDrafts" class="state-msg">
        <div class="loading-spinner"></div>加载中...
      </div>
      <div v-else-if="drafts.length === 0" class="state-empty">
        <svg width="52" height="52" viewBox="0 0 24 24" fill="none" stroke="#DEE2E6" stroke-width="1.2">
          <path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/><polyline points="13 2 13 9 20 9"/>
        </svg>
        <h4>暂无草稿</h4>
        <p>在「智能组卷」中生成并保存试卷</p>
      </div>
      <div v-else class="paper-list">
        <div v-for="p in drafts" :key="getId(p)" class="paper-row">
          <div class="paper-info">
            <div class="paper-title-row">
              <span class="paper-name">{{ getPaperTitle(p) }}</span>
              <span class="badge draft">草稿</span>
            </div>
            <div class="paper-meta">
              <span class="meta">{{ p.questionCount || p.question_count || p.questions?.length || 0 }} 题</span>
              <span class="meta">{{ p.totalScore || p.total_score || 0 }} 分</span>
              <span class="meta">{{ p.createdAt || p.created_at || '-' }}</span>
            </div>
          </div>
          <div class="paper-ops">
            <button class="op-link" @click="viewPaperDetail(p)">查看</button>
            <button class="op-primary" @click="openPublishDialog(getId(p))">发布</button>
            <button class="op-danger" @click="deleteDraft(getId(p))">删除</button>
          </div>
        </div>
      </div>
    </div>

    <!-- ================================================================ -->
    <!-- 已发布试卷 -->
    <!-- ================================================================ -->
    <div v-if="activeTab === 'published'" class="tab-body">
      <div v-if="loadingDrafts" class="state-msg">
        <div class="loading-spinner"></div>加载中...
      </div>
      <div v-else-if="publishedPapers.length === 0" class="state-empty">
        <svg width="52" height="52" viewBox="0 0 24 24" fill="none" stroke="#DEE2E6" stroke-width="1.2">
          <path d="M22 12h-4l-3 9L9 3l-3 9H2"/>
        </svg>
        <h4>暂无已发布试卷</h4>
        <p>将草稿发布后可在此查看</p>
      </div>
      <div v-else class="paper-list">
        <div v-for="p in publishedPapers" :key="getId(p)" class="paper-row">
          <div class="paper-info">
            <div class="paper-title-row">
              <span class="paper-name">{{ getPaperTitle(p) }}</span>
              <span class="badge published">已发布</span>
            </div>
            <div class="paper-meta">
              <span class="meta">{{ p.questionCount || p.question_count || p.questions?.length || 0 }} 题</span>
              <span class="meta">{{ p.totalScore || p.total_score || 0 }} 分</span>
            </div>
          </div>
          <div class="paper-ops">
            <button class="op-link" @click="viewPaperDetail(p)">查看</button>
            <button class="op-danger" @click="revokePublishedPaper(getId(p))">撤回</button>
            <button class="op-delete" @click="deletePublishedPaper(getId(p))">删除</button>
          </div>
        </div>
      </div>
    </div>

    <!-- 试卷详情弹窗 -->
    <Teleport to="body">
      <div v-if="previewRaw" class="overlay" @click.self="clearPreview">
        <div class="modal modal-lg">
          <div class="modal-hdr">
            <h3>{{ previewPaperTitle || '试卷详情' }}</h3>
            <button class="close-x" @click="clearPreview">&times;</button>
          </div>
          <div class="modal-body">
            <!-- 发布信息 -->
            <div v-if="hasPublishInfo" class="publish-info-section">
              <div class="pub-info-title">发布配置</div>
              <div class="pub-info-grid">
                <div class="pub-info-row" v-if="previewPublishInfo.departments.length > 0 || !previewPublishInfo.publishTime">
                  <span class="pub-info-label">目标部门</span>
                  <span class="pub-info-value">
                    <template v-if="previewPublishInfo.departments.length > 0">
                      <span v-for="(d, di) in previewPublishInfo.departments" :key="di" class="pi-tag pi-dept">{{ d }}</span>
                    </template>
                    <span v-else class="pi-tag pi-all">全部部门</span>
                  </span>
                </div>
                <div class="pub-info-row" v-if="previewPublishInfo.userScopeMode === 'target' && previewPublishInfo.targetUsers.length > 0">
                  <span class="pub-info-label">指定用户</span>
                  <span class="pub-info-value">
                    <span v-for="(u, ui) in previewPublishInfo.targetUsers" :key="ui" class="pi-tag pi-user">{{ u }}</span>
                  </span>
                </div>
                <div class="pub-info-row" v-if="previewPublishInfo.userScopeMode === 'exclude' && previewPublishInfo.excludeUsers.length > 0">
                  <span class="pub-info-label">排除用户</span>
                  <span class="pub-info-value">
                    <span v-for="(u, ui) in previewPublishInfo.excludeUsers" :key="ui" class="pi-tag pi-exclude">{{ u }}</span>
                  </span>
                </div>
                <div class="pub-info-row" v-if="previewPublishInfo.publishTime">
                  <span class="pub-info-label">发布时间</span>
                  <span class="pub-info-value pub-info-time">{{ formatPubTime(previewPublishInfo.publishTime) }}</span>
                </div>
                <div class="pub-info-row" v-if="previewPublishInfo.deadline">
                  <span class="pub-info-label">截止时间</span>
                  <span class="pub-info-value pub-info-time" :class="{ 'pi-deadline-near': isDeadlineNear(previewPublishInfo.deadline) }">{{ formatPubTime(previewPublishInfo.deadline) }}</span>
                </div>
              </div>
            </div>

            <!-- 试卷概要 -->
            <div class="paper-detail-summary" v-if="previewQuestions.length > 0">
              <span class="summary-item">共 <strong>{{ previewQuestions.length }}</strong> 道题</span>
              <span class="summary-item">总分 <strong>{{ previewTotalScore }}</strong> 分</span>
            </div>

            <!-- 题目列表 -->
            <div v-if="previewQuestions.length > 0" class="detail-question-list">
              <div
                v-for="(q, index) in previewQuestions"
                :key="q.questionId || q.id || index"
                class="detail-q-card"
              >
                <div class="dqc-header">
                  <span class="dqc-num">{{ index + 1 }}</span>
                  <span class="dqc-type">{{ q._typeLabel || formatQType(q.questionType || q.type || q.question_type) }}</span>
                  <span class="dqc-score">{{ q.score }} 分</span>
                </div>
                <div class="dqc-stem">{{ getDetailStem(q) }}</div>

                <!-- 选择题选项 -->
                <template v-if="isChoiceQuestion(q)">
                  <div v-if="getDetailOptions(q).length > 0" class="dqc-options">
                    <div
                      v-for="(opt, oi) in getDetailOptions(q)"
                      :key="oi"
                      :class="['dqc-option', { 'is-correct-opt': isOptionAnswer(opt, q) }]"
                    >
                      <span class="dqc-opt-key">{{ opt.key }}</span>
                      <span class="dqc-opt-text">{{ opt.text }}</span>
                      <span v-if="isOptionAnswer(opt, q)" class="dqc-opt-tag answer">✓ 正确</span>
                    </div>
                  </div>
                  <div v-else class="dqc-no-options">（未解析到选项）</div>
                </template>

                <!-- 判断题 -->
                <div v-if="(q.questionType || q.type || q.question_type) === 'true_false'" class="dqc-tf-answer">
                  <span class="answer-label">正确答案：</span>
                  <span class="answer-value">{{ getTfAnswerText(q) }}</span>
                </div>

                <!-- 填空/简答题答案 -->
                <div v-if="isTextQuestion(q)" class="dqc-text-answer">
                  <span class="answer-label">参考答案：</span>
                  <span class="answer-value">{{ getDetailAnswer(q) || '（未设置）' }}</span>
                </div>

                <!-- 通用答案兜底：非选择/非判断/非文本题但有答案 -->
                <div v-if="!isChoiceQuestion(q) && (q.questionType||q.type||q.question_type)!=='true_false' && !isTextQuestion(q) && getDetailAnswer(q)" class="dqc-text-answer">
                  <span class="answer-label">答案：</span>
                  <span class="answer-value">{{ getDetailAnswer(q) }}</span>
                </div>

                <!-- 解析说明 -->
                <div v-if="q.explanation" class="dqc-explanation">
                  <span class="explanation-label">解析</span>
                  <span class="explanation-text">{{ q.explanation }}</span>
                </div>

                <!-- 难度标签 -->
                <div v-if="q.difficulty != null" class="dqc-diff">
                  难度：{{ getDifficultyLabel(q.difficulty) }}
                </div>
              </div>
            </div>

            <!-- 无数据但有原始返回 - 展示原始数据兜底 -->
            <div v-else-if="previewRaw" class="detail-fallback">
              <div class="fallback-header">
                <span class="fallback-icon">!</span>
                <span class="fallback-title">系统未能自动解析题目数据</span>
                <span class="fallback-hint">以下为后端返回的原始数据，请查看控制台日志了解详情</span>
              </div>
              <div class="fallback-fields">
                <div class="ff-row">
                  <span class="ff-label">试卷标题</span>
                  <span class="ff-value">{{ previewPaperTitle }}</span>
                </div>
                <div class="ff-row">
                  <span class="ff-label">数据类型</span>
                  <span class="ff-value">{{ fallbackDataType }}</span>
                </div>
                <div class="ff-row">
                  <span class="ff-label">顶层字段</span>
                  <span class="ff-value ff-code">{{ fallbackTopKeys }}</span>
                </div>
              </div>
              <details class="raw-data-details">
                <summary>展开查看完整原始 JSON 数据</summary>
                <pre class="json-block">{{ JSON.stringify(previewRaw, null, 2) }}</pre>
              </details>
            </div>

            <!-- 完全无数据 -->
            <div v-else class="detail-empty-state">
              <svg width="44" height="44" viewBox="0 0 24 24" fill="none" stroke="#DEE2E6" stroke-width="1.2">
                <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/>
              </svg>
              <p>暂无题目数据</p>
            </div>
          </div>
        </div>
      </div>
    </Teleport>

    <!-- 发布弹窗 -->
    <Teleport to="body">
      <div v-if="publishDialog" class="overlay" @click.self="closePublish">
        <div class="modal publish-modal">
          <div class="modal-hdr">
            <h3>发布试卷</h3>
            <button class="close-x" @click="closePublish">&times;</button>
          </div>
          <div class="modal-body pub-two-col">

            <!-- ===== 左列：部门 + 时间 ===== -->
            <div class="pub-col-left">

              <!-- 目标部门 -->
              <div class="pub-field">
                <label class="pub-label">目标部门</label>
                <div class="compact-bar" @click="openPicker('dept')">
                  <div class="compact-chips" v-if="pub.selectedDepts.length > 0">
                    <span v-for="d in pub.selectedDepts" :key="d.id" class="sel-chip dept-chip">{{ d.name }}<button class="chip-x" @click.stop="removeDept(d.id)">&times;</button></span>
                  </div>
                  <span v-else class="compact-placeholder">未选择（默认全部开放）</span>
                  <button class="compact-btn">选择部门</button>
                  <span v-if="pub.selectedDepts.length" class="compact-count">{{ pub.selectedDepts.length }}</span>
                </div>
              </div>

              <!-- 发布时间 -->
              <div class="pub-field">
                <label class="pub-label">发布时间</label>
                <input type="datetime-local" class="field-input pub-datetime" v-model="pub.publishTime" />
                <span class="field-hint">留空则立即发布</span>
              </div>

              <!-- 截止时间 -->
              <div class="pub-field">
                <label class="pub-label">截止时间</label>
                <input type="datetime-local" class="field-input pub-datetime" v-model="pub.deadline" />
                <span class="field-hint">可选，超时无法答题</span>
              </div>

            </div>

            <!-- ===== 右列：用户范围（互斥） ===== -->
            <div class="pub-col-right">

              <!-- 模式切换 -->
              <div class="pub-field">
                <label class="pub-label">用户范围</label>
                <div class="mode-switch">
                  <label :class="['mode-option', { active: pub.userMode === 'target', disabled: pub.selectedExcludes.length > 0 }]">
                    <input type="radio" :value="'target'" v-model="pub.userMode" :disabled="pub.selectedExcludes.length > 0" @change="switchToTarget" />
                    <span>指定目标用户</span>
                  </label>
                  <label :class="['mode-option', { active: pub.userMode === 'exclude', disabled: pub.selectedUsers.length > 0 }]">
                    <input type="radio" :value="'exclude'" v-model="pub.userMode" :disabled="pub.selectedUsers.length > 0" @change="switchToExclude" />
                    <span>排除以下用户</span>
                  </label>
                </div>
                <span v-if="pub.selectedExcludes.length > 0 && pub.userMode === 'target'" class="mode-hint warn">已有排除用户，请先清空再切换</span>
                <span v-if="pub.selectedUsers.length > 0 && pub.userMode === 'exclude'" class="mode-hint warn">已选目标用户，请先清空再切换</span>
              </div>

              <!-- 用户选择栏（根据模式显示不同内容） -->
              <div class="pub-field">
                <label class="pub-label">{{ pub.userMode === 'target' ? '目标用户' : '排除用户' }}</label>
                <div class="compact-bar" :class="{ disabled: isUserSelectorDisabled }" @click="openPicker('user')">
                  <template v-if="pub.userMode === 'target'">
                    <div class="compact-chips" v-if="pub.selectedUsers.length > 0">
                      <span v-for="u in pub.selectedUsers" :key="u.id" class="sel-chip user-chip">{{ u.name }}<button class="chip-x" @click.stop="removeUser(u.id)">&times;</button></span>
                    </div>
                    <span v-else class="compact-placeholder">未选择（默认全部开放）</span>
                    <span v-if="pub.selectedUsers.length" class="compact-count">{{ pub.selectedUsers.length }}人</span>
                  </template>
                  <template v-else>
                    <div class="compact-chips" v-if="pub.selectedExcludes.length > 0">
                      <span v-for="u in pub.selectedExcludes" :key="u.id" class="sel-chip exclude-chip">{{ u.name }}<button class="chip-x" @click.stop="removeExclude(u.id)">&times;</button></span>
                    </div>
                    <span v-else class="compact-placeholder">不排除任何人</span>
                    <span v-if="pub.selectedExcludes.length" class="compact-count">{{ pub.selectedExcludes.length }}人</span>
                  </template>
                  <button class="compact-btn">选择{{ pub.userMode === 'target' ? '目标' : '排除' }}用户</button>
                </div>
              </div>

            </div>

          </div>
          <div class="modal-ftr">
            <button class="btn btn-ghost" @click="closePublish">取消</button>
            <button class="btn btn-dark" :disabled="publishing" @click="confirmPublish">
              {{ publishing ? '发布中...' : '确认发布' }}
            </button>
          </div>
        </div>
      </div>

      <!-- 二级选择器弹窗 -->
      <Teleport to="body">
        <div v-if="pickerDialog.visible" class="overlay" @click.self="closePicker">
          <div class="picker-modal">
            <div class="picker-hdr">
              <h4>{{ pickerDialogTitle }}</h4>
              <button class="close-x" @click="closePicker">&times;</button>
            </div>
            <div class="picker-body">

              <!-- 部门选择 -->
              <template v-if="pickerDialog.type === 'dept'">
                <div class="picker-toolbar">
                  <input class="picker-search" placeholder="搜索部门..." v-model="pubUI.deptSearch" @input="filterDeptList" />
                  <button class="toolbar-btn" @click="selectAllDepts">全选</button>
                  <button class="toolbar-btn" @click="clearAllDepts">清空</button>
                  <span class="picker-stat">已选 {{ pub.selectedDepts.length }}/{{ pub.filteredDeptList.length }}</span>
                </div>
                <div v-if="pubUI.loadingDepts" class="dropdown-loading"><div class="loading-spinner"></div>加载中...</div>
                <div v-else-if="pub.filteredDeptList.length === 0" class="dropdown-empty">暂无部门数据</div>
                <div v-else class="picker-list">
                  <label v-for="d in pub.filteredDeptList" :key="d.id"
                    :class="['picker-item', { checked: isDeptSelected(d.id) }]"
                  >
                    <input type="checkbox" :checked="isDeptSelected(d.id)" @change="toggleDept(d)" />
                    <span class="item-name">{{ d.name }}</span>
                  </label>
                </div>
              </template>

              <!-- 用户选择 -->
              <template v-if="pickerDialog.type === 'user'">
                <div class="picker-toolbar">
                  <input class="picker-search" :placeholder="pub.userMode === 'target' ? '搜索目标用户...' : '搜索排除用户...'" v-model="currentPickerSearch" @input="applyUserFilter" />
                  <button class="toolbar-btn" v-if="pub.userMode === 'target'" @click="selectAllUsers">全选</button>
                  <button class="toolbar-btn" v-if="pub.userMode === 'target'" @click="clearAllUsers">清空</button>
                  <button class="toolbar-btn" v-if="pub.userMode === 'exclude'" @click="selectAllExcludes">全选</button>
                  <button class="toolbar-btn" v-if="pub.userMode === 'exclude'" @click="clearAllExcludes">清空</button>
                  <span class="picker-stat">已选 {{ currentSelectedCount }}/{{ currentFilteredList.length }}</span>
                </div>
                <div v-if="pubUI.loadingUsers" class="dropdown-loading"><div class="loading-spinner"></div>加载中...</div>
                <div v-else-if="currentFilteredList.length === 0" class="dropdown-empty">
                  {{ pub.selectedDepts.length === 0 ? '请先选择目标部门' : '无匹配用户' }}
                </div>
                <div v-else class="picker-list picker-list-tall">
                  <label v-for="u in currentFilteredList" :key="u.id"
                    :class="['picker-item', { checked: isCurrentSelected(u.id) }]"
                  >
                    <input type="checkbox" :checked="isCurrentSelected(u.id)" @change="toggleCurrentUser(u)" />
                    <span class="item-name">{{ u.name }}</span>
                    <span class="item-dept" v-if="u.departmentName">{{ u.departmentName }}</span>
                  </label>
                </div>
              </template>

            </div>
            <div class="picker-ftr">
              <button class="btn btn-ghost btn-sm" @click="closePicker">取消</button>
              <button class="btn btn-dark btn-sm" @click="confirmPicker">确定</button>
            </div>
          </div>
        </div>
      </Teleport>
    </Teleport>
  </div>
</template>

<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { message, Modal } from 'ant-design-vue'
import PageHeader from '../common/PageHeader.vue'
import { examAPI } from '../../api/exam.js'
import { fileAPI } from '../../api/file.js'
import { questionAPI } from '../../api/question.js'
import { departmentAPI, userAPI } from '../../api/auth'

// ==================== Tabs ====================
const activeTab = ref('compose')
const tabs = [
  { key: 'compose',  label: '智能组卷',  icon: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="12 2 22 8.5 22 15.5 12 22 2 15.5 2 8.5 12 2"/><line x1="12" y1="22" x2="12" y2="15.5"/><polyline points="22 8.5 12 15.5 2 8.5"/></svg>' },
  { key: 'drafts',    label: '我的草稿',  icon: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>' },
  { key: 'published', label: '已发布试卷', icon: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>' }
]

// ==================== Helpers ====================
const getId = p => {
  if (!p) return ''
  const id = p.id || p.paperId || p.paper_id || p.examPaperId || p.exam_paper_id || ''
  if (id) return id
  console.warn('[getId] 无法从对象提取ID, 可用字段:', Object.keys(p).join(','))
  return ''
}
const getPaperTitle = p => p.title || p.paper_title || '未命名试卷'

// ==================== Compose ====================
const difficulties = [
  { value: 1, label: '简单' }, { value: 2, label: '中等' },
  { value: 3, label: '较难' }, { value: 4, label: '困难' }, { value: 5, label: '极难' }
]
const questionTypes = [
  { key: 'single_choice_count',   label: '单选题' },
  { key: 'multiple_choice_count', label: '多选题' },
  { key: 'true_false_count',      label: '判断题' },
  { key: 'fill_blank_count',      label: '填空题' },
  { key: 'subjective_count',      label: '简答题' }
]

const composeForm = reactive({
  difficulty: 2,
  include_personal: true,
  single_choice_count: 0,
  multiple_choice_count: 0,
  true_false_count: 0,
  fill_blank_count: 0,
  subjective_count: 0
})

const composing = ref(false)
const composeResult = ref(null)

const totalCount = computed(() =>
  composeForm.single_choice_count + composeForm.multiple_choice_count +
  composeForm.true_false_count + composeForm.fill_blank_count + composeForm.subjective_count
)

const totalPaperScore = computed(() =>
  (composeResult.value?.questions || []).reduce((sum, q) => sum + (Number(q.score) || 0), 0)
)

const increment = key => { if (composeForm[key] < 50) composeForm[key]++ }
const decrement = key => { if (composeForm[key] > 0) composeForm[key]-- }
const formatQType = t => ({ single_choice: '单选', multiple_choice: '多选', true_false: '判断', fill_blank: '填空', subjective: '简答' })[t] || t

// ==================== File Selection ====================
const showFilePicker = ref(false)
const selectedFiles = ref([])
const allFiles = ref([])
const loadingFiles = ref(false)

const isFileSelected = id => selectedFiles.value.some(f => f.id === id)

const handleFileSelect = (event) => {
  const fileId = event.target.value
  if (!fileId) return
  const file = allFiles.value.find(f => String(f.id) === String(fileId))
  if (file && !isFileSelected(file.id)) {
    selectedFiles.value = [...selectedFiles.value, file]
  }
  event.target.value = ''
}

const removeFile = (id) => {
  selectedFiles.value = selectedFiles.value.filter(f => f.id !== id)
}

const openFilePicker = async () => {
  showFilePicker.value = true
  if (allFiles.value.length === 0) {
    loadingFiles.value = true
    try {
      const res = await fileAPI.getFiles({ current: 1, size: 200 })
      const wrapper = res?.data || res
      let list = []
      if (wrapper?.code === 200 && wrapper?.data?.records) {
        list = wrapper.data.records.filter(f => f.auditStatus !== 0)
      } else if (Array.isArray(wrapper)) {
        list = wrapper
      } else if (wrapper?.data) {
        const inner = wrapper.data
        list = Array.isArray(inner) ? inner : (inner?.list || inner?.records || inner?.data?.list || [])
      }
      allFiles.value = list
    } catch (e) {
      console.warn('加载文件列表失败:', e.message?.substring(0, 100))
    } finally {
      loadingFiles.value = false
    }
  }
}

// ==================== Generate Paper ====================
const handleGeneratePaper = async () => {
  if (totalCount.value === 0) { message.warning('请至少设置一种题型数量'); return }
  composing.value = true
  composeResult.value = null
  try {
    const params = {
      single_choice_count: composeForm.single_choice_count,
      multiple_choice_count: composeForm.multiple_choice_count,
      true_false_count: composeForm.true_false_count,
      fill_blank_count: composeForm.fill_blank_count,
      subjective_count: composeForm.subjective_count,
      difficulty: composeForm.difficulty,
      include_personal: composeForm.include_personal
    }
    if (selectedFiles.value.length > 0) {
      params.file_ids = selectedFiles.value.map(f => f.id)
    }

    console.log('[组卷] 请求参数:', JSON.stringify(params))
    const result = await examAPI.generatePaper(params)

    // API返回标准snake_case字段：questions, paper_id, paper_title, total_score, question_count
    const questions = Array.isArray(result?.questions) ? result.questions : []
    const paperId = result?.paper_id || null
    const paperTitle = result?.paper_title || ''
    const totalScore = result?.total_score || 0

    console.log('[组卷] 解析结果 - paperId:', paperId, 'questions:', questions.length, 'totalScore:', totalScore)

    if (questions.length === 0) {
      const hint = allFiles.value.length > 0
        ? `题库中暂无匹配的题目。请先在「生成题目」页面中为文件生成题目，然后再来组卷。`
        : '数据库中暂无题目。请先在「生成题目」页面中为制度文件生成题目。'
      message.warning(hint, 6)
    }

    composeResult.value = { questions, paperId, paperTitle, totalScore }

    if (questions.length > 0) {
      message.success(`组卷成功！${questions.length} 道题目，可保存为草稿`)
    } else {
      const hint = allFiles.value.length > 0
        ? `题库中暂无匹配的题目。请先在「生成题目」页面中为文件生成题目，然后再来组卷。`
        : '数据库中暂无题目。请先在「生成题目」页面中为制度文件生成题目。'
      message.warning(hint, 6)
    }
  } catch (err) {
    console.error('[组卷] 失败:', err)
    message.error('组卷失败: ' + (err.message || '未知错误'))
  } finally {
    composing.value = false
  }
}

// ==================== Save Paper ====================
const handleSavePaper = async () => {
  const r = composeResult.value
  if (!r || !r.questions || r.questions.length === 0) {
    message.warning('请先生成试卷')
    return
  }
  // generatePaper接口返回的题目使用snake_case: question_id
  const ids = r.questions.map(q => q.question_id).filter(Boolean)
  console.log('[保存] 题目IDs:', ids.slice(0, 5), `...共${ids.length}个`)
  if (ids.length === 0) {
    console.error('[保存] 无法提取题目ID，原始题目数据:', JSON.stringify(r.questions.slice(0, 2)))
    message.error('无法提取题目ID')
    return
  }
  try {
    const saveData = {
      title: r.paperTitle || `组卷_${new Date().toLocaleDateString()}`,
      questionIds: ids
    }
    console.log('[保存] 调用 savePaper, data:', JSON.stringify(saveData).substring(0, 200))
    const savedPaper = await examAPI.savePaper(saveData)
    console.log('[保存] savePaper 返回:', savedPaper ? Object.keys(savedPaper).join(',') : 'null')

    // 构建纸卷对象并直接注入草稿列表（savePaper接口返回camelCase）
    let newPaper = null
    if (savedPaper) {
      newPaper = {
        ...savedPaper,
        id: savedPaper.id || savedPaper.paperId,
        title: savedPaper.title || saveData.title,
        questionCount: ids.length,
        totalScore: savedPaper.totalScore || r.totalScore || 0,
        createdAt: savedPaper.createdAt || new Date().toISOString(),
        published: false,
        status: 'draft'
      }
    } else {
      newPaper = {
        id: 'local_' + Date.now(),
        title: saveData.title,
        questionCount: ids.length,
        totalScore: r.totalScore || 0,
        createdAt: new Date().toISOString(),
        published: false,
        status: 'draft'
      }
    }
    if (!drafts.value.some(d => d.id === newPaper.id)) {
      drafts.value.unshift(newPaper)
    }
    console.log('[保存] 已直接注入草稿列表，当前草稿数:', drafts.value.length, 'id:', newPaper.id)

    message.success('已保存为草稿')
    composeResult.value = null
    selectedFiles.value = []

    // 先切换到草稿Tab，用户立即看到直接注入的纸卷
    activeTab.value = 'drafts'

    // 后台刷新列表（保留已注入的纸卷，避免刷新时丢失）
    const savedId = newPaper.id
    const savedTitle = newPaper.title
    const savedCount = ids.length
    loadPapers().then(() => {
      if (!drafts.value.some(d => (d.id || d.paperId || d.paper_id) === savedId)) {
        drafts.value.unshift(newPaper)
        console.log('[保存] 刷新后纸卷丢失，已补回')
      }
      console.log('[保存] 刷新完成，草稿数:', drafts.value.length)
    })

  } catch (err) {
    console.error('[保存] 失败:', err)
    message.error('保存失败: ' + (err.message || ''))
  }
}

// ==================== Drafts & Published ====================
const drafts = ref([])
const publishedPapers = ref([])
const loadingDrafts = ref(false)

const loadPapers = async () => {
  loadingDrafts.value = true
  try {
    const res = await examAPI.getAdminPapers()
    console.log('[loadPapers] 原始响应类型:', typeof res, Array.isArray(res) ? `Array[${res.length}]` : Object.keys(res || {}).join(','))

    // 多路径提取列表
    let list = []
    if (Array.isArray(res)) {
      list = res
    } else if (res) {
      list = res.list || res.records || res.data?.list || res.data?.records ||
             res.data || []
      if (!Array.isArray(list) && res.data && Array.isArray(res.data)) {
        list = res.data
      }
    }
    console.log('[loadPapers] 解析到', list.length, '条试卷')
    if (list.length > 0) {
      console.log('[loadPapers] 首条 keys:', Object.keys(list[0]))
      console.log('[loadPapers] 首条:', JSON.stringify(list[0]).substring(0, 300))
    }

    drafts.value = list.filter(p => !(p.published === true || p.status === 'published'))
    publishedPapers.value = list.filter(p => p.published === true || p.status === 'published')
    console.log('[loadPapers] 草稿:', drafts.value.length, '已发布:', publishedPapers.value.length)
  } catch (e) {
    console.error('加载试卷列表失败:', e)
  } finally {
    loadingDrafts.value = false
  }
}

const deleteDraft = async (id) => {
  try { await examAPI.deleteAdminPaper(id); message.success('已删除'); loadPapers() }
  catch (e) { message.error('删除失败: ' + (e.message || '')) }
}

// ==================== Publish ====================
const publishDialog = ref(false)
const pubPaperId = ref(null)
const publishing = ref(false)

const pub = reactive({
  deptList: [],
  filteredDeptList: [],
  selectedDepts: [],
  userList: [],
  selectedUsers: [],
  selectedExcludes: [],
  userMode: 'target',
  publishTime: '',
  deadline: ''
})

const pubUI = reactive({
  loadingDepts: false,
  loadingUsers: false,
  deptSearch: ''
})

const pickerDialog = reactive({ visible: false, type: '' })
const currentPickerSearch = ref('')
const pendingDeptIds = ref(new Set())
const pendingUserIds = ref(new Set())
const pendingExcludeIds = ref(new Set())

let _deptCache = null
let _deptCacheTime = 0
let _userCache = null

const CACHE_TTL = 5 * 60 * 1000

const openPublishDialog = (id) => {
  pubPaperId.value = id
  pub.selectedDepts = []
  pub.selectedUsers = []
  pub.selectedExcludes = []
  pub.userMode = 'target'
  pub.publishTime = ''
  pub.deadline = ''
  pubUI.deptSearch = ''
  currentPickerSearch.value = ''
  publishDialog.value = true
}

const closePublish = () => { publishDialog.value = false; pubPaperId.value = null }

// --- 计算属性 ---
const isUserSelectorDisabled = computed(() =>
  pub.userMode === 'target' ? pub.selectedExcludes.length > 0 : pub.selectedUsers.length > 0
)

const pickerDialogTitle = computed(() =>
  pickerDialog.type === 'dept' ? '选择目标部门'
    : (pub.userMode === 'target' ? '选择目标用户' : '选择排除用户')
)

const currentFilteredList = computed(() => {
  const q = (currentPickerSearch.value || '').toLowerCase().trim()
  const source = [...pub.userList]
  if (!q) return source
  return source.filter(u =>
    (u.name || '').toLowerCase().includes(q) ||
    String(u.id).includes(q) ||
    (u.departmentName || '').toLowerCase().includes(q)
  )
})

const currentSelectedCount = computed(() =>
  pub.userMode === 'target' ? pendingUserIds.value.size : pendingExcludeIds.value.size
)

const isCurrentSelected = id =>
  pub.userMode === 'target'
    ? pendingUserIds.value.has(id)
    : pendingExcludeIds.value.has(id)

// --- 模式切换 ---
const switchToTarget = () => { if (!pub.selectedExcludes.length) pub.userMode = 'target' }
const switchToExclude = () => { if (!pub.selectedUsers.length) pub.userMode = 'exclude' }

// --- 二级弹窗控制 ---
const openPicker = async (type) => {
  pickerDialog.type = type
  pickerDialog.visible = true
  currentPickerSearch.value = ''
  if (type === 'dept') {
    pendingDeptIds.value = new Set(pub.selectedDepts.map(d => d.id))
    await loadDeptList()
    filterDeptList()
  } else if (type === 'user') {
    pendingUserIds.value = new Set(pub.selectedUsers.map(u => u.id))
    pendingExcludeIds.value = new Set(pub.selectedExcludes.map(u => u.id))
    await loadUserListForPicker()
  }
}
const closePicker = () => { pickerDialog.visible = false; pickerDialog.type = '' }
const confirmPicker = async () => {
  if (pickerDialog.type === 'dept') {
    pub.selectedDepts = pub.filteredDeptList.filter(d => pendingDeptIds.value.has(d.id)).map(d => ({ ...d }))
    await refreshUsersForDepts()
  } else if (pickerDialog.type === 'user') {
    if (pub.userMode === 'target') {
      pub.selectedUsers = pub.userList.filter(u => pendingUserIds.value.has(u.id)).map(u => ({ ...u }))
    } else {
      pub.selectedExcludes = pub.userList.filter(u => pendingExcludeIds.value.has(u.id)).map(u => ({ ...u }))
    }
  }
  closePicker()
}

// --- 部门选择器 ---
const loadDeptList = async () => {
  if (_deptCache && Date.now() - _deptCacheTime < CACHE_TTL) {
    pub.deptList = [..._deptCache]; pub.filteredDeptList = [..._deptCache]; return
  }
  pubUI.loadingDepts = true
  try {
    const res = await departmentAPI.getDepartments()
    let list = Array.isArray(res) ? res : null
    if (!list && typeof res === 'object' && res !== null) {
      for (const key of ['data', 'list', 'records', 'rows', 'items', 'departments']) {
        if (Array.isArray(res[key])) { list = res[key]; break }
        if (res.data && typeof res.data === 'object' && Array.isArray(res.data[key])) { list = res.data[key]; break }
      }
      if (!list && Array.isArray(res.data)) list = res.data
    }
    if (!list) { console.warn('[发布] 部门数据解析失败, keys:', Object.keys(res || {})); list = [] }
    pub.deptList = list.map(d => ({
      id: d.id, name: d.name || d.deptName || d.departmentName || d.dept_name || String(d.id || '')
    }))
    pub.filteredDeptList = [...pub.deptList]
    console.log(`[发布] 解析到 ${pub.deptList.length} 个部门`)
    _deptCache = [...pub.deptList]; _deptCacheTime = Date.now()
  } catch (e) {
    console.error('[发布] 加载部门失败:', e); message.error('加载部门列表失败')
  } finally { pubUI.loadingDepts = false }
}

const filterDeptList = () => {
  const q = (pubUI.deptSearch || '').toLowerCase().trim()
  pub.filteredDeptList = q ? pub.deptList.filter(d => (d.name || '').toLowerCase().includes(q) || String(d.id).includes(q)) : [...pub.deptList]
}

const isDeptSelected = id => pendingDeptIds.value.has(id)

const toggleDept = (d) => {
  const s = new Set(pendingDeptIds.value)
  if (s.has(d.id)) s.delete(d.id)
  else s.add(d.id)
  pendingDeptIds.value = s
}

const removeDept = async (id) => { pub.selectedDepts = pub.selectedDepts.filter(d => d.id !== id); await refreshUsersForDepts() }
const selectAllDepts = () => { pendingDeptIds.value = new Set(pub.filteredDeptList.map(d => d.id)) }
const clearAllDepts = () => { pendingDeptIds.value = new Set() }

const refreshUsersForDepts = async () => {
  _userCache = null; pub.userList = []; pub.selectedUsers = []
  const ids = pub.selectedDepts.map(d => Number(d.id)).filter(Boolean)
  if (ids.length > 0) {
    pubUI.loadingUsers = true
    try {
      let allUsers = []
      for (const did of ids) {
        try {
          let users = Array.isArray(await departmentAPI.getDepartmentUsers(did)) ? [] : null
          const dr = await departmentAPI.getDepartmentUsers(did)
          users = Array.isArray(dr) ? dr : null
          if (!users && typeof dr === 'object') {
            for (const k of ['data', 'list', 'records', 'users', 'rows']) {
              if (Array.isArray(dr[k])) { users = dr[k]; break }
              if (dr.data && Array.isArray(dr.data[k])) { users = dr.data[k]; break }
            }
            if (!users && Array.isArray(dr.data)) users = dr.data
          }
          allUsers = allUsers.concat(users || [])
        } catch (e) { console.warn(`[发布] 获取部门${did}用户失败:`, e.message?.substring(0, 100)) }
      }
      allUsers = allUsers.filter((u, i, arr) => arr.findIndex(x => x.id === u.id) === i)
      pub.userList = allUsers.map(u => ({ id: u.id, name: u.realName || u.username || u.name || u.nickname || ('用户' + u.id), departmentName: u.departmentName || u.deptName || '' }))
      console.log(`[发布] 部门联动加载 ${pub.userList.length} 个用户`)
    } catch (e) { console.error('[发布] 联动加载用户失败:', e) }
    finally { pubUI.loadingUsers = false }
  }
}

// --- 用户选择（统一入口）---
const loadUserListForPicker = async () => {
  if (pub.userList.length > 0) return
  if (pub.selectedDepts.length > 0) { await refreshUsersForDepts(); return }
  pubUI.loadingUsers = true
  try {
    const res = await userAPI.getUsers({ pageNum: 1, pageSize: 200 })
    let list = Array.isArray(res) ? res : null
    if (!list && typeof res === 'object' && res !== null) {
      for (const key of ['data', 'list', 'records', 'rows']) {
        if (Array.isArray(res[key])) { list = res[key]; break }
        if (res.data && Array.isArray(res.data[key])) { list = res.data[key]; break }
      }
      if (!list && Array.isArray(res.data)) list = res.data
    }
    if (!list) { console.warn('[发布] 用户数据解析失败'); list = [] }
    pub.userList = list.map(u => ({ id: u.id, name: u.realName || u.username || u.name || u.nickname || ('用户' + u.id), departmentName: u.departmentName || u.deptName || '' }))
    _userCache = true
  } catch (e) { console.error('[发布] 加载用户失败:', e); message.error('加载用户列表失败') }
  finally { pubUI.loadingUsers = false }
}

const applyUserFilter = () => {}

const toggleCurrentUser = (u) => {
  if (pub.userMode === 'target') {
    const s = new Set(pendingUserIds.value)
    if (s.has(u.id)) s.delete(u.id)
    else s.add(u.id)
    pendingUserIds.value = s
  } else {
    const s = new Set(pendingExcludeIds.value)
    if (s.has(u.id)) s.delete(u.id)
    else s.add(u.id)
    pendingExcludeIds.value = s
  }
}

const removeUser = (id) => { pub.selectedUsers = pub.selectedUsers.filter(u => u.id !== id) }
const removeExclude = (id) => { pub.selectedExcludes = pub.selectedExcludes.filter(u => u.id !== id) }
const isUserSelected = id => pub.selectedUsers.some(u => u.id === id)
const isExcludeSelected = id => pub.selectedExcludes.some(u => u.id === id)

const selectAllUsers = () => { pendingUserIds.value = new Set(currentFilteredList.value.map(u => u.id)) }
const clearAllUsers = () => { pendingUserIds.value = new Set() }
const selectAllExcludes = () => { pendingExcludeIds.value = new Set(currentFilteredList.value.map(u => u.id)) }
const clearAllExcludes = () => { pendingExcludeIds.value = new Set() }

const hasPublishInfo = computed(() => {
  const i = previewPublishInfo
  return i.departments.length > 0 || i.targetUsers.length > 0 || i.excludeUsers.length > 0 || !!i.publishTime || !!i.deadline
})

const formatPubTime = (val) => {
  if (!val) return ''
  try {
    const d = new Date(val)
    if (isNaN(d.getTime())) return String(val)
    const pad = n => String(n).padStart(2, '0')
    return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`
  } catch { return String(val) }
}

const isDeadlineNear = (val) => {
  if (!val) return false
  try {
    const deadline = new Date(val)
    const now = new Date()
    const diffMs = deadline.getTime() - now.getTime()
    const diffDays = diffMs / (1000 * 60 * 60 * 24)
    return diffDays <= 3 && diffDays > 0
  } catch { return false }
}

// --- 提交 ---
const confirmPublish = async () => {
  const deptIds = pub.selectedDepts.map(d => Number(d.id))
  const userIds = pub.selectedUsers.map(u => Number(u.id))
  const excludeUserIds = pub.selectedExcludes.map(u => Number(u.id))
  if (!deptIds.length && !userIds.length && !excludeUserIds.length) { message.warning('请至少选择一个部门或用户'); return }

  publishing.value = true
  try {
    const payload = { deptIds, userIds, excludeUserIds }
    if (pub.publishTime) {
      const d = new Date(pub.publishTime)
      payload.publishTime = `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}T${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}:00`
    }
    if (pub.deadline) {
      const d = new Date(pub.deadline)
      payload.deadline = `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}T${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}:00`
    }
    console.log('[发布] 提交数据:', JSON.stringify(payload))
    await examAPI.publishPaper(pubPaperId.value, payload)
    message.success('发布成功')
    closePublish()
    loadPapers()
  } catch (e) {
    console.error('[发布] 失败:', e)
    console.error('[发布] error类型:', typeof e, '构造器:', e?.constructor?.name)
    console.error('[发布] error.keys:', Object.keys(e || {}))
    console.error('[发布] error.message:', e?.message)
    console.error('[发布] error.code:', e?.code)
    console.error('[发布] error.status:', e?.status)
    const origErr = e?.originalError
    if (origErr) {
      console.error('[发布] originalError类型:', typeof origErr, '构造器:', origErr.constructor?.name)
      console.error('[发布] originalError.keys:', Object.keys(origErr))
      console.error('[发布] originalError.message:', origErr.message)
      console.error('[发布] originalError.response存在:', !!origErr.response)
      if (origErr.response) {
        console.error('[发布] response.status:', origErr.response.status)
        console.error('[发布] response.data:', origErr.response.data)
        console.error('[发布] response.statusText:', origErr.response.statusText)
        console.error('[发布] response.headers:', JSON.stringify(origErr.response.headers))
      }
      if (origErr.request) {
        console.error('[发布] request状态:', origErr.request.readyState)
        console.error('[发布] requestURL:', origErr.config?.url || origErr.request?.responseURL)
      }
    } else {
      console.warn('[发布] 无originalError属性')
    }

    const errMsg = origErr?.response?.data?.message 
      || origErr?.response?.data?.msg 
      || e.message 
      || origErr?.message
      || origErr?.response?.statusText
      || (e?.code === 'EXAM_NETWORK' ? '网络连接失败，请检查后端服务是否正常' : '未知错误')
    message.error('发布失败: ' + errMsg)
  }
  finally { publishing.value = false }
}

const revokePublishedPaper = async (id) => {
  try { await examAPI.revokePaper(id); message.success('已撤回'); loadPapers() }
  catch (e) { message.error('撤回失败: ' + (e.message || '')) }
}

const deletePublishedPaper = (id) => {
  Modal.confirm({
    title: '确认删除',
    content: '删除后不可恢复，确定要删除这份已发布的试卷吗？',
    okText: '删除',
    cancelText: '取消',
    okType: 'danger',
    async onOk() {
      await examAPI.deleteAdminPaper(id)
      message.success('已删除')
      loadPapers()
    },
  })
}

// ==================== Detail ====================
const previewRaw = ref(null)
const previewQuestions = ref([])
const previewPaperTitle = ref('')
const previewPublishInfo = reactive({
  departments: [],
  targetUsers: [],
  excludeUsers: [],
  publishTime: '',
  deadline: '',
  userScopeMode: ''
})

const previewTotalScore = computed(() =>
  previewQuestions.value.reduce((sum, q) => sum + (q.score || q.maxScore || q.max_score || q.question_score || 5), 0)
)

const fallbackDataType = computed(() => {
  const raw = previewRaw.value
  if (!raw) return ''
  const base = typeof raw
  if (Array.isArray(raw)) return `${base} [${raw.length}项]`
  return `${base} {${Object.keys(raw).length}}个字段`
})

const fallbackTopKeys = computed(() => {
  const raw = previewRaw.value
  if (!raw) return ''
  return Object.keys(raw).join(', ') || '(空对象)'
})

const viewPaperDetail = async (p) => {
  try {
    const paperId = getId(p)
    console.log('[查看试卷] 调用参数 - getId(p):', JSON.stringify(paperId), '| p对象keys:', Object.keys(p || {}).join(','))
    console.log('[查看试卷] p对象:', JSON.stringify(p).substring(0, 300))

    const data = await examAPI.getAdminPaperDetail(paperId)
    console.log('[查看试卷] 原始返回类型:', typeof data)
    console.log('[查看试卷] 原始返回 keys:', Array.isArray(data) ? `Array[${data.length}]` : Object.keys(data || {}).join(','))
    console.log('[查看试卷] 原始返回值:', JSON.stringify(data).substring(0, 1000))
    previewRaw.value = data
    previewPaperTitle.value = data?.title || data?.paper_title || data?.paperTitle || getPaperTitle(p) || '试卷详情'

    if (pub.deptList.length === 0) { await loadDeptList() }

    const _preloadUsersForView = async (d) => {
      if (pub.userList.length > 0) return
      const ids = (d.deptIds || d.dept_ids || []).map(Number).filter(Boolean)
      if (ids.length > 0) {
        pubUI.loadingUsers = true
        try {
          let allUsers = []
          for (const did of ids) {
            try {
              const dr = await departmentAPI.getDepartmentUsers(did)
              let users = Array.isArray(dr) ? dr : null
              if (!users && typeof dr === 'object') {
                for (const k of ['data', 'list', 'records', 'users', 'rows']) {
                  if (Array.isArray(dr[k])) { users = dr[k]; break }
                  if (dr.data && Array.isArray(dr.data[k])) { users = dr.data[k]; break }
                }
                if (!users && Array.isArray(dr.data)) users = dr.data
              }
              allUsers = allUsers.concat(users || [])
            } catch (e) { console.warn(`[查看-预加载] 部门${did}用户加载失败:`, e.message?.substring(0, 100)) }
          }
          allUsers = allUsers.filter((u, i, arr) => arr.findIndex(x => x.id === u.id) === i)
          pub.userList = allUsers.map(u => ({ id: u.id, name: u.realName || u.username || u.name || u.nickname || ('用户' + u.id), departmentName: u.departmentName || u.deptName || '' }))
          console.log(`[查看-预加载] 按 deptIds 加载 ${pub.userList.length} 个用户`)
        } catch (e) { console.error('[查看-预加载] 加载用户失败:', e) }
        finally { pubUI.loadingUsers = false }
      } else {
        try {
          const res = await userAPI.getUsers({ pageNum: 1, pageSize: 200 })
          let list = Array.isArray(res) ? res : null
          if (!list && typeof res === 'object' && res !== null) {
            for (const key of ['data', 'list', 'records', 'rows']) {
              if (Array.isArray(res[key])) { list = res[key]; break }
              if (res.data && Array.isArray(res.data[key])) { list = res.data[key]; break }
            }
            if (!list && Array.isArray(res.data)) list = res.data
          }
          if (!list) list = []
          pub.userList = list.map(u => ({ id: u.id, name: u.realName || u.username || u.name || u.nickname || ('用户' + u.id), departmentName: u.departmentName || u.deptName || '' }))
          console.log(`[查看-预加载] 全量加载 ${pub.userList.length} 个用户`)
        } catch (e) { console.warn('[查看-预加载] 全量用户加载失败:', e.message?.substring(0, 100)) }
      }
    }

    await _preloadUsersForView(data)

    const _extractPublishInfo = (d) => {
      const info = previewPublishInfo
      info.departments = []
      info.targetUsers = []
      info.excludeUsers = []
      info.publishTime = ''
      info.deadline = ''
      info.userScopeMode = ''
      if (!d || typeof d !== 'object') return

      console.log('[发布信息] 开始提取，顶层 keys:', Object.keys(d))

      const _resolveDeptNames = (idArr) => {
        if (!Array.isArray(idArr)) return []
        return idArr.map(id => {
          const numId = Number(id)
          if (pub.deptList.length > 0) {
            const found = pub.deptList.find(d => Number(d.id) === numId)
            if (found) return found.name
          }
          return String(id)
        }).filter(Boolean)
      }

      const _resolveUserNames = (idArr) => {
        if (!Array.isArray(idArr)) return []
        return idArr.map(id => {
          const numId = Number(id)
          if (pub.userList.length > 0) {
            const found = pub.userList.find(u => Number(u.id) === numId)
            if (found) return found.name
          }
          return String(id)
        }).filter(Boolean)
      }

      const deptIdArr = d.deptIds || d.dept_ids || d.departmentIds || d.department_ids
      if (Array.isArray(deptIdArr) && deptIdArr.length > 0) {
        info.departments = _resolveDeptNames(deptIdArr)
        console.log('[发布信息] 部门(deptIds):', deptIdArr, '→', info.departments)
      } else if (d.deptId != null || d.dept_id != null) {
        const singleId = d.deptId ?? d.dept_id
        info.departments = _resolveDeptNames([singleId])
        console.log('[发布信息] 部门(deptId单值):', singleId, '→', info.departments)
      }

      const deptsObjArr = d.publishDepartments || d.publish_departments || d.departmentNames || d.department_names ||
        d.departments || d.targetDepartments || d.scopeDepartments || d.deptList || d.dept_list
      if (info.departments.length === 0 && Array.isArray(deptsObjArr) && deptsObjArr.length > 0) {
        info.departments = deptsObjArr.map(x => typeof x === 'string' ? x : (x?.name || x?.departmentName || x?.deptName || String(x?.id || ''))).filter(Boolean)
        console.log('[发布信息] 部门(对象数组):', info.departments)
      }

      if (info.departments.length === 0) {
        for (const k of Object.keys(d)) {
          if (k.toLowerCase().includes('dept') && Array.isArray(d[k]) && d[k].length > 0) {
            info.departments = _resolveDeptNames(d[k])
            if (info.departments.length > 0) { console.log(`[发布信息] 部门(扫描 ${k}):`, info.departments); break }
            info.departments = d[k].map(x => typeof x === 'string' ? x : (x?.name || String(x?.id || x || ''))).filter(Boolean)
            if (info.departments.length > 0) { console.log(`[发布信息] 部门(扫描 ${k} fallback):`, info.departments); break }
          }
        }
      }

      const userIdArr = d.userIds || d.user_ids || d.targetUserIds || d.target_user_ids
      if (Array.isArray(userIdArr) && userIdArr.length > 0) {
        info.targetUsers = _resolveUserNames(userIdArr)
        info.userScopeMode = 'target'
        console.log('[发布信息] 目标用户(userIds):', userIdArr, '→', info.targetUsers)
      }

      const excludeIdArr = d.excludeUserIds || d.exclude_user_ids || d.excludedUserIds
      if (Array.isArray(excludeIdArr) && excludeIdArr.length > 0) {
        info.excludeUsers = _resolveUserNames(excludeIdArr)
        info.userScopeMode = 'exclude'
        console.log('[发布信息] 排除用户(excludeUserIds):', excludeIdArr, '→', info.excludeUsers)
      }

      if (info.targetUsers.length === 0) {
        const tUsersObj = d.targetUsers || d.target_users || d.selectedUsers || d.includeUsers || d.assignUsers || d.userList || d.user_list
        if (Array.isArray(tUsersObj) && tUsersObj.length > 0) {
          info.targetUsers = tUsersObj.map(x => typeof x === 'string' ? x : (x?.realName || x?.name || x?.username || String(x?.id || ''))).filter(Boolean)
          if (info.targetUsers.length > 0) { info.userScopeMode = 'target'; console.log('[发布信息] 目标用户(对象数组):', info.targetUsers) }
        }
      }

      if (info.excludeUsers.length === 0) {
        const eUsersObj = d.excludeUsers || d.excludedUsers || d.excludedUserNames
        if (Array.isArray(eUsersObj) && eUsersObj.length > 0) {
          info.excludeUsers = eUsersObj.map(x => typeof x === 'string' ? x : (x?.realName || x?.name || x?.username || String(x?.id || ''))).filter(Boolean)
          if (info.excludeUsers.length > 0) { info.userScopeMode = 'exclude'; console.log('[发布信息] 排除用户(对象数组):', info.excludeUsers) }
        }
      }

      info.publishTime = d.publishTime || d.publish_time || d.publishedAt || d.published_at || d.createTime || d.releaseTime || ''
      info.deadline = d.deadline || d.deadline_time || d.endTime || d.end_time || ''

      console.log('[发布信息] 最终结果:', JSON.stringify({ ...info }))
    }
    _extractPublishInfo(data)

    let rawQuestions = []

    if (Array.isArray(data)) {
      rawQuestions = data
      console.log('[查看试卷] 路径A: 返回值本身是数组')
    } else if (data && typeof data === 'object') {
      const candidatePaths = [
        { key: 'questions', val: data.questions },
        { key: 'questionList', val: data.questionList },
        { key: 'paperQuestions', val: data.paperQuestions },
        { key: 'items', val: data.items },
        { key: 'question_list', val: data.question_list },
        { key: 'questionList', val: data.questionList },
        { key: 'content(数组)', val: Array.isArray(data.content) ? data.content : null },
        { key: 'data', val: Array.isArray(data.data) ? data.data : null },
        { key: 'data.questions', val: data.data?.questions },
        { key: 'data.questionList', val: data.data?.questionList },
        { key: 'data.items', val: data.data?.items },
        { key: 'body', val: Array.isArray(data.body) ? data.body : null },
        { key: 'paperContent', val: data.paperContent },
        { key: 'paper_content', val: data.paper_content },
        { key: 'questionData', val: data.questionData },
        { key: 'list', val: data.list }
      ]

      for (const cp of candidatePaths) {
        if (Array.isArray(cp.val) && cp.val.length > 0) {
          rawQuestions = cp.val
          console.log(`[查看试卷] 命中路径: ${cp.key}, 题数: ${cp.val.length}`)
          break
        }
      }

      if (rawQuestions.length === 0) {
        for (const cp of candidatePaths) {
          if (Array.isArray(cp.val)) {
            rawQuestions = cp.val
            console.log(`[查看试卷] 兜底路径(空数组): ${cp.key}`)
            break
          }
        }
      }

      if (rawQuestions.length === 0) {
        console.warn('[查看试卷] ⚠️ 所有已知路径均未找到题目数组!')
        console.warn('[查看试卷] 可用字段:', Object.keys(data).map(k => `${k}:${typeof data[k]}`).join(' | '))
      }

      const needCount = Number(data.questionCount || data.question_count || data.question_count || 0)
      if (rawQuestions.length === 0 && needCount > 0) {
        console.log(`[查看试卷] 题目为空但 questionCount=${needCount}，尝试从题库拉取...`)
        try {
          const qRes = await questionAPI.pageQuestions({ pageNum: 1, pageSize: Math.min(needCount, 50) })
          const qBody = qRes?.data || {}
          const qInner = qBody?.data
          let fetched = qInner?.list || qInner?.records || qInner?.questions ||
            (Array.isArray(qInner) ? qInner : null) ||
            qBody?.list || qBody?.records || qBody?.questions || []
          if (Array.isArray(fetched) && fetched.length > 0) {
            rawQuestions = fetched.slice(0, needCount)
            console.log(`[查看试卷] 从题库补充到 ${rawQuestions.length} 道题目`)
          } else {
            console.warn('[查看试卷] 从题库也未获取到题目')
          }
        } catch (fetchErr) {
          console.warn('[查看试卷] 从题库获取题目失败:', fetchErr.message?.substring(0, 100))
        }
      }
    }

    if (!Array.isArray(rawQuestions)) rawQuestions = []

    console.log(`[查看试卷] 最终 rawQuestions 数量: ${rawQuestions.length}`)

    const questions = rawQuestions.map((q, idx) => {
      let contentObj = {}

      try {
        if (q.content != null) {
          if (typeof q.content === 'string' && q.content.trim().startsWith('{')) {
            contentObj = JSON.parse(q.content)
          } else if (typeof q.content === 'object') {
            contentObj = q.content
          }
        }
      } catch (e) {
        console.warn(`[查看试卷] 题${idx} content解析失败:`, e.message)
      }

      if (Object.keys(contentObj).length === 0 && q.data != null && typeof q.data === 'object' && !Array.isArray(q.data)) {
        contentObj = q.data
      }

      if (Object.keys(contentObj).length === 0) {
        const hasDirectFields = q.stem || q.options || q.answer || q.question_stem
        if (hasDirectFields) {
          contentObj = { ...q }
        }
      }

      const rawStem = contentObj.stem ||
                      (contentObj.data && contentObj.data.stem) ||
                      q.stem || q.question_stem || q.questionContent ||
                      q.question_content ||
                      (typeof q.content === 'string' ? q.content : '') ||
                      (q.data && q.data.stem) || ''

      let rawOptions = contentObj.options ||
                       (contentObj.data && contentObj.data.options) ||
                       q.options ||
                       (q.data && q.data.options) || []

      if (rawOptions.length === 0 && contentObj.data && Array.isArray(contentObj.data)) {
        rawOptions = contentObj.data
      }

      let options = []
      if (Array.isArray(rawOptions) && rawOptions.length > 0) {
        if (rawOptions[0] && typeof rawOptions[0] === 'object' && rawOptions[0].key != null) {
          options = rawOptions.map(opt => ({
            key: String(opt.key),
            text: opt.content || opt.text || opt.value || ''
          }))
        } else {
          const optionKeys = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
          options = rawOptions.map((opt, oi) => ({
            key: optionKeys[oi] || String.fromCharCode(65 + oi),
            text: typeof opt === 'object' ? (opt.content || opt.text || opt.value || '') : String(opt)
          }))
        }
      }

      const answer = q.answer !== undefined ? String(q.answer) :
                     (contentObj.answer !== undefined ? String(contentObj.answer) :
                     ((contentObj.data && contentObj.data.answer) !== undefined ? String(contentObj.data.answer) :
                     (q.data && q.data.answer !== undefined ? String(q.data.answer) :
                     (q.correctAnswer || q.correct_answer || q.standardAnswer || ''))))

      const explanation = contentObj.explanation || (contentObj.data && contentObj.data.explanation) || q.explanation || ''
      const rawType = String(q.questionType || q.type || q.question_type || '').toLowerCase()

      return {
        questionId: q.questionId || q.question_id || q.id,
        questionType: rawType,
        _typeLabel: formatQType(rawType),
        stem: rawStem || '（无题干）',
        options: options,
        answer: answer,
        explanation: explanation,
        score: Number(q.score || q.maxScore || q.max_score || q.question_score || 5),
        difficulty: q.difficulty != null ? Number(q.difficulty) : null
      }
    })

    console.log(`[查看试卷] 解析到 ${questions.length} 道题目`)
    if (questions.length > 0) {
      console.log('[查看试卷] 首题字段:', Object.keys(questions[0]))
      console.log('[查看试卷] 首题stem:', (questions[0].stem || '').substring(0, 100))
      console.log('[查看试卷] 首题options:', JSON.stringify(questions[0].options).substring(0, 200))
      console.log('[查看试卷] 首题answer:', questions[0].answer)
    } else {
      console.warn('[查看试卷] ⚠️ 解析后仍为空! 将显示原始数据兜底')
    }
    previewQuestions.value = questions
  } catch (e) {
    console.error('[查看试卷] 失败:', e)
    message.error('获取详情失败: ' + (e.message || ''))
  }
}
const clearPreview = () => {
  previewRaw.value = null
  previewQuestions.value = []
  previewPaperTitle.value = ''
  Object.assign(previewPublishInfo, { departments: [], targetUsers: [], excludeUsers: [], publishTime: '', deadline: '', userScopeMode: '' })
}

const getDetailStem = q => {
  if (!q) return '（无题干）'
  if (q.stem && typeof q.stem === 'string') return q.stem
  if (q.stem && typeof q.stem === 'object') return JSON.stringify(q.stem)
  return q.questionContent || q.content || '（无题干）'
}

const getDetailOptions = q => {
  if (!q) return []
  const opts = q.options
  if (Array.isArray(opts) && opts.length > 0) return opts
  return []
}

const getDetailAnswer = q => {
  if (!q) return ''
  if (q.answer !== undefined && q.answer !== null && q.answer !== '') return String(q.answer)
  return ''
}

const isChoiceQuestion = q => {
  const t = String(q?.questionType || q?.type || q?.question_type || '').toLowerCase()
  return ['single_choice', 'multiple_choice', 'choice', 'single', 'multiple'].includes(t)
}

const isTextQuestion = q => {
  const t = String(q?.questionType || q?.type || q?.question_type || '').toLowerCase()
  return ['fill_blank', 'subjective', 'fill', 'essay'].includes(t)
}

const isOptionAnswer = (opt, q) => {
  if (!q || !opt) return false
  const ans = String(q.answer || '').toLowerCase().trim()
  if (!ans) return false
  const optKey = String(opt.key || opt.label || '').toLowerCase().trim()
  const optText = String(opt.text || opt.content || '').toLowerCase().trim()
  if (ans === optKey) return true
  if (ans.includes(optKey) && optKey.length <= 2) return true
  if (ans.includes(optText) && optText.length > 0) return true
  return false
}

const getTfAnswerText = q => {
  if (!q) return '未设置答案'
  const ans = String(q.answer || '').toUpperCase().trim()
  if (['T', 'TRUE', '1', '正确', '对'].includes(ans)) return '正确 ✓'
  if (['F', 'FALSE', '0', '错误', '错'].includes(ans)) return '错误 ✗'
  return ans || '未设置答案'
}

const getDifficultyLabel = d => ({ 1: '简单', 2: '中等', 3: '较难', 4: '困难', 5: '极难' })[d] || `${d}`

// ==================== Tab ====================
const switchTab = (key) => {
  activeTab.value = key
  if (key !== 'compose') loadPapers()
}

onMounted(() => { loadPapers() })
</script>

<style scoped>
/* ========== Panel ========== */
.paper-management-panel {
  flex: 1; display: flex; flex-direction: column;
  padding: 16px; overflow-y: auto; overflow-x: hidden; min-width: 0; gap: 14px;
}

/* ========== Tab Nav ========== */
.tab-nav {
  display: flex; gap: 4px; background: #F8F9FA;
  border-radius: 10px; padding: 4px; border: 1px solid #E9ECEF;
}
.tab-btn {
  flex: 1; display: flex; align-items: center; justify-content: center; gap: 7px;
  padding: 10px 16px; border: none; background: transparent; border-radius: 8px;
  font-size: 13.5px; font-weight: 500; color: #868E96; cursor: pointer;
  transition: all 0.18s ease; white-space: nowrap;
}
.tab-btn:hover:not(.active) { color: #495057; background: rgba(0,0,0,.03); }
.tab-btn.active { background: #fff; color: #212529; font-weight: 600; box-shadow: 0 1px 3px rgba(0,0,0,.08); }
.tab-icon { display: flex; align-items: center; opacity: .7; }
.tab-btn.active .tab-icon { opacity: 1; }
.tab-badge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 20px; height: 20px; padding: 0 6px;
  background: #E9ECEF; color: #495057; border-radius: 10px; font-size: 11px; font-weight: 700;
}
.tab-btn.active .tab-badge { background: #212529; color: #fff; }
.tab-badge.published { background: #D3F9D8; color: #2B8A3E; }
.tab-btn.active .tab-badge.published { background: #2B8A3E; color: #fff; }
.tab-body { flex: 1; display: flex; flex-direction: column; min-height: 180px; }

/* ========== Compose 两栏 ========== */
.compose-layout { display: flex; gap: 16px; flex: 1; min-height: 0; }
.compose-sidebar {
  width: 360px; min-width: 300px; flex-shrink: 0;
  display: flex; flex-direction: column; gap: 14px;
  background: #fff; border: 1px solid #E9ECEF; border-radius: 10px;
  padding: 18px 20px; box-shadow: 0 1px 3px rgba(0,0,0,.03);
  overflow-y: auto;
}
.compose-result {
  flex: 1; min-width: 0;
  background: #fff; border: 1px solid #E9ECEF; border-radius: 10px;
  box-shadow: 0 1px 3px rgba(0,0,0,.03);
  display: flex; flex-direction: column; overflow: hidden;
}

@media (max-width: 780px) {
  .compose-layout { flex-direction: column; }
  .compose-sidebar { width: 100%; }
  .compose-result { min-height: 300px; }
}

/* ========== Config Sections ========== */
.config-section { display: flex; flex-direction: column; gap: 10px; }
.section-header { display: flex; align-items: center; gap: 8px; }
.section-header h4 { margin: 0; font-size: 13px; font-weight: 600; color: #212529; }
.step-badge {
  width: 22px; height: 22px; border-radius: 50%; background: #212529; color: #fff;
  display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 700; flex-shrink: 0;
}
.section-desc { margin: 0; font-size: 11.5px; color: #ADB5BD; }
.link-btn { background: none; border: none; color: #1C7ED6; font-size: 12px; cursor: pointer; padding: 0; font-weight: 500; }
.link-btn:hover { text-decoration: underline; }
.link-btn.muted { color: #868E96; }
.form-row { display: flex; align-items: center; justify-content: space-between; }
.mini-label { font-size: 12px; color: #6C757D; font-weight: 500; }
.difficulty-bar { display: flex; gap: 3px; }
.diff-btn {
  padding: 4px 12px; border: 1.5px solid #DEE2E6; border-radius: 6px;
  background: #fff; color: #495057; font-size: 12px; cursor: pointer; font-weight: 500;
  transition: all .15s;
}
.diff-btn.active { background: #212529; border-color: #212529; color: #fff; }
.diff-btn:hover:not(.active) { border-color: #212529; color: #212529; }

/* Toggle */
.toggle-switch { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; }
.toggle-switch input { display: none; }
.toggle-track { width: 40px; height: 22px; background: #DEE2E6; border-radius: 11px; position: relative; transition: background .2s; }
.toggle-thumb { position: absolute; width: 18px; height: 18px; background: #fff; border-radius: 50%; top: 2px; left: 2px; transition: left .2s; box-shadow: 0 1px 3px rgba(0,0,0,.12); }
.toggle-switch input:checked + .toggle-track { background: #212529; }
.toggle-switch input:checked + .toggle-track .toggle-thumb { left: 20px; }
.toggle-text { font-size: 12px; color: #495057; font-weight: 500; }

/* Type grid */
.type-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 8px; }
.type-cell {
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  padding: 10px 6px; background: #F8F9FA; border: 1px solid #E9ECEF; border-radius: 8px;
  transition: border-color .15s;
}
.type-cell:hover { border-color: #CED4DA; }
.type-name { font-size: 12px; color: #495057; font-weight: 500; }
.count-row { display: flex; align-items: center; gap: 8px; }
.ctrl {
  width: 26px; height: 26px; border: 1.5px solid #DEE2E6; border-radius: 6px;
  background: #fff; color: #212529; font-size: 15px; cursor: pointer;
  display: flex; align-items: center; justify-content: center; line-height: 1; transition: all .15s;
}
.ctrl:hover { border-color: #212529; background: #F8F9FA; }
.count { font-size: 15px; font-weight: 700; color: #212529; min-width: 20px; text-align: center; }
.total-row { text-align: center; font-size: 12.5px; color: #6C757D; padding: 8px; background: #F8F9FA; border-radius: 6px; }
.total-row strong { color: #212529; }

.generate-btn {
  margin-top: auto; width: 100%; padding: 12px; border: none; border-radius: 8px;
  background: #212529; color: #fff; font-size: 14px; font-weight: 600; cursor: pointer;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  transition: background .15s;
}
.generate-btn:disabled { opacity: .35; cursor: not-allowed; }
.generate-btn:hover:not(:disabled) { background: #000; }

/* ========== File Picker ========== */
.selected-files { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 4px; }
.file-chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 10px; background: #F1F3F5; border: 1px solid #DEE2E6; border-radius: 6px;
  font-size: 12px; color: #495057; font-weight: 500;
}
.chip-remove {
  background: none; border: none; font-size: 15px; cursor: pointer;
  color: #ADB5BD; line-height: 1; padding: 0;
}
.chip-remove:hover { color: #C92A2A; }

.file-picker { display: flex; flex-direction: column; gap: 8px; }
.file-select-bar { width: 100%; }
.file-select-dropdown {
  width: 100%; padding: 9px 10px; border: 1.5px solid #DEE2E6; border-radius: 6px;
  font-size: 13px; box-sizing: border-box; transition: border-color .15s;
  background: #fff; color: #495057; cursor: pointer;
}
.file-select-dropdown:focus { outline: none; border-color: #212529; box-shadow: 0 0 0 2px rgba(33,37,41,0.1); }
.file-list { max-height: 160px; overflow-y: auto; border: 1px solid #E9ECEF; border-radius: 6px; }
.file-item {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 7px 10px; font-size: 12.5px; color: #495057; background: #F4FCE3;
}
.file-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
.file-list-loading, .file-list-empty, .file-list-hint { font-size: 12px; color: #ADB5BD; text-align: center; padding: 20px; }

/* ========== Result Panel ========== */
.result-empty {
  flex: 1; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 14px; color: #ADB5BD; text-align: center;
  font-size: 13px; line-height: 1.7; padding: 40px;
}
.result-loading {
  flex: 1; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 14px; color: #6C757D; font-size: 13px;
}
.result-content { flex: 1; display: flex; flex-direction: column; padding: 18px 20px; overflow-y: auto; }
.paper-preview-card {
  background: #FFFFFF;
  border: 2px solid #228BE6;
  border-radius: 10px;
  overflow: hidden;
  box-shadow: 0 2px 12px rgba(34, 139, 230, 0.1);
}
.paper-preview-header {
  background: linear-gradient(135deg, #E7F5FF 0%, #D0EBFF 100%);
  padding: 14px 18px;
  border-bottom: 1px solid #74C0FC;
}
.paper-preview-title {
  font-size: 15px;
  font-weight: 700;
  color: #1864AB;
  margin: 0 0 6px 0;
}
.paper-preview-stats {
  display: flex;
  gap: 16px;
}
.paper-stat {
  font-size: 12px;
  color: #1864AB;
  font-weight: 500;
  background: rgba(25, 113, 194, 0.08);
  padding: 2px 8px;
  border-radius: 4px;
}

.question-list { display: flex; flex-direction: column; gap: 7px; flex: 1; overflow-y: auto; }
.q-card { padding: 10px 12px; border: 1px solid #E9ECEF; border-radius: 7px; transition: border-color .15s; }
.q-card:hover { border-color: #CED4DA; }
.q-header { display: flex; align-items: center; gap: 7px; margin-bottom: 5px; }
.q-num {
  width: 22px; height: 22px; border-radius: 50%; background: #F1F3F5;
  display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 700; color: #495057; flex-shrink: 0;
}
.q-type { font-size: 10.5px; color: #6C757D; background: #F1F3F5; padding: 1px 7px; border-radius: 3px; font-weight: 500; }
.q-diff { font-size: 10.5px; color: #ADB5BD; margin-left: auto; }
.q-stem { font-size: 12.5px; color: #212529; line-height: 1.6; }

.save-area { margin-top: 14px; padding-top: 14px; border-top: 1px solid #E9ECEF; text-align: center; }
.save-btn {
  width: 100%; padding: 10px; border: none; border-radius: 7px;
  background: #212529; color: #fff; font-size: 13.5px; font-weight: 500; cursor: pointer;
  transition: background .15s;
}
.save-btn:hover { background: #000; }
.save-note { margin-top: 6px; font-size: 11.5px; color: #ADB5BD; }

/* ========== Paper List (Drafts & Published) ========== */
.state-msg { display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 60px 20px; color: #6C757D; font-size: 13px; }
.state-empty { display: flex; flex-direction: column; align-items: center; gap: 10px; padding: 50px 20px; text-align: center; }
.state-empty h4 { margin: 0; color: #495057; font-size: 14px; }
.state-empty p { margin: 0; color: #ADB5BD; font-size: 12.5px; }
.paper-list { display: flex; flex-direction: column; gap: 8px; }

.paper-row {
  display: flex; justify-content: space-between; align-items: center;
  padding: 14px 18px; background: #fff; border: 1px solid #E9ECEF; border-radius: 9px;
  transition: all .18s;
}
.paper-row:hover { border-color: #212529; box-shadow: 0 2px 10px rgba(0,0,0,.05); transform: translateY(-1px); }
.paper-info { flex: 1; min-width: 0; }
.paper-title-row { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; }
.paper-name { font-size: 13.5px; font-weight: 600; color: #212529; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.paper-meta { display: flex; gap: 12px; }
.badge { padding: 2px 8px; border-radius: 4px; font-size: 10.5px; font-weight: 600; flex-shrink: 0; }
.badge.draft { background: #FFF9DB; color: #E67700; border: 1px solid #FFE066; }
.badge.published { background: #D3F9D8; color: #2B8A3E; border: 1px solid #B2F2BB; }
.meta { font-size: 11.5px; color: #868E96; }
.paper-ops { display: flex; align-items: center; gap: 6px; flex-shrink: 0; margin-left: 14px; }

.op-link { background: none; border: none; color: #6C757D; font-size: 12px; cursor: pointer; padding: 4px 7px; border-radius: 4px; font-weight: 500; }
.op-link:hover { background: #F1F3F5; color: #212529; }
.op-primary {
  padding: 4px 12px; border: none; border-radius: 5px; background: #212529; color: #fff;
  font-size: 11.5px; font-weight: 500; cursor: pointer; transition: background .15s;
}
.op-primary:hover { background: #000; }
.op-danger { background: none; border: none; color: #C92A2A; font-size: 12px; cursor: pointer; padding: 4px 7px; border-radius: 4px; font-weight: 500; }
.op-danger:hover { background: #FFF5F5; }
.op-delete { background: none; border: none; color: #862E9C; font-size: 12px; cursor: pointer; padding: 4px 7px; border-radius: 4px; font-weight: 500; }
.op-delete:hover { background: #F8F0FA; }

/* ========== Shared ========== */
.loading-spinner {
  width: 30px; height: 30px; border: 3px solid #E9ECEF;
  border-top-color: #212529; border-radius: 50%; animation: spin .65s linear infinite;
}
.spinner-sm {
  width: 16px; height: 16px; border: 2px solid rgba(255,255,255,.3);
  border-top-color: #fff; border-radius: 50%; animation: spin .65s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ========== Modal ========== */
.overlay {
  position: fixed; top: 0; left: 0; right: 0; bottom: 0;
  background: rgba(0,0,0,.42); display: flex; align-items: center; justify-content: center; z-index: 2000;
}
.modal { background: #fff; border-radius: 10px; width: 520px; max-width: 93vw; max-height: 80vh; display: flex; flex-direction: column; box-shadow: 0 12px 40px rgba(0,0,0,.15); }
.modal-lg { width: 640px; }
.modal-hdr { display: flex; justify-content: space-between; align-items: center; padding: 14px 18px; border-bottom: 1px solid #E9ECEF; }
.modal-hdr h3 { margin: 0; font-size: 14px; font-weight: 600; color: #212529; }
.close-x { width: 26px; height: 26px; border: none; background: #F1F3F5; border-radius: 5px; font-size: 17px; cursor: pointer; color: #868E96; display: flex; align-items: center; justify-content: center; }
.close-x:hover { background: #E9ECEF; color: #212529; }
.modal-body { padding: 16px 18px; overflow-y: auto; flex: 1; display: flex; flex-direction: column; gap: 14px; }
.modal-ftr { display: flex; justify-content: flex-end; gap: 8px; padding: 12px 18px; border-top: 1px solid #E9ECEF; }
.json-block { font-size: 11.5px; white-space: pre-wrap; word-break: break-all; margin: 0; color: #495057; line-height: 1.6; }

/* ========== Detail Question List ========== */
.publish-info-section {
  background: #F8F9FA; border: 1px solid #E9ECEF; border-radius: 7px;
  padding: 14px 16px; margin-bottom: 4px;
}
.pub-info-title {
  font-size: 12.5px; font-weight: 600; color: #212529;
  margin-bottom: 10px; padding-bottom: 6px;
  border-bottom: 1px solid #DEE2E6;
}
.pub-info-grid { display: flex; flex-direction: column; gap: 8px; }
.pub-info-row {
  display: flex; align-items: flex-start; gap: 12px;
  font-size: 12.5px; line-height: 1.5;
}
.pub-info-label {
  color: #868E96; font-size: 12px; white-space: nowrap;
  min-width: 56px; padding-top: 1px;
}
.pub-info-value {
  display: flex; flex-wrap: wrap; gap: 5px; flex: 1;
  color: #495057;
}
.pub-info-time {
  font-family: 'SF Mono', Consolas, 'Courier New', monospace;
  font-size: 12px; color: #495057;
}
.pi-tag {
  display: inline-block; padding: 2px 8px; border-radius: 4px;
  font-size: 11.5px; line-height: 1.6; white-space: nowrap;
}
.pi-dept { background: #E9ECEF; color: #495057; }
.pi-user { background: #D3F9D8; color: #2B8A3E; }
.pi-exclude { background: #FFF0ED; color: #C92A2A; }
.pi-all { background: #DEE2E6; color: #868E96; font-style: italic; }
.pi-deadline-near { color: #C92A2A; font-weight: 600; }

.paper-detail-summary {
  display: flex; gap: 20px; padding: 12px 16px;
  background: #F8F9FA; border: 1px solid #E9ECEF; border-radius: 7px;
  margin-bottom: 14px;
}
.summary-item { font-size: 12.5px; color: #495057; }
.summary-item strong { color: #212529; }

.detail-question-list { display: flex; flex-direction: column; gap: 12px; }
.detail-q-card {
  border: 1px solid #E9ECEF; border-radius: 8px;
  padding: 14px 16px; background: #fff;
  transition: box-shadow .15s, border-color .15s;
}
.detail-q-card:hover { box-shadow: 0 2px 10px rgba(0,0,0,.06); border-color: #CED4DA; }
.dqc-header {
  display: flex; align-items: center; gap: 8px; margin-bottom: 8px;
}
.dqc-num {
  width: 24px; height: 24px; border-radius: 50%; background: #228BE6; color: #fff;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 700; flex-shrink: 0;
}
.dqc-type {
  font-size: 10.5px; color: #6C757D; background: #F1F3F5;
  padding: 2px 8px; border-radius: 3px; font-weight: 500;
}
.dqc-score {
  margin-left: auto; font-size: 12px; color: #E67700; font-weight: 600;
}
.dqc-stem {
  font-size: 13px; color: #212529; line-height: 1.65; margin-bottom: 10px;
}

.dqc-options { display: flex; flex-direction: column; gap: 6px; margin-bottom: 8px; }
.dqc-option {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 12px; border: 1.5px solid #E9ECEF; border-radius: 6px;
  transition: all .15s; font-size: 12.5px; color: #495057;
}
.dqc-option:hover { border-color: #CED4DA; background: #F8F9FA; }
.dqc-option.is-correct-opt { border-color: #B2F2BB; background: #EBFCF1; }
.dqc-opt-key {
  width: 22px; height: 22px; border-radius: 50%; background: #E9ECEF;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 700; color: #495057; flex-shrink: 0;
}
.dqc-option.is-correct-opt .dqc-opt-key { background: #2B8A3E; color: #fff; }
.dqc-opt-text { flex: 1; line-height: 1.5; }
.dqc-opt-tag {
  font-size: 10.5px; padding: 2px 8px; border-radius: 4px; font-weight: 600; flex-shrink: 0;
}
.dqc-opt-tag.answer { background: #D3F9D8; color: #2B8A3E; }

.dqc-no-options {
  padding: 8px 12px; font-size: 12px; color: #ADB5BD;
  background: #F8F9FA; border-radius: 6px; margin-bottom: 8px;
}

.dqc-tf-answer, .dqc-text-answer {
  display: flex; align-items: baseline; gap: 6px;
  padding: 8px 12px; background: #F8F9FA; border-radius: 6px; margin-bottom: 8px;
  font-size: 12.5px;
}
.answer-label { color: #868E96; font-weight: 500; white-space: nowrap; }
.answer-value { color: #212529; font-weight: 500; }

.dqc-diff {
  font-size: 11px; color: #ADB5BD; margin-top: 6px;
  padding-top: 6px; border-top: 1px dashed #E9ECEF;
}

.dqc-explanation {
  margin-top: 8px; padding: 10px 12px;
  background: #FFF9DB; border: 1px solid #FFE066; border-radius: 6px;
  font-size: 12px; line-height: 1.65;
}
.explanation-label { font-weight: 600; color: #495057; margin-right: 6px; }
.explanation-text { color: #495057; }

.detail-empty-state {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  padding: 50px 20px; text-align: center; color: #ADB5BD;
}
.detail-empty-state p { margin: 0; font-size: 13px; }

.detail-fallback {
  border: 1.5px solid #FFE066; border-radius: 8px;
  background: #FFFBEB; overflow: hidden;
}
.fallback-header {
  padding: 14px 16px; background: #FFF9DB;
  border-bottom: 1.5px solid #FFE066;
  display: flex; flex-direction: column; gap: 4px;
}
.fallback-icon {
  width: 20px; height: 20px; border-radius: 50%;
  background: #FFF0ED; color: #C92A2A;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 700; flex-shrink: 0;
}
.fallback-title { font-size: 13px; font-weight: 600; color: #C92A2A; }
.fallback-hint { font-size: 11.5px; color: #868E96; }
.fallback-fields { padding: 12px 16px; display: flex; flex-direction: column; gap: 8px; }
.ff-row { display: flex; align-items: baseline; gap: 10px; font-size: 12px; }
.ff-label { color: #868E96; min-width: 60px; flex-shrink: 0; }
.ff-value { color: #212529; font-weight: 500; word-break: break-all; }
.ff-code {
  font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
  font-size: 11px; background: rgba(0,0,0,.04); padding: 2px 6px; border-radius: 3px;
}
.raw-data-details { margin: 0; }
.raw-data-details summary {
  padding: 10px 16px; cursor: pointer; font-size: 12px; color: #C92A2A;
  font-weight: 500; background: rgba(201,42,42,.03);
}
.raw-data-details summary:hover { background: rgba(201,42,42,.06); }
.raw-data-details[open] summary { border-bottom: 1px solid #E9ECEF; }
.field-label { display: block; font-size: 11.5px; font-weight: 600; color: #868E96; text-transform: uppercase; letter-spacing: .03em; margin-bottom: 2px; }
.field-input {
  width: 100%; padding: 8px 11px; border: 1.5px solid #DEE2E6; border-radius: 6px;
  font-size: 13px; box-sizing: border-box; transition: border-color .15s;
}
.field-input:focus { outline: none; border-color: #212529; box-shadow: 0 0 0 3px rgba(0,0,0,.04); }

.btn { border-radius: 6px; padding: 0 14px; height: 30px; font-size: 12px; font-weight: 500; border: none; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; transition: all .15s; }
.btn-ghost { background: #fff; color: #495057; border: 1px solid #DEE2E6; }
.btn-ghost:hover { background: #F8F9FA; }
.btn-dark { background: #212529; color: #fff; }
.btn-dark:hover:not(:disabled) { background: #000; }
.btn-dark:disabled { opacity: .4; cursor: not-allowed; }

/* ========== Publish Dialog (Two-Column) ========== */
.publish-modal { max-width: 660px; width: 95vw; max-height: 90vh; }
.pub-two-col {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px 24px;
  overflow-y: auto;
  max-height: calc(90vh - 120px);
  padding-right: 4px;
}
.pub-col-left, .pub-col-right {
  display: flex; flex-direction: column; gap: 14px;
}
.pub-field { display: flex; flex-direction: column; gap: 5px; }
.pub-label {
  font-size: 12.5px; font-weight: 600; color: #212529;
}
.field-hint { font-size: 11px; color: #868E96; margin-top: -2px; }

/* Compact bar */
.compact-bar {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 12px; border: 1px solid #DEE2E6; border-radius: 8px;
  cursor: pointer; transition: all .15s; min-height: 38px;
  user-select: none; background: #fff;
}
.compact-bar:hover { border-color: #ADB5BD; background: #F8F9FA; }
.compact-bar.disabled { opacity: .45; pointer-events: none; background: #F1F3F5; }

.compact-placeholder { font-size: 12px; color: #ADB5BD; flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.compact-chips { display: flex; flex-wrap: wrap; gap: 4px; flex: 1; min-width: 0; }
.sel-chip {
  display: inline-flex; align-items: center; gap: 3px;
  padding: 2px 7px; border-radius: 4px; font-size: 11px; font-weight: 500;
  max-width: 120px; overflow: hidden;
}
.sel-chip > span:not(.chip-x) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dept-chip { background: #E9ECEF; color: #495057; }
.user-chip { background: #E9ECEF; color: #495057; }
.exclude-chip { background: #FFF0ED; color: #C92A2A; }
.chip-x {
  background: none; border: none; cursor: pointer; font-size: 12px;
  line-height: 1; padding: 0 1px; opacity: .45; flex-shrink: 0; color: inherit;
}
.chip-x:hover { opacity: 1; }

.compact-btn {
  padding: 4px 10px; border: 1px solid #DEE2E6; border-radius: 6px;
  background: #fff; color: #212529; font-size: 11px; font-weight: 500;
  cursor: pointer; white-space: nowrap; transition: all .15s;
  flex-shrink: 0;
}
.compact-btn:hover { background: #212529; border-color: #212529; color: #fff; }
.compact-count {
  font-size: 10.5px; color: #868E96; font-weight: 600;
  white-space: nowrap; padding: 2px 6px; background: #F1F3F5; border-radius: 3px;
}

/* Mode switch (Radio) */
.mode-switch {
  display: flex; gap: 6px;
}
.mode-option {
  flex: 1; display: flex; align-items: center; justify-content: center;
  padding: 9px 10px; border: 1px solid #DEE2E6; border-radius: 8px;
  cursor: pointer; font-size: 12px; color: #495057; transition: all .15s;
  user-select: none;
}
.mode-option input[type="radio"] { display: none; }
.mode-option:hover:not(.disabled) { border-color: #ADB5BD; background: #F8F9FA; }
.mode-option.active {
  border-color: #212529; background: #212529; color: #fff; font-weight: 600;
  box-shadow: 0 1px 3px rgba(0,0,0,.08);
}
.mode-option.disabled {
  opacity: .4; cursor: not-allowed; background: #F8F9FA;
}

.mode-hint.warn {
  display: block; margin-top: 4px; font-size: 11px;
  color: #C92A2A; background: #FFF0ED; padding: 5px 8px;
  border-radius: 6px; line-height: 1.4;
}

/* Picker Modal (Secondary) */
.picker-modal {
  width: 480px; max-width: 92vw; max-height: 80vh;
  border-radius: 10px; background: #fff;
  box-shadow: 0 16px 48px rgba(0,0,0,.15);
  display: flex; flex-direction: column;
}
.picker-hdr {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 18px; border-bottom: 1px solid #E9ECEF;
  flex-shrink: 0;
}
.picker-hdr h4 { margin: 0; font-size: 14px; font-weight: 700; color: #212529; }
.picker-body { flex: 1; overflow-y: auto; display: flex; flex-direction: column; min-height: 200px; }
.picker-ftr {
  display: flex; justify-content: flex-end; gap: 8px;
  padding: 12px 18px; border-top: 1px solid #E9ECEF;
  flex-shrink: 0;
}

/* Picker toolbar */
.picker-toolbar {
  display: flex; align-items: center; gap: 6px;
  padding: 8px 14px; border-bottom: 1px solid #E9ECEF;
  background: #F8F9FA; flex-shrink: 0;
}
.picker-search {
  flex: 1; padding: 5px 10px; border: 1px solid #DEE2E6; border-radius: 6px;
  font-size: 12px; outline: none; color: #212529;
}
.picker-search:focus { border-color: #212529; box-shadow: 0 0 0 2px rgba(33,37,41,.08); }
.picker-stat {
  font-size: 11px; color: #868E96; white-space: nowrap; font-weight: 500;
}

/* Picker list */
.picker-list {
  flex: 1; overflow-y: auto; padding: 4px 0;
}
.picker-list-tall { max-height: 360px; }
.picker-item {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 14px; cursor: pointer; font-size: 12.5px; color: #495057;
  transition: background .1s;
}
.picker-item:hover { background: #F8F9FA; }
.picker-item.checked { background: #F1F3F5; }
.picker-item input[type="checkbox"] {
  accent-color: #212529; width: 15px; height: 15px; cursor: pointer; flex-shrink: 0;
}
.item-name { flex: 1; line-height: 1.35; }
.item-dept { font-size: 10px; color: #ADB5BD; white-space: nowrap; }

.dropdown-loading, .dropdown-empty {
  padding: 30px; text-align: center; font-size: 12px; color: #ADB5BD;
  display: flex; align-items: center; justify-content: center; gap: 8px;
}
.loading-spinner {
  width: 18px; height: 18px; border: 2px solid #E9ECEF;
  border-top-color: #212529; border-radius: 50%;
  animation: spin .6s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.toolbar-btn {
  padding: 4px 10px; border: 1px solid #DEE2E6; border-radius: 6px;
  background: #fff; font-size: 11px; cursor: pointer; color: #495057;
  white-space: nowrap;
}
.toolbar-btn:hover { background: #E9ECEF; }

/* Datetime */
.pub-datetime {
  border: 1px solid #DEE2E6; border-radius: 6px;
  padding: 8px 10px; font-size: 12.5px; outline: none;
  color: #212529; background: #fff;
  transition: border-color .15s;
}
.pub-datetime:focus { border-color: #212529; box-shadow: 0 0 0 2px rgba(33,37,41,.08); }
</style>