<template>
  <div v-if="status" class="space-y-6">
    <!-- F3 Master health banner -->
    <MasterHealthBanner
      :master-health="masterHealth"
      :min-grace-until="minGraceUntil"
      :loading="masterHealthBusy"
      @refresh="onRefreshMasterHealth" />

    <!-- F6 Pool health card -->
    <PoolHealthCard
      :accounts="status.accounts || []"
      :master-health="masterHealth"
      :min-grace-until="minGraceUntil" />

    <!-- 状态分布卡片(色彩区分明显) -->
    <div class="grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-6 gap-3">
      <div v-for="card in cards" :key="card.label"
        class="glass-soft rounded-lg p-3.5 lift-hover relative overflow-hidden">
        <div class="text-[10px] uppercase tracking-widest text-ink-500 font-semibold flex items-center gap-1.5">
          <span class="w-1 h-1 rounded-full" :style="{ background: card.dot }"></span>
          {{ card.label }}
        </div>
        <div class="text-3xl font-extrabold mt-1.5 tabular leading-none" :class="card.color">{{ card.value }}</div>
      </div>
    </div>

    <!-- 账号表格 -->
    <div class="glass rounded-lg overflow-hidden">
      <div class="px-5 py-4 border-b border-hairline flex items-center justify-between gap-3 flex-wrap">
        <div>
          <h2 class="text-base font-bold text-ink-950 tracking-tight">账号列表</h2>
          <p class="text-[11px] text-ink-500 mt-0.5">
            {{ totalAccounts }} 个账号 · 每页 {{ ACCOUNT_PAGE_SIZE }} 条 · 实时配额来自 quota_cache
          </p>
        </div>
        <div class="flex items-center gap-2">
          <AtButton v-if="selectedDisableEmails.length"
            variant="secondary" size="sm" :loading="bulkToggling" :disabled="actionDisabled"
            @click="bulkDisableSelected">
            禁用自动化 ({{ selectedDisableEmails.length }})
          </AtButton>
          <AtButton v-if="selectedEnableEmails.length"
            variant="primary" size="sm" :loading="bulkToggling" :disabled="actionDisabled"
            @click="bulkEnableSelected">
            启用自动化 ({{ selectedEnableEmails.length }})
          </AtButton>
          <AtButton v-if="selectedEmails.length"
            variant="danger" size="sm" :loading="batchDeleting" :disabled="actionDisabled"
            confirm @click="batchDelete">
            <template #icon>
              <Trash2 class="w-3.5 h-3.5" :stroke-width="2" />
            </template>
            {{ batchDeleting ? `批量删除 ${batchProgress}` : `批量删除 (${selectedEmails.length})` }}
          </AtButton>
          <AtButton v-if="selectedEmails.length" variant="ghost" size="sm" @click="clearSelection">
            取消选择
          </AtButton>
          <AtButton variant="secondary" size="sm" :loading="syncing" @click="syncAccounts">
            <template #icon>
              <RefreshCw class="w-3.5 h-3.5" :stroke-width="2" />
            </template>
            {{ syncing ? '同步中…' : '同步账号' }}
          </AtButton>
        </div>
      </div>

      <div v-if="message" class="mx-5 mt-4 px-4 py-2.5 rounded-lg text-sm border animate-rise" :class="messageClass">
        {{ message }}
      </div>
      <div v-if="!adminReady"
        class="mx-5 mt-4 px-4 py-2.5 rounded-lg text-sm border bg-amber-50 text-amber-800 border-amber-200">
        请先在「设置」页完成管理员登录后,才能操作账号。
      </div>
      <div v-if="hiddenAccountCount"
        class="mx-5 mt-4 px-4 py-2.5 rounded-lg text-sm border bg-sky-50 text-sky-800 border-sky-200">
        当前仅渲染第 {{ accountPageStart + 1 }}-{{ accountPageEnd }} 个账号，另有 {{ hiddenAccountCount }} 个账号在其它页，以控制页面资源占用。
      </div>
      <div v-if="totalAccountPages > 1"
        class="mx-5 mt-4 px-3 py-2 rounded-lg border border-hairline bg-ink-50 flex items-center justify-between gap-3 flex-wrap">
        <div class="text-xs text-ink-600">
          显示 {{ accountPageStart + 1 }}-{{ accountPageEnd }} / {{ totalAccounts }}
        </div>
        <div class="flex items-center gap-2">
          <AtButton variant="ghost" size="sm" :disabled="accountPage <= 1" @click="goAccountPage(accountPage - 1)">
            <template #icon>
              <ChevronLeft class="w-3.5 h-3.5" :stroke-width="2" />
            </template>
            上一页
          </AtButton>
          <span class="text-xs text-ink-600 tabular">第 {{ accountPage }} / {{ totalAccountPages }} 页</span>
          <AtButton variant="ghost" size="sm" :disabled="accountPage >= totalAccountPages" @click="goAccountPage(accountPage + 1)">
            <template #icon>
              <ChevronRight class="w-3.5 h-3.5" :stroke-width="2" />
            </template>
            下一页
          </AtButton>
        </div>
      </div>

      <div class="overflow-x-auto">
        <table class="w-full text-sm">
          <thead>
            <tr class="text-ink-500 text-left border-b border-hairline text-[10px] uppercase tracking-widest">
              <th class="px-3 py-3 font-semibold w-8">
                <input
                  type="checkbox"
                  :checked="allSelectableChecked"
                  :indeterminate.prop="someSelectableChecked"
                  @change="toggleSelectAll"
                  :disabled="!selectableEmails.length"
                  class="accent-indigo-500 cursor-pointer w-3.5 h-3.5"
                  title="全选/取消全选本页账号(主号除外)" />
              </th>
              <th class="px-3 py-3 font-semibold">#</th>
              <th class="px-4 py-3 font-semibold">邮箱</th>
              <th class="px-4 py-3 font-semibold">状态</th>
              <th class="px-4 py-3 font-semibold">实际可用</th>
              <th class="px-4 py-3 font-semibold text-right">5h 剩余</th>
              <th class="px-4 py-3 font-semibold text-right">周 剩余</th>
              <th class="px-4 py-3 font-semibold">5h 重置</th>
              <th class="px-4 py-3 font-semibold">周 重置</th>
              <th class="px-4 py-3 font-semibold text-right">操作</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="(acc, i) in visibleAccounts" :key="acc.email"
              class="row-hoverable border-b border-hairline hover:bg-ink-50 group"
              :class="[isSelected(acc.email) ? 'bg-indigo-50' : '', acc.disabled ? 'opacity-75' : '']">
              <td class="px-3 py-3.5">
                <input
                  v-if="!acc.is_main_account"
                  type="checkbox"
                  :checked="isSelected(acc.email)"
                  @change="toggleSelect(acc.email)"
                  class="accent-indigo-500 cursor-pointer w-3.5 h-3.5" />
              </td>
              <td class="px-3 py-3.5 text-ink-400 font-mono text-xs">{{ String(accountPageStart + i + 1).padStart(2, '0') }}</td>
              <td class="px-4 py-3.5">
                <div class="flex items-center gap-2">
                  <span v-if="acc.is_main_account"
                    class="text-[9px] uppercase tracking-widest font-bold px-1.5 py-0.5 rounded
                           bg-indigo-50 text-indigo-700 border border-indigo-200">
                    Master
                  </span>
                  <span class="font-mono text-[12px] text-ink-900">{{ acc.email }}</span>
                </div>
              </td>
              <td class="px-4 py-3.5">
                <StatusBadge :status="acc.status" :grace-until="acc.grace_until" />
              </td>
              <td class="px-4 py-3.5">
                <UsabilityCell :account="acc" />
              </td>
              <td class="px-4 py-3.5 text-right font-mono tabular text-[12px]" :class="pctColor(quota(acc, 'primary'))">
                {{ quotaPct(acc, 'primary') }}
              </td>
              <td class="px-4 py-3.5 text-right font-mono tabular text-[12px]" :class="pctColor(quota(acc, 'weekly'))">
                {{ quotaPct(acc, 'weekly') }}
              </td>
              <td class="px-4 py-3.5 text-ink-500 font-mono text-[11px]">{{ quotaReset(acc, 'primary') }}</td>
              <td class="px-4 py-3.5 text-ink-500 font-mono text-[11px]">{{ quotaReset(acc, 'weekly') }}</td>
              <td class="px-4 py-3.5 text-right">
                <div class="flex items-center justify-end gap-1.5 flex-wrap">
                  <span v-if="acc.status === 'personal' && !acc.auth_file"
                    class="inline-block px-1.5 py-0.5 rounded text-[9px] uppercase tracking-wide
                           bg-amber-50 text-amber-800 border border-amber-200"
                    title="未拿到 Codex auth_file,请点击补登录">
                    缺认证
                  </span>
                  <AtButton v-if="canLogin(acc)"
                    variant="primary" size="sm"
                    :loading="actionEmail === acc.email && actionType === 'login'"
                    :disabled="actionDisabled || (actionEmail === acc.email && actionType !== 'login')"
                    @click="loginAccount(acc.email)">
                    {{ loginLabel(acc) }}
                  </AtButton>
                  <!-- Round 11:子号"立即探活"按钮(spec realtime-probe.md v1.0 §4) -->
                  <AtButton v-if="canProbe(acc)"
                    variant="secondary" size="sm"
                    :loading="actionEmail === acc.email && actionType === 'probe'"
                    :disabled="actionDisabled || (actionEmail === acc.email && actionType !== 'probe')"
                    @click="probeAccount(acc.email)"
                    title="立即调 cheap_codex_smoke + check_codex_quota,绕过 30min 节流">
                    立即探活
                  </AtButton>
                  <AtButton v-if="!acc.is_main_account && !acc.disabled && acc.raw_status === 'active'"
                    variant="secondary" size="sm"
                    :loading="actionEmail === acc.email && actionType === 'kick'"
                    :disabled="actionDisabled || (actionEmail === acc.email && actionType !== 'kick')"
                    @click="kickAccount(acc.email)">
                    移出
                  </AtButton>
                  <AtButton v-if="acc.raw_status === 'active' || acc.raw_status === 'personal' || acc.is_main_account"
                    variant="ghost" size="sm"
                    :disabled="actionEmail === acc.email"
                    @click="exportCodexAuth(acc.email)">
                    <template #icon>
                      <Download class="w-3.5 h-3.5" :stroke-width="2" />
                    </template>
                    导出
                  </AtButton>
                  <AtButton v-if="!acc.is_main_account"
                    :variant="acc.disabled ? 'primary' : 'secondary'" size="sm"
                    :loading="actionEmail === acc.email && actionType === (acc.disabled ? 'enable' : 'disable')"
                    :disabled="actionDisabled || (actionEmail === acc.email && !['enable', 'disable'].includes(actionType))"
                    @click="toggleAccountDisabled(acc)">
                    {{ acc.disabled ? '启用' : '禁用' }}
                  </AtButton>
                  <AtButton v-if="!acc.is_main_account"
                    variant="danger" size="sm" confirm
                    :loading="actionEmail === acc.email && actionType === 'delete'"
                    :disabled="actionDisabled || (actionEmail === acc.email && actionType !== 'delete')"
                    @click="removeAccount(acc.email)">
                    删除
                  </AtButton>
                </div>
              </td>
            </tr>
          </tbody>
        </table>
      </div>

      <!-- 注册失败明细 -->
      <div class="border-t border-hairline p-5 bg-ink-50">
        <div class="flex items-center justify-between mb-3">
          <div>
            <h2 class="text-sm font-bold text-ink-950 tracking-tight">注册失败明细</h2>
            <div class="text-[11px] text-ink-500 mt-0.5">未能入池的注册尝试 (add-phone / duplicate / OAuth 失败 等)</div>
          </div>
          <AtButton variant="ghost" size="sm" :loading="failuresLoading" @click="loadFailures">
            刷新
          </AtButton>
        </div>
        <div v-if="failuresCounts && Object.keys(failuresCounts).length" class="flex flex-wrap gap-1.5 mb-3 text-[11px]">
          <span v-for="(cnt, cat) in failuresCounts" :key="cat"
            class="px-2 py-0.5 rounded-md border bg-surface border-hairline text-ink-700">
            {{ cat }}: <span class="text-rose-700 font-mono ml-0.5 tabular">{{ cnt }}</span>
          </span>
        </div>
        <div class="overflow-x-auto rounded-lg border border-hairline bg-surface">
          <div v-if="failuresUnavailable"
            class="px-3 py-3 text-xs text-amber-800 bg-amber-50 border-b border-amber-200">
            {{ failuresUnavailable }}
          </div>
          <table class="w-full text-sm">
            <thead class="text-[10px] uppercase tracking-widest text-ink-500 border-b border-hairline bg-ink-50">
              <tr>
                <th class="text-left px-3 py-2 font-semibold">时间</th>
                <th class="text-left px-3 py-2 font-semibold">邮箱</th>
                <th class="text-left px-3 py-2 font-semibold">类别</th>
                <th class="text-left px-3 py-2 font-semibold">原因</th>
                <th class="text-left px-3 py-2 font-semibold">附加</th>
              </tr>
            </thead>
            <tbody class="divide-y divide-hairline text-xs">
              <tr v-if="!failuresItems.length">
                <td class="px-3 py-4 text-ink-400 italic" colspan="5">暂无失败记录</td>
              </tr>
              <tr v-for="(f, idx) in failuresItems" :key="idx" class="hover:bg-ink-50">
                <td class="px-3 py-2 text-ink-500 font-mono">{{ fmtTs(f.timestamp) }}</td>
                <td class="px-3 py-2 text-ink-700 font-mono">{{ f.email || '-' }}</td>
                <td class="px-3 py-2">
                  <span class="px-1.5 py-0.5 rounded-md border text-[10px] font-semibold uppercase tracking-wide"
                    :class="failureCategoryClass(f.category)">{{ f.category }}</span>
                </td>
                <td class="px-3 py-2 text-ink-600">{{ f.reason }}</td>
                <td class="px-3 py-2 text-ink-400 font-mono text-[10px]">{{ fmtFailureExtra(f) }}</td>
              </tr>
            </tbody>
          </table>
        </div>
      </div>

      <!-- Codex 认证导出弹窗 -->
      <div v-if="exportData"
        class="fixed inset-0 bg-ink-600/20 z-50 flex items-center justify-center p-4 animate-rise"
        @click.self="exportData = null">
        <div class="glass rounded-lg w-full max-w-2xl max-h-[80vh] flex flex-col">
          <div class="px-5 py-4 border-b border-hairline flex items-center justify-between">
            <h3 class="text-ink-950 font-bold tracking-tight">Codex CLI 认证文件</h3>
            <button @click="exportData = null" class="text-ink-500 hover:text-ink-950 transition focus-ring rounded-lg p-1">
              <X class="w-4 h-4" :stroke-width="2" />
            </button>
          </div>
          <div class="p-5 space-y-3 overflow-y-auto flex-1">
            <div class="px-3 py-2.5 bg-amber-50 border border-amber-200 rounded-lg text-sm text-amber-800 space-y-2">
              <div class="font-semibold">使用步骤:</div>
              <ol class="list-decimal list-inside space-y-1 text-xs text-amber-700">
                <li>退出当前 Codex CLI 会话</li>
                <li>删除旧文件:<code class="bg-amber-50 border border-amber-200 px-1 rounded">rm ~/.codex/auth.json</code></li>
                <li>将下方内容保存到 <code class="bg-amber-50 border border-amber-200 px-1 rounded">~/.codex/auth.json</code>(Windows: <code class="bg-amber-50 border border-amber-200 px-1 rounded">%APPDATA%\codex\auth.json</code>)</li>
                <li>重新启动 Codex CLI</li>
              </ol>
              <div class="text-xs text-amber-700">导出后 Codex CLI 直连 OpenAI,不走 CPA 代理,响应更快。</div>
            </div>
            <div class="relative">
              <pre class="bg-ink-50 border border-hairline rounded-lg p-4 text-xs font-mono text-ink-800 overflow-x-auto whitespace-pre">{{ exportJson }}</pre>
              <AtButton :variant="copied ? 'primary' : 'secondary'" size="sm"
                class="absolute top-2 right-2" @click="copyExport">
                {{ copied ? '已复制' : '复制' }}
              </AtButton>
            </div>
          </div>
          <div class="px-5 py-4 border-t border-hairline flex justify-end gap-2">
            <AtButton variant="primary" @click="downloadExport">
              <template #icon>
                <Download class="w-3.5 h-3.5" :stroke-width="2" />
              </template>
              下载 auth.json
            </AtButton>
            <AtButton variant="secondary" @click="exportData = null">关闭</AtButton>
          </div>
        </div>
      </div>
    </div>
  </div>

  <!-- Loading skeleton -->
  <div v-else-if="loading" class="space-y-4">
    <div class="grid grid-cols-2 sm:grid-cols-4 gap-4">
      <div v-for="i in 4" :key="i" class="glass-soft rounded-lg p-4 h-20 shimmer-bg"></div>
    </div>
    <div class="glass-soft rounded-lg h-64 shimmer-bg"></div>
  </div>
