<template>
  <ActivityPageLayout :cover-url="coverUrl">
    <view class="bg-decoration"></view>
    <template #header>
      <ActivityHeader 
        :title="detail.name || detail.title || '对对碰'"
        :price="detail.price_draw"
        price-unit="/次"
        :cover-url="coverUrl"
        :tags="['随机玩法', '理性消费']"
        @show-rules="showRules"
        @go-cabinet="goCabinet"
      />

    </template>

    <view class="section-container animate-enter stagger-1">
      <view class="section-header">
        <text class="section-title">卡牌类型</text>
      </view>
      <view v-if="cardTypesLoading" class="card-types-loading">加载中...</view>
      <view v-else-if="cardTypes.length === 0" class="card-types-empty">暂无可选卡牌类型</view>
      <scroll-view v-else class="card-types-scroll" scroll-x="true">
        <view class="card-types-row">
          <view
            v-for="it in cardTypes"
            :key="it.code"
            class="card-type-item"
            :class="{ active: selectedCardTypeCode === it.code }"
            @tap="() => selectCardType(it)"
          >
            <image v-if="it.image_url" class="card-type-img" :src="it.image_url" mode="aspectFill" />
            <view class="card-type-name">{{ it.name }}</view>
            <view v-if="it.quantity !== undefined && it.quantity !== null" class="card-type-qty">×{{ it.quantity }}</view>
          </view>
        </view>
      </scroll-view>
    </view>

    <!-- 奖池/记录切换 -->
    <ActivityTabs 
      v-model="tabActive"
      :tabs="[{key: 'pool', label: '本机奖池'}, {key: 'records', label: '购买记录'}]"
    >
      <!-- 奖池预览 -->
      <RewardsPreview
        v-if="tabActive === 'pool'"
        :rewards="previewRewards"
        :grouped="!['match', 'matching'].includes(detail?.play_type)"
        :play-type="detail?.play_type || 'normal'"
        @view-all="openRewardsPopup"
      />

      <!-- 购买记录 -->
      <RecordsList
        v-if="tabActive === 'records'"
        :records="winRecords"
      />
    </ActivityTabs>

    <view style="height: 180rpx;"></view>

    <template #footer>
      <view class="float-bar">
        <view class="float-bar-inner">
          <view class="float-price">
            <text class="currency">¥</text>
            <text class="amount">{{ (Number(detail.price_draw || 0) / 100).toFixed(2) }}</text>
            <text class="unit">/次</text>
          </view>
          <view v-if="hasResumeGame" class="action-btn secondary" @tap="onResumeGame">
            继续游戏
          </view>
          <view v-if="!hasResumeGame" class="action-btn primary" @tap="onParticipate">
            立即参与
            <view class="btn-shine"></view>
          </view>
        </view>
      </view>
    </template>

    <template #modals>
      <!-- 游戏弹窗 - 全屏沉浸式 -->
      <view v-if="gameVisible" class="game-overlay" @touchmove.stop.prevent>
        <view class="game-mask"></view>
        <view class="game-fullscreen" @tap.stop>
          <!-- 顶部标题栏 -->
          <view class="game-topbar">
            <text class="game-topbar-title">对对碰游戏</text>
            <view class="game-close-btn" @tap="closeGame">
              <text>×</text>
            </view>
          </view>
          
          <!-- 游戏信息 -->
          <view class="game-stats glass-card">
            <button class="game-btn-draw" @tap="manualDraw" :disabled="gameLoading || !canManualDraw">
              <text>摸牌</text>
            </button>
            <view class="stat-item">
              <text class="stat-label">总对数</text>
              <text class="stat-value">{{ totalPairs }}</text>
            </view>
            <view class="stat-item">
              <text class="stat-label">{{ countdownSeconds > 0 ? '倒计时' : '摸牌机会' }}</text>
              <text class="stat-value" :class="{ 'countdown': countdownSeconds > 0 }">
                {{ countdownSeconds > 0 ? `${countdownSeconds}s` : chance }}
              </text>
            </view>
            <view class="stat-item">
              <text class="stat-label">牌组剩余</text>
              <text class="stat-value">{{ deckRemaining }}</text>
            </view>
          </view>
          
          <!-- 游戏区域 -->
          <view class="game-content">
            <view v-if="gameLoading" class="game-loading">
              <text class="loading-icon">⏳</text>
              <text class="loading-text">加载中...</text>
            </view>
            <view v-else-if="gameError" class="game-error">
              <text class="error-icon">⚠️</text>
              <text class="error-text">{{ gameError }}</text>
            </view>
            <view v-else class="game-board glass-card">
              <view class="match-grid-fullscreen">
                <view
                  v-for="(cell, idx) in handGridCells"
                  :key="idx"
                  class="match-cell-large"
                  :class="{ empty: cell.empty, chosen: cell.isChosen, picked: cell.isPicked }"
                  @tap="() => onCellTap(cell)"
                >
                  <image v-if="cell.image" class="match-cell-img-large" :src="cell.image" mode="aspectFill" />
                  <view v-else class="match-cell-img-large"></view>
                  <text v-if="!cell.empty && cell.type" class="match-cell-type-large">{{ cell.type }}</text>
                </view>
              </view>
            </view>
          </view>
          
          <!-- 底部操作栏 -->
          <view class="game-actions">
            <button class="game-btn btn-primary" @tap="advanceOne" :disabled="gameLoading">
              <text>下一步</text>
            </button>
          </view>
        </view>
      </view>

      <!-- 奖品详情弹窗 -->
      <RewardsPopup 
        v-model:visible="rewardsVisible"
        :title="`${detail.name || '奖池'} · 奖品与概率`"
        :reward-groups="rewardGroups"
      />

      <PaymentPopup
        v-model:visible="paymentVisible"
        :amount="paymentAmount"
        :coupons="coupons"
        :propCards="propCards"
        :showCards="true"
        @confirm="onPaymentConfirm"
      />
      <RulesPopup 
        v-model:visible="rulesVisible"
        :content="detail.gameplay_intro"
      />

      <CabinetPreviewPopup
        v-model:visible="cabinetVisible"
        :activity-id="activityId"
      />

      <!-- 开奖结果弹窗 -->
      <LotteryResultPopup
        v-model:visible="resultVisible"
        :results="resultItems"
        @close="onResultClose"
      />
    </template>
  </ActivityPageLayout>
</template>

