<script setup lang="ts">
import { NButton, NSpin } from 'naive-ui'
import { computed, ref } from 'vue'

import { listSessions, revokeSession, revokeSessions } from '@/api/auth'
import ConfirmPop from '@/components/ConfirmPop.vue'
import FootNote from '@/components/FootNote.vue'
import { useAsync } from '@/composables/useAsync'
import { fmtRelative, fmtTime, uaLabel } from '@/composables/useFormat'
import { useAuthStore } from '@/stores/auth'
import { useToast } from '@/composables/useToast'
import type { SessionItem } from '@/types/api'

/** 活跃会话卡:全宽表格列出当前有效登录会话,支持定点撤销与撤销全部 */
const message = useToast()
const auth = useAuthStore()
const sessions = useAsync(listSessions)

const items = computed(() => sessions.data.value?.items ?? [])
/** 零行才是空态:单条本机会话也渲染表格(展示设备/IP/时间比空文案更有信息量);
 *  零行不证明只有一个活跃会话——升级前签发的旧令牌不产生记录 */
const empty = computed(() => items.value.length === 0)

const methodLabels: Record<string, string> = {
  password: '密码',
  passkey: '通行密钥',
  wallet: '钱包',
  oidc: 'OIDC',
  github: 'GitHub',
}

function deviceOf(s: SessionItem) {
  return uaLabel(s.userAgent)
}

/** 撤销互斥:单个与全部共享——撤销全部在服务端递增版本后、前端写入新
 *  token 前,并发的单撤销会携带旧 token 吃 401 并触发全局登出竞态 */
const revoking = ref(false)

async function revokeOne(s: SessionItem) {
  if (revoking.value) return
  revoking.value = true
  try {
    await revokeSession(s.id)
    message.success('已撤销,该设备将立即退出登录')
  } catch (e) {
    message.error(e instanceof Error ? e.message : '撤销失败')
  } finally {
    revoking.value = false
    void sessions.run({ silent: true })
  }
}

/** 撤销全部 = 令牌版本递增:其他设备立即失效,本会话经新 token 无感换新 */
async function revokeAll() {
  if (revoking.value) return
  revoking.value = true
  try {
    const r = await revokeSessions()
    if (r?.token) auth.setSession(r.token, r.expiresAt)
    message.success('已撤销全部会话;其他设备须重新登录')
  } catch (e) {
    message.error(e instanceof Error ? e.message : '操作失败')
  } finally {
    revoking.value = false
    void sessions.run({ silent: true })
  }
}
</script>

