<template>
  <div class="admin-page">
    <section class="card">
      <div class="filter-row">
        <input
          v-model="filters.search"
          class="input"
          type="text"
          placeholder="搜索标题 / 描述"
        />
        <input v-model="filters.author" class="input" type="text" placeholder="作者用户名" />
        <button class="btn" type="button" @click="loadArticles">查询</button>
        <button class="btn ghost" type="button" @click="loadArticles" :disabled="loading">
          {{ loading ? '刷新中...' : '刷新列表' }}
        </button>
      </div>
    </section>

    <section class="card table-card">
      <div v-if="loading" class="empty">文章列表加载中...</div>
      <div v-else-if="articles.length === 0" class="empty">暂无文章</div>
      <div v-else class="table-wrapper">
        <table class="table">
          <thead>
            <tr>
              <th>标题 / 描述</th>
              <th style="width: 160px">标签</th>
              <th>置顶</th>
              <th>推荐</th>
              <th style="width: 90px">权重</th>
              <th>作者</th>
              <th>浏览</th>
              <th style="width: 220px">操作</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="article in articles" :key="article.slug">
              <td>
                <div class="article-title">{{ article.title }}</div>
                <div class="user-meta">Slug：{{ article.slug }}</div>
                <div v-if="article.description" class="article-desc">
                  {{ article.description }}
                </div>
              </td>
              <td>
                <div v-if="article.tagList?.length" class="tag-list">
                  <span
                    v-for="tag in article.tagList"
                    :key="`${article.slug}-${tag}`"
                    class="tag-chip"
                  >
                    {{ tag }}
                  </span>
                </div>
                <span v-else class="muted">--</span>
              </td>
              <td class="flag-cell">
                <button
                  class="btn small"
                  :class="{ primary: article.isTop }"
                  type="button"
                  :disabled="savingFlags[article.slug]"
                  @click="toggleTop(article)"
                >
                  {{ article.isTop ? '已置顶' : '置顶' }}
                </button>
              </td>
              <td class="flag-cell">
                <button
                  class="btn small"
                  :class="{ primary: article.isFeatured }"
                  type="button"
                  :disabled="savingFlags[article.slug]"
                  @click="toggleFeatured(article)"
                >
                  {{ article.isFeatured ? '已推荐' : '推荐' }}
                </button>
              </td>
              <td>
                <div class="weight-cell">
                  <span class="weight-value">{{ article.sortWeight ?? 0 }}</span>
                  <button
                    class="btn small ghost"
                    type="button"
                    :disabled="savingFlags[article.slug]"
                    @click="setSortWeight(article)"
                  >
                    调整
                  </button>
                </div>
              </td>
              <td>{{ article.author?.username || '未知' }}</td>
              <td>{{ article.views ?? 0 }}</td>
              <td class="actions">
                <button class="btn small primary" type="button" @click="openEdit(article)">
                  编辑
                </button>
                <button class="btn small danger" type="button" @click="deleteArticle(article)">
                  删除
                </button>
              </td>
            </tr>
          </tbody>
        </table>
      </div>
    </section>

    <!-- <section class="card">
      <div class="menu-header">
        <div>
          <div class="menu-title">菜单标签配置</div>
          <div class="menu-sub">为「资讯广场」「使用教程」分配标签（多标签 AND 匹配）</div>
        </div>
        <button class="btn ghost" type="button" @click="loadMenuSlots" :disabled="menuLoading">
          {{ menuLoading ? '刷新中...' : '刷新配置' }}
        </button>
      </div>

      <div v-if="tagsLoading" class="empty">加载标签...</div>
      <div v-else class="menu-slots">
        <div v-for="slot in menuSlots" :key="slot.slotKey" class="menu-slot">
          <div class="menu-slot-title">{{ slot.label }}</div>
          <div class="menu-slot-sub">
            选择文章需要同时命中的标签（空为全部）
          </div>
          <select v-model="slot.tags" class="input" multiple>
            <option v-for="tag in allTags" :key="tag" :value="tag">
              {{ tag }}
            </option>
          </select>
          <div class="menu-slot-actions">
            <button
              class="btn primary small"
              type="button"
              :disabled="menuSaving[slot.slotKey]"
              @click="saveMenuSlot(slot)"
            >
              {{ menuSaving[slot.slotKey] ? '保存中...' : '保存标签' }}
            </button>
            <button class="btn small ghost" type="button" @click="clearSlotTags(slot)">
              清空
            </button>
          </div>
        </div>
      </div>
    </section> -->

    <!-- 编辑文章 -->
    <div v-if="showDialog && editing" class="modal-mask">
      <div class="modal">
        <div class="modal-header">
          <h3>编辑文章</h3>
          <button class="modal-close" type="button" @click="closeDialog">×</button>
        </div>
        <form class="form" @submit.prevent="submitEdit">
          <label>标题</label>
          <input v-model="editing.title" class="input" type="text" required />

          <label>描述</label>
          <textarea v-model="editing.description" class="input" rows="3" />

          <div class="inline-flags">
            <label class="checkbox-row">
              <input v-model="editing.isTop" type="checkbox" />
              <span>置顶</span>
            </label>
            <label class="checkbox-row">
              <input v-model="editing.isFeatured" type="checkbox" />
              <span>推荐</span>
            </label>
          </div>

          <label>权重（数字越大越靠前）</label>
          <input v-model.number="editing.sortWeight" class="input" type="number" />

          <div class="modal-footer">
            <button class="btn" type="button" @click="closeDialog" :disabled="saving">
              取消
            </button>
            <button class="btn primary" type="submit" :disabled="saving">
              {{ saving ? '保存中...' : '保存' }}
            </button>
          </div>
        </form>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch, type Ref } from 'vue'
