<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { NAlert, NButton, NEmpty, NIcon, NSkeleton, NTag, useMessage } from 'naive-ui'
import { Check, Eye, EyeOff, Refresh, Trash, X } from '@vicons/tabler'
import ImageDetailModal from '@/components/cloud/ImageDetailModal.vue'
import MiniLocationMap from '@/components/cloud/MiniLocationMap.vue'
import { supabase } from '@/lib/supabase'
import { useAuthStore } from '@/stores/auth'
import { useProfileStore } from '@/stores/profile'
import type { CloudType, Profile } from '@/types/database'

type AdminTab = 'dashboard' | 'review' | 'users' | 'images'
type CloudStatus = 'pending' | 'approved' | 'rejected'
type ImageFilter = 'all' | CloudStatus

interface AdminCloud {
  id: string
  user_id: string
  cloud_type_id: number | null
  custom_cloud_type: string | null
  image_url: string
  thumbnail_url: string | null
  latitude: number | null
  longitude: number | null
  location_name: string | null
  description: string | null
  captured_at: string | null
  created_at: string
  status: CloudStatus
  is_hidden: boolean
  cloudTypeName: string
  cloudTypeRarity: CloudType['rarity']
  username: string
}

interface DashboardStats {
  users: number
  images: number
  todayUploads: number
  pending: number
  approved: number
  rejected: number
  hidden: number
}

const authStore = useAuthStore()
const profileStore = useProfileStore()
const message = useMessage()

const activeTab = ref<AdminTab>('dashboard')
const loading = ref(true)
const actionLoading = ref(false)
const loadError = ref('')
const dashboardStats = ref<DashboardStats>({
  users: 0,
  images: 0,
  todayUploads: 0,
  pending: 0,
  approved: 0,
  rejected: 0,
  hidden: 0,
})
const users = ref<Profile[]>([])
const images = ref<AdminCloud[]>([])
const selectedReviewIds = ref<Set<string>>(new Set())
const imageFilter = ref<ImageFilter>('all')
const selectedImage = ref<AdminCloud | null>(null)

const rarityMeta = {
  common: { label: '常见', chip: 'border-sky-200 bg-sky-100 text-sky-700' },
  uncommon: { label: '少见', chip: 'border-amber-200 bg-amber-100 text-amber-700' },
  rare: { label: '罕见', chip: 'border-rose-200 bg-rose-100 text-rose-700' },
} satisfies Record<CloudType['rarity'], { label: string; chip: string }>

const statusMeta = {
  pending: { label: '待审核', chip: 'border-amber-200 bg-amber-100 text-amber-700' },
  approved: { label: '已通过', chip: 'border-emerald-200 bg-emerald-100 text-emerald-700' },
  rejected: { label: '已拒绝', chip: 'border-rose-200 bg-rose-100 text-rose-700' },
} satisfies Record<CloudStatus, { label: string; chip: string }>

const tabs = [
  { key: 'dashboard', label: '数据看板' },
  { key: 'review', label: '内容审核' },
  { key: 'users', label: '用户管理' },
  { key: 'images', label: '图片管理' },
] satisfies Array<{ key: AdminTab; label: string }>

const pendingImages = computed(() => images.value.filter(item => item.status === 'pending'))
const filteredImages = computed(() => {
  if (imageFilter.value === 'all') return images.value
  return images.value.filter(item => item.status === imageFilter.value)
})

const cloudTypeStats = computed(() => {
  const counts = new Map<string, number>()
  for (const item of images.value) {
    counts.set(item.cloudTypeName, (counts.get(item.cloudTypeName) || 0) + 1)
  }
  return Array.from(counts.entries())
    .map(([name, count]) => ({ name, count }))
    .sort((a, b) => b.count - a.count)
    .slice(0, 8)
})

const selectedReviewCount = computed(() => selectedReviewIds.value.size)

function readJoinedOne(value: unknown) {
  const rows = Array.isArray(value) ? value : value ? [value] : []
  return rows[0] as Record<string, unknown> | undefined
}

