<script setup lang="ts">
/**
 * 附件库：小红书式双列瀑布流 + 分页上拉 + page-container 详情。
 */
import { computed, ref } from 'vue'
import { onReachBottom, onShow } from '@dcloudio/uni-app'
import { useDialog, useToast } from '@wot-ui/ui'
import AppNavbar from '@/components/layout/AppNavbar.vue'
import AppPageShell from '@/components/layout/AppPageShell.vue'
import AdminFilterBar from '../../components/AdminFilterBar.vue'
import AdminDetailDrawer from '../../components/AdminDetailDrawer.vue'
import { useAdminGuard } from '../../composables/useAdminGuard'
import {
  adminDelete,
  adminList,
  adminUpdate,
  normalizeAdminList,
  normalizePagination,
  uploadAttachment,
  type Attachment,
} from '@/api'
import { resolveMediaUrl } from '@/utils/request'

const toast = useToast()
const dialog = useDialog()
const { guardAdmin } = useAdminGuard()

const loading = ref(false)
const uploading = ref(false)
const list = ref<Attachment[]>([])
const categories = ref<{ id: number, name: string }[]>([])
const keyword = ref('')
const filters = ref<Record<string, unknown>>({})
const page = ref(1)
const finished = ref(false)
const detailShow = ref(false)
const current = ref<Attachment | null>(null)

const filterDefs = [
  { key: 'keyword', label: '关键词', type: 'text' as const },
  {
    key: 'fileType',
    label: '类型',
    type: 'select' as const,
    options: [
      { label: '全部', value: '' },
      { label: '图片', value: 'image' },
      { label: '视频', value: 'video' },
      { label: '文档', value: 'document' },
      { label: '其他', value: 'other' },
    ],
  },
]

/** 奇偶分列，形成简易瀑布流 */
const leftCol = computed(() => list.value.filter((_, i) => i % 2 === 0))
const rightCol = computed(() => list.value.filter((_, i) => i % 2 === 1))

async function load(reset = true) {
  if (!guardAdmin()) return
  if (reset) {
    page.value = 1
    finished.value = false
    list.value = []
  }
  if (finished.value && !reset) return
  loading.value = true
  try {
    if (reset || !categories.value.length) {
      categories.value = normalizeAdminList(await adminList('attachment-categories')) as { id: number, name: string }[]
    }
    const data = await adminList('attachments', {
      page: page.value,
      pageSize: 20,
      keyword: keyword.value || undefined,
      ...filters.value,
    })
    const p = normalizePagination<Attachment>(data, page.value, 20)
    list.value = reset ? p.list : [...list.value, ...p.list]
    finished.value = list.value.length >= p.total || p.list.length < 20
  }
  catch (e: unknown) {
    toast.error(e instanceof Error ? e.message : '加载失败')
  }
  finally {
    loading.value = false
  }
}

onShow(() => load(true))
onReachBottom(() => {
  if (finished.value || loading.value) return
  page.value += 1
  load(false)
})

function fileUrl(item: Attachment) {
  return resolveMediaUrl(item.fileUrl || item.url || '')
}

function isImage(item: Attachment) {
  const t = String(item.fileType || item.fileUrl || item.url || '').toLowerCase()
  return t.includes('image') || /\.(png|jpe?g|gif|webp|svg)$/i.test(t)
}

function isVideo(item: Attachment) {
  const t = String(item.fileType || item.fileUrl || item.url || '').toLowerCase()
  return t.includes('video') || /\.(mp4|mov|webm|m4v)$/i.test(t)
}

function openDetail(item: Attachment) {
  current.value = { ...item }
  detailShow.value = true
}

async function onUpload() {
  try {
    const res = await uni.chooseImage({ count: 9, sizeType: ['compressed'] })
    uploading.value = true
    for (const path of res.tempFilePaths || []) {
      await uploadAttachment(path)
    }
    toast.success('上传完成')
    load(true)
  }
  catch (e: unknown) {
    if (e && typeof e === 'object' && 'errMsg' in e && String((e as { errMsg: string }).errMsg).includes('cancel')) return
    toast.error(e instanceof Error ? e.message : '上传失败')
  }
  finally {
    uploading.value = false
  }
}

function copyUrl() {
  const url = fileUrl(current.value || { id: 0 })
  if (!url) return
  uni.setClipboardData({ data: url, success: () => toast.success('已复制') })
}

async function saveCategory(catId: number) {
  if (!current.value?.id) return
  try {
    await adminUpdate('attachments', current.value.id, { categoryId: catId })
    current.value.categoryId = catId
    toast.success('已更新分类')
    load(true)
  }
  catch (e: unknown) {
    toast.error(e instanceof Error ? e.message : '更新失败')
  }
}

function onDelete() {
  if (!current.value?.id) return
  dialog.confirm({ title: '确认删除', msg: '确定删除该附件吗？' }).then(async () => {
    try {
      await adminDelete('attachments', current.value!.id)
      toast.success('已删除')
      detailShow.value = false
      load(true)
    }
    catch (e: unknown) {
      toast.error(e instanceof Error ? e.message : '删除失败')
    }
  }).catch(() => {})
}

