<template>
    <view
        class="eg-page"
        @touchstart.capture="primeSfxFromGesture"
        @mousedown.capture="primeSfxFromGesture"
    >
        <view v-if="confettiVisible" class="eg-confetti-layer" aria-hidden="true">
            <text
                v-for="piece in confettiPieces"
                :key="piece.id"
                class="eg-confetti-piece"
                :class="{ 'is-emoji': piece.emoji }"
                :style="piece.style"
            >{{ piece.emoji }}</text>
        </view>
        <view class="eg-nav" :style="navStyle">
            <view class="eg-nav-leading">
                <view class="eg-nav-btn" @tap="goBack">
                    <TongjiIcon name="chevron-left" size="md" color="#155e4b" />
                </view>
            </view>
            <view class="eg-title-wrap">
                <text class="eg-title">识糖小课堂</text>
                <text class="eg-subtitle">第{{ themeIndex + 1 }}站 · {{ currentTheme.title }}</text>
            </view>
            <view class="eg-nav-actions">
                <view class="eg-nav-btn eg-nav-btn--small" @tap="toggleSfx">
                    <TongjiIcon name="volume" size="sm" :color="sfxEnabled ? '#155e4b' : '#94a3b8'" />
                </view>
                <view class="eg-nav-btn eg-nav-btn--small" @tap="showRules">
                    <TongjiIcon name="info" size="sm" color="#155e4b" />
                </view>
            </view>
        </view>

        <view class="eg-content">
            <view class="eg-score-card">
                <view class="eg-stat">
                    <text class="eg-stat-label">本局认糖</text>
                    <text class="eg-stat-value">{{ learned }}</text>
                </view>
                <view class="eg-stat eg-stat--main">
                    <text class="eg-stat-label">得分</text>
                    <text class="eg-stat-score">{{ score }}</text>
                </view>
                <view
                    class="eg-stat eg-stat--rank"
                    :class="{ 'is-locked': busy || reshuffling || stageVisible || camelJackpot.visible }"
                    @tap="openWeeklyRank"
                >
                    <text class="eg-stat-label">本周同行</text>
                    <text class="eg-stat-rank-value">第{{ weeklyMyRank }}名</text>
                    <text class="eg-stat-rank-hint">点击查看</text>
                </view>
            </view>

            <view class="eg-task-card">
                <view class="eg-task-head">
                    <view class="eg-task-name">
                        <TongjiIcon name="trophy" size="sm" color="#8a5a10" />
                        <text>小目标：{{ currentTask.label }}</text>
                    </view>
                    <text class="eg-task-count">{{ Math.min(taskProgress, currentTask.target) }}/{{ currentTask.target }}</text>
                </view>
                <view class="eg-progress"><view class="eg-progress-fill" :style="{ width: taskPercent + '%' }" /></view>
                <view v-if="taskRewardVisible" :key="taskRewardKey" class="eg-task-reward-flight">
                    <view class="eg-task-reward-glow" />
                    <image :src="foodOf('camel').img" mode="aspectFit" />
                    <text>+1</text>
                </view>
            </view>

            <view class="eg-board-shell">
                <view class="eg-board-top">
                    <view class="eg-board-bonuses">
                        <view class="eg-combo" :class="{ 'is-hot': combo >= 3 }">连消 ×{{ Math.max(combo, 1) }}</view>
                        <view v-if="feastMoves > 0" class="eg-double-state">双倍得分 ×{{ feastMoves }}</view>
                    </view>
                    <view class="eg-risk">
                        <text>高糖食物 {{ highCount }}/3</text>
                        <view class="eg-risk-dots">
                            <view v-for="n in 3" :key="n" class="eg-risk-dot" :class="{ on: n <= highCount }" />
                        </view>
                    </view>
                </view>

                <view class="eg-board" :class="{ 'is-busy': busy }">
                    <view v-for="(row, rowIndex) in boardRows" :key="rowIndex" class="eg-row">
                        <view
                            v-for="tile in row"
                            :key="tile.id"
                            class="eg-cell"
                            :class="cellClasses(tile)"
                            :style="cellMotionStyle(tile.index)"
                            @tap="tapTile(tile.index)"
                            @touchstart="touchStart($event, tile.index)"
                            @touchmove.stop.prevent="touchMove($event, tile.index)"
                            @touchend="touchEnd($event, tile.index)"
                        >
                            <view v-if="tile.special" class="eg-special-mark" :class="`is-${tile.special}`">
                                <view v-if="tile.special === 'row' || tile.special === 'col'" class="eg-mini-flame">
                                    <view class="eg-mini-flame-core" />
                                </view>
                                <view v-else-if="tile.special === 'burst'" class="eg-l-special" aria-label="L形范围消除">
                                    <view class="eg-l-arm is-vertical" />
                                    <view class="eg-l-arm is-horizontal" />
                                    <view class="eg-l-core" />
                                </view>
                                <text v-else>✦</text>
                            </view>
                            <view v-if="fireClearingIndices.includes(tile.index)" class="eg-fire-clear" aria-hidden="true">
                                <view class="eg-fire-glow" />
                                <view class="eg-fire-sweep" />
                                <view class="eg-fire-flame is-left"><view /></view>
                                <view class="eg-fire-flame is-center"><view /></view>
                                <view class="eg-fire-flame is-right"><view /></view>
                                <view class="eg-fire-spark is-one" />
                                <view class="eg-fire-spark is-two" />
                                <view class="eg-fire-spark is-three" />
                            </view>
                            <image class="eg-food-image" :src="foodOf(tile.key).img" mode="aspectFit" />
                            <text class="eg-food-name">{{ foodOf(tile.key).short }}</text>
                            <view class="eg-sugar-tag" :class="`is-${foodOf(tile.key).level || 'low'}`">
                                {{ foodOf(tile.key).levelLabel || '低糖' }}
                            </view>
                            <view v-if="camelMode && foodOf(tile.key).replaceable" class="eg-replace-target">可替换</view>
                        </view>
                    </view>
                </view>

                <view v-if="impactText" class="eg-impact-text" :class="`is-level-${impactLevel}`">{{ impactText }}</view>

                <view v-if="camelJackpot.visible" class="eg-jackpot" :class="`is-match-${camelJackpot.count}`">
                    <view class="eg-jackpot-lights" />
                    <text class="eg-jackpot-kicker">{{ camelJackpot.kicker }}</text>
                    <view v-if="camelJackpot.count === 3" class="eg-triple-cans">
                        <view v-for="n in 3" :key="n" class="eg-triple-can">
                            <image :src="foodOf('camel').img" mode="aspectFit" />
                        </view>
                    </view>
                    <view v-else-if="camelJackpot.count === 4" class="eg-four-teaser">
                        <view class="eg-four-cans">
                            <view v-for="n in 5" :key="n" class="eg-four-can" :class="{ 'is-locked': n === 5 }">
                                <image v-if="n < 5" :src="foodOf('camel').img" mode="aspectFit" />
                                <text v-else>?</text>
                            </view>
                        </view>
                        <text class="eg-four-promise">还差 1 罐，777 大奖即将开启</text>
                    </view>
                    <view v-else class="eg-five-celebration">
                        <view class="eg-five-cans">
                            <view v-for="n in 5" :key="n" class="eg-five-can">
                                <image :src="foodOf('camel').img" mode="aspectFit" />
                            </view>
                        </view>
                        <view class="eg-five-sevens">
                            <text v-for="n in 3" :key="n">7</text>
                        </view>
                    </view>
                    <text class="eg-jackpot-title">{{ camelJackpot.title }}</text>
                    <text v-if="camelJackpot.count >= 5" class="eg-jackpot-points">777 ×2</text>
                    <text class="eg-jackpot-reward">{{ camelJackpot.reward }}</text>
                    <text class="eg-jackpot-note">仅为游戏内娱乐奖励</text>
                </view>

                <view class="eg-board-tip">
                    <TongjiIcon name="bulb" size="sm" color="#9a6718" />
                    <text>{{ statusText }}</text>
                </view>
            </view>

            <view class="eg-tools">
                <view class="eg-tool" :class="{ active: camelMode, disabled: camelCount <= 0 || busy }" @tap="toggleCamel">
                    <view class="eg-tool-icon eg-tool-icon--milk">
                        <image class="eg-tool-food-image" :src="foodOf('camel').img" mode="aspectFit" />
                        <view class="eg-replace-badge">
                            <TongjiIcon name="refresh" size="sm" color="#ffffff" />
                            <text>替换</text>
                        </view>
                    </view>
                    <view class="eg-tool-copy">
                        <text class="eg-tool-name">新疆纯驼乳粉</text>
                        <text class="eg-tool-desc">替换{{ foodOf(currentTheme.danger).short }} · 三杯连消换主题</text>
                    </view>
                    <view class="eg-tool-count">×{{ camelCount }}</view>
                </view>
            </view>

            <view class="eg-legend">
                <text><text class="eg-dot eg-dot--safe">●</text> 低糖</text>
                <text><text class="eg-dot eg-dot--mid">●</text> 中糖</text>
                <text><text class="eg-dot eg-dot--high">●</text> 主动三消高糖会结束</text>
            </view>
        </view>

        <view v-if="weeklyRankVisible" class="eg-weekly-overlay" @tap.stop>
            <view class="eg-weekly-card">
                <view class="eg-weekly-head">
                    <view>
                        <view class="eg-weekly-title-line">
                            <text class="eg-weekly-title">本周7人同行榜</text>
                            <text class="eg-weekly-preview" :class="{ 'is-offline': !platformConnected }">{{ weeklyStatusLabel }}</text>
                        </view>
                        <text class="eg-weekly-sub">{{ weeklyRangeLabel }} · {{ weeklySexLabel }} · {{ weeklyMemberCount }}/7人</text>
                    </view>
                    <view class="eg-weekly-medal">{{ weeklyMyRank }}</view>
                </view>

                <view class="eg-weekly-progress-card">
                    <view>
                        <text class="eg-weekly-progress-label">我的本周认糖</text>
                        <text class="eg-weekly-progress-value">{{ weeklyMyCount }}</text>
                    </view>
                    <view class="eg-weekly-progress-copy">
                        <text v-if="weeklyRankRise > 0">当前第 {{ weeklyMyRank }} 名 · 本周上升 {{ weeklyRankRise }} 名</text>
                        <text v-else>当前第 {{ weeklyMyRank }} 名 · 继续积累认糖</text>
                        <text v-if="weeklyIsFirst">太棒了，本周暂列第一！</text>
                        <text v-else>再认 {{ weeklyDistance }} 个超过上一名</text>
                    </view>
                </view>

                <view class="eg-weekly-list">
                    <view
                        v-for="player in weeklyRankRows"
                        :key="player.id"
                        class="eg-weekly-row"
                        :class="{ 'is-me': player.isMe, 'is-waiting': player.isWaiting }"
                    >
                        <text class="eg-weekly-place">{{ player.rank }}</text>
                        <view class="eg-weekly-avatar" :class="`is-tone-${player.tone}`">
                            <image v-if="player.avatarUrl" class="eg-weekly-avatar-image" :src="player.avatarUrl" mode="aspectFill" />
                            <text v-else>{{ player.avatar }}</text>
                        </view>
                        <text class="eg-weekly-name">{{ player.name }}</text>
                        <text v-if="player.isMe" class="eg-weekly-me">我</text>
                        <text class="eg-weekly-count">{{ player.count }}</text>
                    </view>
                </view>

                <view v-if="!platformConnected" class="eg-weekly-connection" @tap.stop="retryPlatformConnection">
                    <text>{{ platformConnectionHint }}</text>
                    <text class="eg-weekly-retry">点击重试</text>
                </view>

                <view class="eg-cheer-card" :class="`is-${currentCheer.tone}`">
                    <view class="eg-cheer-top">
                        <text class="eg-cheer-source">{{ currentCheer.source }}</text>
                        <text class="eg-cheer-change" @tap.stop="cycleCheer">换一句</text>
                    </view>
                    <text class="eg-cheer-copy">“{{ currentCheer.text }}”</text>
                    <text v-if="currentCheer.userGenerated" class="eg-cheer-note">亲友自定义内容，不作为医学判断</text>
                </view>

                <view class="eg-weekly-start" @tap="closeWeeklyRank">{{ restoredGame ? '继续上次游戏' : '开始消除' }}</view>
                <button class="eg-weekly-share" open-type="share">邀请亲友一起认糖</button>
                <text class="eg-weekly-footnote">使用平台昵称参加真实周榜，每周一重新分组；分享不会自动绑定家庭关系</text>
            </view>
        </view>

        <view v-if="reshuffleVisible" class="eg-reshuffle-overlay" @tap.stop>
            <view class="eg-reshuffle-card">
                <view class="eg-reshuffle-icon">
                    <TongjiIcon name="refresh" size="lg" color="#ffffff" />
                </view>
                <text class="eg-reshuffle-title">{{ reshuffleDone ? '洗牌完成' : '没有可消除组合' }}</text>
                <text class="eg-reshuffle-desc">{{ reshuffleDone ? '新的消除机会已经出现' : '正在为您重新洗牌，请稍候' }}</text>
            </view>
        </view>

        <view v-if="stageVisible" class="eg-stage-overlay" @tap.stop>
            <view class="eg-stage-card">
                <text class="eg-stage-kicker">新菜单已解锁</text>
                <text class="eg-stage-number">第 {{ stageMeta.number }} 站</text>
                <text class="eg-stage-title">{{ stageMeta.title }}</text>
                <view class="eg-stage-foods">
                    <view v-for="food in stageMeta.foods" :key="food.key" class="eg-stage-food" :class="{ 'is-danger': food.high }">
                        <image :src="food.img" mode="aspectFit" />
                        <text>{{ food.short }}</text>
                    </view>
                </view>
                <text class="eg-stage-tip">本组橙框食物可用驼乳粉替换</text>
            </view>
        </view>

        <view v-if="gameOver" class="eg-overlay" @tap.stop>
            <view class="eg-modal eg-result-modal">
                <view class="eg-result-warning">!</view>
                <text class="eg-modal-title">本局结束</text>
                <text class="eg-result-food">你消除了：{{ endedFood.name }}</text>
                <view class="eg-knowledge-card">
                    <text class="eg-knowledge-title">为什么要注意？</text>
                    <text class="eg-knowledge-copy">{{ endedFood.tip }}</text>
                    <text class="eg-knowledge-review">科普内容上线前由甄养堂医生审核</text>
                </view>
                <view class="eg-result-score"><text>本局得分</text><text>{{ score }}</text></view>
                <view class="eg-result-best"><text>历史最高</text><text>{{ bestScore }}</text></view>
                <view class="eg-primary-btn" @tap="startGame">再玩一局</view>
                <button class="eg-share-btn" open-type="share">分享本局认糖成绩</button>
                <view class="eg-secondary-btn" @tap="goBack">返回血糖周报</view>
            </view>
        </view>

    </view>