function toAdminCloud(row: Record<string, unknown>): AdminCloud {
  const cloudType = readJoinedOne(row.cloud_types)
  const profile = readJoinedOne(row.profiles)

  return {
    id: row.id as string,
    user_id: row.user_id as string,
    cloud_type_id: (row.cloud_type_id as number | null) ?? null,
    custom_cloud_type: (row.custom_cloud_type as string | null) ?? null,
    image_url: row.image_url as string,
    thumbnail_url: (row.thumbnail_url as string | null) ?? null,
    latitude: (row.latitude as number | null) ?? null,
    longitude: (row.longitude as number | null) ?? null,
    location_name: (row.location_name as string | null) ?? null,
    description: (row.description as string | null) ?? null,
    captured_at: (row.captured_at as string | null) ?? null,
    created_at: row.created_at as string,
    status: row.status as CloudStatus,
    is_hidden: (row.is_hidden as boolean) ?? false,
    cloudTypeName: (cloudType?.name as string) || (row.custom_cloud_type as string) || '未知云型',
    cloudTypeRarity: (cloudType?.rarity as CloudType['rarity']) || 'common',
    username: (profile?.username as string) || '匿名用户',
  }
}

function formatDateTime(iso: string | null) {
  if (!iso) return '未知时间'
  return new Date(iso).toLocaleString('zh-CN', {
    year: 'numeric',
    month: 'numeric',
    day: 'numeric',
    hour: '2-digit',
    minute: '2-digit',
  })
}

function formatCoordinate(value: number | null) {
  return value === null ? '未记录' : value.toFixed(2)
}

function getErrorMessage(error: unknown, fallback: string) {
  if (!error || typeof error !== 'object') return fallback
  const payload = error as {
    message?: unknown
    code?: unknown
    details?: unknown
    hint?: unknown
  }
  const parts = [
    typeof payload.message === 'string' ? payload.message : '',
    typeof payload.code === 'string' ? `错误码：${payload.code}` : '',
    typeof payload.details === 'string' ? payload.details : '',
    typeof payload.hint === 'string' ? `提示：${payload.hint}` : '',
  ].filter(Boolean)
  return parts.length ? parts.join('；') : fallback
}

function todayIsoStart() {
  const date = new Date()
  date.setHours(0, 0, 0, 0)
  return date.toISOString()
}

async function countProfiles() {
  const { count, error } = await supabase
    .from('profiles')
    .select('*', { head: true, count: 'exact' })
  if (error) throw error
  return count || 0
}

async function countClouds(filters: {
  status?: CloudStatus
  isHidden?: boolean
  createdAfter?: string
} = {}) {
  let query = supabase
    .from('clouds')
    .select('*', { head: true, count: 'exact' })

  if (filters.status) query = query.eq('status', filters.status)
  if (typeof filters.isHidden === 'boolean') query = query.eq('is_hidden', filters.isHidden)
  if (filters.createdAfter) query = query.gte('created_at', filters.createdAfter)

  const { count, error } = await query
  if (error) throw error
  return count || 0
}

async function fetchStats() {
  const [
    userCount,
    imageCount,
    todayUploads,
    pendingCount,
    approvedCount,
    rejectedCount,
    hiddenCount,
  ] = await Promise.all([
    countProfiles(),
    countClouds(),
    countClouds({ createdAfter: todayIsoStart() }),
    countClouds({ status: 'pending' }),
    countClouds({ status: 'approved' }),
    countClouds({ status: 'rejected' }),
    countClouds({ isHidden: true }),
  ])

  dashboardStats.value = {
    users: userCount,
    images: imageCount,
    todayUploads,
    pending: pendingCount,
    approved: approvedCount,
    rejected: rejectedCount,
    hidden: hiddenCount,
  }
}

async function fetchUsers() {
  const { data, error } = await supabase
    .from('profiles')
    .select('id,username,avatar_url,role,is_disabled,created_at')
    .order('created_at', { ascending: false })
    .limit(100)

  if (error) throw error
  users.value = (data || []) as Profile[]
}

