<script setup lang="ts">
import { useAuthSession } from '../../../composables/useAuthSession'

definePageMeta({ title: '媒体库' })

type MediaAssetRow = {
  id: number
  storageKey: string
  publicPath: string
  mime: string
  sizeBytes: number
  createdAt: string
  refCount: number
}

const toast = useToast()
const { refresh: refreshAuth } = useAuthSession()
const runtimeConfig = useRuntimeConfig()
const { fetchData, getApiErrorMessage } = useClientApi()

/** 与 `NUXT_PUBLIC_SITE_URL` / `runtimeConfig.public.siteUrl` 一致；未配置则退回当前页 origin。 */
function resolveCopyOrigin(): string {
  const raw = String(runtimeConfig.public.siteUrl ?? '').trim()
  if (raw) {
    try {
      return new URL(raw).origin
    } catch {
      /* 使用 window */
    }
  }
  return typeof window !== 'undefined' ? window.location.origin : ''
}

const page = ref(1)
const pageSize = ref(20)
const items = ref<MediaAssetRow[]>([])
const total = ref(0)
const loading = ref(true)
const uploading = ref(false)
const fileInput = ref<HTMLInputElement | null>(null)
const reuploadInput = ref<HTMLInputElement | null>(null)
const reuploadTargetId = ref<number | null>(null)
const reuploadingId = ref<number | null>(null)
/** 预览加载失败（多为磁盘缺文件） */
const previewFailedIds = ref<Set<number>>(new Set())
const imgBust = ref<Record<number, number>>({})

const pageSizeItems = [
  { label: '每页 10', value: 10 },
  { label: '每页 20', value: 20 },
  { label: '每页 50', value: 50 },
]

function formatBytes(n: number): string {
  if (n < 1024) {
    return `${n} B`
  }
  if (n < 1024 * 1024) {
    return `${(n / 1024).toFixed(1)} KB`
  }
  return `${(n / (1024 * 1024)).toFixed(1)} MB`
}

function formatDt(iso: string | null): string {
  if (!iso) {
    return '—'
  }
  const d = new Date(iso)
  return Number.isNaN(d.getTime()) ? iso : d.toLocaleString('zh-CN')
}

async function load() {
  loading.value = true
  try {
    const q = new URLSearchParams({
      page: String(page.value),
      pageSize: String(pageSize.value),
    })
    const body = await fetchData<{ items: MediaAssetRow[]; total: number }>(
      `/api/me/media/assets?${q.toString()}`,
      { notify: false },
    )
    items.value = body.items
    total.value = body.total
    const ids = new Set(body.items.map((i) => i.id))
    previewFailedIds.value = new Set([...previewFailedIds.value].filter((id) => ids.has(id)))
  } catch (e: unknown) {
    toast.add({ title: getApiErrorMessage(e), color: 'error' })
  } finally {
    loading.value = false
  }
}

watch(pageSize, () => {
  page.value = 1
})

watch([page, pageSize], () => {
  void load()
})

onMounted(() => {
  void refreshAuth(true)
  void load()
})

function openFilePicker() {
  fileInput.value?.click()
}

async function onFileChange(ev: Event) {
  const input = ev.target as HTMLInputElement
  const files = input.files ? Array.from(input.files) : []
  input.value = ''
  if (!files.length) {
    return
  }

  uploading.value = true
  try {
    const form = new FormData()
    for (const f of files) {
      form.append('file', f)
    }
    await fetchData<{ files: { url: string }[] }>('/api/file/upload', {
      method: 'POST',
      body: form,
      notify: false,
    })
    toast.add({
      title: files.length === 1 ? '上传成功' : `已上传 ${files.length} 个文件`,
      color: 'success',
    })
    await load()
  } catch (e: unknown) {
    toast.add({ title: getApiErrorMessage(e), color: 'error' })
  } finally {
    uploading.value = false
  }
}

async function copyUrl(item: MediaAssetRow) {
  if (!import.meta.client) {
    return
  }
  const absoluteUrl = `${resolveCopyOrigin()}${item.publicPath}`
  try {
    await navigator.clipboard.writeText(absoluteUrl)
    toast.add({ title: '已复制 URL', color: 'success' })
  } catch (e: unknown) {
    toast.add({ title: getApiErrorMessage(e), color: 'error' })
  }
}

function imageSrc(item: MediaAssetRow): string {
  const b = imgBust.value[item.id]
  return b ? `${item.publicPath}?v=${b}` : item.publicPath
}

function onImgError(id: number) {
  previewFailedIds.value = new Set([...previewFailedIds.value, id])
}

function openReuploadPicker(assetId: number) {
  reuploadTargetId.value = assetId
  nextTick(() => reuploadInput.value?.click())
}

