<template>
  <div v-if="isInitialLoading" class="admin-loading">
    <div class="spinner" />
    <p>正在加载监控数据...</p>
  </div>
  <div v-else class="admin-page">
    <div class="secondary-overlay" v-if="!secondaryVerified">
      <div class="secondary-card">
        <h3>请输入管理员二级密码</h3>
        <p class="muted">为保护敏感监控数据，需二级校验后查看。</p>
        <input
          class="secondary-input"
          type="password"
          v-model="secondaryPassword"
          :disabled="secondaryLoading"
          placeholder="二级密码"
          @keyup.enter="handleVerifySecondary"
        />
        <div class="secondary-actions">
          <button type="button" :disabled="secondaryLoading" @click="handleVerifySecondary">
            {{ secondaryLoading ? '校验中...' : '确认进入' }}
          </button>
          <button
            type="button"
            class="ghost-btn"
            :disabled="secondaryLoading"
            @click="checkSecondary"
          >
            重新检测
          </button>
        </div>
        <p v-if="secondaryError" class="secondary-error">{{ secondaryError }}</p>
      </div>
    </div>
    <header class="admin-header">
      <div>
        <h1>管理员监控面板</h1>
        <p>最近更新：{{ timeAgo(snapshot?.generated_at || overview.generated_at) }}</p>
      </div>
      <div class="header-actions">
        <label> <input type="checkbox" v-model="autoRefresh" /> 自动刷新 </label>
        <button type="button" :disabled="refreshing" @click="handleManualRefresh">
          {{ refreshing ? '刷新中...' : '立即刷新' }}
        </button>
        <a class="link-btn" href="/admin/policy" target="_blank" rel="noopener">策略配置</a>
        <a class="link-btn" href="/admin/api" target="_blank" rel="noopener">API 管理</a>
      </div>
    </header>

    <section v-if="bannerError" class="banner-error">
      <strong>刷新失败：</strong>
      <span>{{ bannerError }}</span>
    </section>

    <div class="admin-layout" v-if="snapshot">
      <aside class="admin-sidebar">
        <button
          v-for="tab in sectionTabs"
          :key="tab.id"
          type="button"
          :class="['sidebar-tab', { active: activeSection === tab.id }]"
          @click="activeSection = tab.id"
        >
          {{ tab.label }}
        </button>
      </aside>
      <main class="admin-main">
        <transition name="fade" mode="out-in">
          <section v-if="activeSection === 'overview'" key="overview" class="panel">
            <div class="metrics-grid">
              <div v-for="card in metricCards" :key="card.title" class="metric-card">
                <h3>{{ card.title }}</h3>
                <strong>{{ card.value }}</strong>
                <span>{{ card.sub }}</span>
              </div>
            </div>
          </section>
          <section v-else-if="activeSection === 'usage'" key="usage" class="panel">
            <h2>模型配额与趋势</h2>
            <div class="stats-row">
              <span>快速模式：{{ usageTotals.fast || 0 }}</span>
              <span>思考模式：{{ usageTotals.thinking || 0 }}</span>
              <span>搜索：{{ usageTotals.search || 0 }}</span>
            </div>
            <div class="token-summary">
              <div class="token-card">
                <p>累计输入 Token</p>
                <strong>{{ formatNumber(tokenTotals.input_tokens) }}</strong>
              </div>
              <div class="token-card">
                <p>累计输出 Token</p>
                <strong>{{ formatNumber(tokenTotals.output_tokens) }}</strong>
              </div>
              <div class="token-card">
                <p>总计 Token</p>
                <strong>{{ formatNumber(tokenTotals.total_tokens) }}</strong>
              </div>
            </div>
            <div class="token-breakdown">
              <div class="token-breakdown-header">
                <h3>按用户累计 Token</h3>
                <span>实时拉取 · 按总量降序</span>
              </div>
              <div class="token-table-wrapper" v-if="tokenBreakdown.length">
                <table class="token-table">
                  <thead>
                    <tr>
                      <th>用户</th>
                      <th>角色</th>
                      <th>输入 Token</th>
                      <th>输出 Token</th>
                      <th>累计 Token</th>
                    </tr>
                  </thead>
                  <tbody>
                    <tr v-for="entry in tokenBreakdown" :key="entry.username">
                      <td>{{ entry.username }}</td>
                      <td>{{ entry.role }}</td>
                      <td>{{ formatNumber(entry.input) }}</td>
                      <td>{{ formatNumber(entry.output) }}</td>
                      <td>
                        <strong>{{ formatNumber(entry.total) }}</strong>
                      </td>
                    </tr>
                  </tbody>
                </table>
              </div>
              <p class="token-empty" v-else>暂无 token 统计数据</p>
            </div>
            <div class="usage-leaders">
              <div v-for="block in leaderBlocks" :key="block.key" class="leader-card">
                <h3>{{ block.label }}</h3>
                <ul>
                  <li v-if="!block.items.length" class="muted">暂无数据</li>
                  <li v-for="item in block.items" :key="item.username">
                    <strong>{{ item.username }}</strong>
                    <span>{{ item.count }} / {{ item.limit ?? '∞' }}</span>
                  </li>
                </ul>
              </div>
            </div>
          </section>
          <section v-else-if="activeSection === 'users'" key="users" class="panel">
            <h2>用户与配额</h2>
            <div class="table-wrapper">
              <table>
                <thead>
                  <tr>
                    <th>用户</th>
                    <th>角色</th>
                    <th>状态</th>
                    <th>项目占用</th>
                    <th>Fast</th>
                    <th>Thinking</th>
                    <th>Search</th>
                    <th>项目体积</th>
                    <th>最近活跃</th>
                  </tr>
                </thead>
                <tbody>
                  <tr v-if="!users.length">
                    <td colspan="9">暂无用户</td>
                  </tr>
                  <tr v-for="user in users" :key="user.username">
                    <td>
                      <strong>{{ user.username }}</strong>
                      <div class="subtext">{{ user.email || '—' }}</div>
                    </td>
                    <td>{{ user.role || 'user' }}</td>
                    <td>
                      <span :class="['status-badge', user.status?.online ? 'online' : 'offline']">
                        {{ user.status?.online ? '在线' : '离线' }}
                      </span>
                    </td>
                    <td>
                      <span :class="['status-badge', usageBadge(user.storage?.status)]">
                        {{ formatPercent(user.storage?.usage_percent) }}
                      </span>
                      <div class="progress-bar">
                        <span :style="{ width: percentWidth(user.storage?.usage_percent) }" />
                      </div>
                    </td>
                    <td>{{ user.usage?.fast?.count || 0 }}</td>
                    <td>{{ user.usage?.thinking?.count || 0 }}</td>
                    <td>{{ user.usage?.search?.count || 0 }}</td>
                    <td>{{ formatBytes(user.storage?.project_bytes) }}</td>
                    <td>{{ timeAgo(user.status?.last_active) }}</td>
                  </tr>
                </tbody>
              </table>
            </div>
          </section>
          <section v-else-if="activeSection === 'containers'" key="containers" class="panel">
            <h2>容器运行状态</h2>
            <div class="table-wrapper">
              <table>
                <thead>
                  <tr>
                    <th>用户</th>
                    <th>模式</th>
                    <th>容器名</th>
                    <th>CPU</th>
                    <th>内存%</th>
                    <th>内存使用</th>
                    <th>内存上限</th>
                    <th>网络 RX/TX</th>
                    <th>状态</th>
                    <th>最近活跃</th>
                  </tr>
                </thead>
                <tbody>
                  <tr v-if="!containers.length">
                    <td colspan="10">暂无容器运行</td>
                  </tr>
                  <tr v-for="item in containers" :key="item.username">
                    <td>{{ item.username }}</td>
                    <td>{{ item.mode || 'host' }}</td>
                    <td>{{ item.container_name || '—' }}</td>
                    <td>{{ formatPercentNumber(item.stats?.cpu_percent) }}</td>
                    <td>{{ formatPercentNumber(item.stats?.memory?.percent) }}</td>
                    <td>{{ formatBytes(item.stats?.memory?.used_bytes) }}</td>
                    <td>{{ formatBytes(item.stats?.memory?.limit_bytes) }}</td>
                    <td>
                      {{ formatBytes(item.stats?.net_io?.rx_bytes) }} /
                      {{ formatBytes(item.stats?.net_io?.tx_bytes) }}
                    </td>
                    <td>
                      <span :class="['status-badge', containerStatusClass(item)]">
                        {{ item.state?.status || (item.state?.running ? '运行中' : '未知') }}
                      </span>
                    </td>
                    <td>{{ timeAgo(item.last_active) }}</td>
                  </tr>
                </tbody>
              </table>
            </div>
          </section>
          <section v-else-if="activeSection === 'uploads'" key="uploads" class="panel">
            <h2>上传安全审计</h2>
            <div class="stats-row">
              <span>最近24小时：{{ uploadStats.last_24h || 0 }}</span>
              <span>阻断：{{ uploadStats.blocked_last_24h || 0 }}</span>
              <span>跳过扫描：{{ uploadStats.skipped_scan_last_24h || 0 }}</span>
              <span>隔离区占用：{{ formatBytes(uploadStats.quarantine_bytes) }}</span>
            </div>
            <div class="stats-row">
              <span v-if="!uploadSources.length">暂无来源统计</span>
              <span v-for="source in uploadSources" :key="source.source">
                {{ source.source }}：{{ source.count }}
              </span>
            </div>
            <ul class="upload-feed">
              <li v-if="!recentUploads.length" class="upload-item">暂无近期上传</li>
              <li v-for="upload in recentUploads" :key="upload.upload_id" class="upload-item">
                <div>
                  <strong>{{ upload.original_name || '未命名文件' }}</strong>
                  <div class="upload-meta">
                    <span>用户：{{ upload.username }}</span>
                    <span>来源：{{ upload.source || 'unknown' }}</span>
                    <span>大小：{{ formatBytes(upload.size) }}</span>
                  </div>
                </div>
                <div class="upload-meta">
                  <span>{{ timeAgo(upload.timestamp) }}</span>
                  <span :class="['status-badge', upload.accepted ? 'online' : 'danger']">
                    {{ upload.accepted ? '通过' : '阻断' }}
                  </span>
                  <span v-if="upload.error?.message" class="status-badge danger">{{
                    upload.error.message
                  }}</span>
                </div>
              </li>
            </ul>
          </section>
          <section v-else-if="activeSection === 'balance'" key="balance" class="panel">
            <div class="balance-header">
              <div>
                <h2>余额查询</h2>
                <p class="muted">手动刷新，查看 Kimi / DeepSeek / Qwen 余额</p>
              </div>
              <div class="balance-actions">
                <span class="muted" v-if="balanceUpdatedAt"
                  >上次刷新：{{ timeAgo(balanceUpdatedAt) }}</span
                >
                <button type="button" :disabled="balanceLoading" @click="fetchBalance">
                  {{ balanceLoading ? '查询中...' : '手动刷新' }}
                </button>
              </div>
            </div>
            <section v-if="balanceError" class="banner-error">
              <strong>查询失败：</strong>
              <span>{{ balanceError }}</span>
            </section>
            <div class="balance-grid">
              <div
                v-for="card in balanceCards"
                :key="card.key"
                class="balance-card"
                :class="{ error: !card.success }"
              >
                <div class="balance-card-head">
                  <h3>{{ card.label }}</h3>
                  <span v-if="card.success" class="status-badge online">正常</span>
                  <span v-else class="status-badge danger">失败</span>
                </div>
                <div class="balance-amount">
                  <strong v-if="card.amount !== null">{{ card.amount }} {{ card.currency }}</strong>
                  <strong v-else>—</strong>
                  <span v-if="card.amountCny !== null" class="sub">≈ ¥{{ card.amountCny }}</span>
                </div>
                <ul class="balance-meta" v-if="card.success">
                  <li v-if="card.extras.voucher !== undefined">券：{{ card.extras.voucher }}</li>
                  <li v-if="card.extras.cash !== undefined">现金：{{ card.extras.cash }}</li>
                  <li v-if="card.extras.granted !== undefined">赠送：{{ card.extras.granted }}</li>
                  <li v-if="card.extras.topped_up !== undefined">
                    充值：{{ card.extras.topped_up }}
                  </li>
                  <li v-if="card.extras.rate">汇率 USD→CNY：{{ card.extras.rate }}</li>
                </ul>
                <p v-else class="muted small">{{ card.error || '未知错误' }}</p>
              </div>
            </div>
          </section>
          <section v-else-if="activeSection === 'invites'" key="invites" class="panel">
            <h2>邀请码</h2>
            <div class="stats-row">
              <span>总计：{{ inviteSummary.total || 0 }}</span>
              <span>可用：{{ inviteSummary.active || 0 }}</span>
              <span>已用：{{ inviteSummary.consumed || 0 }}</span>
              <span>无限制：{{ inviteSummary.unlimited || 0 }}</span>
            </div>
            <div class="invite-manage">
              <input
                v-model.trim="newInviteCode"
                class="invite-input"
                type="text"
                placeholder="新邀请码"
                :disabled="inviteSubmitting"
              />
              <input
                v-model="newInviteRemaining"
                class="invite-input short"
                type="number"
                min="0"
                placeholder="剩余次数"
                :disabled="inviteSubmitting || newInviteUnlimited"
              />
              <label class="invite-check">
                <input type="checkbox" v-model="newInviteUnlimited" :disabled="inviteSubmitting" />
                不限次数
              </label>
              <button type="button" :disabled="inviteSubmitting" @click="createInvite">
                {{ inviteSubmitting ? '处理中...' : '新增邀请码' }}
              </button>
            </div>
            <p v-if="inviteError" class="secondary-error">{{ inviteError }}</p>
            <div class="invite-grid">
              <div v-if="!inviteCodes.length" class="invite-card">暂无邀请码数据</div>
              <div v-for="code in inviteCodes" :key="code.code" class="invite-card">
                <h4>{{ code.code }}</h4>
                <span>{{ inviteStatus(code.remaining) }}</span>
                <div class="invite-actions">
                  <button
                    type="button"
                    class="mini-btn"
                    :disabled="inviteSubmitting"
                    @click="editInvite(code)"
                  >
                    调整次数
                  </button>
                  <button
                    type="button"
                    class="mini-btn danger"
                    :disabled="inviteSubmitting"
                    @click="removeInvite(code.code)"
                  >
                    删除
                  </button>
                </div>
              </div>
            </div>
          </section>
          <section v-else-if="activeSection === 'passwords'" key="passwords" class="panel">
            <h2>密码管理</h2>
            <p class="muted">管理员可重置任意用户的登录密码（包括自己）。</p>

            <div class="password-form">
              <div class="password-field">
                <label>选择用户</label>
                <div class="user-search-wrapper" ref="userSearchRef">
                  <div
                    class="user-search-trigger"
                    :class="{ open: passwordDropdownOpen, filled: passwordTargetUser }"
                    @click="togglePasswordDropdown"
                  >
                    <span v-if="passwordTargetUser" class="user-search-selected">
                      <strong>{{ passwordTargetUser }}</strong>
                      <span class="user-search-email">{{ selectedUserEmail }}</span>
                    </span>
                    <span v-else class="user-search-placeholder">搜索用户名或邮箱...</span>
                    <span class="user-search-arrow">▾</span>
                  </div>
                  <div v-if="passwordDropdownOpen" class="user-search-dropdown">
                    <input
                      ref="passwordSearchInputRef"
                      class="user-search-input"
                      type="text"
                      v-model="passwordSearchQuery"
                      placeholder="输入关键词筛选..."
                      @keydown.escape="passwordDropdownOpen = false"
                    />
                    <div class="user-search-list">
                      <div
                        v-for="user in filteredUsers"
                        :key="user.username"
                        class="user-search-item"
                        :class="{ active: passwordTargetUser === user.username }"
                        @click="selectPasswordUser(user)"
                      >
                        <span class="user-search-name">{{ user.username }}</span>
                        <span class="user-search-email">{{ user.email || '—' }}</span>
                        <span v-if="user.role === 'admin'" class="user-search-role">管理员</span>
                      </div>
                      <div v-if="!filteredUsers.length" class="user-search-empty">
                        无匹配用户
                      </div>
                    </div>
                  </div>
                </div>
              </div>
              <div class="password-field">
                <label>新密码（至少 8 位）</label>
                <input
                  class="password-input"
                  type="password"
                  v-model="passwordNewValue"
                  :disabled="passwordSubmitting"
                  placeholder="输入新密码"
                  @keyup.enter="handleResetPassword"
                />
              </div>
              <div class="password-actions">
                <button
                  type="button"
                  :disabled="!passwordTargetUser || !passwordNewValue || passwordSubmitting"
                  @click="handleResetPassword"
                >
                  {{ passwordSubmitting ? '重置中...' : '重置密码' }}
                </button>
                <button
                  type="button"
                  class="ghost-btn"
                  :disabled="passwordSubmitting"
                  @click="passwordTargetUser = ''; passwordNewValue = ''; passwordResult = ''; passwordError = ''"
                >
                  清空
                </button>
              </div>
              <p v-if="passwordResult" class="password-success">{{ passwordResult }}</p>
              <p v-if="passwordError" class="password-error-msg">{{ passwordError }}</p>
            </div>
          </section>
        </transition>
      </main>
    </div>

    <div v-else class="admin-error">
      <strong>加载失败</strong>
      <p>{{ errorMessage || '无法加载监控数据' }}</p>
      <button type="button" @click="handleRetry">重试</button>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted, onBeforeUnmount, watch } from 'vue';