const detailRows = () => {
  const c = current.value
  if (!c) return []
  return [
    { label: '名称', value: String(c.originalName || c.fileName || c.id) },
    { label: '类型', value: String(c.fileType || '') },
    { label: '大小', value: c.fileSize ? `${Math.round(Number(c.fileSize) / 1024)} KB` : '' },
    { label: 'URL', value: fileUrl(c) },
  ]
}
</script>

<template>
  <AppPageShell admin>
    <AppNavbar title="附件库" show-back />
    <view class="page-pad">
      <AdminFilterBar
        v-model:keyword="keyword"
        v-model="filters"
        :filters="filterDefs"
        @search="load(true)"
        @reset="load(true)"
      />
      <!-- 双列瀑布：奇偶分列，避免 CSS grid min-width 撑破 -->
      <view class="waterfall">
        <view class="col">
          <view
            v-for="item in leftCol"
            :key="item.id"
            class="card"
            @click="openDetail(item)"
          >
            <view class="media">
              <image
                v-if="isImage(item)"
                class="thumb"
                :src="fileUrl(item)"
                mode="widthFix"
              />
              <view v-else class="file-icon">
                <wd-icon :name="isVideo(item) ? 'video' : 'file'" size="28px" />
                <text v-if="isVideo(item)" class="badge">视频</text>
              </view>
            </view>
            <text class="name">{{ item.originalName || item.fileName || item.id }}</text>
          </view>
        </view>
        <view class="col">
          <view
            v-for="item in rightCol"
            :key="item.id"
            class="card"
            @click="openDetail(item)"
          >
            <view class="media">
              <image
                v-if="isImage(item)"
                class="thumb"
                :src="fileUrl(item)"
                mode="widthFix"
              />
              <view v-else class="file-icon">
                <wd-icon :name="isVideo(item) ? 'video' : 'file'" size="28px" />
                <text v-if="isVideo(item)" class="badge">视频</text>
              </view>
            </view>
            <text class="name">{{ item.originalName || item.fileName || item.id }}</text>
          </view>
        </view>
      </view>
      <wd-loading v-if="loading || uploading" />
      <text v-if="!finished && list.length" class="more text-art-muted">上拉加载更多</text>
      <wd-empty v-if="!loading && !list.length" description="暂无附件" />
    </view>
    <view class="fab" @click="onUpload"><wd-icon name="upload" size="24px" color="#fff" /></view>

    <AdminDetailDrawer v-model:show="detailShow" title="附件详情" :rows="detailRows()">
      <view class="cat-section">
        <text class="text-art-muted">改分类</text>
        <view class="tags">
          <view
            v-for="c in categories"
            :key="c.id"
            class="tag"
            :class="{ on: current?.categoryId === c.id }"
            @click="saveCategory(c.id)"
          >
            {{ c.name }}
          </view>
        </view>
      </view>
      <template #footer>
        <wd-button size="small" @click="copyUrl">复制 URL</wd-button>
        <wd-button size="small" type="warning" @click="onDelete">删除</wd-button>
      </template>
    </AdminDetailDrawer>
  </AppPageShell>
</template>

<style scoped lang="scss">
.page-pad {
  padding: 24rpx 24rpx 120rpx;
  overflow-x: hidden;
  width: 100%;
  box-sizing: border-box;
}
.waterfall {
  display: flex;
  align-items: flex-start;
  gap: 16rpx;
  width: 100%;
  box-sizing: border-box;
}
.col {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 16rpx;
}
.card {
  min-width: 0;
  width: 100%;
  overflow: hidden;
  border-radius: 16rpx;
  background: rgb(var(--art-admin-card));
  border: 1px solid rgba(255, 255, 255, 0.1);
  box-sizing: border-box;
}
.media {
  width: 100%;
  min-width: 0;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.04);
}
.thumb {
  display: block;
  width: 100%;
  max-width: 100%;
  min-height: 180rpx;
  max-height: 480rpx;
}
.file-icon {
  width: 100%;
  height: 220rpx;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12rpx;
  color: rgba(255, 255, 255, 0.45);
}
.badge {
  font-size: 20rpx;
  padding: 4rpx 12rpx;
  border-radius: 8rpx;
  background: rgba(212, 179, 131, 0.25);
  color: rgb(var(--art-accent));
}
.name {
  display: block;
  padding: 16rpx 18rpx 20rpx;
  font-size: 22rpx;
  color: rgb(var(--art-text));
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.more { display: block; text-align: center; font-size: 22rpx; padding: 20rpx; }
.fab {
  position: fixed;
  right: 40rpx;
  bottom: calc(40rpx + env(safe-area-inset-bottom));
  width: 96rpx;
  height: 96rpx;
  border-radius: 50%;
  background: rgb(var(--art-accent));
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
}
.cat-section { margin-top: 16rpx; }
.tags { display: flex; flex-wrap: wrap; gap: 12rpx; margin-top: 12rpx; }
.tag {
  padding: 8rpx 16rpx;
  border-radius: 8rpx;
  font-size: 22rpx;
  background: rgba(255, 255, 255, 0.06);
  color: rgba(255, 255, 255, 0.65);
}
.tag.on { background: rgba(212, 179, 131, 0.25); color: rgb(var(--art-accent)); }
</style>