async function fetchImages() {
  const { data, error } = await supabase
    .from('clouds')
    .select('id,user_id,cloud_type_id,custom_cloud_type,image_url,thumbnail_url,latitude,longitude,location_name,description,captured_at,created_at,status,is_hidden,cloud_types(name,rarity),profiles(username)')
    .order('created_at', { ascending: false })
    .limit(120)

  if (error) throw error
  images.value = ((data || []) as Array<Record<string, unknown>>).map(toAdminCloud)
  selectedReviewIds.value = new Set([...selectedReviewIds.value].filter(id => images.value.some(item => item.id === id)))
}

async function loadAdminData() {
  loading.value = true
  loadError.value = ''

  try {
    await Promise.all([
      fetchStats(),
      fetchUsers(),
      fetchImages(),
    ])
  } catch (error) {
    loadError.value = error instanceof Error ? error.message : '管理后台加载失败'
  } finally {
    loading.value = false
  }
}

function setSelectedReviewIds(ids: Iterable<string>) {
  selectedReviewIds.value = new Set(ids)
}

function toggleReviewSelection(cloudId: string) {
  const next = new Set(selectedReviewIds.value)
  if (next.has(cloudId)) {
    next.delete(cloudId)
  } else {
    next.add(cloudId)
  }
  setSelectedReviewIds(next)
}

function toggleAllPendingSelection() {
  if (selectedReviewIds.value.size === pendingImages.value.length) {
    setSelectedReviewIds([])
    return
  }
  setSelectedReviewIds(pendingImages.value.map(item => item.id))
}

function patchImages(ids: string[], patch: Partial<AdminCloud>) {
  const idSet = new Set(ids)
  images.value = images.value.map(item => (idSet.has(item.id) ? { ...item, ...patch } : item))
  if (selectedImage.value && idSet.has(selectedImage.value.id)) {
    selectedImage.value = { ...selectedImage.value, ...patch }
  }
}

async function updateCloudStatus(ids: string[], status: CloudStatus) {
  if (!ids.length) return

  actionLoading.value = true
  loadError.value = ''

  try {
    let query = supabase
      .from('clouds')
      .update({ status })
      .select('id')

    query = ids.length === 1 ? query.eq('id', ids[0]) : query.in('id', ids)

    const { data, error } = await query

    if (error) throw error
    if ((data || []).length !== ids.length) {
      throw new Error('部分图片状态没有写入数据库，请检查管理员 UPDATE RLS policy。')
    }

    patchImages(ids, { status })
    setSelectedReviewIds([...selectedReviewIds.value].filter(id => !ids.includes(id)))
    await fetchStats()
    message.success(`已${status === 'approved' ? '通过' : '拒绝'} ${ids.length} 张图片`)
  } catch (error) {
    const text = getErrorMessage(error, '审核操作失败')
    loadError.value = text
    message.error(text)
  } finally {
    actionLoading.value = false
  }
}

async function toggleImageVisibility(cloud: AdminCloud) {
  actionLoading.value = true
  loadError.value = ''

  try {
    const nextHidden = !cloud.is_hidden
    const { data, error } = await supabase
      .from('clouds')
      .update({ is_hidden: nextHidden })
      .eq('id', cloud.id)
      .select('id')

    if (error) throw error
    if (!data?.length) {
      throw new Error('图片可见性没有写入数据库，请检查管理员 UPDATE RLS policy。')
    }

    patchImages([cloud.id], { is_hidden: nextHidden })
    await fetchStats()
    message.success(nextHidden ? '图片已设为隐藏' : '图片已恢复公开')
  } catch (error) {
    const text = getErrorMessage(error, '可见性更新失败')
    loadError.value = text
    message.error(text)
  } finally {
    actionLoading.value = false
  }
}