import { useSecondaryPass } from './useSecondaryPass';

type Snapshot = Record<string, any> | null;

type SectionId = 'overview' | 'usage' | 'users' | 'containers' | 'uploads' | 'balance' | 'invites' | 'passwords';

const {
  verified: secondaryVerified,
  loading: secondaryLoading,
  error: secondaryError,
  check: checkSecondary,
  verify: verifySecondary
} = useSecondaryPass();
const secondaryPassword = ref('');

const loading = ref(true);
const refreshing = ref(false);
const snapshot = ref<Snapshot>(null);
const errorMessage = ref<string | null>(null);
const bannerError = ref<string | null>(null);
const autoRefresh = ref(true);
const activeSection = ref<SectionId>('overview');
let timer: number | undefined;
const balanceLoading = ref(false);
const balanceError = ref<string | null>(null);
const balanceData = ref<Record<string, any> | null>(null);
const balanceUpdatedAt = ref<number | null>(null);
const inviteSubmitting = ref(false);
const inviteError = ref<string | null>(null);
const newInviteCode = ref('');
const newInviteRemaining = ref('1');
const newInviteUnlimited = ref(false);
const passwordTargetUser = ref('');
const passwordNewValue = ref('');
const passwordSubmitting = ref(false);
const passwordResult = ref('');
const passwordError = ref('');
const passwordSearchQuery = ref('');
const passwordDropdownOpen = ref(false);
const userSearchRef = ref<HTMLElement | null>(null);
const passwordSearchInputRef = ref<HTMLInputElement | null>(null);