<script setup>
import { ref, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import PaymentPopup from '../../../components/PaymentPopup.vue'
import ActivityPageLayout from '@/components/activity/ActivityPageLayout.vue'
import ActivityHeader from '@/components/activity/ActivityHeader.vue'
import ActivityTabs from '@/components/activity/ActivityTabs.vue'
import RewardsPreview from '@/components/activity/RewardsPreview.vue'
import RewardsPopup from '@/components/activity/RewardsPopup.vue'
import RecordsList from '@/components/activity/RecordsList.vue'
import RulesPopup from '@/components/activity/RulesPopup.vue'
import CabinetPreviewPopup from '@/components/activity/CabinetPreviewPopup.vue'
import LotteryResultPopup from '@/components/activity/LotteryResultPopup.vue'
import { getActivityDetail, getActivityIssues, getActivityIssueRewards, getUserCoupons, getItemCards, createWechatOrder, getMatchingCardTypes, createMatchingPreorder, checkMatchingGame, getIssueDrawLogs, getMatchingGameCards } from '../../../api/appUser'
import { levelToAlpha } from '@/utils/activity'
import { vibrateShort } from '@/utils/vibrate.js'

const detail = ref({})
const statusText = ref('')
const issues = ref([])
const rewardsMap = ref({})
const currentIssueId = ref('')
const selectedIssueIndex = ref(0)
const showIssues = computed(() => (detail.value && detail.value.status !== 2))
const activityId = ref('')
const tabActive = ref('pool')
const winRecords = ref([])
const paymentVisible = ref(false)
const coupons = ref([])
const propCards = ref([])
const selectedCoupon = ref(null)
const selectedCard = ref(null)
const paymentAmount = computed(() => (((Number(detail.value.price_draw || 0) / 100) || 0)).toFixed(2))
const cardTypesLoading = ref(false)
const cardTypes = ref([])
const selectedCardTypeCode = ref('')
const rewardsVisible = ref(false)
const rulesVisible = ref(false)
const cabinetVisible = ref(false)
const resultVisible = ref(false)
const resultItems = ref([])
const resumeGame = ref(null)
const resumeIssueId = ref('')
const hasResumeGame = computed(() => {
  const v = resumeGame.value || null
  return !!(v && v.game_id)
})
const gameVisible = ref(false)
const gameLoading = ref(false)
const gameError = ref('')
const gameEntry = ref(null)
const gameIssueId = ref('')
const hand = ref([])
const deckIndex = ref(0)
const chance = ref(0)
const totalPairs = ref(0)
const gameFinished = ref(false)
const pickedHandIndex = ref(-1)
const countdownTimer = ref(null)
const countdownSeconds = ref(0)
const gameIdText = computed(() => String((gameEntry.value && gameEntry.value.game_id) || ''))
const deckRemaining = computed(() => {
  const entry = gameEntry.value || null
  const deck = entry && Array.isArray(entry.all_cards) ? entry.all_cards : []
  return Math.max(0, deck.length - Number(deckIndex.value || 0))
})
const cardTypeImageMap = computed(() => {
  const map = {}
  ;(cardTypes.value || []).forEach(it => {
    if (!it) return
    const code = String(it.code || '')
    if (!code) return
    map[code] = cleanUrl(it.image_url || it.image || it.img || it.pic || '')
  })
  return map
})
const cardTypeNameMap = computed(() => {
  const map = {}
  ;(cardTypes.value || []).forEach(it => {
    if (!it) return
    const code = String(it.code || '')
    if (!code) return
    map[code] = String(it.name || it.title || it.label || code)
  })
  return map
})
const selectedPositionCode = computed(() => String((gameEntry.value && gameEntry.value.position) || selectedCardTypeCode.value || ''))
const selectedPositionText = computed(() => {
  const code = String(selectedPositionCode.value || '')
  if (!code) return ''
  const name = (cardTypeNameMap.value || {})[code]
  return name ? `${name}(${code})` : code
})
const canManualDraw = computed(() => {
  const entry = gameEntry.value || null
  if (!entry || !entry.game_id) return false
  if (Number(chance.value || 0) <= 0) return false
  return canDrawOne()
})

function getCardTypeCode(card) {
  const c = card || {}
  const v = c.type ?? c.type_code ?? c.card_type_code ?? c.position ?? c.pos ?? c.code
  return String(v || '')
}

const handGridCells = computed(() => {
  const cards = Array.isArray(hand.value) ? hand.value : []
  const imgMap = cardTypeImageMap.value || {}
  const chosen = String(selectedPositionCode.value || '')
  const picked = Number(pickedHandIndex.value || -1)
  const cells = []
  for (let i = 0; i < 18; i++) {
    const raw = cards[i]
    const code = raw ? getCardTypeCode(raw) : ''
    const image = code ? (imgMap[code] || cleanUrl(raw.image || raw.image_url || raw.img || raw.pic || '')) : ''
    cells.push({
      id: raw && (raw.id ?? raw.card_id) ? (raw.id ?? raw.card_id) : String(i),
      handIndex: raw ? i : -1,
      type: code,
      image,
      empty: !raw,
      isChosen: !!(code && chosen && code === chosen),
      isPicked: !!(raw && i === picked)
    })
  }
  return cells
})

const coverUrl = computed(() => cleanUrl(detail.value.banner || detail.value.cover || detail.value.image || ''))
const currentIssueRewards = computed(() => {
  const iid = currentIssueId.value || ''
  const m = rewardsMap.value || {}
  return (iid && Array.isArray(m[iid])) ? m[iid] : []
})

// 用于奖池预览的 rewards（已排序）
const previewRewards = computed(() => {
  const isMatchType = ['match', 'matching'].includes(detail.value?.play_type)

  if (isMatchType) {
    // 对对碰模式：按 min_score 升序
    const sorted = [...currentIssueRewards.value].sort((a, b) => (a.min_score - b.min_score))
    console.log('[对对碰] previewRewards 排序后:', sorted.map(r => ({
      name: r.title,
      min_score: r.min_score,
      weight: r.weight,
      percent: r.percent
    })))
    return sorted
  } else {
    // 普通模式：返回原数组
    return currentIssueRewards.value
  }
})

const rewardGroups = computed(() => {
  const isMatchType = ['match', 'matching'].includes(detail.value?.play_type)

  // 对对碰模式：不分组，直接按 min_score 平铺所有奖品
  if (isMatchType) {
    // 先按 min_score 升序排序
    const sortedRewards = [...currentIssueRewards.value].sort((a, b) => (a.min_score - b.min_score))

    // 将每个奖品作为一个单独的分组
    const groups = sortedRewards.map(item => ({
      level: `${item.min_score}对子`,
      rewards: [item],
      totalPercent: item.percent.toFixed(1)
    }))

    console.log('[对对碰] rewardGroups 分组后:', groups.map(g => ({
      level: g.level,
      count: g.rewards.length,
      totalPercent: g.totalPercent,
      firstItem: g.rewards[0]?.title
    })))

    return groups
  }

  // 普通模式：按原来的分组逻辑
  const groups = {}
  currentIssueRewards.value.forEach(item => {
    let level = item.level || '赏'

    // 普通模式：只显示 min_score > 0 的奖品
    if (item.min_score > 0 && level !== 'BOSS') {
      level = `${item.min_score}对子`
    }

    if (!groups[level]) groups[level] = []
    groups[level].push(item)
  })

  return Object.keys(groups).sort((a, b) => {
    // 普通模式：Last 和 BOSS 优先
    if (a === 'Last' || a === 'BOSS') return -1
    if (b === 'Last' || b === 'BOSS') return 1

    // 普通模式：分组之间按该组最小 weight 排序（升序）
    const minWeightA = Math.min(...groups[a].map(item => item.weight || 0))
    const minWeightB = Math.min(...groups[b].map(item => item.weight || 0))
    return minWeightA - minWeightB
  }).map(key => {
    const rewards = groups[key]

    // 普通模式：分组内按 weight 升序排列
    rewards.sort((a, b) => (a.weight - b.weight))

    const total = rewards.reduce((sum, item) => sum + (Number(item.percent) || 0), 0)
    return {
      level: key,
      rewards: rewards,
      totalPercent: total.toFixed(1)
    }
  })
})
const currentIssueTitle = computed(() => {
  const arr = issues.value || []
  const cur = arr[selectedIssueIndex.value]
  // 对对碰不显示“期数”，优先显示标题，兜底显示“奖池”
  return (cur && (cur.title || '奖池')) || '-'
})
const currentIssueStatusText = computed(() => {
  const arr = issues.value || []
  const cur = arr[selectedIssueIndex.value]
  return (cur && (cur.status_text || '')) || ''
})

const selectedCardType = computed(() => {
  const code = String(selectedCardTypeCode.value || '')
  if (!code) return null
  return (cardTypes.value || []).find(i => i && i.code === code) || null
})

function statusToText(s) {
  if (s === 1) return '进行中'
  if (s === 0) return '未开始'
  if (s === 2) return '已结束'
  return String(s || '')
}

async function fetchDetail(id) {
  const data = await getActivityDetail(id)
  detail.value = data || {}
  statusText.value = statusToText(detail.value.status)
}

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 normalizeCardTypes(list) {
  const arr = unwrap(list)
  return arr.map((i, idx) => ({
    code: String(i.code ?? i.type_code ?? i.card_type_code ?? i.id ?? String(idx)),
    name: i.name ?? i.title ?? i.label ?? '卡牌类型',
    image_url: cleanUrl(i.image_url ?? i.image ?? i.img ?? i.pic ?? ''),
    quantity: i.quantity !== undefined ? Number(i.quantity) : undefined
  }))
}

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()
}

const MATCHING_GAME_CACHE_KEY = 'matching_game_cache_v1'

function getMatchingGameCache() {
  const obj = uni.getStorageSync(MATCHING_GAME_CACHE_KEY) || {}
  return typeof obj === 'object' && obj ? obj : {}
}

function findLatestMatchingGameCacheEntry(aid) {
  const activityKey = String(aid || '')
  if (!activityKey) return null
  const cache = getMatchingGameCache()
  const act = cache[activityKey]
  if (!act || typeof act !== 'object') return null
  let bestIssueId = ''
  let bestEntry = null
  let bestTs = -Infinity
  Object.keys(act).forEach(issueId => {
    const entry = act[issueId]
    if (!entry || typeof entry !== 'object' || !entry.game_id) return
    const ts = Number(entry.ts || 0)
    if (!bestEntry || ts > bestTs) {
      bestTs = ts
      bestIssueId = issueId
      bestEntry = entry
    }
  })
  if (!bestEntry) return null
  return { issue_id: bestIssueId, entry: bestEntry }
}

function syncResumeGame(aid) {
  const latest = findLatestMatchingGameCacheEntry(aid)
  if (!latest || !latest.entry || !latest.entry.game_id) {
    resumeIssueId.value = ''
    resumeGame.value = null
    return null
  }
  resumeIssueId.value = String(latest.issue_id || '')
  resumeGame.value = latest.entry
  return latest
}

function writeMatchingGameCacheEntry(aid, iid, entry) {
  const activityKey = String(aid || '')
  const issueKey = String(iid || '')
  if (!activityKey || !issueKey) return
  const cache = getMatchingGameCache()
  const act = (cache[activityKey] && typeof cache[activityKey] === 'object') ? cache[activityKey] : {}
  act[issueKey] = entry
  cache[activityKey] = act
  uni.setStorageSync(MATCHING_GAME_CACHE_KEY, cache)
  syncResumeGame(activityKey)
}

