<template>
  <div class="community-page">
    <div class="community-shell">
      <header class="community-header">
        <div class="header-meta">
          <h1 class="title">社区</h1>
          <p class="desc">社区动态流 · 热门/最新内容，支持搜索、标签筛选、置顶</p>
        </div>
        <div class="header-actions">
          <button class="cta-btn" type="button" @click="goCreateArticle">
            发布文章参与讨论
          </button>
          <button class="btn" type="button" @click="() => loadArticles(true)" :disabled="loading">
            {{ loading ? '加载中...' : '刷新' }}
          </button>
        </div>
      </header>

      <section class="toolbar">
        <div class="toolbar-left">
          <div class="input-shell">
            <input
              v-model.trim="keyword"
              class="input"
              type="search"
              placeholder="搜索标题 / 描述 / 作者..."
            />
            <span class="input-icon">🔍</span>
          </div>
          <div class="chips-filter">
            <button
              v-for="tag in availableTags"
              :key="tag || 'all'"
              type="button"
              class="chip"
              :class="{ active: selectedTag === tag }"
              @click="selectedTag = tag"
            >
              {{ tag || '全部' }}
            </button>
          </div>
        </div>
        <div class="toolbar-right">
          <div class="stat-pill">
            <span class="stat-label">文章</span>
            <span class="stat-value">{{ filteredArticles.length }}</span>
          </div>
          <div class="stat-pill">
            <span class="stat-label">标签</span>
            <span class="stat-value">{{ availableTags.length - 1 }}</span>
          </div>
        </div>
      </section>

      <div v-if="loading" class="state">加载中...</div>
      <div v-else-if="error" class="state error">加载失败：{{ error }}</div>
      <div v-else-if="filteredArticles.length === 0" class="state">暂无动态</div>
      <div v-else class="list">
        <article
          v-for="it in filteredArticles"
          :key="it.slug"
          class="feed-card"
          :ref="el => setCardRef(it.slug, el)"
          :data-transition-slug="it.slug"
          @click="goDetail(it.slug)"
        >
          <div class="feed-top">
            <div class="feed-author">
              <div class="avatar">{{ (it.author?.username || '佚名').slice(0, 1).toUpperCase() }}</div>
              <div>
                <div class="author-name">{{ it.author?.username || '匿名用户' }}</div>
                <div class="author-meta">
                  <span v-if="it.createdAt">发布 {{ formatDate(it.createdAt) }}</span>
                  <span v-if="it.tagList?.length"> · {{ it.tagList.join(' / ') }}</span>
                </div>
              </div>
            </div>
            <div class="feed-actions">
              <button
                v-if="isAdmin || it.isTop"
                class="fire-btn"
                :class="{ active: it.isTop, readonly: !isAdmin }"
                :disabled="!isAdmin || savingMap[it.slug]"
                @click.stop="isAdmin ? toggleTop(it) : undefined"
                aria-label="置顶"
              >
                <svg viewBox="0 0 1024 1024" aria-hidden="true">
                  <path
                    d="M437.376 42.667c232.277 92.587 240 364.245 240.256 382.293v.896l72.107-71.85c325.973 401.365 24.576 573.952-110.507 627.328 1.792-3.413 3.499-6.997 5.248-10.709 21.376-45.44 33.237-91.776 29.995-137.088a173.355 173.355 0 0 0-59.734-120.576l-8.192-6.912c-40.192-32-64-65.024-75.477-97.621a119.168 119.168 0 0 1-7.424-46.763l.683-6.485 23.765-94.848-83.627 51.285c-151.381 92.885-201.387 213.547-171.179 342.485 10.155 43.435 28.715 84.395 52.48 122.027-184.917-78.379-225.963-201.941-216.491-322.432 7.979-101.931 75.392-182.016 146.432-257.408l17.792-18.773C391.509 274.688 487.723 177.92 437.376 42.667Z"
                    :fill="it.isTop ? '#ee3921' : '#cdcdcd'"
                  />
                  <path
                    d="M449.92 620.16c13.568 47.957 46.507 98.389 105.984 145.792 24.363 19.413 36.01 42.667 38.016 70.827 2.133 29.483-6.485 63.317-22.613 97.493l-5.291 10.752-5.461 10.155-3.669 6.187-5.291.597c-16.64 1.707-35.797 2.731-57.216 2.731-21.291 0-39.467-1.024-54.485-2.688l-5.845-.725c-7.531-8.789-14.613-18.86-20.651-29.227-30.272-52.523-45.632-86.923-53.739-121.398-18.304-78.208 2.219-149.931 77.056-214.4Z"
                    :fill="it.isTop ? '#ee3921' : '#cdcdcd'"
                    :opacity="it.isTop ? '0.5' : '0.5'"
                  />
                </svg>
              </button>
              <!-- <button type="button" class="link-btn" @click.stop="goDetail(it.slug)">查看</button> -->
            </div>
          </div>

          <div class="feed-body">
            <div class="feed-title" :data-hero-title="it.slug">{{ it.title || '未命名文章' }}</div>
            <div class="feed-desc" v-if="it.description">{{ it.description }}</div>
            <div class="feed-meta">
              <span>浏览 {{ it.views ?? 0 }}</span>
              <span> · 点赞 {{ getLikes(it) }}</span>
            </div>
          </div>

          <div class="feed-foot">
            <div class="tag-list" v-if="it.tagList?.length">
              <span v-for="t in it.tagList" :key="`${it.slug}-${t}`" class="tag-chip">{{ t }}</span>
            </div>
            <button class="join-btn" type="button" @click.stop="handleCardClick(it.slug)">
              参与讨论 →
            </button>
          </div>
        </article>
        <div ref="sentinelRef" class="sentinel">
          <span v-if="loadingMore">正在加载更多...</span>
          <span v-else-if="!hasMore">已到底部</span>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed, onMounted, onBeforeUnmount, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useApi, useAuthToken } from '@/composables/useApi'
