<template>
  <div class="system-config">
    <!-- 🌐 网站设置 -->
    <el-card id="section-sys-site" v-show="!activeSection || activeSection === 'sys-site'">
      <template #header>
        <span>🌐 网站设置</span>
      </template>
      <el-form label-width="180px" label-position="left">
        <el-form-item label="网站名称">
          <el-input v-model="configs.site_name" placeholder="CloudSearch" style="max-width: 300px" />
          <div class="form-tip">显示在网站标题和页脚</div>
        </el-form-item>
        <el-form-item label="网站 LOGO">
          <div class="fallback-upload-wrap">
            <div class="fallback-upload-row">
              <el-button type="primary" @click="triggerLogoInput">
                <template #icon><el-icon><Upload /></el-icon></template>
                选择LOGO图片并上传
              </el-button>
              <input
                ref="logoInputRef"
                type="file"
                accept=".jpg,.jpeg,.png,.webp"
                hidden
                @change="handleLogoSelect"
              />
              <div class="form-tip" style="margin-left: 12px;">
                推荐 <strong>320×60</strong> 或宽比例（如 4:1），JPEG/PNG/WebP，最大 2MB。<br>
                LOGO 同时也作为搜索结果无封面图时的兜底图使用。
              </div>
            </div>
            <div v-if="configs.site_logo" class="fallback-preview">
              <img :src="String(configs.site_logo)" alt="LOGO预览" @error="(e: any) => e.target.style.display='none'" />
              <el-button size="small" type="danger" plain @click="handleRemoveLogo">移除</el-button>
            </div>
          </div>
        </el-form-item>
        <el-form-item label="底部免责声明">
          <el-input
            v-model="configs.site_disclaimer"
            type="textarea"
            :rows="4"
            placeholder="输入免责声明内容"
          />
          <div class="form-tip">显示在网站底部，留空则不显示</div>
        </el-form-item>
        <el-form-item label="滚动通知文字">
          <el-input
            v-model="configs.site_marquee"
            placeholder="📢 欢迎使用CloudSearch"
            style="max-width: 500px"
          />
          <div class="form-tip">搜索栏下方滚动的通知条（从右往左滚动），留空则不显示</div>
        </el-form-item>
        <el-form-item label="系统时区">
          <el-input
            v-model="configs.timezone"
            placeholder="Asia/Shanghai"
            style="max-width: 300px"
          />
          <div class="form-tip">例如 Asia/Shanghai、America/New_York、UTC，修改后保存配置即可生效</div>
        </el-form-item>
      </el-form>
    </el-card>

    <!-- 🔗 外部服务 + 数据库与缓存 -->
    <el-card id="section-sys-services" v-show="!activeSection || activeSection === 'sys-services'">
      <template #header>
        <span>🔗 外部服务 &amp; 缓存</span>
      </template>
      <el-form label-width="180px" label-position="left">
        <el-form-item label="PanSou 搜索引擎地址">
          <div style="display: flex; gap: 8px; align-items: center; width: 100%;">
            <el-input v-model="configs.pansou_url" placeholder="http://pansou:8888" style="max-width: 360px" />
            <el-button type="primary" :loading="pansouTesting" @click="handleTestPansou" size="default" style="width: 100px;">
              {{ pansouTesting ? '测试中...' : '验证连接' }}
            </el-button>
            <el-button type="warning" :loading="pansouUpdating" @click="handleUpdatePansou" size="default" style="width: 130px;" :disabled="!pansouInfo?.hasUpdate">
              {{ pansouUpdating ? '更新中...' : (pansouInfo?.hasUpdate ? '🔄 有新版本' : '无更新') }}
            </el-button>
            <span v-if="pansouInfo?.latestVersion" style="font-size:11px;color:#e6a23c;white-space:nowrap;">{{ pansouInfo.latestVersion }}</span>
          </div>
          <div class="form-tip" style="margin-top: 4px;">盘搜搜索引擎的地址</div>
        </el-form-item>

        <!-- PanSou 状态面板 -->
        <div class="pansou-status-grid">
          <div class="db-stat-item">
            <div class="db-stat-value" :class="pansouInfo?.status === 'connected' ? 'text-success' : 'text-warning'">
              {{ pansouInfo?.status === 'connected' ? '已连接' : pansouInfo ? '未连接' : '-' }}
            </div>
            <div class="db-stat-label">PanSou 状态</div>
          </div>
          <div class="db-stat-item">
            <div class="db-stat-value">{{ pansouInfo?.channelCount ?? '-' }}</div>
            <div class="db-stat-label">频道数量</div>
          </div>
          <div class="db-stat-item">
            <div class="db-stat-value">{{ pansouInfo?.pluginCount ?? '-' }}</div>
            <div class="db-stat-label">插件数量</div>
          </div>
          <div class="db-stat-item">
            <div class="db-stat-value">{{ pansouInfo?.diskCount ?? '-' }}</div>
            <div class="db-stat-label">网盘数量</div>
          </div>
          <div class="db-stat-item">
            <div class="db-stat-value">{{ pansouInfo?.version || '-' }}</div>
            <div class="db-stat-label">版本</div>
          </div>
        </div>

        <el-form-item label="PanSou Web 端访问">
          <el-switch v-model="pansouWebEnabled" active-text="启用" inactive-text="关闭" />
          <div class="form-tip" style="margin-left: 12px;">
            开启后可通过 <code>/pansou/</code> 路径访问 PanSou 搜索引擎管理界面
          </div>
        </el-form-item>
        <el-form-item label="启用代理">
          <el-switch v-model="proxyEnabled" active-text="启用" inactive-text="关闭" />
          <div class="form-tip" style="margin-left: 8px;">
            仅 PanSou 需要此配置，开启后搜索请求将经过代理转发
          </div>
        </el-form-item>
        <el-form-item v-if="proxyEnabled" label="代理地址" style="margin-top: -12px;">
          <div style="display: flex; gap: 8px; align-items: center; width: 100%;">
            <el-input v-model="configs.search_proxy_url" placeholder="http://127.0.0.1:7890" style="max-width: 360px" />
            <el-button type="primary" :loading="proxyTesting" @click="handleTestProxy" size="default" style="width: 100px;">
              {{ proxyTesting ? '测试中...' : '测试代理' }}
            </el-button>
          </div>
          <div class="form-tip" style="margin-top: 4px;">HTTP 或 SOCKS5 协议地址</div>
        </el-form-item>
        <el-form-item label="视频解析服务地址">
          <div style="display: flex; gap: 8px; align-items: center; width: 100%;">
            <el-input v-model="configs.video_parser_url" placeholder="http://video-parser:3001" style="max-width: 360px" />
            <el-button type="primary" :loading="videoParserTesting" @click="handleTestVideoParser" size="default" style="width: 100px;">
              {{ videoParserTesting ? '测试中...' : '验证连接' }}
            </el-button>
          </div>
          <div class="form-tip" style="margin-top: 4px;">视频链接解析服务地址</div>
        </el-form-item>
        <el-form-item label="TMDB 读取令牌">
          <div style="display: flex; gap: 8px; align-items: center; width: 100%;">
            <el-input
              v-model="configs.tmdb_api_token"
              type="password"
              show-password
              placeholder="输入 TMDB API 读取令牌（Bearer Token）"
              style="max-width: 360px"
            />
            <el-button type="primary" :loading="tmdbTesting" @click="handleTestTmdb" size="default" style="width: 100px;">
              {{ tmdbTesting ? '测试中...' : '验证令牌' }}
            </el-button>
          </div>
          <div class="form-tip" style="margin-top: 4px;">
            用于搜索 TMDB 获取评分、导演、演员等完整内容信息。
            <a href="https://www.themoviedb.org/settings/api" target="_blank" rel="noopener" style="color: var(--primary-color)">获取令牌 →</a>
          </div>
        </el-form-item>