function readMatchingGameCacheEntry(aid, iid) {
  const activityKey = String(aid || '')
  const issueKey = String(iid || '')
  if (!activityKey || !issueKey) return null
  const cache = getMatchingGameCache()
  const act = cache[activityKey] || {}
  const entry = act && act[issueKey]
  const ok = entry && typeof entry === 'object' && entry.game_id
  return ok ? entry : null
}

function clearMatchingGameCacheEntry(aid, iid) {
  const activityKey = String(aid || '')
  const issueKey = String(iid || '')
  const cache = getMatchingGameCache()
  const act = cache[activityKey]
  if (!act || typeof act !== 'object') {
    syncResumeGame(activityKey)
    return
  }
  if (act[issueKey] !== undefined) delete act[issueKey]
  if (Object.keys(act).length === 0) delete cache[activityKey]
  else cache[activityKey] = act
  uni.setStorageSync(MATCHING_GAME_CACHE_KEY, cache)
  syncResumeGame(activityKey)
}

function normalizeAllCards(v) {
  const arr = Array.isArray(v) ? v : (v ? [v] : [])
  return arr.map((it, idx) => {
    const obj = (it && typeof it === 'object') ? { ...it } : { value: it }
    if (obj.image_url !== undefined) obj.image_url = cleanUrl(obj.image_url)
    if (obj.image !== undefined) obj.image = cleanUrl(obj.image)
    if (!obj.id && obj.card_id) obj.id = obj.card_id
    if (!obj.id) obj.id = String(idx)
    return obj
  })
}

function truthy(v) {
  if (typeof v === 'boolean') return v
  const s = String(v || '').trim().toLowerCase()
  if (!s) return false
  return s === '1' || s === 'true' || s === 'yes' || s === 'y' || s === '是' || s === 'boss是真的' || s === 'boss' || s === '大boss'
}
function detectBoss(i) {
  return truthy(i.is_boss) || truthy(i.boss) || truthy(i.isBoss) || truthy(i.boss_true) || truthy(i.boss_is_true) || truthy(i.bossText) || truthy(i.tag)
}

function normalizeIssues(list) {
  const arr = unwrap(list)
  return arr.map((i, idx) => ({
    id: i.id ?? String(idx),
    title: i.title ?? i.name ?? '',
    no: i.no ?? i.index ?? i.issue_no ?? i.issue_number ?? null,
    status_text: i.status_text ?? (i.status === 1 ? '进行中' : i.status === 0 ? '未开始' : i.status === 2 ? '已结束' : '')
  }))
}
function normalizeRewards(list, playType = 'normal') {
  const arr = unwrap(list)
  const items = arr.map((i, idx) => ({
    ...i, // Spread original properties first
    id: i.product_id ?? i.id ?? String(idx),
    title: i.name ?? i.title ?? '',
    image: cleanUrl(i.product_image ?? i.image ?? i.img ?? i.pic ?? i.banner ?? ''),
    weight: Number(i.weight) || 0,
    boss: detectBoss(i),
    min_score: Number(i.min_score) || 0, // Extract min_score
    level: levelToAlpha(i.prize_level ?? i.level ?? (detectBoss(i) ? 'BOSS' : '赏'))
  }))
  const total = items.reduce((acc, it) => acc + (it.weight > 0 ? it.weight : 0), 0)
  const enriched = items.map(it => ({
    ...it,
    percent: total > 0 ? Math.round((it.weight / total) * 1000) / 10 : 0
  }))

  // 根据 play_type 决定排序方式
  if (['match', 'matching'].includes(playType)) {
    // 对对碰：按 min_score 升序排列，不过滤 min_score=0 的奖品
    enriched.sort((a, b) => (a.min_score - b.min_score))
  } else {
    // 普通活动：按 weight 升序排列（从小到大）
    enriched.sort((a, b) => (a.weight - b.weight))
  }

  return enriched
}
async function fetchRewardsForIssues(activityId) {
  const list = issues.value || []
  const promises = list.map(it => getActivityIssueRewards(activityId, it.id))
  const results = await Promise.allSettled(promises)

  // 获取 play_type
  const playType = detail.value?.play_type || 'normal'

  results.forEach((res, i) => {
    const issueId = list[i] && list[i].id
    if (!issueId) return
    const value = res.status === 'fulfilled' ? normalizeRewards(res.value, playType) : []
    rewardsMap.value = { ...(rewardsMap.value || {}), [issueId]: value }
  })
}

async function fetchIssues(id) {
  const data = await getActivityIssues(id)
  issues.value = normalizeIssues(data)
  const latestId = pickLatestIssueId(issues.value)
  setSelectedById(latestId)
  await fetchRewardsForIssues(id)
  // 获取购买记录
  if (currentIssueId.value) {
    fetchWinRecords(id, currentIssueId.value)
  }
}

function pickLatestIssueId(list) {
  const arr = Array.isArray(list) ? list : []
  let latest = arr[arr.length - 1] && arr[arr.length - 1].id
  let maxNo = -Infinity
  arr.forEach(i => {
    const n = Number(i.no)
    if (!Number.isNaN(n) && Number.isFinite(n) && n > maxNo) {
      maxNo = n
      latest = i.id
    }
  })
  return latest || (arr[0] && arr[0].id) || ''
}
function setSelectedById(id) {
  const arr = issues.value || []
  const idx = Math.max(0, arr.findIndex(x => x && x.id === id))
  selectedIssueIndex.value = idx
  const cur = arr[idx]
  currentIssueId.value = (cur && cur.id) || ''
  syncResumeGame(activityId.value)
}

function prevIssue() {
  const arr = issues.value || []
  if (!arr.length) return
  const next = Math.max(0, Number(selectedIssueIndex.value || 0) - 1)
  selectedIssueIndex.value = next
  currentIssueId.value = (arr[next] && arr[next].id) || ''
  syncResumeGame(activityId.value)
}
function nextIssue() {
  const arr = issues.value || []
  if (!arr.length) return
  const next = Math.min(arr.length - 1, Number(selectedIssueIndex.value || 0) + 1)
  selectedIssueIndex.value = next
  currentIssueId.value = (arr[next] && arr[next].id) || ''
  syncResumeGame(activityId.value)
}

async function fetchWinRecords(actId, issId) {
  if (!actId || !issId) return
  try {
    const res = await getIssueDrawLogs(actId, issId)
    const list = (res && res.list) || (Array.isArray(res) ? res : [])
    // 聚合同一奖品的记录
    const aggregate = {}
    list.forEach(it => {
      const key = it.reward_id || it.id
      if (!aggregate[key]) {
        aggregate[key] = {
          id: key,
          title: it.reward_name || it.title || it.name || '-',
          image: it.reward_image || it.image || '',
          count: 0
        }
      }
      aggregate[key].count += 1
    })
    const total = list.length || 1
    winRecords.value = Object.values(aggregate).map(it => ({
      ...it,
      percent: ((it.count / total) * 100).toFixed(1)
    }))
  } catch (e) {
    console.error('fetchWinRecords error', e)
    winRecords.value = []
  }
}

function formatPercent(v) {
  const n = Number(v)
  if (!Number.isFinite(n)) return '0%'
  return `${n}%`
}


function openRewardsPopup() {
  rewardsVisible.value = true
}
function closeRewardsPopup() {
  rewardsVisible.value = false
}

function showRules() {
  rulesVisible.value = true
}

function goCabinet() {
  cabinetVisible.value = true
}

function onPreviewBanner() {
  const url = detail.value.banner || ''
  if (url) uni.previewImage({ urls: [url], current: url })
}

function previewCard(c) {
  const img = String(c && c.image || '')
  if (img) uni.previewImage({ urls: [img], current: img })
}

async function openGame(latest) {
  const aid = activityId.value || ''
  const targetIssueId = String(latest && latest.issue_id || '')
  if (targetIssueId && targetIssueId !== String(currentIssueId.value || '')) {
    const inList = (issues.value || []).some(x => x && String(x.id) === targetIssueId)
    if (inList) setSelectedById(targetIssueId)
  }

  gameIssueId.value = targetIssueId || String(currentIssueId.value || '')
  gameEntry.value = latest && latest.entry ? latest.entry : null
  gameVisible.value = true
  gameError.value = ''
  await applyResumeEntry(gameEntry.value)
  restoreOrInitLocalGame()

  // 检查是否摸牌次数为0且无法继续配对
  if (Number(chance.value || 0) <= 0 && !canEliminateNow()) {
    // 使用 setTimeout 确保界面先显示出来,再启动倒计时
    setTimeout(() => {
      startCountdown()
    }, 500)
  }
}

function closeGame() {
  gameVisible.value = false
  gameLoading.value = false
  gameError.value = ''
  gameEntry.value = null
  gameIssueId.value = ''
  pickedHandIndex.value = -1
  // 清理倒计时
  clearCountdown()
}

