<script setup>
definePageMeta({
  layout: 'admin'
})

const searchText = ref('')
const activeFolder = ref('')
const newFolderName = ref('')
const editingUrl = ref('')
const editingName = ref('')
const editingCategory = ref('')
const deletingUrl = ref('')
const errorMessage = ref('')
const selectedMediaUrl = ref('')
const selectedMediaUrls = ref([])
const lastSelectedIndex = ref(-1)
const draggingUrls = ref([])

const { data: mediaItems, refresh } = await useFetch('/admin/api/media', {
  default: () => []
})

const { data: mediaFolders, refresh: refreshMediaFolders } = await useFetch('/admin/api/media-folders', {
  default: () => ['미분류']
})

const selectedMedia = computed(() => mediaItems.value.find((item) => item.url === selectedMediaUrl.value) || null)

const normalizedFolders = computed(() => {
  const folderSet = new Set(['미분류'])

  mediaFolders.value.forEach((folder) => {
    String(folder || '').split('/').filter(Boolean).reduce((parentPath, segment) => {
      const nextPath = parentPath ? `${parentPath}/${segment}` : segment
      folderSet.add(nextPath)
      return nextPath
    }, '')
  })

  mediaItems.value.forEach((item) => {
    String(item.category || '미분류').split('/').filter(Boolean).reduce((parentPath, segment) => {
      const nextPath = parentPath ? `${parentPath}/${segment}` : segment
      folderSet.add(nextPath)
      return nextPath
    }, '')
  })

  return [...folderSet].sort((left, right) => left.localeCompare(right))
})

const folderMediaCounts = computed(() => normalizedFolders.value.reduce((counts, folder) => {
  counts[folder] = mediaItems.value.filter((item) => item.category === folder || item.category?.startsWith(`${folder}/`)).length
  return counts
}, {}))

const filteredMediaItems = computed(() => {
  const query = searchText.value.trim().toLowerCase()
  const folder = activeFolder.value

  return mediaItems.value.filter((item) => {
    const matchesFolder = !folder || item.category === folder || item.category?.startsWith(`${folder}/`)
    const matchesQuery = !query || [
      item.name,
      item.url,
      item.category,
      ...item.usage.map((usage) => usage.title)
    ].some((value) => String(value || '').toLowerCase().includes(query))

    return matchesFolder && matchesQuery
  })
})

/**
 * 파일 크기 표시 문자열 생성
 * @param {number} size - 파일 크기
 * @returns {string} 표시 문자열
 */
const formatFileSize = (size) => {
  if (size < 1024) {
    return `${size} B`
  }

  if (size < 1024 * 1024) {
    return `${(size / 1024).toFixed(1)} KB`
  }

  return `${(size / 1024 / 1024).toFixed(1)} MB`
}

/**
 * 폴더 경로의 표시 이름 조회
 * @param {string} folder - 폴더 경로
 * @returns {string} 표시 이름
 */
const getFolderName = (folder) => folder.split('/').filter(Boolean).pop() || '미분류'

/**
 * 폴더 깊이 조회
 * @param {string} folder - 폴더 경로
 * @returns {number} 폴더 깊이
 */
const getFolderDepth = (folder) => Math.max(folder.split('/').filter(Boolean).length - 1, 0)

/**
 * 미디어 선택 여부 확인
 * @param {Object} item - 미디어 항목
 * @returns {boolean} 선택 여부
 */
const isMediaSelected = (item) => selectedMediaUrls.value.includes(item.url)

/**
 * 폴더 선택
 * @param {string} folder - 폴더 경로
 * @returns {void}
 */
const selectFolder = (folder) => {
  activeFolder.value = folder
  selectedMediaUrls.value = []
  lastSelectedIndex.value = -1
}

/**
 * 미디어 항목 선택
 * @param {MouseEvent} event - 클릭 이벤트
 * @param {Object} item - 미디어 항목
 * @param {number} index - 필터 목록 내 순서
 * @returns {void}
 */
const selectMediaItem = (event, item, index) => {
  if (event.shiftKey && lastSelectedIndex.value >= 0) {
    const startIndex = Math.min(lastSelectedIndex.value, index)
    const endIndex = Math.max(lastSelectedIndex.value, index)
    const rangeUrls = filteredMediaItems.value.slice(startIndex, endIndex + 1).map((mediaItem) => mediaItem.url)
    selectedMediaUrls.value = [...new Set([...selectedMediaUrls.value, ...rangeUrls])]
    return
  }

  if (event.metaKey || event.ctrlKey) {
    selectedMediaUrls.value = isMediaSelected(item)
      ? selectedMediaUrls.value.filter((url) => url !== item.url)
      : [...selectedMediaUrls.value, item.url]
    lastSelectedIndex.value = index
    return
  }

  selectedMediaUrls.value = [item.url]
  lastSelectedIndex.value = index
}