import { navigateTo } from '#app'
import { useAuth } from '@/composables/useAuth'
import { useAuthToken, useApi } from '@/composables/useApi'

definePageMeta({
  layout: 'admin',
  adminTitle: '文章管理',
  adminSub: '按标题、描述或作者过滤，支持置顶、推荐与菜单标签配置',
})

interface ArticleItem {
  slug: string
  title: string
  description: string | null
  body?: string
  tagList?: string[]
  isTop?: boolean
  isFeatured?: boolean
  sortWeight?: number
  createdAt?: string
  updatedAt?: string
  author: {
    username: string
    image?: string | null
  }
  views?: number
}

interface AdminArticlesResponse {
  articles?: ArticleItem[]
}

interface TagsResponse {
  tags?: string[]
}

interface MenuSlot {
  slotKey: string
  label: string
  tags: string[]
}

interface MenuSlotsResponse {
  slots?: MenuSlot[]
}

const menuDefaults: MenuSlot[] = [
  { slotKey: 'news', label: '资讯广场', tags: [] },
  { slotKey: 'tutorial', label: '使用教程', tags: [] },
]

const { token } = useAuthToken()
const { user: authUser, fetchMe } = useAuth() as {
  user: Ref<{ roles?: string[] } | null>
  fetchMe: () => Promise<unknown>
}
const api = useApi()

const articles = ref<ArticleItem[]>([])
const loading = ref(false)
const saving = ref(false)
const showDialog = ref(false)
const hasAccess = ref(false)
const savingFlags = reactive<Record<string, boolean>>({})

const filters = reactive({
  search: '',
  author: '',
})

const editing = ref<{
  slug: string
  title: string
  description: string | null
  isTop: boolean
  isFeatured: boolean
  sortWeight: number
} | null>(null)

const menuSlots = ref<MenuSlot[]>([...menuDefaults])
const menuSaving = reactive<Record<string, boolean>>({})
const menuLoading = ref(false)
const tagsLoading = ref(false)
const allTags = ref<string[]>([])

const isAdmin = computed(() => {
  const rs = authUser.value?.roles
  return Array.isArray(rs) && rs.includes('admin')
})

async function ensureAccess(redirect = true): Promise<boolean> {
  if (!token.value) {
    hasAccess.value = false
    if (redirect) await navigateTo('/login')
    return false
  }
  if (!authUser.value) {
    try {
      await fetchMe()
    } catch (err) {
      console.error('[Admin] fetch user failed', err)
    }
  }
  const ok = isAdmin.value
  hasAccess.value = ok
  if (!ok && redirect) await navigateTo('/')
  return ok
}

function normalizeArticle(item: ArticleItem): ArticleItem {
  return {
    ...item,
    isTop: Boolean(item.isTop),
    isFeatured: Boolean(item.isFeatured),
    sortWeight: Number(item.sortWeight ?? 0),
  }
}

async function loadArticles(): Promise<void> {
  if (!hasAccess.value) return
  loading.value = true
  try {
    const query: Record<string, any> = { limit: 50, offset: 0 }
    const kw = filters.search.trim()
    if (kw) query.search = kw
    const author = filters.author.trim()
    if (author) query.author = author
    const res = (await api.get('/admin/articles', query)) as AdminArticlesResponse
    const list = Array.isArray(res.articles) ? res.articles : []
    articles.value = list.map((a) => normalizeArticle(a))
  } catch (err) {
    console.error('[Admin] load articles failed', err)
    alert('加载文章列表失败')
  } finally {
    loading.value = false
  }
}