async function deleteImage(cloud: AdminCloud) {
  const confirmed = window.confirm(`确定删除 ${cloud.cloudTypeName} 这张图片吗？删除后无法在页面中恢复。`)
  if (!confirmed) return

  actionLoading.value = true
  loadError.value = ''

  try {
    await profileStore.deleteClouds(cloud.user_id, [cloud.id])
    images.value = images.value.filter(item => item.id !== cloud.id)
    if (selectedImage.value?.id === cloud.id) selectedImage.value = null
    setSelectedReviewIds([...selectedReviewIds.value].filter(id => id !== cloud.id))
    await fetchStats()
    message.success('图片已删除')
  } catch (error) {
    const text = getErrorMessage(error, '图片删除失败')
    loadError.value = text
    message.error(text)
  } finally {
    actionLoading.value = false
  }
}

async function updateUserRole(user: Profile, role: Profile['role']) {
  if (user.id === authStore.user?.id && role !== 'admin') {
    message.warning('不能移除自己的管理员权限')
    return
  }

  actionLoading.value = true
  loadError.value = ''

  try {
    const { data, error } = await supabase
      .from('profiles')
      .update({ role })
      .eq('id', user.id)
      .select('id,role')

    if (error) throw error
    if (!data?.length) {
      throw new Error('用户角色没有写入数据库，请检查管理员 UPDATE RLS policy。')
    }

    users.value = users.value.map(item => (item.id === user.id ? { ...item, role } : item))
    message.success('用户角色已更新')
  } catch (error) {
    const text = getErrorMessage(error, '用户角色更新失败')
    loadError.value = text
    message.error(text)
  } finally {
    actionLoading.value = false
  }
}

async function toggleUserDisabled(user: Profile) {
  if (user.id === authStore.user?.id) {
    message.warning('不能禁用当前登录的管理员账号')
    return
  }

  actionLoading.value = true
  loadError.value = ''

  try {
    const nextDisabled = !user.is_disabled
    const { data, error } = await supabase
      .from('profiles')
      .update({ is_disabled: nextDisabled })
      .eq('id', user.id)
      .select('id,is_disabled')

    if (error) throw error
    if (!data?.length) {
      throw new Error('用户状态没有写入数据库，请检查 profiles 表的 UPDATE RLS policy。')
    }

    users.value = users.value.map(item => (item.id === user.id ? { ...item, is_disabled: nextDisabled } : item))
    message.success(nextDisabled ? '用户已禁用' : '用户已恢复')
  } catch (error) {
    const text = getErrorMessage(error, '用户状态更新失败')
    loadError.value = text
    message.error(text)
  } finally {
    actionLoading.value = false
  }
}

onMounted(loadAdminData)
</script>