</template>

<script setup>
import { computed, getCurrentInstance, ref } from 'vue'
import { onHide, onLoad, onShareAppMessage, onShareTimeline, onShow, onUnload } from '@dcloudio/uni-app'
import TongjiIcon from '../components/TongjiIcon.vue'
import { useGameSfx } from './composables/useGameSfx.js'
import { useGamePlatform } from './composables/useGamePlatform.js'
import { useGameAuth } from './composables/useGameAuth.js'

const FOOD_IMAGE_BASE_URL = 'https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/games/food/'

const ROWS = 5
const COLS = 4
const GAME_DIFFICULTY = 5
const HIGH_CAP = 3
const MAX_CAMEL_COUNT = 5
const OVERFLOW_CAMEL_SCORE = 100
const MAX_CASCADE_WAVES = 3
const COMBO_TASK_WAVE = 2
const COMBO_TASK_TARGET = 2
const COMBO_PITY_TURNS = 6
const MEGA_COMBO_SCORE = 300
const ACTIVE_GAME_KEY = 'tongji_endless_active_game_v1'
const ACTIVE_GAME_VERSION = 1
const DANGER_DROP_CHANCE = 0.05 + GAME_DIFFICULTY * 0.01
const COLUMN_GROUP_BIAS = 0.86 - GAME_DIFFICULTY * 0.028
const CHEER_MESSAGES = [
    { source: '同行鼓励', text: '你需要加油哦！再向前一点，就能超过上一名。', tone: 'warm' },
    { source: '同行鼓励', text: '你太棒了！今天也在认真认识食物。', tone: 'bright' },
    { source: '开心鼓励', text: '你肯定是个大美女（大帅哥）！保持好心情。', tone: 'bright' },
    { source: '亲友留言示例', text: '游戏虽好，可不要贪吃哦！', tone: 'family' },
    { source: '亲友留言示例', text: '差不多行了，赶快洗洗睡吧！', tone: 'family' },
    { source: '亲友留言示例', text: '你太努力了，你是我的榜样！', tone: 'warm' },
    { source: '亲友留言示例', text: '好样的！', tone: 'bright' },
    { source: '亲友自定义留言示例', text: '我血糖已经正常了，你们玩吧。', tone: 'family', userGenerated: true }
]
const FOODS = {
    corn: { name: '玉米', short: '玉米', emoji: '🌽', color: 'yellow', level: 'mid', levelLabel: '中糖' },
    egg: { name: '鸡蛋', short: '鸡蛋', emoji: '🥚', color: 'cream', level: 'low', levelLabel: '低糖' },
    tomato: { name: '番茄', short: '番茄', emoji: '🍅', color: 'red', level: 'low', levelLabel: '低糖' },
    potato: { name: '红薯', short: '红薯', emoji: '🍠', color: 'purple', level: 'mid', levelLabel: '中糖' },
    cucumber: { name: '黄瓜', short: '黄瓜', emoji: '🥒', color: 'yellow', level: 'low', levelLabel: '低糖' },
    lettuce: { name: '生菜', short: '生菜', emoji: '🥬', color: 'cream', level: 'low', levelLabel: '低糖' },
    grainMantou: { name: '杂粮馒头', short: '杂粮馒头', emoji: '🫓', color: 'red', level: 'mid', levelLabel: '中糖' },
    wonton: { name: '馄饨', short: '馄饨', emoji: '🥟', color: 'purple', level: 'mid', levelLabel: '中糖' },
    celery: { name: '芹菜', short: '芹菜', emoji: '🌿', color: 'yellow', level: 'low', levelLabel: '低糖' },
    mushroom: { name: '香菇', short: '香菇', emoji: '🍄', color: 'cream', level: 'low', levelLabel: '低糖' },
    chicken: { name: '鸡肉', short: '鸡肉', emoji: '🍗', color: 'red', level: 'mid', levelLabel: '中糖' },
    taro: { name: '芋头', short: '芋头', emoji: '🥔', color: 'purple', level: 'mid', levelLabel: '中糖' },
    broccoli: { name: '西兰花', short: '西兰花', emoji: '🥦', color: 'yellow', level: 'low', levelLabel: '低糖' },
    milk: { name: '牛奶', short: '牛奶', emoji: '🥛', color: 'cream', level: 'low', levelLabel: '低糖' },
    brownRice: { name: '糙米饭', short: '糙米饭', emoji: '🍙', color: 'red', level: 'mid', levelLabel: '中糖' },
    apple: { name: '苹果', short: '苹果', emoji: '🍎', color: 'yellow', level: 'low', levelLabel: '低糖' },
    strawberry: { name: '草莓', short: '草莓', emoji: '🍓', color: 'cream', level: 'low', levelLabel: '低糖' },
    banana: { name: '香蕉', short: '香蕉', emoji: '🍌', color: 'red', level: 'mid', levelLabel: '中糖' },
    mango: { name: '芒果', short: '芒果', emoji: '🥭', color: 'purple', level: 'mid', levelLabel: '中糖' },
    tea: { name: '红茶', short: '红茶', emoji: '🍵', color: 'yellow', level: 'low', levelLabel: '低糖' },
    blackCoffee: { name: '黑咖啡', short: '黑咖啡', emoji: '☕', color: 'cream', level: 'low', levelLabel: '低糖' },
    coffee: { name: '咖啡', short: '咖啡', emoji: '☕', color: 'red', level: 'mid', levelLabel: '中糖' },
    pineapple: { name: '菠萝', short: '菠萝', emoji: '🍍', color: 'purple', level: 'mid', levelLabel: '中糖' },
    pepper: { name: '青椒', short: '青椒', emoji: '🫑', color: 'yellow', level: 'low', levelLabel: '低糖' },
    greenBean: { name: '四季豆', short: '四季豆', emoji: '🫛', color: 'cream', level: 'low', levelLabel: '低糖' },
    shrimp: { name: '虾仁', short: '虾仁', emoji: '🦐', color: 'red', level: 'mid', levelLabel: '中糖' },
    udon: { name: '乌冬面', short: '乌冬面', emoji: '🍲', color: 'purple', level: 'mid', levelLabel: '中糖' },
    onion: { name: '洋葱', short: '洋葱', emoji: '🧅', color: 'yellow', level: 'low', levelLabel: '低糖' },
    congee: {
        name: '白米粥', short: '白粥', emoji: '🥣', color: 'orange', level: 'high', levelLabel: '高糖', high: true, replaceable: true,
        tip: '白米粥煮得软烂，消化吸收通常较快。糖尿病患者应结合食用量、搭配和个人血糖情况，在医生或营养师指导下合理选择。'
    },
    eightCongee: { name: '八宝粥', short: '八宝粥', emoji: '🥣', color: 'orange', level: 'high', levelLabel: '高糖', high: true, replaceable: true, tip: '八宝粥常含糯米、豆类或添加糖，实际影响与配方和食用量有关，请留意营养成分。' },
    centuryCongee: { name: '皮蛋瘦肉粥', short: '瘦肉粥', emoji: '🥣', color: 'orange', level: 'high', levelLabel: '高糖', high: true, replaceable: true, tip: '粥类煮得软烂后通常吸收较快，搭配和食用量需要结合个人情况。' },
    milletCongee: { name: '小米粥', short: '小米粥', emoji: '🥣', color: 'orange', level: 'high', levelLabel: '高糖', high: true, replaceable: true, tip: '小米粥的升糖影响与浓稠度和食用量有关，游戏等级需由医生最终审核。' },
    orangeJuice: { name: '柳橙汁', short: '橙汁', emoji: '🧃', color: 'orange', level: 'high', levelLabel: '高糖', high: true, replaceable: true, tip: '果汁缺少完整水果的部分膳食纤维，饮用量和是否添加糖都需要注意。' },
    soda: { name: '含糖汽水', short: '汽水', emoji: '🥤', color: 'orange', level: 'high', levelLabel: '高糖', high: true, replaceable: true, tip: '含糖饮料中的糖吸收较快，日常应优先选择不加糖的饮品。' },
    riceNoodleSoup: { name: '汤粉', short: '汤粉', emoji: '🍜', color: 'orange', level: 'high', levelLabel: '高糖', high: true, replaceable: true, tip: '汤粉以精制米粉为主，实际影响与分量和配菜有关，应注意搭配。' },
    noodleSoup: { name: '汤面', short: '汤面', emoji: '🍜', color: 'orange', level: 'high', levelLabel: '高糖', high: true, replaceable: true, tip: '汤面以精细面食为主，汤汁和分量需要注意，具体建议以医生审核为准。' },
    milkTea: { name: '含糖奶茶', short: '奶茶', emoji: '🧋', color: 'orange', level: 'high', levelLabel: '高糖', high: true, replaceable: true, tip: '含糖奶茶可能同时含有较多添加糖和能量，应关注配方与饮用量。' },
    camel: { name: '驼乳', short: '驼乳', emoji: '🥛', color: 'milk', level: 'prop', levelLabel: '道具', product: true }
}
Object.keys(FOODS).forEach((key) => { FOODS[key].img = `${FOOD_IMAGE_BASE_URL}${key}.jpg` })
const FOOD_GROUPS = [
    { title: '中式早餐', safe: ['egg', 'cucumber', 'corn', 'grainMantou'], danger: 'congee' },
    { title: '家常粥餐', safe: ['celery', 'mushroom', 'chicken', 'taro'], danger: 'centuryCongee' },
    { title: '粗粮早餐', safe: ['broccoli', 'milk', 'brownRice', 'potato'], danger: 'milletCongee' },
    { title: '水果饮品', safe: ['apple', 'strawberry', 'banana', 'mango'], danger: 'orangeJuice' },
    { title: '日常饮品', safe: ['milk', 'tea', 'blackCoffee', 'coffee'], danger: 'soda' },
    { title: '南方汤粉', safe: ['pepper', 'greenBean', 'shrimp', 'udon'], danger: 'riceNoodleSoup' },
    { title: '北方面食', safe: ['onion', 'celery', 'corn', 'grainMantou'], danger: 'noodleSoup' },
]