import { useAuth } from '@/composables/useAuth'
import { useArticleNavContext } from '@/composables/useArticleNavContext'
import { useSharedTransition } from '@/composables/useSharedTransition'

interface ArticleItem {
  slug: string
  title: string
  description?: string | null
  cover?: string | null
  tagList?: string[]
  isTop?: boolean
  sortWeight?: number
  createdAt?: string
  views?: number
  favorited?: boolean
  favoritesCount?: number
  favorites_count?: number
  author?: {
    username?: string
    image?: string | null
  }
}

interface ArticlesResponse {
  articles?: ArticleItem[]
}

const api = useApi()
const router = useRouter()
const { user: authUser, fetchMe } = useAuth() as any
const { token } = useAuthToken()
const navCtx = useArticleNavContext()
const transition = useSharedTransition()

const articles = ref<ArticleItem[]>([])
const loading = ref(false)
const loadingMore = ref(false)
const error = ref('')
const keyword = ref('')
const selectedTag = ref<string | null>(null)
const defaultCover = '/cover.jpg'
const offset = ref(0)
const hasMore = ref(true)
const currentMode = ref<'and' | 'or'>('and')
const PAGE_SIZE = 20
const sentinelRef = ref<HTMLElement | null>(null)
let observer: IntersectionObserver | null = null
const savingMap = ref<Record<string, boolean>>({})
const cardRefs = new Map<string, HTMLElement>()

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

function getLikes(it: ArticleItem): number {
  return Number(it.favoritesCount ?? it.favorites_count ?? 0)
}

function formatDate(v?: string): string {
  if (!v) return ''
  try {
    const d = new Date(v)
    if (Number.isNaN(d.getTime())) return ''
    return d.toLocaleDateString()
  } catch {
    return ''
  }
}

async function loadArticles(reset = false): Promise<void> {
  if (loading.value || loadingMore.value) return
  if (reset) {
    loading.value = true
    articles.value = []
    offset.value = 0
    hasMore.value = true
    currentMode.value = 'and'
    error.value = ''
  } else {
    if (!hasMore.value) return
    loadingMore.value = true
  }

  try {
    const res = (await api.get('/articles/menu/community', {
      limit: PAGE_SIZE,
      offset: offset.value,
      mode: currentMode.value,
    })) as ArticlesResponse
    let list = Array.isArray(res.articles) ? res.articles : []

    if (offset.value === 0 && currentMode.value === 'and' && list.length === 0) {
      currentMode.value = 'or'
      const orRes = (await api.get('/articles/menu/community', {
        limit: PAGE_SIZE,
        offset: 0,
        mode: currentMode.value,
      })) as ArticlesResponse
      list = Array.isArray(orRes.articles) ? orRes.articles : []
    }

    const merged = reset ? list : [...articles.value, ...list]
    articles.value = merged
    offset.value += list.length
    hasMore.value = list.length === PAGE_SIZE
    navCtx.setList('community', merged.map((a) => a.slug).filter(Boolean))
  } catch (err: any) {
    console.error('[Community] load articles failed', err)
    error.value = err?.statusMessage || err?.message || '加载失败'
  } finally {
    loading.value = false
    loadingMore.value = false
  }
}