function startCountdown() {
  // 清除之前的倒计时
  clearCountdown()

  countdownSeconds.value = 3
  uni.showToast({
    title: `摸牌次数已用完，${countdownSeconds.value}秒后结束游戏`,
    icon: 'none',
    duration: 1000
  })

  countdownTimer.value = setInterval(() => {
    countdownSeconds.value -= 1

    if (countdownSeconds.value > 0) {
      uni.showToast({
        title: `${countdownSeconds.value}秒后结束游戏`,
        icon: 'none',
        duration: 900
      })
    } else {
      // 倒计时结束，执行游戏结束逻辑
      clearCountdown()
      finishAndReport()
    }
  }, 1000)
}

function clearCountdown() {
  if (countdownTimer.value) {
    clearInterval(countdownTimer.value)
    countdownTimer.value = null
  }
  countdownSeconds.value = 0
}

function getSelectedCodeFromEntry(entry) {
  const e = entry || {}
  return String(e.position || selectedCardTypeCode.value || '')
}

function isSelectedCard(card, selectedCode) {
  const code = String(selectedCode || '')
  if (!code) return false
  const c = card || {}
  const v = c.position ?? c.pos ?? c.type_code ?? c.card_type_code ?? c.type ?? c.code
  return String(v || '') === code
}

function getMatchKey(card) {
  const c = card || {}
  const v = c.match_key ?? c.key ?? c.card_key ?? c.type ?? c.type_code ?? c.card_type_code ?? c.position ?? c.code ?? c.product_id ?? c.reward_id ?? c.prize_id ?? c.card_id ?? c.name ?? c.title
  return String(v || '')
}

function getDeckFromEntry(entry) {
  const e = entry || {}
  return Array.isArray(e.all_cards) ? e.all_cards : []
}

function restoreOrInitLocalGame() {
  const entry = gameEntry.value || null
  const aid = activityId.value || ''
  const issueId = String(gameIssueId.value || currentIssueId.value || '')
  if (!entry || !entry.game_id) return

  pickedHandIndex.value = -1
  const deck = normalizeAllCards(getDeckFromEntry(entry))
  const cachedHand = Array.isArray(entry.hand) ? entry.hand : null
  const cachedDeckIndexRaw = entry.deck_index ?? entry.deckIndex
  const cachedChanceRaw = entry.chance
  const cachedPairsRaw = entry.total_pairs ?? entry.totalPairs

  if (cachedHand) {
    hand.value = cachedHand
    const idx = Number(cachedDeckIndexRaw)
    const minIdx = cachedHand.length
    const nextIdx = Number.isFinite(idx) ? Math.max(minIdx, idx) : minIdx
    deckIndex.value = Math.min(deck.length, Math.max(0, nextIdx))
    chance.value = Number.isFinite(Number(cachedChanceRaw)) ? Number(cachedChanceRaw) : 0
    totalPairs.value = Number.isFinite(Number(cachedPairsRaw)) ? Number(cachedPairsRaw) : 0
    gameFinished.value = false
    if (deck !== entry.all_cards) gameEntry.value = { ...entry, all_cards: deck }
    return
  }

  const initialHand = deck.slice(0, 9)
  const selectedCode = getSelectedCodeFromEntry(entry)
  const initialChance = initialHand.reduce((acc, it) => acc + (isSelectedCard(it, selectedCode) ? 1 : 0), 0)
  hand.value = initialHand
  deckIndex.value = initialHand.length
  chance.value = initialChance
  totalPairs.value = 0
  gameFinished.value = false

  writeMatchingGameCacheEntry(aid, issueId, {
    ...entry,
    all_cards: deck,
    hand: initialHand,
    deck_index: initialHand.length,
    chance: initialChance,
    total_pairs: 0,
    ts: Date.now()
  })
}

function persistLocalGame() {
  const aid = activityId.value || ''
  const issueId = String(gameIssueId.value || currentIssueId.value || '')
  const entry = gameEntry.value || null
  if (!entry || !entry.game_id) return

  const deck = normalizeAllCards(getDeckFromEntry(entry))
  const next = {
    ...entry,
    all_cards: deck,
    hand: Array.isArray(hand.value) ? hand.value : [],
    deck_index: Number(deckIndex.value || 0),
    chance: Number(chance.value || 0),
    total_pairs: Number(totalPairs.value || 0),
    ts: Date.now()
  }
  gameEntry.value = next
  writeMatchingGameCacheEntry(aid, issueId, next)
}

function eliminateAllPairs() {
  const cards = Array.isArray(hand.value) ? [...hand.value] : []
  let removed = 0
  for (;;) {
    const counts = new Map()
    for (const c of cards) {
      const k = getMatchKey(c)
      if (!k) continue
      counts.set(k, (counts.get(k) || 0) + 1)
    }
    let targetKey = ''
    for (const [k, n] of counts.entries()) {
      if (n >= 2) { targetKey = k; break }
    }
    if (!targetKey) break

    let first = -1
    let second = -1
    for (let i = 0; i < cards.length; i++) {
      if (getMatchKey(cards[i]) !== targetKey) continue
      if (first === -1) first = i
      else { second = i; break }
    }
    if (first === -1 || second === -1) break
    const a = Math.max(first, second)
    const b = Math.min(first, second)
    cards.splice(a, 1)
    cards.splice(b, 1)
    removed += 1
  }
  if (removed > 0) {
    hand.value = cards
    totalPairs.value = Number(totalPairs.value || 0) + removed
    chance.value = Number(chance.value || 0) + removed
  }
  return removed
}

function canEliminateNow() {
  const cards = Array.isArray(hand.value) ? hand.value : []
  const counts = new Map()
  for (const c of cards) {
    const k = getMatchKey(c)
    if (!k) continue
    const n = (counts.get(k) || 0) + 1
    if (n >= 2) return true
    counts.set(k, n)
  }
  return false
}

function canDrawOne() {
  const entry = gameEntry.value || null
  if (!entry) return false
  const deck = getDeckFromEntry(entry)
  return Number(deckIndex.value || 0) < deck.length
}

function drawOne() {
  const entry = gameEntry.value || null
  if (!entry) return null
  const deck = getDeckFromEntry(entry)
  const idx = Number(deckIndex.value || 0)
  if (idx >= deck.length) return null
  const next = deck[idx]
  hand.value = [...(Array.isArray(hand.value) ? hand.value : []), next]
  deckIndex.value = idx + 1
  return next
}

function manualDraw() {
  if (gameLoading.value) return
  if (!canManualDraw.value) return
  vibrateShort()
  drawOne()
  chance.value = Math.max(0, Number(chance.value || 0) - 1)
  pickedHandIndex.value = -1
  persistLocalGame()

  // 检查摸牌次数是否为0且无法继续配对
  if (Number(chance.value || 0) <= 0 && !canEliminateNow()) {
    startCountdown()
  }
}

async function autoDrawIfStuck() {
  const entry = gameEntry.value || null
  const gameId = entry && entry.game_id ? String(entry.game_id) : ''
  if (!gameId) return
  if (gameFinished.value) return
  if (canEliminateNow()) return

  let guard = 0
  while (!canEliminateNow() && Number(chance.value || 0) > 0 && canDrawOne()) {
    guard += 1
    if (guard > 1000) throw new Error('自动摸牌次数过多')
    drawOne()
    chance.value = Math.max(0, Number(chance.value || 0) - 1)
    pickedHandIndex.value = -1
    persistLocalGame()
  }

  if (!canEliminateNow() && (Number(chance.value || 0) <= 0 || !canDrawOne())) {
    await finishAndReport()
  }
}

async function onCellTap(cell) {
  if (gameLoading.value) return
  if (!cell || cell.empty) return
  vibrateShort()
  const hi = Number(cell.handIndex)
  if (!Number.isFinite(hi) || hi < 0) return

  const cards = Array.isArray(hand.value) ? hand.value : []
  if (!cards[hi]) return

  const picked = Number(pickedHandIndex.value || -1)
  if (picked < 0) {
    pickedHandIndex.value = hi
    return
  }
  if (picked === hi) {
    pickedHandIndex.value = -1
    return
  }

  const a = cards[picked]
  const b = cards[hi]
  const ka = getMatchKey(a)
  const kb = getMatchKey(b)
  if (ka && kb && ka === kb) {
    const next = [...cards]
    const max = Math.max(picked, hi)
    const min = Math.min(picked, hi)
    next.splice(max, 1)
    next.splice(min, 1)
    hand.value = next
    totalPairs.value = Number(totalPairs.value || 0) + 1
    chance.value = Number(chance.value || 0) + 1
    pickedHandIndex.value = -1
    persistLocalGame()

    // 检查配对后是否无法继续配对且摸牌次数为0
    if (!canEliminateNow() && Number(chance.value || 0) <= 0) {
      startCountdown()
    }

    // 移除自动摸牌，让玩家手动控制
    return
  }

  pickedHandIndex.value = hi
  uni.showToast({ title: '不相同', icon: 'none' })
}