<el-form-item label="IP 归属地查询">
          <div style="display:flex; flex-direction:column; gap:8px; width:100%;">
            <el-select v-model="configs.ip_geo_provider" placeholder="选择接口" style="max-width:260px">
              <el-option label="接口盒子 (apihz.cn)" value="apihz" />
            </el-select>
            <template v-if="configs.ip_geo_provider === 'apihz'">
              <el-input v-model="configs.ip_geo_api_id" placeholder="API ID（如：10014356）" style="max-width:360px" />
              <el-input v-model="configs.ip_geo_api_key" placeholder="API Key" type="password" show-password style="max-width:360px" />
            </template>
            <el-button type="primary" :loading="ipGeoTesting" @click="handleTestIpGeo" size="small" style="width:100px">
              {{ ipGeoTesting ? "测试中..." : "验证接口" }}
            </el-button>
            <div class="form-tip">用于查询用户 IP 归属地（搜索/转存记录中显示）。留空则不做 IP 归属地查询。</div>
          </div>
        </el-form-item>
        <el-divider content-position="left">Redis 缓存</el-divider>
        <el-form-item label="Redis 连接地址">
          <div style="display: flex; gap: 8px; align-items: center; width: 100%;">
            <el-input v-model="configs.redis_url" placeholder='redis://:***@172.17.0.1:6379' style="max-width: 360px" />
            <el-button type="primary" size="default" :loading="redisTesting" @click="handleTestRedis" style="width: 100px;">
              {{ redisTesting ? '测试中...' : '验证连接' }}
            </el-button>
          </div>
          <div class="form-tip" style="margin-top: 4px;">
            Redis 用于缓存搜索验证结果，提升响应速度。<br>
            <strong>带密码格式：</strong><code>redis://:你的密码@地址:6379</code><br>
            修改后保存配置即可生效，无需重启。<strong>切换 Redis 只会清空缓存，不影响任何重要数据。</strong>
          </div>
        </el-form-item>
      </el-form>

      <div v-if="dbLoading" style="text-align: center; padding: 16px;">
        <el-icon class="is-loading" :size="20"><Loading /></el-icon>
        <span style="margin-left: 8px; color: #909399;">加载中...</span>
      </div>
      <div v-else class="db-status-grid">
        <div class="db-stat-item">
          <div class="db-stat-value" :class="dbStatus.redis_status === '已连接' ? 'text-success' : 'text-warning'">
            {{ dbStatus.redis_status }}
          </div>
          <div class="db-stat-label">Redis 状态</div>
        </div>
        <div class="db-stat-item">
          <div class="db-stat-value">{{ dbStatus.db_size }}</div>
          <div class="db-stat-label">数据库大小</div>
        </div>
        <div class="db-stat-item">
          <div class="db-stat-value">{{ dbStatus.save_records }}</div>
          <div class="db-stat-label">转存记录</div>
        </div>
        <div class="db-stat-item">
          <div class="db-stat-value">{{ dbStatus.search_stats }}</div>
          <div class="db-stat-label">搜索记录</div>
        </div>
        <div class="db-stat-item">
          <div class="db-stat-value">{{ dbStatus.cloud_configs }}</div>
          <div class="db-stat-label">网盘配置</div>
        </div>
        <div class="db-stat-item">
          <div class="db-stat-value">{{ dbStatus.content_cache }}</div>
          <div class="db-stat-label">内容缓存</div>
        </div>
      </div>
    </el-card>

    <!-- 📊 搜索结果展示策略（含链接验证配置） -->
    <el-card id="section-sys-strategy" v-show="!activeSection || activeSection === 'sys-strategy'">
      <template #header>
        <span>🔧 性能配置</span>
      </template>

      <div class="strategy-section">
        <el-divider content-position="left">搜索结果返回方式</el-divider>

        <div class="field-block">
          <div class="field-label-row">
            <span class="field-label">开启资源链接有效性监测</span>
            <el-switch
              v-model="configs.link_validation_enabled"
              active-value="true"
              inactive-value="false"
              active-text="开启"
              inactive-text="关闭"
            />
          </div>
          <div class="field-desc">开启后搜索时会自动检测链接是否有效，过滤失效链接。关闭则直接返回所有结果（更快）</div>
        </div>

        <div class="field-block">
          <div class="field-label-row">
            <span class="field-label">搜索结果方式</span>
            <el-radio-group v-model="configs.search_strategy">
              <el-radio value="wait_all">等待全部结果后展示</el-radio>
              <el-radio value="stream_channel">频道结果逐步展示</el-radio>
            </el-radio-group>
          </div>
          <div class="field-desc">逐步展示会分频道并发请求并优先展示先返回的频道；该模式下频道顺序按返回先后，不按配置顺序。</div>
        </div>

        <el-divider content-position="left">搜索策略</el-divider>

        <!-- 开关 + 数字输入 3列 -->
        <div class="strategy-grid">
          <div class="grid-cell">
            <div class="field-label-row">
              <span class="field-label">使用所有频道参与搜索</span>
              <el-switch v-model="searchAllChannels" active-text="开启" inactive-text="关闭" />
            </div>
            <div class="field-desc">包含未启用频道，命中更广但请求压力更高。</div>
          </div>
          <div class="grid-cell">
            <div class="field-label-row">
              <span class="field-label">每类网盘有效结果数</span>
              <el-input-number v-model="configs.search_result_limit" :min="1" :max="100" />
            </div>
            <div class="field-desc">每个网盘类型最多展示的有效链接数量</div>
          </div>
          <div class="grid-cell">
            <div class="field-label-row">
              <span class="field-label">验证并发数</span>
              <el-input-number v-model="configs.validation_concurrency" :min="1" :max="50" />
            </div>
            <div class="field-desc">同时验证的链接数量</div>
          </div>

          <div class="grid-cell">
            <div class="field-label-row">
              <span class="field-label">有效链接缓存 (s)</span>
              <el-input-number v-model="configs.validation_cache_ttl_valid" :min="60" :max="86400" :step="60" />
            </div>
            <div class="field-desc">有效验证结果的缓存时间</div>
          </div>
          <div class="grid-cell">
            <div class="field-label-row">
              <span class="field-label">无效链接缓存 (s)</span>
              <el-input-number v-model="configs.validation_cache_ttl_invalid" :min="60" :max="86400" :step="60" />
            </div>
            <div class="field-desc">无效验证结果的缓存时间</div>
          </div>
          <div class="grid-cell">
            <div class="field-label-row">
              <span class="field-label">验证超时 (ms)</span>
              <el-input-number v-model="configs.validation_timeout" :min="1000" :max="30000" :step="500" />
            </div>
            <div class="field-desc">单个链接验证超时毫秒数</div>
          </div>
        </div>

        <el-divider content-position="left">链接检测配置</el-divider>

        <!-- 搜索标题过滤规则（放入链接检测配置内） -->
        <div class="field-block">
          <div class="field-label-row">
            <span class="field-label">搜索标题过滤规则</span>
          </div>
          <div style="display:flex; gap:8px; align-items:stretch;">
            <el-input
              v-model="filterRuleBatchInput"
              type="textarea"
              :rows="1"
              placeholder="每行一条规则，回车分隔"
              style="max-width:420px; font-family:monospace; font-size:12px"
            />
            <el-button type="primary" @click="batchAddFilterRules" :disabled="!filterRuleBatchInput.trim()">
              确认添加
            </el-button>
          </div>
          <div class="tag-list" v-if="filterRuleList.length > 0">
            <el-tag
              v-for="(rule, i) in filterRuleList"
              :key="i"
              closable
              :type="ruleTagType(rule)"
              :disable-transitions="false"
              @close="removeFilterRule(i)"
            >{{ rule }}</el-tag>
          </div>
          <div v-else class="tag-empty">暂无过滤规则</div>
          <div class="filter-rule-help">
            <div class="help-title">📖 规则说明</div>
            <div class="help-row"><code># 注释内容</code> — <code>#</code> 后必须跟<strong>空格</strong>才会被识别为注释，单纯的 <code>#动漫</code> 会被当作要移除的文本</div>
            <div class="help-row"><code>/正则表达式/标志</code> — 正则匹配，匹配到的内容会被删除。如 <code>/^\\d+[、,.\\s]/</code> 去掉开头的序号</div>
            <div class="help-row"><code>纯文本</code> — 直接写要移除的文字（支持 emoji），凡是标题中含有的都会被删除</div>
          </div>
        </div>

        <div class="field-block">
          <div class="field-label-row">
            <span class="field-label">失效关键词</span>
          </div>
          <div style="display:flex; gap:8px; align-items:stretch;">
            <el-input
              v-model="invalidKeywordBatchInput"
              type="textarea"
              :rows="1"
              placeholder="每行一个关键词，回车分隔"
              style="max-width:420px; font-family:monospace; font-size:12px"
            />
            <el-button type="danger" @click="batchAddInvalidKeywords" :disabled="!invalidKeywordBatchInput.trim()">
              确认添加
            </el-button>
          </div>
          <div class="field-desc">自定义失效关键词，PanSou 检测 summary 或 HTML 页面内容包含即判为失效。</div>
          <div class="tag-list" v-if="invalidKeywordList.length > 0">
            <el-tag
              v-for="(kw, i) in invalidKeywordList"
              :key="i"
              closable
              type="danger"
              :disable-transitions="false"
              @close="removeInvalidKeyword(i)"
            >{{ kw }}</el-tag>
          </div>
          <div v-else class="tag-empty">暂无失效关键词，所有链接将默认判为有效</div>
        </div>

        <el-divider content-position="left">转存文件清理</el-divider>

        <div class="field-block">
          <div class="field-label-row">
            <span class="field-label">广告关键词</span>
          </div>
          <div style="display:flex; gap:8px; align-items:stretch;">
            <el-input
              v-model="adKeywordBatchInput"
              type="textarea"
              :rows="1"
              placeholder="每行一个关键词，回车分隔"
              style="max-width:420px; font-family:monospace; font-size:12px"
            />
            <el-button type="danger" @click="batchAddAdKeywords" :disabled="!adKeywordBatchInput.trim()">
              确认添加
            </el-button>
          </div>
          <div class="field-desc">夸克转存完成后自动删除文件名/文件夹名含这些关键词的内容（防广告病毒）</div>
          <div class="tag-list" v-if="adKeywordList.length > 0">
            <el-tag
              v-for="(kw, i) in adKeywordList"
              :key="i"
              closable
              type="warning"
              :disable-transitions="false"
              @close="removeAdKeyword(i)"
            >{{ kw }}</el-tag>
          </div>
          <div v-else class="tag-empty">暂未配置广告关键词，不会进行广告清理</div>
        </div>

        <div class="field-block">
          <div class="field-label-row">
            <span class="field-label">警示文件夹名</span>
          </div>
          <div style="display:flex; gap:8px; align-items:stretch;">
            <el-input
              v-model="warningFolderBatchInput"
              type="textarea"
              :rows="1"
              placeholder="每行一个文件夹名，回车分隔"
              style="max-width:420px; font-family:monospace; font-size:12px"
            />
            <el-button type="primary" @click="batchAddWarningFolders" :disabled="!warningFolderBatchInput.trim()">
              确认添加
            </el-button>
          </div>
          <div class="field-desc">夸克转存完成后在网盘根目录自动创建这些警示文件夹（自动加上 ⚠️ 前缀）</div>
          <div class="tag-list" v-if="warningFolderList.length > 0">
            <el-tag
              v-for="(name, i) in warningFolderList"
              :key="i"
              closable
              type="info"
              :disable-transitions="false"
              @close="removeWarningFolder(i)"
            >{{ name }}</el-tag>
          </div>
          <div v-else class="tag-empty">暂未配置警示文件夹</div>
        </div>

        <div class="field-block">
          <div class="field-label-row">
            <span class="field-label">可疑文件后缀</span>
          </div>
          <div style="display:flex; gap:8px; align-items:stretch;">
            <el-input
              v-model="susExtensionBatchInput"
              type="textarea"
              :rows="1"
              placeholder="每行一个后缀，不要带点号，回车分隔"
              style="max-width:420px; font-family:monospace; font-size:12px"
            />
            <el-button type="danger" @click="batchAddSusExtensions" :disabled="!susExtensionBatchInput.trim()">
              确认添加
            </el-button>
          </div>
          <div class="field-desc">夸克转存完成后自动删除后缀匹配的文件（防病毒，如 bat、exe、scr 等）</div>
          <div class="tag-list" v-if="susExtensionList.length > 0">
            <el-tag
              v-for="(ext, i) in susExtensionList"
              :key="i"
              closable
              type="danger"
              :disable-transitions="false"
              @close="removeSusExtension(i)"
            >.{{ ext }}</el-tag>
          </div>
          <div v-else class="tag-empty">暂无配置，使用默认列表：bat、exe、vbs、scr、cmd、com、pif、js、jar、msi、reg、inf、ps1</div>
        </div>
      </div>
    </el-card>

    <!-- 修改密码 -->
    <el-card id="section-sys-password" v-show="!activeSection || activeSection === 'sys-password'">
      <template #header>
        <span>🔑 修改管理员密码</span>
      </template>
      <el-form ref="passwordFormRef" :model="passwordForm" :rules="passwordRules" label-width="120px" label-position="left">
        <el-form-item label="原密码" prop="oldPassword">
          <el-input v-model="passwordForm.oldPassword" type="password" show-password placeholder="输入原密码" style="max-width: 300px" />
        </el-form-item>
        <el-form-item label="新密码" prop="newPassword">
          <el-input v-model="passwordForm.newPassword" type="password" show-password placeholder="输入新密码（至少6位）" style="max-width: 300px" />
        </el-form-item>
        <el-form-item label="确认新密码" prop="confirmPassword">
          <el-input v-model="passwordForm.confirmPassword" type="password" show-password placeholder="再次输入新密码" style="max-width: 300px" />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" :loading="changingPassword" @click="handleChangePassword">修改密码</el-button>
        </el-form-item>
      </el-form>
    </el-card>

    <!-- 📬 消息推送 -->
    <el-card id="section-sys-notify" v-show="!activeSection || activeSection === 'sys-notify'">
      <template #header>
        <div style="display:flex; align-items:center; justify-content:space-between;">
          <span>📬 消息推送</span>
        </div>
      </template>

            <!-- 全局推送兜底（动态渲染全部通道） -->
      <el-collapse :model-value="['global']">
        <el-collapse-item title="全局推送（管理员兜底）" name="global">
          <div class="strategy-section">
            <el-form label-width="140px" label-position="left">
              <div style="display:grid; grid-template-columns:repeat(2,1fr); gap:8px;">
              <div v-for="(np, nkey) in notifyProviders" :key="nkey" style="border:1px solid var(--el-border-color-light); border-radius:6px; padding:8px 12px;">
                <div style="display:flex; align-items:center; gap:8px; margin-bottom:6px;">
                  <el-switch v-model="globalNotifyForm.channels[nkey]._enabled" size="small" />
                  <strong>{{ np.label }}</strong>
                  <el-button v-if="globalNotifyForm.channels[nkey]._enabled" size="small" text type="primary" @click="testGlobalChannel(nkey)" :loading="globalNotifyForm.channels[nkey]._testing">测试</el-button>
                </div>
                <div v-if="globalNotifyForm.channels[nkey]._enabled">
                  <el-form-item v-for="p in np.params" :key="p.key" :label="p.label" style="margin-bottom:6px;">
                    <el-input v-if="p.type==='password'" v-model="globalNotifyForm.channels[nkey][p.key]" type="password" show-password :placeholder="p.placeholder || ''" style="max-width:360px" />
                    <el-switch v-else-if="p.type==='switch'" v-model="globalNotifyForm.channels[nkey][p.key]" />
                    <el-input-number v-else-if="p.type==='number'" v-model="globalNotifyForm.channels[nkey][p.key]" :min="1" :max="10" style="max-width:160px" />
                    <el-input v-else v-model="globalNotifyForm.channels[nkey][p.key]" :placeholder="p.placeholder || ''" style="max-width:360px" />
                  </el-form-item>
                </div>
              </div>
            </div>
              <el-divider content-position="left">全局事件开关</el-divider>
              <div style="display:grid; grid-template-columns:repeat(2,1fr); gap:10px;">
                <div class="event-card" :class="{ active: globalNotifyForm.events.on_save_success }" style="padding:10px 14px; border-radius:8px; border:1px solid var(--el-border-color-light); transition:all .2s;">
                  <div style="display:flex; align-items:center; justify-content:space-between;">
                    <span style="display:flex; align-items:center; gap:6px; font-size:14px; font-weight:500;">
                      <span>✅</span> 转存成功
                    </span>
                    <el-switch v-model="globalNotifyForm.events.on_save_success" size="small" />
                  </div>
                  <div style="margin-top:4px;">
                    <el-button size="small" text type="primary" @click="openTemplateEditor('on_save_success')">✏️ 编辑模板</el-button>
                  </div>
                </div>
                <div class="event-card" :class="{ active: globalNotifyForm.events.on_save_fail }" style="padding:10px 14px; border-radius:8px; border:1px solid var(--el-border-color-light); transition:all .2s;">
                  <div style="display:flex; align-items:center; justify-content:space-between;">
                    <span style="display:flex; align-items:center; gap:6px; font-size:14px; font-weight:500;">
                      <span>⚠️</span> 转存失败
                    </span>
                    <el-switch v-model="globalNotifyForm.events.on_save_fail" size="small" />
                  </div>
                  <div style="margin-top:4px;">
                    <el-button size="small" text type="primary" @click="openTemplateEditor('on_save_fail')">✏️ 编辑模板</el-button>
                  </div>
                </div>
                <div class="event-card" :class="{ active: globalNotifyForm.events.on_cookie_expire }" style="padding:10px 14px; border-radius:8px; border:1px solid var(--el-border-color-light); transition:all .2s;">
                  <div style="display:flex; align-items:center; justify-content:space-between;">
                    <span style="display:flex; align-items:center; gap:6px; font-size:14px; font-weight:500;">
                      <span>🍪</span> Cookie过期
                    </span>
                    <el-switch v-model="globalNotifyForm.events.on_cookie_expire" size="small" />
                  </div>
                  <div style="margin-top:4px;">
                    <el-button size="small" text type="primary" @click="openTemplateEditor('on_cookie_expire')">✏️ 编辑模板</el-button>
                  </div>
                </div>
                <div class="event-card" :class="{ active: globalNotifyForm.events.on_cleanup }" style="padding:10px 14px; border-radius:8px; border:1px solid var(--el-border-color-light); transition:all .2s;">
                  <div style="display:flex; align-items:center; justify-content:space-between;">
                    <span style="display:flex; align-items:center; gap:6px; font-size:14px; font-weight:500;">
                      <span>🧹</span> 清理完成
                    </span>
                    <el-switch v-model="globalNotifyForm.events.on_cleanup" size="small" />
                  </div>
                  <div style="margin-top:4px;">
                    <el-button size="small" text type="primary" @click="openTemplateEditor('on_cleanup')">✏️ 编辑模板</el-button>
                  </div>
                </div>
              </div>
              <div class="form-tip" style="margin-top:8px;">全局推送作为兜底通道。设置了推送用户的网盘配置走用户推送，未设置的走全局推送。</div>
            </el-form>
          </div>
        </el-collapse-item>
      </el-collapse>      <el-divider content-position="left">添加推送用户</el-divider>

      <!-- Inline push user add/edit form -->
      <div style="border:1px solid var(--el-border-color-light); border-radius:6px; padding:12px 16px; margin-bottom:16px;">
        <div style="display:flex; align-items:center; gap:12px; flex-wrap:wrap;">
          <el-select v-model="pushUserForm.account" filterable allow-create clearable placeholder="选择推广账户" style="width:200px;">
            <el-option
              v-for="acc in pushUserAccountOptions"
              :key="acc"
              :label="acc"
              :value="acc"
            />
          </el-select>

          <el-select v-model="pushUserForm.channel" clearable placeholder="选择消息频道" style="width:220px;" @change="onPushUserChannelChange">
            <el-option
              v-for="(np, nkey) in enabledNotifyProviders"
              :key="nkey"
              :label="np.label"
              :value="nkey"
            />
          </el-select>

          <!-- Channel params inputs (filtered: no title/content/level/priority; SMTP only shows to) -->
          <template v-if="pushUserForm.channel && notifyProviders[pushUserForm.channel]">
            <div v-for="p in getPushUserChannelParams(pushUserForm.channel)" :key="p.key" style="display:inline-flex; align-items:center; gap:4px;">
              <span style="font-size:13px; color:var(--el-text-color-secondary); white-space:nowrap;">{{ p.label }}:</span>
              <el-input
                v-if="p.type === 'password'"
                v-model="pushUserForm.channelParams[p.key]"
                type="password" show-password
                :placeholder="p.placeholder || p.label"
                style="width:200px;" size="small"
              />
              <el-input
                v-else
                v-model="pushUserForm.channelParams[p.key]"
                :placeholder="p.placeholder || p.label"
                style="width:200px;" size="small"
              />
            </div>
          </template>

          <div style="display:flex; align-items:center; gap:12px; flex-wrap:wrap;">
            <el-switch v-model="pushUserForm.events.on_save_success" active-text="转存成功" />
            <el-switch v-model="pushUserForm.events.on_save_fail" active-text="转存失败" />
            <el-switch v-model="pushUserForm.events.on_cookie_expire" active-text="Cookie过期" />
            <el-switch v-model="pushUserForm.events.on_cleanup" active-text="清理完成" />
          </div>

          <el-button type="primary" size="small" :loading="pushUserSaving" @click="savePushUser">{{ pushUserForm.id ? '更新' : '确认添加' }}</el-button>
          <el-button v-if="pushUserForm.id" size="small" @click="cancelEditPushUser">取消编辑</el-button>
        </div>
      </div>

      <el-divider content-position="left">推送用户列表</el-divider>

      <el-table :data="pushUsers" stripe style="width:100%" empty-text="暂无推送用户">
        <el-table-column prop="account" label="推广账号" min-width="140" />
        <el-table-column label="转存成功" width="90" align="center">
          <template #default="{ row }">
            <el-tag v-if="getEventEnabled(row, 'on_save_success')" type="success" size="small">✔</el-tag>
            <span v-else style="color:#ccc;">—</span>
          </template>
        </el-table-column>
        <el-table-column label="转存失败" width="90" align="center">
          <template #default="{ row }">
            <el-tag v-if="getEventEnabled(row, 'on_save_fail')" type="success" size="small">✔</el-tag>
            <span v-else style="color:#ccc;">—</span>
          </template>
        </el-table-column>
        <el-table-column label="Cookie过期" width="90" align="center">
          <template #default="{ row }">
            <el-tag v-if="getEventEnabled(row, 'on_cookie_expire')" type="success" size="small">✔</el-tag>
            <span v-else style="color:#ccc;">—</span>
          </template>
        </el-table-column>
        <el-table-column label="清理完成" width="90" align="center">
          <template #default="{ row }">
            <el-tag v-if="getEventEnabled(row, 'on_cleanup')" type="success" size="small">✔</el-tag>
            <span v-else style="color:#ccc;">—</span>
          </template>
        </el-table-column>
        <el-table-column label="消息频道" min-width="180">
          <template #default="{ row }">
            <template v-if="hasEnabledChannels(row)">
              <el-tag v-for="(params, key) in getEnabledChannels(row)" :key="key" size="small" style="margin-right:4px;margin-bottom:2px;">{{ getProviderLabel(key) }}</el-tag>
              <span style="font-size:11px; color:var(--el-text-color-secondary); margin-left:4px;">{{ getChannelParamsSummary(row) }}</span>
            </template>
            <span v-else style="color:#909399;font-size:12px;">走全局推送</span>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="180" fixed="right">
          <template #default="{ row }">
            <el-button text type="primary" size="small" @click="editPushUser(row)">编辑</el-button>
            <el-popconfirm title="确定删除该推送用户？" @confirm="deletePushUser(row)">
              <template #reference>
                <el-button text type="danger" size="small">删除</el-button>
              </template>
            </el-popconfirm>
          </template>
        </el-table-column>
      </el-table>
    </el-card>

    
    <!-- 📊 每日汇报 -->
    <el-card id="section-sys-daily-report" v-show="!activeSection || activeSection === 'sys-daily-report'">
      <template #header>
        <div style="display:flex; align-items:center; justify-content:space-between;">
          <span>📊 每日汇报</span>
          <div>
            <el-button size="small" :loading="dailyReportPreviewing" @click="handleDailyReportPreview">📋 预览</el-button>
            <el-button size="small" type="primary" :loading="dailyReportSending" @click="handleDailyReportSendTest">▶ 发送测试</el-button>
          </div>
        </div>
      </template>
      <el-form label-width="140px" label-position="left">
        <el-form-item label="启用每日汇报">
          <el-switch v-model="dailyReportForm.enabled" active-text="每天8点自动发送" />
        </el-form-item>
        <el-form-item label="发送时间">
          <el-time-picker
            v-model="dailyReportForm.time"
            format="HH:mm"
            value-format="HH:mm"
            placeholder="选择时间"
            :disabled="!dailyReportForm.enabled"
          />
          <div class="form-tip">默认每天 08:00 发送前一天的汇总报告</div>
        </el-form-item>
        <el-form-item label="报告内容">
          <div style="display:flex; flex-wrap:wrap; gap:16px;">
            <el-switch v-model="dailyReportForm.includeSearch" active-text="搜索统计" :disabled="!dailyReportForm.enabled" />
            <el-switch v-model="dailyReportForm.includeSaves" active-text="转存统计" :disabled="!dailyReportForm.enabled" />
            <el-switch v-model="dailyReportForm.includeStorage" active-text="网盘容量" :disabled="!dailyReportForm.enabled" />
            <el-switch v-model="dailyReportForm.includeUsers" active-text="用户数" :disabled="!dailyReportForm.enabled" />
          </div>
        </el-form-item>
        <el-form-item label="推送通道">
          <el-select v-model="dailyReportForm.channels" multiple placeholder="留空=全部全局通道" :disabled="!dailyReportForm.enabled" style="width:100%;max-width:480px">
            <el-option v-for="(np, nk) in notifyProviders" :key="nk" :label="np.label" :value="nk" />
          </el-select>
          <div class="form-tip">留空则发送至全部已启用的全局通道</div>
        </el-form-item>
        <el-form-item label="上次发送">
          <span>{{ dailyReportLastRun || '从未发送' }}</span>
        </el-form-item>
      </el-form>

      <!-- Preview modal -->
      <el-dialog v-model="dailyReportPreviewVisible" title="📊 每日汇报预览" width="600px">
        <div style="white-space: pre-wrap; font-family: monospace; background: var(--el-fill-color-light); padding: 16px; border-radius: 8px; max-height: 500px; overflow-y: auto;">{{ dailyReportPreview }}</div>
      </el-dialog>
    </el-card>

    <!-- 🔄 系统维护 -->    <el-card id="section-sys-maintenance" v-show="!activeSection || activeSection === 'sys-maintenance'">      <template #header>        <span>🔄 系统维护</span>      </template>      <el-form label-width="180px" label-position="left">        <el-form-item label="自动更新镜像">          <el-switch v-model="autoUpdateEnabled" active-text="启用" inactive-text="禁用" />          <div class="form-tip">启用后 CloudSearch 将自动检测并更新到最新镜像版本</div>          <div class="form-tip" style="color: var(--(--el-color-warning,#e6a23c));">            当前需手动在服务器执行：docker-compose -f /opt/CloudSearch/docker-compose.yml pull && docker-compose -f /opt/CloudSearch/docker-compose.yml up -d          </div>        </el-form-item>      </el-form>    </el-card>

    <!-- 消息模板编辑弹窗 -->
    <el-dialog v-model="templateEditorVisible" :title="'✏️ 编辑消息模板 — ' + templateEditorLabel" width="560px" @close="templateEditorKey = ''">
      <div style="margin-bottom:10px; font-size:12px; color:var(--el-text-color-secondary); line-height:2;">
        可用变量：<br/>
        <el-tag size="small" style="margin:2px">{cloud_type}</el-tag> 网盘类型
        <el-tag size="small" style="margin:2px">{nickname}</el-tag> 网盘昵称
        <el-tag size="small" style="margin:2px">{file_name}</el-tag> 文件名
        <el-tag size="small" style="margin:2px">{file_size}</el-tag> 文件大小
        <el-tag size="small" style="margin:2px">{duration}</el-tag> 耗时
        <el-tag size="small" style="margin:2px">{share_url}</el-tag> 分享链接
        <el-tag size="small" style="margin:2px">{fail_count}</el-tag> 连续失败次数
        <el-tag size="small" style="margin:2px">{error_message}</el-tag> 错误信息
        <el-tag size="small" style="margin:2px">{freed_space}</el-tag> 释放空间
      </div>
      <el-form v-if="templateEditorKey && globalNotifyForm.eventTemplates[templateEditorKey]" label-width="60px">
        <el-form-item label="标题">
          <el-input v-model="globalNotifyForm.eventTemplates[templateEditorKey].title" placeholder="消息标题" />
        </el-form-item>
        <el-form-item label="内容">
          <el-input v-model="globalNotifyForm.eventTemplates[templateEditorKey].content" type="textarea" :rows="5" placeholder="消息正文，支持 Markdown 和 {变量}" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="templateEditorVisible = false">关闭</el-button>
      </template>
    </el-dialog>

    <!-- 保存按钮 -->
    <div class="save-bar">
      <el-button type="primary" size="large" :loading="saving" @click="handleSave">
        保存配置
      </el-button>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, reactive, onMounted, computed, watch } from "vue"