</template>

<script setup>
import { computed, ref, watch } from 'vue'
import { api } from '../api.js'
import StatusBadge from './StatusBadge.vue'
import UsabilityCell from './UsabilityCell.vue'
import MasterHealthBanner from './MasterHealthBanner.vue'
import PoolHealthCard from './PoolHealthCard.vue'
import AtButton from './AtButton.vue'
import { ChevronLeft, ChevronRight, Download, RefreshCw, Trash2, X } from 'lucide-vue-next'
import {
  quotaRemainingPct as _qr,
  quotaPctText,
  formatQuotaReset,
  quotaPctColor,
} from '../composables/useStatus.js'
import { useToast } from '../composables/useToast.js'

const toast = useToast()

const props = defineProps({
  status: Object,
  loading: Boolean,
  runningTask: Object,
  adminStatus: { type: Object, default: null },
  masterHealth: { type: Object, default: null },
  registerFailures: { type: Object, default: null },
  registerFailuresLoading: { type: Boolean, default: false },
  registerFailuresUnavailable: { type: String, default: '' },
})
const emit = defineEmits(['refresh', 'reload-master-health'])

const actionEmail = ref('')
const actionType = ref('')
const syncing = ref(false)
const message = ref('')
const exportData = ref(null)
const copied = ref(false)
const messageClass = ref('')
const masterHealthBusy = ref(false)
const accountPage = ref(1)
const ACCOUNT_PAGE_SIZE = 50