async function finishAndReport() {
  const aid = activityId.value || ''
  const issueId = String(gameIssueId.value || currentIssueId.value || '')
  const entry = gameEntry.value || null
  const gameId = entry && entry.game_id ? String(entry.game_id) : ''
  if (!gameId) return
  
  try {
    const checkRes = await checkMatchingGame(gameId, Number(totalPairs.value || 0))
    clearMatchingGameCacheEntry(aid, issueId)
    gameFinished.value = true
    closeGame()
    
    console.log('[对对碰] checkRes:', JSON.stringify(checkRes))
    console.log('[对对碰] currentIssueRewards:', currentIssueRewards.value?.length, 'items')
    console.log('[对对碰] rewardsMap keys:', Object.keys(rewardsMap.value || {}))
    
    // 解析中奖结果 - 后端返回格式是 { reward: { reward_id, name, level } }
    let wonItems = []
    
    // 后端返回单个 reward 对象的情况
    if (checkRes?.reward && checkRes.reward.reward_id) {
      const reward = checkRes.reward
      console.log('[对对碰] 检测到reward对象:', reward)
      // 从本地 rewardsMap 查找图片
      const allRewards = rewardsMap.value[issueId] || currentIssueRewards.value || []
      console.log('[对对碰] 本地奖励数据:', allRewards.length, 'items')
      const foundReward = allRewards.find(r => 
        String(r.id) === String(reward.reward_id) || 
        String(r.reward_id) === String(reward.reward_id)
      )
      console.log('[对对碰] 匹配到的奖励:', foundReward)
      wonItems = [{
        title: reward.name || foundReward?.name || foundReward?.title || '神秘奖励',
        image: foundReward?.image || foundReward?.pic || foundReward?.img || foundReward?.product_image || '',
        reward_id: reward.reward_id
      }]
    }
    // 处理数组格式（兼容）
    else if (Array.isArray(checkRes) && checkRes.length > 0) {
      wonItems = checkRes
    } else if (checkRes?.list && checkRes.list.length > 0) {
      wonItems = checkRes.list
    } else if (checkRes?.data && Array.isArray(checkRes.data) && checkRes.data.length > 0) {
      wonItems = checkRes.data
    } else if (checkRes?.rewards && checkRes.rewards.length > 0) {
      wonItems = checkRes.rewards
    } else if (checkRes?.items && checkRes.items.length > 0) {
      wonItems = checkRes.items
    }
    
    console.log('[对对碰] wonItems:', wonItems)
    
    // 转换为 LotteryResultPopup 需要的格式
    if (wonItems.length > 0) {
      const allRewards = rewardsMap.value[issueId] || currentIssueRewards.value || []
      resultItems.value = wonItems.map(item => {
        // 如果已经有 title，直接使用
        if (item.title) return item
        // 否则尝试从本地数据匹配
        const found = allRewards.find(r => 
          String(r.id) === String(item.reward_id) || 
          String(r.reward_id) === String(item.reward_id)
        )
        return {
          title: item.title || item.name || found?.name || found?.title || '神秘奖励',
          image: cleanUrl(item.image || item.img || found?.image || found?.pic || found?.product_image || ''),
          reward_id: item.reward_id || item.id
        }
      })
    } else {
      resultItems.value = []
    }
    
    console.log('[对对碰] resultItems:', resultItems.value)
    
    // 显示结果弹窗
    if (resultItems.value.length > 0) {
      resultVisible.value = true
    } else {
      // 没有中奖物品时显示简单提示
      uni.showModal({
        title: '游戏结束',
        content: `总对数：${Number(totalPairs.value || 0)}`,
        showCancel: false
      })
    }
  } catch (e) {
    console.error('finishAndReport error', e)
    uni.showToast({ title: e?.message || '结算失败', icon: 'none' })
  }
}

function onResultClose() {
  resultVisible.value = false
  resultItems.value = []
}

async function advanceOne() {
  if (gameLoading.value) return
  vibrateShort()
  const entry = gameEntry.value || null
  const gameId = entry && entry.game_id ? String(entry.game_id) : ''
  if (!gameId) return

  gameLoading.value = true
  gameError.value = ''
  try {
    const removed = eliminateAllPairs()
    if (removed > 0) {
      persistLocalGame()
      return
    }

    // 移除自动摸牌逻辑，让玩家手动控制
    // 如果无法配对，提示玩家需要摸牌
    if (!canEliminateNow()) {
      if (Number(chance.value || 0) > 0 && canDrawOne()) {
        uni.showToast({ title: '请摸牌后再试', icon: 'none' })
      } else if (Number(chance.value || 0) <= 0 && !canDrawOne()) {
        // 摸牌次数为0且无法继续抽牌，再次确认场上无法配对才结束游戏
        if (!canEliminateNow()) {
          await finishAndReport()
        }
      }
      return
    }

    persistLocalGame()
  } catch (e) {
    gameError.value = e?.message || '操作失败'
  } finally {
    gameLoading.value = false
  }
}

async function autoRun() {
  if (gameLoading.value) return
  const entry = gameEntry.value || null
  const gameId = entry && entry.game_id ? String(entry.game_id) : ''
  if (!gameId) return

  gameLoading.value = true
  gameError.value = ''
  try {
    let guard = 0
    for (;;) {
      guard += 1
      if (guard > 1000) throw new Error('自动进行次数过多')

      const removed = eliminateAllPairs()
      if (removed > 0) {
        persistLocalGame()
        continue
      }

      await autoDrawIfStuck()
      break
    }
  } catch (e) {
    gameError.value = e?.message || '操作失败'
  } finally {
    gameLoading.value = false
  }
}

async function onParticipate() {
  vibrateShort()
  const aid = activityId.value || ''
  const iid = currentIssueId.value || ''
  if (!aid || !iid) { uni.showToast({ title: '期数未选择', icon: 'none' }); return }
  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
  }
  const openid = uni.getStorageSync('openid')
  if (!openid) { uni.showToast({ title: '缺少OpenID，请重新登录', icon: 'none' }); return }

  const latest = syncResumeGame(aid)
  if (latest && latest.entry && latest.entry.game_id) {
    await openGame(latest)
    return
  }

  await fetchCardTypes()
  if (!selectedCardType.value) {
    uni.showToast({ title: '请选择卡牌类型', icon: 'none' })
    return
  }
  coupons.value = []
  propCards.value = []
  selectedCoupon.value = null
  selectedCard.value = null
  paymentVisible.value = true
  fetchCoupons()
  fetchPropCards()
}

async function applyResumeEntry(entry) {
  if (!entry) return
  await fetchCardTypes()
  const pos = String(entry.position || '')
  if (pos) selectedCardTypeCode.value = pos
}

async function onResumeGame() {
  vibrateShort()
  const aid = activityId.value || ''
  const latest = syncResumeGame(aid)
  if (!latest || !latest.entry || !latest.entry.game_id) return
  await openGame(latest)
}

function selectCardType(it) {
  selectedCardTypeCode.value = it && it.code ? String(it.code) : ''
}

async function fetchCardTypes() {
  if (cardTypesLoading.value) return
  cardTypesLoading.value = true
  try {
    const res = await getMatchingCardTypes()
    cardTypes.value = normalizeCardTypes(res)
  } catch (e) {
    cardTypes.value = []
    selectedCardTypeCode.value = ''
  } finally {
    cardTypesLoading.value = false
  }
}

async function onPaymentConfirm(data) {
  selectedCoupon.value = data?.coupon || null
  selectedCard.value = data?.card || null
  paymentVisible.value = false
  await doDraw()
}