const sectionTabs: Array<{ id: SectionId; label: string }> = [
  { id: 'overview', label: '系统总览' },
  { id: 'usage', label: '配额趋势' },
  { id: 'users', label: '用户' },
  { id: 'containers', label: '容器' },
  { id: 'uploads', label: '上传审计' },
  { id: 'balance', label: '余额查询' },
  { id: 'invites', label: '邀请码' },
  { id: 'passwords', label: '密码管理' }
];

const isInitialLoading = computed(() => loading.value && !snapshot.value);

const fetchDashboard = async (background = false) => {
  if (!secondaryVerified.value) {
    loading.value = false;
    return;
  }
  if (background) {
    refreshing.value = true;
  } else if (!snapshot.value) {
    loading.value = true;
  }
  try {
    const resp = await fetch('/api/admin/dashboard', { credentials: 'same-origin' });
    if (!resp.ok) {
      throw new Error(`请求失败：${resp.status}`);
    }
    const payload = await resp.json();
    if (!payload.success) {
      throw new Error(payload.error || '未知错误');
    }
    // 额外调试：若后台返回 debug 字段，打印到控制台便于问题定位（仅开发模式会看到）
    if (import.meta.env.DEV && payload.data?.debug) {
      console.info('[admin dashboard debug]', payload.data.debug);
    }
    snapshot.value = payload.data;
    errorMessage.value = null;
    bannerError.value = null;
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    if (!snapshot.value) {
      errorMessage.value = message;
    } else {
      bannerError.value = message;
    }
  } finally {
    if (background) {
      refreshing.value = false;
    }
    if (!snapshot.value) {
      loading.value = false;
    }
  }
};