/**
 * 선택된 미디어 해제
 * @returns {void}
 */
const clearMediaSelection = () => {
  selectedMediaUrls.value = []
  lastSelectedIndex.value = -1
}

/**
 * 미디어 상세 모달 열기
 * @param {Object} item - 미디어 항목
 * @returns {void}
 */
const openMediaDetail = (item) => {
  selectedMediaUrl.value = item.url
  editingUrl.value = item.url
  editingName.value = item.title
  editingCategory.value = item.category
  errorMessage.value = ''
}

/**
 * 미디어 상세 모달 닫기
 * @returns {void}
 */
const closeMediaDetail = () => {
  selectedMediaUrl.value = ''
  cancelRename()
}

/**
 * 미디어 파일명 변경 취소
 * @returns {void}
 */
const cancelRename = () => {
  editingUrl.value = ''
  editingName.value = ''
}

/**
 * 미디어 폴더 생성
 * @returns {Promise<void>}
 */
const createFolder = async () => {
  const folderName = newFolderName.value.trim()

  if (!folderName) {
    return
  }

  errorMessage.value = ''

  try {
    const folderPath = activeFolder.value ? `${activeFolder.value}/${folderName}` : folderName
    const createdFolder = await $fetch('/admin/api/media-folders', {
      method: 'POST',
      body: {
        path: folderPath
      }
    })

    newFolderName.value = ''
    activeFolder.value = createdFolder.path
    await refreshMediaFolders()
  } catch (error) {
    errorMessage.value = error?.data?.message || '폴더를 만들지 못했습니다.'
  }
}

/**
 * 선택한 미디어를 폴더로 이동
 * @param {string} folder - 폴더 경로
 * @param {Array<string>} urls - 이동할 미디어 URL 목록
 * @returns {Promise<void>}
 */
const moveMediaToFolder = async (folder, urls = selectedMediaUrls.value) => {
  const targetUrls = [...new Set(urls.filter(Boolean))]

  if (!targetUrls.length) {
    return
  }

  errorMessage.value = ''

  try {
    await $fetch('/admin/api/media', {
      method: 'PUT',
      body: {
        urls: targetUrls,
        category: folder || '미분류'
      }
    })
    await Promise.all([
      refresh(),
      refreshMediaFolders()
    ])
    activeFolder.value = folder || '미분류'
    clearMediaSelection()
  } catch (error) {
    errorMessage.value = error?.data?.message || '미디어 폴더를 변경하지 못했습니다.'
  }
}

/**
 * 미디어 드래그 시작
 * @param {DragEvent} event - 드래그 이벤트
 * @param {Object} item - 미디어 항목
 * @returns {void}
 */
const startMediaDrag = (event, item) => {
  if (!isMediaSelected(item)) {
    selectedMediaUrls.value = [item.url]
  }

  draggingUrls.value = [...selectedMediaUrls.value]

  if (event.dataTransfer) {
    event.dataTransfer.effectAllowed = 'move'
    event.dataTransfer.setData('text/plain', draggingUrls.value.join('\n'))
  }
}

/**
 * 폴더로 미디어 드롭
 * @param {string} folder - 폴더 경로
 * @returns {Promise<void>}
 */
const dropMediaOnFolder = async (folder) => {
  const urls = draggingUrls.value.length ? draggingUrls.value : selectedMediaUrls.value
  draggingUrls.value = []
  await moveMediaToFolder(folder, urls)
}

/**
 * 미디어 카테고리 저장
 * @returns {Promise<void>} 저장 결과
 */
const saveMediaCategory = async () => {
  errorMessage.value = ''

  try {
    await $fetch('/admin/api/media', {
      method: 'PUT',
      body: {
        url: selectedMedia.value.url,
        category: editingCategory.value
      }
    })
    await Promise.all([
      refresh(),
      refreshMediaFolders()
    ])
  } catch (error) {
    errorMessage.value = error?.data?.message || '카테고리를 저장하지 못했습니다.'
  }
}

/**
 * 미디어 파일명 변경
 * @returns {Promise<void>}
 */