function openEdit(article: ArticleItem): void {
  editing.value = {
    slug: article.slug,
    title: article.title,
    description: article.description || '',
    isTop: Boolean(article.isTop),
    isFeatured: Boolean(article.isFeatured),
    sortWeight: Number(article.sortWeight ?? 0),
  }
  showDialog.value = true
}

function closeDialog(): void {
  editing.value = null
  showDialog.value = false
}

async function submitEdit(): Promise<void> {
  if (!hasAccess.value || !editing.value) return
  saving.value = true
  try {
    await api.put(`/admin/articles/${editing.value.slug}`, {
      article: {
        title: editing.value.title,
        description: editing.value.description,
        isTop: editing.value.isTop,
        isFeatured: editing.value.isFeatured,
        sortWeight: editing.value.sortWeight,
      },
    })
    closeDialog()
    await loadArticles()
  } catch (err: any) {
    console.error('[Admin] save article failed', err)
    alert(err?.statusMessage || '保存文章失败')
  } finally {
    saving.value = false
  }
}

async function deleteArticle(article: ArticleItem): Promise<void> {
  if (!hasAccess.value || !article?.slug) return
  if (!confirm(`确定删除文章「${article.title}」吗？`)) return
  try {
    await api.del(`/admin/articles/${article.slug}`)
    await loadArticles()
  } catch (err: any) {
    console.error('[Admin] delete article failed', err)
    alert(err?.statusMessage || '删除文章失败')
  }
}

async function updateArticleFlags(article: ArticleItem, patch: Partial<ArticleItem>): Promise<void> {
  if (!hasAccess.value || !article?.slug) return
  if (savingFlags[article.slug]) return
  savingFlags[article.slug] = true
  try {
    await api.put(`/admin/articles/${article.slug}`, { article: patch })
    Object.assign(article, normalizeArticle({ ...article, ...patch }))
  } catch (err: any) {
    console.error('[Admin] update flags failed', err)
    alert(err?.statusMessage || '更新失败')
  } finally {
    delete savingFlags[article.slug]
  }
}

async function toggleTop(article: ArticleItem): Promise<void> {
  await updateArticleFlags(article, { isTop: !article.isTop })
}

async function toggleFeatured(article: ArticleItem): Promise<void> {
  await updateArticleFlags(article, { isFeatured: !article.isFeatured })
}

async function setSortWeight(article: ArticleItem): Promise<void> {
  const current = Number(article.sortWeight ?? 0)
  const input = prompt('设置权重（整数，越大越靠前）', String(current))
  if (input === null) return
  const num = Number(input)
  if (Number.isNaN(num)) {
    alert('请输入数字')
    return
  }
  await updateArticleFlags(article, { sortWeight: num })
}

async function loadAllTags(): Promise<void> {
  tagsLoading.value = true
  try {
    const res = (await api.get('/tags')) as TagsResponse
    allTags.value = Array.isArray(res.tags) ? res.tags : []
  } catch (err) {
    console.error('[Admin] load tags failed', err)
    allTags.value = []
  } finally {
    tagsLoading.value = false
  }
}

function mergeMenuSlots(serverSlots: MenuSlot[] | undefined): MenuSlot[] {
  const serverMap = new Map<string, MenuSlot>()
  for (const s of serverSlots || []) {
    serverMap.set(s.slotKey, {
      slotKey: s.slotKey,
      label: s.label || s.slotKey,
      tags: Array.isArray(s.tags) ? [...s.tags] : [],
    })
  }
  return menuDefaults.map((d) => {
    const matched = serverMap.get(d.slotKey)
    return {
      slotKey: d.slotKey,
      label: matched?.label || d.label,
      tags: matched?.tags || [],
    }
  })
}

async function loadMenuSlots(): Promise<void> {
  if (!hasAccess.value) return
  menuLoading.value = true
  try {
    const res = (await api.get('/admin/menu-slots')) as MenuSlotsResponse
    menuSlots.value = mergeMenuSlots(res.slots)
  } catch (err) {
    console.error('[Admin] load menu slots failed', err)
    alert('加载菜单标签配置失败')
  } finally {
    menuLoading.value = false
  }
}