const scheduleAutoRefresh = () => {
  if (timer) {
    clearInterval(timer);
    timer = undefined;
  }
  if (autoRefresh.value && secondaryVerified.value) {
    timer = window.setInterval(() => fetchDashboard(true), 30000);
  }
};

const handleManualRefresh = () => fetchDashboard(true);
const handleRetry = () => fetchDashboard(false);

const fetchCsrfToken = async (): Promise<string> => {
  const resp = await fetch('/api/csrf-token', { credentials: 'same-origin' });
  if (!resp.ok) {
    throw new Error(`获取 CSRF Token 失败：${resp.status}`);
  }
  const payload = await resp.json();
  const token = payload?.token;
  if (!token || typeof token !== 'string') {
    throw new Error('获取 CSRF Token 失败：响应无 token');
  }
  return token;
};

const parseInviteRemaining = (raw: unknown): number | null => {
  const text = String(raw ?? '')
    .trim()
    .toLowerCase();
  if (
    !text ||
    text === 'unlimited' ||
    text === 'null' ||
    text === 'none' ||
    text === '不限' ||
    text === '无限制'
  ) {
    return null;
  }
  const parsed = Number(text);
  if (!Number.isFinite(parsed) || parsed < 0 || !Number.isInteger(parsed)) {
    throw new Error('剩余次数必须是大于等于 0 的整数，或留空表示不限次数');
  }
  return parsed;
};

const createInvite = async () => {
  const code = newInviteCode.value.trim();
  if (!code) {
    inviteError.value = '请输入邀请码';
    return;
  }
  inviteSubmitting.value = true;
  inviteError.value = null;
  try {
    const remaining = newInviteUnlimited.value
      ? null
      : parseInviteRemaining(newInviteRemaining.value);
    const csrfToken = await fetchCsrfToken();
    const resp = await fetch('/api/admin/invites', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Token': csrfToken
      },
      credentials: 'same-origin',
      body: JSON.stringify({ code, remaining })
    });
    const payload = await resp.json();
    if (!resp.ok || !payload.success) {
      throw new Error(payload.error || `请求失败：${resp.status}`);
    }
    newInviteCode.value = '';
    newInviteRemaining.value = '1';
    newInviteUnlimited.value = false;
    await fetchDashboard(true);
  } catch (error) {
    inviteError.value = error instanceof Error ? error.message : String(error);
  } finally {
    inviteSubmitting.value = false;
  }
};

const handleResetPassword = async () => {
  const username = passwordTargetUser.value.trim();
  const password = passwordNewValue.value.trim();
  if (!username || !password) return;
  if (password.length < 8) {
    passwordError.value = '密码长度至少 8 位';
    passwordResult.value = '';
    return;
  }
  passwordSubmitting.value = true;
  passwordResult.value = '';
  passwordError.value = '';
  try {
    const csrfToken = await fetchCsrfToken();
    const resp = await fetch(`/api/admin/users/${encodeURIComponent(username)}/reset-password`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Token': csrfToken
      },
      credentials: 'same-origin',
      body: JSON.stringify({ password })
    });
    const payload = await resp.json();
    if (!resp.ok || !payload.success) {
      throw new Error(payload.error || `请求失败：${resp.status}`);
    }
    passwordResult.value = `用户 ${username} 的密码已成功重置`;
    passwordNewValue.value = '';
  } catch (error) {
    passwordError.value = error instanceof Error ? error.message : String(error);
  } finally {
    passwordSubmitting.value = false;
  }
};

const filteredUsers = computed(() => {
  const query = passwordSearchQuery.value.trim().toLowerCase();
  if (!query) return users.value;
  return users.value.filter(
    (u: any) =>
      u.username.toLowerCase().includes(query) ||
      (u.email || '').toLowerCase().includes(query)
  );
});

const selectedUserEmail = computed(() => {
  const user = users.value.find((u: any) => u.username === passwordTargetUser.value);
  return user?.email || '';
});

const togglePasswordDropdown = () => {
  passwordDropdownOpen.value = !passwordDropdownOpen.value;
  if (passwordDropdownOpen.value) {
    passwordSearchQuery.value = '';
    // 聚焦搜索框
    setTimeout(() => {
      passwordSearchInputRef.value?.focus();
    }, 50);
  }
};

const selectPasswordUser = (user: any) => {
  passwordTargetUser.value = user.username;
  passwordSearchQuery.value = '';
  passwordDropdownOpen.value = false;
};

const handleClickOutside = (event: MouseEvent) => {
  if (userSearchRef.value && !userSearchRef.value.contains(event.target as Node)) {
    passwordDropdownOpen.value = false;
  }
};