const availableTags = computed(() => {
  const set = new Set<string>()
  for (const a of articles.value) {
    for (const t of a.tagList || []) {
      const tag = String(t || '').trim()
      if (tag) set.add(tag)
    }
  }
  const list = ['全部', ...Array.from(set)]
  return list.map((t) => (t === '全部' ? null : t)) as (string | null)[]
})

const filteredArticles = computed(() => {
  const kw = keyword.value.toLowerCase()
  const list = articles.value.filter((a) => {
    const matchTag = selectedTag.value ? (a.tagList || []).includes(selectedTag.value) : true
    const matchKw = kw
      ? [a.title, a.description, a.author?.username]
          .map((v) => (v || '').toLowerCase())
          .some((v) => v.includes(kw))
      : true
    return matchTag && matchKw
  })

  return [...list].sort((a, b) => {
    const topA = a.isTop ? 1 : 0
    const topB = b.isTop ? 1 : 0
    if (topA !== topB) return topB - topA
    const wA = Number(a.sortWeight ?? 0)
    const wB = Number(b.sortWeight ?? 0)
    if (wA !== wB) return wB - wA
    const tA = new Date(a.createdAt || 0).getTime()
    const tB = new Date(b.createdAt || 0).getTime()
    return tB - tA
  })
})

function setCardRef(slug: string, el: any): void {
  if (el && slug) {
    cardRefs.set(slug, el as HTMLElement)
  }
}

function handleCardClick(slug: string): void {
  const cardEl = cardRefs.get(slug)
  if (cardEl) {
    transition.markSource(slug, cardEl)
  }
  goDetail(slug)
}

function goDetail(slug: string): void {
  const cardEl = cardRefs.get(slug)
  if (cardEl) {
    transition.markSource(slug, cardEl)
  }
  router.push(`/articles/${slug}`)
}

function goCreateArticle(): void {
  router.push('/articles/new')
}

async function ensureUser(): Promise<void> {
  if (!token.value) return
  if (!authUser.value) {
    try {
      await fetchMe()
    } catch (err) {
      console.warn('[Community] fetchMe failed', err)
    }
  }
}

async function updateFlags(
  article: ArticleItem,
  patch: { isTop?: boolean; sortWeight?: number },
): Promise<void> {
  if (!isAdmin.value || !article?.slug) return
  if (savingMap.value[article.slug]) return
  savingMap.value[article.slug] = true
  try {
    await api.put(`/admin/articles/${article.slug}`, { article: patch })
    Object.assign(article, patch)
  } catch (err: any) {
    console.error('[Community] update flags failed', err)
    alert(err?.statusMessage || '更新失败')
  } finally {
    delete savingMap.value[article.slug]
  }
}

async function toggleTop(article: ArticleItem): Promise<void> {
  const next = !article.isTop
  const sortWeight = next ? Math.floor(Date.now() / 1000) : 0
  await updateFlags(article, { isTop: next, sortWeight })
}

function setupObserver(): void {
  if (!('IntersectionObserver' in window)) return
  observer = new IntersectionObserver(
    (entries) => {
      for (const entry of entries) {
        if (entry.isIntersecting) {
          loadArticles(false)
        }
      }
    },
    { rootMargin: '200px 0px 200px 0px', threshold: 0 },
  )
  if (sentinelRef.value) {
    observer.observe(sentinelRef.value)
  }
}

onMounted(async () => {
  await ensureUser()
  await loadArticles(true)
  setupObserver()
})

onBeforeUnmount(() => {
  if (observer) observer.disconnect()
})
</script>

<style scoped>
.community-page {
  margin-top: 5%;
  padding: 20px 0 32px;
  display: flex;
  justify-content: center;
  background: var(--soft-page-bg);
  min-height: 100vh;
}

.community-shell {
  width: min(1100px, 96vw);
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.community-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}

.header-meta {
  flex: 1 1 520px;
}

.header-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  justify-content: flex-end;
}

.title {
  margin: 0;
  font-size: 24px;
  font-weight: 800;
}

.desc {
  margin: 4px 0 0;
  color: #6b7280;
}

.btn {
  padding: 10px 14px;
  border-radius: 12px;
  border: 1px solid #d1d5db;
  background: #fff;
  cursor: pointer;
  transition: all 0.18s ease;
  font-weight: 600;
}

