<script setup>
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
import Sortable from 'sortablejs'
import { api } from '../lib/api'
import { toApiUrl } from '../lib/runtime'
import { useAuthStore } from '../stores/auth'

const auth = useAuthStore()
const isAdmin = computed(() => !!auth.user?.isAdmin)

const activeTab = ref('games')
const gameMode = ref('existing')

const games = ref([])
const selectedGameId = ref('')
const selectedGame = ref(null)
const featuredGameIds = ref([])

const customItems = ref([])
const customItemQuery = ref('')
const customItemPage = ref(1)
const customItemLimit = ref(50)
const customItemTotal = ref(0)
const customItemOrphanOnly = ref(false)

const users = ref([])

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

const newGameId = ref('')
const newGameName = ref('')

const uploadLabel = ref('')
const uploadFile = ref(null)
const thumbFile = ref(null)
const itemPreviewUrl = ref('')
const thumbPreviewUrl = ref('')
const itemFileInput = ref(null)
const thumbFileInput = ref(null)
const featuredListEl = ref(null)
const featuredSortable = ref(null)

const hasSelectedGame = computed(() => !!selectedGame.value?.game?.id)
const canApplyThumbnail = computed(() => !!thumbFile.value && !!selectedGameId.value)
const canAddItem = computed(() => !!uploadFile.value && !!selectedGameId.value)
const customItemPageCount = computed(() => Math.max(1, Math.ceil(customItemTotal.value / customItemLimit.value)))
const featuredGames = computed(() =>
  featuredGameIds.value
    .map((gameId) => games.value.find((game) => game.id === gameId))
    .filter(Boolean)
)
const availableGamesForFeatured = computed(() => games.value.filter((game) => !featuredGameIds.value.includes(game.id)))

onMounted(async () => {
  await auth.refresh()
  await Promise.all([refreshGames(), refreshCustomItems(), refreshUsers()])
  await syncFeaturedSortable()
})

onUnmounted(() => {
  clearPreviewUrl('item')
  clearPreviewUrl('thumb')
  destroyFeaturedSortable()
})

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

function setTab(tab) {
  resetMessages()
  activeTab.value = tab
}

async function refreshGames() {
  try {
    const data = await api.listGames()
    games.value = data.games || []
    featuredGameIds.value = games.value
      .filter((game) => game.displayRank != null)
      .sort((a, b) => a.displayRank - b.displayRank)
      .map((game) => game.id)
    await syncFeaturedSortable()
  } catch (e) {
    error.value = '게임 목록을 불러오지 못했어요.'
  }
}

function destroyFeaturedSortable() {
  if (featuredSortable.value) {
    featuredSortable.value.destroy()
    featuredSortable.value = null
  }
}

async function syncFeaturedSortable() {
  await nextTick()
  destroyFeaturedSortable()
  if (!featuredListEl.value) return

  featuredSortable.value = Sortable.create(featuredListEl.value, {
    animation: 160,
    draggable: '[data-featured-id]',
    handle: '[data-featured-handle]',
    ghostClass: 'ghost',
    chosenClass: 'chosen',
    onEnd: (evt) => {
      if (evt.oldIndex == null || evt.newIndex == null || evt.oldIndex === evt.newIndex) return
      const nextIds = [...featuredGameIds.value]
      const [moved] = nextIds.splice(evt.oldIndex, 1)
      nextIds.splice(evt.newIndex, 0, moved)
      featuredGameIds.value = nextIds
    },
  })
}

