<script setup>
import { Teleport, computed, inject, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { api } from '../lib/api'
import { editorPath, userProfilePath } from '../lib/paths'
import { toApiUrl } from '../lib/runtime'
import deleteIcon from '../assets/icons/delete.svg'
import SvgIcon from '../components/SvgIcon.vue'
import TagBadgeInput from '../components/TagBadgeInput.vue'
import AdminFeaturedSection from '../components/admin/AdminFeaturedSection.vue'
import AdminTemplatesSection from '../components/admin/AdminTemplatesSection.vue'
import AdminItemsSection from '../components/admin/AdminItemsSection.vue'
import AdminTierlistsSection from '../components/admin/AdminTierlistsSection.vue'
import AdminUsersSection from '../components/admin/AdminUsersSection.vue'
import { useAdminCustomItems } from '../composables/useAdminCustomItems'
import { useAdminFeaturedTemplates } from '../composables/useAdminFeaturedTemplates'
import { useAdminTemplateManager } from '../composables/useAdminTemplateManager'
import { useAdminTemplateRequests } from '../composables/useAdminTemplateRequests'
import { useAdminUsers } from '../composables/useAdminUsers'
import { useAuthStore } from '../stores/auth'
import { useToast } from '../composables/useToast'

const auth = useAuthStore()
const toast = useToast()
const route = useRoute()
const router = useRouter()
const globalRightRailOpen = inject('rightRailOpen', ref(true))
const localRightRailTarget = inject('localRightRailTarget', '#local-right-rail-root')
const isAdmin = computed(() => !!auth.user?.isAdmin)

const activeTab = ref('featured')
const tierlistsMode = ref('requests')

const templates = ref([])
const selectedTemplateId = ref('')
const selectedTemplate = ref(null)
const featuredTemplateIds = ref([])
const templatePickerModalOpen = ref(false)
const templatePickerMode = ref('template-admin')
const templatePickerQuery = ref('')
const templatePickerSort = ref('recent')

const customItems = ref([])
const customItemQuery = ref('')
const customItemPage = ref(1)
const customItemLimit = ref(50)
const customItemTotal = ref(0)
const customItemFilter = ref('library')
const customItemModalTargetTemplateId = ref('')
const customItemReplacementQuery = ref('')
const customItemReplacementItems = ref([])
const customItemReplacementLoading = ref(false)
const customItemReplacementTargetId = ref('')
const customItemReplacementBusy = ref(false)

const adminTierLists = ref([])
const adminTierListQuery = ref('')
const adminTierListTopicId = ref('')
const adminTierListSort = ref('recent')
const adminTierListMinFavorites = ref(0)
const adminTierListPage = ref(1)
const adminTierListLimit = ref(50)
const adminTierListTotal = ref(0)
const adminTierListStats = ref({ total: 0, publicCount: 0, privateCount: 0, featuredCount: 0 })
const selectedTemplateTierListStats = ref({ total: 0, publicCount: 0, privateCount: 0 })
const templateRequests = ref([])
const importModalOpen = ref(false)
const importModalMode = ref('existing')
const importModalTierList = ref(null)
const importModalItems = ref([])
const importModalTargetTemplateId = ref('')
const importModalNewTemplateId = ref('')
const importModalNewTemplateName = ref('')
const templateSourceImportModalOpen = ref(false)
const templateSourceImportQuery = ref('')
const templateSourceImportSelectedIds = ref([])
const templateLibraryItemModalOpen = ref(false)
const templateLibraryItemQuery = ref('')
const templateLibraryItemResults = ref([])
const templateLibraryItemSelectedIds = ref([])
const templateLibraryItemLoading = ref(false)
const templateBulkTagModalOpen = ref(false)
const templateBulkTagDrafts = ref([])
const templateBulkTagSaving = ref(false)
const previewModalOpen = ref(false)
const previewTierList = ref(null)
const adminTierListManageModalOpen = ref(false)
const activeTemplateRequest = ref(null)
const userEditModalOpen = ref(false)
const userPasswordModalOpen = ref(false)
const userDeleteModalOpen = ref(false)
const userRoleModalOpen = ref(false)
const customItemModalOpen = ref(false)
const customItemDeleteModalOpen = ref(false)
const templateItemDeleteModalOpen = ref(false)
const customItemModalHistoryActive = ref(false)
const modalTargetUser = ref(null)
const modalPasswordDraft = ref('')
const modalRoleNextAdmin = ref(false)
const modalUserDraftEmail = ref('')
const modalUserDraftNickname = ref('')
const modalUserDraftIsAdmin = ref(false)
const modalTargetCustomItem = ref(null)
const modalTargetTemplateItem = ref(null)
const modalTargetTemplateItemUsage = ref({ totalCount: 0, publicCount: 0, privateCount: 0 })
const customItemModalDraftLabel = ref('')
const customItemModalDraftTags = ref([])
const customItemModalLabelSaving = ref(false)
const modalTargetAdminTierList = ref(null)
const adminTierListDraftTitle = ref('')
const adminTierListDraftDescription = ref('')
const adminTierListDraftIsPublic = ref(false)
const adminTierListSaving = ref(false)
const adminTierListDeleting = ref(false)

const users = ref([])
const userQuery = ref('')
const userSort = ref('recent')
const userSortDirection = ref('desc')
const imageStats = ref(null)
const imageQueue = ref({ concurrency: 1, activeCount: 0, pendingCount: 0 })
const imageRecentJobs = ref([])
const imageStatsMonth = ref('')
const imageStatsLimit = ref(12)
const imageResetModalOpen = ref(false)
const imageMissingCleanupBusy = ref(false)

const error = ref('')
const success = ref('')

const newTemplateId = ref('')
const newTemplateName = ref('')
const newTemplateIsPublic = ref(false)
const templateMetaDraftName = ref('')
const templateMetaDraftSlug = ref('')
const templateMetaSaving = ref(false)
const templateVisibilitySaving = ref(false)

const uploadFiles = ref([])
const uploadItemDrafts = ref([])
const thumbFile = ref(null)
const itemPreviewUrls = ref([])
const isItemDragOver = ref(false)
const isThumbDragOver = ref(false)
const thumbPreviewUrl = ref('')
const itemFileInput = ref(null)
const thumbFileInput = ref(null)
const featuredListEl = ref(null)
const featuredSortable = ref(null)
const templateItemListEl = ref(null)
const templateItemSortable = ref(null)
let templateItemSortableSyncTimer = null
const savedTemplateItemOrderIds = ref([])
const userAvatarInputs = ref({})
const isTemplateLoading = ref(false)
const templateCreateModalOpen = ref(false)
const previousBodyOverflow = ref('')

function setFeaturedListRef(el) {
  featuredListEl.value = el
}

function setItemFileInputRef(el) {
  itemFileInput.value = el
}

function setThumbFileInputRef(el) {
  thumbFileInput.value = el
}

function scheduleTemplateItemSortableSync() {
  if (templateItemSortableSyncTimer) {
    clearTimeout(templateItemSortableSyncTimer)
    templateItemSortableSyncTimer = null
  }
  if (!templateItemListEl.value || !selectedTemplate.value?.items?.length) return

  templateItemSortableSyncTimer = setTimeout(() => {
    templateItemSortableSyncTimer = null
    syncTemplateItemSortable()
  }, 0)
}

function setTemplateItemListRef(el) {
  templateItemListEl.value = el
  if (!el) return
  scheduleTemplateItemSortableSync()
}

function normalizeAdminSrc(src) {
  if (typeof src !== 'string') return ''
  const raw = src.trim()
  if (!raw) return ''
  if (raw.startsWith('/uploads/')) return raw
  try {
    const url = new URL(raw)
    return url.pathname || raw
  } catch (e) {
    return raw
  }
}

function parseAdminTagsText(value) {
  const values = Array.isArray(value) ? value : String(value || '').split(',')
  return Array.from(
    new Set(
      values
        .map((tag) => tag.trim().replace(/^#/, '').slice(0, 40))
        .filter(Boolean)
    )
  ).slice(0, 30)
}

const hasSelectedTemplate = computed(() => !!selectedTemplate.value?.template?.id)
const canSaveTemplateMeta = computed(() => {
  const template = selectedTemplate.value?.template
  if (!template?.id) return false
  const nextName = templateMetaDraftName.value.trim()
  const nextSlug = templateMetaDraftSlug.value.trim()
  return (
    !!nextName &&
    !!nextSlug &&
    (nextName !== (template.name || '') ||
      nextSlug !== (template.slug || template.id || ''))
  )
})
const canApplyThumbnail = computed(() => !!thumbFile.value && !!selectedTemplateId.value)
const canBulkTagTemplateItems = computed(() => !!selectedTemplate.value?.items?.length)
const canAddItem = computed(() => uploadItemDrafts.value.length > 0 && uploadItemDrafts.value.every((item) => !!item.label.trim()) && !!selectedTemplateId.value)
const stagedRequestDraftCount = computed(() => uploadItemDrafts.value.filter((item) => item.kind === 'request').length)
const appliedRequestItemCount = computed(() => {
  if (!activeTemplateRequest.value?.id || !selectedTemplate.value?.items?.length) return 0
  const sourceRequest = templateRequests.value.find((request) => request.id === activeTemplateRequest.value.id)
  if (!sourceRequest?.items?.length) return 0
  const templateSrcs = new Set((selectedTemplate.value.items || []).map((item) => normalizeAdminSrc(item?.src)).filter(Boolean))
  return sourceRequest.items.filter((item) => templateSrcs.has(normalizeAdminSrc(item?.src))).length
})
const hasTemplateItemOrderChanges = computed(() => {
  const currentIds = (selectedTemplate.value?.items || []).map((item) => item.id)
  return currentIds.join('|') !== savedTemplateItemOrderIds.value.join('|')
})
const customItemPageCount = computed(() => Math.max(1, Math.ceil(customItemTotal.value / customItemLimit.value)))
const adminTierListPageCount = computed(() => Math.max(1, Math.ceil(adminTierListTotal.value / adminTierListLimit.value)))
const featuredTemplates = computed(() =>
  featuredTemplateIds.value
    .map((templateId) => templates.value.find((template) => template.id === templateId))
    .filter(Boolean)
)
const availableTemplatesForFeatured = computed(() => templates.value.filter((template) => !featuredTemplateIds.value.includes(template.id)))
const filteredTemplatePickerTemplates = computed(() => {
  const query = templatePickerQuery.value.trim().toLowerCase()
  const list = templates.value.filter((template) => {
    if (!query) return true
    const haystack = `${template.name || ''} ${template.slug || ''} ${template.id || ''}`.toLowerCase()
    return haystack.includes(query)
  })

  return list.slice().sort((a, b) => {
    if (templatePickerSort.value === 'oldest') return Number(a.createdAt || 0) - Number(b.createdAt || 0)
    return Number(b.createdAt || 0) - Number(a.createdAt || 0)
  })
})
const customItemTargetTemplate = computed(() => templates.value.find((template) => template.id === customItemModalTargetTemplateId.value) || null)
const customItemReplacementTarget = computed(
  () => customItemReplacementItems.value.find((item) => item.id === customItemReplacementTargetId.value) || null
)
const importModalItemCount = computed(() => importModalItems.value.length)
const selectedTemplateLibraryItems = computed(() =>
  templateLibraryItemResults.value.filter((item) => templateLibraryItemSelectedIds.value.includes(item.id))
)
const filteredTemplateSourceImportTemplates = computed(() => {
  const query = templateSourceImportQuery.value.trim().toLowerCase()
  return templates.value
    .filter((template) => template.id !== selectedTemplateId.value)
    .filter((template) => {
      if (!query) return true
      const haystack = `${template.name || ''} ${template.slug || ''} ${template.id || ''}`.toLowerCase()
      return haystack.includes(query)
    })
    .sort((a, b) => Number(b.createdAt || 0) - Number(a.createdAt || 0))
})
const activeTabTitle = computed(() => {
  if (activeTab.value === 'featured') return '목록 관리'
  if (activeTab.value === 'template-admin') return '템플릿 관리'
  if (activeTab.value === 'items') return '아이템 관리'
  if (activeTab.value === 'tierlists') {
    return tierlistsMode.value === 'requests' ? '템플릿 요청 관리' : '전체 티어표 관리'
  }
  return '회원 관리'
})
const activeTabDescription = computed(() => {
  if (activeTab.value === 'featured') {
    return '홈 화면 상단에 고정 노출되는 템플릿 순서를 따로 관리합니다.'
  }
  if (activeTab.value === 'template-admin') {
    return '템플릿 생성, 선택, 썸네일, 기본 아이템 관리를 전용 작업 화면으로 분리했습니다.'
  }
  if (activeTab.value === 'items') {
    return '사용자 아이템과 템플릿 아이템을 함께 검수하고, 필요한 템플릿에 직접 연결할 수 있어요.'
  }
  if (activeTab.value === 'tierlists') {
    return tierlistsMode.value === 'requests'
      ? '사용자 요청을 확인하고, 템플릿 관리 화면에서 필요한 아이템만 선별 반영한 뒤 직접 완료 처리합니다.'
      : '공개/비공개 포함 전체 티어표를 확인하고, 추가 아이템을 템플릿으로 가져올 수 있어요.'
  }
  return '계정 정보, 권한, 비밀번호와 최근 활동을 더 가볍게 확인하고 수정합니다.'
})
const adminOverviewStats = computed(() => {
  const pendingRequests = templateRequests.value.length
  const orphanItems = customItems.value.filter(
    (item) =>
      item.sourceType === 'user' &&
      Number(item.usageCount || 0) === 0 &&
      !(Array.isArray(item.linkedTemplates) && item.linkedTemplates.length > 0)
  ).length
  const adminCount = users.value.filter((user) => user.isAdmin).length

  if (activeTab.value === 'featured') {
    return [
      { label: '전체 템플릿', value: `${templates.value.length}` },
      { label: '상단 고정', value: `${featuredTemplateIds.value.length}/50` },
      { label: '추가 가능', value: `${Math.max(0, 50 - featuredTemplateIds.value.length)}` },
    ]
  }
  if (activeTab.value === 'template-admin') {
    return [
      { label: '전체 템플릿', value: `${templates.value.length}` },
      { label: '티어표 전체', value: `${selectedTemplateTierListStats.value.total || 0}` },
      { label: '공개', value: `${selectedTemplateTierListStats.value.publicCount || 0}` },
      { label: '비공개', value: `${selectedTemplateTierListStats.value.privateCount || 0}` },
      { label: '기본 아이템', value: `${selectedTemplate.value?.items?.length || 0}` },
    ]
  }
  if (activeTab.value === 'items') {
    return [
      { label: '검색 결과', value: `${customItemTotal.value}` },
      { label: '미사용 아이템', value: `${orphanItems}` },
      { label: '템플릿 아이템', value: `${customItems.value.filter((item) => item.sourceType === 'template').length}` },
      { label: '이미지 자산', value: `${customItems.value.filter((item) => item.sourceType === 'asset').length}` },
    ]
  }
  if (activeTab.value === 'tierlists') {
    return tierlistsMode.value === 'requests'
      ? [
          { label: '대기 요청', value: `${pendingRequests}` },
          { label: '확인함', value: `${templateRequests.value.filter((request) => request.status === 'reviewing').length}` },
          { label: '생성 요청', value: `${templateRequests.value.filter((request) => request.type === 'create').length}` },
          { label: '업데이트 요청', value: `${templateRequests.value.filter((request) => request.type === 'update').length}` },
        ]
      : [
          { label: '검색 결과', value: `${adminTierListStats.value.total || 0}` },
          { label: '추천', value: `${adminTierListStats.value.featuredCount || 0}` },
          { label: '공개', value: `${adminTierListStats.value.publicCount || 0}` },
          { label: '비공개', value: `${adminTierListStats.value.privateCount || 0}` },
          { label: '현재 페이지', value: `${adminTierListPage.value}/${adminTierListPageCount.value}` },
        ]
  }
  return [
    { label: '가입 회원', value: `${users.value.length}` },
    { label: '관리자', value: `${adminCount}` },
    { label: '활동 계정', value: `${users.value.filter((user) => user.tierListCount > 0).length}` },
  ]
})
const isAnyModalOpen = computed(
  () =>
    templateCreateModalOpen.value ||
    templatePickerModalOpen.value ||
    userEditModalOpen.value ||
    userPasswordModalOpen.value ||
    userDeleteModalOpen.value ||
    userRoleModalOpen.value ||
    importModalOpen.value ||
    templateSourceImportModalOpen.value ||
    templateLibraryItemModalOpen.value ||
    templateBulkTagModalOpen.value ||
    templatePickerModalOpen.value ||
    customItemModalOpen.value ||
    customItemDeleteModalOpen.value ||
    templateItemDeleteModalOpen.value ||
    adminTierListManageModalOpen.value ||
    imageResetModalOpen.value ||
    previewModalOpen.value
)
const adminRouteNameByTab = {
  featured: 'adminFeatured',
  'template-admin': 'adminTemplates',
  items: 'adminItems',
  tierlists: 'adminTierlists',
  users: 'adminUsers',
}

function tabFromAdminRoute(name) {
  if (name === 'adminTemplates') return 'template-admin'
  if (name === 'adminItems') return 'items'
  if (name === 'adminTierlists') return 'tierlists'
  if (name === 'adminUsers') return 'users'
  return 'featured'
}

function syncAdminRouteQuery(nextQuery = {}) {
  const normalizedEntries = Object.entries(nextQuery).filter(([, value]) => value != null && value !== '')
  const currentEntries = Object.entries(route.query || {}).filter(([, value]) => value != null && value !== '')
  const current = JSON.stringify(Object.fromEntries(currentEntries))
  const next = JSON.stringify(Object.fromEntries(normalizedEntries))
  if (current === next) return
  router.replace({ name: route.name, query: Object.fromEntries(normalizedEntries) })
}

function handleAdminPopState() {
  if (customItemDeleteModalOpen.value) {
    customItemDeleteModalOpen.value = false
    if (customItemModalOpen.value) pushCustomItemModalHistoryState()
    return
  }
  if (customItemModalOpen.value) {
    closeCustomItemModal({ fromPopState: true })
  }
}

function handleAdminKeydown(event) {
  if (event.key !== 'Escape') return
  if (previewModalOpen.value) {
    event.preventDefault()
    closePreviewModal()
    return
  }
  if (imageResetModalOpen.value) {
    event.preventDefault()
    closeImageResetModal()
    return
  }
  if (adminTierListManageModalOpen.value) {
    event.preventDefault()
    closeAdminTierListManageModal()
    return
  }
  if (templateItemDeleteModalOpen.value) {
    event.preventDefault()
    closeTemplateItemDeleteModal()
    return
  }
  if (customItemDeleteModalOpen.value) {
    event.preventDefault()
    closeCustomItemDeleteModal()
    return
  }
  if (customItemModalOpen.value) {
    event.preventDefault()
    closeCustomItemModal()
    return
  }
  if (templatePickerModalOpen.value) {
    event.preventDefault()
    closeTemplatePickerModal()
    return
  }
  if (templateBulkTagModalOpen.value) {
    event.preventDefault()
    closeTemplateBulkTagModal()
    return
  }
  if (templateLibraryItemModalOpen.value) {
    event.preventDefault()
    closeTemplateLibraryItemModal()
    return
  }
  if (templateSourceImportModalOpen.value) {
    event.preventDefault()
    closeTemplateSourceImportModal()
    return
  }
  if (importModalOpen.value) {
    event.preventDefault()
    closeTierListImportModal()
    return
  }
  if (userRoleModalOpen.value) {
    event.preventDefault()
    closeUserRoleModal()
    return
  }
  if (userDeleteModalOpen.value) {
    event.preventDefault()
    closeUserDeleteModal()
    return
  }
  if (userPasswordModalOpen.value) {
    event.preventDefault()
    closeUserPasswordModal()
    return
  }
  if (userEditModalOpen.value) {
    event.preventDefault()
    closeUserEditModal()
    return
  }
  if (templateCreateModalOpen.value) {
    event.preventDefault()
    closeTemplateCreateModal()
    return
  }
}

onMounted(async () => {
  if (typeof window !== 'undefined') {
    window.addEventListener('popstate', handleAdminPopState)
    window.addEventListener('keydown', handleAdminKeydown)
  }
  await auth.refresh()
  await Promise.all([refreshTemplates(), refreshCustomItems(), refreshAdminTierLists(), refreshUsers(), refreshTemplateRequests(), refreshImageDiagnostics()])
  await syncFeaturedSortable()
})

onUnmounted(() => {
  if (typeof window !== 'undefined') {
    window.removeEventListener('popstate', handleAdminPopState)
    window.removeEventListener('keydown', handleAdminKeydown)
  }
  if (typeof document !== 'undefined') document.body.style.overflow = previousBodyOverflow.value || ''
  clearPreviewUrl('item')
  clearPreviewUrl('thumb')
  if (templateItemSortableSyncTimer) {
    clearTimeout(templateItemSortableSyncTimer)
    templateItemSortableSyncTimer = null
  }
  destroyFeaturedSortable()
  destroyTemplateItemSortable()
})

function clearPreviewUrl(kind) {
  if (kind === 'item') {
    itemPreviewUrls.value.forEach((url) => {
      if (url) URL.revokeObjectURL(url)
    })
    itemPreviewUrls.value = []
    return
  }

  if (thumbPreviewUrl.value) {
    URL.revokeObjectURL(thumbPreviewUrl.value)
    thumbPreviewUrl.value = ''
  }
}

function resetFileInput(kind) {
  if (kind === 'item') {
    if (itemFileInput.value) itemFileInput.value.value = ''
    return
  }

  if (thumbFileInput.value) thumbFileInput.value.value = ''
}

watch(error, (message) => {
  if (!message) return
  toast.error(message)
  error.value = ''
})

watch(success, (message) => {
  if (!message) return
  toast.success(message)
  success.value = ''
})

watch(
  () => route.name,
  (name) => {
    activeTab.value = tabFromAdminRoute(name)
    if (name === 'adminTemplates') {
      const nextTopicId = typeof route.query.topicId === 'string' ? route.query.topicId : ''
      if (nextTopicId && nextTopicId !== selectedTemplateId.value) {
        selectedTemplateId.value = nextTopicId
        queueMicrotask(() => {
          if (selectedTemplateId.value === nextTopicId) void loadTemplate()
        })
      }
      return
    }
    if (name === 'adminTierlists') {
      const nextMode = route.query.mode === 'all' ? 'all' : 'requests'
      if (tierlistsMode.value !== nextMode) tierlistsMode.value = nextMode
      const nextTierListTopicId = typeof route.query.topicId === 'string' ? route.query.topicId : ''
      if (adminTierListTopicId.value !== nextTierListTopicId) adminTierListTopicId.value = nextTierListTopicId
    }
  },
  { immediate: true }
)

watch(
  () => selectedTemplateId.value,
  (templateId) => {
    if (route.name !== 'adminTemplates') return
    syncAdminRouteQuery({ topicId: templateId || undefined })
  }
)

watch(
  () => selectedTemplate.value?.template?.id || '',
  async (templateId) => {
    templateMetaDraftName.value = selectedTemplate.value?.template?.name || ''
    templateMetaDraftSlug.value = selectedTemplate.value?.template?.slug || selectedTemplate.value?.template?.id || ''
    await refreshSelectedTemplateTierListStats(templateId)
  },
  { immediate: true }
)

watch(
  () => tierlistsMode.value,
  (mode) => {
    if (route.name !== 'adminTierlists') return
    syncAdminRouteQuery({
      mode: mode === 'all' ? 'all' : undefined,
      topicId: mode === 'all' && adminTierListTopicId.value ? adminTierListTopicId.value : undefined,
    })
  }
)

watch(
  () => adminTierListTopicId.value,
  (topicId) => {
    if (route.name !== 'adminTierlists' || tierlistsMode.value !== 'all') return
    syncAdminRouteQuery({ topicId: topicId || undefined })
  }
)

watch(
  () => activeTab.value,
  async (tab) => {
    if (tab === 'template-admin' && selectedTemplateId.value && !selectedTemplate.value?.template?.id) {
      await loadTemplate()
      return
    }

    if (tab === 'items') {
      customItemQuery.value = ''
      customItemFilter.value = 'library'
      customItemPage.value = 1
      await refreshCustomItems()
      return
    }

    if (tab === 'tierlists') {
      if (tierlistsMode.value === 'requests') await refreshTemplateRequests()
      else await refreshAdminTierLists()
      return
    }

    if (tab === 'users') {
      await refreshUsers()
    }
  }
)

watch(
  () => tierlistsMode.value,
  async (mode) => {
    if (activeTab.value !== 'tierlists') return
    if (mode === 'requests') await refreshTemplateRequests()
    else await refreshAdminTierLists()
  }
)

watch(
  () => auth.user?.id,
  async (userId) => {
    if (!userId || !auth.user?.isAdmin) return
    await Promise.all([refreshTemplates(), refreshCustomItems(), refreshAdminTierLists(), refreshUsers(), refreshTemplateRequests(), refreshImageDiagnostics()])
  }
)

watch(
  () => [selectedTemplate.value?.template?.id || '', selectedTemplate.value?.items?.length || 0, !!templateItemListEl.value],
  ([templateId, itemCount, hasListEl]) => {
    if (!templateId || !itemCount || !hasListEl) return
    scheduleTemplateItemSortableSync()
  }
)


watch(
  () => isAnyModalOpen.value,
  (open) => {
    if (typeof document === 'undefined') return
    if (open) {
      if (!previousBodyOverflow.value) previousBodyOverflow.value = document.body.style.overflow || ''
      document.body.style.overflow = 'hidden'
      return
    }
    document.body.style.overflow = previousBodyOverflow.value || ''
    previousBodyOverflow.value = ''
  },
  { immediate: true }
)

function resetMessages() {
  error.value = ''
  success.value = ''
}

function formatBytes(value) {
  const size = Number(value || 0)
  if (!size) return '0 B'
  const units = ['B', 'KB', 'MB', 'GB']
  let current = size
  let unitIndex = 0
  while (current >= 1024 && unitIndex < units.length - 1) {
    current /= 1024
    unitIndex += 1
  }
  return `${current >= 10 || unitIndex === 0 ? current.toFixed(0) : current.toFixed(1)} ${units[unitIndex]}`
}

function formatImageJobSourceCategory(category) {
  switch (String(category || '').trim()) {
    case 'custom':
      return '커스텀 아이템'
    case 'tierlists':
      return '티어표 썸네일'
    case 'topics':
      return '주제/템플릿 이미지'
    case 'avatars':
      return '프로필 아바타'
    default:
      return '기타 이미지'
  }
}

function formatImageJobStatus(status) {
  switch (String(status || '').trim()) {
    case 'queued':
      return '대기'
    case 'processing':
      return '처리중'
    case 'completed':
      return '완료'
    case 'failed':
      return '실패'
    default:
      return status || '알 수 없음'
  }
}

function customItemDeleteImpactText(item) {
  if (!item) return ''
  if (item.sourceType === 'asset' || item.isAssetLibraryItem) {
    return `"${item.label}" ${item.sourceLabel || '이미지 자산'} 항목을 정리할까요? 라이브러리 항목만 제거되고, 같은 이미지를 쓰는 다른 참조는 그대로 유지됩니다.`
  }
  if (item.sourceType === 'template') {
    return `"${item.label}" 템플릿 항목을 정리할까요? 연결된 템플릿과 같은 주제의 저장된 티어표에서 이 항목이 함께 제거될 수 있어요.`
  }

  return `"${item.label}" 사용자 아이템을 삭제할까요? 현재 항목만 정리됩니다.`
}

const imageDiagnosticsCards = computed(() => {
  const stats = imageStats.value
  if (!stats) return []
  return [
    { label: '실사용 파일', value: `${stats.referencedCount || 0}` },
    { label: '현재 용량', value: formatBytes(stats.referencedByteSize) },
    { label: '추적 자산', value: `${stats.trackedReferencedCount || 0}` },
    { label: '레거시 참조', value: `${stats.legacyReferencedCount || 0}` },
    { label: '절감 용량', value: formatBytes(stats.savedByteSize) },
    { label: '절감률', value: `${Math.round((stats.savingsRatio || 0) * 100)}%` },
  ]
})
const visibleLinkedTemplates = computed(() =>
  (modalTargetCustomItem.value?.linkedTemplates || []).filter((template) => template?.id && template.id !== 'freeform')
)
const linkedCustomItemTemplateIds = computed(() => new Set(visibleLinkedTemplates.value.map((template) => template.id).filter(Boolean)))
const canReplaceModalTarget = computed(() => modalTargetCustomItem.value?.sourceType === 'user')
const replacementCandidateCount = computed(() => customItemReplacementItems.value.length)
const hasDeletableUnusedItems = computed(() =>
  customItems.value.some(
    (item) =>
      (item?.sourceType === 'user' &&
        (!!item.replacedAt ||
          (Number(item.usageCount || 0) === 0 && !(Array.isArray(item.linkedTemplates) && item.linkedTemplates.length > 0)))) ||
      item?.sourceType === 'asset' ||
      !!item?.isAssetLibraryItem
  )
)

const imageStatsPeriodLabel = computed(() => (imageStatsMonth.value ? `${imageStatsMonth.value} 기준` : '전체 기간'))
const imageStatsYearOptions = computed(() => {
  const currentYear = new Date().getFullYear()
  return Array.from({ length: 6 }, (_, index) => String(currentYear - index))
})
const imageStatsMonthOptions = [
  { value: '01', label: '1월' },
  { value: '02', label: '2월' },
  { value: '03', label: '3월' },
  { value: '04', label: '4월' },
  { value: '05', label: '5월' },
  { value: '06', label: '6월' },
  { value: '07', label: '7월' },
  { value: '08', label: '8월' },
  { value: '09', label: '9월' },
  { value: '10', label: '10월' },
  { value: '11', label: '11월' },
  { value: '12', label: '12월' },
]
const selectedImageStatsYear = computed({
  get: () => (imageStatsMonth.value ? imageStatsMonth.value.slice(0, 4) : ''),
  set: (year) => {
    if (!year) {
      imageStatsMonth.value = ''
      return
    }
    const month = imageStatsMonth.value ? imageStatsMonth.value.slice(5, 7) : '01'
    imageStatsMonth.value = `${year}-${month}`
  },
})
const selectedImageStatsMonthNumber = computed({
  get: () => (imageStatsMonth.value ? imageStatsMonth.value.slice(5, 7) : ''),
  set: (month) => {
    if (!month) {
      imageStatsMonth.value = ''
      return
    }
    const year = imageStatsMonth.value ? imageStatsMonth.value.slice(0, 4) : String(new Date().getFullYear())
    imageStatsMonth.value = `${year}-${month}`
  },
})
function clearImageStatsMonth() {
  imageStatsMonth.value = ''
}

async function refreshImageDiagnostics() {
  try {
    const data = await api.getAdminImageAssetStats({
      month: imageStatsMonth.value || '',
      limit: imageStatsLimit.value,
    })
    imageStats.value = data.stats || null
    imageQueue.value = data.queue || { concurrency: 1, activeCount: 0, pendingCount: 0 }
    imageRecentJobs.value = data.recentJobs || []
  } catch (e) {
    error.value = '이미지 최적화 현황을 불러오지 못했어요.'
  }
}

function openImageResetModal() {
  imageResetModalOpen.value = true
}

function closeImageResetModal() {
  imageResetModalOpen.value = false
}

async function confirmImageReset() {
  try {
    const data = await api.resetAdminImageAssetStats({ month: imageStatsMonth.value || null })
    success.value = imageStatsMonth.value
      ? `${imageStatsMonth.value} 기록 ${data.deletedCount || 0}건을 정리했어요.`
      : `전체 최적화 기록 ${data.deletedCount || 0}건을 정리했어요.`
    closeImageResetModal()
    await refreshImageDiagnostics()
  } catch (e) {
    error.value = '이미지 최적화 기록을 정리하지 못했어요.'
  }
}

async function cleanupMissingImageReferences() {
  const ok = window.confirm('파일이 실제로 없는 이미지 참조만 정리할까요? 누락된 썸네일은 비워지고, 누락된 템플릿/커스텀 아이템은 관련 참조와 함께 정리됩니다.')
  if (!ok) return

  try {
    imageMissingCleanupBusy.value = true
    const data = await api.cleanupAdminMissingImageReferences()
    await Promise.all([refreshImageDiagnostics(), refreshTemplates(), refreshCustomItems(), refreshTemplateRequests()])
    const result = data.result || {}
    success.value =
      `누락 참조를 정리했어요. ` +
      `아바타 ${result.clearedAvatars || 0}건, ` +
      `템플릿 썸네일 ${result.clearedTopicThumbnails || 0}건, ` +
      `티어표 썸네일 ${result.clearedTierListThumbnails || 0}건, ` +
      `요청 썸네일 ${result.clearedTemplateRequestThumbnails || 0}건, ` +
      `템플릿 아이템 ${result.deletedTopicItems || 0}건, ` +
      `커스텀 아이템 ${result.deletedCustomItems || 0}건`
  } catch (e) {
    error.value = '누락 이미지 참조 정리에 실패했어요.'
  } finally {
    imageMissingCleanupBusy.value = false
  }
}

function setTab(tab) {
  resetMessages()
  const nextRouteName = adminRouteNameByTab[tab]
  if (nextRouteName && route.name !== nextRouteName) {
    const nextQuery =
      tab === 'template-admin'
        ? { topicId: selectedTemplateId.value || undefined }
        : tab === 'tierlists' && tierlistsMode.value === 'all'
          ? { mode: 'all' }
          : {}
    router.push({ name: nextRouteName, query: nextQuery })
  }
  activeTab.value = tab
  if (tab === 'tierlists') {
    tierlistsMode.value = 'requests'
  }
  if (tab === 'items') {
    customItemQuery.value = ''
    customItemFilter.value = 'library'
    customItemPage.value = 1
    refreshCustomItems()
  }
}

function setTierlistsMode(mode) {
  resetMessages()
  tierlistsMode.value = mode
}

function openTemplateCreateModal() {
  resetMessages()
  if (activeTemplateRequest.value?.type === 'create' && !activeTemplateRequest.value?.targetTopicId) {
    newTemplateId.value = activeTemplateRequest.value?.draftTopicId || ''
    newTemplateName.value = activeTemplateRequest.value?.draftTopicName || ''
    newTemplateIsPublic.value = !!activeTemplateRequest.value?.draftTopicIsPublic
  } else {
    newTemplateId.value = ''
    newTemplateName.value = ''
    newTemplateIsPublic.value = false
  }
  templateCreateModalOpen.value = true
}

function closeTemplateCreateModal() {
  templateCreateModalOpen.value = false
}

async function handleSelectedTemplateChange(event) {
  selectedTemplateId.value = event?.target?.value || ''
  await loadTemplate()
}

async function selectAdminTemplate(templateId) {
  if (!templateId || selectedTemplateId.value === templateId) return
  selectedTemplateId.value = templateId
  await loadTemplate()
}

async function refreshTemplates() {
  try {
    const data = await api.listTopics()
    templates.value = data.topics || []
    featuredTemplateIds.value = templates.value
      .filter((template) => template.displayRank != null)
      .sort((a, b) => a.displayRank - b.displayRank)
      .map((template) => template.id)
    await syncFeaturedSortable()
  } catch (e) {
    error.value = '템플릿 목록을 불러오지 못했어요.'
  }
}

async function refreshCustomItems() {
  if (!auth.user?.isAdmin) return
  try {
    const data = await api.listAdminCustomItems({
      q: customItemQuery.value,
      page: customItemPage.value,
      limit: customItemLimit.value,
      filter: customItemFilter.value,
      collapseShared: !['user', 'template', 'unused-user', 'replaced-user'].includes(customItemFilter.value),
    })
    customItems.value = data.items || []
    customItemTotal.value = data.total || 0
    customItemPage.value = data.page || 1
    customItemLimit.value = data.limit || customItemLimit.value
  } catch (e) {
    error.value = '아이템 라이브러리를 불러오지 못했어요.'
  }
}

async function refreshAdminTierLists() {
  if (!auth.user?.isAdmin) return
  try {
    const data = await api.listAdminTierLists({
      q: adminTierListQuery.value,
      topicId: adminTierListTopicId.value,
      sort: adminTierListSort.value,
      minFavorites: adminTierListMinFavorites.value,
      page: adminTierListPage.value,
      limit: adminTierListLimit.value,
    })
    adminTierLists.value = data.tierLists || []
    adminTierListTotal.value = data.total || 0
    adminTierListPage.value = data.page || 1
    adminTierListLimit.value = data.limit || adminTierListLimit.value
    await refreshAdminTierListStats()
  } catch (e) {
    error.value = '관리자 티어표 목록을 불러오지 못했어요.'
  }
}

async function refreshAdminTierListStats() {
  if (!auth.user?.isAdmin) return
  try {
    const data = await api.getAdminTierListStats({
      q: adminTierListQuery.value,
      topicId: adminTierListTopicId.value,
      minFavorites: adminTierListMinFavorites.value,
    })
    adminTierListStats.value = {
      total: data.total || 0,
      publicCount: data.publicCount || 0,
      privateCount: data.privateCount || 0,
      featuredCount: data.featuredCount || 0,
    }
  } catch (e) {
    adminTierListStats.value = { total: 0, publicCount: 0, privateCount: 0, featuredCount: 0 }
  }
}

async function refreshSelectedTemplateTierListStats(templateId = '') {
  if (!auth.user?.isAdmin || !templateId) {
    selectedTemplateTierListStats.value = { total: 0, publicCount: 0, privateCount: 0 }
    return
  }

  try {
    const data = await api.getAdminTierListStats({ topicId: templateId })
    selectedTemplateTierListStats.value = {
      total: data.total || 0,
      publicCount: data.publicCount || 0,
      privateCount: data.privateCount || 0,
    }
  } catch (e) {
    selectedTemplateTierListStats.value = { total: 0, publicCount: 0, privateCount: 0 }
  }
}

async function refreshTemplateRequests() {
  if (!auth.user?.isAdmin) return
  try {
    const data = await api.listAdminTemplateRequests()
    templateRequests.value = (data.requests || []).map((request) => ({
      ...request,
      draftTopicId:
        request.type === 'create'
          ? ('tmpl-' + String(request.id || 'request').replace(/[^a-zA-Z0-9]+/g, '').slice(0, 12).toLowerCase())
          : request.targetTopicSlug || request.sourceTopicSlug || request.targetTopicId || request.sourceTopicId || '',
      draftTopicName:
        request.type === 'create'
          ? `${request.sourceTierListTitle || request.sourceTopicName || '새 템플릿'}`
          : request.targetTopicName || request.sourceTopicName || '',
      draftTopicIsPublic: false,
    }))
  } catch (e) {
    error.value = '템플릿 요청 목록을 불러오지 못했어요.'
  }
}

async function refreshUsers() {
  if (!auth.user?.isAdmin) return
  try {
    const data = await api.listAdminUsers({ q: userQuery.value, sort: userSort.value, direction: userSortDirection.value })
    users.value = (data.users || []).map((user) => ({
      ...user,
      isAvatarBusy: false,
    }))
  } catch (e) {
    error.value = '회원 목록을 불러오지 못했어요.'
  }
}

function resetUploadState() {
  uploadFiles.value = []
  uploadItemDrafts.value = []
  thumbFile.value = null
  resetFileInput('item')
  resetFileInput('thumb')
  clearPreviewUrl('item')
  clearPreviewUrl('thumb')
}

const {
  destroyFeaturedSortable,
  syncFeaturedSortable,
  addFeaturedTemplate,
  removeFeaturedTemplate,
  moveFeaturedTemplate,
  saveFeaturedOrder,
} = useAdminFeaturedTemplates({
  api,
  featuredListEl,
  featuredSortable,
  featuredTemplateIds,
  templates,
  resetMessages,
  success,
  error,
})

const {
  destroyTemplateItemSortable,
  syncTemplateItemSortable,
  mergeRequestItemsIntoDrafts,
  mergeLibraryItemsIntoDrafts,
  removeUploadDraft,
  loadTemplate,
  createTemplate,
  handleItemFiles,
  onFile,
  openItemFilePicker,
  clearItemFiles,
  uploadItem,
  saveTemplateItemOrder,
} = useAdminTemplateManager({
  api,
  toApiUrl,
  selectedTemplateId,
  selectedTemplate,
  uploadFiles,
  uploadItemDrafts,
  thumbFile,
  itemPreviewUrls,
  itemFileInput,
  templateItemListEl,
  templateItemSortable,
  savedTemplateItemOrderIds,
  isTemplateLoading,
  activeTemplateRequest,
  templateRequests,
  customItemModalOpen,
  customItemModalTargetTemplateId,
  newTemplateId,
  newTemplateName,
  newTemplateIsPublic,
  clearPreviewUrl,
  resetFileInput,
  resetUploadState,
  refreshTemplates,
  closeTemplateCreateModal,
  resetMessages,
  success,
  error,
})

const {
  templateRequestStatusLabel,
  templateRequestSourceUrl,
  templateRequestReviewHint,
  startTemplateRequestReview,
  completeTemplateRequest,
} = useAdminTemplateRequests({
  api,
  activeTemplateRequest,
  refreshTemplateRequests,
  setTab,
  openTemplateCreateModal,
  newTemplateId,
  newTemplateName,
  selectAdminTemplate,
  mergeRequestItemsIntoDrafts,
  resetMessages,
  success,
  error,
})

const {
  submitCustomItemSearch,
  changeCustomItemFilter,
  changeCustomItemLimit,
  moveCustomItemPage,
  openCustomItemModal,
  closeCustomItemModal,
  openCustomItemDeleteModal,
  closeCustomItemDeleteModal,
  removeCustomItem,
  removeUnusedCustomItems,
  showUnusedCustomItems,
  saveCustomItemModalLabel,
  promoteCustomItem,
  unlinkCustomItemTemplate,
  refreshReplacementCandidates,
  replaceCustomItem,
  restoreCustomItem,
} = useAdminCustomItems({
  api,
  toast,
  customItems,
  customItemPage,
  customItemLimit,
  customItemPageCount,
  customItemQuery,
  customItemFilter,
  customItemModalOpen,
  customItemDeleteModalOpen,
  customItemModalHistoryActive,
  modalTargetCustomItem,
  customItemModalDraftLabel,
  customItemModalDraftTags,
  customItemModalLabelSaving,
  customItemModalTargetTemplateId,
  customItemReplacementQuery,
  customItemReplacementItems,
  customItemReplacementLoading,
  customItemReplacementTargetId,
  customItemReplacementBusy,
  templates,
  selectedTemplateId,
  refreshCustomItems,
  loadTemplate,
  resetMessages,
  success,
  error,
})

const {
  setUserAvatarInput,
  canManageModalRole,
  canEditUserAvatar,
  canEditUserInfo,
  canResetUserPassword,
  canDeleteUser,
  isUserEditDirty,
  roleLabelOf,
  openUserAvatarPicker,
  onUserAvatarChange,
  removeUserAvatar,
  openUserEditModal,
  closeUserEditModal,
  saveUserEdit,
  openUserPasswordModal,
  closeUserPasswordModal,
  confirmUserPasswordReset,
  openUserDeleteModal,
  closeUserDeleteModal,
  confirmUserDelete,
  openUserRoleModal,
  closeUserRoleModal,
  confirmUserRoleDraft,
  submitUserFilters,
  userDisplayName,
} = useAdminUsers({
  api,
  auth,
  users,
  userQuery,
  userSort,
  userSortDirection,
  userAvatarInputs,
  modalTargetUser,
  modalPasswordDraft,
  modalRoleNextAdmin,
  modalUserDraftEmail,
  modalUserDraftNickname,
  modalUserDraftIsAdmin,
  userEditModalOpen,
  userPasswordModalOpen,
  userDeleteModalOpen,
  userRoleModalOpen,
  resetMessages,
  refreshUsers,
  success,
  error,
})


function handleThumbFile(file) {
  const nextFile = file && (file.type || '').startsWith('image/') ? file : null
  thumbFile.value = nextFile
  clearPreviewUrl('thumb')
  if (thumbFile.value) thumbPreviewUrl.value = URL.createObjectURL(thumbFile.value)
}

function onThumb(event) {
  handleThumbFile(event.target.files && event.target.files[0] ? event.target.files[0] : null)
}

function openThumbFilePicker() {
  thumbFileInput.value?.click()
}

function onThumbDragEnter(event) {
  event.preventDefault()
  isThumbDragOver.value = true
}

function onThumbDragOver(event) {
  event.preventDefault()
  isThumbDragOver.value = true
}

function onThumbDragLeave(event) {
  if (event.currentTarget === event.target) isThumbDragOver.value = false
}

function onThumbDrop(event) {
  event.preventDefault()
  isThumbDragOver.value = false
  handleThumbFile(event.dataTransfer?.files?.[0] || null)
}

function onItemDragEnter(event) {
  event.preventDefault()
  isItemDragOver.value = true
}

function onItemDragOver(event) {
  event.preventDefault()
  isItemDragOver.value = true
}

function onItemDragLeave(event) {
  if (event.currentTarget === event.target) isItemDragOver.value = false
}

function onItemDrop(event) {
  event.preventDefault()
  isItemDragOver.value = false
  handleItemFiles(event.dataTransfer?.files)
}

async function uploadThumbnail() {
  resetMessages()
  if (!thumbFile.value || !selectedTemplateId.value) {
    error.value = '썸네일 파일을 선택해주세요.'
    return
  }

  try {
    const fd = new FormData()
    fd.append('thumbnail', thumbFile.value)
    const res = await fetch(toApiUrl(`/api/admin/templates/${encodeURIComponent(selectedTemplateId.value)}/thumbnail`), {
      method: 'POST',
      credentials: 'include',
      body: fd,
    })
    if (!res.ok) throw new Error('failed')

    thumbFile.value = null
    resetFileInput('thumb')
    clearPreviewUrl('thumb')
    await refreshTemplates()
    await loadTemplate()
    success.value = '썸네일이 반영됐어요.'
  } catch (e) {
    error.value = '썸네일 업로드 실패(관리자 권한/파일 크기 확인)'
  }
}

async function saveTemplateVisibility() {
  if (!selectedTemplate.value?.template?.id) return
  try {
    templateVisibilitySaving.value = true
    const data = await api.updateAdminTemplate(selectedTemplate.value.template.id, {
      isPublic: !!selectedTemplate.value.template.isPublic,
      tags: Array.isArray(selectedTemplate.value.template.tags) ? selectedTemplate.value.template.tags : [],
    })
    const nextTemplate = data.template || {}
    selectedTemplate.value = {
      ...selectedTemplate.value,
      template: {
        ...selectedTemplate.value.template,
        ...nextTemplate,
      },
    }
    await refreshTemplates()
    success.value = nextTemplate?.isPublic ? '템플릿을 공개 상태로 전환했어요.' : '템플릿을 비공개 상태로 전환했어요.'
    return true
  } catch (e) {
    error.value = '템플릿 공개 상태를 저장하지 못했어요.'
    return false
  } finally {
    templateVisibilitySaving.value = false
  }
}

async function saveTemplateMeta() {
  if (!selectedTemplate.value?.template?.id || templateMetaSaving.value || !canSaveTemplateMeta.value) return

  try {
    templateMetaSaving.value = true
    const data = await api.updateAdminTemplate(selectedTemplate.value.template.id, {
      name: templateMetaDraftName.value.trim(),
      slug: templateMetaDraftSlug.value.trim().toLowerCase(),
      isPublic: !!selectedTemplate.value.template.isPublic,
    })
    const nextTemplate = data.template || {}
    selectedTemplate.value = {
      ...selectedTemplate.value,
      template: {
        ...selectedTemplate.value.template,
        ...nextTemplate,
      },
    }
    templateMetaDraftName.value = nextTemplate.name || selectedTemplate.value.template.name || ''
    templateMetaDraftSlug.value = nextTemplate.slug || selectedTemplate.value.template.slug || selectedTemplate.value.template.id || ''
    await refreshTemplates()
    success.value = '템플릿 이름과 주소를 저장했어요.'
  } catch (e) {
    const errorCode = e?.data?.error || ''
    if (errorCode === 'topic_slug_taken') {
      error.value = '이미 사용 중인 템플릿 slug입니다.'
      return
    }
    if (errorCode === 'topic_slug_invalid') {
      error.value = 'slug는 영문 소문자, 숫자, 하이픈만 사용할 수 있어요.'
      return
    }
    error.value = '템플릿 이름과 주소를 저장하지 못했어요.'
  } finally {
    templateMetaSaving.value = false
  }
}

function openTemplateBulkTagModal() {
  resetMessages()
  if (!selectedTemplate.value?.items?.length) {
    error.value = '태그를 추가할 기본 아이템이 없어요.'
    return
  }
  templateBulkTagDrafts.value = []
  templateBulkTagSaving.value = false
  templateBulkTagModalOpen.value = true
}

function closeTemplateBulkTagModal() {
  templateBulkTagModalOpen.value = false
  templateBulkTagDrafts.value = []
  templateBulkTagSaving.value = false
}

async function applyTemplateBulkTags() {
  resetMessages()
  if (!selectedTemplateId.value || !selectedTemplate.value?.items?.length) {
    error.value = '태그를 적용할 템플릿을 먼저 선택해주세요.'
    return
  }

  const nextTags = parseAdminTagsText(templateBulkTagDrafts.value)
  if (!nextTags.length) {
    error.value = '추가할 태그를 하나 이상 입력해주세요.'
    return
  }

  try {
    templateBulkTagSaving.value = true
    const items = selectedTemplate.value.items || []
    let updatedCount = 0

    for (const item of items) {
      const mergedTags = Array.from(new Set([...(Array.isArray(item.tags) ? item.tags : []), ...nextTags]))
      if (JSON.stringify(mergedTags) === JSON.stringify(Array.isArray(item.tags) ? item.tags : [])) continue
      const data = await api.updateAdminTemplateItem(selectedTemplateId.value, item.id, {
        label: item.label,
        tags: mergedTags,
      })
      item.tags = Array.isArray(data.item?.tags) ? data.item.tags : mergedTags
      updatedCount += 1
    }

    closeTemplateBulkTagModal()
    success.value = updatedCount
      ? `기본 아이템 ${updatedCount}개에 공통 태그를 추가했어요.`
      : '이미 같은 태그가 들어 있어서 바뀐 항목이 없었어요.'
  } catch (e) {
    error.value = '기본 아이템 공통 태그 추가에 실패했어요.'
  } finally {
    templateBulkTagSaving.value = false
  }
}

async function toggleSelectedTemplateVisibility(nextValue) {
  if (!selectedTemplate.value?.template?.id || templateVisibilitySaving.value) return
  const previous = !!selectedTemplate.value.template.isPublic
  selectedTemplate.value = {
    ...selectedTemplate.value,
    template: {
      ...selectedTemplate.value.template,
      isPublic: !!nextValue,
    },
  }
  const saved = await saveTemplateVisibility()
  if (!saved) {
    selectedTemplate.value = {
      ...selectedTemplate.value,
      template: {
        ...selectedTemplate.value.template,
        isPublic: previous,
      },
    }
  }
}

function closeTemplateItemDeleteModal() {
  templateItemDeleteModalOpen.value = false
  modalTargetTemplateItem.value = null
  modalTargetTemplateItemUsage.value = { totalCount: 0, publicCount: 0, privateCount: 0 }
}

function templateItemDeleteImpactText() {
  const usage = modalTargetTemplateItemUsage.value || { totalCount: 0, publicCount: 0, privateCount: 0 }
  if (usage.totalCount) {
    return `이 아이템은 이미 저장된 티어표 ${usage.totalCount}개(공개 ${usage.publicCount}개, 비공개 ${usage.privateCount}개)에서 사용 중이에요. 기존 저장 티어표에는 영향을 주지 않고, 이후 새로 만드는 티어표에서만 제외됩니다.`
  }
  return '이 기본 아이템을 삭제할까요? 기존 저장 티어표에는 영향을 주지 않고, 이후 새로 만드는 티어표에서만 제외됩니다.'
}

async function removeTemplateItem(item) {
  resetMessages()
  if (!selectedTemplateId.value || !item?.id) return
  try {
    const usageRes = await fetch(
      toApiUrl(`/api/admin/templates/${encodeURIComponent(selectedTemplateId.value)}/items/${encodeURIComponent(item.id)}/usage`),
      {
        credentials: 'include',
      }
    )
    if (!usageRes.ok) throw new Error('usage_failed')

    const usageData = await usageRes.json()
    modalTargetTemplateItem.value = item
    modalTargetTemplateItemUsage.value = usageData?.usage || { totalCount: 0, publicCount: 0, privateCount: 0 }
    templateItemDeleteModalOpen.value = true
  } catch (e) {
    error.value = '템플릿 기본 아이템 삭제에 실패했어요.'
  }
}

async function confirmTemplateItemDelete() {
  resetMessages()
  if (!selectedTemplateId.value || !modalTargetTemplateItem.value?.id) return

  try {
    const previousScrollY = window.scrollY
    const res = await fetch(
      toApiUrl(`/api/admin/templates/${encodeURIComponent(selectedTemplateId.value)}/items/${encodeURIComponent(modalTargetTemplateItem.value.id)}`),
      {
        method: 'DELETE',
        credentials: 'include',
      }
    )
    if (!res.ok) throw new Error('failed')

    closeTemplateItemDeleteModal()
    await loadTemplate()
    await nextTick()
    window.scrollTo({ top: previousScrollY, behavior: 'auto' })
    success.value = '템플릿 기본 아이템을 삭제했어요.'
  } catch (e) {
    error.value = '템플릿 기본 아이템 삭제에 실패했어요.'
  }
}

async function saveTemplateItemLabel(item) {
  resetMessages()
  if (!selectedTemplateId.value) return
  const nextLabel = (item.draftLabel || '').trim()
  if (!nextLabel) {
    error.value = '아이템 이름을 입력해주세요.'
    return
  }
  if (nextLabel === item.label) return

  try {
    item.isSavingLabel = true
    const data = await api.updateAdminTemplateItem(selectedTemplateId.value, item.id, {
      label: nextLabel,
      tags: Array.isArray(item.tags) ? item.tags : [],
    })
    item.label = data.item.label
    item.draftLabel = data.item.label
    item.tags = Array.isArray(data.item.tags) ? data.item.tags : []
    success.value = '기본 아이템 이름을 수정했어요.'
  } catch (e) {
    error.value = '기본 아이템 이름 수정에 실패했어요.'
  } finally {
    item.isSavingLabel = false
  }
}

async function removeTemplate() {
  resetMessages()
  if (!selectedTemplateId.value || !selectedTemplate.value?.template) return

  const ok = window.confirm(`"${selectedTemplate.value.template.name}" 템플릿을 삭제할까요? 관련 기본 아이템과 티어표도 함께 삭제됩니다.`)
  if (!ok) return

  try {
    const res = await fetch(toApiUrl(`/api/admin/templates/${encodeURIComponent(selectedTemplateId.value)}`), {
      method: 'DELETE',
      credentials: 'include',
    })
    if (!res.ok) throw new Error('failed')

    const deletedName = selectedTemplate.value.template.name
    selectedTemplateId.value = ''
    selectedTemplate.value = null
    resetUploadState()
    await refreshTemplates()
    success.value = `${deletedName} 템플릿을 삭제했어요.`
  } catch (e) {
    error.value = '템플릿 삭제에 실패했어요.'
  }
}

function submitAdminTierListSearch() {
  adminTierListPage.value = 1
  refreshAdminTierLists()
}

function changeAdminTierListSort() {
  adminTierListPage.value = 1
  refreshAdminTierLists()
}

function changeAdminTierListMinFavorites() {
  adminTierListMinFavorites.value = Math.max(Number(adminTierListMinFavorites.value) || 0, 0)
  adminTierListPage.value = 1
  refreshAdminTierLists()
}

function setAdminTierListTopicId(topicId) {
  adminTierListTopicId.value = topicId || ''
  adminTierListPage.value = 1
  refreshAdminTierLists()
}

function openTemplatePickerModal(mode = 'template-admin') {
  templatePickerMode.value = mode
  templatePickerQuery.value = ''
  templatePickerSort.value = 'recent'
  templatePickerModalOpen.value = true
}

function closeTemplatePickerModal() {
  templatePickerModalOpen.value = false
  templatePickerQuery.value = ''
}

async function chooseTemplateFromPicker(templateId) {
  if (!templateId) return
  if (templatePickerMode.value === 'tierlists-filter') {
    setAdminTierListTopicId(templateId)
    closeTemplatePickerModal()
    return
  }
  if (templatePickerMode.value === 'custom-item-target') {
    if (linkedCustomItemTemplateIds.value.has(templateId)) return
    customItemModalTargetTemplateId.value = templateId
    closeTemplatePickerModal()
    return
  }

  await selectAdminTemplate(templateId)
  closeTemplatePickerModal()
}

function changeAdminTierListLimit(limit) {
  adminTierListLimit.value = limit
  adminTierListPage.value = 1
  refreshAdminTierLists()
}

function moveAdminTierListPage(direction) {
  const nextPage = adminTierListPage.value + direction
  if (nextPage < 1 || nextPage > adminTierListPageCount.value) return
  adminTierListPage.value = nextPage
  refreshAdminTierLists()
}


function buildModalItemFromTierListItem(item, tierList) {
  const matchedItem = customItems.value.find((entry) => entry.id === item?.id || entry.src === item?.src)
  const id = matchedItem?.id || item?.id || ''
  return {
    ...matchedItem,
    ...item,
    id,
    label: item?.label || matchedItem?.label || '이름 없음',
    src: item?.src || matchedItem?.src || '',
    sourceType: matchedItem?.sourceType || (String(id).startsWith('asset:') ? 'asset' : 'user'),
    sourceLabel: matchedItem?.sourceLabel || '티어표 추가 아이템',
    ownerName: matchedItem?.ownerName || tierListAuthorDisplayName(tierList),
    tags: Array.isArray(matchedItem?.tags) ? matchedItem.tags : [],
    linkedTemplates: Array.isArray(matchedItem?.linkedTemplates) ? matchedItem.linkedTemplates : [],
    usageCount: matchedItem?.usageCount || 0,
    canDelete: typeof matchedItem?.canDelete === 'boolean' ? matchedItem.canDelete : false,
    isPromoting: false,
    createdAt: matchedItem?.createdAt || item?.createdAt || tierList?.updatedAt || tierList?.createdAt || Date.now(),
  }
}

function openTierListExtraItemModal(item, tierList) {
  if (!item) return
  openCustomItemModal(buildModalItemFromTierListItem(item, tierList))
}

function tierListThumbUrl(tierList) {
  return tierList.thumbnailSrc ? toApiUrl(tierList.thumbnailSrc) : ''
}

function tierListAuthorDisplayName(tierList) {
  return tierList.authorName || '알 수 없음'
}

function tierListVisibilityLabel(tierList) {
  return tierList.isPublic ? '공개' : '비공개'
}

function openAdminTierListManageModal(tierList) {
  if (!tierList) return
  modalTargetAdminTierList.value = tierList
  adminTierListDraftTitle.value = tierList.title || ''
  adminTierListDraftDescription.value = tierList.description || ''
  adminTierListDraftIsPublic.value = !!tierList.isPublic
  adminTierListManageModalOpen.value = true
}

function closeAdminTierListManageModal() {
  adminTierListManageModalOpen.value = false
  modalTargetAdminTierList.value = null
  adminTierListDraftTitle.value = ''
  adminTierListDraftDescription.value = ''
  adminTierListDraftIsPublic.value = false
  adminTierListSaving.value = false
  adminTierListDeleting.value = false
}

async function saveAdminTierListMeta() {
  if (!modalTargetAdminTierList.value?.id || adminTierListSaving.value) return
  const nextTitle = adminTierListDraftTitle.value.trim()
  if (!nextTitle) {
    error.value = '티어표 제목을 입력해주세요.'
    return
  }

  resetMessages()
  adminTierListSaving.value = true
  try {
    const data = await api.updateAdminTierList(modalTargetAdminTierList.value.id, {
      title: nextTitle,
      description: adminTierListDraftDescription.value.trim(),
      isPublic: !!adminTierListDraftIsPublic.value,
    })
    const updated = data.tierList
    adminTierLists.value = adminTierLists.value.map((tierList) => (tierList.id === updated.id ? { ...tierList, ...updated } : tierList))
    if (previewTierList.value?.id === updated.id) previewTierList.value = { ...previewTierList.value, ...updated }
    modalTargetAdminTierList.value = updated
    await Promise.all([refreshAdminTierListStats(), refreshSelectedTemplateTierListStats(selectedTemplate.value?.template?.id || '')])
    success.value = '티어표 정보를 수정했어요.'
    closeAdminTierListManageModal()
  } catch (e) {
    error.value = '티어표 정보 수정에 실패했어요.'
  } finally {
    adminTierListSaving.value = false
  }
}

async function deleteAdminTierListEntry() {
  if (!modalTargetAdminTierList.value?.id || adminTierListDeleting.value) return
  const ok = window.confirm(`"${modalTargetAdminTierList.value.title}" 티어표를 삭제할까요? 이 작업은 되돌릴 수 없어요.`)
  if (!ok) return

  resetMessages()
  adminTierListDeleting.value = true
  try {
    await api.deleteAdminTierList(modalTargetAdminTierList.value.id)
    adminTierLists.value = adminTierLists.value.filter((tierList) => tierList.id !== modalTargetAdminTierList.value.id)
    adminTierListTotal.value = Math.max(0, adminTierListTotal.value - 1)
    if (previewTierList.value?.id === modalTargetAdminTierList.value.id) previewTierList.value = null
    await Promise.all([refreshAdminTierListStats(), refreshSelectedTemplateTierListStats(selectedTemplate.value?.template?.id || '')])
    success.value = '티어표를 삭제했어요.'
    closeAdminTierListManageModal()
    if (!adminTierLists.value.length && adminTierListPage.value > 1) {
      adminTierListPage.value -= 1
      await refreshAdminTierLists()
    }
  } catch (e) {
    error.value = '티어표 삭제에 실패했어요.'
  } finally {
    adminTierListDeleting.value = false
  }
}

async function toggleAdminTierListFeatured(tierList) {
  if (!tierList?.id) return
  const nextFeatured = !tierList.isFeatured
  resetMessages()

  try {
    const data = await api.updateAdminTierListFeatured(tierList.id, { isFeatured: nextFeatured })
    const updated = data.tierList || {}
    adminTierLists.value = adminTierLists.value.map((entry) => (entry.id === tierList.id ? { ...entry, ...updated } : entry))
    if (previewTierList.value?.id === tierList.id) previewTierList.value = { ...previewTierList.value, ...updated }
    if (modalTargetAdminTierList.value?.id === tierList.id) {
      modalTargetAdminTierList.value = { ...modalTargetAdminTierList.value, ...updated }
    }
    await refreshAdminTierListStats()
    success.value = nextFeatured ? '추천 티어표로 지정했어요.' : '추천 지정을 해제했어요.'
  } catch (e) {
    error.value =
      e?.data?.error === 'public_tierlist_required' ? '공개 티어표만 추천으로 지정할 수 있어요.' : '추천 상태 변경에 실패했어요.'
  }
}

function openAdminTierList(tierList) {
  previewTierList.value = tierList
  previewModalOpen.value = true
}

function previewRequestItemsById(preview) {
  const items = Array.isArray(preview?.snapshotItems) ? preview.snapshotItems : []
  return items.reduce((acc, item) => {
    if (item?.id) acc[item.id] = item
    return acc
  }, {})
}

function previewRequestGroupItems(preview, group) {
  const itemsById = previewRequestItemsById(preview)
  return (group?.itemIds || []).map((itemId) => itemsById[itemId]).filter(Boolean)
}


function previewRequestColumns(preview) {
  const groups = Array.isArray(preview?.snapshotGroups) ? preview.snapshotGroups : []
  const columnSource = groups.find((group) => Array.isArray(group?.columnNames) && group.columnNames.length) || null
  const namedColumns = Array.isArray(columnSource?.columnNames) ? columnSource.columnNames : []
  const cellCount = Math.max(1, namedColumns.length, ...groups.map((group) => (Array.isArray(group?.cells) ? group.cells.length : 0)))

  return Array.from({ length: cellCount }, (_, index) => ({
    id: namedColumns[index]?.id || ('column-' + index),
    name: namedColumns[index]?.name || '',
  }))
}

function previewRequestHasColumns(preview) {
  const columns = previewRequestColumns(preview)
  return columns.length > 1 || columns.some((column) => column.name)
}

function previewRequestGridStyle(preview) {
  const count = previewRequestColumns(preview).length
  return { gridTemplateColumns: 'repeat(' + count + ', minmax(0, 1fr))' }
}

function previewRequestGroupCellItems(preview, group, columnIndex) {
  const itemsById = previewRequestItemsById(preview)
  if (Array.isArray(group?.cells?.[columnIndex])) {
    return group.cells[columnIndex].map((itemId) => itemsById[itemId]).filter(Boolean)
  }
  if (columnIndex === 0) return previewRequestGroupItems(preview, group)
  return []
}

function previewRequestPoolItems(preview) {
  const groupedIds = new Set(
    (preview?.snapshotGroups || []).flatMap((group) => {
      if (Array.isArray(group?.cells) && group.cells.length) {
        return group.cells.flatMap((cell) => (Array.isArray(cell) ? cell : []))
      }
      return group.itemIds || []
    })
  )
  return (preview?.snapshotItems || []).filter((item) => !groupedIds.has(item.id))
}

function openTemplateRequestPreview(request) {
  const snapshotItems = Array.isArray(request.snapshotItems) && request.snapshotItems.length ? request.snapshotItems : Array.isArray(request.items) ? request.items : []
  previewTierList.value = {
    id: request.id,
    title: request.sourceTierListTitle || '템플릿 요청 미리보기',
    description: request.sourceDescription || '',
    thumbnailSrc: request.thumbnailSrc || '',
    requestPreview: true,
    snapshotGroups: request.snapshotGroups || [],
    snapshotItems,
    snapshotShowCharacterNames: !!request.snapshotShowCharacterNames,
  }
  previewModalOpen.value = true
}

function closePreviewModal() {
  previewModalOpen.value = false
  previewTierList.value = null
}

function previewTierListUrl(tierList) {
  const topicRef = tierList?.topicSlug || tierList?.topicId || ''
  if (!topicRef || !tierList?.id) return ''
  return editorPath(topicRef, tierList.id, { preview: true })
}

function openTierListImportModal(tierList, items) {
  resetMessages()
  const nextItems = (items || []).filter(Boolean)
  if (!nextItems.length) {
    error.value = '가져올 아이템이 없어요.'
    return
  }

  importModalTierList.value = tierList
  importModalItems.value = nextItems
  importModalMode.value = 'existing'
  importModalTargetTemplateId.value = ''
  const baseSlug = tierList.topicSlug || tierList.topicId || ''
  importModalNewTemplateId.value = baseSlug === 'freeform' ? '' : `${baseSlug}-copy`
  importModalNewTemplateName.value =
    baseSlug === 'freeform' ? `${tierList.title} 템플릿` : `${tierList.topicName || baseSlug} 파생 템플릿`
  importModalOpen.value = true
}

function closeTierListImportModal() {
  importModalOpen.value = false
  importModalTierList.value = null
  importModalItems.value = []
}

function openTemplateSourceImportModal() {
  resetMessages()
  if (!selectedTemplateId.value) {
    error.value = '먼저 가져올 대상을 받을 템플릿을 선택해주세요.'
    return
  }
  templateSourceImportSelectedIds.value = []
  templateSourceImportQuery.value = ''
  templateSourceImportModalOpen.value = true
}

function closeTemplateSourceImportModal() {
  templateSourceImportModalOpen.value = false
  templateSourceImportSelectedIds.value = []
  templateSourceImportQuery.value = ''
}

function openTemplateLibraryItemModal() {
  resetMessages()
  if (!selectedTemplateId.value) {
    error.value = '먼저 아이템을 받을 템플릿을 선택해주세요.'
    return
  }
  templateLibraryItemQuery.value = ''
  templateLibraryItemResults.value = []
  templateLibraryItemSelectedIds.value = []
  templateLibraryItemLoading.value = false
  templateLibraryItemModalOpen.value = true
}

function closeTemplateLibraryItemModal() {
  templateLibraryItemModalOpen.value = false
  templateLibraryItemQuery.value = ''
  templateLibraryItemResults.value = []
  templateLibraryItemSelectedIds.value = []
  templateLibraryItemLoading.value = false
}

function toggleTemplateLibraryItemSelection(itemId) {
  if (!itemId) return
  if (templateLibraryItemSelectedIds.value.includes(itemId)) {
    templateLibraryItemSelectedIds.value = templateLibraryItemSelectedIds.value.filter((id) => id !== itemId)
    return
  }
  templateLibraryItemSelectedIds.value = [...templateLibraryItemSelectedIds.value, itemId]
}

async function searchTemplateLibraryItems() {
  resetMessages()
  const query = templateLibraryItemQuery.value.trim()
  if (!query) {
    templateLibraryItemResults.value = []
    templateLibraryItemSelectedIds.value = []
    return
  }

  try {
    templateLibraryItemLoading.value = true
    const data = await api.listAdminCustomItems({
      q: query,
      page: 1,
      limit: 50,
      filter: 'library',
      collapseShared: true,
    })
    templateLibraryItemResults.value = (data.items || []).filter((item) => item?.id)
    templateLibraryItemSelectedIds.value = templateLibraryItemSelectedIds.value.filter((id) =>
      templateLibraryItemResults.value.some((item) => item.id === id)
    )
  } catch (e) {
    templateLibraryItemResults.value = []
    templateLibraryItemSelectedIds.value = []
    error.value = '개별 아이템 검색에 실패했어요.'
  } finally {
    templateLibraryItemLoading.value = false
  }
}

async function confirmTemplateLibraryItemImport() {
  resetMessages()
  if (!selectedTemplateId.value) {
    error.value = '아이템을 받을 템플릿을 먼저 선택해주세요.'
    return
  }
  if (!selectedTemplateLibraryItems.value.length) {
    error.value = '가져올 아이템을 하나 이상 선택해주세요.'
    return
  }

  const stagedCount = mergeLibraryItemsIntoDrafts(selectedTemplateLibraryItems.value, '라이브러리 검색')
  closeTemplateLibraryItemModal()
  success.value = stagedCount
    ? `${stagedCount}개의 아이템을 초안 목록에 추가했어요. 필요 없는 항목은 제외한 뒤 저장하면 됩니다.`
    : '추가로 가져올 새 아이템이 없었어요.'
}

function toggleTemplateSourceImportSelection(templateId) {
  if (!templateId) return
  if (templateSourceImportSelectedIds.value.includes(templateId)) {
    templateSourceImportSelectedIds.value = templateSourceImportSelectedIds.value.filter((id) => id !== templateId)
    return
  }
  templateSourceImportSelectedIds.value = [...templateSourceImportSelectedIds.value, templateId]
}

async function confirmTemplateSourceImport() {
  resetMessages()
  if (!selectedTemplateId.value) {
    error.value = '가져올 대상을 받을 템플릿을 먼저 선택해주세요.'
    return
  }
  if (!templateSourceImportSelectedIds.value.length) {
    error.value = '아이템을 가져올 원본 템플릿을 하나 이상 선택해주세요.'
    return
  }

  try {
    let stagedCount = 0
    for (const templateId of templateSourceImportSelectedIds.value) {
      const template = templates.value.find((entry) => entry.id === templateId)
      const data = await api.getTopic(templateId)
      const libraryItems = (data.items || []).map((item) => ({
        ...item,
        sourceType: 'template',
      }))
      stagedCount += mergeLibraryItemsIntoDrafts(libraryItems, template?.name || data.topic?.name || templateId)
    }
    closeTemplateSourceImportModal()
    success.value = stagedCount
      ? `${stagedCount}개의 기존 템플릿 아이템을 초안 목록에 추가했어요. 필요 없는 항목은 제외한 뒤 저장하면 됩니다.`
      : '추가로 가져올 새 아이템이 없었어요.'
  } catch (e) {
    error.value = '기존 템플릿 아이템을 가져오지 못했어요.'
  }
}

async function confirmTierListImport() {
  resetMessages()
  if (!importModalTierList.value || !importModalItems.value.length) {
    error.value = '가져올 티어표 정보를 확인하지 못했어요.'
    return
  }

  const tierList = importModalTierList.value
  const itemIds = importModalItems.value.map((item) => item.id)

  try {
    if (importModalMode.value === 'existing') {
      if (!importModalTargetTemplateId.value) {
        error.value = '아이템을 추가할 기존 템플릿을 선택해주세요.'
        return
      }

      const data = await api.promoteAdminTierListItems(tierList.id, {
        topicId: importModalTargetTemplateId.value,
        itemIds,
      })
      if (selectedTemplateId.value === importModalTargetTemplateId.value) await loadTemplate()
      success.value = `${data.items?.length || 0}개의 아이템을 기존 템플릿에 추가했어요.`
    } else {
      const nextTopicId = (importModalNewTemplateId.value || '').trim().toLowerCase()
      const nextTopicName = (importModalNewTemplateName.value || '').trim()
      if (!nextTopicId || !nextTopicName) {
        error.value = '새 템플릿 slug와 이름을 모두 입력해주세요.'
        return
      }

      const data = await api.createAdminTemplateFromTierList(tierList.id, {
        slug: nextTopicId,
        name: nextTopicName,
        itemIds,
      })
      await refreshTemplates()
      success.value = `"${data.template?.name || nextTopicName}" 템플릿을 생성했어요.`
    }

    closeTierListImportModal()
  } catch (e) {
    error.value = '티어표 아이템 가져오기에 실패했어요.'
  }
}

function templateRequestTypeLabel(request) {
  return request.type === 'create' ? '템플릿 등록 요청' : '템플릿 업데이트 요청'
}

function templateRequestTargetLabel(request) {
  if (request.type === 'create') {
    if (request.targetTopicName || request.targetTopicId) {
      return `연결된 템플릿 · ${request.targetTopicName || request.targetTopicSlug || request.targetTopicId}`
    }
    return '연결된 템플릿 없음'
  }
  return request.targetTopicName || request.targetTopicSlug || request.targetTopicId || request.sourceTopicName
}

const displayThumbnailUrl = computed(() => {
  if (thumbPreviewUrl.value) return thumbPreviewUrl.value
  if (selectedTemplate.value?.template?.thumbnailSrc) return toApiUrl(selectedTemplate.value.template.thumbnailSrc)
  return ''
})

function fmt(ts) {
  return new Date(ts).toLocaleString(undefined, {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
  })
}

function userAvatarUrl(user) {
  return user?.avatarSrc ? toApiUrl(user.avatarSrc) : ''
}

function userAvatarFallback(user) {
  return (user?.email?.trim()?.[0] || '?').toUpperCase()
}

function openUserProfile(user) {
  if (!user?.id) return
  router.push(userProfilePath(user.id))
}

</script>

<template>
  <section class="wrap adminUiScope">
    <!-- <h2 class="title">관리자</h2> -->
    <div class="card">
      <!-- <div class="desc">기능이 많아진 만큼 관리 영역을 템플릿, 아이템, 회원 관리로 나눠서 정리합니다.</div> -->
      <div v-if="!auth.user" class="warn">로그인이 필요해요.</div>
      <div v-else-if="!isAdmin" class="warn">이 계정은 관리자 권한이 없어요.</div>

      <template v-else>
        <div class="adminWorkspace">
          <div class="adminMain">
            <header class="adminHero">
              <div class="adminHero__eyebrow">관리자 작업실</div>
              <h2 class="adminHero__title">{{ activeTabTitle }}</h2>
              <p class="adminHero__desc">{{ activeTabDescription }}</p>
              <div class="adminHero__stats">
                <article v-for="stat in adminOverviewStats" :key="stat.label" class="adminHeroStat">
                  <span class="adminHeroStat__label">{{ stat.label }}</span>
                  <strong class="adminHeroStat__value">{{ stat.value }}</strong>
                </article>
              </div>
            </header>

            <AdminFeaturedSection
              v-if="activeTab === 'featured'"
              :featured-templates="featuredTemplates"
              :available-templates-for-featured="availableTemplatesForFeatured"
              :featured-template-ids="featuredTemplateIds"
              :featured-list-ref="setFeaturedListRef"
              :save-featured-order="saveFeaturedOrder"
              :move-featured-template="moveFeaturedTemplate"
              :remove-featured-template="removeFeaturedTemplate"
              :add-featured-template="addFeaturedTemplate"
            />

            <AdminTemplatesSection
              v-else-if="activeTab === 'template-admin'"
              :active-template-request="activeTemplateRequest"
              :template-request-source-url="templateRequestSourceUrl"
              :staged-request-draft-count="stagedRequestDraftCount"
              :applied-request-item-count="appliedRequestItemCount"
              :open-template-create-modal="openTemplateCreateModal"
              :open-template-source-import-modal="openTemplateSourceImportModal"
              :open-template-library-item-modal="openTemplateLibraryItemModal"
              :open-template-bulk-tag-modal="openTemplateBulkTagModal"
              :is-template-loading="isTemplateLoading"
              :has-selected-template="hasSelectedTemplate"
              :selected-template="selectedTemplate"
              :display-thumbnail-url="displayThumbnailUrl"
              v-model:template-meta-draft-name="templateMetaDraftName"
              v-model:template-meta-draft-slug="templateMetaDraftSlug"
              :template-meta-saving="templateMetaSaving"
              :can-save-template-meta="canSaveTemplateMeta"
              :save-template-meta="saveTemplateMeta"
              :can-bulk-tag-template-items="canBulkTagTemplateItems"
              :can-apply-thumbnail="canApplyThumbnail"
              :template-visibility-saving="templateVisibilitySaving"
              :thumb-file-input-ref="setThumbFileInputRef"
              :open-thumb-file-picker="openThumbFilePicker"
              :on-thumb="onThumb"
              :on-thumb-drag-enter="onThumbDragEnter"
              :on-thumb-drag-over="onThumbDragOver"
              :on-thumb-drag-leave="onThumbDragLeave"
              :on-thumb-drop="onThumbDrop"
              :is-thumb-drag-over="isThumbDragOver"
              :upload-thumbnail="uploadThumbnail"
              :remove-template="removeTemplate"
              :toggle-selected-template-visibility="toggleSelectedTemplateVisibility"
              :item-file-input-ref="setItemFileInputRef"
              :on-file="onFile"
              :is-item-drag-over="isItemDragOver"
              :on-item-drag-enter="onItemDragEnter"
              :on-item-drag-over="onItemDragOver"
              :on-item-drag-leave="onItemDragLeave"
              :on-item-drop="onItemDrop"
              :open-item-file-picker="openItemFilePicker"
              :upload-item-drafts="uploadItemDrafts"
              :clear-item-files="clearItemFiles"
              :can-add-item="canAddItem"
              :upload-item="uploadItem"
              :remove-upload-draft="removeUploadDraft"
              :has-template-item-order-changes="hasTemplateItemOrderChanges"
              :save-template-item-order="saveTemplateItemOrder"
              :template-item-list-ref="setTemplateItemListRef"
              :save-template-item-label="saveTemplateItemLabel"
              :remove-template-item="removeTemplateItem"
              :selected-template-id="selectedTemplateId"
            />

            <AdminItemsSection
              v-else-if="activeTab === 'items'"
              :custom-items="customItems"
              :open-custom-item-modal="openCustomItemModal"
              :custom-item-page="customItemPage"
              :custom-item-page-count="customItemPageCount"
              :custom-item-total="customItemTotal"
              :move-custom-item-page="moveCustomItemPage"
            />

            <AdminTierlistsSection
              v-else-if="activeTab === 'tierlists'"
              :tierlists-mode="tierlistsMode"
              :template-requests="templateRequests"
              :open-template-request-preview="openTemplateRequestPreview"
              :fmt="fmt"
              :template-request-target-label="templateRequestTargetLabel"
              :template-request-status-label="templateRequestStatusLabel"
              :template-request-source-url="templateRequestSourceUrl"
              :start-template-request-review="startTemplateRequestReview"
              :complete-template-request="completeTemplateRequest"
              :admin-tier-lists="adminTierLists"
              :tier-list-thumb-url="tierListThumbUrl"
              :open-admin-tier-list="openAdminTierList"
              :tier-list-author-display-name="tierListAuthorDisplayName"
              :tier-list-visibility-label="tierListVisibilityLabel"
              :open-tier-list-extra-item-modal="openTierListExtraItemModal"
              :open-tier-list-import-modal="openTierListImportModal"
              :admin-tier-list-page="adminTierListPage"
              :admin-tier-list-page-count="adminTierListPageCount"
              :admin-tier-list-total="adminTierListTotal"
              :admin-tier-list-stats="adminTierListStats"
              :open-admin-tier-list-manage-modal="openAdminTierListManageModal"
              :toggle-admin-tier-list-featured="toggleAdminTierListFeatured"
              :move-admin-tier-list-page="moveAdminTierListPage"
            />

            <AdminUsersSection
              v-else
              :user-query="userQuery"
              :user-sort="userSort"
              :user-sort-direction="userSortDirection"
              :users="users"
              :submit-user-filters="submitUserFilters"
              :set-user-avatar-input="setUserAvatarInput"
              :on-user-avatar-change="onUserAvatarChange"
              :open-user-avatar-picker="openUserAvatarPicker"
              :user-avatar-url="userAvatarUrl"
              :user-display-name="userDisplayName"
              :user-avatar-fallback="userAvatarFallback"
              :remove-user-avatar="removeUserAvatar"
              :can-edit-user-avatar="canEditUserAvatar"
              :can-edit-user-info="canEditUserInfo"
              :can-delete-user="canDeleteUser"
              :role-label-of="roleLabelOf"
              :fmt="fmt"
              :open-user-profile="openUserProfile"
              :open-user-delete-modal="openUserDeleteModal"
              :open-user-edit-modal="openUserEditModal"
              :delete-icon="deleteIcon"
              @update:user-query="userQuery = $event"
              @update:user-sort="userSort = $event"
              @update:user-sort-direction="userSortDirection = $event"
            />

            <div v-if="templateCreateModalOpen" class="modalOverlay" @click.self="closeTemplateCreateModal">
                <div class="modalCard" role="dialog" aria-modal="true">
                  <div class="modalCard__title">새 템플릿 만들기</div>
                <div class="modalCard__desc">템플릿 이름과 공개 주소용 slug를 입력하면, 내부 ID는 서버가 자동으로 생성합니다.</div>
                <div class="modalCard__form">
                  <label class="field">
                    <span class="field__label">템플릿 이름</span>
                    <input v-model="newTemplateName" class="field__input" maxlength="60" placeholder="템플릿 이름" />
                    <span class="field__hint">{{ newTemplateName.length }}/60자</span>
                  </label>
                  <label class="field">
                    <span class="field__label">템플릿 slug</span>
                    <input
                      v-model="newTemplateId"
                      class="field__input"
                      maxlength="120"
                      placeholder="예: idol-rhythm"
                      @keydown.enter.prevent="createTemplate"
                    />
                    <span class="field__hint">영문 소문자, 숫자, 하이픈만 사용 · {{ newTemplateId.length }}/120자</span>
                  </label>
                  <label class="toggleSwitch">
                    <input v-model="newTemplateIsPublic" type="checkbox" />
                    <span class="toggleSwitch__label">{{ newTemplateIsPublic ? '템플릿 공개중' : '비공개 상태' }}</span>
                    <span class="toggleSwitch__track"><span class="toggleSwitch__thumb"></span></span>
                  </label>
                </div>
                <div class="modalCard__actions">
                  <button class="btn btn--ghost" @click="closeTemplateCreateModal">취소</button>
                  <button class="btn btn--primary" :disabled="!newTemplateId.trim() || !newTemplateName.trim()" @click="createTemplate">템플릿 생성</button>
                </div>
              </div>
            </div>

            <div v-if="userEditModalOpen" class="modalOverlay" @click.self="closeUserEditModal">
              <div class="modalCard modalCard--userEdit" role="dialog" aria-modal="true">
                <div class="modalCard__title">회원 정보 수정</div>
                <div class="modalCard__desc">{{ modalTargetUser ? `${userDisplayName(modalTargetUser)} 계정의 정보와 권한을 조정할 수 있어요.` : '' }}</div>
                <div class="userEditForm">
                  <label class="field">
                    <span class="field__label">이메일</span>
                    <input v-model="modalUserDraftEmail" class="field__input" maxlength="255" placeholder="계정 이메일" />
                    <span class="field__hint">로그인 계정으로 사용하는 이메일입니다. {{ modalUserDraftEmail.length }}/255자</span>
                  </label>

                  <label class="field">
                    <span class="field__label">닉네임</span>
                    <input v-model="modalUserDraftNickname" class="field__input" maxlength="40" placeholder="표시용 닉네임" />
                    <span class="field__hint">티어표 작성자명과 프로필에 표시됩니다. {{ modalUserDraftNickname.length }}/40자</span>
                  </label>

                  <button
                    v-if="canManageModalRole"
                    class="userRoleAction"
                    type="button"
                    @click="openUserRoleModal(modalTargetUser, !modalUserDraftIsAdmin)"
                  >
                    {{ modalUserDraftIsAdmin ? '운영자 권한 해제' : '운영자 권한 부여' }}
                  </button>
                </div>
                <div class="modalCard__actions">
                  <button class="btn btn--ghost" @click="closeUserEditModal">취소</button>
                  <button class="btn btn--primary" :disabled="!isUserEditDirty" @click="saveUserEdit">회원 정보 저장</button>
                </div>
              </div>
            </div>

            <div v-if="userDeleteModalOpen" class="modalOverlay" @click.self="closeUserDeleteModal">
              <div class="modalCard" role="dialog" aria-modal="true">
                <div class="modalCard__title">회원 삭제</div>
                <div class="modalCard__desc">{{ modalTargetUser ? `${modalTargetUser.email} 계정을 삭제할까요? 작성한 티어표와 커스텀 이미지도 함께 삭제됩니다.` : '' }}</div>
                <div class="modalCard__actions">
                  <button class="btn btn--ghost" @click="closeUserDeleteModal">취소</button>
                  <button class="btn btn--danger" @click="confirmUserDelete">삭제</button>
                </div>
              </div>
            </div>

            <div v-if="userRoleModalOpen" class="modalOverlay" @click.self="closeUserRoleModal">
              <div class="modalCard" role="dialog" aria-modal="true">
                <div class="modalCard__title">운영자 권한 변경</div>
                <div class="modalCard__desc">
                  {{
                    modalTargetUser
                      ? modalRoleNextAdmin
                        ? `${userDisplayName(modalTargetUser)} 사용자를 운영자로 지정할까요?`
                        : `${userDisplayName(modalTargetUser)} 사용자의 운영자 권한을 해제할까요?`
                      : ''
                  }}
                </div>
                <div class="modalCard__actions">
                  <button class="btn btn--ghost" @click="closeUserRoleModal">취소</button>
                  <button class="btn btn--primary" @click="confirmUserRoleDraft">확인</button>
                </div>
              </div>
            </div>

            <div v-if="importModalOpen" class="modalOverlay" @click.self="closeTierListImportModal">
              <div class="modalCard modalCard--import" role="dialog" aria-modal="true">
                <div class="modalCard__title">티어표 아이템 가져오기</div>
                <div class="modalCard__desc">
                  "{{ importModalTierList?.title }}"의 아이템 {{ importModalItemCount }}개를 어디로 가져올지 선택해주세요.
                </div>

                <div class="importModeTabs">
                  <button class="modeTab" :class="{ 'modeTab--active': importModalMode === 'existing' }" @click="importModalMode = 'existing'">
                    기존 템플릿에 추가
                  </button>
                  <button class="modeTab" :class="{ 'modeTab--active': importModalMode === 'new' }" @click="importModalMode = 'new'">
                    새 템플릿 만들기
                  </button>
                </div>

                <div v-if="importModalMode === 'existing'" class="modalCard__form">
                  <select v-model="importModalTargetTemplateId" class="select">
                    <option value="">기존 템플릿 선택</option>
                    <option v-for="template in templates" :key="template.id" :value="template.id">{{ template.name }}</option>
                  </select>
                </div>

                <div v-else class="modalCard__form">
                  <input v-model="importModalNewTemplateId" class="input" placeholder="새 템플릿 slug" />
                  <input v-model="importModalNewTemplateName" class="input" placeholder="새 템플릿 이름" />
                </div>

                <div class="modalCard__actions">
                  <button class="btn btn--ghost" @click="closeTierListImportModal">취소</button>
                  <button class="btn btn--primary" @click="confirmTierListImport">
                    {{ importModalMode === 'existing' ? '여기로 가져오기' : '새 템플릿 생성' }}
                  </button>
                </div>
              </div>
            </div>

            <div v-if="templateSourceImportModalOpen" class="modalOverlay" @click.self="closeTemplateSourceImportModal">
              <div class="modalCard modalCard--import" role="dialog" aria-modal="true">
                <div class="modalCard__title">기존 템플릿 아이템 가져오기</div>
                <div class="modalCard__desc">
                  여러 템플릿의 기본 아이템을 초안 목록으로 모아둘 수 있어요. 이후 필요 없는 항목은 제외하고 현재 템플릿에만 저장하면 됩니다.
                </div>

                <div class="modalCard__form">
                  <input v-model="templateSourceImportQuery" class="input" placeholder="템플릿 이름 또는 slug 검색" />
                </div>

                <div class="templateImportList">
                  <button
                    v-for="template in filteredTemplateSourceImportTemplates"
                    :key="template.id"
                    type="button"
                    class="adminTemplatePicker__item"
                    :class="{ 'adminTemplatePicker__item--active': templateSourceImportSelectedIds.includes(template.id) }"
                    @click="toggleTemplateSourceImportSelection(template.id)"
                  >
                    <span class="adminTemplatePicker__name">{{ template.name }}</span>
                    <span class="adminTemplatePicker__meta">{{ template.slug || template.id }}</span>
                  </button>
                  <div v-if="!filteredTemplateSourceImportTemplates.length" class="hint">조건에 맞는 템플릿이 없어요.</div>
                </div>

                <div class="modalCard__actions">
                  <button class="btn btn--ghost" @click="closeTemplateSourceImportModal">취소</button>
                  <button class="btn btn--primary" @click="confirmTemplateSourceImport">초안으로 가져오기</button>
                </div>
              </div>
            </div>

            <div v-if="templateLibraryItemModalOpen" class="modalOverlay" @click.self="closeTemplateLibraryItemModal">
              <div class="modalCard modalCard--import" role="dialog" aria-modal="true">
                <div class="modalCard__title">개별 아이템 검색</div>
                <div class="modalCard__desc">
                  이름, 파일명, 태그로 검색해서 현재 템플릿 초안 목록에 필요한 아이템만 모아둘 수 있어요.
                </div>

                <div class="modalCard__form modalCard__form--search">
                  <input
                    v-model="templateLibraryItemQuery"
                    class="input"
                    placeholder="아이템 이름, 파일명, 태그 검색"
                    @keydown.enter.prevent="searchTemplateLibraryItems"
                  />
                  <button class="btn btn--ghost" type="button" @click="searchTemplateLibraryItems">
                    {{ templateLibraryItemLoading ? '검색중...' : '검색' }}
                  </button>
                </div>

                <div class="templateImportList">
                  <div v-if="!templateLibraryItemQuery.trim() && !templateLibraryItemLoading" class="hint">
                    검색 전에는 목록을 보여주지 않아요. 필요한 키워드로 직접 찾아서 추가해주세요.
                  </div>
                  <button
                    v-for="item in templateLibraryItemResults"
                    :key="item.id"
                    type="button"
                    class="adminTemplatePicker__item"
                    :class="{ 'adminTemplatePicker__item--active': templateLibraryItemSelectedIds.includes(item.id) }"
                    @click="toggleTemplateLibraryItemSelection(item.id)"
                  >
                    <span class="customItemModal__replacementRow">
                      <img class="customItemModal__replacementThumb" :src="toApiUrl(item.src)" :alt="item.label" />
                      <span class="customItemModal__replacementCopy">
                        <span class="adminTemplatePicker__name">{{ item.label }}</span>
                        <span class="adminTemplatePicker__meta">{{ item.sourceLabel }} · {{ item.ownerName || '알 수 없음' }}</span>
                        <span v-if="item.tags?.length" class="adminTemplatePicker__meta">#{{ item.tags.join(' #') }}</span>
                      </span>
                    </span>
                  </button>
                  <div v-if="templateLibraryItemQuery.trim() && !templateLibraryItemLoading && !templateLibraryItemResults.length" class="hint">
                    조건에 맞는 아이템이 없어요.
                  </div>
                </div>

                <div class="modalCard__actions">
                  <button class="btn btn--ghost" @click="closeTemplateLibraryItemModal">취소</button>
                  <button class="btn btn--primary" :disabled="!selectedTemplateLibraryItems.length" @click="confirmTemplateLibraryItemImport">
                    초안으로 가져오기
                  </button>
                </div>
              </div>
            </div>

            <div v-if="templateBulkTagModalOpen" class="modalOverlay" @click.self="closeTemplateBulkTagModal">
              <div class="modalCard" role="dialog" aria-modal="true">
                <div class="modalCard__title">기본 아이템 공통 태그</div>
                <div class="modalCard__desc">
                  현재 템플릿의 기본 아이템 전체에 같은 태그를 한 번에 추가합니다. 이미 있는 태그는 중복 저장하지 않아요.
                </div>
                <div class="modalCard__form">
                  <label class="field">
                    <span class="field__label">추가할 태그</span>
                    <TagBadgeInput v-model="templateBulkTagDrafts" placeholder="태그 입력 후 Enter" :disabled="templateBulkTagSaving" />
                  </label>
                </div>
                <div class="modalCard__actions">
                  <button class="btn btn--ghost" :disabled="templateBulkTagSaving" @click="closeTemplateBulkTagModal">취소</button>
                  <button class="btn btn--primary" :disabled="templateBulkTagSaving || !templateBulkTagDrafts.length" @click="applyTemplateBulkTags">
                    {{ templateBulkTagSaving ? '적용중...' : '공통 태그 추가' }}
                  </button>
                </div>
              </div>
            </div>

            <div v-if="customItemModalOpen" class="modalOverlay" @click.self="closeCustomItemModal">
              <div class="modalCard modalCard--customItem" role="dialog" aria-modal="true">
                <div v-if="modalTargetCustomItem" class="customItemModal">
                  <aside class="customItemModal__pickerPanel">
                    <div class="customItemModal__pickerHead">
                      <div class="customItemModal__pickerEyebrow">TEMPLATE PICKER</div>
                      <div class="customItemModal__pickerTitle">아이템을 추가할 템플릿</div>
                    </div>
                    <div class="adminSelectionCard">
                      <div class="adminSelectionCard__label">선택한 템플릿</div>
                      <div class="adminSelectionCard__title">{{ customItemTargetTemplate?.name || '아직 선택하지 않음' }}</div>
                      <div class="adminSelectionCard__meta">{{ customItemTargetTemplate?.slug || customItemTargetTemplate?.id || '템플릿을 골라 주세요.' }}</div>
                    </div>
                    <div class="customItemModal__pickerActions">
                      <button class="btn btn--ghost" type="button" @click="openTemplatePickerModal('custom-item-target')">템플릿 선택</button>
                    </div>
                    <template v-if="canReplaceModalTarget">
                      <div class="customItemModal__pickerHead">
                        <div class="customItemModal__pickerEyebrow">IMAGE REPLACEMENT</div>
                        <div class="customItemModal__pickerTitle">대체할 이미지 선택</div>
                      </div>
                      <div class="adminSelectionCard">
                        <div class="adminSelectionCard__label">선택한 대체 이미지</div>
                        <div class="adminSelectionCard__title">{{ customItemReplacementTarget?.label || '아직 선택하지 않음' }}</div>
                        <div class="adminSelectionCard__meta">{{ customItemReplacementTarget?.sourceLabel || '검색 후 대체할 이미지를 골라 주세요.' }}</div>
                      </div>
                      <div class="customItemModal__pickerActions">
                        <input
                          v-model="customItemReplacementQuery"
                          class="input"
                          type="text"
                          maxlength="120"
                          placeholder="대체할 이미지 이름 또는 파일명 검색"
                          @keydown.enter.prevent="refreshReplacementCandidates"
                        />
                        <button class="btn btn--ghost btn--small" type="button" @click="refreshReplacementCandidates">
                          {{ customItemReplacementLoading ? '검색중...' : '대체 이미지 검색' }}
                        </button>
                      </div>
                      <div class="customItemModal__replacementList">
                        <button
                          v-for="item in customItemReplacementItems"
                          :key="item.id"
                          class="adminTemplatePicker__item"
                          :class="{ 'adminTemplatePicker__item--active': customItemReplacementTargetId === item.id }"
                          type="button"
                          @click="customItemReplacementTargetId = item.id"
                        >
                          <span class="customItemModal__replacementRow">
                            <img class="customItemModal__replacementThumb" :src="toApiUrl(item.src)" :alt="item.label" />
                            <span class="customItemModal__replacementCopy">
                              <span class="adminTemplatePicker__name">{{ item.label }}</span>
                              <span class="adminTemplatePicker__meta">{{ item.sourceLabel }} · {{ item.ownerName || '알 수 없음' }}</span>
                            </span>
                          </span>
                        </button>
                        <div v-if="!customItemReplacementLoading && !replacementCandidateCount" class="hint hint--tight">
                          검색 전에는 대체 후보를 보여주지 않아요. 검색어를 입력한 뒤 직접 찾아 선택해주세요.
                        </div>
                      </div>
                    </template>
                    <div v-else class="hint hint--tight">
                      이미지 대체는 현재 사용자 업로드 아이템에서만 지원합니다.
                    </div>
                  </aside>
                  <div class="customItemModal__body">
                    <button class="customItemModal__close" type="button" @click="closeCustomItemModal">닫기</button>
                    <div class="customItemModal__content">
                      <div class="customItemModal__preview">
                        <img class="customItemModal__previewImage" :src="toApiUrl(modalTargetCustomItem.src)" :alt="modalTargetCustomItem.label" />
                      </div>
                      <div class="customItemModal__titleRow">
                        <div>
                          <div class="customItemModal__title">{{ modalTargetCustomItem.label }}</div>
                          <div class="customItemModal__source">{{ modalTargetCustomItem.sourceLabel }}</div>
                        </div>
                      </div>
                      <div class="customItemModal__labelEditor">
                        <label class="field">
                          <span class="field__label">아이템 이름</span>
                          <input v-model="customItemModalDraftLabel" class="field__input" type="text" maxlength="60" placeholder="아이템 이름" />
                        </label>
                        <label v-if="modalTargetCustomItem.sourceType !== 'asset'" class="field">
                          <span class="field__label">내부 태그</span>
                          <TagBadgeInput v-model="customItemModalDraftTags" placeholder="태그 입력 후 Enter" />
                        </label>
                        <button class="btn btn--ghost customItemModal__renameButton" type="button" :disabled="customItemModalLabelSaving || !customItemModalDraftLabel.trim() || (customItemModalDraftLabel.trim() === modalTargetCustomItem.label && (modalTargetCustomItem.sourceType === 'asset' || JSON.stringify(parseAdminTagsText(customItemModalDraftTags)) === JSON.stringify(modalTargetCustomItem.tags || [])))" @click="saveCustomItemModalLabel">
                          {{ customItemModalLabelSaving ? '저장중...' : '메타 저장' }}
                        </button>
                      </div>
                    <div class="customItemModal__metaList">
                      <div class="customItemModal__metaRow"><span>파일</span><strong :title="modalTargetCustomItem.src.split('/').pop()">{{ modalTargetCustomItem.src.split('/').pop() }}</strong></div>
                      <div class="customItemModal__metaRow"><span>업로더/출처</span><strong :title="modalTargetCustomItem.ownerName">{{ modalTargetCustomItem.ownerName }}</strong></div>
                      <div class="customItemModal__metaRow"><span>태그</span><strong>{{ modalTargetCustomItem.tags?.length ? '#' + modalTargetCustomItem.tags.join(' #') : '없음' }}</strong></div>
                      <div class="customItemModal__metaRow"><span>템플릿 연결</span><strong>{{ visibleLinkedTemplates.length }}개 템플릿</strong></div>
                      <div class="customItemModal__metaRow"><span>등록일</span><strong>{{ fmt(modalTargetCustomItem.createdAt) }}</strong></div>
                    </div>
                    <div class="customItemModal__linked">
                      <span class="customItemModal__label">이 이미지를 사용하는 템플릿</span>
                      <div v-if="visibleLinkedTemplates.length" class="customItemModal__chips">
                        <span v-for="template in visibleLinkedTemplates" :key="template.id" class="customItemModal__templateChip">
                          <span>{{ template.name }}</span>
                          <button class="customItemModal__templateChipRemove" type="button" @click="unlinkCustomItemTemplate(modalTargetCustomItem, template)">X</button>
                        </span>
                      </div>
                      <div v-else class="hint hint--tight">아직 템플릿에 연결된 항목이 없어요.</div>
                    </div>
                      <div v-if="modalTargetCustomItem.replacedAt" class="customItemModal__linked">
                        <span class="customItemModal__label">대체 상태</span>
                        <div class="customItemModal__titleRow">
                          <div>
                            <div class="customItemModal__title">{{ modalTargetCustomItem.replacedByLabel || '대체 대상 이름 없음' }}</div>
                            <div class="customItemModal__source">이 아이템은 위 대상 이미지로 대체된 상태예요.</div>
                          </div>
                        </div>
                        <div class="customItemModal__metaRow">
                          <span>대체 시각</span>
                          <strong>{{ fmt(modalTargetCustomItem.replacedAt) }}</strong>
                        </div>
                      </div>
                      <div class="customItemModal__actions">
                        <a class="btn btn--ghost customItemModal__action" :href="toApiUrl(modalTargetCustomItem.src)" :download="modalTargetCustomItem.label">이미지 다운로드</a>
                        <button class="btn btn--ghost customItemModal__action" :disabled="!customItemModalTargetTemplateId || modalTargetCustomItem.isPromoting" @click="promoteCustomItem(modalTargetCustomItem)">
                          {{ modalTargetCustomItem.isPromoting ? '추가중...' : '기본 템플릿에 추가' }}
                        </button>
                        <button v-if="canReplaceModalTarget" class="btn btn--primary customItemModal__action" :disabled="!customItemReplacementTargetId || customItemReplacementBusy" @click="replaceCustomItem(modalTargetCustomItem)">
                          {{ customItemReplacementBusy ? '대체중...' : '선택한 이미지로 대체' }}
                        </button>
                        <button v-if="modalTargetCustomItem.replacedAt" class="btn btn--ghost customItemModal__action" :disabled="customItemReplacementBusy" @click="restoreCustomItem(modalTargetCustomItem)">
                          {{ customItemReplacementBusy ? '복구중...' : '원래 이미지로 복구' }}
                        </button>
                        <button v-if="modalTargetCustomItem.canDelete" class="btn btn--danger customItemModal__action" :disabled="modalTargetCustomItem.sourceType === 'user' && !modalTargetCustomItem.replacedAt && (modalTargetCustomItem.usageCount > 0 || visibleLinkedTemplates.length > 0)" @click="openCustomItemDeleteModal(modalTargetCustomItem)">삭제</button>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div>

            <div v-if="templatePickerModalOpen" class="modalOverlay" @click.self="closeTemplatePickerModal">
              <div class="modalCard" role="dialog" aria-modal="true">
                <div class="modalCard__titleRow">
                  <div>
                    <div class="modalCard__title">템플릿 선택</div>
                    <div class="modalCard__desc">
                      {{ templatePickerMode === 'tierlists-filter' ? '특정 주제의 티어표만 보려면 템플릿을 선택하세요.' : '관리할 템플릿을 검색해서 바로 열 수 있어요.' }}
                    </div>
                  </div>
                  <button class="btn btn--ghost btn--small" @click="closeTemplatePickerModal">닫기</button>
                </div>
                <div class="modalCard__form">
                  <input v-model="templatePickerQuery" class="input" placeholder="템플릿 이름 또는 slug 검색" />
                  <select v-model="templatePickerSort" class="select">
                    <option value="recent">최신순</option>
                    <option value="oldest">오래된순</option>
                  </select>
                  <button
                    v-if="templatePickerMode === 'tierlists-filter' && adminTierListTopicId"
                    class="btn btn--ghost"
                    type="button"
                    @click="setAdminTierListTopicId(''); closeTemplatePickerModal()"
                  >
                    모든 주제 보기
                  </button>
                </div>
                <div class="templatePickerModalList">
                  <button
                    v-for="template in filteredTemplatePickerTemplates"
                    :key="template.id"
                    class="adminTemplatePicker__item"
                    :class="{
                      'adminTemplatePicker__item--active': templatePickerMode === 'tierlists-filter'
                        ? adminTierListTopicId === template.id
                        : templatePickerMode === 'custom-item-target'
                          ? customItemModalTargetTemplateId === template.id
                          : selectedTemplateId === template.id,
                      'adminTemplatePicker__item--disabled': templatePickerMode === 'custom-item-target' && linkedCustomItemTemplateIds.has(template.id),
                    }"
                    type="button"
                    :disabled="templatePickerMode === 'custom-item-target' && linkedCustomItemTemplateIds.has(template.id)"
                    @click="chooseTemplateFromPicker(template.id)"
                  >
                    <span class="adminTemplatePicker__name">{{ template.name }}</span>
                    <span class="adminTemplatePicker__meta">{{ template.slug || template.id }}</span>
                    <span v-if="templatePickerMode === 'custom-item-target' && linkedCustomItemTemplateIds.has(template.id)" class="adminTemplatePicker__state">이미 추가됨</span>
                  </button>
                  <div v-if="!filteredTemplatePickerTemplates.length" class="hint hint--tight">검색 결과가 없어요.</div>
                </div>
              </div>
            </div>

            <div v-if="customItemDeleteModalOpen" class="modalOverlay" @click.self="closeCustomItemDeleteModal">
              <div class="modalCard" role="dialog" aria-modal="true">
                <div class="modalCard__title">아이템 삭제</div>
                <div class="modalCard__desc">{{ customItemDeleteImpactText(modalTargetCustomItem) }}</div>
                <div class="modalCard__actions">
                  <button class="btn btn--ghost" @click="closeCustomItemDeleteModal">취소</button>
                  <button class="btn btn--danger" @click="removeCustomItem()">삭제</button>
                </div>
              </div>
            </div>

            <div v-if="templateItemDeleteModalOpen" class="modalOverlay" @click.self="closeTemplateItemDeleteModal">
              <div class="modalCard" role="dialog" aria-modal="true">
                <div class="modalCard__title">기본 아이템 삭제</div>
                <div class="modalCard__desc">
                  <strong>{{ modalTargetTemplateItem?.label || '선택한 아이템' }}</strong>
                  {{ ' ' }}{{ templateItemDeleteImpactText() }}
                </div>
                <div class="modalCard__actions">
                  <button class="btn btn--ghost" @click="closeTemplateItemDeleteModal">취소</button>
                  <button class="btn btn--danger" @click="confirmTemplateItemDelete">삭제</button>
                </div>
              </div>
            </div>

            <div v-if="adminTierListManageModalOpen" class="modalOverlay" @click.self="closeAdminTierListManageModal">
              <div class="modalCard" role="dialog" aria-modal="true">
                <div class="modalCard__title">티어표 관리</div>
                <div class="modalCard__desc">
                  {{ modalTargetAdminTierList ? `${modalTargetAdminTierList.topicName || modalTargetAdminTierList.topicId} · ${tierListAuthorDisplayName(modalTargetAdminTierList)}` : '' }}
                </div>
                <div class="modalCard__form">
                  <label class="field">
                    <span class="field__label">제목</span>
                    <input v-model="adminTierListDraftTitle" class="field__input" maxlength="120" placeholder="티어표 제목" />
                  </label>
                  <label class="field">
                    <span class="field__label">설명</span>
                    <textarea v-model="adminTierListDraftDescription" class="field__input field__input--textarea" rows="4" maxlength="500" placeholder="설명 수정"></textarea>
                  </label>
                  <label class="toggleSwitch">
                    <input v-model="adminTierListDraftIsPublic" type="checkbox" />
                    <span class="toggleSwitch__label">{{ adminTierListDraftIsPublic ? '공개 상태' : '비공개 상태' }}</span>
                    <span class="toggleSwitch__track"><span class="toggleSwitch__thumb"></span></span>
                  </label>
                </div>
                <div class="modalCard__actions">
                  <button class="btn btn--ghost" @click="closeAdminTierListManageModal">취소</button>
                  <button class="btn btn--danger" :disabled="adminTierListDeleting" @click="deleteAdminTierListEntry">
                    {{ adminTierListDeleting ? '삭제중...' : '삭제' }}
                  </button>
                  <button class="btn btn--primary" :disabled="adminTierListSaving || !adminTierListDraftTitle.trim()" @click="saveAdminTierListMeta">
                    {{ adminTierListSaving ? '저장중...' : '저장' }}
                  </button>
                </div>
              </div>
            </div>

            <div v-if="imageResetModalOpen" class="modalOverlay" @click.self="closeImageResetModal">
              <div class="modalCard" role="dialog" aria-modal="true">
                <div class="modalCard__title">최적화 기록 비우기</div>
                <div class="modalCard__desc">
                  {{ imageStatsMonth ? `${imageStatsMonth} 기간의 최적화 기록만 삭제합니다.` : '전체 최적화 작업 기록을 비웁니다. 실제 이미지 파일은 삭제되지 않아요.' }}
                </div>
                <div class="modalCard__actions">
                  <button class="btn btn--ghost" @click="closeImageResetModal">취소</button>
                  <button class="btn btn--danger" @click="confirmImageReset">기록 비우기</button>
                </div>
              </div>
            </div>

            <div v-if="previewModalOpen" class="modalOverlay" @click.self="closePreviewModal">
              <div class="modalCard modalCard--preview" role="dialog" aria-modal="true">
                <div class="modalCard__titleRow">
                  <div class="modalCard__title">{{ previewTierList?.title || '티어표 미리보기' }}</div>
                  <button class="btn btn--ghost btn--small" @click="closePreviewModal">닫기</button>
                </div>
                
                <div v-if="previewTierList?.requestPreview" class="requestPreview">
                  <div class="requestPreview__sheet">
                    <div class="requestPreview__title">{{ previewTierList.title || '티어표 미리보기' }}</div>
                    <div v-if="previewTierList.description" class="requestPreview__description">{{ previewTierList.description }}</div>
                    <div class="requestPreview__meta">
                      {{ previewRequestHasColumns(previewTierList) ? (previewRequestColumns(previewTierList).length + '열 구성') : '단일 열 구성' }} ·
                      {{ previewTierList.snapshotGroups?.length || 0 }}개 행 ·
                      {{ previewTierList.snapshotItems?.length || 0 }}개 아이템
                    </div>
                    <div v-if="previewRequestHasColumns(previewTierList)" class="requestPreview__columns">
                      <div class="requestPreview__columnsSpacer" aria-hidden="true"></div>
                      <div class="requestPreview__columnsGrid" :style="previewRequestGridStyle(previewTierList)">
                        <div
                          v-for="(column, columnIndex) in previewRequestColumns(previewTierList)"
                          :key="column.id"
                          class="requestPreview__columnHeader"
                        >
                          {{ column.name || ('열 ' + (columnIndex + 1)) }}
                        </div>
                      </div>
                    </div>
                    <div class="requestPreview__rows">
                      <div v-for="group in previewTierList.snapshotGroups" :key="group.id" class="requestPreview__row">
                        <div class="requestPreview__label">{{ group.name }}</div>
                        <div class="requestPreview__dropGrid" :style="previewRequestGridStyle(previewTierList)">
                          <div
                            v-for="(column, columnIndex) in previewRequestColumns(previewTierList)"
                            :key="group.id + '-' + column.id"
                            class="requestPreview__dropColumn"
                          >
                            <div class="requestPreview__drop">
                              <div
                                v-for="item in previewRequestGroupCellItems(previewTierList, group, columnIndex)"
                                :key="item.id"
                                class="requestPreview__item"
                              >
                                <img class="thumb requestPreview__itemThumb" :src="toApiUrl(item.src)" :alt="item.label" />
                                <div v-if="previewTierList.snapshotShowCharacterNames" class="itemNameOverlay requestPreview__itemLabel">{{ item.label }}</div>
                              </div>
                            </div>
                          </div>
                        </div>
                      </div>
                    </div>
                    <div v-if="previewRequestPoolItems(previewTierList).length" class="requestPreview__pool">
                      <div class="requestPreview__poolTitle">남은 아이템</div>
                      <div class="requestPreview__poolGrid">
                        <div
                          v-for="item in previewRequestPoolItems(previewTierList)"
                          :key="item.id"
                          class="requestPreview__poolItem requestPreview__item requestPreview__item--muted"
                        >
                          <img class="thumb requestPreview__itemThumb" :src="toApiUrl(item.src)" :alt="item.label" />
                          <div v-if="previewTierList.snapshotShowCharacterNames" class="itemNameOverlay requestPreview__itemLabel">{{ item.label }}</div>
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
                <iframe
                  v-else-if="previewTierList"
                  class="previewFrame"
                  :src="previewTierListUrl(previewTierList)"
                  title="티어표 미리보기"
                />
              </div>
            </div>
          </div>
        </div>
      </template>
    </div>
  </section>

  <Teleport :to="localRightRailTarget">
    <aside v-show="globalRightRailOpen" class="adminSidebar adminUiScope">
      <section class="adminSidebar__panel">
        <div class="adminSidebar__label">Mode</div>
        <div class="adminSidebar__tabs">
          <button class="tab" :class="{ 'tab--active': activeTab === 'featured' }" @click="setTab('featured')">목록 관리</button>
          <button class="tab" :class="{ 'tab--active': activeTab === 'template-admin' }" @click="setTab('template-admin')">템플릿 관리</button>
          <button class="tab" :class="{ 'tab--active': activeTab === 'items' }" @click="setTab('items')">아이템 관리</button>
          <button class="tab" :class="{ 'tab--active': activeTab === 'tierlists' }" @click="setTab('tierlists')">티어표 관리</button>
          <button class="tab" :class="{ 'tab--active': activeTab === 'users' }" @click="setTab('users')">회원 관리</button>
        </div>
      </section>

      <section v-if="activeTab === 'template-admin'" class="adminSidebar__panel">
        <div class="adminSidebar__label">Template</div>
        <div class="adminSidebar__group">
          <button class="btn btn--primary" @click="openTemplateCreateModal">새 템플릿 생성</button>
          <button class="btn btn--ghost" @click="openTemplatePickerModal('template-admin')">템플릿 선택</button>
          <div v-if="selectedTemplate?.template" class="adminSelectionCard">
            <div class="adminSelectionCard__label">선택한 템플릿</div>
            <div class="adminSelectionCard__title">{{ selectedTemplate.template.name }}</div>
            <div class="adminSelectionCard__meta">{{ selectedTemplate.template.slug || selectedTemplate.template.id }}</div>
          </div>
          <div v-if="selectedTemplateId && !hasSelectedTemplate && !isTemplateLoading" class="hint hint--tight">선택된 템플릿 ID: {{ selectedTemplateId }}</div>
        </div>
      </section>

      <section v-else-if="activeTab === 'items'" class="adminSidebar__panel">
        <div class="adminSidebar__label">Filters</div>
        <div class="adminSidebar__group">
          <div class="adminSidebar__inlineRow">
            <input v-model="customItemQuery" class="input" placeholder="파일명, 라벨, 태그, 업로더 검색" @keydown.enter.prevent="submitCustomItemSearch" />
            <button class="btn btn--ghost" @click="submitCustomItemSearch">검색</button>
          </div>
        </div>
        <div class="adminSidebar__group">
          <select :value="customItemLimit" class="select" @change="changeCustomItemLimit(Number($event.target.value))">
            <option :value="50">50개씩 보기</option>
            <option :value="200">200개씩 보기</option>
          </select>
          <select :value="customItemFilter" class="select" @change="changeCustomItemFilter($event.target.value)">
            <option value="all">전체 이미지</option>
            <option value="library">아이템(템플릿 + 사용자)</option>
            <option value="template">템플릿 아이템</option>
            <option value="user">사용자 아이템</option>
            <option value="replaced-user">대체된 아이템</option>
            <option value="thumbnail">썸네일 이미지</option>
            <option value="avatar">프로필 이미지</option>
            <option value="unused">미사용 이미지</option>
          </select>
        </div>
        <div class="adminSidebar__actions">
          <button class="btn btn--ghost" @click="refreshCustomItems">새로고침</button>
          <button
            v-if="customItemFilter === 'unused'"
            class="btn btn--danger"
            :disabled="!hasDeletableUnusedItems"
            @click="removeUnusedCustomItems"
          >
            미사용 이미지 일괄 삭제
          </button>
          <button v-else class="btn btn--ghost" @click="showUnusedCustomItems">미사용 이미지 확인</button>
        </div>
        <div class="adminSidebar__stats">
          <div class="sidebarStat">
            <span class="sidebarStat__label">현재 페이지</span>
            <strong class="sidebarStat__value">{{ customItemPage }}/{{ customItemPageCount }}</strong>
          </div>
          <div class="sidebarStat">
            <span class="sidebarStat__label">검색 결과</span>
            <strong class="sidebarStat__value">{{ customItemTotal }}</strong>
          </div>
        </div>
      </section>

      <section v-else-if="activeTab === 'tierlists'" class="adminSidebar__panel">
        <div class="adminSidebar__label">Tierlists</div>
        <div class="modeTabs modeTabs--stack">
          <button class="modeTab" :class="{ 'modeTab--active': tierlistsMode === 'requests' }" @click="setTierlistsMode('requests')">
            템플릿 요청 관리
          </button>
          <button class="modeTab" :class="{ 'modeTab--active': tierlistsMode === 'all' }" @click="setTierlistsMode('all')">
            전체 티어표 관리
          </button>
        </div>
        <template v-if="tierlistsMode === 'requests'"></template>
        <template v-else>
          <div class="adminSidebar__group">
            <div class="adminSidebar__inlineRow">
              <input
                v-model="adminTierListQuery"
                class="input"
                placeholder="제목, 작성자, 주제 이름 검색"
                @keydown.enter.prevent="submitAdminTierListSearch"
              />
              <button class="btn btn--ghost" @click="submitAdminTierListSearch">검색</button>
            </div>
            <button class="btn btn--ghost" @click="openTemplatePickerModal('tierlists-filter')">주제 선택</button>
            <div v-if="adminTierListTopicId" class="adminSelectionCard">
              <div class="adminSelectionCard__label">필터된 주제</div>
              <div class="adminSelectionCard__title">{{ templates.find((template) => template.id === adminTierListTopicId)?.name || adminTierListTopicId }}</div>
              <div class="adminSelectionCard__meta">{{ adminTierListTopicId }}</div>
              <button class="btn btn--ghost btn--small" @click="setAdminTierListTopicId('')">필터 해제</button>
            </div>
            <select :value="adminTierListLimit" class="select" @change="changeAdminTierListLimit(Number($event.target.value))">
              <option :value="50">50개씩 보기</option>
              <option :value="200">200개씩 보기</option>
            </select>
            <select v-model="adminTierListSort" class="select" @change="changeAdminTierListSort">
              <option value="recent">최근 수정순</option>
              <option value="created">최근 생성순</option>
              <option value="favorites">즐겨찾기 많은 순</option>
            </select>
            <input
              v-model.number="adminTierListMinFavorites"
              class="input"
              type="number"
              min="0"
              max="1000000"
              placeholder="최소 즐겨찾기 수"
              @change="changeAdminTierListMinFavorites"
              @keydown.enter.prevent="changeAdminTierListMinFavorites"
            />
          </div>
          <div class="adminSidebar__actions">
            <button class="btn btn--ghost" @click="refreshAdminTierLists">새로고침</button>
          </div>
          <div class="adminSidebar__stats">
            <div class="sidebarStat">
              <span class="sidebarStat__label">현재 페이지</span>
              <strong class="sidebarStat__value">{{ adminTierListPage }}/{{ adminTierListPageCount }}</strong>
            </div>
            <div class="sidebarStat">
              <span class="sidebarStat__label">검색 결과</span>
              <strong class="sidebarStat__value">{{ adminTierListTotal }}</strong>
            </div>
          </div>
        </template>
      </section>


      <section v-if="activeTab === 'featured'" class="adminSidebar__panel">
        <div class="adminSidebar__label">Image Optimization</div>
        <div class="adminSidebar__group adminSidebar__group--monthPicker">
          <div class="monthPicker">
            <select v-model="selectedImageStatsYear" class="select monthPicker__select monthPicker__select--year">
              <option value="">전체 기간</option>
              <option v-for="year in imageStatsYearOptions" :key="year" :value="year">{{ year }}년</option>
            </select>
            <select v-if="selectedImageStatsYear" v-model="selectedImageStatsMonthNumber" class="select monthPicker__select monthPicker__select--month">
              <option value="">월 선택</option>
              <option v-for="month in imageStatsMonthOptions" :key="month.value" :value="month.value">{{ month.label }}</option>
            </select>
            <button v-if="imageStatsMonth" class="btn btn--ghost btn--tiny" type="button" @click="clearImageStatsMonth">전체</button>
          </div>
          <select v-model.number="imageStatsLimit" class="select">
            <option :value="6">최근 6건</option>
            <option :value="12">최근 12건</option>
            <option :value="24">최근 24건</option>
          </select>
        </div>
        <div class="adminSidebar__actions adminSidebar__actions--split">
          <button class="btn btn--ghost" @click="refreshImageDiagnostics">현황 새로고침</button>
          <button class="btn btn--ghost" @click="openImageResetModal">기록 비우기</button>
        </div>
        <div class="adminSidebar__actions">
          <button class="btn btn--danger" :disabled="!imageStats?.missingReferencedCount || imageMissingCleanupBusy" @click="cleanupMissingImageReferences">
            {{ imageMissingCleanupBusy ? '누락 참조 정리중...' : '누락 참조 정리' }}
          </button>
        </div>
        <div class="hint hint--tight">{{ imageStatsPeriodLabel }}</div>
        <div v-if="imageDiagnosticsCards.length" class="adminSidebar__stats adminSidebar__stats--grid">
          <article v-for="stat in imageDiagnosticsCards" :key="stat.label" class="sidebarStat">
            <span class="sidebarStat__label">{{ stat.label }}</span>
            <strong class="sidebarStat__value">{{ stat.value }}</strong>
          </article>
        </div>
        <div class="adminSidebar__stats">
          <div class="sidebarStat">
            <span class="sidebarStat__label">큐 상태</span>
            <strong class="sidebarStat__value">{{ imageQueue.activeCount }} 실행 / {{ imageQueue.pendingCount }} 대기</strong>
          </div>
          <div class="sidebarStat">
            <span class="sidebarStat__label">작업 누적</span>
            <strong class="sidebarStat__value">{{ imageStats?.completedCount || 0 }} 완료 · {{ imageStats?.failedCount || 0 }} 실패</strong>
          </div>
          <div class="sidebarStat">
            <span class="sidebarStat__label">중복 재사용</span>
            <strong class="sidebarStat__value">{{ imageStats?.reusedCount || 0 }}건</strong>
          </div>
          <div class="sidebarStat">
            <span class="sidebarStat__label">누락 파일</span>
            <strong class="sidebarStat__value">{{ imageStats?.missingReferencedCount || 0 }}건</strong>
          </div>
        </div>
        <div class="adminSidebar__group">
          <div class="section__title">최근 최적화 작업</div>
          <div class="hint hint--tight">현재 {{ imageRecentJobs.length }}건 표시 중</div>
          <div v-if="!imageRecentJobs.length" class="hint hint--tight">아직 기록된 최적화 작업이 없어요.</div>
          <div v-else class="imageJobList">
            <article v-for="job in imageRecentJobs" :key="job.id" class="imageJobRow">
              <div class="imageJobRow__head">
                <strong>{{ formatImageJobSourceCategory(job.sourceCategory) }}</strong>
                <span class="imageJobRow__status">{{ formatImageJobStatus(job.status) }}</span>
              </div>
              <div class="hint hint--tight">
                {{
                  job.reusedAsset
                    ? `이번 업로드 ${formatBytes(job.originalByteSize)} · 재사용 자산 ${formatBytes(job.optimizedByteSize)}`
                    : `${formatBytes(job.originalByteSize)} → ${formatBytes(job.optimizedByteSize)}`
                }}
              </div>
              <div v-if="job.reusedAsset" class="hint hint--tight">동일한 최적화 결과가 이미 있어 새 파일을 다시 만들지 않았어요.</div>
              <div class="hint hint--tight">{{ fmt(job.queuedAt) }}</div>
            </article>
          </div>
        </div>
      </section>
    </aside>
  </Teleport>
</template>

<style>
.adminUiScope.wrap {
  display: grid;
  gap: 16px;
}
.adminUiScope .adminWorkspace {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 16px;
  align-items: start;
}
.adminUiScope .adminMain {
  min-width: 0;
  display: grid;
  gap: 14px;
}
.adminUiScope .adminHero {
  display: grid;
  gap: 10px;
  padding: 22px 24px;
  border-radius: 24px;
  border: 1px solid var(--theme-border);
  background:
    linear-gradient(180deg, var(--theme-surface-soft-2), var(--theme-pill-bg)),
    var(--theme-pill-bg);
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.22);
}
.adminUiScope .adminHero__eyebrow {
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--theme-text-faint);
}
.adminUiScope .adminHero__title {
  margin: 0;
  font-size: 28px;
  line-height: 1.05;
  font-weight: 900;
  letter-spacing: -0.04em;
}
.adminUiScope .adminHero__desc {
  margin: 0;
  color: var(--theme-text-muted);
  line-height: 1.6;
}
.adminUiScope .adminHero__stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
  margin-top: 6px;
}
.adminUiScope .adminHeroStat {
  display: grid;
  gap: 6px;
  padding: 14px 16px;
  border-radius: 16px;
  border: 1px solid var(--theme-border);
  background: var(--theme-pill-bg);
}
.adminUiScope .adminHeroStat__label {
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--theme-text-faint);
}
.adminUiScope .adminHeroStat__value {
  font-size: 22px;
  line-height: 1;
  font-weight: 900;
  letter-spacing: -0.04em;
}
.adminUiScope.adminSidebar {
  display: grid;
  gap: 12px;
}
.adminUiScope .adminSidebar__panel {
  display: grid;
  gap: 12px;
  padding: 16px;
  border-radius: 22px;
  border: 1px solid var(--theme-border);
  background:
    linear-gradient(180deg, var(--theme-surface-soft), var(--theme-pill-bg)),
    color-mix(in srgb, var(--theme-rail-bg) 98%, transparent);
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.18);
}