const renameMedia = async () => {
  const editingItem = mediaItems.value.find((item) => item.url === editingUrl.value)

  if (editingItem?.usage.length) {
    errorMessage.value = '사용 중인 미디어는 파일명을 변경할 수 없습니다.'
    return
  }

  errorMessage.value = ''

  try {
    const renamedItem = await $fetch('/admin/api/media', {
      method: 'PUT',
      body: {
        url: editingUrl.value,
        name: editingName.value
      }
    })
    cancelRename()
    await refresh()
    selectedMediaUrl.value = renamedItem.url
  } catch (error) {
    errorMessage.value = error?.data?.message || '파일명을 변경하지 못했습니다.'
  }
}

/**
 * 미디어 삭제
 * @param {Object} item - 미디어 항목
 * @returns {Promise<void>}
 */
const deleteMedia = async (item) => {
  if (item.usage.length) {
    errorMessage.value = '사용 중인 미디어는 삭제할 수 없습니다.'
    return
  }

  if (!confirm(`"${item.name}" 파일을 삭제할까요?`)) {
    return
  }

  deletingUrl.value = item.url
  errorMessage.value = ''

  try {
    await $fetch('/admin/api/media', {
      method: 'DELETE',
      body: {
        url: item.url
      }
    })
    closeMediaDetail()
    await refresh()
  } catch (error) {
    errorMessage.value = error?.data?.message || '파일을 삭제하지 못했습니다.'
  } finally {
    deletingUrl.value = ''
  }
}
</script>