async function refreshCustomItems() {
  if (!auth.user?.isAdmin) return
  try {
    const data = await api.listAdminCustomItems({
      q: customItemQuery.value,
      page: customItemPage.value,
      limit: customItemLimit.value,
      orphanOnly: customItemOrphanOnly.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 refreshUsers() {
  if (!auth.user?.isAdmin) return
  try {
    const data = await api.listAdminUsers()
    users.value = (data.users || []).map((user) => ({
      ...user,
      draftEmail: user.email,
      draftNickname: user.nickname || '',
      draftIsAdmin: !!user.isAdmin,
      draftPassword: '',
    }))
  } catch (e) {
    error.value = '회원 목록을 불러오지 못했어요.'
  }
}

function resetUploadState() {
  uploadLabel.value = ''
  uploadFile.value = null
  thumbFile.value = null
  resetFileInput('item')
  resetFileInput('thumb')
  clearPreviewUrl('item')
  clearPreviewUrl('thumb')
}

function setGameMode(mode) {
  resetMessages()
  gameMode.value = mode
  selectedGameId.value = ''
  selectedGame.value = null
  newGameId.value = ''
  newGameName.value = ''
  resetUploadState()
}

function clearPreviewUrl(type) {
  if (type === 'item' && itemPreviewUrl.value) {
    URL.revokeObjectURL(itemPreviewUrl.value)
    itemPreviewUrl.value = ''
  }
  if (type === 'thumb' && thumbPreviewUrl.value) {
    URL.revokeObjectURL(thumbPreviewUrl.value)
    thumbPreviewUrl.value = ''
  }
}

function resetFileInput(type) {
  if (type === 'item' && itemFileInput.value) itemFileInput.value.value = ''
  if (type === 'thumb' && thumbFileInput.value) thumbFileInput.value.value = ''
}

async function loadGame() {
  resetMessages()
  resetUploadState()

  if (!selectedGameId.value) {
    selectedGame.value = null
    return
  }

  try {
    const data = await api.getGame(selectedGameId.value)
    selectedGame.value = data
  } catch (e) {
    error.value = '게임 정보를 불러오지 못했어요.'
  }
}

async function createGame() {
  resetMessages()
  try {
    const res = await fetch(toApiUrl('/api/admin/games'), {
      method: 'POST',
      credentials: 'include',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ id: newGameId.value, name: newGameName.value }),
    })
    if (!res.ok) throw new Error('failed')

    const data = await res.json()
    await refreshGames()
    gameMode.value = 'existing'
    selectedGameId.value = data.game.id
    await loadGame()
    success.value = '게임이 생성됐어요. 이어서 썸네일과 아이템을 관리할 수 있어요.'
  } catch (e) {
    error.value = '게임 생성 실패(관리자 권한/중복 ID 확인)'
  }
}

function onThumb(event) {
  thumbFile.value = event.target.files && event.target.files[0] ? event.target.files[0] : null
  clearPreviewUrl('thumb')
  if (thumbFile.value) thumbPreviewUrl.value = URL.createObjectURL(thumbFile.value)
}

function onFile(event) {
  uploadFile.value = event.target.files && event.target.files[0] ? event.target.files[0] : null
  clearPreviewUrl('item')
  if (uploadFile.value) itemPreviewUrl.value = URL.createObjectURL(uploadFile.value)
}

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

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

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

async function uploadItem() {
  resetMessages()
  if (!uploadFile.value || !selectedGameId.value) {
    error.value = '아이템 파일을 선택해주세요.'
    return
  }

  try {
    const fd = new FormData()
    fd.append('label', uploadLabel.value || 'item')
    fd.append('image', uploadFile.value)
    const res = await fetch(toApiUrl(`/api/admin/games/${encodeURIComponent(selectedGameId.value)}/images`), {
      method: 'POST',
      credentials: 'include',
      body: fd,
    })
    if (!res.ok) throw new Error('failed')

    resetUploadState()
    await loadGame()
    success.value = '게임 기본 아이템이 추가됐어요.'
  } catch (e) {
    error.value = '아이템 추가 실패(관리자 권한/파일 크기 확인)'
  }
}

async function removeGameItem(itemId) {
  resetMessages()
  try {
    const res = await fetch(
      toApiUrl(`/api/admin/games/${encodeURIComponent(selectedGameId.value)}/items/${encodeURIComponent(itemId)}`),
      {
        method: 'DELETE',
        credentials: 'include',
      }
    )
    if (!res.ok) throw new Error('failed')

    await loadGame()
    success.value = '게임 기본 아이템을 삭제했어요.'
  } catch (e) {
    error.value = '게임 기본 아이템 삭제에 실패했어요.'
  }
}

async function removeGame() {
  resetMessages()
  if (!selectedGameId.value || !selectedGame.value?.game) return

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

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

    const deletedName = selectedGame.value.game.name
    selectedGameId.value = ''
    selectedGame.value = null
    resetUploadState()
    await refreshGames()
    success.value = `${deletedName} 게임을 삭제했어요.`
  } catch (e) {
    error.value = '게임 삭제에 실패했어요.'
  }
}

async function saveUser(user) {
  resetMessages()
  try {
    const data = await api.updateAdminUser(user.id, {
      email: user.draftEmail,
      nickname: user.draftNickname,
      isAdmin: !!user.draftIsAdmin,
    })
    const updated = data.user
    users.value = users.value.map((entry) =>
      entry.id === updated.id
        ? { ...entry, ...updated, draftEmail: updated.email, draftNickname: updated.nickname || '', draftIsAdmin: !!updated.isAdmin }
        : entry
    )
    success.value = '회원 정보를 저장했어요.'
  } catch (e) {
    error.value = '회원 정보 저장에 실패했어요.'
  }
}

async function resetUserPassword(user) {
  resetMessages()
  if (!(user.draftPassword || '').trim()) {
    error.value = '새 비밀번호를 입력해주세요.'
    return
  }

  try {
    await api.updateAdminUserPassword(user.id, { password: user.draftPassword })
    user.draftPassword = ''
    success.value = '비밀번호를 초기화했어요.'
  } catch (e) {
    error.value = '비밀번호 초기화에 실패했어요.'
  }
}

async function removeUser(user) {
  resetMessages()
  if (user.id === auth.user?.id) {
    error.value = '현재 로그인한 관리자 계정은 직접 삭제할 수 없어요.'
    return
  }

  const ok = window.confirm(`${user.email} 계정을 삭제할까요? 작성한 티어표와 커스텀 이미지도 함께 삭제됩니다.`)
  if (!ok) return

  try {
    await api.deleteAdminUser(user.id)
    users.value = users.value.filter((entry) => entry.id !== user.id)
    success.value = '회원 계정을 삭제했어요.'
  } catch (e) {
    error.value = '회원 삭제에 실패했어요.'
  }
}

function submitCustomItemSearch() {
  customItemPage.value = 1
  refreshCustomItems()
}

function toggleCustomItemOrphanOnly() {
  customItemPage.value = 1
  refreshCustomItems()
}

function changeCustomItemLimit(limit) {
  customItemLimit.value = limit
  customItemPage.value = 1
  refreshCustomItems()
}

function moveCustomItemPage(direction) {
  const nextPage = customItemPage.value + direction
  if (nextPage < 1 || nextPage > customItemPageCount.value) return
  customItemPage.value = nextPage
  refreshCustomItems()
}

async function removeCustomItem(item) {
  resetMessages()
  if (item.usageCount > 0) {
    error.value = '사용 중인 커스텀 이미지는 먼저 참조를 정리해야 삭제할 수 있어요.'
    return
  }

  const ok = window.confirm(`"${item.label}" 미사용 커스텀 이미지를 삭제할까요?`)
  if (!ok) return

  try {
    await api.deleteAdminCustomItem(item.id)
    await refreshCustomItems()
    success.value = '미사용 커스텀 이미지를 삭제했어요.'
  } catch (e) {
    error.value = '커스텀 이미지 삭제에 실패했어요.'
  }
}

async function removeUnusedCustomItems() {
  resetMessages()
  const ok = window.confirm('현재 검색 조건에 맞는 미사용 커스텀 이미지를 모두 삭제할까요?')
  if (!ok) return

  try {
    const data = await api.deleteAdminUnusedCustomItems({ q: customItemQuery.value })
    await refreshCustomItems()
    success.value = `${data.deletedCount || 0}개의 미사용 커스텀 이미지를 삭제했어요.`
  } catch (e) {
    error.value = '미사용 커스텀 이미지 일괄 삭제에 실패했어요.'
  }
}

const displayThumbnailUrl = computed(() => {
  if (thumbPreviewUrl.value) return thumbPreviewUrl.value
  if (selectedGame.value?.game?.thumbnailSrc) return toApiUrl(selectedGame.value.game.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 addFeaturedGame(gameId) {
  resetMessages()
  if (!gameId || featuredGameIds.value.includes(gameId)) return
  if (featuredGameIds.value.length >= 50) {
    error.value = '상단 고정 게임은 최대 50개까지만 설정할 수 있어요.'
    return
  }
  featuredGameIds.value = [...featuredGameIds.value, gameId]
  syncFeaturedSortable()
}

function removeFeaturedGame(gameId) {
  resetMessages()
  featuredGameIds.value = featuredGameIds.value.filter((id) => id !== gameId)
  syncFeaturedSortable()
}

function moveFeaturedGame(gameId, direction) {
  const currentIndex = featuredGameIds.value.indexOf(gameId)
  const nextIndex = currentIndex + direction
  if (currentIndex < 0 || nextIndex < 0 || nextIndex >= featuredGameIds.value.length) return
  const nextIds = [...featuredGameIds.value]
  const [moved] = nextIds.splice(currentIndex, 1)
  nextIds.splice(nextIndex, 0, moved)
  featuredGameIds.value = nextIds
  syncFeaturedSortable()
}

async function saveFeaturedOrder() {
  resetMessages()
  try {
    const data = await api.updateAdminGameDisplayOrder({ gameIds: featuredGameIds.value })
    games.value = data.games || []
    featuredGameIds.value = games.value
      .filter((game) => game.displayRank != null)
      .sort((a, b) => a.displayRank - b.displayRank)
      .map((game) => game.id)
    success.value = '홈 화면 게임 순서를 저장했어요.'
  } catch (e) {
    error.value = '게임 순서 저장에 실패했어요.'
  }
}
</script>

<template>
  <section class="wrap">
    <!-- <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="tabs">
          <button class="tab" :class="{ 'tab--active': activeTab === 'games' }" @click="setTab('games')">게임 관리</button>
          <button class="tab" :class="{ 'tab--active': activeTab === 'items' }" @click="setTab('items')">아이템 관리</button>
          <button class="tab" :class="{ 'tab--active': activeTab === 'users' }" @click="setTab('users')">회원 관리</button>
        </div>

        <div v-if="error" class="error">{{ error }}</div>
        <div v-if="success" class="success">{{ success }}</div>

        <template v-if="activeTab === 'games'">
          <div class="panel">
            <div class="sectionHeader">
              <div>
                <div class="panel__title">홈 화면 상단 고정 순서</div>
                <div class="hint hint--tight">여기에 넣은 게임은 지정한 순서대로 먼저 노출되고, 나머지 게임은 최근 생성순으로 뒤에 이어집니다. 최대 50개까지 설정할 수 있어요.</div>
              </div>
              <button class="btn btn--primary" @click="saveFeaturedOrder">순서 저장</button>
            </div>

            <div class="featuredOrderPanel">
              <div class="featuredOrderPanel__list">
                <div class="section__title">상단 고정 목록</div>
                <div v-if="!featuredGames.length" class="hint">아직 상단 고정 게임이 없어요.</div>
                <div v-else ref="featuredListEl" class="featuredList">
                  <article v-for="(game, index) in featuredGames" :key="game.id" class="featuredCard" :data-featured-id="game.id">
                    <div class="featuredCard__meta">
                      <span class="featuredCard__rank">{{ index + 1 }}</span>
                      <div>
                        <div class="featuredCard__title">{{ game.name }}</div>
                        <div class="featuredCard__id">{{ game.id }}</div>
                      </div>
                    </div>
                    <div class="featuredCard__actions">
                      <button class="btn btn--ghost btn--small" data-featured-handle>드래그</button>
                      <button class="btn btn--ghost btn--small" :disabled="index === 0" @click="moveFeaturedGame(game.id, -1)">위로</button>
                      <button class="btn btn--ghost btn--small" :disabled="index === featuredGames.length - 1" @click="moveFeaturedGame(game.id, 1)">아래로</button>
                      <button class="btn btn--danger btn--small" @click="removeFeaturedGame(game.id)">제외</button>
                    </div>
                  </article>
                </div>
              </div>

              <div class="featuredOrderPanel__picker">
                <div class="section__title">게임 추가</div>
                <div class="featuredPickerList">
                  <button
                    v-for="game in availableGamesForFeatured"
                    :key="game.id"
                    class="featuredPickerItem"
                    :disabled="featuredGameIds.length >= 50"
                    @click="addFeaturedGame(game.id)"
                  >
                    <span>{{ game.name }}</span>
                    <span class="featuredPickerItem__id">{{ game.id }}</span>
                  </button>
                </div>
              </div>
            </div>
          </div>

          <div class="modeTabs">
            <button class="modeTab" :class="{ 'modeTab--active': gameMode === 'existing' }" @click="setGameMode('existing')">
              등록된 게임 선택
            </button>
            <button class="modeTab" :class="{ 'modeTab--active': gameMode === 'new' }" @click="setGameMode('new')">
              새 게임 추가
            </button>
          </div>

          <div class="panel panel--compact">
            <template v-if="gameMode === 'existing'">
              <!-- <div class="panel__title">등록된 게임 선택</div> -->
              <select v-model="selectedGameId" class="select" @change="loadGame">
                <option value="">게임을 선택해주세요</option>
                <option v-for="game in games" :key="game.id" :value="game.id">{{ game.name }} ({{ game.id }})</option>
              </select>
              <!-- <div class="hint">이 영역은 게임 자체와 관리자 기본 아이템만 관리합니다. 여기서 아이템을 삭제해도 사용자 커스텀 이미지는 삭제되지 않아요.</div> -->
            </template>

            <template v-else>
              <div class="panel__title">새 게임 정보 입력</div>
              <input v-model="newGameId" class="input" placeholder="game id (영문/숫자)" />
              <input v-model="newGameName" class="input" placeholder="게임 이름" />
              <button class="btn btn--primary" @click="createGame">게임 생성</button>
            </template>
          </div>

          <div v-if="hasSelectedGame" class="panel">
            <div class="detailHead">
              <div>
                <div class="panel__title">선택된 게임 정보</div>
                <div class="selectedGame__name">{{ selectedGame.game.name }}</div>
                <div class="selectedGame__id">{{ selectedGame.game.id }}</div>
              </div>
              <div class="detailHead__actions">
                <button class="btn btn--danger" @click="removeGame">게임 삭제</button>
              </div>
            </div>

            <div class="section section--topGrid">
              <section class="adminCard">
                <div class="section__title">썸네일 적용</div>
                <div class="uploadPreviewCard">
                  <img v-if="displayThumbnailUrl" class="selectedThumb" :src="displayThumbnailUrl" :alt="selectedGame.game.name" />
                  <div v-else class="selectedThumb selectedThumb--empty">썸네일 미리보기</div>
                </div>
                <div class="uploadControls">
                  <input ref="thumbFileInput" type="file" accept="image/*" class="inputFile" @change="onThumb" />
                  <button class="btn" :disabled="!canApplyThumbnail" @click="uploadThumbnail">썸네일 적용</button>
                </div>
              </section>

              <section class="adminCard">
                <div class="section__title">기본 아이템 추가</div>
                <div class="itemComposer">
                  <div class="itemComposer__form">
                    <input v-model="uploadLabel" class="input input--compact" placeholder="아이템 이름" />
                    <input ref="itemFileInput" type="file" accept="image/*" class="inputFile inputFile--tight" @change="onFile" />
                    <button class="btn" :disabled="!canAddItem" @click="uploadItem">아이템 추가</button>
                  </div>
                  <div class="itemPreviewCard">
                    <div class="itemPreviewFrame">
                      <img v-if="itemPreviewUrl" class="itemPreviewImage" :src="itemPreviewUrl" alt="item preview" />
                      <div v-else class="itemPreviewEmpty">이미지를 선택해주세요</div>
                    </div>
                    <div class="thumbLabel thumbLabel--preview">{{ uploadLabel || '아이템 이름 미리보기' }}</div>
                  </div>
                </div>
              </section>
            </div>

            <div class="section">
              <div class="section__title">현재 기본 아이템 목록</div>
              <div v-if="!selectedGame?.items?.length" class="hint">아직 등록된 기본 아이템이 없어요.</div>
              <div v-else class="thumbGrid">
                <div v-for="item in selectedGame.items" :key="item.id" class="thumbCard">
                  <img class="thumb thumb--game" :src="toApiUrl(item.src)" :alt="item.label" />
                  <div class="thumbLabel">{{ item.label }}</div>
                  <button class="btn btn--danger btn--small" @click="removeGameItem(item.id)">아이템 삭제</button>
                </div>
              </div>
            </div>
          </div>
        </template>

        <template v-else-if="activeTab === 'items'">
          <div class="panel">
            <div class="sectionHeader">
              <div>
                <div class="panel__title">사용자 커스텀 아이템 관리</div>
                <div class="hint hint--tight">사용자가 업로드한 이미지를 파일명/라벨 기준으로 검색하고, 한 번에 50개 또는 200개씩 페이지 형태로 확인할 수 있어요.</div>
              </div>
              <button class="btn btn--ghost" @click="refreshCustomItems">새로고침</button>
            </div>

            <div class="toolbar">
              <input v-model="customItemQuery" class="input toolbar__search" placeholder="파일명, 라벨, 업로더 검색" @keydown.enter.prevent="submitCustomItemSearch" />
              <button class="btn btn--ghost toolbar__button" @click="submitCustomItemSearch">검색</button>
              <select :value="customItemLimit" class="select toolbar__select" @change="changeCustomItemLimit(Number($event.target.value))">
                <option :value="50">50개씩 보기</option>
                <option :value="200">200개씩 보기</option>
              </select>
            </div>

            <div class="toolbar toolbar--secondary">
              <label class="checkRow checkRow--toolbar">
                <input v-model="customItemOrphanOnly" type="checkbox" @change="toggleCustomItemOrphanOnly" />
                <span>미사용 커스텀 이미지만 보기</span>
              </label>
              <button class="btn btn--danger toolbar__button" :disabled="!customItems.length" @click="removeUnusedCustomItems">
                미사용 이미지 일괄 삭제
              </button>
            </div>

            <div v-if="!customItems.length" class="hint">조건에 맞는 커스텀 아이템이 없어요.</div>
            <div v-else class="customItemGrid">
              <article v-for="item in customItems" :key="item.id" class="customItemCard">
                <img class="customItemCard__image" :src="toApiUrl(item.src)" :alt="item.label" />
                <div class="customItemCard__body">
                  <div class="customItemCard__title">{{ item.label }}</div>
                  <div class="customItemCard__meta">파일: {{ item.src.split('/').pop() }}</div>
                  <div class="customItemCard__meta">업로더: {{ item.ownerName }}</div>
                  <div class="customItemCard__meta">사용 중: {{ item.usageCount }}개 티어표</div>
                  <div class="customItemCard__meta">{{ fmt(item.createdAt) }}</div>
                  <div class="customItemCard__actions">
                    <a class="btn btn--small btn--ghost" :href="toApiUrl(item.src)" :download="item.label">이미지 다운로드</a>
                    <button class="btn btn--small btn--danger" :disabled="item.usageCount > 0" @click="removeCustomItem(item)">개별 삭제</button>
                  </div>
                </div>
              </article>
            </div>

            <div class="pager">
              <button class="btn btn--ghost" :disabled="customItemPage <= 1" @click="moveCustomItemPage(-1)">이전</button>
              <div class="pager__info">{{ customItemPage }} / {{ customItemPageCount }} 페이지 · 총 {{ customItemTotal }}개</div>
              <button class="btn btn--ghost" :disabled="customItemPage >= customItemPageCount" @click="moveCustomItemPage(1)">다음</button>
            </div>
          </div>
        </template>

        <template v-else>
          <div class="panel">
            <div class="sectionHeader">
              <div>
                <div class="panel__title">회원 관리</div>
                <div class="hint hint--tight">이메일, 닉네임, 관리자 권한을 수정하고 비밀번호도 직접 초기화할 수 있어요.</div>
              </div>
              <button class="btn btn--ghost" @click="refreshUsers">새로고침</button>
            </div>

            <div v-if="!users.length" class="hint">아직 가입한 회원이 없어요.</div>
            <div v-else class="userList">
              <article v-for="user in users" :key="user.id" class="userCard">
                <div class="userCard__head">
                  <div>
                    <div class="userCard__title">{{ user.nickname || '닉네임 없음' }}</div>
                    <div class="userCard__meta">{{ fmt(user.createdAt) }}</div>
                  </div>
                  <span class="roleBadge" :class="{ 'roleBadge--admin': user.draftIsAdmin }">
                    {{ user.draftIsAdmin ? '관리자' : '일반 회원' }}
                  </span>
                </div>

                <input v-model="user.draftEmail" class="input" placeholder="이메일" />
                <input v-model="user.draftNickname" class="input" placeholder="닉네임" />
                <label class="checkRow">
                  <input v-model="user.draftIsAdmin" type="checkbox" :disabled="user.id === auth.user?.id" />
                  <span>관리자 권한</span>
                </label>

                <div class="passwordBox">
                  <input v-model="user.draftPassword" class="input" type="text" placeholder="새 비밀번호 직접 입력" />
                  <button class="btn btn--ghost" @click="resetUserPassword(user)">비밀번호 초기화</button>
                </div>

                <div class="userCard__actions">
                  <button class="btn btn--ghost" @click="saveUser(user)">회원 저장</button>
                  <button class="btn btn--danger" :disabled="user.id === auth.user?.id" @click="removeUser(user)">회원 삭제</button>
                </div>
              </article>
            </div>
          </div>
        </template>
      </template>
    </div>
  </section>
</template>

<style scoped>
.wrap {
  padding: 10px 2px;
}
.title {
  margin: 0 0 10px;
  font-size: 26px;
  letter-spacing: -0.02em;
}
.card {
  border: 1px solid rgba(255, 255, 255, 0.12);
  background: rgba(255, 255, 255, 0.04);
  border-radius: 16px;
  padding: 14px;
}
.desc {
  opacity: 0.82;
  line-height: 1.5;
}
.warn,
.error,
.success {
  margin-top: 10px;
  padding: 10px 12px;
  border-radius: 12px;
}
.warn {
  border: 1px solid rgba(245, 158, 11, 0.35);
  background: rgba(245, 158, 11, 0.14);
}
.error {
  border: 1px solid rgba(239, 68, 68, 0.3);
  background: rgba(239, 68, 68, 0.12);
}
.success {
  border: 1px solid rgba(52, 211, 153, 0.32);
  background: rgba(52, 211, 153, 0.14);
}
.tabs,
.modeTabs {
  margin-top: 14px;
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}
.tab,
.modeTab {
  padding: 10px 14px;
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: rgba(255, 255, 255, 0.06);
  color: rgba(255, 255, 255, 0.92);
  cursor: pointer;
  font-weight: 800;
}
.tab--active,
.modeTab--active {
  background: rgba(96, 165, 250, 0.2);
}
.panel {
  margin-top: 14px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  background: rgba(0, 0, 0, 0.12);
  border-radius: 16px;
  padding: 14px;
}
.panel--compact {
  max-width: 480px;
}
.featuredOrderPanel {
  margin-top: 14px;
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(260px, 0.95fr);
  gap: 16px;
}
.featuredOrderPanel__list,
.featuredOrderPanel__picker {
  border: 1px solid rgba(255, 255, 255, 0.1);
  background: rgba(255, 255, 255, 0.03);
  border-radius: 16px;
  padding: 14px;
}
.featuredList,
.featuredPickerList {
  margin-top: 10px;
  display: grid;
  gap: 10px;
  max-height: 420px;
  overflow: auto;
}
.featuredCard {
  display: flex;
  gap: 12px;
  justify-content: space-between;
  align-items: center;
  padding: 12px;
  border-radius: 14px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  background: rgba(0, 0, 0, 0.18);
}
.featuredCard__meta {
  display: flex;
  gap: 12px;
  align-items: center;
  min-width: 0;
}
.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;
}
.featuredCard__title {
  font-weight: 900;
}
.featuredCard__id {
  margin-top: 4px;
  opacity: 0.68;
  font-size: 12px;
  word-break: break-all;
}
.featuredCard__actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: flex-end;
}
.featuredCard [data-featured-handle] {
  cursor: grab;
}
.featuredPickerItem {
  width: 100%;
  display: flex;
  gap: 10px;
  justify-content: space-between;
  align-items: center;
  padding: 12px;
  border-radius: 14px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  background: rgba(0, 0, 0, 0.16);
  color: rgba(255, 255, 255, 0.92);
  cursor: pointer;
  text-align: left;
}
.featuredPickerItem:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}
.featuredPickerItem__id {
  opacity: 0.64;
  font-size: 12px;
}
.panel__title,
.section__title {
  font-weight: 900;
}
.section {
  margin-top: 18px;
  padding-top: 18px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.section--topGrid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
}
.adminCard {
  border: 1px solid rgba(255, 255, 255, 0.1);
  background: rgba(255, 255, 255, 0.03);
  border-radius: 16px;
  padding: 14px;
  min-width: 0;
}
.sectionHeader {
  display: flex;
  gap: 12px;
  justify-content: space-between;
  align-items: flex-start;
  flex-wrap: wrap;
}
.toolbar {
  margin-top: 14px;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  gap: 10px;
  align-items: end;
}
.toolbar--secondary {
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
}
.toolbar__search,
.toolbar__select {
  margin-top: 0;
}
.toolbar__button {
  margin-top: 0;
}
.uploadPreviewCard {
  margin-top: 10px;
  display: flex;
  justify-content: center;
}
.uploadControls {
  margin-top: 14px;
  display: grid;
  gap: 12px;
  justify-items: center;
}
.select,
.input {
  width: 100%;
  box-sizing: border-box;
  padding: 10px 12px;
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  background: rgba(0, 0, 0, 0.18);
  color: rgba(255, 255, 255, 0.92);
  outline: none;
  /* margin-top: 10px; */
}
.input--compact {
  max-width: 320px;
}
.hint {
  margin-top: 10px;
  opacity: 0.78;
  font-size: 13px;
}
.hint--tight {
  margin-top: 6px;
}
.inputFile {
  width: 100%;
  max-width: 360px;
}
.inputFile--tight {
  margin-top: 0;
}
.btn {
  font-size: 12px;
  margin-top: 12px;
  padding: 10px 12px;
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: rgba(255, 255, 255, 0.06);
  color: rgba(255, 255, 255, 0.92);
  cursor: pointer;
  font-weight: 800;
  text-align: center;
  text-decoration: none;
}
.btn--small {
  margin-top: 0;
  padding: 8px 10px;
  font-size: 11px;
}
.ghost {
  opacity: 0.4;
}
.chosen {
  outline: 2px solid rgba(96, 165, 250, 0.45);
}
.btn:disabled {
  cursor: not-allowed;
  opacity: 0.45;
}
.btn--primary {
  background: rgba(96, 165, 250, 0.2);
}
.btn--danger {
  background: rgba(239, 68, 68, 0.14);
  border-color: rgba(239, 68, 68, 0.28);
}
.btn--ghost {
  background: rgba(255, 255, 255, 0.03);
}
.detailHead {
  display: flex;
  gap: 12px;
  justify-content: space-between;
  align-items: flex-start;
  flex-wrap: wrap;
}
.detailHead__actions {
  display: flex;
  gap: 8px;
}
.selectedGame__name {
  margin-top: 8px;
  font-size: 22px;
  font-weight: 900;
}
.selectedGame__id {
  margin-top: 6px;
  opacity: 0.72;
  word-break: break-all;
}
.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: rgba(255, 255, 255, 0.04);
}
.selectedThumb--empty {
  display: grid;
  place-items: center;
  color: rgba(255, 255, 255, 0.62);
}
.itemComposer {
  margin-top: 10px;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(160px, 192px);
  gap: 16px;
  align-items: start;
}
.itemComposer__form {
  display: grid;
  gap: 12px;
  align-items: start;
}
.itemPreviewCard {
  padding: 10px;
  border-radius: 14px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  background: rgba(255, 255, 255, 0.04);
}
.itemPreviewFrame {
  width: min(100%, 192px);
  aspect-ratio: 1 / 1;
  border-radius: 12px;
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.12);
  background: rgba(0, 0, 0, 0.18);
  margin: 0 auto;
}
.itemPreviewImage {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.itemPreviewEmpty {
  width: 100%;
  height: 100%;
  display: grid;
  place-items: center;
  color: rgba(255, 255, 255, 0.62);
  font-size: 13px;
}
.thumbGrid {
  margin-top: 12px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
  gap: 12px;
}
.thumbCard {
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.04);
  padding: 10px;
  min-width: 0;
}
.thumb {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, 0.12);
}
.thumb--game {
  max-width: 150px;
  margin: 0 auto;
  display: block;
}
.thumbLabel {
  margin-top: 8px;
  font-weight: 900;
  font-size: 13px;
  opacity: 0.9;
  word-break: break-word;
}
.thumbLabel--preview {
  text-align: center;
}
.customItemGrid {
  margin-top: 14px;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}