const editInvite = async (item: any) => {
  const current = item?.remaining;
  const seed = current === null || typeof current === 'undefined' ? 'unlimited' : String(current);
  const value = window.prompt('请输入新的剩余次数（留空或输入 unlimited 表示不限次数）', seed);
  if (value === null) return;
  inviteSubmitting.value = true;
  inviteError.value = null;
  try {
    const remaining = parseInviteRemaining(value);
    const csrfToken = await fetchCsrfToken();
    const resp = await fetch('/api/admin/invites', {
      method: 'PUT',
      headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Token': csrfToken
      },
      credentials: 'same-origin',
      body: JSON.stringify({ code: item?.code, remaining })
    });
    const payload = await resp.json();
    if (!resp.ok || !payload.success) {
      throw new Error(payload.error || `请求失败：${resp.status}`);
    }
    await fetchDashboard(true);
  } catch (error) {
    inviteError.value = error instanceof Error ? error.message : String(error);
  } finally {
    inviteSubmitting.value = false;
  }
};

const removeInvite = async (code: string) => {
  if (!code) return;
  if (!window.confirm(`确认删除邀请码 ${code}？`)) return;
  inviteSubmitting.value = true;
  inviteError.value = null;
  try {
    const csrfToken = await fetchCsrfToken();
    const resp = await fetch('/api/admin/invites', {
      method: 'DELETE',
      headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Token': csrfToken
      },
      credentials: 'same-origin',
      body: JSON.stringify({ code })
    });
    const payload = await resp.json();
    if (!resp.ok || !payload.success) {
      throw new Error(payload.error || `请求失败：${resp.status}`);
    }
    await fetchDashboard(true);
  } catch (error) {
    inviteError.value = error instanceof Error ? error.message : String(error);
  } finally {
    inviteSubmitting.value = false;
  }
};

const fetchBalance = async () => {
  balanceLoading.value = true;
  balanceError.value = null;
  try {
    const resp = await fetch('/api/admin/balance', { credentials: 'same-origin' });
    if (!resp.ok) throw new Error(`请求失败：${resp.status}`);
    const payload = await resp.json();
    if (!payload.success) throw new Error(payload.error || '未知错误');
    balanceData.value = payload.data || {};
    balanceUpdatedAt.value = Date.now();
  } catch (error) {
    balanceError.value = error instanceof Error ? error.message : String(error);
  } finally {
    balanceLoading.value = false;
  }
};

const handleVerifySecondary = async () => {
  await verifySecondary(secondaryPassword.value);
  if (secondaryVerified.value) {
    secondaryPassword.value = '';
    fetchDashboard(false);
    scheduleAutoRefresh();
  }
};

watch(autoRefresh, () => {
  scheduleAutoRefresh();
});

watch(
  () => activeSection.value,
  (val) => {
    if (val === 'balance' && !balanceData.value && !balanceLoading.value) {
      fetchBalance();
    }
  }
);

onMounted(async () => {
  document.addEventListener('click', handleClickOutside);
  await checkSecondary();
  if (secondaryVerified.value) {
    fetchDashboard(false);
    scheduleAutoRefresh();
  } else {
    loading.value = false;
  }
});

watch(secondaryVerified, (val) => {
  if (val) {
    fetchDashboard(false);
    scheduleAutoRefresh();
  } else if (timer) {
    clearInterval(timer);
    timer = undefined;
    loading.value = false;
  }
});

onBeforeUnmount(() => {
  document.removeEventListener('click', handleClickOutside);
  if (timer) {
    clearInterval(timer);
  }
});

const snapshotData = computed(() => snapshot.value || null);
const overview = computed(() => snapshotData.value?.overview ?? {});
const usageTotals = computed(() => overview.value.usage_totals ?? {});
const tokenTotals = computed(() => overview.value.token_totals ?? {});
const leaderBlocks = computed(() => {
  const leaders = overview.value.usage_leaders ?? {};
  return [
    { key: 'fast', label: '快速模式 TOP', items: leaders.fast || [] },
    { key: 'thinking', label: '思考模式 TOP', items: leaders.thinking || [] },
    { key: 'search', label: '搜索调用 TOP', items: leaders.search || [] }
  ];
});
const storage = computed(() => overview.value.storage ?? {});
const uploadStats = computed(() => snapshotData.value?.uploads?.stats ?? {});
const uploadSources = computed(() => snapshotData.value?.uploads?.sources ?? []);
const recentUploads = computed(() => snapshotData.value?.uploads?.recent_events ?? []);
const users = computed(() => snapshotData.value?.users ?? []);
const containers = computed(() => snapshotData.value?.containers ?? []);
const inviteSummary = computed(() => snapshotData.value?.invites?.summary ?? {});
const inviteCodes = computed(() => snapshotData.value?.invites?.codes ?? []);
const totals = computed(() => overview.value.totals ?? {});
const containerSummary = computed(() => overview.value.containers ?? {});
const invitesOverview = computed(() => overview.value.invites ?? {});
const tokenBreakdown = computed(() => {
  const list = users.value.map((user: any) => ({
    username: user.username,
    role: user.role || 'user',
    input: Number(user.tokens?.input_tokens || 0),
    output: Number(user.tokens?.output_tokens || 0),
    total: Number(user.tokens?.total_tokens || 0)
  }));
  return list.sort((a, b) => b.total - a.total);
});

const usdToCnyRate = computed(() => balanceData.value?.rate_usd_cny ?? null);

const balanceCards = computed(() => {
  const makeCard = (key: string, label: string) => {
    const data = (balanceData.value as any)?.[key] || {};
    const success = data.success === true;
    const currency = data.currency || '—';
    const amount = data.available ?? data.total ?? null;
    return {
      key,
      label,
      success,
      currency,
      amount,
      amountCny:
        data.available_cny ??
        (currency === 'USD' && usdToCnyRate.value && amount != null
          ? Math.round(amount * usdToCnyRate.value * 100) / 100
          : null),
      extras: data,
      error: success ? null : data.error
    };
  };
  return [
    makeCard('kimi', 'Kimi'),
    makeCard('deepseek', 'DeepSeek'),
    makeCard('qwen', 'Qwen / Aliyun')
  ];
});