// 批量删除选中态:按邮箱(小写)保存,便于跨刷新复用
const selectedSet = ref(new Set())
const batchDeleting = ref(false)
const batchProgress = ref('')
const bulkToggling = ref(false)

const failuresItems = computed(() => props.registerFailures?.items || [])
const failuresCounts = computed(() => props.registerFailures?.counts || {})
const failuresLoading = computed(() => !!props.registerFailuresLoading)
const failuresUnavailable = computed(() => props.registerFailuresUnavailable || '')

const accounts = computed(() => props.status?.accounts || [])
const totalAccounts = computed(() => accounts.value.length)
const totalAccountPages = computed(() => Math.max(1, Math.ceil(totalAccounts.value / ACCOUNT_PAGE_SIZE)))
const accountPageStart = computed(() => (accountPage.value - 1) * ACCOUNT_PAGE_SIZE)
const accountPageEnd = computed(() => Math.min(accountPageStart.value + ACCOUNT_PAGE_SIZE, totalAccounts.value))
const visibleAccounts = computed(() => accounts.value.slice(accountPageStart.value, accountPageEnd.value))
const hiddenAccountCount = computed(() => Math.max(0, accounts.value.length - visibleAccounts.value.length))

function goAccountPage(page) {
  const nextPage = Math.min(Math.max(1, page), totalAccountPages.value)
  if (nextPage === accountPage.value) return
  accountPage.value = nextPage
  clearSelection()
}