async function doDraw() {
  const aid = activityId.value || ''
  const iid = currentIssueId.value || ''
  if (!aid || !iid) return
  const openid = uni.getStorageSync('openid')
  if (!openid) { uni.showToast({ title: '缺少OpenID，请重新登录', icon: 'none' }); return }

  uni.showLoading({ title: '创建订单...' })
  try {
    if (!selectedCardType.value) {
      uni.hideLoading()
      uni.showToast({ title: '请选择卡牌类型', icon: 'none' })
      return
    }

    // 1. 调用 createMatchingPreorder 创建对对碰订单（不再返回游戏数据）
    const preRes = await createMatchingPreorder({
      issue_id: Number(iid),
      position: String(selectedCardType.value.code || ''),
      coupon_id: selectedCoupon.value?.id ? Number(selectedCoupon.value.id) : 0,
      item_card_id: selectedCard.value?.id ? Number(selectedCard.value.id) : 0
    })
    if (!preRes) throw new Error('创建订单失败')

    // 2. 提取订单号和游戏ID（注意：all_cards 不再在这里返回）
    const orderNo = preRes.order_no || preRes.data?.order_no || preRes.result?.order_no || preRes.orderNo
    if (!orderNo) throw new Error('未获取到订单号')
    
    const gameId = preRes.game_id || preRes.data?.game_id || preRes.result?.game_id || preRes.gameId
    if (!gameId) throw new Error('未获取到游戏ID')

    // 3. 用对对碰订单号调用微信支付
    uni.showLoading({ title: '拉起支付...' })
    const payRes = await createWechatOrder({ openid, order_no: orderNo })
    await new Promise((resolve, reject) => {
      uni.requestPayment({
        provider: 'wxpay',
        timeStamp: payRes.timeStamp || payRes.timestamp,
        nonceStr: payRes.nonceStr || payRes.noncestr,
        package: payRes.package,
        signType: payRes.signType || 'RSA',
        paySign: payRes.paySign,
        success: resolve,
        fail: reject
      })
    })

    // 4. 【关键】支付成功后，调用新接口获取游戏数据
    uni.showLoading({ title: '加载游戏...' })
    const cardsRes = await getMatchingGameCards(gameId)
    if (!cardsRes) throw new Error('获取游戏数据失败')
    
    const allCards = normalizeAllCards(cardsRes.all_cards || cardsRes.data?.all_cards || [])
    if (!allCards.length) throw new Error('游戏数据为空')

    // 5. 保存游戏数据到本地缓存
    writeMatchingGameCacheEntry(aid, iid, {
      game_id: String(gameId),
      position: String(selectedCardType.value.code || ''),
      all_cards: allCards,
      ts: Date.now()
    })

    uni.hideLoading()
    uni.showToast({ title: '支付成功', icon: 'success' })
    
    // 6. 自动打开游戏
    syncResumeGame(aid)
    const latest = findLatestMatchingGameCacheEntry(aid)
    if (latest && latest.entry && latest.entry.game_id) {
      await openGame(latest)
    }
  } catch (e) {
    uni.hideLoading()
    if (e?.errMsg && String(e.errMsg).includes('cancel')) {
      uni.showToast({ title: '支付已取消', icon: 'none' })
      return
    }
    uni.showToast({ title: e?.message || '操作失败', icon: 'none' })
  }
}

async function fetchCoupons() {
  const user_id = uni.getStorageSync('user_id')
  if (!user_id) return
  try {
    const res = await getUserCoupons(user_id, 0, 1, 100)
    let list = []
    if (Array.isArray(res)) list = res
    else if (res && Array.isArray(res.list)) list = res.list
    else if (res && Array.isArray(res.data)) list = res.data
    coupons.value = list.map((i, idx) => {
      const cents = (i.remaining !== undefined && i.remaining !== null) ? Number(i.remaining) : Number(i.amount ?? i.value ?? 0)
      const yuan = isNaN(cents) ? 0 : (cents / 100)
      return {
        id: i.id ?? i.coupon_id ?? String(idx),
        name: i.name ?? i.title ?? '优惠券',
        amount: Number(yuan).toFixed(2)
      }
    })
  } catch (e) {
    coupons.value = []
  }
}

async function fetchPropCards() {
  const user_id = uni.getStorageSync('user_id')
  if (!user_id) return
  try {
    // Status 1 = Unused
    const res = await getItemCards(user_id, 1)
    let list = []
    if (Array.isArray(res)) list = res
    else if (res && Array.isArray(res.list)) list = res.list
    else if (res && Array.isArray(res.data)) list = res.data
    // Group identical cards by name
    const groupedMap = new Map()
    list.forEach((i, idx) => {
      const name = i.name ?? i.title ?? i.card_name ?? '道具卡'
      if (!groupedMap.has(name)) {
        groupedMap.set(name, {
          id: i.id ?? i.card_id ?? i.item_card_id ?? String(idx),
          name: name,
          count: 0
        })
      }
      // If the API returns a 'count' or 'remaining', use it. Otherwise assume 1.
      const inc = (i.count !== undefined && i.count !== null) ? Number(i.count) : ((i.remaining !== undefined && i.remaining !== null) ? Number(i.remaining) : 1)
      groupedMap.get(name).count += inc
    })
    
    propCards.value = Array.from(groupedMap.values()).map(item => ({
      id: item.id,
      name: item.name, // PaymentPopup will handle the " (xN)" display if we pass it correctly. 
                       // Wait, PaymentPopup.vue expects 'name' to be the display string?
                       // Let's check PaymentPopup.vue again. 
                       // It shows {{ selectedCoupon.name }} (-¥...).
                       // For cards: {{ selectedCard.name }}.
                       // So I should format the name here OR update PaymentPopup to show count.
                       // The plan said "Update PaymentPopup text if needed (e.g. show count)".
                       // I will format it here for consistency if PaymentPopup is generic, 
                       // BUT updating PaymentPopup to show "Name (xCount)" is cleaner.
                       // For now, I'll pass 'count' property.
      count: item.count
    }))
  } catch (e) {
    propCards.value = []
  }
}

onLoad((opts) => {
  const id = (opts && opts.id) || ''
  if (id) {
    activityId.value = id
    syncResumeGame(id)
    fetchDetail(id)
    fetchIssues(id)
  }
  fetchCardTypes()
})
</script>

<style lang="scss" scoped>
/* ============================================
   对对碰活动页面 - 高级设计重构 (SCSS Integration)
   ============================================ */

.page-wrapper {
  min-height: 100vh;
  background: $bg-page;
  position: relative;
  overflow: hidden;
}

/* 背景装饰 */
.bg-decoration {
  position: absolute;
  top: 0; left: 0; width: 100%; height: 100%;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
}
.orb {
  position: absolute;
  border-radius: 50%;
  filter: blur(80rpx);
  opacity: 0.6;
}
.orb-1 {
  width: 500rpx; height: 500rpx;
  background: radial-gradient(circle, rgba($brand-primary, 0.2) 0%, transparent 70%);
  top: -100rpx; left: -100rpx;
}
.orb-2 {
  width: 600rpx; height: 600rpx;
  background: radial-gradient(circle, rgba($accent-gold, 0.15) 0%, transparent 70%);
  bottom: -100rpx; right: -100rpx;
}

.page-bg {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 700rpx;
  z-index: 1;
}
.bg-image {
  width: 100%;
  height: 100%;
  filter: blur(30rpx) brightness(0.9);
  transform: scale(1.1);
}
.bg-mask {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: linear-gradient(180deg, rgba($bg-page, 0.2) 0%, $bg-page 90%, $bg-page 100%);
}

.main-scroll {
  position: relative;
  z-index: 2;
  height: 100vh;
}

.header-card {
  margin: $spacing-xl $spacing-lg;
  background: rgba($bg-card, 0.85);
  backdrop-filter: blur(24rpx);
  border-radius: $radius-xl;
  padding: $spacing-lg;
  display: flex;
  align-items: center;
  box-shadow: $shadow-card;
  border: 1rpx solid rgba(255, 255, 255, 0.6);
  position: relative;
  overflow: hidden;

  &::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 2rpx;
    background: linear-gradient(90deg, transparent, rgba(255,255,255,0.8), transparent);
  }
}
.header-cover {
  width: 180rpx;
  height: 180rpx;
  border-radius: $radius-md;
  margin-right: $spacing-lg;
  background: $bg-secondary;
  box-shadow: $shadow-md;
  flex-shrink: 0;
}
.header-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 6rpx 0;
}
.header-title {
  font-size: $font-xl;
  font-weight: 800;
  color: $text-main;
  margin-bottom: $spacing-xs;
  line-height: 1.3;
  @include text-ellipsis(2);
}
.header-price-row {
  display: flex;
  align-items: baseline;
  color: $brand-primary;
  margin-bottom: $spacing-sm;
  text-shadow: 0 2rpx 4rpx rgba($brand-primary, 0.1);
}
.price-symbol { font-size: $font-md; font-weight: 700; }
.price-num { font-size: $font-xxl; font-weight: 900; margin: 0 4rpx; font-family: 'DIN Alternate', sans-serif; }
.price-unit { font-size: $font-sm; color: $text-sub; margin-left: 4rpx; }

.header-tags {
  display: flex;
  gap: $spacing-xs;
  flex-wrap: wrap;
}
.tag-item {
  font-size: $font-xs;
  color: $brand-primary-dark;
  background: rgba($brand-primary, 0.08);
  padding: 4rpx $spacing-sm;
  border-radius: $radius-sm;
  font-weight: 600;
  border: 1rpx solid rgba($brand-primary, 0.1);
}

.header-actions {
  display: flex;
  flex-direction: column;
  gap: 28rpx;
  margin-left: 16rpx;
  padding-left: 24rpx;
  border-left: 2rpx solid #E8E8E8;
  justify-content: center;
  align-self: stretch;
}
.action-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  transition: all 0.2s;
  &:active {
    transform: scale(0.92);
    opacity: 0.8;
  }
}
.action-icon {
  width: 44rpx;
  height: 44rpx;
  margin-bottom: 8rpx;
  background-size: contain;
  background-repeat: no-repeat;
}
/* Banner */
.banner-wrapper {
  margin: $spacing-md $spacing-lg;
  border-radius: $radius-lg;
  overflow: hidden;
  box-shadow: $shadow-lg;
  position: relative;
  animation: fadeInDown 0.6s ease-out;
}
.banner-img {
  width: 100%;
  display: block;
}
.banner-shadow {
  position: absolute;
  bottom: 0; left: 0; width: 100%; height: 40%;
  background: linear-gradient(to top, rgba(0,0,0,0.3), transparent);
}