const metricCards = computed(() => [
  {
    title: '注册用户',
    value: totals.value.users || 0,
    sub: `${totals.value.active_users || 0} 个活跃`
  },
  {
    title: '容器使用',
    value: `${containerSummary.value.active || 0}/${containerSummary.value.max_containers ?? '∞'}`,
    sub:
      containerSummary.value.available_slots === null ||
      containerSummary.value.available_slots === undefined
        ? '无限制'
        : `${containerSummary.value.available_slots} 个空闲`
  },
  {
    title: '资源告警',
    value: (storage.value.warning_users || []).length,
    sub: '达到预警阈值的工作区'
  },
  {
    title: '上传审计 (24h)',
    value: uploadStats.value.last_24h || 0,
    sub: `${uploadStats.value.blocked_last_24h || 0} 条被阻断`
  },
  {
    title: '配额调用 (fast / thinking / search)',
    value: `${usageTotals.value.fast || 0} / ${usageTotals.value.thinking || 0} / ${usageTotals.value.search || 0}`,
    sub: '窗口内累计调用'
  },
  {
    title: '邀请码',
    value: invitesOverview.value.total || 0,
    sub: `${invitesOverview.value.active || 0} 个可用 · ${invitesOverview.value.consumed || 0} 已用`
  },
  {
    title: '存储占用',
    value: formatBytes(storage.value.total_bytes || 0),
    sub: storage.value.per_user_limit_bytes
      ? `单用户上限 ${formatBytes(storage.value.per_user_limit_bytes)}`
      : '未设置上限'
  }
]);

const formatBytes = (value?: number | null) => {
  if (value === null || value === undefined || Number(value) <= 0) return '0 B';
  const units = ['B', 'KB', 'MB', 'GB', 'TB'];
  let val = Number(value);
  const index = Math.min(Math.floor(Math.log(val) / Math.log(1024)), units.length - 1);
  val /= 1024 ** index;
  const digits = val >= 100 ? 0 : 1;
  return `${val.toFixed(digits)} ${units[index]}`;
};

const percentWidth = (value?: number | null) => {
  if (value === null || value === undefined) return '0%';
  return `${Math.min(100, Math.max(0, Number(value)))}%`;
};

const formatPercent = (value?: number | null) => {
  if (value === null || value === undefined || isNaN(Number(value))) return '—';
  return `${Number(value).toFixed(1)}%`;
};

const formatNumber = (value?: number | null) => {
  if (value === null || value === undefined) return '0';
  const parsed = Number(value);
  if (Number.isNaN(parsed)) return '0';
  return parsed.toLocaleString();
};

const formatPercentNumber = (value?: number | null) => {
  if (value === null || value === undefined || isNaN(Number(value))) return '—';
  return `${Number(value).toFixed(1)}%`;
};

const timeAgo = (input?: string | number | null) => {
  if (!input) return '未知时间';
  const date = new Date(input);
  if (Number.isNaN(date.getTime())) return String(input);
  const diff = Date.now() - date.getTime();
  if (diff < 60_000) return '刚刚';
  const minutes = Math.floor(diff / 60_000);
  if (minutes < 60) return `${minutes} 分钟前`;
  const hours = Math.floor(minutes / 60);
  if (hours < 24) return `${hours} 小时前`;
  const days = Math.floor(hours / 24);
  return `${days} 天前`;
};

const usageBadge = (status?: string) => {
  if (status === 'critical') return 'danger';
  if (status === 'warning') return 'warning';
  return 'online';
};

const containerStatusClass = (item: any) => {
  if (item?.error) return 'danger';
  if (item?.state && item.state.running === false) return 'warning';
  return 'online';
};

const inviteStatus = (remaining: number | null | undefined) => {
  if (remaining === null || remaining === undefined) return '无限制';
  if (remaining > 0) return `剩余 ${remaining}`;
  return '已用完';
};
</script>

<style scoped>
:global(body) {
  margin: 0;
  background: #f7f3ea;
  font-family: 'Iowan Old Style', ui-serif, Georgia, Cambria, 'Times New Roman', serif;
  color: #2a2013;
  overflow: hidden;
}

:global(#admin-app) {
  min-height: 100vh;
  height: 100vh;
  padding: 24px 32px;
  background: #f7f3ea;
  color: #2a2013;
  box-sizing: border-box;
}

.admin-page {
  max-width: 1400px;
  margin: 0 auto;
  height: 100%;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.admin-header {
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
}

.admin-header h1 {
  margin: 0;
  font-size: 28px;
}

.admin-header p {
  margin: 4px 0 0;
  color: #6b5b44;
}

.header-actions {
  display: flex;
  align-items: center;
  gap: 12px;
}

.header-actions label {
  display: flex;
  align-items: center;
  gap: 6px;
  color: #5b4b35;
}

.link-btn {
  padding: 10px 14px;
  border-radius: 12px;
  border: 1px solid rgba(0, 0, 0, 0.12);
  background: #fff;
  color: #2a2013;
  text-decoration: none;
  font-weight: 600;
}

.link-btn:hover {
  background: rgba(0, 0, 0, 0.04);
}

button {
  border: none;
  border-radius: 999px;
  padding: 10px 18px;
  background: var(--claude-highlight);
  color: #3b2f1d;
  font-weight: 600;
  cursor: pointer;
  box-shadow: inset 0 -2px 0 rgba(189, 93, 58, 0.25);
  transition:
    transform 0.15s ease,
    box-shadow 0.15s ease,
    background 0.2s ease;
}

button:disabled {
  opacity: 0.6;
  cursor: not-allowed;
  box-shadow: none;
}

button:not(:disabled):hover {
  transform: translateY(-1px);
  background: var(--claude-accent);
  box-shadow: 0 8px 16px rgba(189, 93, 58, 0.25);
}

.banner-error {
  padding: 12px 16px;
  border-radius: 14px;
  border: 1px solid rgba(189, 93, 58, 0.5);
  background: rgba(189, 93, 58, 0.15);
  color: #7c3418;
}

.secondary-overlay {
  position: fixed;
  inset: 0;
  background: rgba(20, 12, 5, 0.35);
  backdrop-filter: blur(6px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 999;
}

.secondary-card {
  width: 360px;
  max-width: 90vw;
  background: #f6ecda;
  border: 1px solid rgba(118, 103, 84, 0.35);
  border-radius: 18px;
  padding: 20px;
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.16);
}

.secondary-card h3 {
  margin: 0 0 8px;
}

.secondary-card p {
  margin: 0 0 12px;
  color: #5b4b35;
}

.secondary-input {
  width: 100%;
  box-sizing: border-box;
  padding: 12px 14px;
  border-radius: 12px;
  border: 1px solid rgba(44, 32, 19, 0.2);
  margin-bottom: 12px;
  font-size: 15px;
}

.secondary-actions {
  display: flex;
  gap: 10px;
  align-items: center;
}

.secondary-actions .ghost-btn {
  background: transparent;
  border: 1px dashed rgba(44, 32, 19, 0.35);
  padding: 10px 14px;
  border-radius: 12px;
  cursor: pointer;
}

.secondary-error {
  color: #b5473d;
  margin-top: 10px;
}

.admin-layout {
  display: flex;
  gap: 20px;
  flex: 1;
  min-height: 0;
}

.admin-sidebar {
  width: 220px;
  flex-shrink: 0;
  background: rgba(255, 255, 255, 0.85);
  border-radius: 24px;
  border: 1px solid rgba(118, 103, 84, 0.35);
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.sidebar-tab {
  width: 100%;
  padding: 10px 12px;
  border-radius: 14px;
  border: 1px solid transparent;
  background: rgba(255, 255, 255, 0.7);
  color: #3c2f1d;
  text-align: left;
}

.sidebar-tab.active {
  background: rgba(218, 119, 86, 0.2);
  color: #3c2f1d;
  border-color: rgba(189, 93, 58, 0.55);
  box-shadow: 0 10px 22px rgba(189, 93, 58, 0.18);
  font-weight: 600;
}

.admin-main {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding-right: 8px;
}

.panel {
  background: #f6ecda;
  border-radius: 28px;
  padding: 24px;
  border: 1px solid rgba(118, 103, 84, 0.35);
  box-shadow: var(--claude-shadow);
  color: #23170b;
}

.metrics-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 16px;
}

.metric-card {
  background: rgba(255, 255, 255, 0.95);
  border-radius: 22px;
  padding: 18px;
  border: 1px solid rgba(118, 103, 84, 0.4);
}

.metric-card h3 {
  margin: 0;
  font-size: 13px;
  color: var(--claude-text-secondary);
  letter-spacing: 0.08em;
}

.metric-card strong {
  font-size: 28px;
  display: block;
  margin-top: 8px;
}

.metric-card span {
  color: #574630;
  font-size: 13px;
}

.stats-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 18px;
  color: #5c4a32;
}

