<script setup lang="ts">
interface CardItem {
  id: number
  type: 'text' | 'image' | 'image-text' | 'portfolio' | 'project'
  image?: string
  images?: string[]
  title: string
  description?: string
  tags?: string[]
  aspectRatio: number
}

const allItems = ref<CardItem[]>([])
const page = ref(1)
const hasMore = ref(true)
const loading = ref(false)
const initLoading = ref(true)
const sentinel = ref<HTMLElement | null>(null)

const columnCount = ref(4)
const columns = ref<CardItem[][]>([[], [], [], []])
const columnHeights = ref<number[]>([0, 0, 0, 0])

function getColumnWidth() {
  const padding = 48
  const maxWidth = 1400
  const containerWidth = Math.min(window.innerWidth - padding, maxWidth)
  const gap = 20
  return (containerWidth - gap * (columnCount.value - 1)) / columnCount.value
}

function estimateCardHeight(item: CardItem) {
  const colWidth = getColumnWidth()
  if (item.type === 'text') {
    const textWidth = colWidth - 48
    const charsPerLine = Math.max(1, Math.floor(textWidth / 13.5))
    const charCount = (item.description || '').length
    const lines = Math.ceil(charCount / charsPerLine)
    return 109 + lines * 24
  }
  return colWidth / item.aspectRatio
}

function addToShortestColumn(item: CardItem) {
  let minIdx = 0
  for (let i = 1; i < columnCount.value; i++) {
    if (columnHeights.value[i] < columnHeights.value[minIdx]) minIdx = i
  }
  columns.value[minIdx].push(item)
  columnHeights.value[minIdx] += estimateCardHeight(item)
}

function distributeAll() {
  const n = columnCount.value
  columns.value = Array.from({ length: n }, () => [])
  columnHeights.value = new Array(n).fill(0)
  for (const item of allItems.value) {
    addToShortestColumn(item)
  }
}

let resizeTimer: ReturnType<typeof setTimeout> | null = null
function onResize() {
  if (resizeTimer) clearTimeout(resizeTimer)
  resizeTimer = setTimeout(() => {
    updateColumns()
  }, 150)
}

function updateColumns() {
  const w = window.innerWidth
  let n: number
  if (w < 640) n = 1
  else if (w < 768) n = 2
  else if (w < 1024) n = 3
  else if (w < 1280) n = 4
  else n = 5

  if (n !== columnCount.value) {
    columnCount.value = n
    distributeAll()
  }
}

async function loadMore() {
  if (loading.value || !hasMore.value) return
  loading.value = true
  try {
    const data = await $fetch<{ items: CardItem[]; hasMore: boolean }>('/api/cards', {
      query: { page: page.value, pageSize: 12 },
    })
    for (const item of data.items) {
      allItems.value.push(item)
      addToShortestColumn(item)
    }
    hasMore.value = data.hasMore
    page.value++
  } finally {
    loading.value = false
    initLoading.value = false
  }

  await nextTick()
  tryLoadMoreIfSentinelVisible()
}

function tryLoadMoreIfSentinelVisible() {
  if (!sentinel.value || loading.value || !hasMore.value) return
  const rect = sentinel.value.getBoundingClientRect()
  if (rect.top < window.innerHeight + 300) {
    loadMore()
  }
}

onMounted(() => {
  updateColumns()
  window.addEventListener('resize', onResize)
  loadMore()
})

onUnmounted(() => {
  window.removeEventListener('resize', onResize)
  if (resizeTimer) clearTimeout(resizeTimer)
})

let observer: IntersectionObserver | null = null
watch(sentinel, (el) => {
  observer?.disconnect()
  if (!el) return
  observer = new IntersectionObserver(
    ([entry]) => {
      if (entry.isIntersecting) loadMore()
    },
    { rootMargin: '300px' },
  )
  observer.observe(el)
})
</script>