.card-types {
  padding: 0 $spacing-lg;
  margin-bottom: $spacing-lg;
}
.card-types-title {
  font-size: $font-md;
  font-weight: 700;
  color: $text-main;
  margin-bottom: $spacing-sm;
}
.card-types-loading,
.card-types-empty {
  font-size: $font-sm;
  color: $text-sub;
  padding: $spacing-md 0;
}
.card-types-scroll {
  width: 100%;
}
.card-types-row {
  display: flex;
  gap: $spacing-md;
  padding-bottom: $spacing-xs;
}
.card-type-item {
  width: 160rpx;
  flex: 0 0 auto;
  background: rgba($bg-card, 0.9);
  border-radius: $radius-lg;
  padding: $spacing-sm;
  border: 2rpx solid rgba(0,0,0,0.05);
  box-shadow: $shadow-sm;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  position: relative;
}
.card-type-item.active {
  border-color: rgba($brand-primary, 0.6);
  box-shadow: 0 8rpx 20rpx rgba($brand-primary, 0.18);
}
.card-type-img {
  width: 120rpx;
  height: 120rpx;
  border-radius: $radius-md;
  background: $bg-secondary;
}
.card-type-name {
  margin-top: $spacing-sm;
  font-size: $font-sm;
  font-weight: 700;
  color: $text-main;
  text-align: center;
  width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.card-type-qty {
  margin-top: 6rpx;
  font-size: 22rpx;
  color: $text-sub;
}

/* Game Modal Styles */
.game-scroll-list {
  max-height: 60vh;
  padding: $spacing-lg;
}
.game-empty-state {
  padding: 60rpx 0 20rpx;
  text-align: center;
  color: $text-tertiary;
  font-size: $font-sm;
}
.game-info-card {
  display: flex;
  background: #FFFFFF;
  padding: $spacing-lg;
  border-radius: $radius-lg;
  box-shadow: $shadow-sm;
  align-items: center;
  margin-bottom: $spacing-lg;
}
.game-info-content {
  flex: 1;
}
.game-info-title {
  font-size: $font-md;
  font-weight: 600;
  color: $text-main;
  margin-bottom: $spacing-xs;
}
.game-info-meta {
  display: flex;
  gap: $spacing-md;
  font-size: $font-sm;
  color: $text-sub;
}

.section-container {
  padding: 0 $spacing-lg;
  margin-bottom: $spacing-xl;
}

.section-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: $spacing-md;
  padding: 0 4rpx;
  position: relative;
  z-index: 20;
}
.section-title {
  font-size: $font-lg;
  font-weight: 800;
  color: $text-main;
  position: relative;
  padding-left: 20rpx;
  
  &::before {
    content: '';
    position: absolute;
    left: 0; top: 50%; transform: translateY(-50%);
    width: 8rpx; height: 32rpx;
    background: $gradient-brand;
    border-radius: 4rpx;
  }
}
.issue-indicator {
  font-size: $font-sm;
  color: $brand-primary;
  background: rgba($brand-primary, 0.1);
  padding: 4rpx $spacing-md;
  border-radius: $radius-round;
  font-weight: 600;
}

/* Custom Picker */
.custom-picker {
  height: 280rpx;
  background: rgba($bg-secondary, 0.5);
  border-radius: $radius-lg;
  margin-bottom: $spacing-lg;
  overflow: hidden;
}
.picker-item {
  height: 80rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: $spacing-md;
}
.picker-text { font-size: $font-lg; color: $text-main; font-weight: 600; }
.picker-status {
  font-size: $font-xs; color: $text-sub; background: rgba(0,0,0,0.05); padding: 2rpx $spacing-sm; border-radius: $radius-sm;
  
  &.status-active { background: #D1FAE5; color: #059669; }
}

.record-count {
  background: rgba($brand-primary, 0.1);
  color: $brand-primary;
  padding: 2rpx $spacing-sm;
  border-radius: $radius-sm;
}

.match-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: $spacing-sm;
}
.match-cell {
  position: relative;
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: $radius-md;
  overflow: hidden;
  background: rgba(255,255,255,0.75);
  border: 2rpx solid rgba(0,0,0,0.06);
  box-shadow: $shadow-xs;
}
.match-cell.chosen {
  border-color: rgba($brand-primary, 0.8);
  box-shadow: 0 10rpx 22rpx rgba($brand-primary, 0.18);
}
.match-cell.picked {
  border-color: rgba($accent-gold, 0.9);
  box-shadow: 0 10rpx 22rpx rgba($accent-gold, 0.22);
}
.match-cell.empty {
  background: rgba(255,255,255,0.35);
  border-style: dashed;
}
.match-cell-img {
  width: 100%;
  height: 100%;
  background: $bg-secondary;
}
.match-cell-type {
  position: absolute;
  left: 8rpx;
  bottom: 8rpx;
  max-width: 90%;
  font-size: 20rpx;
  font-weight: 700;
  color: #fff;
  background: rgba(0, 0, 0, 0.5);
  padding: 2rpx 8rpx;
  border-radius: 10rpx;
  pointer-events: none;
  @include text-ellipsis(1);
}

/* Empty State */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 80rpx 0;
  color: $text-placeholder;
}
.empty-icon { font-size: 80rpx; margin-bottom: $spacing-lg; opacity: 0.5; }
.empty-text { font-size: $font-md; }

/* 底部悬浮操作栏 - 高级重置 */
.float-bar {
  position: fixed;
  left: 32rpx;
  right: 32rpx;
  bottom: calc(40rpx + env(safe-area-inset-bottom));
  z-index: 100;
  animation: slideUp 0.6s cubic-bezier(0.23, 1, 0.32, 1) backwards;
}

.float-bar-inner {
  background: rgba(255, 255, 255, 0.85);
  backdrop-filter: blur(30rpx);
  padding: 24rpx 40rpx;
  border-radius: 999rpx;
  display: flex;
  align-items: center;
  justify-content: space-between;
  box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.12);
  border: 1rpx solid rgba(255, 255, 255, 0.6);
}

.float-price {
  display: flex;
  align-items: baseline;
  color: $text-main;
  font-weight: 800;
  min-width: 0;
}
.float-price .currency { font-size: 26rpx; margin-right: 4rpx; color: $brand-primary; }
.float-price .amount { font-size: 44rpx; font-weight: 900; font-family: 'DIN Alternate', sans-serif; color: $brand-primary; }
.float-price .unit { font-size: 24rpx; color: $text-sub; margin-left: 4rpx; font-weight: 600; }

.rewards-overlay { position: fixed; left: 0; right: 0; top: 0; bottom: 0; z-index: 9000; }
.rewards-mask {
  position: absolute; left: 0; right: 0; top: 0; bottom: 0;
  background: rgba(0,0,0,0.6);
  backdrop-filter: blur(10rpx);
}
.rewards-panel {
  position: absolute;
  left: $spacing-lg;
  right: $spacing-lg;
  bottom: calc(env(safe-area-inset-bottom) + 24rpx);
  max-height: 70vh;
  background: rgba($bg-card, 0.95);
  border-radius: $radius-xl;
  box-shadow: $shadow-card;
  border: 1rpx solid rgba(255,255,255,0.5);
  overflow: hidden;
  animation: slideUp 0.25s ease-out;
}
.rewards-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: $spacing-lg;
  border-bottom: 1rpx solid rgba(0,0,0,0.06);
}
.rewards-title {
  font-size: $font-md;
  font-weight: 800;
  color: $text-main;
}
.rewards-close {
  font-size: 48rpx;
  line-height: 1;
  color: $text-tertiary;
  padding: 0 10rpx;
}
.rewards-list {
  max-height: 60vh;
  padding: $spacing-lg;
}

.rewards-group-v2 {
  margin-bottom: $spacing-xl;
  &:last-child { margin-bottom: 0; }
}

.group-header-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: $spacing-md;
  padding: 0 4rpx;
}

.group-badge {
  font-size: $font-xs;
  font-weight: 900;
  color: $text-main;
  background: #F0F0F0;
  padding: 4rpx 16rpx;
  border-radius: 8rpx;
  font-style: italic;
  border: 1rpx solid rgba(0,0,0,0.05);
  box-shadow: $shadow-xs;
  
  &.badge-boss {
    background: $gradient-gold;
    color: #78350F;
    border-color: rgba(217, 119, 6, 0.3);
  }
}