const {
    enabled: sfxEnabled,
    play: playSfx,
    playLayer: playSfxLayer,
    playDrop: playDropSfx,
    warmUp: warmUpSfx
} = useGameSfx()

const { proxy } = getCurrentInstance()
const { ensureLoggedIn } = useGameAuth(proxy)
const {
    connected: platformConnected,
    loading: platformLoading,
    syncStatus: platformSyncStatus,
    lastError: platformLastError,
    leaderboard: platformLeaderboard,
    confirmedSessionLearned,
    connect: connectPlatform,
    beginSession: beginPlatformSession,
    getSessionKey: getPlatformSessionKey,
    queueProgress: queuePlatformProgress,
    flushProgress: flushPlatformProgress,
    syncAndRefresh: syncAndRefreshPlatform,
    recordShare: recordPlatformShare,
    dispose: disposePlatform
} = useGamePlatform(proxy, ensureLoggedIn)

const board = ref([])
const selected = ref(-1)
const score = ref(0)
const bestScore = ref(0)
const learned = ref(0)
const combo = ref(0)
const feastMoves = ref(0)
const camelCount = ref(3)
const camelMode = ref(false)
const busy = ref(false)
const gameOver = ref(false)
const themeIndex = ref(0)
const endedFood = ref(FOODS.congee)
const taskIndex = ref(0)
const taskProgress = ref(0)
const comboDryTurns = ref(0)
const taskRewardVisible = ref(false)
const taskRewardKey = ref(0)
const statusText = ref('交换相邻食物，三个相同即可消除')
const navStyle = ref({ paddingTop: '20px' })
const touch = { x: 0, y: 0, index: -1 }
const dragIndex = ref(-1)
const dragTarget = ref(-1)
const dragX = ref(0)
const dragY = ref(0)
const clearingIndices = ref([])
const fireClearingIndices = ref([])
const impactText = ref('')
const impactLevel = ref(1)
const camelJackpot = ref({ visible: false, count: 0, kicker: '', title: '', reward: '' })
const stageVisible = ref(false)
const stageMeta = ref({ number: 1, title: '', foods: [] })
const reshuffleVisible = ref(false)
const reshuffleDone = ref(false)
const reshuffling = ref(false)
const confettiVisible = ref(false)
const weeklyRankVisible = ref(false)
const restoredGame = ref(false)
const cheerIndex = ref(0)
const CONFETTI_COLORS = ['#ff3154', '#ffd928', '#28c76f', '#4d8dff', '#a855f7', '#ff7a1a']
const confettiPieces = Array.from({ length: 34 }, (_, index) => ({
    id: index,
    emoji: index % 8 === 0 ? '🎉' : (index % 11 === 0 ? '★' : ''),
    style: {
        left: `${(index * 37 + 7) % 100}%`,
        backgroundColor: index % 8 === 0 ? 'transparent' : CONFETTI_COLORS[index % CONFETTI_COLORS.length],
        animationDelay: `${(index % 9) * .065}s`,
        animationDuration: `${3.7 + (index % 5) * .36}s`,
        width: `${12 + (index % 3) * 5}rpx`,
        height: `${22 + (index % 4) * 6}rpx`
    }
}))
let suppressTapUntil = 0
let tileId = 1
let hintTimer = null
let jackpotTimer = null
let confettiTimer = null
let stageTimer = null
let taskRewardTimer = null
let pendingThemeMatchCount = 0
let taskRewardStatusUntil = 0
let sfxGesturePrimed = false
let incomingInviteCode = ''