import { useRoute, useRouter } from "vue-router"
import { ElMessage } from 'element-plus'
import type { ElForm } from 'element-plus'
import { getSystemConfigs, updateSystemConfigs, changePassword as changePasswordApi, uploadFallbackImage, uploadLogo, updateSetting, getDbStatus, testRedisConnection, testExternalService, testNotifyChannel, getAllNotifierProviders, getCloudConfigs } from "../../api"
import { Upload, Loading } from "@element-plus/icons-vue"


const props = defineProps<{ section?: string }>()
const route = useRoute()
const router = useRouter()
const activeSection = computed(() => props.section || (route.query.section as string) || "")

interface ConfigMap {
  [key: string]: string | number
}

const passwordFormRef = ref<InstanceType<typeof ElForm>>()
const rawConfigs = ref<{ key: string; value: string; description: string }[]>([])
const configs = reactive<ConfigMap>({})
const filterRuleList = ref<string[]>([])
const filterRuleBatchInput = ref('')
const invalidKeywordList = ref<string[]>([])
const invalidKeywordBatchInput = ref('')
const adKeywordList = ref<string[]>([])
const adKeywordBatchInput = ref('')
const warningFolderList = ref<string[]>([])
const warningFolderBatchInput = ref('')
const susExtensionBatchInput = ref('')
const susExtensionList = ref([])
const saving = ref(false)
const changingPassword = ref(false)
const dbStatus = reactive({
  db_size: '-',
  save_records: 0,
  search_stats: 0,
  cloud_configs: 0,
  content_cache: 0,
  redis_status: '未连接',
})
const dbLoading = ref(true)
const redisTesting = ref(false)
const pansouTesting = ref(false)
const videoParserTesting = ref(false)
const tmdbTesting = ref(false)
const proxyTesting = ref(false)
const ipGeoTesting = ref(false)
const pansouInfo = ref<any>(null)
const pansouInfoLoading = ref(true)
const pansouUpdating = ref(false)
const proxyEnabled = computed({
  get: () => String(configs.search_proxy_enabled) === 'true',
  set: (val: boolean) => { configs.search_proxy_enabled = val ? 'true' : 'false' },
})

