<template>
  <view class="page">
    <view v-if="showNotice" class="notice-mask">
      <view class="notice-dialog">
        <view class="notice-title">提示</view>
        <view class="notice-content">由于价格浮动，当前暂不支持自行兑换商品，兑换请联系客服核对价格。</view>
        <view class="notice-actions">
          <view class="notice-check" @tap="toggleHideForever">
            <view class="check-box" :class="{ on: hideForever }"></view>
            <text class="check-text">不再显示</text>
          </view>
          <button class="notice-btn" type="primary" @tap="onDismissNotice">我知道了</button>
        </view>
      </view>
    </view>
    <view v-if="loading" class="loading-wrap"><view class="spinner"></view></view>
    <view class="products-section" v-else>
      <view class="section-title">商品</view>
      <view class="toolbar">
        <input class="search" v-model="keyword" placeholder="搜索商品" confirm-type="search" @confirm="onSearchConfirm" />
        <view class="filters">
          <input class="price" type="number" v-model="minPrice" placeholder="最低价" />
          <text class="dash">-</text>
          <input class="price" type="number" v-model="maxPrice" placeholder="最高价" />
          <button class="apply-btn" size="mini" @tap="onApplyFilters">筛选</button>
        </view>
      </view>
    <view v-if="displayCount" class="products-columns">
      <view class="column" v-for="(col, ci) in columns" :key="ci">
        <view class="product-item" v-for="p in col" :key="p.id" @tap="onProductTap(p)">
          <view class="product-card">
            <view class="thumb-wrap">
              <image class="product-thumb" :class="{ visible: isLoaded(p) }" :src="p.image" mode="widthFix" lazy-load="true" @load="onImageLoad(p)" @error="onImageError(p)" />
              <view v-if="!isLoaded(p)" class="skeleton"></view>
              <view class="badge">
                <text class="badge-price" v-if="p.price !== null">￥{{ p.price }}</text>
                <text class="badge-points" v-if="p.points !== null">{{ p.points }}积分</text>
              </view>
            </view>
            <text class="product-title">{{ p.title }}</text>
            <view class="product-extra" v-if="p.stock !== null">
              <text class="stock">库存 {{ p.stock }}</text>
            </view>
          </view>
        </view>
      </view>
    </view>
    <view v-else class="empty">暂无商品</view>
  </view>
  </view>
</template>

<script setup>
import { onShow } from '@dcloudio/uni-app'
import { ref, computed } from 'vue'
import { request, authRequest } from '../../utils/request.js'