.btn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.cta-btn {
  padding: 10px 16px;
  border-radius: 14px;
  border: none;
  background: linear-gradient(120deg, #ff7a7a, #ffb347 50%, #7ad7f0);
  color: #fff;
  cursor: pointer;
  font-weight: 800;
  letter-spacing: 0.5px;
  box-shadow: 0 12px 30px rgba(255, 122, 122, 0.35);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.cta-btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 18px 38px rgba(255, 179, 71, 0.4);
}

.toolbar {
  background: #fff;
  border: 1px solid #e0e7ff;
  border-radius: 14px;
  padding: 12px 14px;
  display: flex;
  justify-content: space-between;
  gap: 12px;
  box-shadow: 0 10px 28px rgba(15, 23, 42, 0.05);
  flex-wrap: wrap;
}

.toolbar-left {
  display: flex;
  flex: 1 1 60%;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.input {
  padding: 10px 12px;
  border-radius: 10px;
  border: 1px solid #d1d5db;
  min-width: 240px;
  font-size: 14px;
  background: #fff;
  padding-left: 32px;
}

.input-shell {
  position: relative;
}

.input-icon {
  position: absolute;
  left: 10px;
  top: 50%;
  transform: translateY(-50%);
  color: #9ca3af;
  font-size: 12px;
}

.chips-filter {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.chip {
  border: 1px solid #e5e7eb;
  background: #f9fafb;
  color: #374151;
  border-radius: 999px;
  padding: 6px 12px;
  cursor: pointer;
  transition: all 0.15s ease;
}

.chip.active {
  background: #111827;
  color: #fff;
  border-color: #111827;
}

.toolbar-right {
  display: flex;
  align-items: center;
  gap: 10px;
}

.stat-pill {
  padding: 8px 12px;
  border-radius: 12px;
  border: 1px solid #e5e7eb;
  background: #f0f4ff;
  min-width: 90px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.stat-label {
  color: #6b7280;
  font-size: 12px;
}

.stat-value {
  font-size: 18px;
  font-weight: 700;
  color: #0f172a;
}

.list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 40px;
  align-items: start;
}

.feed-card {
  border: 1px solid #e5e7eb;
  border-radius: 16px;
  background: #fff;
  box-shadow: 0 10px 30px rgba(15, 23, 42, 0.08);
  padding: 12px 12px 3px 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  height: 100%;
}

.feed-top {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.feed-author {
  display: flex;
  align-items: center;
  gap: 10px;
}

.avatar {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: linear-gradient(135deg, #a855f7, #6366f1);
  color: #fff;
  display: grid;
  place-items: center;
  font-weight: 700;
}

.author-name {
  font-weight: 700;
  color: #0f172a;
}

.author-meta {
  color: #9ca3af;
  font-size: 12px;
}

.feed-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

.feed-body {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.feed-title {
  font-size: 18px;
  font-weight: 800;
}

.feed-desc {
  color: #4b5563;
  line-height: 1.5;
}

.feed-meta {
  color: #6b7280;
  font-size: 13px;
}

.feed-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px;
}

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

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

.fire-btn {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 1px solid #e5e7eb;
  background: #fff;
  box-shadow: 0 6px 18px rgba(15, 23, 42, 0.12);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 6px;
  transition: all 0.15s ease;
}

.fire-btn svg {
  width: 16px;
  height: 16px;
}

.fire-btn.active {
  border-color: #ee3921;
}

.fire-btn.readonly {
  cursor: default;
  opacity: 0.9;
  box-shadow: none;
}

.link-btn {
  padding: 8px 12px;
  border-radius: 999px;
  border: 1px solid #111827;
  background: #111827;
  color: #fff;
  cursor: pointer;
  transition: all 0.15s ease;
}

.link-btn:hover {
  background: #0b1224;
}

.join-btn {
  padding: 6px 12px;
  border-radius: 10px;
  border: 1px solid #e0e7ff;
  background: #eef2ff;
  color: #4338ca;
  cursor: pointer;
  font-size: 13px;
}

.join-btn:hover {
  border-color: #6366f1;
  color: #111827;
}

.state {
  padding: 28px 10px;
  text-align: center;
  color: #6b7280;
}

.state.error {
  color: #b91c1c;
}

.sentinel {
  text-align: center;
  color: #9ca3af;
  padding: 12px 0;
  font-size: 13px;
  grid-column: 1 / -1;
}

@media (max-width: 960px) {
  .community-shell {
    width: 100%;
    padding: 0 10px;
  }
  .community-header {
    align-items: flex-start;
  }
  .list {
    grid-template-columns: 1fr;
  }
}
</style>