const pansouWebEnabled = computed({
  get: () => String(configs.pansou_web_enabled) === 'true',
  set: (val: boolean) => { configs.pansou_web_enabled = val ? 'true' : 'false' },
})
const searchAllChannels = computed({
  get: () => String(configs.search_all_channels) === 'true',
  set: (val: boolean) => { configs.search_all_channels = val ? 'true' : 'false' },
})

  get: () => String(configs.auto_update_enabled) === 'true',
  set: (val: boolean) => { configs.auto_update_enabled = val ? 'true' : 'false' },
})

// ======================== Push User Notifications ========================

// ======================== Daily Report ========================
const dailyReportForm = reactive({
  enabled: true,
  time: '08:00',
  includeSearch: true,
  includeSaves: true,
  includeStorage: true,
  includeUsers: true,
  channels: [] as string[],
})
const dailyReportPreviewing = ref(false)
const dailyReportSending = ref(false)
const dailyReportPreview = ref('')
const dailyReportPreviewVisible = ref(false)
const dailyReportLastRun = ref('')

async function loadDailyReportConfig() {
  try {
    const res = await fetch('/api/admin/daily-report/config', {
      headers: { Authorization: `Bearer ${localStorage.getItem("admin_token")}` }
    })
    if (res.ok) {
      const cfg = await res.json()
      Object.assign(dailyReportForm, cfg)
    }
  } catch {}
}
async function loadDailyReportLastRun() {
  try {
    const res = await fetch('/api/admin/daily-report/last-run', {
      headers: { Authorization: `Bearer ${localStorage.getItem("admin_token")}` }
    })
    if (res.ok) {
      const data = await res.json()
      if (data.date) {
        dailyReportLastRun.value = `${data.date} ${new Date(data.sentAt).toLocaleTimeString('zh-CN')}`
      }
    }
  } catch {}
}
async function saveDailyReportConfig() {
  try {
    await fetch('/api/admin/daily-report/config', {
      method: 'PUT',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${localStorage.getItem("admin_token")}`,
      },
      body: JSON.stringify({ ...dailyReportForm }),
    })
  } catch {}
}
async function handleDailyReportPreview() {
  dailyReportPreviewing.value = true
  try {
    const res = await fetch('/api/admin/daily-report/preview', {
      headers: { Authorization: `Bearer ${localStorage.getItem("admin_token")}` }
    })
    if (res.ok) {
      const data = await res.json()
      dailyReportPreview.value = data.content
      dailyReportPreviewVisible.value = true
    }
  } finally {
    dailyReportPreviewing.value = false
  }
}
async function handleDailyReportSendTest() {
  dailyReportSending.value = true
  try {
    const res = await fetch('/api/admin/daily-report/test', {
      method: 'POST',
      headers: { Authorization: `Bearer ${localStorage.getItem("admin_token")}` },
    })
    if (res.ok) {
      ElMessage.success('测试报告已发送到全局通知通道')
    } else {
      ElMessage.error('发送失败')
    }
  } catch {
    ElMessage.error('发送失败')
  } finally {
    dailyReportSending.value = false
  }
}

const pushUsers = ref<any[]>([])
const notifyProviders = ref<Record<string, any>>({})
// const pushUserDialogVisible = ref(false)  // removed - using inline form
const pushUserSaving = ref(false)
const pushUserAccountOptions = ref<string[]>([])

async function loadPushUserAccountOptions() {
  try {
    const resp = await fetch('/api/admin/cloud-configs', {
      headers: { 'Authorization': 'Bearer ' + (localStorage.getItem('admin_token') || '') }
    })
    if (!resp.ok) return
    const configs = await resp.json()
    const options = Array.isArray(configs)
      ? [...new Set(configs.map((c: any) => c.promotion_account || '').filter(Boolean))]
      : []
    pushUserAccountOptions.value = options
  } catch {}
}

const pushUserForm = reactive<any>({
  id: null,
  account: '',
  channel: '',
  channelParams: {} as Record<string, string>,
  events: {
    on_save_success: true,
    on_save_fail: true,
    on_cookie_expire: true,
    on_cleanup: false,
  },
})
function onPushUserChannelChange() {
  // Reset params when channel changes
  pushUserForm.channelParams = {}
}

// Only show channels that are enabled in global notification settings
const enabledNotifyProviders = computed(() => {
  const result: Record<string, any> = {}
  for (const [k, np] of Object.entries(notifyProviders.value)) {
    if (globalNotifyForm.channels[k]?._enabled) {
      result[k] = np
    }
  }
  return result
})

const globalNotifyForm = reactive<{ channels: Record<string, any>; events: Record<string, boolean>; eventTemplates: Record<string, { title: string; content: string }> }>({
  channels: {},
  events: { on_save_success: true, on_save_fail: true, on_cookie_expire: true, on_cleanup: false },
  eventTemplates: {
    on_save_success: { title: '✅ 转存成功', content: '**{cloud_type}** · {nickname}\n文件: {file_name}\n大小: {file_size}\n耗时: {duration}s\n链接: {share_url}' },
    on_save_fail: { title: '❌ 转存连续失败 {fail_count} 次', content: '**{cloud_type}** · {nickname}\n连续失败次数: {fail_count}\n最后失败: {error_message}\n请检查网盘状态' },
    on_cookie_expire: { title: '⚠️ Cookie 已过期', content: '**{cloud_type}** · {nickname}\nCookie 已过期或失效，请重新登录\n最后操作: {share_url}' },
    on_cleanup: { title: '🧹 清理完成', content: '**{cloud_type}** · {nickname}\n清理完成\n释放空间: {freed_space}' },
  },
})

function initPushUserChannelForm() {
  const channels: Record<string, any> = {}
  for (const [k, np] of Object.entries(notifyProviders.value)) {
    channels[k] = { _enabled: false, _testing: false }
    for (const p of np.params || []) {
      channels[k][p.key] = p.default || ''
    }
  }
  return channels
}

function editPushUser(row?: any) {
  if (row) {
    pushUserForm.id = row.id
    pushUserForm.account = row.account
    const nc = row.notify_config || {}
    const chKeys = Object.keys(nc.channels || {})
    pushUserForm.channel = chKeys[0] || ''
    pushUserForm.channelParams = chKeys[0] ? (nc.channels[chKeys[0]] || {}) : {}
    pushUserForm.events = {
      on_save_success: nc.events?.on_save_success !== false,
      on_save_fail: nc.events?.on_save_fail !== false,
      on_cookie_expire: nc.events?.on_cookie_expire !== false,
      on_cleanup: nc.events?.on_cleanup === true,
    }
  } else {
    pushUserForm.id = null
    pushUserForm.account = ''
    pushUserForm.channel = ''
    pushUserForm.channelParams = {}
    pushUserForm.events = { on_save_success: true, on_save_fail: true, on_cookie_expire: true, on_cleanup: false }
  }
}

function cancelEditPushUser() {
  pushUserForm.id = null
  pushUserForm.account = ''
  pushUserForm.channel = ''
  pushUserForm.channelParams = {}
  pushUserForm.events = { on_save_success: true, on_save_fail: true, on_cookie_expire: true, on_cleanup: false }
}

function getEventEnabled(row: any, eventKey: string): boolean {
  const nc = row.notify_config || {}
  const events = nc.events || {}
  return events[eventKey] === true
}

async function savePushUser() {
  if (!pushUserForm.account) {
    ElMessage.warning('请填写推广账号')
    return
  }
  pushUserSaving.value = true
  try {
    const payload: any = {
      account: pushUserForm.account,
      notify_config: { channels: {}, events: pushUserForm.events },
    }
    // Build channel with params
    const ch: Record<string, any> = {}
    if (pushUserForm.channel) {
      ch[pushUserForm.channel] = { ...pushUserForm.channelParams }
    }
    payload.notify_config.channels = ch
    if (pushUserForm.id) {
      await fetch('/api/admin/push-users/' + pushUserForm.id, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + (localStorage.getItem('admin_token') || '') },
        body: JSON.stringify(payload),
      })
    } else {
      await fetch('/api/admin/push-users', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + (localStorage.getItem('admin_token') || '') },
        body: JSON.stringify(payload),
      })
    }
    const isUpdate = !!pushUserForm.id
    pushUserForm.id = null
    pushUserForm.account = ''
    pushUserForm.channel = ''
    pushUserForm.channelParams = {}
    pushUserForm.events = { on_save_success: true, on_save_fail: true, on_cookie_expire: true, on_cleanup: false }
    ElMessage.success(isUpdate ? '推送用户已更新' : '推送用户已添加')
    await loadPushUsers()
  } catch (e: any) {
    ElMessage.error(e.message || '保存失败')
  } finally {
    pushUserSaving.value = false
  }
}

async function loadPushUsers() {
  try {
    const resp = await fetch('/api/admin/push-users', {
        headers: { 'Authorization': 'Bearer ' + (localStorage.getItem('admin_token') || '') }
      })
    if (resp.ok) {
      pushUsers.value = await resp.json()
    }
  } catch (e) {
    console.error('Failed to load push users', e)
  }
}

async function loadNotifyProviders() {
  try {
    notifyProviders.value = await getAllNotifierProviders()
  } catch (e) {
    console.error('Failed to load providers', e)
  }
}

async function deletePushUser(row: any) {
  try {
    await fetch('/api/admin/push-users/' + row.id, { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + (localStorage.getItem('admin_token') || '') } })
    ElMessage.success('已删除')
    await loadPushUsers()
  } catch (e: any) {
    ElMessage.error(e.message || '删除失败')
  }
}



function getEnabledChannels(row: any): Record<string, any> {
  const ch = row.notify_config?.channels || {}
  // Support both old format ({key: {...params}}) and new format ({key: {}})
  return ch
}

// Filter params for push user form: exclude title/content/level/priority; SMTP only shows 'to'
function getPushUserChannelParams(channelKey: string): any[] {
  const np = notifyProviders.value[channelKey]
  if (!np || !np.params) return []
  const exclude = ['title', 'content', 'level', 'priority']
  return np.params.filter((p: any) => {
    if (exclude.includes(p.key)) return false
    // SMTP: only show 'to' (email recipient)
    if (channelKey === 'smtp' && p.key !== 'to') return false
    return true
  })
}
function getProviderLabel(key: string): string {
  return notifyProviders.value[key]?.label || key
}

function hasEnabledChannels(row: any): boolean {
  return Object.keys(getEnabledChannels(row)).length > 0
}
function getChannelParamsSummary(row: any): string {
  const ch = row.notify_config?.channels || {}
  const keys = Object.keys(ch)
  if (keys.length === 0) return ''
  const params = ch[keys[0]] || {}
  const parts: string[] = []
  for (const [k, v] of Object.entries(params)) {
    if (v && typeof v === 'string') {
      const masked = k.includes('token') || k.includes('key') || k.includes('secret') || k.includes('password')
        ? (v as string).substring(0, 4) + '***'
        : (v as string).length > 20 ? (v as string).substring(0, 20) + '...' : v
      parts.push(masked)
    }
  }
  return parts.join(' | ')
}
// ==================== End Push User Notifications ====================


// ==================== Global Notify Functions ====================

function initGlobalNotifyForm() {
  const channels: Record<string, any> = {}
  for (const [k, np] of Object.entries(notifyProviders.value)) {
    channels[k] = { _enabled: false, _testing: false }
    for (const p of np.params || []) {
      channels[k][p.key] = p.default || ''
    }
  }
  globalNotifyForm.channels = channels
  globalNotifyForm.events = { on_save_success: true, on_save_fail: true, on_cookie_expire: true, on_cleanup: false }
}

async function loadGlobalNotifyConfig() {
  try {
    const resp = await fetch('/api/admin/system-configs', {
        headers: { 'Authorization': 'Bearer ' + (localStorage.getItem('admin_token') || '') }
      })
    const configs = await resp.json() as any[]
    const gcfg = configs.find((c: any) => c.key === 'global_notify_config')
    if (gcfg && gcfg.value) {
      try {
        const parsed = JSON.parse(gcfg.value)
        const nc = parsed.channels || {}
        for (const [k, v] of Object.entries(nc)) {
          if (globalNotifyForm.channels[k]) {
            globalNotifyForm.channels[k]._enabled = true
            for (const [pk, pv] of Object.entries(v as Record<string, any>)) {
              globalNotifyForm.channels[k][pk] = pv
            }
          }
        }
        if (parsed.events) {
          globalNotifyForm.events.on_save_success = parsed.events.on_save_success !== false
          globalNotifyForm.events.on_save_fail = parsed.events.on_save_fail !== false
          globalNotifyForm.events.on_cookie_expire = parsed.events.on_cookie_expire !== false
          globalNotifyForm.events.on_cleanup = parsed.events.on_cleanup === true
        }
        if (parsed.eventTemplates) {
          for (const [ek, et] of Object.entries(parsed.eventTemplates)) {
            globalNotifyForm.eventTemplates[ek] = et as any
          }
        }
      } catch {}
    }
  } catch {}
}

// ── 消息模板编辑器 ──
const templateEditorVisible = ref(false)
const templateEditorKey = ref('')
const templateEditorLabel = computed(() => {
  const labels: Record<string, string> = { on_save_success: '转存成功', on_save_fail: '转存失败', on_cookie_expire: 'Cookie过期', on_cleanup: '清理完成' }
  return labels[templateEditorKey.value] || ''
})
function openTemplateEditor(key: string) {
  templateEditorKey.value = key
  templateEditorVisible.value = true
  if (!globalNotifyForm.eventTemplates[key]) {
    globalNotifyForm.eventTemplates[key] = { title: '', content: '' }
  }
}

async function testGlobalChannel(channelName: string) {
  const ch = globalNotifyForm.channels[channelName]
  if (!ch || !ch._enabled) return
  ch._testing = true
  try {
    // 过滤掉前端标记字段，只传实际参数
    const params: Record<string, any> = {}
    for (const [k, v] of Object.entries(ch)) {
      if (!k.startsWith('_')) params[k] = v
    }
    const result = await testNotifyChannel(channelName, undefined, params)
    if (result.success) {
      ElMessage.success(result.message)
    } else {
      ElMessage.error(result.message)
    }
  } catch (e: any) {
    ElMessage.error(e.message || '测试失败')
  } finally {
    ch._testing = false
  }
}
const passwordForm = reactive({
  oldPassword: '',
  newPassword: '',
  confirmPassword: '',
})
const passwordRules = {
  oldPassword: [{ required: true, message: '请输入原密码', trigger: 'blur' }],
  newPassword: [
    { required: true, message: '请输入新密码', trigger: 'blur' },
    { min: 6, message: '新密码至少需要6个字符', trigger: 'blur' },
  ],
  confirmPassword: [
    { required: true, message: '请确认新密码', trigger: 'blur' },
    {
      validator: (_rule: any, value: string, callback: Function) => {
        if (value !== passwordForm.newPassword) {
          callback(new Error('两次输入的密码不一致'))
        } else {
          callback()
        }
      },
      trigger: 'blur',
    },
  ],
}

onMounted(async () => {
  try {
    rawConfigs.value = await getSystemConfigs()
    for (const cfg of rawConfigs.value) {
      configs[cfg.key] = cfg.value
    }
    // 解析过滤规则为列表
    const rules = String(configs.title_filter_rules || '')
    filterRuleList.value = rules.split('\n').filter(r => r.trim())
    // 解析失效关键词列表
    const invalidKws = String(configs.link_invalid_keywords || '')
    invalidKeywordList.value = invalidKws.split('\n').filter(k => k.trim())
    // 解析广告关键词列表
    const adKws = String(configs.quark_ad_keywords || '')
    adKeywordList.value = adKws.split('\n').filter(k => k.trim())
    // 解析警示文件夹名列表
    const wfNames = String(configs.quark_warning_folder_names || '')
    warningFolderList.value = wfNames.split('\n').filter(k => k.trim())
    // 解析可疑文件后缀列表
    const susExts = String(configs.quark_sus_extensions || '')
    susExtensionList.value = susExts.split('\n').filter(k => k.trim())
  } catch (e) {
    ElMessage.error('加载系统配置失败')
  }
  // 加载数据库状态
  try {
    const status = await getDbStatus()
    Object.assign(dbStatus, status)
  } catch {
    dbStatus.db_size = '无法读取'
  } finally {
    dbLoading.value = false
  }
  // Auto-load PanSou info on page load
  fetchPansouInfo()
  await loadNotifyProviders()
  initGlobalNotifyForm()
  await loadGlobalNotifyConfig()
  loadPushUsers()
  loadDailyReportConfig()
  loadDailyReportLastRun()
  loadPushUserAccountOptions()
})

// Watch for notifyProviders loaded asynchronously — sync global form channels
watch(notifyProviders, () => {
  for (const [k, np] of Object.entries(notifyProviders.value)) {
    if (!globalNotifyForm.channels[k]) {
      globalNotifyForm.channels[k] = { _enabled: false, _testing: false }
      for (const p of np.params || []) {
        globalNotifyForm.channels[k][p.key] = p.default || ''
      }
    }
  }
})

async function handleTestRedis() {
  const url = String(configs.redis_url || 'redis://redis:6379')
  redisTesting.value = true
  try {
    const result = await testRedisConnection(url)
    if (result.ok) {
      ElMessage.success(`✅ Redis 连接成功 — ${result.info}`)
    } else {
      ElMessage.error(`❌ Redis 连接失败 — ${result.info}`)
    }
  } catch (e: any) {
    ElMessage.error(e.response?.data?.error || '测试请求失败')
  } finally {
    redisTesting.value = false
  }
}

async function fetchPansouInfo() {
  pansouInfoLoading.value = true
  try {
    const token = localStorage.getItem('admin_token')
    const headers: Record<string, string> = {}
    if (token) {
      headers['Authorization'] = 'Bearer ' + token
    }
    const res = await fetch('/api/admin/pansou-info', { headers })
    if (!res.ok) {
      throw new Error('HTTP ' + res.status)
    }
    const data = await res.json()
    pansouInfo.value = data
  } catch {
    pansouInfo.value = { status: "disconnected", version: "-", channelCount: 0, pluginCount: 0, diskCount: 0 }
  } finally {
    pansouInfoLoading.value = false
  }
}
async function handleUpdatePansou() {
  pansouUpdating.value = true
  try {
    const token = localStorage.getItem('admin_token')
    const headers: Record<string, string> = { 'Content-Type': 'application/json' }
    if (token) headers['Authorization'] = 'Bearer ' + token
    const res = await fetch('/api/admin/update-pansou', { method: 'POST', headers })
    const data = await res.json()
    if (data.ok) {
      ElMessage.success('✅ PanSou 已更新并重启')
      setTimeout(() => fetchPansouInfo(), 3000)
    } else {
      ElMessage.error('❌ 更新失败 — ' + (data.error || '未知错误'))
    }
  } catch (e: any) {
    ElMessage.error(e.message || '更新请求失败')
  } finally {
    pansouUpdating.value = false
  }
}

async function handleTestPansou() {
  pansouTesting.value = true
  try {
    const result = await testExternalService({ type: 'pansou', url: String(configs.pansou_url || '') })
    if (result.ok) {
      fetchPansouInfo()
      ElMessage.success(`✅ PanSou 连接成功 — ${result.info}`)
    } else {
      ElMessage.error(`❌ PanSou 连接失败 — ${result.info}`)
    }
  } catch (e: any) {
    ElMessage.error(e.response?.data?.error || '测试请求失败')
  } finally {
    pansouTesting.value = false
  }
}

async function handleTestVideoParser() {
  videoParserTesting.value = true
  try {
    const result = await testExternalService({ type: 'video_parser', url: String(configs.video_parser_url || '') })
    if (result.ok) {
      ElMessage.success(`✅ 视频解析服务连接成功 — ${result.info}`)
    } else {
      ElMessage.error(`❌ 视频解析服务连接失败 — ${result.info}`)
    }
  } catch (e: any) {
    ElMessage.error(e.response?.data?.error || '测试请求失败')
  } finally {
    videoParserTesting.value = false
  }
}

async function handleTestTmdb() {
  tmdbTesting.value = true
  try {
    const result = await testExternalService({ type: 'tmdb', token: String(configs.tmdb_api_token || '') })
    if (result.ok) {
      ElMessage.success(`✅ TMDB 令牌有效 — ${result.info}`)
    } else {
      ElMessage.error(`❌ TMDB 连接失败 — ${result.info}`)
    }
  } catch (e: any) {
    ElMessage.error(e.response?.data?.error || '测试请求失败')
  } finally {
    tmdbTesting.value = false
  }
}

async function handleTestProxy() {
  proxyTesting.value = true
  try {
    const result = await testExternalService({ type: 'proxy', url: String(configs.search_proxy_url || '') })
    if (result.ok) {
      ElMessage.success(`✅ 搜索代理可用 — ${result.info}`)
    } else {
      ElMessage.error(`❌ 搜索代理不可用 — ${result.info}`)
    }
  } catch (e: any) {
    ElMessage.error(e.response?.data?.error || '测试请求失败')
  } finally {
    proxyTesting.value = false
  }
}

async function handleTestIpGeo() {
  ipGeoTesting.value = true
  try {
    const apiId = String(configs.ip_geo_api_id || "")
    if (!apiId) {
      ElMessage.warning("请先输入 API ID")
      return
    }
    const result = await testExternalService({ type: "ip_geo", url: apiId })
    if (result.ok) {
      ElMessage.success("✅ IP 归属地接口可用 — " + result.info)
    } else {
      ElMessage.error("❌ IP 归属地接口不可用 — " + result.info)
    }
  } catch (e: any) {
    ElMessage.error(e.response?.data?.error || "测试请求失败")
  } finally {
    ipGeoTesting.value = false
  }
}

// ── 标题过滤规则管理 ──
function batchAddFilterRules() {
  const raw = filterRuleBatchInput.value.trim()
  if (!raw) return
  const items = raw.split('\n').map(s => s.trim()).filter(s => s)
  let added = 0
  for (const item of items) {
    if (!filterRuleList.value.includes(item)) {
      filterRuleList.value.push(item)
      added++
    }
  }
  filterRuleBatchInput.value = ''
  syncFilterRules()
  if (added > 0) ElMessage.success(`已添加 ${added} 条规则`)
  else ElMessage.info('所有规则已存在')
}

function removeFilterRule(index: number) {
  filterRuleList.value.splice(index, 1)
  syncFilterRules()
}

function ruleTagType(rule: string): string {
  if (rule.startsWith('#')) return 'info'
  if (rule.startsWith('/') && (rule.endsWith('/') || rule.endsWith('/g') || rule.endsWith('/i') || rule.endsWith('/gi'))) return 'warning'
  return ''
}

function syncFilterRules() {
  configs.title_filter_rules = filterRuleList.value.join('\n')
}

// ── 失效关键词管理 ──
function batchAddInvalidKeywords() {
  const raw = invalidKeywordBatchInput.value.trim()
  if (!raw) return
  const items = raw.split('\n').map(s => s.trim()).filter(s => s)
  let added = 0
  for (const item of items) {
    if (!invalidKeywordList.value.includes(item)) {
      invalidKeywordList.value.push(item)
      added++
    }
  }
  invalidKeywordBatchInput.value = ''
  syncInvalidKeywords()
  if (added > 0) ElMessage.success(`已添加 ${added} 个关键词`)
  else ElMessage.info('所有关键词已存在')
}
function removeInvalidKeyword(index: number) {
  invalidKeywordList.value.splice(index, 1)
  syncInvalidKeywords()
}
function syncInvalidKeywords() {
  configs.link_invalid_keywords = invalidKeywordList.value.join('\n')
}

// ── 广告关键词管理 ──
function batchAddAdKeywords() {
  const raw = adKeywordBatchInput.value.trim()
  if (!raw) return
  const items = raw.split('\n').map(s => s.trim()).filter(s => s)
  let added = 0
  for (const item of items) {
    if (!adKeywordList.value.includes(item)) {
      adKeywordList.value.push(item)
      added++
    }
  }
  adKeywordBatchInput.value = ''
  syncAdKeywords()
  if (added > 0) ElMessage.success()
  else ElMessage.info('所有关键词已存在')
}
function removeAdKeyword(index: number) {
  adKeywordList.value.splice(index, 1)
  syncAdKeywords()
}
function syncAdKeywords() {
  configs.quark_ad_keywords = adKeywordList.value.join('\n')
}

// ── 警示文件夹名管理 ──
function batchAddWarningFolders() {
  const raw = warningFolderBatchInput.value.trim()
  if (!raw) return
  const items = raw.split('\n').map(s => s.trim()).filter(s => s)
  let added = 0
  for (const item of items) {
    if (!warningFolderList.value.includes(item)) {
      warningFolderList.value.push(item)
      added++
    }
  }
  warningFolderBatchInput.value = ''
  syncWarningFolders()
  if (added > 0) ElMessage.success()
  else ElMessage.info('所有文件夹名已存在')
}
function removeWarningFolder(index: number) {
  warningFolderList.value.splice(index, 1)
  syncWarningFolders()
}
function syncWarningFolders() {
  configs.quark_warning_folder_names = warningFolderList.value.join('\n')
}

// ── 可疑文件后缀管理 ──
function batchAddSusExtensions() {
  const raw = susExtensionBatchInput.value.trim()
  if (!raw) return
  const items = raw.split('\n').map(s => s.trim().toLowerCase().replace(/^\./, '')).filter(s => s)
  let added = 0
  for (const item of items) {
    if (!susExtensionList.value.includes(item)) {
      susExtensionList.value.push(item)
      added++
    }
  }
  susExtensionBatchInput.value = ''
  syncSusExtensions()
  if (added > 0) ElMessage.success('已添加 ${added} 个后缀')
  else ElMessage.info('所有后缀已存在')
}
function removeSusExtension(index: number) {
  susExtensionList.value.splice(index, 1)
  syncSusExtensions()
}
function syncSusExtensions() {
  configs.quark_sus_extensions = susExtensionList.value.join('\n')
}

function handleLogout() {
  localStorage.removeItem("admin_token")
  router.push("/admin/login")
}

async function handleSave() {
  saving.value = true
  try {
    // Build global_notify_config from form
    const channels: Record<string, any> = {}
    for (const [k, v] of Object.entries(globalNotifyForm.channels)) {
      if ((v as any)._enabled) {
        const params: Record<string, string> = {}
        for (const [pk, pv] of Object.entries(v as Record<string, any>)) {
          if (!pk.startsWith('_') && pv !== '') params[pk] = String(pv)
        }
        if (Object.keys(params).length > 0) channels[k] = params
      }
    }
    const entries = rawConfigs.value.map(cfg => ({
      key: cfg.key,
      value: String(configs[cfg.key] ?? cfg.value),
    }))
    await saveDailyReportConfig()
    // Add global_notify_config as JSON entry
    entries.push({
      key: 'global_notify_config',
      value: JSON.stringify({ channels, events: globalNotifyForm.events, eventTemplates: globalNotifyForm.eventTemplates }),
    })
    await updateSystemConfigs(entries)
    ElMessage.success('配置已保存')
  } catch (e: any) {
    ElMessage.error(e.response?.data?.error || '保存失败')
  } finally {
    saving.value = false
  }
}

async function handleChangePassword() {
  const valid = await passwordFormRef.value?.validate().catch(() => false)
  if (!valid) return

  changingPassword.value = true
  try {
    const result = await changePasswordApi(passwordForm.oldPassword, passwordForm.newPassword)
    if (result.success) {
      ElMessage.success('✅ 密码修改成功，下次登录请使用新密码')
      passwordForm.oldPassword = ''
      passwordForm.newPassword = ''
      passwordForm.confirmPassword = ''
    } else {
      ElMessage.error(result.message)
    }
  } catch (e: any) {
    ElMessage.error(e.response?.data?.error || '密码修改失败')
  } finally {
    changingPassword.value = false
  }
}

const fileInputRef = ref<HTMLInputElement>()

function triggerFileInput() {
  fileInputRef.value?.click()
}

async function handleFileSelect(event: Event) {
  const target = event.target as HTMLInputElement
  const file = target.files?.[0]
  if (!file) return

  if (!file.type.startsWith('image/')) {
    ElMessage.error('仅支持图片文件（JPEG/PNG/WebP）')
    target.value = ''
    return
  }

  if (file.size > 2 * 1024 * 1024) {
    ElMessage.error('图片大小不能超过 2MB')
    target.value = ''
    return
  }

  try {
    const result = await uploadFallbackImage(file)
    if (result.success) {
      configs.search_fallback_image = result.url
      ElMessage.success('✅ 兜底图已上传并生效')
    } else {
      ElMessage.error(result.message)
    }
  } catch (e: any) {
    ElMessage.error(e.response?.data?.error || '上传失败')
  }
  target.value = ''
}

async function handleRemoveFallback() {
  try {
    configs.search_fallback_image = ''
    await updateSetting('search_fallback_image', '')
    ElMessage.success('已移除兜底图')
  } catch (e: any) {
    ElMessage.error('移除失败')
  }
}

const logoInputRef = ref<HTMLInputElement>()

function triggerLogoInput() {
  logoInputRef.value?.click()
}

async function handleLogoSelect(event: Event) {
  const target = event.target as HTMLInputElement
  const file = target.files?.[0]
  if (!file) return

  if (!file.type.startsWith('image/')) {
    ElMessage.error('仅支持图片文件（JPEG/PNG/WebP）')
    target.value = ''
    return
  }

  if (file.size > 2 * 1024 * 1024) {
    ElMessage.error('图片大小不能超过 2MB')
    target.value = ''
    return
  }

  try {
    const result = await uploadLogo(file)
    if (result.success) {
      configs.site_logo = result.url
      ElMessage.success('✅ LOGO 已上传并生效')
    } else {
      ElMessage.error(result.message)
    }
  } catch (e: any) {
    ElMessage.error(e.response?.data?.error || '上传失败')
  }
  target.value = ''
}

async function handleRemoveLogo() {
  try {
    configs.site_logo = ''
    await updateSetting('site_logo', '')
    ElMessage.success('已移除 LOGO')
  } catch (e: any) {
    ElMessage.error('移除失败')
  }
}
</script>

<style scoped>
.system-config {
}
.el-card {
  margin-bottom: 20px;
}
.el-card :deep(.el-card__header) {
  font-weight: 600;
  font-size: 15px;
}

/* el-divider 内容左对齐 */
:deep(.el-divider__text.is-left) {
  left: 0;
  padding-left: 0;
}

.form-tip {
  font-size: 12px;
  color: #909399;
  margin-top: 4px;
}
.fallback-upload-wrap {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.fallback-upload-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
}
.fallback-preview {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.fallback-preview img {
  max-width: 100%;
  height: auto;
  max-height: 120px;
  border-radius: 8px;
  border: 1px solid var(--border-color);
  background: #f0f0f0;
  object-fit: contain;
}

/* ── 搜索策略 3列网格 ── */
.strategy-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 12px 16px;
}
.grid-cell {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* ── 策略卡片内的字段块 ── */
.strategy-section {
  padding: 0 4px;
}
.field-block {
  margin: 12px 0;
}
.field-label-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.field-label {
  font-size: 14px;
  font-weight: 500;
  color: #303133;
  white-space: nowrap;
}
.field-desc {
  font-size: 12px;
  color: #909399;
  margin: 3px 0 0 0;
  line-height: 1.5;
}

/* ── 关键词输入行 ── */
.keyword-input-row {
  display: flex;
  gap: 8px;
  flex: 1;
  min-width: 200px;
}
.tag-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 8px;
}
.tag-empty {
  font-size: 13px;
  color: #c0c4cc;
  margin-top: 8px;
}

/* ── 标题过滤规则说明（嵌入链接检测配置内） ── */
.filter-rule-help {
  margin-top: 8px;
  padding: 10px 12px;
  background: #f8f9fa;
  border-radius: 8px;
  border: 1px solid #e8e8e8;
}
.filter-rule-help .help-title {
  font-weight: 600;
  font-size: 13px;
  margin: 8px 0 4px;
  color: #333;
}
.filter-rule-help .help-title:first-child {
  margin-top: 0;
}
.filter-rule-help .help-row {
  font-size: 12px;
  color: #555;
  margin: 3px 0;
  line-height: 1.6;
}
.filter-rule-help .help-row code {
  background: #eef1f5;
  padding: 1px 5px;
  border-radius: 3px;
  font-size: 11px;
  font-family: monospace;
}

/* 标题过滤规则帮助 */
.filter-rules-help {
  margin-top: 8px;
  padding: 12px;
  background: #f8f9fa;
  border-radius: 8px;
  border: 1px solid #e8e8e8;
}
.help-title {
  font-weight: 600;
  font-size: 13px;
  margin: 10px 0 6px;
  color: #333;
}
.help-title:first-child {
  margin-top: 0;
}
.help-row {
  font-size: 12px;
  color: #555;
  margin: 3px 0;
  line-height: 1.6;
}
.help-row code {
  background: #eef1f5;
  padding: 1px 5px;
  border-radius: 3px;
  font-size: 11px;
  font-family: monospace;
}
.help-sample {
  background: #1e1e1e;
  color: #d4d4d4;
  padding: 10px 14px;
  border-radius: 6px;
  font-size: 12px;
  line-height: 1.6;
  overflow-x: auto;
  white-space: pre;
  margin: 6px 0 0;
  font-family: monospace;
}
.help-preview-row {
  font-size: 13px;
  margin: 4px 0;
  display: flex;
  align-items: center;
  gap: 6px;
}
.help-preview-label {
  color: #888;
  min-width: 70px;
  font-size: 12px;
}
.help-preview-original {
  color: #e74c3c;
}
.help-preview-filtered {
  color: #27ae60;
  font-weight: 500;
}

/* ── 过滤规则输入行 ── */
.filter-input-row {
  display: flex;
  gap: 8px;
  width: 100%;
  margin-bottom: 8px;
}
.filter-tag-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 8px;
}
.filter-empty {
  font-size: 13px;
  color: #c0c4cc;
  padding: 8px 0;
  margin-bottom: 8px;
}

/* 数据库状态网格 */
.db-status-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 12px;
  margin-top: 8px;
}

.db-stat-item {
  background: #f8f9fa;
  border-radius: 10px;
  padding: 16px 12px;
  text-align: center;
  border: 1px solid #eee;
  transition: transform 0.15s, box-shadow 0.15s;
}
.db-stat-item:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0,0,0,0.06);
}
.db-stat-value {
  white-space: nowrap;
  font-size: 24px;
  font-weight: 700;
  color: #303133;
  margin-bottom: 4px;
}
.db-stat-value.text-success { color: #67c23a; }
.db-stat-value.text-warning { color: #e6a23c; }
.db-stat-label {
  font-size: 12px;
  color: #909399;
}

/* 响应式：小屏幕降为2列或1列 */
@media (max-width: 900px) {
  .strategy-grid {
    grid-template-columns: 1fr 1fr;
  }
}
@media (max-width: 600px) {
  .strategy-grid {
    grid-template-columns: 1fr;
  }
}

/* PanSou info panel */
.pansou-status-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 12px;
  margin-top: 8px;
}
.status-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  display: inline-block;
}
.dot-ok { background: #67c23a; }
.dot-err { background: #f56c6c; }

/* 事件开关卡片高亮 */
.event-card.active {
  border-color: var(--el-color-primary) !important;
  background: var(--el-color-primary-light-9);
}
.event-card {
  cursor: default;
}


</style>