const products = ref([])
const columns = ref([[], []])
const colHeights = ref([0, 0])
const CACHE_KEY = 'products_cache_v1'
const TTL_MS = 10 * 60 * 1000
const loading = ref(false)
const keyword = ref('')
const minPrice = ref('')
const maxPrice = ref('')
const displayCount = computed(() => (columns.value[0].length + columns.value[1].length))
const loadedMap = ref({})
const showNotice = ref(false)
const hideForever = ref(false)
const skipReloadOnce = ref(false)
function getKey(p) { return String((p && p.id) ?? '') + '|' + String((p && p.image) ?? '') }
function unwrap(list) {
  if (Array.isArray(list)) return list
  const obj = list || {}
  const data = obj.data || {}
  const arr = obj.list || obj.items || data.list || data.items || data
  return Array.isArray(arr) ? arr : []
}
function cleanUrl(u) {
  const s = String(u || '').trim()
  const m = s.match(/https?:\/\/[^\s'"`]+/)
  if (m && m[0]) return m[0]
  return s.replace(/[`'\"]/g, '').trim()
}
function apiGet(url, data = {}) {
  const token = uni.getStorageSync('token')
  const fn = token ? authRequest : request
  return fn({ url, method: 'GET', data })
}

function getCachedProducts() {
  try {
    const obj = uni.getStorageSync(CACHE_KEY)
    if (obj && Array.isArray(obj.data) && typeof obj.ts === 'number') {
      const fresh = Date.now() - obj.ts < TTL_MS
      if (fresh) return obj.data
    }
  } catch (_) {}
  return null
}

function setCachedProducts(list) {
  try {
    uni.setStorageSync(CACHE_KEY, { data: Array.isArray(list) ? list : [], ts: Date.now() })
  } catch (_) {}
}

function estimateHeight(p) {
  const base = 220
  const len = String(p.title || '').length
  const lines = Math.min(2, Math.ceil(len / 12))
  const titleH = lines * 36
  const stockH = p.stock !== null && p.stock !== undefined ? 34 : 0
  const padding = 28
  return base + titleH + stockH + padding
}

function distributeToColumns(list) {
  const arr = Array.isArray(list) ? list : []
  const cols = Array.from({ length: 2 }, () => [])
  const hs = [0, 0]
  for (let i = 0; i < arr.length; i++) {
    const h = estimateHeight(arr[i])
    const idx = hs[0] <= hs[1] ? 0 : 1
    cols[idx].push(arr[i])
    hs[idx] += h
  }
  columns.value = cols
  colHeights.value = hs
  const presentKeys = new Set(arr.map(getKey))
  const next = {}
  const prev = loadedMap.value || {}
  for (const k in prev) { if (presentKeys.has(k)) next[k] = prev[k] }
  loadedMap.value = next
}

function extractListAndTotal(payload) {
  if (Array.isArray(payload)) return { list: payload, total: payload.length }
  const obj = payload || {}
  const data = obj.data || {}
  const list = obj.list || obj.items || data.list || data.items || []
  const totalRaw = obj.total ?? data.total
  const total = typeof totalRaw === 'number' ? totalRaw : (Array.isArray(list) ? list.length : 0)
  return { list: Array.isArray(list) ? list : [], total }
}

function normalizeProducts(list) {
  const arr = unwrap(list)
  return arr.map((i, idx) => ({
    id: i.id ?? i.productId ?? i._id ?? i.sku_id ?? String(idx),
    image: cleanUrl(i.main_image ?? i.imageUrl ?? i.image_url ?? i.image ?? i.img ?? i.pic ?? ''),
    title: i.title ?? i.name ?? i.product_name ?? i.sku_name ?? '',
    price: i.price_sale ?? i.price ?? i.price_min ?? i.amount ?? null,
    points: i.points_required ?? i.points ?? i.integral ?? null,
    stock: i.stock ?? i.inventory ?? i.quantity ?? null,
    link: cleanUrl(i.linkUrl ?? i.link_url ?? i.link ?? i.url ?? '')
  })).filter(i => i.image || i.title)
}

function onProductTap(p) {
  const imgs = (Array.isArray(products.value) ? products.value : []).map(x => x.image).filter(Boolean)
  const current = p && p.image
  if (current) {
    skipReloadOnce.value = true
    try { uni.setStorageSync('shop_skip_reload_once', '1') } catch (_) {}
    uni.previewImage({ urls: imgs.length ? imgs : [current], current })
    return
  }
  if (p.link && /^\/.+/.test(p.link)) {
    uni.navigateTo({ url: p.link })
  }
}

function applyFilters() {
  const k = String(keyword.value || '').trim().toLowerCase()
  const min = Number(minPrice.value)
  const max = Number(maxPrice.value)
  const hasMin = !isNaN(min) && String(minPrice.value).trim() !== ''
  const hasMax = !isNaN(max) && String(maxPrice.value).trim() !== ''
  const list = Array.isArray(products.value) ? products.value : []
  const filtered = list.filter(p => {
    const title = String(p.title || '').toLowerCase()
    if (k && !title.includes(k)) return false
    const priceNum = typeof p.price === 'number' ? p.price : Number(p.price)
    if (hasMin) {
      if (isNaN(priceNum)) return false
      if (priceNum < min) return false
    }
    if (hasMax) {
      if (isNaN(priceNum)) return false
      if (priceNum > max) return false
    }
    return true
  })
  distributeToColumns(filtered)
}

function onSearchConfirm() { applyFilters() }
function onApplyFilters() { applyFilters() }

async function loadProducts() {
  try {
    const cached = getCachedProducts()
    if (cached) {
      products.value = cached
      distributeToColumns(cached)
      return
    }
    const first = await apiGet('/api/app/products', { page: 1 })
    const { list: firstList, total } = extractListAndTotal(first)
    const pageSize = 20
    const totalPages = Math.max(1, Math.ceil(((typeof total === 'number' ? total : 0)) / pageSize))
    if (totalPages <= 1) {
      const normalized = normalizeProducts(firstList)
      products.value = normalized
      distributeToColumns(normalized)
      setCachedProducts(normalized)
      return
    }
    const tasks = []
    for (let p = 2; p <= totalPages; p++) {
      tasks.push(apiGet('/api/app/products', { page: p }))
    }
    const results = await Promise.allSettled(tasks)
    const restLists = results.map(r => {
      if (r.status === 'fulfilled') {
        const { list } = extractListAndTotal(r.value)
        return Array.isArray(list) ? list : []
      }
      return []
    })
    const merged = [firstList, ...restLists].flat()
    const normalized = normalizeProducts(merged)
    products.value = normalized
    distributeToColumns(normalized)
    setCachedProducts(normalized)
  } catch (e) {
    products.value = []
    columns.value = [[], []]
    colHeights.value = [0, 0]
    const presentKeys = new Set([])
    const next = {}
    const prev = loadedMap.value || {}
    for (const k in prev) { if (presentKeys.has(k)) next[k] = prev[k] }
    loadedMap.value = next
  }
}

function isLoaded(p) { return !!(loadedMap.value && loadedMap.value[getKey(p)]) }
function onImageLoad(p) { const k = getKey(p); if (!k) return; loadedMap.value = { ...(loadedMap.value || {}), [k]: true } }
function onImageError(p) { const k = getKey(p); if (!k) return; const prev = { ...(loadedMap.value || {}) }; delete prev[k]; loadedMap.value = prev }

onShow(async () => {
  const token = uni.getStorageSync('token')
  const phoneBound = !!uni.getStorageSync('phone_bound')
  if (!token || !phoneBound) {
    uni.showModal({
      title: '提示',
      content: '请先登录并绑定手机号',
      confirmText: '去登录',
      success: (res) => {
        if (res.confirm) {
          uni.navigateTo({ url: '/pages/login/index' })
        }
      }
    })
    return
  }
  try {
    const sess = String(uni.getStorageSync('app_session_id') || '')
    const hiddenSess = String(uni.getStorageSync('shop_notice_hidden_session_id') || '')
    const hiddenThisSession = !!(sess && hiddenSess && hiddenSess === sess)
    showNotice.value = !hiddenThisSession
    hideForever.value = hiddenThisSession
  } catch (_) { showNotice.value = true; hideForever.value = false }
  try {
    const skip = !!uni.getStorageSync('shop_skip_reload_once')
    if (skipReloadOnce.value || skip) {
      skipReloadOnce.value = false
      uni.setStorageSync('shop_skip_reload_once', '')
      return
    }
  } catch (_) {}
  loading.value = true
  await loadProducts()
  loading.value = false
})

function toggleHideForever() { hideForever.value = !hideForever.value }
function onDismissNotice() {
  if (hideForever.value) {
    try {
      const sess = String(uni.getStorageSync('app_session_id') || '')
      if (sess) uni.setStorageSync('shop_notice_hidden_session_id', sess)
    } catch (_) {}
  }
  showNotice.value = false
}
</script>

<style scoped>
.page { padding: 24rpx }
.notice-mask { position: fixed; left: 0; top: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.45); z-index: 9999; display: flex; align-items: center; justify-content: center }
.notice-dialog { width: 86%; max-width: 640rpx; background: #fff; border-radius: 16rpx; overflow: hidden; box-shadow: 0 12rpx 24rpx rgba(0,0,0,0.18) }
.notice-title { font-size: 32rpx; font-weight: 600; padding: 24rpx 24rpx 0 }
.notice-content { padding: 16rpx 24rpx; font-size: 26rpx; color: #333; line-height: 1.6 }
.notice-actions { display: flex; align-items: center; justify-content: space-between; padding: 16rpx 24rpx 24rpx }
.notice-check { display: flex; align-items: center; gap: 8rpx }
.check-box { width: 28rpx; height: 28rpx; border-radius: 6rpx; border: 2rpx solid #007AFF; background: #fff }
.check-box.on { background: #007AFF }
.check-text { font-size: 26rpx; color: #555 }
.notice-btn { background: #007AFF; color: #fff; border-radius: 999rpx; padding: 0 28rpx }
.section-title { font-size: 30rpx; font-weight: 600; margin-bottom: 16rpx }
.products-section { background: #ffffff; border-radius: 12rpx; padding: 24rpx; margin-top: 24rpx }
.loading-wrap { min-height: 60vh; display: flex; align-items: center; justify-content: center }
.spinner { width: 56rpx; height: 56rpx; border: 6rpx solid rgba(0,122,255,0.15); border-top-color: #007AFF; border-radius: 50%; animation: spin 1s linear infinite }
@keyframes spin { from { transform: rotate(0) } to { transform: rotate(360deg) } }
.toolbar { display: flex; flex-direction: column; gap: 12rpx; margin-bottom: 16rpx }
.search { background: #f6f8ff; border: 1rpx solid rgba(0,122,255,0.25); border-radius: 999rpx; padding: 14rpx 20rpx; font-size: 26rpx }
.filters { display: flex; align-items: center; gap: 12rpx }
.price { flex: 1; background: #f6f8ff; border: 1rpx solid rgba(0,122,255,0.25); border-radius: 999rpx; padding: 12rpx 16rpx; font-size: 26rpx }
.dash { color: #888; font-size: 26rpx }
.apply-btn { background: #007AFF; color: #fff; border-radius: 999rpx; padding: 0 20rpx }
.products-columns { display: flex; gap: 12rpx }
.column { flex: 1 }
.product-item { margin-bottom: 12rpx }
.empty { padding: 40rpx; color: #888; text-align: center }
.product-card { background: #fff; border-radius: 16rpx; overflow: hidden; box-shadow: 0 6rpx 16rpx rgba(0,122,255,0.08); transition: transform .15s ease }
.product-item:active .product-card { transform: scale(0.98) }
.thumb-wrap { position: relative }
.product-thumb { width: 100%; height: auto; display: block; opacity: 0; transition: opacity .25s ease; z-index: 0 }
.product-thumb.visible { opacity: 1 }
.skeleton { position: absolute; left: 0; top: 0; right: 0; bottom: 0; background: linear-gradient(90deg, #eef2ff 25%, #f6f8ff 37%, #eef2ff 63%); background-size: 400% 100%; animation: shimmer 1.2s ease infinite; z-index: 1 }
@keyframes shimmer { 0% { background-position: 100% 0 } 100% { background-position: 0 0 } }
.thumb-wrap { background: #f6f8ff; min-height: 220rpx }
.badge { position: absolute; left: 12rpx; bottom: 12rpx; display: flex; gap: 8rpx }
.badge-price { background: #007AFF; color: #fff; font-size: 22rpx; padding: 6rpx 12rpx; border-radius: 999rpx; box-shadow: 0 2rpx 8rpx rgba(0,122,255,0.25) }
.badge-points { background: rgba(0,122,255,0.85); color: #fff; font-size: 22rpx; padding: 6rpx 12rpx; border-radius: 999rpx }
.product-title { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; margin: 12rpx; font-size: 26rpx; color: #222 }
.product-extra { display: flex; justify-content: flex-end; align-items: center; margin: 0 12rpx 12rpx }
.stock { font-size: 22rpx; color: #888 }
</style>