const currentTheme = computed(() => FOOD_GROUPS[themeIndex.value % FOOD_GROUPS.length])
const currentTask = computed(() => {
    const safe = currentTheme.value.safe
    const templates = [
        { key: safe[0], label: `消除 7 个${foodOf(safe[0]).short}`, target: 7, kind: 'food' },
        { key: 'special', label: '制造 1 个惊喜棋子', target: 1, kind: 'special' },
        { key: 'combo', label: '完成连消 ×2', target: COMBO_TASK_TARGET, kind: 'combo' },
        { key: safe[1], label: `消除 8 个${foodOf(safe[1]).short}`, target: 8, kind: 'food' }
    ]
    return templates[taskIndex.value % templates.length]
})
const taskPercent = computed(() => Math.min(100, taskProgress.value / currentTask.value.target * 100))
const highCount = computed(() => board.value.filter(tile => tile && foodOf(tile.key).high).length)
const boardRows = computed(() => Array.from({ length: ROWS }, (_, row) => (
    board.value.slice(row * COLS, row * COLS + COLS).map((tile, col) => ({ ...tile, index: row * COLS + col }))
)))
const pendingWeeklyLearned = computed(() => Math.max(0, learned.value - confirmedSessionLearned.value))
const weeklyMyCount = computed(() => Number(platformLeaderboard.value.me?.count || 0) + pendingWeeklyLearned.value)
const weeklyRankRows = computed(() => {
    const remote = Array.isArray(platformLeaderboard.value.players)
        ? platformLeaderboard.value.players
        : []
    const userData = uni.getStorageSync('userData') || {}
    const fallbackName = String(userData.nickname || '我')
    const source = remote.length
        ? remote.map((player, index) => ({
            ...player,
            isMe: !!(player.is_me || player.isMe),
            avatarUrl: player.avatar || '',
            avatar: String(player.name || '同行').slice(0, 1),
            tone: index % 7 + 1,
            count: player.is_me || player.isMe ? weeklyMyCount.value : Number(player.count || 0)
        }))
        : [{
            id: 'local-me',
            name: fallbackName,
            avatar: fallbackName.slice(0, 1) || '我',
            avatarUrl: userData.avatar || '',
            count: weeklyMyCount.value,
            tone: 1,
            isMe: true
        }]
    const ranked = source
        .sort((a, b) => b.count - a.count || Number(b.best_score || 0) - Number(a.best_score || 0))
        .map((player, index) => ({ ...player, rank: index + 1 }))
    while (ranked.length < 7) {
        const rank = ranked.length + 1
        ranked.push({
            id: `waiting-${rank}`,
            rank,
            name: '等待同行加入',
            avatar: '+',
            avatarUrl: '',
            count: '—',
            tone: rank,
            isMe: false,
            isWaiting: true
        })
    }
    return ranked.slice(0, 7)
})
const weeklyMyRank = computed(() => weeklyRankRows.value.find(player => player.isMe)?.rank || 1)
const weeklyRankRise = computed(() => 0)
const weeklyIsFirst = computed(() => weeklyMyRank.value === 1)
const weeklyDistance = computed(() => {
    const mineIndex = weeklyRankRows.value.findIndex(player => player.isMe)
    if (mineIndex <= 0) return 0
    return Math.max(1, weeklyRankRows.value[mineIndex - 1].count - weeklyMyCount.value + 1)
})
const currentCheer = computed(() => CHEER_MESSAGES[cheerIndex.value % CHEER_MESSAGES.length])
const weeklyRangeLabel = computed(() => {
    const start = platformLeaderboard.value.week_start
    const end = platformLeaderboard.value.week_end
    if (!start || !end) return getWeekRangeLabel(new Date())
    return `${formatWeekDate(start)}—${formatWeekDate(end)}`
})
const weeklySexLabel = computed(() => platformLeaderboard.value.sex_label || '同行')
const weeklyMemberCount = computed(() => {
    const reported = Number(platformLeaderboard.value.member_count || 0)
    const remoteCount = Array.isArray(platformLeaderboard.value.players)
        ? platformLeaderboard.value.players.length
        : 0
    return Math.max(1, Math.min(7, reported || remoteCount || 1))
})
const weeklyStatusLabel = computed(() => {
    if (platformLoading.value) return '连接中'
    if (!platformConnected.value) return '离线记录中'
    if (platformSyncStatus.value === 'pending' || platformSyncStatus.value === 'syncing') return '保存中'
    return '真实周榜'
})
const platformConnectionHint = computed(() => (
    platformLoading.value
        ? '正在连接甄养堂账号和本周同行榜'
        : (platformLastError.value || '当前成绩暂存在本机，联网登录后会自动补传')
))

onShareAppMessage(() => {
    recordPlatformShare()
    return {
        title: gameShareTitle(),
        path: gameSharePath()
    }
})

onShareTimeline(() => {
    recordPlatformShare()
    const inviteCode = String(platformLeaderboard.value.invite_code || '')
    return {
        title: gameShareTitle(),
        query: inviteCode ? `from=share&invite_code=${encodeURIComponent(inviteCode)}` : 'from=share'
    }
})

function initNavLayout() {
    let paddingTop = 20
    let paddingRight = uni.upx2px(28)

    try {
        const info = uni.getSystemInfoSync()
        paddingTop = Number(info.safeAreaInsets?.top || info.statusBarHeight) || 20

        // #ifdef MP-WEIXIN
        const menu = typeof uni.getMenuButtonBoundingClientRect === 'function'
            ? uni.getMenuButtonBoundingClientRect()
            : null
        if (menu && menu.width > 0) {
            paddingTop = Math.max(paddingTop, Number(menu.top) || 0)
            paddingRight = Math.max(
                paddingRight,
                (Number(info.windowWidth) || 0) - Number(menu.left) + uni.upx2px(8)
            )
        }
        // #endif
    } catch (_) {}

    navStyle.value = {
        paddingTop: `${paddingTop}px`,
        paddingRight: `${paddingRight}px`
    }
}

onLoad((options = {}) => {
    initNavLayout()
    bestScore.value = Number(uni.getStorageSync('tongji_endless_best') || 0)
    incomingInviteCode = String(options.invite_code || '').trim()
    if (!restoreActiveGame()) startGame()
    weeklyRankVisible.value = true
    connectPlatform(incomingInviteCode)
})

onShow(() => {
    connectPlatform(incomingInviteCode).then((ok) => {
        if (ok) flushPlatformProgress()
    })
})

onHide(() => {
    saveActiveGame()
})

onUnload(() => {
    saveActiveGame()
    if (learned.value > 0 || gameOver.value) {
        queuePlatformProgress(learned.value, score.value, gameOver.value)
        flushPlatformProgress()
    }
    disposePlatform()
    if (hintTimer) clearTimeout(hintTimer)
    if (jackpotTimer) clearTimeout(jackpotTimer)
    if (confettiTimer) clearTimeout(confettiTimer)
    if (stageTimer) clearTimeout(stageTimer)
    if (taskRewardTimer) clearTimeout(taskRewardTimer)
})

function makeTile(key, special = '') {
    return { id: tileId++, key, special }
}

function foodOf(key) {
    return FOODS[key] || FOODS.corn
}

function getWeekRangeLabel(now) {
    const date = new Date(now)
    const day = date.getDay() || 7
    const monday = new Date(date)
    monday.setDate(date.getDate() - day + 1)
    const sunday = new Date(monday)
    sunday.setDate(monday.getDate() + 6)
    const label = value => `${value.getMonth() + 1}月${value.getDate()}日`
    return `${label(monday)}—${label(sunday)}`
}

function formatWeekDate(value) {
    const parts = String(value).split('-').map(Number)
    if (parts.length !== 3) return String(value)
    return `${parts[1]}月${parts[2]}日`
}

function gameShareTitle() {
    if (learned.value > 0) {
        return `我在识糖小课堂认识了${learned.value}份食物，一起来挑战吧`
    }
    return '识糖小课堂：边玩边认识常见食物'
}

function gameSharePath() {
    const inviteCode = String(platformLeaderboard.value.invite_code || '')
    const query = inviteCode ? `?from=share&invite_code=${encodeURIComponent(inviteCode)}` : '?from=share'
    return `/tongji/endless-game/index${query}`
}

function openWeeklyRank() {
    primeSfxFromGesture()
    if (gameOver.value) return
    if (busy.value || reshuffling.value || stageVisible.value || camelJackpot.value.visible) {
        statusText.value = '本次消除完成后即可查看同行榜'
        return
    }
    weeklyRankVisible.value = true
    syncAndRefreshPlatform(learned.value, score.value)
    playSfx('select', .72)
}

function closeWeeklyRank() {
    primeSfxFromGesture()
    weeklyRankVisible.value = false
    playSfx('select', .86)
    scheduleHint()
}

function cycleCheer() {
    primeSfxFromGesture()
    cheerIndex.value = (cheerIndex.value + 1) % CHEER_MESSAGES.length
    playSfx('select', .68)
}

async function retryPlatformConnection() {
    if (platformLoading.value) return
    const connected = await connectPlatform(incomingInviteCode)
    if (connected) {
        await syncAndRefreshPlatform(learned.value, score.value)
        uni.showToast({ title: '周榜已更新', icon: 'none' })
        return
    }
    uni.showToast({ title: platformConnectionHint.value, icon: 'none' })
}