async function saveMenuSlot(slot: MenuSlot): Promise<void> {
  if (!hasAccess.value) return
  if (menuSaving[slot.slotKey]) return
  menuSaving[slot.slotKey] = true
  try {
    const res = (await api.put(`/admin/menu-slots/${slot.slotKey}`, {
      slot: { tags: slot.tags, label: slot.label },
    })) as { slot?: MenuSlot }
    if (res?.slot) {
      Object.assign(slot, {
        tags: Array.isArray(res.slot.tags) ? res.slot.tags : [],
        label: res.slot.label || slot.label,
      })
    }
  } catch (err: any) {
    console.error('[Admin] save menu slot failed', err)
    alert(err?.statusMessage || '保存菜单标签失败')
  } finally {
    delete menuSaving[slot.slotKey]
  }
}

function clearSlotTags(slot: MenuSlot): void {
  slot.tags = []
}

onMounted(async () => {
  if (await ensureAccess(true)) {
    await Promise.all([loadArticles(), loadAllTags(), loadMenuSlots()])
  }
})

watch(
  () => authUser.value?.roles,
  async () => {
    if (await ensureAccess(false)) {
      await Promise.all([loadArticles(), loadAllTags(), loadMenuSlots()])
    }
  },
)

watch(
  () => token.value,
  async () => {
    if (await ensureAccess(false)) {
      await Promise.all([loadArticles(), loadAllTags(), loadMenuSlots()])
    }
  },
)
</script>

<style scoped>
.admin-page {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.card {
  background: #ffffff;
  border-radius: 16px;
  border: 1px solid #e5e7eb;
  padding: 14px 16px;
  box-shadow: 0 12px 32px rgba(15, 23, 42, 0.08);
}

.filter-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
}

.input {
  padding: 8px 10px;
  border-radius: 10px;
  border: 1px solid #d1d5db;
  min-width: 180px;
  font-size: 14px;
  background: #fff;
}

.btn {
  padding: 8px 14px;
  border-radius: 999px;
  border: 1px solid #d1d5db;
  background: #fff;
  cursor: pointer;
  transition: all 0.18s ease;
}

.btn.primary {
  background: #111827;
  color: #fff;
  border-color: #111827;
}

.btn.ghost {
  border-color: #e5e7eb;
  background: #fff;
}

.btn.danger {
  color: #b91c1c;
  border-color: #fecdd3;
  background: #fff5f5;
}

.btn.small {
  padding: 6px 10px;
  font-size: 13px;
}

.table-card {
  padding: 10px 12px;
}

.table-wrapper {
  overflow-x: auto;
}

.table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
}

.table th,
.table td {
  padding: 9px 10px;
  border-bottom: 1px solid #f3f4f6;
  text-align: left;
}

.table th {
  color: #6b7280;
  font-size: 13px;
  font-weight: 500;
}

.article-title {
  font-weight: 600;
  color: #0f172a;
}

.article-desc {
  margin-top: 4px;
  color: #4b5563;
  font-size: 13px;
}

.user-meta {
  margin-top: 2px;
  color: #9ca3af;
  font-size: 12px;
}

.tag-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.tag-chip {
  display: inline-flex;
  align-items: center;
  padding: 4px 8px;
  border-radius: 12px;
  background: #f3f4f6;
  color: #4b5563;
  font-size: 12px;
}

.flag-cell {
  min-width: 80px;
}

.weight-cell {
  display: flex;
  align-items: center;
  gap: 6px;
}

.weight-value {
  font-weight: 600;
  color: #111827;
}

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.empty {
  padding: 18px 6px;
  text-align: center;
  color: #9ca3af;
}

.muted {
  color: #9ca3af;
}

.modal-mask {
  position: fixed;
  inset: 0;
  background: rgba(15, 23, 42, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 30;
}

.modal {
  width: min(520px, 94vw);
  background: #fff;
  border-radius: 16px;
  padding: 16px;
  box-shadow: 0 24px 60px rgba(15, 23, 42, 0.45);
  display: flex;
  flex-direction: column;
}

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
}

.modal-close {
  background: none;
  border: none;
  font-size: 20px;
  cursor: pointer;
  color: #6b7280;
}

.form {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.form label {
  font-size: 13px;
  color: #6b7280;
}

.inline-flags {
  display: flex;
  gap: 12px;
  margin-top: 4px;
}

.checkbox-row {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: #374151;
}

.modal-footer {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 10px;
}

.menu-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.menu-title {
  font-weight: 600;
  font-size: 16px;
}

.menu-sub {
  color: #6b7280;
  font-size: 13px;
}

.menu-slots {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 12px;
  margin-top: 12px;
}

.menu-slot {
  border: 1px solid #e5e7eb;
  border-radius: 12px;
  padding: 12px;
  background: #fafafa;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.menu-slot-title {
  font-weight: 600;
}

.menu-slot-sub {
  font-size: 13px;
  color: #6b7280;
}

.menu-slot-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
</style>