.stats-row span {
  background: rgba(218, 119, 86, 0.2);
  padding: 6px 12px;
  border-radius: 999px;
}

.token-summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 12px;
  margin-bottom: 18px;
}

.token-card {
  border-radius: 18px;
  padding: 16px;
  background: rgba(255, 255, 255, 0.92);
  border: 1px solid rgba(44, 32, 19, 0.1);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.6);
}

.token-card p {
  margin: 0;
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--claude-text-secondary);
}

.token-card strong {
  display: block;
  margin-top: 10px;
  font-size: 22px;
  color: #1f160c;
}

.token-breakdown {
  border: 1px solid rgba(118, 103, 84, 0.25);
  border-radius: 20px;
  padding: 18px;
  background: rgba(255, 255, 255, 0.86);
  margin-bottom: 18px;
}

.token-breakdown-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}

.token-breakdown-header h3 {
  margin: 0;
  font-size: 18px;
}

.token-breakdown-header span {
  font-size: 12px;
  color: var(--claude-text-secondary);
}

.token-table-wrapper {
  max-height: 320px;
  overflow: auto;
}

.token-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

.token-table th,
.token-table td {
  padding: 10px 8px;
  border-bottom: 1px solid rgba(118, 103, 84, 0.15);
  text-align: left;
}

.token-table th {
  font-size: 12px;
  color: #4c3a24;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.token-table td strong {
  font-size: 14px;
  color: #2c1f12;
}

.token-empty {
  margin: 0;
  color: var(--claude-text-secondary);
  font-size: 13px;
}

.usage-leaders {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 14px;
}

.leader-card {
  border: 1px solid rgba(118, 103, 84, 0.18);
  border-radius: 18px;
  padding: 14px;
  background: rgba(255, 255, 255, 0.75);
}

.leader-card h3 {
  margin: 0 0 10px;
  color: var(--claude-text-secondary);
  font-size: 14px;
}

.leader-card ul {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.leader-card li {
  display: flex;
  justify-content: space-between;
  font-size: 13px;
}

.leader-card .muted {
  color: var(--claude-muted);
}

.table-wrapper {
  overflow-x: auto;
}

table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

thead {
  background: rgba(255, 248, 238, 0.85);
}

th,
td {
  padding: 12px;
  border-bottom: 1px solid rgba(118, 103, 84, 0.18);
  text-align: left;
}

th {
  color: #4f3f2a;
  font-weight: 600;
  font-size: 12px;
  letter-spacing: 0.05em;
}

.status-badge {
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
}

.status-badge.online {
  background: rgba(118, 176, 134, 0.18);
  color: var(--claude-success);
}

.status-badge.offline {
  background: rgba(121, 109, 94, 0.2);
  color: #5b4d3b;
}

.status-badge.warning {
  background: rgba(217, 152, 69, 0.2);
  color: var(--claude-warning);
}

.status-badge.danger {
  background: rgba(189, 93, 58, 0.18);
  color: var(--claude-accent-strong);
}

.progress-bar {
  height: 6px;
  border-radius: 999px;
  background: rgba(121, 109, 94, 0.12);
  margin-top: 6px;
}

.progress-bar span {
  display: block;
  height: 100%;
  background: linear-gradient(90deg, var(--claude-highlight), var(--claude-accent));
}

.upload-feed {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.upload-item {
  padding: 12px 16px;
  border-radius: 16px;
  border: 1px solid rgba(118, 103, 84, 0.3);
  background: rgba(255, 255, 255, 0.88);
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: space-between;
}

.upload-meta {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  color: var(--claude-text-secondary);
  font-size: 12px;
}

.invite-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 12px;
}

.invite-manage {
  margin: 10px 0 14px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}

.invite-input {
  min-width: 180px;
  height: 38px;
  border-radius: 10px;
  border: 1px solid rgba(118, 103, 84, 0.35);
  background: rgba(255, 255, 255, 0.92);
  padding: 0 10px;
  box-sizing: border-box;
}

.invite-input.short {
  min-width: 120px;
  width: 120px;
}

.invite-check {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--claude-text-secondary);
  font-size: 13px;
}

.invite-card {
  border: 1px solid rgba(118, 103, 84, 0.3);
  border-radius: 16px;
  padding: 14px;
  background: rgba(255, 255, 255, 0.9);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5);
}

.invite-card h4 {
  margin: 0 0 6px;
}