<template>
  <div class="home">
    <header class="page-header">
      <h1>发现</h1>
      <p class="subtitle">灵感与美学的无声对话</p>
    </header>

    <div class="masonry">
      <div
        v-for="(col, ci) in columns"
        :key="ci"
        class="masonry-col"
      >
        <template v-for="(item, ri) in col" :key="item.id">
          <div
            class="card-reveal"
            :style="{ '--enter-delay': `${ci * 80 + ri * 60}ms` }"
          >
            <IndexWaterfallCard
              v-if="item.type === 'image-text'"
              :image="item.image!"
              :title="item.title"
              :description="item.description!"
              :aspect-ratio="item.aspectRatio"
            />
            <IndexWaterfallTextCard
              v-else-if="item.type === 'text'"
              :title="item.title"
              :description="item.description!"
              :aspect-ratio="item.aspectRatio"
            />
            <IndexWaterfallImageCard
              v-else-if="item.type === 'image'"
              :image="item.image!"
              :title="item.title"
              :aspect-ratio="item.aspectRatio"
            />
            <IndexWaterfallPortfolioCard
              v-else-if="item.type === 'portfolio'"
              :images="item.images!"
              :title="item.title"
              :description="item.description!"
              :aspect-ratio="item.aspectRatio"
            />
            <IndexWaterfallProjectCard
              v-else
              :image="item.image!"
              :title="item.title"
              :description="item.description!"
              :tags="item.tags!"
              :aspect-ratio="item.aspectRatio"
            />
          </div>
        </template>
      </div>
    </div>

    <div ref="sentinel" class="sentinel">
      <template v-if="initLoading">
        <div class="loading-bars">
          <span v-for="i in 3" :key="i" class="bar" :style="{ animationDelay: `${(i - 1) * 0.15}s` }" />
        </div>
      </template>
      <template v-else-if="loading">
        <div class="loading-bars">
          <span v-for="i in 3" :key="i" class="bar" :style="{ animationDelay: `${(i - 1) * 0.15}s` }" />
        </div>
      </template>
      <span v-else-if="hasMore" class="sentinel-text">继续滚动查看更多</span>
      <span v-else class="sentinel-text end">—— 已经到底了 ——</span>
    </div>
  </div>
</template>

<style scoped>
.home {
  max-width: 1400px;
  margin: 0 auto;
  padding: 0 24px;
}

/* ── Header: cream canvas, coral type ── */

.page-header {
  padding: clamp(64px, 10vw, 108px) 0 clamp(20px, 3vw, 32px);
  animation: header-reveal 0.8s var(--ease-out-expo) both;
}

@keyframes header-reveal {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.page-header h1 {
  font-family: var(--font-display);
  font-size: clamp(64px, 10vw, 132px);
  font-weight: 400;
  color: var(--color-primary);
  margin: 0;
  line-height: 0.88;
  letter-spacing: -0.02em;
}

.subtitle {
  color: var(--color-muted);
  font-size: clamp(16px, 2vw, 20px);
  font-weight: 400;
  margin: clamp(14px, 2.5vw, 24px) 0 0;
  letter-spacing: 0.06em;
  animation: fade-slide-up 0.7s var(--ease-out-expo) both;
  animation-delay: 200ms;
}

@keyframes fade-slide-up {
  from {
    opacity: 0;
    transform: translateY(16px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* ── Masonry ── */

.masonry {
  display: flex;
  gap: 20px;
  align-items: flex-start;
}

.masonry-col {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

/* ── Card reveal ── */

.card-reveal {
  animation: card-enter 0.65s var(--ease-out-expo) both;
  animation-delay: var(--enter-delay, 0ms);
}

@keyframes card-enter {
  from {
    opacity: 0;
    transform: translateY(24px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* ── Sentinel ── */

.sentinel {
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 56px 0 96px;
  min-height: 60px;
}

.sentinel-text {
  font-size: 14px;
  color: var(--color-muted-soft);
}

.sentinel-text.end {
  color: var(--color-hairline);
}

.loading-bars {
  display: flex;
  gap: 6px;
  align-items: center;
}

.bar {
  width: 5px;
  height: 20px;
  background: var(--color-primary);
  border-radius: 3px;
  opacity: 0.5;
  animation: pulse 0.9s ease-in-out infinite;
}

@keyframes pulse {
  0%, 100% { opacity: 0.3; transform: scaleY(0.6); }
  50% { opacity: 0.8; transform: scaleY(1); }
}

/* ── Responsive ── */

@media (max-width: 768px) {
  .home {
    padding: 0 16px;
  }

  .page-header {
    margin: 16px 0 40px;
    padding: 40px 24px;
  }

  .page-header h1 {
    font-size: clamp(40px, 12vw, 64px);
  }
}
</style>