.adminUiScope .adminSidebar__stats--grid {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
}

.adminUiScope .imageJobList {
  display: grid;
  gap: 8px;
}

.adminUiScope .imageJobRow {
  border: 1px solid var(--theme-border);
  border-radius: 14px;
  padding: 10px 12px;
  background: var(--theme-pill-bg);
  display: grid;
  gap: 4px;
}

.adminUiScope .imageJobRow__head {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  align-items: center;
  font-size: 12px;
}

.adminUiScope .imageJobRow__status {
  color: var(--theme-text-soft);
  text-transform: capitalize;
}
.adminUiScope .adminSidebar__label {
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--theme-text-faint);
}
.adminUiScope .adminSidebar__tabs,
.adminUiScope .adminSidebar__group,
.adminUiScope .adminSidebar__actions,
.adminUiScope .adminSidebar__stats {
  display: grid;
  gap: 10px;
}
.adminUiScope .adminSidebar__inlineRow {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 8px;
  align-items: center;
}
.adminUiScope .adminSidebar__inlineRow .btn {
  white-space: nowrap;
}
.adminUiScope .adminSidebar__group--monthPicker {
  align-items: start;
}
.adminUiScope .monthPicker {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.adminUiScope .monthPicker__select {
  min-width: 0;
}
.adminUiScope .monthPicker__select--year {
  flex: 1 1 132px;
}
.adminUiScope .monthPicker__select--month {
  flex: 1 1 108px;
}
.adminUiScope .adminSidebar__actions--stack .btn {
  width: 100%;
}
.adminUiScope .adminSidebar__actions--split {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.adminUiScope .adminSidebar__groupTitle {
  font-size: 13px;
  font-weight: 800;
  color: var(--theme-text);
}
.adminUiScope .adminTemplatePicker {
  display: grid;
  gap: 8px;
  max-height: 640px;
  overflow: auto;
  padding-right: 4px;
}
.adminUiScope .adminTemplatePicker__item {
  display: grid;
  /* gap: 2px; */
  padding: 11px 12px;
  text-align: left;
  border-radius: 14px;
  border: 1px solid var(--theme-border);
  background: var(--theme-pill-bg);
  color: var(--theme-text);
  cursor: pointer;
}
.adminUiScope .adminTemplatePicker__item--active {
  border-color: rgba(77, 127, 233, 0.58);
  background: rgba(77, 127, 233, 0.12);
}
.adminUiScope .adminTemplatePicker__item--disabled {
  cursor: not-allowed;
  opacity: 0.58;
  border-style: dashed;
}
.adminUiScope .templateImportList {
  display: grid;
  gap: 10px;
  max-height: 360px;
  overflow: auto;
}
.adminUiScope .modalCard__form--search {
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
}
.adminUiScope .adminTemplatePicker__name {
  font-size: 13px;
  font-weight: 800;
}
.adminUiScope .adminTemplatePicker__meta {
  font-size: 11px;
  color: var(--theme-text-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.adminUiScope .adminTemplatePicker__state {
  margin-top: 4px;
  font-size: 11px;
  color: var(--theme-text-faint);
}
.adminUiScope .templatePickerModalList {
  margin-top: 14px;
  display: grid;
  gap: 8px;
  max-height: min(56dvh, 520px);
  overflow: auto;
}
.adminUiScope .adminSelectionCard {
  display: grid;
  gap: 6px;
  padding: 12px 13px;
  border-radius: 14px;
  border: 1px solid var(--theme-border);
  background: var(--theme-pill-bg);
}
.adminUiScope .adminSelectionCard__label {
  font-size: 11px;
  color: var(--theme-text-faint);
}
.adminUiScope .adminSelectionCard__title {
  font-size: 14px;
  font-weight: 800;
}
.adminUiScope .adminSelectionCard__meta {
  font-size: 11px;
  color: var(--theme-text-soft);
  word-break: break-word;
}
.adminUiScope .sidebarStat {
  display: grid;
  gap: 4px;
  padding: 12px 14px;
  border-radius: 14px;
  border: 1px solid var(--theme-border);
  background: var(--theme-pill-bg);
}
.adminUiScope .sidebarStat__label {
  font-size: 12px;
  color: var(--theme-text-soft);
}
.adminUiScope .sidebarStat__value {
  font-size: 14px;
  font-weight: 900;
}
.adminUiScope .card {
  border: 0;
  background: transparent;
  border-radius: 0;
  padding: 0;
}
.adminUiScope .desc {
  opacity: 0.82;
  line-height: 1.5;
}
.adminUiScope .warn,
.adminUiScope .error,
.adminUiScope .success {
  margin-top: 10px;
  padding: 10px 12px;
  border-radius: 12px;
}
.adminUiScope .warn {
  border: 1px solid rgba(245, 158, 11, 0.35);
  background: rgba(245, 158, 11, 0.14);
}
.adminUiScope .error {
  border: 1px solid rgba(239, 68, 68, 0.3);
  background: rgba(239, 68, 68, 0.12);
}
.adminUiScope .success {
  border: 1px solid rgba(52, 211, 153, 0.32);
  background: rgba(52, 211, 153, 0.14);
}
.adminUiScope .tabs,
.adminUiScope .modeTabs {
  margin-top: 0;
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}
.adminUiScope .modeTabs--stack {
  display: grid;
  gap: 8px;
}
.adminUiScope .tab,
.adminUiScope .modeTab {
  padding: 12px 14px;
  border-radius: 14px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  background: var(--theme-surface-soft);
  color: var(--theme-text);
  cursor: pointer;
  font-weight: 800;
  transition:
    border-color 0.16s ease,
    background 0.16s ease,
    transform 0.16s ease;
}
.adminUiScope .tab:hover,
.adminUiScope .modeTab:hover {
  border-color: rgba(255, 255, 255, 0.18);
  background: var(--theme-surface-soft-2);
  transform: translateY(-1px);
}
.adminUiScope .tab--active,
.adminUiScope .modeTab--active {
  background: rgba(96, 165, 250, 0.14);
  border-color: rgba(96, 165, 250, 0.28);
  color: rgba(239, 246, 255, 0.98);
}
.adminUiScope .adminSidebar__tabs .tab,
.adminUiScope .modeTabs--stack .modeTab {
  width: 100%;
  text-align: left;
}
.adminUiScope .panel {
  border: 1px solid rgba(255, 255, 255, 0.1);
  background:
    linear-gradient(180deg, var(--theme-surface-soft), var(--theme-pill-bg)),
    color-mix(in srgb, var(--theme-card-bg) 96%, transparent);
  border-radius: 24px;
  padding: 18px;
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.18);
}
.adminUiScope .panel--empty {
  min-height: 240px;
  display: grid;
  place-items: center;
}
.adminUiScope .panel--compact {
  max-width: 520px;
}
.adminUiScope .emptyState {
  max-width: 520px;
  display: grid;
  gap: 8px;
  text-align: center;
}
.adminUiScope .emptyState__title {
  font-size: 18px;
  font-weight: 900;
}
.adminUiScope .emptyState__desc {
  color: var(--theme-text-muted);
  line-height: 1.6;
}
.adminUiScope .featuredOrderPanel {
  margin-top: 14px;
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(260px, 0.95fr);
  gap: 16px;
}
.adminUiScope .featuredOrderPanel__list,
.adminUiScope .featuredOrderPanel__picker {
  border: 1px solid rgba(255, 255, 255, 0.1);
  background: color-mix(in srgb, var(--theme-pill-bg) 85%, transparent);
  border-radius: 18px;
  padding: 16px;
}
.adminUiScope .featuredList,
.adminUiScope .featuredPickerList {
  margin-top: 10px;
  display: grid;
  gap: 10px;
  max-height: 420px;
  overflow: auto;
}
.adminUiScope .featuredCard {
  display: flex;
  gap: 12px;
  justify-content: space-between;
  align-items: center;
  padding: 14px;
  border-radius: 16px;
  border: 1px solid var(--theme-border);
  background: var(--theme-pill-bg);
}
.adminUiScope .featuredCard__meta {
  display: flex;
  gap: 12px;
  align-items: center;
  min-width: 0;
}
.adminUiScope .featuredCard__rank {
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  border-radius: 999px;
  background: rgba(96, 165, 250, 0.18);
  font-weight: 900;
  flex: 0 0 auto;
}
.adminUiScope .featuredCard__title {
  font-weight: 900;
}
.adminUiScope .featuredCard__id {
  margin-top: 4px;
  opacity: 0.68;
  font-size: 12px;
  word-break: break-all;
}
.adminUiScope .featuredCard__actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: flex-end;
}
.adminUiScope .featuredCard [data-featured-handle] {
  cursor: grab;
}
.adminUiScope .featuredPickerItem {
  width: 100%;
  display: flex;
  gap: 10px;
  justify-content: space-between;
  align-items: center;
  padding: 14px;
  border-radius: 16px;
  border: 1px solid var(--theme-border);
  background: var(--theme-pill-bg);
  color: var(--theme-text);
  cursor: pointer;
  text-align: left;
}
.adminUiScope .featuredPickerItem:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}
.adminUiScope .featuredPickerItem__id {
  opacity: 0.64;
  font-size: 12px;
}
.adminUiScope .panel__title,
.adminUiScope .section__title {
  font-weight: 900;
}
.adminUiScope .section {
  margin-top: 18px;
  padding-top: 18px;
  border-top: 1px solid var(--theme-border);
}
.adminUiScope .section--topGrid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
}
.adminUiScope .templateManagerGrid {
  margin-top: 14px;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
}
.adminUiScope .templateManagerGrid--single {
  grid-template-columns: minmax(0, 1fr);
}
.adminUiScope .templateManagerCard__body {
  margin-top: 10px;
  display: grid;
  gap: 10px;
}
.adminUiScope .adminCard {
  border: 1px solid rgba(255, 255, 255, 0.1);
  background: color-mix(in srgb, var(--theme-pill-bg) 85%, transparent);
  border-radius: 18px;
  padding: 16px;
  min-width: 0;
}
.adminUiScope .adminCard--muted {
  background: var(--theme-pill-bg);
}
.adminUiScope .sectionHeader {
  display: flex;
  gap: 12px;
  justify-content: space-between;
  align-items: flex-start;
  flex-wrap: wrap;
}
.adminUiScope .toolbar {
  margin-top: 14px;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  gap: 10px;
  align-items: end;
}
.adminUiScope .toolbar--secondary {
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
}
.adminUiScope .toolbar__search,
.adminUiScope .toolbar__select {
  margin-top: 0;
}
.adminUiScope .toolbar__button {
  margin-top: 0;
}
.adminUiScope .uploadPreviewCard {
  margin-top: 10px;
  display: flex;
  justify-content: center;
}
.adminUiScope .uploadControls {
  margin-top: 14px;
  display: grid;
  gap: 12px;
  justify-items: center;
}
.adminUiScope .select,
.adminUiScope .input {
  width: 100%;
  box-sizing: border-box;
  padding: 11px 13px;
  border-radius: 14px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  background: var(--theme-pill-bg);
  color: var(--theme-text);
  outline: none;
  /* margin-top: 10px; */
}
.adminUiScope .input--compact {
  max-width: 320px;
}
.adminUiScope .input--labelEdit {
  margin-top: 10px;
}
.adminUiScope .input--dense {
  margin-top: 0;
  padding-top: 9px;
  padding-bottom: 9px;
}
.adminUiScope .hint {
  margin-top: 10px;
  opacity: 0.78;
  font-size: 13px;
}
.adminUiScope .hint--tight {
  margin-top: 6px;
}
.adminUiScope .inputFile {
  width: 100%;
  max-width: 360px;
}
.adminUiScope .inputFile--tight {
  margin-top: 0;
}
.adminUiScope .srOnlyInput {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.adminUiScope .btn {
  height: 100%;
  font-size: 12px;
  line-height: 1.2;
  white-space: nowrap;
  word-break: keep-all;
  padding: 11px 13px;
  border-radius: 14px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: rgba(255, 255, 255, 0.06);
  color: var(--theme-text);
  cursor: pointer;
  font-weight: 800;
  text-align: center;
  text-decoration: none;
  transition:
    background 0.16s ease,
    border-color 0.16s ease,
    transform 0.16s ease;
}
.adminUiScope .btn:hover:not(:disabled) {
  transform: translateY(-1px);
  border-color: rgba(255, 255, 255, 0.22);
  background: rgba(255, 255, 255, 0.1);
}
.adminUiScope .btn--small {
  margin-top: 0;
  padding: 8px 10px;
  font-size: 11px;
}
.adminUiScope .ghost {
  opacity: 0.4;
}
.adminUiScope .chosen {
  outline: 2px solid rgba(96, 165, 250, 0.45);
}
.adminUiScope .thumbCard--dragging {
  box-shadow: 0 18px 38px rgba(15, 23, 42, 0.34);
  opacity: 0.96;
}
.adminUiScope .btn:disabled {
  cursor: not-allowed;
  opacity: 0.45;
}
.adminUiScope .btn--primary {
  background: rgba(96, 165, 250, 0.2);
  border-color: rgba(96, 165, 250, 0.26);
}
.adminUiScope .btn--danger {
  background: rgba(239, 68, 68, 0.14);
  border-color: rgba(239, 68, 68, 0.28);
}
.adminUiScope .btn--ghost {
  background: var(--theme-pill-bg);
}
.adminUiScope .detailHead {
  display: flex;
  gap: 12px;
  justify-content: space-between;
  align-items: flex-start;
  flex-wrap: wrap;
}
.adminUiScope .detailHead__actions {
  display: flex;
  gap: 8px;
}
.adminUiScope .selectedTemplate__name {
  margin-top: 8px;
  font-size: 22px;
  font-weight: 900;
}
.adminUiScope .selectedTemplate__id {
  margin-top: 6px;
  opacity: 0.72;
  word-break: break-all;
}
.adminUiScope .templateSettingsCard {
  display: grid;
  grid-template-columns: minmax(220px, 300px) minmax(0, 1fr);
  gap: 18px;
  align-items: center;
  min-width: 0;
}
.adminUiScope .templateSettingsCard__media {
  min-width: 0;
}
.adminUiScope .templateSettingsCard__body {
  display: grid;
  gap: 14px;
  align-content: center;
  min-width: 0;
}
.adminUiScope .templateSettingsCard__meta {
  color: var(--theme-text-soft);
  font-size: 13px;
  line-height: 1.5;
  word-break: break-all;
}
.adminUiScope .templateSettingsCard__actions {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  min-width: 0;
}
.adminUiScope .templateSettingsCard__actions > .btn {
  flex: 0 0 auto;
  height: auto;
  max-width: 100%;
  white-space: normal;
}
.adminUiScope .templateSettingsCard__actions > a.btn {
  height: auto;
  max-width: 100%;
  white-space: normal;
}
.adminUiScope .templateMetaForm,
.adminUiScope .templateMetaField {
  min-width: 0;
}
.adminUiScope .templateMetaField .input {
  width: 100%;
  min-width: 0;
}
.adminUiScope .selectedThumb {
  width: min(100%, 256px);
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: 16px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  background: var(--theme-surface-soft);
}
.adminUiScope .selectedThumb--empty {
  display: grid;
  place-items: center;
  color: var(--theme-text-soft);
  background: var(--theme-card-bg);
}
.adminUiScope .selectedThumb--sidebar {
  width: 100%;
}
.adminUiScope .selectedTemplateSidebar__name {
  font-size: 18px;
  font-weight: 900;
}
.adminUiScope .selectedTemplateSidebar__id {
  font-size: 12px;
  opacity: 0.68;
  word-break: break-all;
}
.adminUiScope .thumbDropZone {
  position: relative;
  width: 100%;
  display: grid;
  gap: 0;
  padding: 0;
  overflow: hidden;
  border-radius: 22px;
  border: 2px dashed color-mix(in srgb, var(--theme-border-strong) 82%, rgba(255, 255, 255, 0.12));
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--theme-card-bg) 88%, white 4%), color-mix(in srgb, var(--theme-card-bg-hover) 82%, white 6%)),
    radial-gradient(circle at top, color-mix(in srgb, var(--theme-accent) 12%, transparent), transparent 60%);
  text-align: left;
  transition: border-color 0.16s ease, background 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease;
}
.adminUiScope .thumbDropZone--active {
  border-color: rgba(96, 165, 250, 0.56);
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--theme-card-bg-hover) 90%, white 6%), color-mix(in srgb, var(--theme-card-bg) 84%, white 4%)),
    radial-gradient(circle at top, color-mix(in srgb, var(--theme-accent) 18%, transparent), transparent 60%);
  box-shadow: 0 0 0 1px rgba(96, 165, 250, 0.18);
  transform: translateY(-1px);
}
.adminUiScope .thumbDropZone__copy {
  position: absolute;
  inset: auto 0 0 0;
  display: grid;
  place-items: center;
  gap: 8px;
  min-height: 80px;
  padding: 16px 18px;
  background: linear-gradient(180deg, transparent 0%, color-mix(in srgb, var(--theme-main-bg) 82%, transparent) 46%, color-mix(in srgb, var(--theme-main-bg) 94%, transparent) 100%);
}
.adminUiScope .thumbDropZone__iconWrap {
  width: 46px;
  height: 46px;
  display: grid;
  place-items: center;
  border-radius: 14px;
  background: color-mix(in srgb, var(--theme-text) 10%, transparent);
}
.adminUiScope .thumbDropZone__icon {
  width: 24px;
  height: 24px;
  opacity: 0.86;
}
.adminUiScope .thumbDropZone__title {
  font-weight: 900;
  font-size: 14px;
  letter-spacing: 0.01em;
  color: var(--theme-text);
}
.adminUiScope .itemComposer {
  margin-top: 10px;
  /* display: grid; */
  /* grid-template-columns: minmax(0, 1fr) minmax(160px, 192px); */
  /* gap: 16px; */
  /* align-items: start; */
}
.adminUiScope .itemComposer__form {
  display: grid;
  gap: 12px;
  align-items: start;
}
.adminUiScope .dropZone {
  min-height: 180px;
  padding: 28px 22px;
  border-radius: 22px;
  border: 2px dashed color-mix(in srgb, var(--theme-border-strong) 82%, rgba(255, 255, 255, 0.12));
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--theme-card-bg) 88%, white 4%), color-mix(in srgb, var(--theme-card-bg-hover) 82%, white 6%)),
    radial-gradient(circle at top, color-mix(in srgb, var(--theme-accent) 12%, transparent), transparent 60%);
  display: grid;
  place-items: center;
  align-content: center;
  text-align: center;
  cursor: pointer;
  transition:
    border-color 0.16s ease,
    background 0.16s ease,
    transform 0.16s ease;
}
.adminUiScope .dropZone--active {
  border-color: rgba(96, 165, 250, 0.56);
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--theme-card-bg-hover) 90%, white 6%), color-mix(in srgb, var(--theme-card-bg) 84%, white 4%)),
    radial-gradient(circle at top, color-mix(in srgb, var(--theme-accent) 18%, transparent), transparent 60%);
  transform: translateY(-1px);
}
.adminUiScope .dropZone__iconWrap {
  width: 52px;
  height: 52px;
  margin: 0 auto 12px;
  display: grid;
  place-items: center;
  border-radius: 16px;
  background: color-mix(in srgb, var(--theme-text) 10%, transparent);
}
.adminUiScope .dropZone__icon {
  width: 28px;
  height: 28px;
  opacity: 0.86;
}
.adminUiScope .dropZone__title {
  font-weight: 900;
  font-size: 16px;
}
.adminUiScope .dropZone__desc {
  margin-top: 8px;
  font-size: 13px;
  opacity: 0.74;
  line-height: 1.5;
  max-width: 480px;
}
.adminUiScope .dropZone__actions {
  margin-top: 12px;
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  justify-content: center;
}
.adminUiScope .dropZone__button {
  min-width: 124px;
  min-height: 34px;
}
.adminUiScope .itemPreviewCard {
  margin-top: 12px;
  padding: 12px;
  border-radius: 16px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  background: var(--theme-surface-soft);
}
.adminUiScope .itemPreviewCard__submit {
  margin-top: 12px;
  width: 100%;
}
.adminUiScope .itemPreviewGrid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}
.adminUiScope .itemDraftList {
  display: grid;
  gap: 10px;
  grid-template-columns: 1fr 1fr;
}
.adminUiScope .itemDraftRow {
  display: grid;
  grid-template-columns: 72px minmax(0, 1fr);
  gap: 12px;
  align-items: center;
}
.adminUiScope .itemDraftRow__preview {
  width: 72px;
  height: 72px;
  overflow: hidden;
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  background: var(--theme-pill-bg);
}
.adminUiScope .itemDraftRow__body {
  min-width: 0;
  display: grid;
  gap: 6px;
}
.adminUiScope .itemDraftRow__meta {
  display: flex;
  gap: 8px;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
}
.adminUiScope .itemPreviewFrame {
  aspect-ratio: 1 / 1;
  border-radius: 12px;
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.12);
  background: var(--theme-pill-bg);
}
.adminUiScope .itemPreviewImage {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.adminUiScope .itemPreviewEmpty {
  min-height: 192px;
  display: grid;
  place-items: center;
  color: var(--theme-text-soft);
  font-size: 13px;
  text-align: center;
  line-height: 1.5;
}
.adminUiScope .thumbGrid {
  margin-top: 12px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
  gap: 12px;
}
.adminUiScope .thumbCard {
  position: relative;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 16px;
  background: var(--theme-surface-soft);
  padding: 12px;
  min-width: 0;
  cursor: grab;
  user-select: none;
  -webkit-user-drag: none;
  touch-action: none;
}
.adminUiScope .thumbCard__media {
  position: relative;
}
.adminUiScope .thumbCard:active {
  cursor: grabbing;
}
.adminUiScope .thumb {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, 0.12);
}
.adminUiScope .thumb--template {
  max-width: 150px;
  margin: 0 auto;
  display: block;
}
.adminUiScope .thumbLabel {
  margin-top: 8px;
  font-weight: 900;
  font-size: 13px;
  opacity: 0.9;
  word-break: break-word;
}
.adminUiScope .thumbCard__deleteBtn {
  position: absolute;
  top: -24px;
  right: -24px;
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: rgba(9, 13, 22, 0.82);
  color: var(--theme-text);
  font-size: 11px;
  font-weight: 900;
  cursor: pointer;
  z-index: 2;
}
.adminUiScope .thumbCard__deleteBtn:hover {
  background: rgba(190, 24, 24, 0.9);
}
.adminUiScope .thumbLabel--preview {
  text-align: center;
}
.adminUiScope .pill--soft {
  background: rgba(255, 255, 255, 0.08);
}
.adminUiScope .pill--create {
  border-color: rgba(56, 189, 248, 0.36);
  background: rgba(56, 189, 248, 0.16);
  color: rgba(224, 242, 254, 0.98);
}
.adminUiScope .pill--owned {
  border-color: rgba(167, 139, 250, 0.34);
  background: rgba(167, 139, 250, 0.14);
  color: rgba(243, 232, 255, 0.98);
}
.adminUiScope .pill--requestItem {
  border-color: rgba(250, 204, 21, 0.34);
  background: rgba(250, 204, 21, 0.14);
  color: rgba(254, 249, 195, 0.98);
}
.adminUiScope .pill--directFile {
  border-color: rgba(52, 211, 153, 0.34);
  background: rgba(52, 211, 153, 0.14);
  color: rgba(209, 250, 229, 0.98);
}
.adminUiScope .requestWorkspace {
  display: grid;
  gap: 14px;
}
.adminUiScope .requestWorkspace__head {
  display: flex;
  gap: 16px;
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
}
.adminUiScope .requestWorkspace__title {
  margin-top: 4px;
  font-size: 18px;
  font-weight: 900;
}
.adminUiScope .requestWorkspace__stats,
.adminUiScope .requestWorkspace__actions {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
}
.adminUiScope .customItemGrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
  gap: 12px;
}
.adminUiScope .customItemCard {
  appearance: none;
  position: relative;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 18px;
  background: var(--theme-surface-soft);
  overflow: hidden;
  display: grid;
  gap: 10px;
  padding: 10px;
  min-width: 0;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.18s ease, transform 0.18s ease, background 0.18s ease;
}
.adminUiScope .customItemCard__badge {
  position: absolute;
  top: 10px;
  left: 10px;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  padding: 5px 8px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--theme-main-bg) 82%, transparent);
  color: var(--theme-text);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.02em;
}
.adminUiScope .customItemCard__badge--template {
  background: rgba(96, 165, 250, 0.18);
}
.adminUiScope .customItemCard__badge--asset {
  background: rgba(251, 191, 36, 0.18);
}
.adminUiScope .customItemCard__badge--replaced {
  top: 40px;
  background: rgba(245, 158, 11, 0.2);
  color: #fde68a;
}
.adminUiScope .customItemCard:hover {
  border-color: rgba(126, 162, 255, 0.42);
  background: rgba(255, 255, 255, 0.06);
  transform: translateY(-1px);
}
.adminUiScope .customItemCard__image {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  display: block;
  border-radius: 14px;
  background: var(--theme-pill-bg);
}
.adminUiScope .customItemCard__title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 800;
  font-size: 13px;
  line-height: 1.3;
  color: var(--theme-text);
}
.adminUiScope .customItemModal {
  display: grid;
  grid-template-columns: 340px minmax(0, 1fr);
  min-height: min(860px, calc(100dvh - 40px));
  align-items: stretch;
}
.adminUiScope .customItemModal__pickerPanel {
  display: grid;
  align-content: start;
  gap: 18px;
  min-width: 0;
  min-height: 0;
  padding: 28px 22px;
  border-right: 1px solid var(--theme-border);
  background: var(--theme-pill-bg);
  overflow: auto;
  overscroll-behavior: contain;
}
.adminUiScope .customItemModal__pickerHead {
  display: grid;
  gap: 10px;
}
.adminUiScope .customItemModal__pickerEyebrow {
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--theme-text-faint);
}
.adminUiScope .customItemModal__pickerTitle {
  font-size: 18px;
  font-weight: 900;
}
.adminUiScope .customItemModal__pickerActions {
  display: grid;
  gap: 10px;
}
.adminUiScope .customItemModal__replacementList {
  display: grid;
  gap: 8px;
}
.adminUiScope .customItemModal__replacementRow {
  display: grid;
  grid-template-columns: 48px minmax(0, 1fr);
  gap: 10px;
  align-items: center;
}
.adminUiScope .customItemModal__replacementThumb {
  width: 48px;
  height: 48px;
  object-fit: cover;
  border-radius: 12px;
  border: 1px solid var(--theme-border);
  background: var(--theme-surface-soft);
}
.adminUiScope .customItemModal__replacementCopy {
  min-width: 0;
  display: grid;
  gap: 2px;
}
.adminUiScope .customItemModal__body {
  min-width: 0;
  min-height: 0;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  gap: 16px;
  padding: 24px 28px 28px;
  overflow: hidden;
}
.adminUiScope .customItemModal__content {
  min-width: 0;
  min-height: 0;
  display: grid;
  align-content: start;
  gap: 18px;
  overflow: auto;
  padding-right: 8px;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.18) transparent;
}
.adminUiScope .customItemModal__preview {
  display: flex;
  justify-content: flex-start;
}
.adminUiScope .customItemModal__previewImage {
  width: 88px;
  height: 88px;
  object-fit: cover;
  border-radius: 18px;
  border: 1px solid var(--theme-border);
  background: var(--theme-surface-soft);
  flex: 0 0 auto;
}
.adminUiScope .customItemModal__pickerPanel::-webkit-scrollbar,
.adminUiScope .customItemModal__content::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}
.adminUiScope .customItemModal__pickerPanel::-webkit-scrollbar-thumb,
.adminUiScope .customItemModal__content::-webkit-scrollbar-thumb {
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.16);
}
.adminUiScope .customItemModal__pickerPanel::-webkit-scrollbar-track,
.adminUiScope .customItemModal__content::-webkit-scrollbar-track {
  background: transparent;
}
.adminUiScope .customItemModal__labelEditor {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.adminUiScope .customItemModal__renameButton {
  white-space: nowrap;
}
.adminUiScope .customItemModal__titleRow,
.adminUiScope .customItemModal__linked {
  display: grid;
  gap: 8px;
}
.adminUiScope .customItemModal__linked {
  padding: 14px 16px;
  border-radius: 18px;
  border: 1px solid var(--theme-border);
  background: var(--theme-surface-soft);
}
.adminUiScope .customItemModal__close {
  justify-self: end;
  border: 0;
  background: transparent;
  color: var(--theme-text-muted);
  cursor: pointer;
  font-size: 13px;
}
.adminUiScope .customItemModal__label {
  font-size: 11px;
  color: var(--theme-text-faint);
}
.adminUiScope .customItemModal__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.adminUiScope .customItemModal__templateChip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 11px;
  border-radius: 999px;
  background: var(--theme-pill-bg);
  border: 1px solid var(--theme-border);
  font-size: 12px;
  color: var(--theme-text);
}
.adminUiScope .customItemModal__templateChipRemove {
  border: 0;
  background: transparent;
  color: var(--theme-text-soft);
  cursor: pointer;
  padding: 0;
  font-size: 11px;
  font-weight: 800;
}
.adminUiScope .customItemModal__title {
  font-size: 19px;
  font-weight: 900;
  line-height: 1.35;
  word-break: break-word;
}
.adminUiScope .customItemModal__source {
  margin-top: 4px;
  font-size: 12px;
  color: var(--theme-text-soft);
}
.adminUiScope .customItemModal__metaList {
  display: grid;
  gap: 10px;
  padding: 14px 16px;
  border-radius: 18px;
  border: 1px solid var(--theme-border);
  background: var(--theme-surface-soft);
}
.adminUiScope .customItemModal__metaRow {
  display: grid;
  gap: 4px;
  min-width: 0;
}
.adminUiScope .customItemModal__metaRow span {
  font-size: 11px;
  color: var(--theme-text-faint);
}
.adminUiScope .customItemModal__metaRow strong {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 13px;
  color: var(--theme-text);
}
.adminUiScope .customItemModal__actions {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
  align-self: end;
}
.adminUiScope .customItemModal__action {
  width: 100%;
  min-width: 0;
  padding-inline: 12px;
  white-space: normal;
  line-height: 1.35;
  text-align: center;
}
.adminUiScope .modalCard--customItem {
  width: min(1480px, calc(100vw - 40px));
  min-width: min(980px, calc(100vw - 40px));
  height: min(860px, calc(100dvh - 40px));
  max-height: calc(100dvh - 40px);
  padding: 0;
  overflow: hidden;
  border-radius: 28px;
  border: 1px solid var(--theme-border-strong);
  background: linear-gradient(180deg, rgba(34, 34, 34, 0.98), rgba(18, 18, 18, 0.98));
  box-shadow: 0 28px 90px rgba(0, 0, 0, 0.42);
}
.adminUiScope .pager {
  margin-top: 16px;
  display: flex;
  gap: 12px;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
}
.adminUiScope .pager__info {
  opacity: 0.82;
  font-size: 14px;
}
.adminUiScope .userList {
  margin-top: 14px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 12px;
}
.adminUiScope .userCard {
  position: relative;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 18px;
  background: var(--theme-surface-soft);
  padding: 24px 16px 16px;
  overflow: visible;
}
.adminUiScope .userCard__head {
  display: block;
}
.adminUiScope .userCard__identityMeta {
  min-width: 0;
}
.adminUiScope .userCard__identity {
  display: flex;
  gap: 12px;
  align-items: center;
  min-width: 0;
}
.adminUiScope .userAvatarWrap {
  position: relative;
  width: 56px;
  height: 56px;
  flex: 0 0 auto;
}
.adminUiScope .userCard__title {
  font-weight: 900;
}
.adminUiScope .userCard__meta {
  margin-top: 4px;
  opacity: 0.72;
  font-size: 13px;
}
.adminUiScope .userAvatar {
  width: 56px;
  height: 56px;
  display: grid;
  place-items: center;
  border-radius: 999px;
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: rgba(96, 165, 250, 0.18);
}
.adminUiScope .userAvatarButton {
  position: relative;
  padding: 0;
  cursor: pointer;
}
.adminUiScope .userAvatarButton:disabled {
  cursor: wait;
}
.adminUiScope .userAvatarRemoveButton {
  position: absolute;
  top: -4px;
  right: -4px;
  width: 24px;
  height: 24px;
  display: grid;
  place-items: center;
  padding: 0;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: rgba(10, 14, 22, 0.96);
  box-shadow: 0 10px 22px rgba(0, 0, 0, 0.3);
  cursor: pointer;
  z-index: 2;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(2px) scale(0.96);
  transition: opacity 160ms ease, transform 160ms ease, background 160ms ease, visibility 160ms ease;
}
.adminUiScope .userAvatarRemoveIcon {
  color: rgba(255, 255, 255, 0.96);
}
.adminUiScope .userAvatarRemoveButton:disabled {
  opacity: 0.45;
  cursor: wait;
}
.adminUiScope .userAvatarRemoveButton:hover {
  background: rgba(255, 255, 255, 0.12);
}
.adminUiScope .userAvatarButton__overlay {
  position: absolute;
  inset: auto 0 0 0;
  padding: 10px 0 6px;
  background: linear-gradient(180deg, rgba(7, 10, 18, 0), rgba(7, 10, 18, 0.88));
  color: var(--theme-text);
  font-size: 10px;
  font-weight: 800;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 160ms ease, transform 160ms ease;
}
.adminUiScope .userAvatarWrap:hover .userAvatarButton__overlay,
.adminUiScope .userAvatarWrap:focus-within .userAvatarButton__overlay {
  opacity: 1;
  transform: translateY(0);
}
.adminUiScope .userAvatarWrap:hover .userAvatarRemoveButton,
.adminUiScope .userAvatarWrap:focus-within .userAvatarRemoveButton {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translateY(0) scale(1);
}
.adminUiScope .userAvatar__image {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.adminUiScope .userAvatar__fallback {
  font-size: 18px;
  font-weight: 900;
}
.adminUiScope .userInfoList {
  margin-top: 14px;
  display: grid;
  gap: 8px;
}
.adminUiScope .userInfoLine {
  display: flex;
  gap: 12px;
  justify-content: space-between;
  align-items: baseline;
  padding-bottom: 8px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.adminUiScope .userInfoLine span {
  font-size: 12px;
  color: var(--theme-text-soft);
}
.adminUiScope .userInfoLine strong {
  min-width: 0;
  text-align: right;
  font-size: 14px;
  font-weight: 900;
}
.adminUiScope .field {
  display: grid;
  gap: 8px;
}
.adminUiScope .field__label {
  font-size: 13px;
  color: var(--theme-text-soft);
}
.adminUiScope .field__input {
  width: 100%;
  padding: 14px 0;
  border: 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.12);
  background: transparent;
  color: var(--theme-text-strong);
  outline: none;
  font-size: 18px;
  letter-spacing: -0.02em;
}
.adminUiScope .field__input:focus {
  border-bottom-color: rgba(96, 165, 250, 0.9);
}
.adminUiScope .field__hint {
  font-size: 12px;
  color: var(--theme-text-faint);
}

.adminUiScope .userEditForm {
  display: grid;
  gap: 18px;
}
.adminUiScope .userEditForm .field {
  gap: 10px;
}
.adminUiScope .modalCard--userEdit {
  max-width: 520px;
}

.adminUiScope .userCard__actions {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}
.adminUiScope .userCard__actions--compact {
  grid-template-columns: auto auto minmax(0, 1fr);
  align-items: center;
  margin-top: 12px;
}
.adminUiScope .roleBadge {
  width: fit-content;
  padding: 6px 10px;
  border-radius: 999px;
  border: 1px solid rgba(96, 165, 250, 0.28);
  background: rgba(96, 165, 250, 0.1);
  color: color-mix(in srgb, var(--theme-accent-bg) 80%, white);
  font-size: 12px;
  font-weight: 700;
}
.adminUiScope .userCard__roleBadge {
  position: absolute;
  top: 6px;
  right: 6px;
  z-index: 2;
  box-shadow: 0 10px 24px rgba(7, 10, 18, 0.28);
}
.adminUiScope .iconActionButton {
  width: 42px;
  height: 42px;
  display: grid;
  place-items: center;
  padding: 0;
  border-radius: 14px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  background: var(--theme-surface-soft);
  cursor: pointer;
}
.adminUiScope .iconActionButton__icon {
  color: var(--theme-text);
}
.adminUiScope .iconActionButton:disabled {
  cursor: not-allowed;
  opacity: 0.45;
}
.adminUiScope .iconActionButton--danger {
  border-color: rgba(239, 68, 68, 0.24);
  background: rgba(239, 68, 68, 0.1);
}
.adminUiScope .userSaveButton:disabled {
  opacity: 0.4;
}
.adminUiScope .userRoleAction {
  width: fit-content;
  margin-top: 8px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--theme-text-faint);
  font-size: 9px;
  line-height: 1.4;
  letter-spacing: 0.01em;
  cursor: pointer;
}
.adminUiScope .userRoleAction:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}
.adminUiScope .templateRequestList {
  margin-top: 14px;
  display: grid;
  gap: 14px;
}
.adminUiScope .templateRequestCard {
}
.adminUiScope .templateRequestCard--aligned {
  align-items: start;
}
.adminUiScope .templateRequestCard__side {
  display: grid;
  gap: 12px;
  align-self: start;
  align-content: start;
}
.adminUiScope .templateRequestCard__preview {
  align-self: start;
  display: block;
  width: 100%;
  line-height: 0;
}
.adminUiScope .templateRequestCard__thumbMeta {
  display: grid;
  gap: 10px;
  padding: 14px;
  border-radius: 18px;
  border: 1px solid var(--theme-border);
  background: var(--theme-surface-soft);
}
.adminUiScope .templateRequestField {
  display: grid;
  gap: 8px;
}
.adminUiScope .templateRequestField__label {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--theme-text-faint);
}
.adminUiScope .templateRequestCard__thumbLabel {
  font-size: 11px;
  color: var(--theme-text-faint);
}
.adminUiScope .templateRequestCard__thumbValue {
  font-size: 14px;
  font-weight: 800;
  color: var(--theme-text);
  word-break: break-word;
}
.adminUiScope .templateRequestCard__items {
  grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
}
.adminUiScope .templateRequestCard__footer {
  display: flex;
  gap: 12px;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
}
.adminUiScope .templateRequestCard__footerLeft {
  display: flex;
  gap: 10px;
  align-items: center;
}
.adminUiScope .templateRequestCard__actions {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
  flex-wrap: wrap;
}
.adminUiScope .requestPreview {
  display: grid;
}
.adminUiScope .requestPreview__sheet {
  display: grid;
  gap: 16px;
  width: 100%;
  max-width: 1280px;
  margin: 0 auto;
  padding: 28px;
  border-radius: 24px;
  border: 1px solid var(--theme-border);
  background: color-mix(in srgb, var(--theme-main-bg) 92%, transparent);
  max-height: min(78vh, 980px);
  overflow: auto;
  overscroll-behavior: contain;
}
.adminUiScope .requestPreview__title {
  font-size: 28px;
  font-weight: 900;
  letter-spacing: -0.03em;
}
.adminUiScope .requestPreview__description {
  margin-top: -8px;
  font-size: 14px;
  line-height: 1.6;
  color: var(--theme-text-muted);
}
.adminUiScope .requestPreview__meta {
  color: var(--theme-text-soft);
  font-size: 13px;
}
.adminUiScope .requestPreview__columns {
  display: grid;
  grid-template-columns: 132px 1fr;
  gap: 10px;
  margin-bottom: 10px;
}
.adminUiScope .requestPreview__columnsSpacer {
  min-width: 0;
}
.adminUiScope .requestPreview__columnsGrid {
  display: grid;
  gap: 10px;
}
.adminUiScope .requestPreview__columnHeader {
  min-height: 20px;
  font-size: 12px;
  font-weight: 800;
  text-align: center;
  opacity: 0.72;
}
.adminUiScope .requestPreview__rows {
  display: grid;
  gap: 10px;
}
.adminUiScope .requestPreview__row {
  display: grid;
  grid-template-columns: 132px 1fr;
  gap: 10px;
}
.adminUiScope .requestPreview__label {
  display: grid;
  place-items: center;
  padding: 10px 12px;
  text-align: center;
  font-weight: 900;
  border-radius: 14px;
  background: var(--theme-surface-soft-2);
  border: 1px solid var(--theme-border-strong);
}
.adminUiScope .requestPreview__dropGrid {
  display: grid;
  gap: 10px;
}
.adminUiScope .requestPreview__dropColumn {
  display: grid;
  gap: 8px;
}
.adminUiScope .requestPreview__drop {
  border-radius: 14px;
  background: var(--theme-pill-bg);
  border: 1px solid var(--theme-border);
  min-height: calc(var(--thumb-size, 80px) + 24px);
  padding: 10px;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-content: flex-start;
}
.adminUiScope .requestPreview__item {
  display: inline-flex;
  position: relative;
  overflow: hidden;
  border-radius: 16px;
}
.adminUiScope .requestPreview__item--muted {
  opacity: 0.52;
  filter: grayscale(0.22) brightness(0.78);
}
.adminUiScope .requestPreview__itemThumb {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  display: block;
}
.adminUiScope .requestPreview__itemLabel {
  position: absolute;
  left: 8px;
  right: 8px;
  bottom: 8px;
  padding: 4px 6px;
  border-radius: 8px;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.02), rgba(0, 0, 0, 0.7));
  font-size: 11px;
  font-weight: 700;
  text-align: center;
  line-height: 1.3;
}
.adminUiScope .requestPreview__pool {
  display: grid;
  gap: 10px;
  padding-top: 8px;
}
.adminUiScope .requestPreview__poolTitle {
  font-weight: 900;
  opacity: 0.82;
}
.adminUiScope .requestPreview__poolGrid {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.adminUiScope .requestPreview__poolItem {
  display: inline-flex;
  position: relative;
}
.adminUiScope .tierAdminList {
  margin-top: 14px;
  display: grid;
  gap: 14px;
}
.adminUiScope .tierAdminCard {
  display: grid;
  grid-template-columns: minmax(220px, 280px) minmax(0, 1fr);
  gap: 16px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 20px;
  background: var(--theme-pill-bg);
  padding: 16px;
}
.adminUiScope .tierAdminCard__preview {
  cursor: pointer;
  appearance: none;
  border: 0;
  padding: 0;
  background: transparent;
  text-align: left;
  align-self: start;
  display: block;
  width: 100%;
  line-height: 0;
}
.adminUiScope .tierAdminCard__thumb {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  object-position: top center;
  display: block;
  border-radius: 14px;
  background: var(--theme-surface-soft);
}
.adminUiScope .tierAdminCard__thumb--empty {
  background: linear-gradient(135deg, rgba(255,255,255,0.08), rgba(255,255,255,0.03));
}
.adminUiScope .tierAdminCard__body {
  min-width: 0;
  display: grid;
  gap: 14px;
  position: relative;
}
.adminUiScope .tierAdminCard__head {
  display: flex;
  gap: 12px;
  justify-content: space-between;
  align-items: flex-start;
}
.adminUiScope .templateRequestCard__cornerBadge {
  position: absolute;
  top: 0;
  right: 0;
}
.adminUiScope .tierAdminCard__title {
  font-size: 18px;
  font-weight: 900;
  padding-right: 132px;
}
.adminUiScope .tierAdminCard__desc {
  margin-top: 6px;
  color: var(--theme-text-muted);
  line-height: 1.5;
  display: -webkit-box;
  line-clamp: 2;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.adminUiScope .tierAdminCard__meta {
  margin-top: 4px;
  opacity: 0.74;
  font-size: 13px;
  word-break: break-word;
}
.adminUiScope .tierAdminCard__stats {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.adminUiScope .tierAdminHeaderStats {
  margin-top: 10px;
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.adminUiScope .pill {
  display: inline-flex;
  align-items: center;
  padding: 7px 10px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  background: var(--theme-surface-soft);
  color: var(--theme-text);
  font-size: 12px;
  font-weight: 800;
}
.adminUiScope .pill--create {
  border-color: rgba(56, 189, 248, 0.36);
  background: rgba(56, 189, 248, 0.16);
  color: rgba(224, 242, 254, 0.98);
}
.adminUiScope .pill--owned {
  border-color: rgba(167, 139, 250, 0.34);
  background: rgba(167, 139, 250, 0.14);
  color: rgba(243, 232, 255, 0.98);
}
.adminUiScope .pill--requestItem {
  border-color: rgba(250, 204, 21, 0.34);
  background: rgba(250, 204, 21, 0.14);
  color: rgba(254, 249, 195, 0.98);
}
.adminUiScope .pill--directFile {
  border-color: rgba(52, 211, 153, 0.34);
  background: rgba(52, 211, 153, 0.14);
  color: rgba(209, 250, 229, 0.98);
}
.adminUiScope .pill--accent {
  border-color: rgba(251, 191, 36, 0.32);
  background: rgba(251, 191, 36, 0.12);
  color: rgba(253, 230, 138, 0.96);
}
.adminUiScope .pill--public {
  border-color: rgba(52, 211, 153, 0.34);
  background: rgba(52, 211, 153, 0.14);
  color: rgba(209, 250, 229, 0.98);
}
.adminUiScope .pill--private {
  border-color: rgba(251, 191, 36, 0.32);
  background: rgba(251, 191, 36, 0.12);
  color: rgba(253, 230, 138, 0.96);
}
.adminUiScope .pill--link {
  color: var(--theme-text);
  cursor: pointer;
  transition: background 160ms ease, border-color 160ms ease, transform 160ms ease, color 160ms ease, box-shadow 160ms ease;
}
.adminUiScope .pill--link:hover {
  color: var(--theme-text-strong);
  border-color: rgba(96, 165, 250, 0.4);
  background: color-mix(in srgb, var(--theme-surface-soft) 76%, rgba(96, 165, 250, 0.2));
  box-shadow: 0 10px 22px rgba(0, 0, 0, 0.18);
  transform: translateY(-1px);
}
.adminUiScope .pill--link:focus-visible {
  outline: 2px solid rgba(96, 165, 250, 0.42);
  outline-offset: 2px;
}
.adminUiScope .tierAdminSection {
  display: grid;
  gap: 10px;
  padding: 14px;
  border-radius: 16px;
  border: 1px solid var(--theme-border);
  background: var(--theme-pill-bg);
}
.adminUiScope .tierAdminSection__title {
  font-weight: 800;
}
.adminUiScope .tierAdminSection__actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}
.adminUiScope .tierAdminItemList {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
  gap: 10px;
}
.adminUiScope .tierAdminItem {
  display: grid;
  gap: 8px;
  justify-items: center;
  padding: 12px 10px;
  border-radius: 14px;
  border: 1px solid var(--theme-border);
  background: var(--theme-pill-bg);
  color: var(--theme-text);
  cursor: pointer;
  text-align: center;
  min-width: 0;
}
.adminUiScope .tierAdminItem__thumb {
  width: min(100%, 72px);
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: 12px;
}
.adminUiScope .tierAdminItem__title {
  width: 100%;
  font-weight: 800;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px;
}
.adminUiScope .modalOverlay {
  position: fixed;
  inset: 0;
  z-index: 20;
  display: grid;
  place-items: center;
  padding: 20px;
  background: color-mix(in srgb, var(--theme-body-bg) 76%, transparent);
  backdrop-filter: blur(6px);
  overscroll-behavior: contain;
}
.adminUiScope .modalCard {
  width: min(560px, 100%);
  display: grid;
  gap: 14px;
  border-radius: 24px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  background: color-mix(in srgb, var(--theme-main-bg) 96%, transparent);
}
.adminUiScope .modalCard:not(.modalCard--customItem) {
  padding: 20px;
}
.adminUiScope .modalCard.modalCard--customItem {
  gap: 0;
  padding: 0;
}
.adminUiScope .modalCard--preview {
  width: min(1200px, 100%);
  max-height: calc(100dvh - 40px);
  overflow: auto;
  overscroll-behavior: contain;
}
.adminUiScope .modalCard__titleRow {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
}
.adminUiScope .modalCard__title {
  font-size: 18px;
  font-weight: 900;
}
.adminUiScope .modalCard__desc {
  opacity: 0.78;
  line-height: 1.5;
}
.adminUiScope .modalCard__form {
  display: grid;
  gap: 10px;
}
.adminUiScope .modalCard__actions {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 8px;
}
.adminUiScope .previewFrame {
  width: 100%;
  min-height: min(80vh, 820px);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 16px;
  background: var(--theme-pill-bg);
}
.adminUiScope .importModeTabs {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}
.adminUiScope .checkRow {
  margin-top: 12px;
  display: inline-flex;
  gap: 8px;
  align-items: center;
  opacity: 0.88;
}
.adminUiScope .toggleSwitch {
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 12px;
  border-radius: 14px;
  border: 1px solid var(--theme-border);
  background: var(--theme-pill-bg);
  cursor: pointer;
  user-select: none;
}
.adminUiScope .toggleSwitch input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.adminUiScope .toggleSwitch__track {
  position: relative;
  width: 42px;
  height: 24px;
  border-radius: 999px;
  background: var(--theme-surface-soft-3);
  border: 1px solid var(--theme-border-strong);
  transition: background 180ms ease, border-color 180ms ease;
  flex: 0 0 auto;
}
.adminUiScope .toggleSwitch__thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 18px;
  height: 18px;
  border-radius: 999px;
  background: var(--theme-text-strong);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.24);
  transition: transform 180ms ease;
}
.adminUiScope .toggleSwitch__label {
  font-weight: 800;
  color: var(--theme-text);
}
.adminUiScope .toggleSwitch input:checked ~ .toggleSwitch__track {
  background: rgba(96, 165, 250, 0.34);
  border-color: rgba(96, 165, 250, 0.42);
}
.adminUiScope .toggleSwitch input:checked ~ .toggleSwitch__track .toggleSwitch__thumb {
  transform: translateX(18px);
}
.adminUiScope .toggleSwitch--disabled {
  opacity: 0.55;
  pointer-events: none;
}
.adminUiScope .checkRow--compact {
  margin-top: 0;
}
.adminUiScope .checkRow--toolbar {
  margin-top: 0;
}
@media (max-width: 980px) {
  .adminUiScope .adminHero__stats {
    grid-template-columns: 1fr;
  }
  .adminUiScope .customItemModal {
    grid-template-columns: 1fr;
    min-height: auto;
  }
  .adminUiScope .requestPreview__summary,
  .adminUiScope .requestPreview__frame {
    padding: 18px;
    gap: 18px;
  }
  .adminUiScope .requestPreview__boardHead,
  .adminUiScope .requestPreview__row {
    grid-template-columns: 1fr;
  }
  .adminUiScope .modalCard--customItem {
    width: min(100%, calc(100vw - 24px));
    min-width: 0;
    height: min(100%, calc(100dvh - 24px));
  }
  .adminUiScope .customItemModal__pickerPanel {
    border-right: 0;
    border-bottom: 1px solid var(--theme-border);
    padding: 20px 18px;
  }
  .adminUiScope .customItemModal__body {
    min-height: 0;
    padding: 20px 18px 18px;
  }
  .adminUiScope .customItemModal__content {
    min-height: 0;
  }
  .adminUiScope .customItemModal__labelEditor {
    grid-template-columns: 1fr;
  }
  .adminUiScope .customItemModal__actions {
    grid-template-columns: 1fr;
  }
  .adminUiScope .modalCard__form--search {
    grid-template-columns: 1fr;
  }
  .adminUiScope .templateSettingsCard__actions > .btn,
  .adminUiScope .templateSettingsCard__actions > a.btn {
    width: 100%;
  }
  .adminUiScope.adminSidebar {
    display: none;
  }
  .adminUiScope .featuredOrderPanel,
  .adminUiScope .section--topGrid,
  .adminUiScope .templateManagerGrid,
  .adminUiScope .templateSettingsCard,
  .adminUiScope .toolbar,
  .adminUiScope .itemComposer,
  .adminUiScope .tierAdminCard,
  .adminUiScope .templateRequestCard__form,
  .adminUiScope .toolbar--secondary {
    grid-template-columns: 1fr;
  }
  .adminUiScope .itemPreviewCard {
    max-width: none;
  }
  .adminUiScope .itemDraftList {
    grid-template-columns: 1fr;
  }
  .adminUiScope .userCard__identity {
    width: 100%;
  }
  .adminUiScope .userInfoLine {
    display: grid;
    gap: 4px;
  }
  .adminUiScope .userInfoLine strong {
    text-align: left;
  }
  .adminUiScope .userCard__actions--compact {
    grid-template-columns: repeat(3, minmax(0, auto));
  }
  .adminUiScope .userSaveButton {
    width: 100%;
  }
}


@media (max-width: 640px) {
  .adminUiScope .adminHero {
    padding: 16px;
  }
  .adminUiScope .adminHero__title {
    font-size: 24px;
  }
  .adminUiScope .thumbGrid,
  .adminUiScope .userList {
    grid-template-columns: 1fr;
  }
  .adminUiScope .customItemGrid {
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  }
  .adminUiScope .tierAdminCard__head {
    display: grid;
  }
  .adminUiScope .customItemCard {
    align-items: stretch;
    padding: 10px;
  }
  .adminUiScope .customItemCard__image {
    width: 100%;
    aspect-ratio: 1 / 1;
  }
}
</style>