.invite-card span {
  color: var(--claude-text-secondary);
  font-size: 13px;
}

.invite-actions {
  margin-top: 10px;
  display: flex;
  gap: 8px;
}

.mini-btn {
  border-radius: 10px;
  padding: 6px 10px;
  font-size: 12px;
}

.mini-btn.danger {
  background: rgba(189, 93, 58, 0.2);
  color: #7c3418;
  box-shadow: none;
}

.balance-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}

.balance-actions {
  display: flex;
  align-items: center;
  gap: 10px;
}

.balance-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 12px;
  margin-top: 12px;
}

.balance-card {
  border: 1px solid rgba(118, 103, 84, 0.2);
  border-radius: 14px;
  padding: 12px;
  background: rgba(255, 255, 255, 0.9);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35);
}

.balance-card.error {
  border-color: rgba(189, 93, 58, 0.4);
  background: rgba(189, 93, 58, 0.08);
}

.balance-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 6px;
}

.balance-amount {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 8px;
}

.balance-amount .sub {
  color: #6a5d4c;
  font-size: 13px;
}

.balance-meta {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
  color: #6a5d4c;
}

.small {
  font-size: 13px;
}

.admin-loading {
  min-height: 60vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  color: var(--claude-text-secondary);
}

.spinner {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: 4px solid rgba(0, 0, 0, 0.12);
  border-top-color: #050505;
  border-right-color: #050505;
  border-bottom-color: #f4f4f4;
  border-left-color: #f4f4f4;
  background: transparent;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

.admin-error {
  background: rgba(189, 93, 58, 0.15);
  border: 1px solid rgba(189, 93, 58, 0.45);
  border-radius: 16px;
  padding: 18px;
  color: #7d341a;
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: flex-start;
}

.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.15s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

@media (max-width: 900px) {
  :global(#admin-app) {
    padding: 16px;
  }

  .admin-layout {
    flex-direction: column;
  }

  .admin-sidebar {
    width: 100%;
    flex-direction: row;
    flex-wrap: wrap;
  }

  .sidebar-tab {
    flex: 1;
    text-align: center;
  }
}

.password-form {
  display: flex;
  flex-direction: column;
  gap: 16px;
  max-width: 480px;
  margin-top: 12px;
}

.password-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.password-field label {
  font-size: 13px;
  color: var(--claude-text-secondary);
  font-weight: 600;
}

.password-select,
.password-input {
  width: 100%;
  box-sizing: border-box;
  padding: 12px 14px;
  border-radius: 12px;
  border: 1px solid rgba(44, 32, 19, 0.2);
  font-size: 14px;
  background: rgba(255, 255, 255, 0.92);
}

.password-input {
  width: 100%;
  box-sizing: border-box;
  padding: 12px 14px;
  border-radius: 12px;
  border: 1px solid rgba(44, 32, 19, 0.2);
  font-size: 14px;
  background: rgba(255, 255, 255, 0.92);
}

/* 自定义用户搜索下拉 */
.user-search-wrapper {
  position: relative;
}

.user-search-trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 12px 14px;
  border-radius: 12px;
  border: 1px solid rgba(44, 32, 19, 0.2);
  background: rgba(255, 255, 255, 0.92);
  cursor: pointer;
  min-height: 44px;
  box-sizing: border-box;
  transition: border-color 0.15s ease;
}

.user-search-trigger:hover {
  border-color: rgba(44, 32, 19, 0.4);
}

.user-search-trigger.open {
  border-color: rgba(189, 93, 58, 0.5);
  border-radius: 12px 12px 0 0;
}

.user-search-trigger.filled {
  border-color: rgba(44, 32, 19, 0.35);
}

.user-search-selected {
  display: flex;
  align-items: center;
  gap: 8px;
  overflow: hidden;
}

.user-search-selected strong {
  font-size: 14px;
  flex-shrink: 0;
}

.user-search-selected .user-search-email {
  font-size: 12px;
  color: var(--claude-text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.user-search-placeholder {
  color: #9a8b7a;
  font-size: 14px;
}

.user-search-arrow {
  font-size: 12px;
  color: var(--claude-text-secondary);
  transition: transform 0.15s ease;
  flex-shrink: 0;
}

.user-search-trigger.open .user-search-arrow {
  transform: rotate(180deg);
}

.user-search-dropdown {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  background: #fff;
  border: 1px solid rgba(189, 93, 58, 0.5);
  border-top: none;
  border-radius: 0 0 12px 12px;
  z-index: 100;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.12);
  overflow: hidden;
}

.user-search-input {
  width: 100%;
  box-sizing: border-box;
  padding: 10px 14px;
  border: none;
  border-bottom: 1px solid rgba(118, 103, 84, 0.15);
  font-size: 13px;
  outline: none;
  background: rgba(255, 255, 255, 0.95);
}

.user-search-input::placeholder {
  color: #b0a392;
}

.user-search-list {
  max-height: 220px;
  overflow-y: auto;
}

.user-search-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  cursor: pointer;
  transition: background 0.1s ease;
}

.user-search-item:hover {
  background: rgba(218, 119, 86, 0.08);
}

.user-search-item.active {
  background: rgba(218, 119, 86, 0.14);
}

.user-search-name {
  font-size: 14px;
  font-weight: 600;
  flex-shrink: 0;
}

.user-search-item .user-search-email {
  font-size: 12px;
  color: var(--claude-text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.user-search-role {
  font-size: 11px;
  padding: 2px 6px;
  border-radius: 6px;
  background: rgba(189, 93, 58, 0.15);
  color: #7c3418;
  flex-shrink: 0;
  margin-left: auto;
}

.user-search-empty {
  padding: 16px;
  text-align: center;
  color: var(--claude-text-secondary);
  font-size: 13px;
}

.password-actions {
  display: flex;
  gap: 10px;
  align-items: center;
}

.password-actions .ghost-btn {
  background: transparent;
  border: 1px dashed rgba(44, 32, 19, 0.35);
  padding: 10px 14px;
  border-radius: 12px;
  cursor: pointer;
  box-shadow: none;
  color: var(--claude-text-secondary);
}

.password-actions .ghost-btn:hover:not(:disabled) {
  background: rgba(0, 0, 0, 0.04);
  transform: none;
}

.password-success {
  color: var(--claude-success);
  margin: 0;
  font-weight: 600;
}

.password-error-msg {
  color: #b5473d;
  margin: 0;
}
</style>