<template>
  <div class="panel">
    <div
      class="flex flex-wrap items-center justify-between gap-3 border-b border-line-soft px-4.5 py-3.5"
    >
      <div class="min-w-0">
        <div class="text-sm font-semibold">活跃会话</div>
        <div class="mt-0.5 text-xs text-ink-3">
          当前有效登录会话,最近活跃在前;登出、撤销或凭据变更后自动消失
        </div>
      </div>
      <ConfirmPop
        title="撤销全部会话？"
        content="其他设备与旧令牌立即失效并需重新登录;本会话自动换新。"
        confirm-text="全部撤销"
        :on-confirm="revokeAll"
      >
        <template #trigger>
          <!-- 不依赖列表禁用(存量令牌唯一失效入口);仅与单撤销互斥 -->
          <NButton size="tiny" type="error" quaternary :disabled="revoking">撤销全部会话</NButton>
        </template>
      </ConfirmPop>
    </div>

    <div v-if="sessions.loading.value && !sessions.data.value" class="flex justify-center py-8">
      <NSpin size="small" />
    </div>
    <!-- 请求失败与空列表分开呈现,失败不得伪装成「仅本机一个会话」 -->
    <div
      v-else-if="sessions.error.value"
      class="px-4.5 py-8 text-center text-xs leading-relaxed text-ink-3"
    >
      会话列表加载失败:{{ sessions.error.value }}
      <NButton size="tiny" quaternary type="primary" class="ml-1" @click="() => sessions.run()">
        重试
      </NButton>
    </div>
    <div v-else-if="empty" class="px-4.5 py-8 text-center text-xs leading-relaxed text-ink-3">
      暂无会话记录。升级前签发的旧令牌不产生记录,不代表没有其他活跃会话;
      如有顾虑可直接「撤销全部会话」。
    </div>
    <div v-else class="overflow-x-auto">
      <table class="w-full text-[12.5px]">
        <thead>
          <tr class="text-left text-xs text-ink-3">
            <th class="border-b border-line-soft px-4.5 py-2 font-medium">设备</th>
            <th class="border-b border-line-soft px-3 py-2 font-medium">登录方式</th>
            <th class="border-b border-line-soft px-3 py-2 font-medium">IP</th>
            <th class="border-b border-line-soft px-3 py-2 font-medium">登录时间</th>
            <th class="border-b border-line-soft px-3 py-2 font-medium">最近活跃</th>
            <th class="w-20 border-b border-line-soft px-3 py-2 font-medium">操作</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="s in items" :key="s.id" class="[&:last-child>td]:border-b-0">
            <td class="border-b border-line-soft px-4.5 py-2.5">
              <div class="flex items-center gap-2.5">
                <span
                  class="flex h-7 w-7 flex-none items-center justify-center rounded-md bg-wash text-ink-2"
                >
                  <svg
                    v-if="deviceOf(s).mobile"
                    class="h-3.5 w-3.5"
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    stroke-width="1.8"
                    stroke-linecap="round"
                  >
                    <rect x="7" y="2" width="10" height="20" rx="2" />
                    <path d="M11 18h2" />
                  </svg>
                  <svg
                    v-else
                    class="h-3.5 w-3.5"
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    stroke-width="1.8"
                    stroke-linecap="round"
                  >
                    <rect x="3" y="5" width="18" height="12" rx="2" />
                    <path d="M8 21h8M12 17v4" />
                  </svg>
                </span>
                <span class="font-medium whitespace-nowrap">{{ deviceOf(s).label }}</span>
                <span
                  v-if="s.current"
                  class="rounded-full bg-ok/12 px-2 py-px text-[11px] font-semibold whitespace-nowrap text-ok"
                >
                  本机
                </span>
              </div>
            </td>
            <td class="border-b border-line-soft px-3 py-2.5">
              <span
                class="rounded-full border border-line bg-white px-2 py-px text-[11.5px] whitespace-nowrap text-ink-2"
              >
                {{ methodLabels[s.method] || s.method || '—' }}
              </span>
            </td>
            <td class="mono border-b border-line-soft px-3 py-2.5 text-ink-2">{{ s.clientIp }}</td>
            <td class="border-b border-line-soft px-3 py-2.5 whitespace-nowrap text-ink-2">
              {{ fmtTime(s.createdAt) }}
            </td>
            <td class="border-b border-line-soft px-3 py-2.5 whitespace-nowrap text-ink-2">
              {{ fmtRelative(s.lastSeenAt) }}
            </td>
            <td class="border-b border-line-soft px-3 py-2.5">
              <span v-if="s.current" class="px-2 text-xs text-ink-3">—</span>
              <ConfirmPop
                v-else
                title="撤销该会话？"
                :content="`${deviceOf(s).label}(${s.clientIp})将立即退出登录;其他会话不受影响。`"
                confirm-text="撤销"
                :on-confirm="() => revokeOne(s)"
              >
                <template #trigger>
                  <NButton size="tiny" type="error" quaternary :disabled="revoking">撤销</NButton>
                </template>
              </ConfirmPop>
            </td>
          </tr>
        </tbody>
      </table>
    </div>
    <FootNote>
      当前会话不可定点撤销(请用退出登录);升级前签发的存量令牌不在列表中,24h 内自然过期
    </FootNote>
  </div>
</template>