<template>
  <div class="min-h-screen bg-[linear-gradient(180deg,#f8fafc_0%,#eef6ff_55%,#f8fafc_100%)]">
    <section class="border-b border-slate-200 bg-white/82 backdrop-blur">
      <div class="mx-auto max-w-7xl px-4 py-10">
        <div class="flex flex-col gap-5 lg:flex-row lg:items-end lg:justify-between">
          <div>
            <p class="text-sm font-semibold uppercase tracking-[0.24em] text-sky-700">Admin Console</p>
            <h1 class="mt-3 text-4xl font-bold text-slate-950">管理后台</h1>
            <p class="mt-4 max-w-2xl text-sm leading-7 text-slate-600">
              集中处理社区云图审核、图片可见性、用户角色和运行数据。所有写入都直接落到 Supabase。
            </p>
          </div>

          <NButton secondary strong type="default" class="oc-panel-button oc-panel-button--neutral" :loading="loading" @click="loadAdminData">
            <template #icon>
              <NIcon><Refresh /></NIcon>
            </template>
            刷新数据
          </NButton>
        </div>
      </div>
    </section>

    <main class="mx-auto max-w-7xl px-4 py-8">
      <NAlert
        v-if="loadError"
        class="mb-6"
        type="error"
        :show-icon="false"
        :bordered="false"
        title="后台操作失败"
      >
        {{ loadError }}
      </NAlert>

      <section v-if="loading" class="grid gap-4 md:grid-cols-4">
        <div v-for="n in 8" :key="n" class="border border-slate-200 bg-white p-5">
          <NSkeleton class="h-4 w-1/2" />
          <NSkeleton class="mt-4 h-8 w-2/3" />
          <NSkeleton class="mt-3 h-3 w-full" />
        </div>
      </section>

      <template v-else>
        <section class="grid gap-3 md:grid-cols-4">
          <button
            v-for="tab in tabs"
            :key="tab.key"
            type="button"
            class="border px-5 py-4 text-left transition-colors"
            :class="activeTab === tab.key ? 'border-sky-500 bg-sky-50 text-sky-900' : 'border-slate-200 bg-white text-slate-600 hover:border-slate-400'"
            @click="activeTab = tab.key"
          >
            <span class="text-sm font-semibold">{{ tab.label }}</span>
            <span v-if="tab.key === 'review'" class="ml-2 text-xs text-amber-600">{{ dashboardStats.pending }}</span>
          </button>
        </section>

        <section v-if="activeTab === 'dashboard'" class="mt-6 space-y-6">
          <div class="grid gap-4 md:grid-cols-4">
            <div class="border border-slate-200 bg-white p-5">
              <p class="text-sm text-slate-500">总用户数</p>
              <p class="mt-3 text-3xl font-bold text-slate-950">{{ dashboardStats.users }}</p>
            </div>
            <div class="border border-slate-200 bg-white p-5">
              <p class="text-sm text-slate-500">图片总数</p>
              <p class="mt-3 text-3xl font-bold text-slate-950">{{ dashboardStats.images }}</p>
            </div>
            <div class="border border-slate-200 bg-white p-5">
              <p class="text-sm text-slate-500">今日上传</p>
              <p class="mt-3 text-3xl font-bold text-slate-950">{{ dashboardStats.todayUploads }}</p>
            </div>
            <div class="border border-amber-200 bg-amber-50 p-5">
              <p class="text-sm text-amber-700">待审核</p>
              <p class="mt-3 text-3xl font-bold text-amber-900">{{ dashboardStats.pending }}</p>
            </div>
          </div>

          <div class="grid gap-6 lg:grid-cols-[1fr_1.2fr]">
            <div class="border border-slate-200 bg-white p-6">
              <h2 class="text-xl font-bold text-slate-950">审核状态</h2>
              <div class="mt-5 grid gap-3">
                <div class="flex items-center justify-between border border-slate-100 bg-slate-50 px-4 py-3">
                  <span class="text-sm text-slate-600">已通过</span>
                  <span class="font-semibold text-emerald-700">{{ dashboardStats.approved }}</span>
                </div>
                <div class="flex items-center justify-between border border-slate-100 bg-slate-50 px-4 py-3">
                  <span class="text-sm text-slate-600">已拒绝</span>
                  <span class="font-semibold text-rose-700">{{ dashboardStats.rejected }}</span>
                </div>
                <div class="flex items-center justify-between border border-slate-100 bg-slate-50 px-4 py-3">
                  <span class="text-sm text-slate-600">隐藏图片</span>
                  <span class="font-semibold text-slate-900">{{ dashboardStats.hidden }}</span>
                </div>
              </div>
            </div>

            <div class="border border-slate-200 bg-white p-6">
              <h2 class="text-xl font-bold text-slate-950">云型分布</h2>
              <div v-if="cloudTypeStats.length" class="mt-5 space-y-3">
                <div v-for="item in cloudTypeStats" :key="item.name">
                  <div class="mb-1 flex items-center justify-between text-sm">
                    <span class="text-slate-600">{{ item.name }}</span>
                    <span class="font-medium text-slate-900">{{ item.count }}</span>
                  </div>
                  <div class="h-2 bg-slate-100">
                    <div
                      class="h-full bg-sky-500"
                      :style="{ width: `${Math.max(8, item.count / Math.max(1, dashboardStats.images) * 100)}%` }"
                    ></div>
                  </div>
                </div>
              </div>
              <NEmpty v-else class="py-6" description="暂无图片数据" />
            </div>
          </div>
        </section>

        <section v-else-if="activeTab === 'review'" class="mt-6">
          <div class="mb-4 flex flex-col gap-3 border border-slate-200 bg-white p-4 md:flex-row md:items-center md:justify-between">
            <div>
              <h2 class="text-xl font-bold text-slate-950">待审核队列</h2>
              <p class="mt-1 text-sm text-slate-500">共 {{ pendingImages.length }} 张待处理图片。</p>
            </div>
            <div class="flex flex-wrap gap-2">
              <NButton secondary strong type="default" class="oc-panel-button oc-panel-button--neutral" @click="toggleAllPendingSelection">
                {{ selectedReviewCount === pendingImages.length && pendingImages.length ? '取消全选' : '全选待审核' }}
              </NButton>
              <NButton
                secondary
                strong
                type="default"
                class="oc-panel-button oc-panel-button--teal"
                :disabled="!selectedReviewCount"
                :loading="actionLoading"
                @click="updateCloudStatus(Array.from(selectedReviewIds), 'approved')"
              >
                批量通过 {{ selectedReviewCount || '' }}
              </NButton>
              <NButton
                secondary
                strong
                type="default"
                class="oc-panel-button oc-panel-button--danger"
                :disabled="!selectedReviewCount"
                :loading="actionLoading"
                @click="updateCloudStatus(Array.from(selectedReviewIds), 'rejected')"
              >
                批量拒绝 {{ selectedReviewCount || '' }}
              </NButton>
            </div>
          </div>

          <div v-if="pendingImages.length" class="grid gap-4 lg:grid-cols-2">
            <article v-for="item in pendingImages" :key="item.id" class="grid gap-4 border border-slate-200 bg-white p-4 md:grid-cols-[180px_minmax(0,1fr)]">
              <button type="button" class="overflow-hidden bg-slate-100" @click="selectedImage = item">
                <img :src="item.thumbnail_url || item.image_url" :alt="item.cloudTypeName" class="h-44 w-full object-cover transition-transform hover:scale-105" />
              </button>
              <div class="min-w-0">
                <div class="flex items-start justify-between gap-3">
                  <div>
                    <p class="text-lg font-semibold text-slate-950">{{ item.cloudTypeName }}</p>
                    <p class="mt-1 text-sm text-slate-500">上传者：{{ item.username }}</p>
                  </div>
                  <input
                    type="checkbox"
                    class="mt-1 h-5 w-5"
                    :checked="selectedReviewIds.has(item.id)"
                    @change="toggleReviewSelection(item.id)"
                  />
                </div>
                <p class="mt-3 text-sm text-slate-600">{{ item.location_name || '未填写位置' }} · {{ formatDateTime(item.created_at) }}</p>
                <p class="mt-2 line-clamp-2 text-sm leading-6 text-slate-500">{{ item.description || '没有图片说明。' }}</p>
                <div class="mt-4 flex flex-wrap gap-2">
                  <NButton size="small" type="default" secondary strong class="oc-panel-button oc-panel-button--teal" :loading="actionLoading" @click="updateCloudStatus([item.id], 'approved')">
                    <template #icon><NIcon><Check /></NIcon></template>
                    通过
                  </NButton>
                  <NButton size="small" type="default" secondary strong class="oc-panel-button oc-panel-button--danger" :loading="actionLoading" @click="updateCloudStatus([item.id], 'rejected')">
                    <template #icon><NIcon><X /></NIcon></template>
                    拒绝
                  </NButton>
                  <NButton size="small" type="default" secondary strong class="oc-panel-button oc-panel-button--neutral" @click="selectedImage = item">查看大图</NButton>
                </div>
              </div>
            </article>
          </div>

          <div v-else class="border border-dashed border-slate-300 bg-white p-10">
            <NEmpty description="当前没有待审核图片" />
          </div>
        </section>

        <section v-else-if="activeTab === 'users'" class="mt-6">
          <div class="overflow-hidden border border-slate-200 bg-white">
            <div class="grid grid-cols-[1.1fr_0.8fr_0.7fr_0.9fr_1.2fr] border-b border-slate-200 bg-slate-50 px-4 py-3 text-xs font-semibold uppercase tracking-[0.14em] text-slate-500">
              <span>用户</span>
              <span>角色</span>
              <span>状态</span>
              <span>注册时间</span>
              <span>操作</span>
            </div>
            <div v-for="user in users" :key="user.id" class="grid grid-cols-[1.1fr_0.8fr_0.7fr_0.9fr_1.2fr] items-center gap-3 border-b border-slate-100 px-4 py-4 last:border-b-0">
              <div class="min-w-0">
                <p class="truncate font-medium text-slate-950">{{ user.username }}</p>
                <p class="mt-1 truncate text-xs text-slate-400">{{ user.id }}</p>
              </div>
              <NTag size="small" :bordered="false" :type="user.role === 'admin' ? 'success' : 'default'">
                {{ user.role === 'admin' ? '管理员' : '用户' }}
              </NTag>
              <NTag size="small" :bordered="false" :type="user.is_disabled ? 'error' : 'success'">
                {{ user.is_disabled ? '已禁用' : '正常' }}
              </NTag>
              <span class="text-sm text-slate-500">{{ formatDateTime(user.created_at) }}</span>
              <div class="flex flex-wrap gap-2">
                <NButton
                  size="small"
                  secondary
                  strong
                  type="default"
                  class="oc-panel-button oc-panel-button--amber"
                  :disabled="user.id === authStore.user?.id && user.role === 'admin'"
                  :loading="actionLoading"
                  @click="updateUserRole(user, user.role === 'admin' ? 'user' : 'admin')"
                >
                  {{ user.role === 'admin' ? '设为用户' : '设为管理员' }}
                </NButton>
                <NButton
                  size="small"
                  type="default"
                  secondary
                  strong
                  :class="user.is_disabled ? 'oc-panel-button oc-panel-button--teal' : 'oc-panel-button oc-panel-button--danger'"
                  :disabled="user.id === authStore.user?.id"
                  :loading="actionLoading"
                  @click="toggleUserDisabled(user)"
                >
                  {{ user.is_disabled ? '恢复' : '禁用' }}
                </NButton>
              </div>
            </div>
          </div>
        </section>

        <section v-else class="mt-6">
          <div class="mb-4 flex flex-col gap-3 border border-slate-200 bg-white p-4 md:flex-row md:items-center md:justify-between">
            <div>
              <h2 class="text-xl font-bold text-slate-950">图片管理</h2>
              <p class="mt-1 text-sm text-slate-500">最近 {{ images.length }} 张图片，可调整审核状态、可见性或删除。</p>
            </div>
            <select
              v-model="imageFilter"
              class="border border-slate-300 bg-white px-3 py-2 text-sm focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-500"
            >
              <option value="all">全部状态</option>
              <option value="pending">待审核</option>
              <option value="approved">已通过</option>
              <option value="rejected">已拒绝</option>
            </select>
          </div>

          <div v-if="filteredImages.length" class="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
            <article v-for="item in filteredImages" :key="item.id" class="overflow-hidden border border-slate-200 bg-white">
              <button type="button" class="block w-full overflow-hidden bg-slate-100" @click="selectedImage = item">
                <img :src="item.thumbnail_url || item.image_url" :alt="item.cloudTypeName" class="h-52 w-full object-cover transition-transform hover:scale-105" />
              </button>
              <div class="p-4">
                <div class="flex items-start justify-between gap-3">
                  <div class="min-w-0">
                    <p class="truncate font-semibold text-slate-950">{{ item.cloudTypeName }}</p>
                    <p class="mt-1 truncate text-sm text-slate-500">{{ item.username }} · {{ item.location_name || '未填写位置' }}</p>
                  </div>
                  <NTag size="small" :bordered="false" :class="statusMeta[item.status].chip">
                    {{ statusMeta[item.status].label }}
                  </NTag>
                </div>
                <div class="mt-4 flex flex-wrap gap-2">
                  <NButton size="small" type="default" secondary strong class="oc-panel-button oc-panel-button--neutral" @click="selectedImage = item">查看</NButton>
                  <NButton size="small" type="default" secondary strong class="oc-panel-button oc-panel-button--teal" :disabled="item.status === 'approved'" :loading="actionLoading" @click="updateCloudStatus([item.id], 'approved')">通过</NButton>
                  <NButton size="small" type="default" secondary strong class="oc-panel-button oc-panel-button--danger" :disabled="item.status === 'rejected'" :loading="actionLoading" @click="updateCloudStatus([item.id], 'rejected')">拒绝</NButton>
                  <NButton size="small" type="default" secondary strong class="oc-panel-button oc-panel-button--amber" :loading="actionLoading" @click="toggleImageVisibility(item)">
                    <template #icon>
                      <NIcon>
                        <Eye v-if="item.is_hidden" />
                        <EyeOff v-else />
                      </NIcon>
                    </template>
                    {{ item.is_hidden ? '公开' : '隐藏' }}
                  </NButton>
                  <NButton size="small" type="default" secondary strong class="oc-panel-button oc-panel-button--danger" :loading="actionLoading" @click="deleteImage(item)">
                    <template #icon><NIcon><Trash /></NIcon></template>
                    删除
                  </NButton>
                </div>
              </div>
            </article>
          </div>

          <div v-else class="border border-dashed border-slate-300 bg-white p-10">
            <NEmpty description="没有符合筛选条件的图片" />
          </div>
        </section>
      </template>
    </main>

    <ImageDetailModal
      v-if="selectedImage"
      :open="!!selectedImage"
      :image-url="selectedImage.image_url"
      :thumbnail-url="selectedImage.thumbnail_url"
      :image-alt="selectedImage.cloudTypeName"
      :title="selectedImage.cloudTypeName"
      :subtitle="`上传者：${selectedImage.username}`"
      :badge-label="rarityMeta[selectedImage.cloudTypeRarity].label"
      :badge-class="rarityMeta[selectedImage.cloudTypeRarity].chip"
      @close="selectedImage = null"
    >
      <div class="grid gap-4 sm:grid-cols-2">
        <div class="border border-slate-200 bg-slate-50 p-4">
          <p class="text-xs uppercase tracking-[0.18em] text-slate-500">上传时间</p>
          <p class="mt-2 text-sm font-medium text-slate-900">{{ formatDateTime(selectedImage.created_at) }}</p>
        </div>
        <div class="border border-slate-200 bg-slate-50 p-4">
          <p class="text-xs uppercase tracking-[0.18em] text-slate-500">拍摄时间</p>
          <p class="mt-2 text-sm font-medium text-slate-900">{{ formatDateTime(selectedImage.captured_at) }}</p>
        </div>
        <div class="border border-slate-200 bg-slate-50 p-4">
          <p class="text-xs uppercase tracking-[0.18em] text-slate-500">状态</p>
          <p class="mt-2 text-sm font-medium text-slate-900">
            {{ statusMeta[selectedImage.status].label }} / {{ selectedImage.is_hidden ? '隐藏' : '公开' }}
          </p>
        </div>
        <div class="border border-slate-200 bg-slate-50 p-4">
          <p class="text-xs uppercase tracking-[0.18em] text-slate-500">模糊化经纬度</p>
          <p class="mt-2 text-sm font-medium text-slate-900">
            纬度 {{ formatCoordinate(selectedImage.latitude) }} / 经度 {{ formatCoordinate(selectedImage.longitude) }}
          </p>
        </div>
      </div>

      <div class="mt-5 border border-slate-200 bg-white p-5">
        <p class="text-xs uppercase tracking-[0.18em] text-slate-500">图片说明</p>
        <p class="mt-3 text-sm leading-7 text-slate-700">
          {{ selectedImage.description || '上传者没有留下额外说明。' }}
        </p>
      </div>
      <MiniLocationMap
        :latitude="selectedImage.latitude"
        :longitude="selectedImage.longitude"
        :location-name="selectedImage.location_name"
      />
    </ImageDetailModal>
  </div>
</template>