.group-total-prob {
  font-size: 24rpx;
  color: $brand-primary;
  font-weight: 800;
}
.rewards-item {
  display: flex;
  align-items: center;
  padding: $spacing-md;
  border-radius: $radius-lg;
  background: rgba(255,255,255,0.75);
  border: 1rpx solid rgba(0,0,0,0.03);
  box-shadow: $shadow-xs;
  margin-bottom: $spacing-md;
}
.rewards-thumb {
  width: 96rpx;
  height: 96rpx;
  border-radius: $radius-md;
  background: $bg-secondary;
  flex-shrink: 0;
  margin-right: $spacing-md;
}
.rewards-info { flex: 1; min-width: 0; }
.rewards-name-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: $spacing-sm;
  margin-bottom: 8rpx;
}
.rewards-name {
  font-size: $font-md;
  font-weight: 700;
  color: $text-main;
  @include text-ellipsis(1);
}
.rewards-tag {
  font-size: $font-xs;
  font-weight: 800;
  color: #fff;
  background: $gradient-brand;
  border-radius: $radius-sm;
  padding: 4rpx 10rpx;
  flex-shrink: 0;
}
.rewards-percent {
  font-size: $font-sm;
  color: $text-sub;
  font-weight: 600;
}
.rewards-empty {
  padding: 60rpx 0 20rpx;
  text-align: center;
  color: $text-tertiary;
  font-size: $font-sm;
}

.animate-enter {
  animation: fadeInUp 0.6s cubic-bezier(0.2, 0.8, 0.2, 1) backwards;
}
.stagger-1 { animation-delay: 0.1s; }
.stagger-2 { animation-delay: 0.2s; }
.stagger-3 { animation-delay: 0.3s; }

@keyframes fadeInUp {
  from { opacity: 0; transform: translateY(40rpx); }
  to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}
@keyframes scaleIn {
  from { opacity: 0; transform: scale(0.9); }
  to { opacity: 1; transform: scale(1); }
}
@keyframes slideUp {
  from { transform: translateY(20rpx); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}
.action-btn {
  height: 88rpx;
  line-height: 88rpx;
  padding: 0 56rpx;
  border-radius: 999rpx;
  font-size: 30rpx;
  font-weight: 900;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
  position: relative;
  overflow: hidden;
  
  &.primary {
    background: $gradient-brand !important;
    color: #fff !important;
    box-shadow: 0 12rpx 32rpx rgba($brand-primary, 0.35);
    
    &::before {
      content: '';
      position: absolute;
      top: -50%;
      left: -150%;
      width: 200%;
      height: 200%;
      background: linear-gradient(
        120deg, 
        rgba(255, 255, 255, 0) 30%, 
        rgba(255, 255, 255, 0.4) 50%, 
        rgba(255, 255, 255, 0) 70%
      );
      transform: rotate(25deg);
      animation: btnShine 4s infinite cubic-bezier(0.19, 1, 0.22, 1);
      pointer-events: none;
    }
  }
  
  &.secondary {
    background: #1A1A1A !important;
    color: $accent-gold !important;
    box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.15);
  }

  &:active {
    transform: scale(0.92);
  }
}

@keyframes btnShine {
  0% { left: -150%; }
  100% { left: 150%; }
}

.flip-overlay {
  position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 9999;
}
.flip-mask {
  position: absolute; top: 0; bottom: 0; width: 100%; background: rgba(0,0,0,0.85);
  backdrop-filter: blur(10rpx);
  animation: fadeIn 0.3s ease-out;
}
.flip-content {
  position: relative;
  z-index: 2;
  height: 100%;
  display: flex;
  flex-direction: column;
  padding: 40rpx;
  justify-content: center;
  animation: scaleIn 0.3s ease-out;
}
.close-btn {
  margin-top: 60rpx;
  background: #fff;
  color: #333;
  border-radius: 100rpx;
  font-weight: 700;
  width: 50%;
  height: 80rpx;
  line-height: 80rpx;
  align-self: center;
  box-shadow: 0 10rpx 30rpx rgba(255,255,255,0.15);
  transition: all 0.2s;
  &:active { transform: scale(0.95); }
}

/* Animation Utilities */
.animate-stagger {
  animation: fadeInUp 0.5s ease-out backwards;
  animation-delay: var(--delay, 0s);
}

/* ============= 全屏游戏样式 ============= */
.game-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 9999;
}

.game-mask {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.92);
  backdrop-filter: blur(16rpx);
  animation: fadeIn 0.3s ease-out;
}

.game-fullscreen {
  position: relative;
  z-index: 2;
  height: 100%;
  display: flex;
  flex-direction: column;
  animation: scaleIn 0.3s ease-out;
}

.game-topbar {
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 40rpx 32rpx;
  padding-top: calc(40rpx + env(safe-area-inset-top));
  position: relative;
}

.game-topbar-title {
  font-size: 36rpx;
  font-weight: 800;
  color: #fff;
  letter-spacing: 2rpx;
}

.game-close-btn {
  position: absolute;
  right: 32rpx;
  top: calc(40rpx + env(safe-area-inset-top));
  width: 64rpx;
  height: 64rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.1);
  border-radius: 50%;
  
  text {
    font-size: 48rpx;
    color: rgba(255, 255, 255, 0.8);
    line-height: 1;
  }
  
  &:active {
    background: rgba(255, 255, 255, 0.2);
  }
}

.game-stats {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 32rpx;
  padding: 24rpx 32rpx;
  margin: 0 32rpx;
}

.game-btn-draw {
  flex-shrink: 0;
  width: 120rpx;
  height: 120rpx;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.15);
  border: 3rpx solid rgba(255, 255, 255, 0.3);
  color: #FF6B6B;
  font-size: 28rpx;
  font-weight: 800;
  display: flex;
  align-items: center;
  justify-content: center;
  backdrop-filter: blur(10rpx);
  box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.3);
  transition: all 0.3s;

  &:active {
    transform: scale(0.9);
    background: rgba(255, 255, 255, 0.25);
  }

  &[disabled] {
    opacity: 0.3;
    transform: none;
  }
}

.stat-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8rpx;
}

.stat-label {
  font-size: 24rpx;
  color: rgba(255, 255, 255, 0.6);
}

.stat-value {
  font-size: 44rpx;
  font-weight: 900;
  color: $accent-gold;
  font-family: 'DIN Alternate', sans-serif;

  &.countdown {
    color: #FF6B6B;
    animation: pulse 1s ease-in-out infinite;
  }
}

@keyframes pulse {
  0%, 100% {
    opacity: 1;
    transform: scale(1);
  }
  50% {
    opacity: 0.7;
    transform: scale(1.1);
  }
}

.game-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  padding: 32rpx;
}

.game-loading,
.game-error {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20rpx;
}

.loading-icon,
.error-icon {
  font-size: 80rpx;
}

.loading-text,
.error-text {
  font-size: 28rpx;
  color: rgba(255, 255, 255, 0.7);
}

.game-board {
  width: 100%;
  max-width: 700rpx;
  padding: 32rpx;
}

.match-grid-fullscreen {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20rpx;
}

.match-cell-large {
  position: relative;
  aspect-ratio: 1 / 1;
  border-radius: 20rpx;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.1);
  border: 2rpx solid rgba(255, 255, 255, 0.15);
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  
  &.empty {
    background: rgba(255, 255, 255, 0.03);
    border-style: dashed;
    border-color: rgba(255, 255, 255, 0.1);
  }
  
  &.chosen {
    border-color: $brand-primary;
    box-shadow: 0 0 24rpx rgba($brand-primary, 0.5);
    transform: scale(1.02);
  }
  
  &.picked {
    border-color: $accent-gold;
    box-shadow: 0 0 24rpx rgba($accent-gold, 0.5);
  }
  
  &:active {
    transform: scale(0.95);
  }
}

.match-cell-img-large {
  width: 100%;
  height: 100%;
  background: rgba(255, 255, 255, 0.05);
}

.match-cell-type-large {
  position: absolute;
  left: 12rpx;
  bottom: 12rpx;
  max-width: 85%;
  font-size: 24rpx;
  font-weight: 700;
  color: #fff;
  background: rgba(0, 0, 0, 0.6);
  padding: 6rpx 14rpx;
  border-radius: 12rpx;
  @include text-ellipsis(1);
}

.game-actions {
  display: flex;
  justify-content: center;
  padding: 32rpx;
  padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
}

.game-btn {
  width: 400rpx;
  height: 96rpx;
  border-radius: 48rpx;
  font-size: 32rpx;
  font-weight: 800;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  transition: all 0.3s;
  color: #fff;

  &.btn-primary {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.4);
  }

  &.btn-secondary {
    background: rgba(255, 255, 255, 0.15);
    border: 2rpx solid rgba(255, 255, 255, 0.3);
    color: rgba(255, 255, 255, 0.9);
    backdrop-filter: blur(10rpx);
  }

  &:active {
    transform: scale(0.95);
  }

  &[disabled] {
    opacity: 0.4;
  }
}
</style>