function currentWeekStartKey(now = new Date()) {
    const date = new Date(now)
    const day = date.getDay() || 7
    date.setHours(0, 0, 0, 0)
    date.setDate(date.getDate() - day + 1)
    const pad = value => String(value).padStart(2, '0')
    return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`
}

function clearActiveGame() {
    try { uni.removeStorageSync(ACTIVE_GAME_KEY) } catch (_) {}
}

function saveActiveGame() {
    if (gameOver.value) {
        clearActiveGame()
        return false
    }
    if (busy.value || board.value.length !== ROWS * COLS) return false
    const stableBoard = board.value.every(tile => tile && FOODS[tile.key])
    if (!stableBoard) return false
    try {
        uni.setStorageSync(ACTIVE_GAME_KEY, {
            version: ACTIVE_GAME_VERSION,
            week_start: currentWeekStartKey(),
            session_key: getPlatformSessionKey(),
            confirmed_session_learned: Math.max(0, Number(confirmedSessionLearned.value) || 0),
            saved_at: Date.now(),
            theme_index: themeIndex.value,
            board: board.value.map(tile => ({ id: tile.id, key: tile.key, special: tile.special || '' })),
            score: score.value,
            learned: learned.value,
            combo: combo.value,
            feast_moves: feastMoves.value,
            camel_count: camelCount.value,
            task_index: taskIndex.value,
            task_progress: taskProgress.value,
            combo_dry_turns: comboDryTurns.value
        })
        return true
    } catch (_) {
        return false
    }
}

function restoreActiveGame() {
    let saved
    try { saved = uni.getStorageSync(ACTIVE_GAME_KEY) } catch (_) { return false }
    const validBoard = Array.isArray(saved?.board)
        && saved.board.length === ROWS * COLS
        && saved.board.every(tile => tile && FOODS[tile.key])
    if (saved?.version !== ACTIVE_GAME_VERSION || saved?.week_start !== currentWeekStartKey() || !validBoard) {
        clearActiveGame()
        return false
    }

    themeIndex.value = Math.max(0, Math.min(FOOD_GROUPS.length - 1, Number(saved.theme_index) || 0))
    board.value = saved.board.map(tile => ({
        id: Math.max(1, Number(tile.id) || tileId++),
        key: tile.key,
        special: ['row', 'col', 'all', 'burst'].includes(tile.special) ? tile.special : ''
    }))
    tileId = Math.max(tileId, ...board.value.map(tile => tile.id + 1))
    score.value = Math.max(0, Number(saved.score) || 0)
    learned.value = Math.max(0, Number(saved.learned) || 0)
    combo.value = Math.max(0, Number(saved.combo) || 0)
    feastMoves.value = Math.max(0, Number(saved.feast_moves) || 0)
    camelCount.value = Math.max(0, Math.min(MAX_CAMEL_COUNT, Number(saved.camel_count) || 0))
    taskIndex.value = Math.max(0, Number(saved.task_index) || 0) % 4
    taskProgress.value = Math.max(0, Number(saved.task_progress) || 0)
    comboDryTurns.value = Math.max(0, Number(saved.combo_dry_turns) || 0)
    beginPlatformSession(saved.session_key)
    confirmedSessionLearned.value = Math.max(0, Number(saved.confirmed_session_learned) || 0)
    selected.value = -1
    camelMode.value = false
    busy.value = false
    gameOver.value = false
    taskRewardVisible.value = false
    clearingIndices.value = []
    fireClearingIndices.value = []
    impactText.value = ''
    camelJackpot.value.visible = false
    stageVisible.value = false
    reshuffleVisible.value = false
    reshuffleDone.value = false
    reshuffling.value = false
    confettiVisible.value = false
    pendingThemeMatchCount = 0
    taskRewardStatusUntil = 0
    endedFood.value = foodOf(currentTheme.value.danger)
    restoredGame.value = true
    resetDrag()
    statusText.value = '已恢复上次未完成的游戏和得分'
    scheduleHint()
    return true
}

function startGame() {
    if (hintTimer) clearTimeout(hintTimer)
    clearActiveGame()
    beginPlatformSession()
    themeIndex.value = 0
    board.value = buildPlayableBoard(openingKeysForTheme(currentTheme.value))
    selected.value = -1
    score.value = 0
    learned.value = 0
    combo.value = 0
    feastMoves.value = 0
    camelCount.value = 3
    camelMode.value = false
    busy.value = false
    gameOver.value = false
    taskIndex.value = 0
    taskProgress.value = 0
    comboDryTurns.value = 0
    taskRewardVisible.value = false
    clearingIndices.value = []
    fireClearingIndices.value = []
    impactText.value = ''
    camelJackpot.value.visible = false
    stageVisible.value = false
    reshuffleVisible.value = false
    reshuffleDone.value = false
    reshuffling.value = false
    confettiVisible.value = false
    pendingThemeMatchCount = 0
    taskRewardStatusUntil = 0
    restoredGame.value = false
    resetDrag()
    statusText.value = '交换相邻食物，三个相同即可消除'
    saveActiveGame()
    scheduleHint()
}

function goBack() {
    const pages = getCurrentPages()
    if (pages.length > 1) {
        uni.navigateBack({
            delta: 1,
            fail: () => uni.redirectTo({ url: '/tongji/pages/weekly' })
        })
        return
    }
    uni.redirectTo({ url: '/tongji/pages/weekly' })
}

function showRules() {
    uni.showModal({
        title: '玩法说明',
        content: `当前难度：${GAME_DIFFICULTY}/10\n\n交换相邻食物，三个相同即可消除。四连、直线五连会生成火焰惊喜棋子；横向三个与竖向三个相交的 L/T 形五消，会生成范围爆破棋子，再次消除可清除周围九格。连消×2会计入连消小目标，连续普通消除后会出现一次连消机会；连消×3可获得额外惊喜奖励。完成小目标直接获得 1 罐驼乳粉；驼乳粉可替换本组指定稀食或饮品，三个驼乳连消后进入下一组食品。主动三消“高糖”食物代表吃掉，会结束本局；火焰特效波及高糖食物代表将它移出餐盘，不算吃掉。没有可走步时会自动洗牌。`,
        showCancel: false,
        confirmText: '知道了'
    })
}

function toggleSfx() {
    sfxEnabled.value = !sfxEnabled.value
    try { uni.setStorageSync('tongji_game_sfx_enabled', sfxEnabled.value ? '1' : '0') } catch (_) {}
    if (sfxEnabled.value) {
        primeSfxFromGesture()
        playSfx('select', .8)
    }
    uni.showToast({ title: sfxEnabled.value ? '音效已开启' : '音效已关闭', icon: 'none' })
}

function primeSfxFromGesture() {
    if (sfxGesturePrimed || !sfxEnabled.value) return
    sfxGesturePrimed = true
    warmUpSfx()
}

function cellClasses(tile) {
    const food = foodOf(tile.key)
    return [
        `is-${food.color}`,
        `is-level-${food.level}`,
        {
            'is-selected': selected.value === tile.index,
            'is-high': food.high,
            'is-camel-target': camelMode.value && food.replaceable,
            'is-clearing': clearingIndices.value.includes(tile.index),
            'is-fire-clearing': fireClearingIndices.value.includes(tile.index),
            'is-dragging': dragIndex.value === tile.index || dragTarget.value === tile.index
        }
    ]
}

function cellMotionStyle(index) {
    if (dragIndex.value === index) return { transform: `translate(${dragX.value}px, ${dragY.value}px) scale(1.05)`, zIndex: 6 }
    if (dragTarget.value === index) return { transform: `translate(${-dragX.value * .72}px, ${-dragY.value * .72}px) scale(.96)`, zIndex: 4 }
    return {}
}

function touchStart(event, index) {
    primeSfxFromGesture()
    const point = event.touches?.[0]
    if (!point) return
    touch.x = point.clientX
    touch.y = point.clientY
    touch.index = index
    dragIndex.value = index
    dragTarget.value = -1
    dragX.value = 0
    dragY.value = 0
}

function touchMove(event, index) {
    const point = event.touches?.[0]
    if (!point || touch.index !== index || busy.value) return
    const rawX = point.clientX - touch.x
    const rawY = point.clientY - touch.y
    const horizontal = Math.abs(rawX) > Math.abs(rawY)
    dragX.value = horizontal ? Math.max(-82, Math.min(82, rawX)) : 0
    dragY.value = horizontal ? 0 : Math.max(-82, Math.min(82, rawY))
    let target = index
    if (horizontal && Math.abs(rawX) > 10) target += rawX > 0 ? 1 : -1
    if (!horizontal && Math.abs(rawY) > 10) target += rawY > 0 ? COLS : -COLS
    dragTarget.value = target >= 0 && target < ROWS * COLS && isAdjacent(index, target) ? target : -1
}

async function touchEnd(event, index) {
    const point = event.changedTouches?.[0]
    if (!point || touch.index !== index) {
        resetDrag()
        return
    }
    const dx = point.clientX - touch.x
    const dy = point.clientY - touch.y
    if (Math.max(Math.abs(dx), Math.abs(dy)) < 32) {
        resetDrag()
        return
    }
    let target = index
    if (Math.abs(dx) > Math.abs(dy)) target += dx > 0 ? 1 : -1
    else target += dy > 0 ? COLS : -COLS
    suppressTapUntil = Date.now() + 350
    if (target >= 0 && target < ROWS * COLS && isAdjacent(index, target)) {
        const horizontal = Math.abs(dx) > Math.abs(dy)
        dragX.value = horizontal ? (dx > 0 ? 78 : -78) : 0
        dragY.value = horizontal ? 0 : (dy > 0 ? 78 : -78)
        dragTarget.value = target
        playSfx('swap', .85)
        await wait(85)
        resetDrag()
        trySwap(index, target, true)
    } else {
        resetDrag()
        playSfx('swapFail', .65)
    }
}

function resetDrag() {
    dragIndex.value = -1
    dragTarget.value = -1
    dragX.value = 0
    dragY.value = 0
}

async function tapTile(index) {
    primeSfxFromGesture()
    if (Date.now() < suppressTapUntil) return
    if (busy.value || gameOver.value) return
    const tile = board.value[index]
    if (camelMode.value) {
        if (!foodOf(tile.key).replaceable) {
            statusText.value = `本组驼乳粉用于替换${foodOf(currentTheme.value.danger).short}`
            return
        }
        board.value[index] = makeTile('camel')
        board.value = [...board.value]
        camelCount.value -= 1
        camelMode.value = false
        selected.value = -1
        playSfxLayer('insulin', 'sparkle', 55, .5)
        vibrate('light')
        const groups = findMatches(board.value)
        if (groups.length) {
            busy.value = true
            statusText.value = '驼乳棋子连起来了！'
            await resolveMatches(groups, index)
            busy.value = false
            finishTurn()
        } else {
            statusText.value = `已将${foodOf(currentTheme.value.danger).short}移出餐盘，凑齐三个驼乳可换主题`
            saveActiveGame()
            if (!checkBoardHealth()) scheduleHint()
        }
        return
    }
    if (selected.value < 0) {
        selected.value = index
        playSfx('select', .75)
        statusText.value = '再选择一个相邻食物进行交换'
        return
    }
    if (selected.value === index) {
        selected.value = -1
        playSfx('deselect', .65)
        return
    }
    if (!isAdjacent(selected.value, index)) {
        selected.value = index
        statusText.value = '只能交换上下左右相邻的食物'
        return
    }
    const from = selected.value
    selected.value = -1
    trySwap(from, index)
}

function toggleCamel() {
    primeSfxFromGesture()
    if (busy.value || camelCount.value <= 0 || gameOver.value) return
    camelMode.value = !camelMode.value
    selected.value = -1
    statusText.value = camelMode.value ? `请选择橙色边框的${foodOf(currentTheme.value.danger).short}进行替换` : '已取消使用驼乳粉'
}

function isAdjacent(a, b) {
    const ar = Math.floor(a / COLS)
    const ac = a % COLS
    const br = Math.floor(b / COLS)
    const bc = b % COLS
    return Math.abs(ar - br) + Math.abs(ac - bc) === 1
}

async function trySwap(a, b, soundPlayed = false) {
    if (busy.value || gameOver.value) return
    busy.value = true
    if (!soundPlayed) playSfx('swap', .85)
    swap(a, b)
    await wait(130)
    const groups = findMatches(board.value)
    if (!groups.length) {
        swap(a, b)
        playSfx('swapFail', .8)
        vibrate('light')
        statusText.value = '这一步没有形成三连，再试一次'
        busy.value = false
        if (!checkBoardHealth()) scheduleHint()
        return
    }
    const directIndices = new Set(groups.flatMap(group => group.indices))
    const highTile = [...directIndices].map(i => board.value[i]).find(tile => foodOf(tile.key).high)
    if (highTile) {
        playSfx('lose', .9)
        vibrate('heavy')
        endedFood.value = foodOf(highTile.key)
        saveBest()
        gameOver.value = true
        clearActiveGame()
        queuePlatformProgress(learned.value, score.value, true)
        busy.value = false
        return
    }
    await resolveMatches(groups, b)
    busy.value = false
    if (!gameOver.value) finishTurn()
}

function swap(a, b) {
    const next = [...board.value]
    ;[next[a], next[b]] = [next[b], next[a]]
    board.value = next
}

async function resolveMatches(initialGroups, movedIndex) {
    const comboTaskAtStart = currentTask.value.kind === 'combo'
    let groups = initialGroups
    let wave = 0
    let turnConsumed = 0
    let madeSpecial = 0
    let firstWave = true
    while (groups.length && wave < MAX_CASCADE_WAVES) {
        wave += 1
        combo.value = wave
        const remove = new Set(groups.flatMap(group => group.indices))
        let keeper = -1
        let keeperSpecial = ''
        const bentMatch = firstWave ? findBentFiveMatch(groups) : null
        if (firstWave) {
            const large = groups.find(group => group.indices.length >= 4)
            if (bentMatch) {
                keeper = bentMatch.intersection
                keeperSpecial = 'burst'
                remove.delete(keeper)
                madeSpecial = 1
            } else if (large) {
                keeper = large.indices.includes(movedIndex) ? movedIndex : large.indices[0]
                keeperSpecial = large.indices.length >= 5 ? 'all' : large.direction
                remove.delete(keeper)
                madeSpecial = 1
            }
        }
        fireClearingIndices.value = []
        const specialEffects = expandSpecials(remove)
        const camelGroup = groups
            .filter(group => group.indices.every(index => board.value[index]?.key === 'camel'))
            .sort((a, b) => b.indices.length - a.indices.length)[0]
        const bentIndices = bentMatch ? [...new Set([...bentMatch.row.indices, ...bentMatch.col.indices])] : []
        const bentCamelCount = bentIndices.length === 5
            && bentIndices.every(index => board.value[index]?.key === 'camel')
            ? 5
            : 0
        const camelMatchCount = Math.max(bentCamelCount, camelGroup?.indices.length || 0)
        if (camelMatchCount >= 3) applyCamelJackpot(camelMatchCount, remove)
        const removedTiles = [...remove].map(i => board.value[i]).filter(Boolean)
        // 排名统计的是正确认识并消除的普通食物；驼乳是道具，不计入认糖数。
        const removedNormal = removedTiles.filter(tile => {
            const food = foodOf(tile.key)
            return !food.high && !food.product
        })
        const fireRemovedHigh = fireClearingIndices.value.some(index => foodOf(board.value[index]?.key).high)
        const largestMatch = Math.max(
            ...groups.map(group => group.indices.length),
            keeperSpecial === 'burst' || specialEffects.hasBurst ? 5 : 3
        )
        clearingIndices.value = [...remove]
        const triggeredSpecial = fireRemovedHigh
            ? 'fire-high'
            : specialEffects.hasBurst
                ? 'burst-triggered'
                : specialEffects.hasFire
                    ? 'fire-triggered'
                    : keeperSpecial
        showImpact(largestMatch, wave, triggeredSpecial)
        playMatchImpact(largestMatch, wave)
        vibrate(largestMatch >= 5 || wave >= 3 ? 'medium' : 'light')
        turnConsumed += removedNormal.length
        learned.value += removedNormal.length
        updateFoodTask(removedNormal)
        const multiplier = feastMoves.value > 0 ? 2 : 1
        score.value += (removedNormal.length * 12 + Math.max(0, wave - 1) * 20 + madeSpecial * 35) * multiplier
        queuePlatformProgress(learned.value, score.value, false)
        await wait(specialEffects.hasFire ? 330 : 210)
        const next = [...board.value]
        remove.forEach(index => { next[index] = null })
        if (keeper >= 0 && next[keeper]) next[keeper] = { ...next[keeper], special: keeperSpecial }
        board.value = next
        clearingIndices.value = []
        fireClearingIndices.value = []
        if (madeSpecial && currentTask.value.kind === 'special') advanceTask(madeSpecial)
        await wait(70)
        const shouldGuaranteeCombo = firstWave
            && comboTaskAtStart
            && comboDryTurns.value >= COMBO_PITY_TURNS - 1
        collapseAndFill(shouldGuaranteeCombo)
        playDropSfx(remove.size)
        await wait(190)
        groups = findMatches(board.value)
        const automaticHighMatch = groups.some(group => (
            group.indices.some(index => foodOf(board.value[index]?.key).high)
        ))
        if (automaticHighMatch) {
            board.value = makeStablePlayableBoard(board.value)
            groups = []
            statusText.value = '自动掉落避开了高糖连消，棋盘已整理'
        } else if (wave >= MAX_CASCADE_WAVES && groups.length) {
            board.value = makeStablePlayableBoard(board.value)
            groups = []
            statusText.value = '连消奖励已结算，棋盘已稳定'
        }
        firstWave = false
        madeSpecial = 0
    }
    if (wave >= 3) rewardMegaCombo(wave)
    if (comboTaskAtStart) {
        if (wave >= COMBO_TASK_WAVE) {
            comboDryTurns.value = 0
            advanceTask(1)
        } else {
            comboDryTurns.value += 1
        }
    }
    if (turnConsumed > 0 && feastMoves.value > 0) feastMoves.value -= 1
    if (pendingThemeMatchCount >= 3) {
        const matchedCount = pendingThemeMatchCount
        pendingThemeMatchCount = 0
        const celebrationDelay = matchedCount >= 5 ? 2300 : matchedCount === 4 ? 2000 : 1250
        await wait(celebrationDelay)
        advanceFoodTheme()
    }
}

function playMatchImpact(matchSize, wave) {
    if (wave >= 3) playSfxLayer('comboMega', 'blast', 70, .55)
    else if (wave === 2) playSfxLayer('combo3', 'pop', 60, .5)
    else if (matchSize >= 5) playSfxLayer('match5', 'blast', 65, .55)
    else if (matchSize >= 4) playSfxLayer('match4', 'sparkle', 55, .5)
    else playSfxLayer('match3', 'pop', 45, .45)
}

function planCamelReward(requested) {
    const available = Math.max(0, MAX_CAMEL_COUNT - camelCount.value)
    const granted = Math.min(requested, available)
    const overflow = requested - granted
    return { granted, overflow, overflowScore: overflow * OVERFLOW_CAMEL_SCORE }
}

function grantCamelReward(requested) {
    const reward = planCamelReward(requested)
    camelCount.value += reward.granted
    score.value += reward.overflowScore
    return reward
}

function rewardMegaCombo(wave) {
    score.value += MEGA_COMBO_SCORE
    const camelReward = grantCamelReward(1)
    const rewardText = camelRewardText(camelReward)
    statusText.value = `${wave} 连消惊喜：+${MEGA_COMBO_SCORE} 分${rewardText ? ` · ${rewardText}` : ''}`
    taskRewardStatusUntil = Date.now() + 1800
    if (camelReward.granted > 0) showTaskCamelReward()
    startConfetti()
    playSfxLayer('comboMega', 'fanfare', 100, .82)
    vibrate('medium')
    uni.showToast({
        title: `${wave} 连消惊喜：+${MEGA_COMBO_SCORE} 分${camelReward.granted > 0 ? '、驼乳粉 +1' : ''}`,
        icon: 'none'
    })
}

function camelRewardText(reward) {
    const parts = []
    if (reward.granted > 0) parts.push(`驼乳粉 ×${reward.granted}`)
    if (reward.overflow > 0) parts.push(`满仓多余奖励换 ${reward.overflowScore} 分`)
    return parts.join(' · ')
}

function applyCamelJackpot(count, remove) {
    pendingThemeMatchCount = Math.max(pendingThemeMatchCount, count)
    if (count >= 5) {
        score.value += 1554
        const camelReward = grantCamelReward(4)
        feastMoves.value = Math.max(feastMoves.value, 6)
        board.value.forEach((tile, index) => { if (foodOf(tile?.key).high) remove.add(index) })
        showCamelJackpot(5, '驼乳五连双倍大奖', `+1554 分 · ${camelRewardText(camelReward)} · 双倍得分 ×6`)
        startConfetti()
        playJackpotSequence()
        return
    }
    if (count === 4) {
        score.value += 288
        const camelReward = grantCamelReward(1)
        const highIndex = board.value.findIndex((tile, index) => foodOf(tile?.key).high && !remove.has(index))
        if (highIndex >= 0) remove.add(highIndex)
        showCamelJackpot(4, '驼乳超级四连', `+288 分 · ${camelRewardText(camelReward)} · 移出一份高糖食物`)
        playSfxLayer('match5', 'fanfare', 90, .7)
        setTimeout(() => playSfx('tap', 1.16, .58), 260)
        vibrate('medium')
        return
    }
    score.value += 77
    showCamelJackpot(3, '驼乳幸运三连', '+77 分 · 轻量连消奖励')
    playSfxLayer('match3', 'sparkle', 44, .38)
    vibrate('light')
}

function advanceFoodTheme() {
    const previous = currentTheme.value
    themeIndex.value = (themeIndex.value + 1) % FOOD_GROUPS.length
    const nextTheme = currentTheme.value
    const mapping = {}
    previous.safe.forEach((key, index) => { mapping[key] = nextTheme.safe[index] })
    mapping[previous.danger] = nextTheme.danger
    const mappedBoard = board.value.map(tile => {
        if (!tile || !mapping[tile.key]) return tile
        return { ...tile, key: mapping[tile.key] }
    })
    board.value = mappedBoard
    if (findMatches(mappedBoard).length || !hasSafeMoveIn(mappedBoard)) {
        board.value = buildPlayableBoard(openingKeysForTheme(nextTheme))
    }
    taskIndex.value = 0
    taskProgress.value = 0
    comboDryTurns.value = 0
    selected.value = -1
    camelMode.value = false
    endedFood.value = foodOf(nextTheme.danger)
    stageMeta.value = {
        number: themeIndex.value + 1,
        title: nextTheme.title,
        foods: [...nextTheme.safe, nextTheme.danger].map(key => ({ key, ...foodOf(key) }))
    }
    stageVisible.value = true
    statusText.value = `已进入${nextTheme.title}，橙框${foodOf(nextTheme.danger).short}可替换`
    playSfxLayer('fanfare', 'sparkle', 100, .65)
    if (stageTimer) clearTimeout(stageTimer)
    stageTimer = setTimeout(() => { stageVisible.value = false }, 1800)
}

function showCamelJackpot(count, title, reward) {
    if (jackpotTimer) clearTimeout(jackpotTimer)
    camelJackpot.value = {
        visible: true,
        count,
        kicker: count >= 5 ? 'JACKPOT · 777' : count === 4 ? 'ONE MORE · 再来一罐' : 'LUCKY · 小惊喜',
        title,
        reward
    }
    const duration = count >= 5 ? 2200 : count === 4 ? 1900 : 1150
    jackpotTimer = setTimeout(() => { camelJackpot.value.visible = false }, duration)
}

function startConfetti() {
    if (confettiTimer) clearTimeout(confettiTimer)
    confettiVisible.value = false
    setTimeout(() => { confettiVisible.value = true }, 20)
    confettiTimer = setTimeout(() => { confettiVisible.value = false }, 6000)
}

function playJackpotSequence() {
    playSfx('tap', .9, .82)
    setTimeout(() => playSfx('tap', 1, 1.05), 150)
    setTimeout(() => playSfx('tap', 1.08, 1.35), 300)
    setTimeout(() => playSfxLayer('comboMega', 'fanfare', 100, .85), 430)
    setTimeout(() => playSfxLayer('blast', 'sparkle', 90, .75), 690)
    vibrate('medium')
    setTimeout(() => vibrate('medium'), 170)
    setTimeout(() => vibrate('heavy'), 430)
}

function showImpact(matchSize, wave, specialType = '') {
    impactLevel.value = Math.min(3, wave >= 3 ? 3 : matchSize >= 5 ? 3 : matchSize >= 4 ? 2 : 1)
    if (specialType === 'fire-high') impactText.value = '火焰移出高糖！'
    else if (wave >= 3) impactText.value = `${wave} 连消！`
    else if (specialType === 'fire-triggered') impactText.value = '火焰清除！'
    else if (specialType === 'burst-triggered') impactText.value = '范围爆破！'
    else if (specialType === 'burst') impactText.value = 'L形惊喜！'
    else if (matchSize >= 5) impactText.value = '五连惊喜！'
    else if (matchSize >= 4) impactText.value = '四连！'
    else impactText.value = '消除！'
    setTimeout(() => { impactText.value = '' }, 520)
}

function vibrate(type = 'light') {
    try { uni.vibrateShort({ type }) } catch (_) {}
}

function expandSpecials(remove) {
    const queue = [...remove]
    const seen = new Set()
    const fire = new Set()
    let hasBurst = false
    let hasFire = false
    while (queue.length) {
        const index = queue.shift()
        if (seen.has(index)) continue
        seen.add(index)
        const tile = board.value[index]
        if (!tile?.special) continue
        hasFire = true
        const extra = []
        if (tile.special === 'row') {
            const row = Math.floor(index / COLS)
            for (let col = 0; col < COLS; col += 1) extra.push(row * COLS + col)
        } else if (tile.special === 'col') {
            const col = index % COLS
            for (let row = 0; row < ROWS; row += 1) extra.push(row * COLS + col)
        } else if (tile.special === 'burst') {
            hasBurst = true
            const centerRow = Math.floor(index / COLS)
            const centerCol = index % COLS
            for (let row = Math.max(0, centerRow - 1); row <= Math.min(ROWS - 1, centerRow + 1); row += 1) {
                for (let col = Math.max(0, centerCol - 1); col <= Math.min(COLS - 1, centerCol + 1); col += 1) {
                    extra.push(row * COLS + col)
                }
            }
        } else {
            board.value.forEach((item, i) => { if (item?.key === tile.key) extra.push(i) })
        }
        extra.forEach(i => fire.add(i))
        extra.forEach(i => {
            if (!remove.has(i)) { remove.add(i); queue.push(i) }
        })
    }
    fireClearingIndices.value = [...fire]
    return { hasBurst, hasFire }
}

function collapseAndFill(forceSafeMatch = false) {
    const next = Array(ROWS * COLS).fill(null)
    for (let col = 0; col < COLS; col += 1) {
        const existing = []
        for (let row = ROWS - 1; row >= 0; row -= 1) {
            const tile = board.value[row * COLS + col]
            if (tile) existing.push(tile)
        }
        existing.forEach((tile, offset) => {
            next[(ROWS - 1 - offset) * COLS + col] = tile
        })
    }
    const forcedIndices = forceSafeMatch ? findForcedDropMatch(next) : []
    const forcedSet = new Set(forcedIndices)
    const forcedKey = forcedIndices.length ? pickForcedDropKey(next, forcedIndices) : ''
    for (let row = 0; row < ROWS; row += 1) {
        for (let col = 0; col < COLS; col += 1) {
            const index = row * COLS + col
            if (!next[index]) {
                const key = forcedSet.has(index) ? forcedKey : pickDropFoodKey(next, index, col)
                next[index] = makeTile(key)
            }
        }
    }
    board.value = next
    return forcedIndices.length === 3
}

function findForcedDropMatch(snapshot) {
    const empty = new Set(snapshot.map((tile, index) => tile ? -1 : index).filter(index => index >= 0))
    for (let row = 0; row < ROWS; row += 1) {
        for (let col = 0; col <= COLS - 3; col += 1) {
            const indices = [0, 1, 2].map(offset => row * COLS + col + offset)
            if (indices.every(index => empty.has(index))) return indices
        }
    }
    for (let col = 0; col < COLS; col += 1) {
        for (let row = 0; row <= ROWS - 3; row += 1) {
            const indices = [0, 1, 2].map(offset => (row + offset) * COLS + col)
            if (indices.every(index => empty.has(index))) return indices
        }
    }
    return []
}

function pickForcedDropKey(snapshot, indices) {
    return currentTheme.value.safe
        .map(key => ({
            key,
            nearby: snapshot.reduce((count, tile, index) => (
                tile?.key === key && indices.some(target => isAdjacent(target, index)) ? count + 1 : count
            ), 0)
        }))
        .sort((a, b) => a.nearby - b.nearby)[0]?.key || currentTheme.value.safe[0]
}

function pickDropFoodKey(snapshot, index, col) {
    for (let attempt = 0; attempt < 16; attempt += 1) {
        const key = randomFoodKey(snapshot, col)
        if (!wouldCreateMatchAt(snapshot, index, key)) return key
    }
    const fallback = [...currentTheme.value.safe, currentTheme.value.danger]
        .find(key => !wouldCreateMatchAt(snapshot, index, key))
    return fallback || currentTheme.value.safe[(index + col) % currentTheme.value.safe.length]
}

function wouldCreateMatchAt(source, index, key) {
    const row = Math.floor(index / COLS)
    const col = index % COLS
    const keyAt = target => target === index ? key : source[target]?.key
    let horizontal = 1
    for (let c = col - 1; c >= 0 && keyAt(row * COLS + c) === key; c -= 1) horizontal += 1
    for (let c = col + 1; c < COLS && keyAt(row * COLS + c) === key; c += 1) horizontal += 1
    if (horizontal >= 3) return true
    let vertical = 1
    for (let r = row - 1; r >= 0 && keyAt(r * COLS + col) === key; r -= 1) vertical += 1
    for (let r = row + 1; r < ROWS && keyAt(r * COLS + col) === key; r += 1) vertical += 1
    return vertical >= 3
}

function randomFoodKey(snapshot, col = -1) {
    const highs = snapshot.filter(tile => tile && foodOf(tile.key).high).length
    if (highs < HIGH_CAP && Math.random() < DANGER_DROP_CHANCE) return currentTheme.value.danger
    const keys = currentTheme.value.safe
    if (col >= 0 && Math.random() < COLUMN_GROUP_BIAS) return keys[col % keys.length]
    return keys[Math.floor(Math.random() * keys.length)]
}

function findBentFiveMatch(groups) {
    const rows = groups.filter(group => group.direction === 'row' && group.indices.length === 3)
    const cols = groups.filter(group => group.direction === 'col' && group.indices.length === 3)
    for (const row of rows) {
        for (const col of cols) {
            const intersection = row.indices.find(index => col.indices.includes(index))
            if (intersection !== undefined) return { intersection, row, col }
        }
    }
    return null
}

function findMatches(source) {
    const groups = []
    for (let row = 0; row < ROWS; row += 1) {
        let start = 0
        while (start < COLS) {
            let end = start + 1
            while (end < COLS && source[row * COLS + end]?.key === source[row * COLS + start]?.key) end += 1
            if (source[row * COLS + start] && end - start >= 3) {
                groups.push({ direction: 'row', indices: Array.from({ length: end - start }, (_, n) => row * COLS + start + n) })
            }
            start = end
        }
    }
    for (let col = 0; col < COLS; col += 1) {
        let start = 0
        while (start < ROWS) {
            let end = start + 1
            while (end < ROWS && source[end * COLS + col]?.key === source[start * COLS + col]?.key) end += 1
            if (source[start * COLS + col] && end - start >= 3) {
                groups.push({ direction: 'col', indices: Array.from({ length: end - start }, (_, n) => (start + n) * COLS + col) })
            }
            start = end
        }
    }
    return groups
}

function updateFoodTask(tiles) {
    if (currentTask.value.kind !== 'food') return
    advanceTask(tiles.filter(tile => tile.key === currentTask.value.key).length)
}

function advanceTask(amount) {
    if (!amount) return
    taskProgress.value += amount
    if (taskProgress.value < currentTask.value.target) return
    score.value += 120
    const camelReward = grantCamelReward(1)
    if (camelReward.granted > 0) {
        showTaskCamelReward()
        statusText.value = '小目标完成，驼乳粉已放入道具栏'
    } else {
        statusText.value = `驼乳粉已满，多余奖励换 ${camelReward.overflowScore} 分`
    }
    uni.showToast({
        title: camelReward.granted > 0 ? '小目标完成：驼乳粉 +1' : `驼乳粉已满：奖励换 ${camelReward.overflowScore} 分`,
        icon: 'none'
    })
    taskIndex.value = (taskIndex.value + 1) % 4
    taskProgress.value = 0
    comboDryTurns.value = 0
}

function showTaskCamelReward() {
    if (taskRewardTimer) clearTimeout(taskRewardTimer)
    taskRewardKey.value += 1
    taskRewardVisible.value = true
    taskRewardStatusUntil = Date.now() + 1600
    playSfxLayer('boost', 'sparkle', 75, .55)
    vibrate('light')
    taskRewardTimer = setTimeout(() => { taskRewardVisible.value = false }, 1150)
}

function finishTurn() {
    saveBest()
    saveActiveGame()
    if (checkBoardHealth()) return
    if (stageVisible.value) {
        scheduleHint()
        return
    }
    if (Date.now() < taskRewardStatusUntil) {
        scheduleHint()
        return
    }
    statusText.value = combo.value >= 2 ? `漂亮！完成 ${combo.value} 连消` : '食物已从上方补充，继续挑战'
    scheduleHint()
}

function checkBoardHealth() {
    if (hasSafeMove() || reshuffling.value) return false
    performReshuffle()
    return true
}

function hasSafeMove() {
    return hasSafeMoveIn(board.value)
}

function hasSafeMoveIn(source) {
    return Boolean(findSafeMoveIn(source))
}

function findSafeMoveIn(source) {
    for (let i = 0; i < source.length; i += 1) {
        const candidates = []
        if (i % COLS < COLS - 1) candidates.push(i + 1)
        if (i + COLS < source.length) candidates.push(i + COLS)
        for (const j of candidates) {
            const copy = source.map(tile => ({ ...tile }))
            ;[copy[i], copy[j]] = [copy[j], copy[i]]
            const groups = findMatches(copy)
            if (groups.length && !groups.flatMap(group => group.indices).some(index => foodOf(copy[index].key).high)) {
                return { from: i, to: j }
            }
        }
    }
    return null
}

async function performReshuffle() {
    if (reshuffling.value || gameOver.value) return
    reshuffling.value = true
    busy.value = true
    reshuffleDone.value = false
    reshuffleVisible.value = true
    selected.value = -1
    camelMode.value = false
    statusText.value = '没有可消除组合，正在重新洗牌'
    playSfx('reshuffle', .9)
    vibrate('medium')
    await wait(760)

    const source = board.value.map(tile => ({ ...tile }))
    let candidate = null
    for (let attempt = 0; attempt < 240; attempt += 1) {
        const shuffled = shuffleTiles(source)
        if (findMatches(shuffled).length) continue
        if (!hasSafeMoveIn(shuffled)) continue
        candidate = shuffled
        break
    }
    board.value = candidate || buildGuaranteedBoard()
    reshuffleDone.value = true
    statusText.value = '洗牌完成，新的消除机会已经出现'
    playSfxLayer('drop', 'sparkle', 70, .55)
    vibrate('light')
    await wait(520)
    reshuffleVisible.value = false
    reshuffling.value = false
    busy.value = false
    saveActiveGame()
    scheduleHint()
}

function shuffleTiles(source) {
    const result = source.map(tile => ({ ...tile }))
    for (let i = result.length - 1; i > 0; i -= 1) {
        const j = Math.floor(Math.random() * (i + 1))
        ;[result[i], result[j]] = [result[j], result[i]]
    }
    return result
}

function buildGuaranteedBoard() {
    return buildPlayableBoard(openingKeysForTheme(currentTheme.value))
}

function openingKeysForTheme(theme) {
    const [a, b, c, d] = theme.safe
    const danger = theme.danger
    return [
        a, b, c, d,
        b, c, d, a,
        c, d, a, danger,
        d, a, danger, b,
        a, b, c, d
    ]
}

function buildPlayableBoard(keys) {
    let candidate = keys.map(key => makeTile(key))
    for (let attempt = 0; attempt < 240; attempt += 1) {
        if (!findMatches(candidate).length && hasSafeMoveIn(candidate)) return candidate
        candidate = shuffleTiles(candidate)
    }
    return forcedPlayableKeysForTheme(currentTheme.value).map(key => makeTile(key))
}

function forcedPlayableKeysForTheme(theme) {
    const [a, b, c, d] = theme.safe
    const danger = theme.danger
    return [
        d, a, danger, c,
        d, c, b, danger,
        c, a, b, a,
        d, d, a, a,
        b, d, c, b
    ]
}

function makeStablePlayableBoard(source) {
    const tiles = source.filter(Boolean).map(tile => ({ ...tile }))
    if (tiles.length === ROWS * COLS) {
        for (let attempt = 0; attempt < 300; attempt += 1) {
            const candidate = shuffleTiles(tiles)
            if (!findMatches(candidate).length && hasSafeMoveIn(candidate)) return candidate
        }
    }
    return buildGuaranteedBoard()
}

function safeMoveHint(move) {
    if (!move) return ''
    const from = foodOf(board.value[move.from]?.key).short
    const to = foodOf(board.value[move.to]?.key).short
    const direction = move.to === move.from + 1 ? '向右' : '向下'
    return `提示：把${from}${direction}与${to}交换`
}

function scheduleHint() {
    if (hintTimer) clearTimeout(hintTimer)
    hintTimer = setTimeout(() => {
        if (busy.value || gameOver.value || stageVisible.value) return
        const move = findSafeMoveIn(board.value)
        if (!move) {
            performReshuffle()
            return
        }
        statusText.value = safeMoveHint(move)
    }, 5000)
}

function saveBest() {
    if (score.value <= bestScore.value) return
    bestScore.value = score.value
    uni.setStorageSync('tongji_endless_best', bestScore.value)
}

function wait(ms) {
    return new Promise(resolve => setTimeout(resolve, ms))
}
</script>

<style lang="scss" scoped>
@import './game-endless.scss';
</style>