async function onReuploadFile(ev: Event) {
  const assetId = reuploadTargetId.value
  reuploadTargetId.value = null
  const input = ev.target as HTMLInputElement
  const file = input.files?.[0]
  input.value = ''
  if (!assetId || !file) {
    return
  }
  reuploadingId.value = assetId
  try {
    const form = new FormData()
    form.append('file', file)
    await fetchData<{ url: string; sizeBytes: number }>(`/api/me/media/assets/${assetId}/reupload`, {
      method: 'POST',
      body: form,
      notify: false,
    })
    previewFailedIds.value = new Set([...previewFailedIds.value].filter((id) => id !== assetId))
    imgBust.value = { ...imgBust.value, [assetId]: Date.now() }
    toast.add({ title: '已重新写入磁盘', color: 'success' })
    await load()
  } catch (e: unknown) {
    toast.add({ title: getApiErrorMessage(e), color: 'error' })
  } finally {
    reuploadingId.value = null
  }
}

async function copyMarkdown(item: MediaAssetRow) {
  if (!import.meta.client) {
    return
  }
  const absoluteUrl = `${resolveCopyOrigin()}${item.publicPath}`
  const md = `![](${absoluteUrl})`
  try {
    await navigator.clipboard.writeText(md)
    toast.add({ title: '已复制 Markdown', color: 'success' })
  } catch (e: unknown) {
    toast.add({ title: getApiErrorMessage(e), color: 'error' })
  }
}
</script>

<template>
  <div class="space-y-6 w-full max-w-[min(100%,88rem)] px-0 sm:px-0">
    <input
      ref="reuploadInput"
      type="file"
      class="sr-only"
      accept="image/png,image/jpeg,image/jpg,image/webp"
      @change="onReuploadFile"
    >
    <div>
      <h2 class="text-lg font-medium">
        资源库
      </h2>
      <p class="text-sm text-muted mt-1">
        浏览已上传图片、复制外链或 Markdown，用于文章与资料。
      </p>
    </div>

    <div class="flex flex-wrap items-end gap-x-5 gap-y-3">
      <input
        ref="fileInput"
        type="file"
        class="sr-only"
        accept="image/png,image/jpeg,image/jpg,image/webp"
        multiple
        @change="onFileChange"
      >
      <UButton
        color="primary"
        :loading="uploading"
        :disabled="uploading"
        @click="openFilePicker"
      >
        选择图片上传
      </UButton>
      <UFormField label="每页" class="w-40 shrink-0">
        <USelect v-model="pageSize" :items="pageSizeItems" value-key="value" class="w-full" />
      </UFormField>
    </div>

    <div v-if="loading" class="text-muted">
      加载中…
    </div>
    <UEmpty
      v-else-if="!items.length"
      title="暂无图片"
      description="上传 PNG / JPEG / WebP 后，将在此列出并可直接复制链接。"
    />
    <div v-else class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
      <UCard v-for="item in items" :key="item.id">
        <div class="space-y-3">
          <div class="relative w-full aspect-video rounded-md border border-default overflow-hidden bg-elevated/30">
            <img
              :src="imageSrc(item)"
              :alt="item.storageKey"
              class="w-full h-full object-cover"
              loading="lazy"
              @error="onImgError(item.id)"
            />
            <div
              v-if="previewFailedIds.has(item.id)"
              class="absolute inset-0 flex flex-col items-center justify-center gap-2 bg-default/80 p-2"
            >
              <p class="text-xs text-muted text-center">
                文件缺失或无法加载
              </p>
              <UButton
                size="xs"
                color="primary"
                :loading="reuploadingId === item.id"
                :disabled="reuploadingId !== null && reuploadingId !== item.id"
                @click="openReuploadPicker(item.id)"
              >
                重新上传
              </UButton>
            </div>
          </div>
          <div
            class="truncate font-mono text-xs text-default"
            :title="item.storageKey"
          >
            {{ item.storageKey }}
          </div>
          <div class="flex flex-wrap gap-x-3 gap-y-1 text-xs text-muted tabular-nums">
            <span>{{ formatBytes(item.sizeBytes) }}</span>
            <span>{{ formatDt(item.createdAt) }}</span>
          </div>
          <p class="text-xs text-default">
            引用数: {{ item.refCount }}
          </p>
          <div class="flex flex-wrap gap-2">
            <UButton size="xs" variant="soft" @click="copyUrl(item)">
              复制 URL
            </UButton>
            <UButton size="xs" variant="soft" @click="copyMarkdown(item)">
              复制 Markdown
            </UButton>
          </div>
        </div>
      </UCard>
    </div>

    <div v-if="!loading && total > pageSize" class="flex justify-end">
      <UPagination v-model:page="page" :total="total" :items-per-page="pageSize" size="sm" />
    </div>
  </div>
</template>