watch(totalAccountPages, pages => {
  if (accountPage.value > pages) {
    accountPage.value = pages
    clearSelection()
  }
})

function loadFailures() {
  emit('refresh')
}

function fmtTs(ts) {
  if (!ts) return '-'
  const d = new Date(ts * 1000)
  const pad = n => String(n).padStart(2, '0')
  return `${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
}

function failureCategoryClass(cat) {
  const map = {
    phone_blocked: 'bg-rose-50 text-rose-700 border-rose-200',
    duplicate_exhausted: 'bg-orange-50 text-orange-700 border-orange-200',
    register_failed: 'bg-amber-50 text-amber-700 border-amber-200',
    oauth_failed: 'bg-sky-50 text-sky-700 border-sky-200',
    kick_failed: 'bg-yellow-50 text-yellow-700 border-yellow-200',
    exception: 'bg-red-50 text-red-700 border-red-200',
    master_subscription_degraded: 'bg-orange-50 text-orange-700 border-orange-200',
  }
  return map[cat] || 'bg-ink-50 text-ink-600 border-hairline'
}

function fmtFailureExtra(f) {
  const keys = ['step', 'register_attempts', 'duplicate_swaps', 'stage']
  const parts = []
  for (const k of keys) {
    if (f[k] !== undefined && f[k] !== null && f[k] !== '') parts.push(`${k}=${f[k]}`)
  }
  return parts.join(' ') || '-'
}

const adminReady = computed(() => !!props.adminStatus?.configured)
const actionDisabled = computed(() => !!props.runningTask || !adminReady.value || bulkToggling.value)

const selectableEmails = computed(() =>
  visibleAccounts.value.filter(a => !a.is_main_account).map(a => a.email)
)
const selectedEmails = computed(() =>
  selectableEmails.value.filter(e => selectedSet.value.has(e.toLowerCase()))
)
const selectedDisableEmails = computed(() =>
  visibleAccounts.value
    .filter(a => !a.is_main_account && !a.disabled && selectedSet.value.has((a.email || '').toLowerCase()))
    .map(a => a.email)
)
const selectedEnableEmails = computed(() =>
  visibleAccounts.value
    .filter(a => !a.is_main_account && a.disabled && selectedSet.value.has((a.email || '').toLowerCase()))
    .map(a => a.email)
)
const allSelectableChecked = computed(() =>
  selectableEmails.value.length > 0 && selectedEmails.value.length === selectableEmails.value.length
)
const someSelectableChecked = computed(() =>
  selectedEmails.value.length > 0 && selectedEmails.value.length < selectableEmails.value.length
)

function isSelected(email) {
  return selectedSet.value.has((email || '').toLowerCase())
}
function toggleSelect(email) {
  const key = (email || '').toLowerCase()
  const next = new Set(selectedSet.value)
  if (next.has(key)) next.delete(key)
  else next.add(key)
  selectedSet.value = next
}
function toggleSelectAll() {
  if (allSelectableChecked.value) selectedSet.value = new Set()
  else selectedSet.value = new Set(selectableEmails.value.map(e => e.toLowerCase()))
}
function clearSelection() { selectedSet.value = new Set() }

// Round 9 — 派生 minGraceUntil:从所有 GRACE 子号取最早到期者,banner/PoolHealthCard 共用
const minGraceUntil = computed(() => {
  let min = null
  for (const acc of accounts.value) {
    if (acc.status === 'degraded_grace' && typeof acc.grace_until === 'number') {
      if (min === null || acc.grace_until < min) min = acc.grace_until
    }
  }
  return min
})

const cards = computed(() => {
  if (!props.status) return []
  const s = props.status.summary || {}
  const dg = s.degraded_grace ?? accounts.value.filter(a => a.status === 'degraded_grace').length
  return [
    { label: 'Active', value: s.active || 0, color: 'text-emerald-700', dot: 'rgb(16, 185, 129)' },
    { label: 'Standby', value: s.standby || 0, color: 'text-amber-700', dot: 'rgb(217, 119, 6)' },
    { label: 'Grace', value: dg, color: 'text-orange-700', dot: 'rgb(234, 88, 12)' },
    { label: 'Personal', value: s.personal || 0, color: 'text-sky-700', dot: 'rgb(2, 132, 199)' },
    { label: 'Disabled', value: s.disabled || 0, color: 'text-stone-700', dot: 'rgb(120, 113, 108)' },
    { label: 'Total', value: s.total || 0, color: 'text-ink-950', dot: 'rgb(79, 70, 229)' },
  ]
})

function quota(acc, type) {
  const qi = props.status?.quota_cache?.[acc.email] || acc.last_quota
  if (!qi) return null
  return _qr(qi, type)
}
function quotaPct(acc, type) {
  const qi = props.status?.quota_cache?.[acc.email] || acc.last_quota
  return quotaPctText(qi, type)
}
function quotaReset(acc, type) {
  const qi = props.status?.quota_cache?.[acc.email] || acc.last_quota
  return formatQuotaReset(qi, type)
}
function pctColor(remain) { return quotaPctColor(remain) }

const exportJson = computed(() => {
  if (!exportData.value) return ''
  return JSON.stringify(exportData.value.codex_auth, null, 2)
})

async function exportCodexAuth(email) {
  try {
    exportData.value = await api.getCodexAuth(email)
    copied.value = false
  } catch (e) {
    toast.error('导出失败', e.message)
  }
}

async function copyExport() {
  try {
    await navigator.clipboard.writeText(exportJson.value)
  } catch {
    const ta = document.createElement('textarea')
    ta.value = exportJson.value
    ta.style.position = 'fixed'; ta.style.opacity = '0'
    document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta)
  }
  copied.value = true
  setTimeout(() => { copied.value = false }, 2400)
}

function downloadExport() {
  const blob = new Blob([exportJson.value], { type: 'application/json' })
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url; a.download = 'auth.json'; a.click()
  URL.revokeObjectURL(url)
}

async function syncAccounts() {
  syncing.value = true
  try {
    const result = await api.postSyncAccounts()
    toast.success('同步完成', result.message || '')
    emit('refresh')
  } catch (e) {
    toast.error('同步失败', e.message)
  } finally {
    syncing.value = false
  }
}

function canLogin(acc) {
  if (acc.is_main_account) return false
  if (acc.disabled) return false
  if (acc.status === 'active') return false
  if (acc.status === 'personal' && acc.auth_file) return false
  if (acc.status === 'degraded_grace') return false // grace 期内仍可用,无需补登录
  return true
}
// Round 11 realtime-probe.md v1.0 §4 — 仅对持有 auth_file 的子号显示"立即探活"
// (主号有自己的"立即重测"按钮在 MasterHealthBanner;orphan/pending 无 token 探不动)
function canProbe(acc) {
  if (acc.is_main_account) return false
  if (acc.disabled) return false
  if (!acc.auth_file) return false
  if (acc.status === 'pending' || acc.status === 'orphan') return false
  return true
}
function loginLabel(acc) {
  if (acc.status === 'personal' || acc.status === 'auth_invalid' || acc.status === 'orphan') return '补登录'
  return '登录'
}

async function loginAccount(email) {
  if (actionDisabled.value) return
  actionEmail.value = email; actionType.value = 'login'
  try {
    const result = await api.loginAccount(email)
    toast.success('登录任务已提交', `${email} · ${result.task_id}`)
    emit('refresh')
  } catch (e) {
    toast.error('提交登录任务失败', e.message)
  } finally {
    actionEmail.value = ''; actionType.value = ''
  }
}

// Round 11 realtime-probe.md v1.0 — 子号实时探活
// 调 POST /api/accounts/{email}/probe (force_codex_smoke=true)
// 副作用:后端落 last_quota_check_at,绕过 30min 节流;不修改 status (RT-I1)
async function probeAccount(email) {
  if (actionDisabled.value) return
  actionEmail.value = email; actionType.value = 'probe'
  try {
    const result = await api.probeAccount(email, true)
    // 后端 spec RT-I2:probe 永不抛 5xx,smoke_result ∈ {alive, auth_invalid, uncertain}
    const smoke = result?.smoke_result || result?.last_smoke_result || '-'
    const lastCheck = result?.last_quota_check_at
    const checkedAt = lastCheck
      ? new Date(lastCheck * 1000).toLocaleTimeString()
      : '刚刚'
    if (smoke === 'alive') {
      toast.success('探活成功', `${email} · ${checkedAt} · alive`)
    } else if (smoke === 'auth_invalid') {
      toast.warn('探活完成 · token 失效', `${email} · ${checkedAt} · auth_invalid`)
    } else {
      toast.info('探活完成', `${email} · ${checkedAt} · ${smoke}`)
    }
    emit('refresh')
  } catch (e) {
    toast.error('探活失败', e.message)
  } finally {
    actionEmail.value = ''; actionType.value = ''
  }
}

async function kickAccount(email) {
  if (actionDisabled.value) return
  const ok = window.confirm(`确认将 ${email} 移出 Team?\n账号会变为 standby 状态,额度恢复后可重新复用。`)
  if (!ok) return
  actionEmail.value = email; actionType.value = 'kick'
  try {
    const result = await api.kickAccount(email)
    toast.success('已移出 Team', result.message || email)
    emit('refresh')
  } catch (e) {
    toast.error('移出失败', e.message)
  } finally {
    actionEmail.value = ''; actionType.value = ''
  }
}

async function toggleAccountDisabled(acc) {
  if (actionDisabled.value) return
  const disabling = !acc.disabled
  const ok = window.confirm(
    disabling
      ? `确认禁用账号 ${acc.email}?\n禁用后会保留本地记录,但自动巡检、轮转和 CPA 同步会跳过该账号。`
      : `确认启用账号 ${acc.email}?\n启用后该账号会重新参与自动巡检、轮转和 CPA 同步。`
  )
  if (!ok) return
  actionEmail.value = acc.email
  actionType.value = disabling ? 'disable' : 'enable'
  try {
    const result = disabling
      ? await api.disableAccount(acc.email)
      : await api.enableAccount(acc.email)
    toast.success(disabling ? '账号已禁用' : '账号已启用', result.message || acc.email)
    emit('refresh')
  } catch (e) {
    toast.error(disabling ? '禁用失败' : '启用失败', e.message)
  } finally {
    actionEmail.value = ''; actionType.value = ''
  }
}

async function removeAccount(email) {
  if (actionDisabled.value) return
  // 二次确认已在 AtButton(confirm) 内做了第一次,这里再用 native confirm 提示破坏性操作细节
  const ok = window.confirm(`确认删除账号 ${email}?\n这会清理本地记录、CPA、Team/Invite 和 CloudMail。`)
  if (!ok) return
  actionEmail.value = email; actionType.value = 'delete'
  try {
    const result = await api.deleteAccount(email)
    toast.success('账号已删除', result.message || email)
    emit('refresh')
  } catch (e) {
    toast.error('删除失败', e.message)
  } finally {
    actionEmail.value = ''; actionType.value = ''
  }
}

async function bulkDisableSelected() {
  if (actionDisabled.value || bulkToggling.value) return
  const emails = selectedDisableEmails.value
  if (!emails.length) return
  const preview = emails.slice(0, 8).join('\n')
  const more = emails.length > 8 ? `\n…还有 ${emails.length - 8} 个` : ''
  const ok = window.confirm(
    `确认批量禁用以下 ${emails.length} 个账号?\n禁用后会保留本地记录,但自动巡检、轮转和 CPA 同步会跳过它们。\n\n${preview}${more}`
  )
  if (!ok) return
  bulkToggling.value = true
  try {
    const result = await api.bulkDisableAccounts(emails)
    toast.success('批量禁用完成', result.message || `已禁用 ${emails.length} 个账号`)
    clearSelection()
    emit('refresh')
  } catch (e) {
    toast.error('批量禁用失败', e.message)
  } finally {
    bulkToggling.value = false
  }
}

async function bulkEnableSelected() {
  if (actionDisabled.value || bulkToggling.value) return
  const emails = selectedEnableEmails.value
  if (!emails.length) return
  const preview = emails.slice(0, 8).join('\n')
  const more = emails.length > 8 ? `\n…还有 ${emails.length - 8} 个` : ''
  const ok = window.confirm(
    `确认批量启用以下 ${emails.length} 个账号?\n启用后它们会重新参与自动巡检、轮转和 CPA 同步。\n\n${preview}${more}`
  )
  if (!ok) return
  bulkToggling.value = true
  try {
    const result = await api.bulkEnableAccounts(emails)
    toast.success('批量启用完成', result.message || `已启用 ${emails.length} 个账号`)
    clearSelection()
    emit('refresh')
  } catch (e) {
    toast.error('批量启用失败', e.message)
  } finally {
    bulkToggling.value = false
  }
}

async function batchDelete() {
  if (actionDisabled.value || batchDeleting.value) return
  const emails = selectedEmails.value
  if (!emails.length) return
  const preview = emails.slice(0, 8).join('\n')
  const more = emails.length > 8 ? `\n…还有 ${emails.length - 8} 个` : ''
  const ok = window.confirm(
    `确认批量删除以下 ${emails.length} 个账号?这会清理本地记录、CPA、Team/Invite 和 CloudMail。\n\n${preview}${more}`
  )
  if (!ok) return
  batchDeleting.value = true; batchProgress.value = `0/${emails.length}`
  try {
    const r = await api.deleteAccountsBatch(emails, true)
    const s = r?.summary || {}
    const failed = (r?.results || []).filter(x => !x.ok)
    if (failed.length === 0) {
      toast.success('批量删除完成', `成功 ${s.ok}/${s.total}`)
    } else {
      const head = failed.slice(0, 3).map(x => `${x.email}: ${x.error}`).join('; ')
      toast.warn('批量删除部分失败', `成功 ${s.ok}/${s.total}; ${head}${failed.length > 3 ? ' …' : ''}`)
    }
    clearSelection()
    emit('refresh')
  } catch (e) {
    toast.error('批量删除失败', e.message)
  } finally {
    batchDeleting.value = false; batchProgress.value = ''
  }
}

async function onRefreshMasterHealth() {
  masterHealthBusy.value = true
  try {
    emit('reload-master-health', true)
    toast.info('已请求重测', '走 force_refresh,绕过 5min cache')
  } finally {
    setTimeout(() => { masterHealthBusy.value = false }, 1200)
  }
}
</script>