.customItemCard {
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 16px;
  background: rgba(255, 255, 255, 0.04);
  overflow: hidden;
  display: flex;
  gap: 12px;
  align-items: flex-start;
  padding: 12px;
  min-width: 0;
}
.customItemCard__image {
  width: clamp(88px, 22vw, 150px);
  flex: 0 1 150px;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  display: block;
  border-radius: 12px;
  background: rgba(0, 0, 0, 0.18);
}
.customItemCard__body {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
  flex: 1 1 auto;
}
.customItemCard__actions {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
  margin-top: 4px;
}
.customItemCard__title {
  min-width: 0;
  overflow-wrap: anywhere;
  word-break: break-word;
  font-weight: 900;
}
.customItemCard__meta {
  opacity: 0.72;
  font-size: 13px;
  min-width: 0;
  overflow-wrap: anywhere;
  word-break: break-word;
}
.pager {
  margin-top: 16px;
  display: flex;
  gap: 12px;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
}
.pager__info {
  opacity: 0.82;
  font-size: 14px;
}
.userList {
  margin-top: 14px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 12px;
}
.userCard {
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 16px;
  background: rgba(255, 255, 255, 0.04);
  padding: 14px;
}
.userCard__head {
  display: flex;
  gap: 10px;
  justify-content: space-between;
  align-items: flex-start;
}
.userCard__title {
  font-weight: 900;
}
.userCard__meta {
  margin-top: 4px;
  opacity: 0.72;
  font-size: 13px;
}
.userCard__actions {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}
.passwordBox {
  margin-top: 12px;
  display: grid;
  gap: 10px;
}
.roleBadge {
  padding: 6px 10px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  font-size: 12px;
  font-weight: 800;
  opacity: 0.8;
}
.roleBadge--admin {
  background: rgba(96, 165, 250, 0.18);
}
.checkRow {
  margin-top: 12px;
  display: inline-flex;
  gap: 8px;
  align-items: center;
  opacity: 0.88;
}
.checkRow--toolbar {
  margin-top: 0;
}
@media (max-width: 980px) {
  .featuredOrderPanel,
  .section--topGrid,
  .toolbar,
  .itemComposer {
    grid-template-columns: 1fr;
  }
  .itemPreviewCard {
    max-width: 192px;
  }
}
@media (max-width: 640px) {
  .thumbGrid,
  .customItemGrid,
  .userList {
    grid-template-columns: 1fr;
  }
  .customItemCard {
    align-items: stretch;
  }
  .customItemCard__image {
    width: clamp(72px, 28vw, 120px);
    flex-basis: 120px;
  }
}
</style>