<template>
  <section class="admin-media bg-paper p-6">
    <div class="admin-media__header flex flex-wrap items-center justify-between gap-4">
      <div>
        <p class="admin-media__eyebrow text-xs font-semibold uppercase text-muted">
          Media
        </p>
        <h1 class="admin-media__title mt-2 text-3xl font-semibold">
          미디어
        </h1>
      </div>
      <input
        v-model="searchText"
        class="admin-media__search w-full rounded border border-line bg-white px-3 py-2 text-sm md:w-72"
        type="search"
        placeholder="파일명, 경로, 폴더, 사용처 검색"
      >
    </div>

    <p v-if="errorMessage" class="admin-media__error mt-6 rounded border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
      {{ errorMessage }}
    </p>

    <div class="admin-media__layout mt-8 grid gap-5 lg:grid-cols-[16rem_minmax(0,1fr)]">
      <aside class="admin-media__folders rounded border border-line bg-white p-3">
        <button
          class="admin-media__folder-button flex w-full items-center justify-between rounded px-3 py-2 text-left text-sm font-semibold hover:bg-surface"
          :class="!activeFolder ? 'bg-[#15171a] text-white hover:bg-[#15171a]' : 'text-ink'"
          type="button"
          @click="selectFolder('')"
          @dragover.prevent
          @drop.prevent="dropMediaOnFolder('미분류')"
        >
          <span>전체 미디어</span>
          <span>{{ mediaItems.length }}</span>
        </button>

        <div class="admin-media__folder-list mt-3 grid gap-1">
          <button
            v-for="folder in normalizedFolders"
            :key="folder"
            class="admin-media__folder-button flex w-full items-center justify-between rounded py-2 pr-3 text-left text-sm hover:bg-surface"
            :class="activeFolder === folder ? 'bg-[#15171a] text-white hover:bg-[#15171a]' : 'text-ink'"
            :style="{ paddingLeft: `${12 + getFolderDepth(folder) * 14}px` }"
            type="button"
            @click="selectFolder(folder)"
            @dragover.prevent
            @drop.prevent="dropMediaOnFolder(folder)"
          >
            <span class="admin-media__folder-name min-w-0 truncate">{{ getFolderName(folder) }}</span>
            <span class="admin-media__folder-count shrink-0 text-xs opacity-70">{{ folderMediaCounts[folder] || 0 }}</span>
          </button>
        </div>

        <form class="admin-media__folder-create mt-4 grid gap-2 border-t border-line pt-4" @submit.prevent="createFolder">
          <label class="admin-media__folder-label text-xs font-semibold text-muted" for="media-folder-name">
            새 폴더
          </label>
          <input
            id="media-folder-name"
            v-model="newFolderName"
            class="admin-media__folder-input rounded border border-line px-3 py-2 text-sm"
            type="text"
            placeholder="폴더 이름"
          >
          <button class="admin-media__folder-submit rounded bg-[#15171a] px-3 py-2 text-xs font-semibold text-white" type="submit">
            폴더 추가
          </button>
        </form>
      </aside>

      <div class="admin-media__content min-w-0">
        <div class="admin-media__toolbar flex flex-wrap items-center justify-between gap-3">
          <div>
            <h2 class="admin-media__folder-title text-lg font-semibold text-ink">
              {{ activeFolder || '전체 미디어' }}
            </h2>
            <p class="admin-media__folder-summary mt-1 text-xs text-muted">
              {{ filteredMediaItems.length }}개 표시
            </p>
          </div>
          <div v-if="selectedMediaUrls.length" class="admin-media__selection flex flex-wrap items-center gap-2 rounded border border-line bg-white px-3 py-2 text-xs">
            <strong class="admin-media__selection-count text-ink">{{ selectedMediaUrls.length }}개 선택됨</strong>
            <button class="admin-media__selection-clear font-semibold text-muted hover:text-ink" type="button" @click="clearMediaSelection">
              선택 해제
            </button>
            <button
              v-if="activeFolder"
              class="admin-media__selection-move rounded bg-[#15171a] px-2.5 py-1 font-semibold text-white"
              type="button"
              @click="moveMediaToFolder(activeFolder)"
            >
              현재 폴더로 이동
            </button>
          </div>
        </div>

        <div v-if="filteredMediaItems.length" class="admin-media__grid mt-5 grid grid-cols-3 gap-2 sm:grid-cols-4 md:grid-cols-6 xl:grid-cols-8 2xl:grid-cols-10">
          <button
            v-for="(item, index) in filteredMediaItems"
            :key="item.url"
            class="admin-media__item group relative overflow-hidden border border-line bg-white text-left outline-none transition"
            :class="isMediaSelected(item) ? 'ring-2 ring-[#15171a]' : 'hover:border-[#15171a]'"
            type="button"
            draggable="true"
            @click="selectMediaItem($event, item, index)"
            @dblclick="openMediaDetail(item)"
            @dragstart="startMediaDrag($event, item)"
          >
            <img class="admin-media__image aspect-square w-full bg-surface object-cover" :src="item.url" :alt="item.title">
            <span
              v-if="item.usage.length"
              class="admin-media__usage-badge absolute right-1.5 top-1.5 rounded bg-[#15171a] px-1.5 py-0.5 text-[10px] font-semibold text-white"
            >
              {{ item.usage.length }}
            </span>
            <span
              v-if="isMediaSelected(item)"
              class="admin-media__selected-badge absolute left-1.5 top-1.5 grid h-5 w-5 place-items-center rounded-full bg-white text-[11px] font-bold text-ink shadow"
            >
              ✓
            </span>
            <span class="admin-media__name block truncate px-2 py-1.5 text-xs font-semibold text-ink">
              {{ item.name }}
            </span>
          </button>
        </div>

        <p v-else class="admin-media__empty mt-5 border border-dashed border-line bg-white p-8 text-center text-sm text-muted">
          표시할 미디어가 없습니다.
        </p>
      </div>
    </div>

    <div
      v-if="selectedMedia"
      class="admin-media__modal fixed inset-0 z-50 grid place-items-center bg-black/40 px-5 py-8"
      role="dialog"
      aria-modal="true"
      @click.self="closeMediaDetail"
    >
      <section class="admin-media__modal-panel grid max-h-[86vh] w-full max-w-5xl overflow-hidden bg-white text-ink shadow-xl lg:grid-cols-[minmax(0,1fr)_22rem]">
        <div class="admin-media__preview grid min-h-[20rem] place-items-center bg-[#f5f5f2] p-5">
          <img class="admin-media__preview-image max-h-[72vh] max-w-full object-contain" :src="selectedMedia.url" :alt="selectedMedia.title">
        </div>

        <aside class="admin-media__detail grid max-h-[86vh] content-start gap-5 overflow-y-auto border-l border-line p-5">
          <div class="admin-media__detail-header flex items-start justify-between gap-3">
            <div>
              <p class="admin-media__detail-eyebrow text-xs font-semibold uppercase text-muted">
                Attachment
              </p>
              <h2 class="admin-media__detail-title mt-1 break-all text-xl font-semibold">
                {{ selectedMedia.name }}
              </h2>
            </div>
            <button class="admin-media__detail-close rounded border border-line px-3 py-1.5 text-sm font-semibold" type="button" @click="closeMediaDetail">
              닫기
            </button>
          </div>

          <dl class="admin-media__info grid gap-3 text-sm">
            <div class="admin-media__info-row">
              <dt class="admin-media__info-label text-xs font-semibold text-muted">경로</dt>
              <dd class="admin-media__info-value mt-1 break-all">{{ selectedMedia.url }}</dd>
            </div>
            <div class="admin-media__info-row">
              <dt class="admin-media__info-label text-xs font-semibold text-muted">용량</dt>
              <dd class="admin-media__info-value mt-1">{{ formatFileSize(selectedMedia.size) }}</dd>
            </div>
            <div class="admin-media__info-row">
              <dt class="admin-media__info-label text-xs font-semibold text-muted">폴더</dt>
              <dd class="admin-media__info-value mt-1 break-all">{{ selectedMedia.category }}</dd>
            </div>
          </dl>

          <div class="admin-media__category grid gap-2">
            <label class="admin-media__category-label text-xs font-semibold text-muted" for="media-category">
              폴더
            </label>
            <div class="admin-media__category-row flex gap-2">
              <input
                id="media-category"
                v-model="editingCategory"
                class="admin-media__category-input min-w-0 flex-1 rounded border border-line px-3 py-2 text-sm"
                type="text"
                list="media-folder-options"
                placeholder="미분류"
                @keydown.enter.prevent="saveMediaCategory"
              >
              <button class="admin-media__category-save rounded border border-line px-3 py-2 text-xs font-semibold" type="button" @click="saveMediaCategory">
                저장
              </button>
            </div>
            <datalist id="media-folder-options">
              <option v-for="folder in normalizedFolders" :key="folder" :value="folder" />
            </datalist>
          </div>

          <div class="admin-media__usage rounded bg-surface p-3 text-xs">
            <strong class="admin-media__usage-title text-ink">
              사용 현황 {{ selectedMedia.usage.length }}곳
            </strong>
            <ul v-if="selectedMedia.usage.length" class="admin-media__usage-list mt-2 grid gap-1.5 text-muted">
              <li v-for="usage in selectedMedia.usage" :key="`${selectedMedia.url}-${usage.type}-${usage.id}-${usage.location}`" class="admin-media__usage-item">
                <NuxtLink
                  v-if="usage.adminUrl"
                  class="admin-media__usage-link font-semibold text-ink hover:opacity-70"
                  :to="usage.adminUrl"
                >
                  {{ usage.title }}
                </NuxtLink>
                <span v-else class="admin-media__usage-name font-semibold text-ink">{{ usage.title }}</span>
                <span class="admin-media__usage-meta"> · {{ usage.typeLabel }} · {{ usage.label }}</span>
              </li>
            </ul>
            <p v-else class="admin-media__usage-empty mt-2 text-muted">
              사용 중인 곳이 없습니다.
            </p>
          </div>

          <div class="admin-media__rename grid gap-2">
            <label class="admin-media__rename-label text-xs font-semibold text-muted" for="media-name">
              파일명
            </label>
            <input
              id="media-name"
              v-model="editingName"
              class="admin-media__rename-input rounded border border-line px-3 py-2 text-sm disabled:opacity-50"
              type="text"
              :disabled="selectedMedia.usage.length > 0"
              :placeholder="selectedMedia.title"
              @keydown.enter.prevent="renameMedia"
            >
            <p v-if="selectedMedia.usage.length" class="admin-media__locked text-xs text-muted">
              사용 중인 미디어는 파일명 변경과 삭제가 잠겨 있습니다.
            </p>
          </div>

          <div class="admin-media__actions flex flex-wrap gap-2">
            <button
              class="admin-media__rename-save rounded bg-[#15171a] px-3 py-1.5 text-xs font-semibold text-white disabled:opacity-50"
              type="button"
              :disabled="selectedMedia.usage.length > 0 || !editingName"
              @click="renameMedia"
            >
              파일명 저장
            </button>
            <button
              class="admin-media__delete rounded border border-red-200 px-3 py-1.5 text-xs font-semibold text-red-700 disabled:opacity-50"
              type="button"
              :disabled="deletingUrl === selectedMedia.url || selectedMedia.usage.length > 0"
              @click="deleteMedia(selectedMedia)"
            >
              {{ deletingUrl === selectedMedia.url ? '삭제 중' : '삭제' }}
            </button>
          </div>
        </aside>
      </section>
    </div>
  </section>
</template>
