<template>
	<view class="adv">
		<view class="adv-bg"></view>
		<view class="nav" :style="{paddingTop: statusBarHeight + 'px'}">
			<view class="nav-back" @tap="goBack"><text>‹</text></view>
			<view class="nav-title">松鼠屯屯乐</view>
		</view>

		<view class="hud" :style="{top: (statusBarHeight + 48) + 'px'}">
			<view><text>{{score}}</text><text>分数</text></view>
			<view><text>{{timeLeft}}</text><text>秒</text></view>
			<view><text>{{Math.round(player.mass)}}</text><text>体型</text></view>
		</view>

		<canvas
			canvas-id="adventureCanvas"
			id="adventureCanvas"
			class="game"
			:disable-scroll="true"
			@touchstart="onTouch"
			@touchmove="onTouch">
		</canvas>

		<view v-if="!playing" class="panel">
			<view class="panel-card">
				<image class="hero-img" src="/static/images/img/loading.gif" mode="aspectFit"></image>
				<view class="panel-kicker">SQUIRREL TUN TUN</view>
				<view class="panel-title">松鼠屯屯乐</view>
				<view class="panel-sub">在糖果宇宙里屯松子、抢补给、躲大松鼠。撞掉的松子会散落成一片松子雨</view>
				<view class="event" v-if="home.event">{{home.event.name}}：{{home.event.description}}</view>
				<view class="my">我的最高分 {{home.myBestScore || 0}} · 今日第 {{home.myRankNo || '-'}} 名</view>
				<view class="start-btn" @tap="startGame">{{home.freeAvailable ? '免费开始' : '消耗1张星球券开始'}}</view>
				<view class="rule">21:30结算，前10名发放10-1张星球券</view>
			</view>
		</view>

		<view v-if="result.show" class="panel">
			<view class="panel-card">
				<view class="panel-kicker">FINISH</view>
				<view class="panel-title">本局 {{score}} 分</view>
				<view class="panel-sub">普通{{counts.normal}} · 金色{{counts.golden}} · 彩虹{{counts.rainbow}} · 补给{{counts.prop}}</view>
				<view class="my">当前排名：{{result.rankNo || '-'}}</view>
				<view class="start-btn" @tap="closeResult">知道了</view>
			</view>
		</view>

		<view class="rank">
			<view class="rank-title">今日前10</view>
			<scroll-view scroll-y class="rank-list">
				<view class="rank-row" v-for="(item,i) in ranks" :key="i">
					<text class="rank-no">{{i + 1}}</text>
					<image class="rank-avatar" :src="item.avatar || defaultAvatar" mode="aspectFill"></image>
					<view class="rank-mid">
						<text>{{item.nickname || '松鼠同学'}}</text>
						<text>{{item.college || '校园玩家'}}</text>
					</view>
					<text class="rank-score">{{item.score || 0}}</text>
				</view>
			</scroll-view>
		</view>
	</view>
</template>

<script>
	export default {
		data() {
			return {
				statusBarHeight: 20,
				userId: '',
				regionId: '',
				nickname: '',
				avatar: '',
				college: '',
				home: {},
				ranks: [],
				ctx: null,
				w: 375,
				h: 667,
				playing: false,
				session: null,
				startTs: 0,
				timeLeft: 60,
				score: 0,
				world: { w: 2200, h: 2200 },
				camera: { x: 0, y: 0 },
				pointer: null,
				player: { x: 1100, y: 1100, r: 34, mass: 34, speed: 4.2, name: '我' },
				items: [],
				ais: [],
				drops: [],
				props: [],
				effects: { magnet: 0, double: 0, shield: 0, rocket: 0 },
				counts: { normal: 0, golden: 0, diamond: 0, rainbow: 0, ticketBag: 0, prop: 0, hit: 0 },
				raf: null,
				lastSpawn: 0,
				result: { show: false, rankNo: 0 },
				squirrelImg: null,
				defaultAvatar: 'https://jewel-shop.oss-cn-beijing.aliyuncs.com/41cfb56caff4419b94b69d0f2303b602.png'
			}
		},
		onLoad() {
			const sys = uni.getSystemInfoSync()
			this.statusBarHeight = sys.statusBarHeight || 20
			this.w = sys.windowWidth
			this.h = sys.windowHeight
			this.userId = uni.getStorageSync('id') || ''
			this.nickname = uni.getStorageSync('nickName') || uni.getStorageSync('nickname') || ''
			this.avatar = uni.getStorageSync('avatarUrl') || uni.getStorageSync('avatar') || ''
			try {
				const area = uni.getStorageSync('area')
				if (area) this.regionId = JSON.parse(area).id || ''
			} catch (e) {}
			this.ctx = uni.createCanvasContext('adventureCanvas', this)
			this.squirrelImg = '/static/images/img/loading.gif'
			this.loadHome()
		},
		onUnload() {
			this.stopLoop()
		},
		methods: {
			loadHome() {
				this.tui.request('/app/planet/adventure/home', 'POST', {
					userId: this.userId,
					regionId: this.regionId
				}, false, false, true).then(res => {
					if (res.code == 200 && res.result) {
						this.home = res.result
						this.ranks = res.result.rankList || []
					} else if (res.message) {
						this.tui.toast(res.message)
					}
				})
			},
			startGame() {
				this.tui.request('/app/planet/adventure/start', 'POST', {
					userId: this.userId,
					regionId: this.regionId,
					nickname: this.nickname,
					avatar: this.avatar,
					college: this.college
				}).then(res => {
					if (res.code != 200 || !res.result) {
						this.tui.toast(res.message)
						return
					}
					this.session = res.result
					this.resetGame()
					this.playing = true
					this.loop()
				})
			},
			resetGame() {
				this.score = 0
				this.timeLeft = 60
				this.items = []
				this.ais = []
				this.drops = []
				this.props = []
				this.effects = { magnet: 0, double: 0, shield: 0, rocket: 0 }
				this.counts = { normal: 0, golden: 0, diamond: 0, rainbow: 0, ticketBag: 0, prop: 0, hit: 0 }
				this.player = { x: this.world.w / 2, y: this.world.h / 2, r: 38, mass: 38, speed: 4.2, name: this.nickname || '我' }
				this.pointer = null
				this.startTs = Date.now()
				this.lastSpawn = 0
				this.result.show = false
				for (let i = 0; i < 120; i++) this.spawnNut()
				for (let i = 0; i < 10; i++) this.spawnAI(i)
			},
			loop() {
				if (!this.playing) return
				const now = Date.now()
				const elapsed = Math.floor((now - this.startTs) / 1000)
				this.timeLeft = Math.max(0, 60 - elapsed)
				this.update(now)
				this.draw()
				if (this.timeLeft <= 0) {
					this.finishGame()
					return
				}
				this.raf = setTimeout(() => this.loop(), 16)
			},
			stopLoop() {
				if (this.raf) clearTimeout(this.raf)
				this.raf = null
			},
			update(now) {
				if (now - this.lastSpawn > 420) {
					this.spawnNut()
					if (this.items.length < 130) this.spawnNut()
					if (Math.random() < 0.18) this.spawnProp()
					this.lastSpawn = now
				}
				this.movePlayer()
				this.moveAI()
				this.updateCamera()
				this.moveDrops()
				this.checkCollisions(now)
				Object.keys(this.effects).forEach(k => {
					if (this.effects[k] > 0 && this.effects[k] < now) this.effects[k] = 0
				})
			},
			spawnNut(x, y, forceType) {
				x = x === undefined ? 40 + Math.random() * (this.world.w - 80) : x
				y = y === undefined ? 40 + Math.random() * (this.world.h - 80) : y
				const r = Math.random()
				let type = forceType || 'normal'
				if (!forceType) {
					if (r > 0.96) type = 'rainbow'
					else if (r > 0.82) type = 'golden'
				}
				const map = {
					normal: { score: 1, r: 13, color: '#B87932' },
					golden: { score: 5, r: 16, color: '#FFB84D' },
					rainbow: { score: 20, r: 20, color: '#D96DFF' }
				}
				this.items.push(Object.assign({ x, y, type }, map[type]))
			},
			spawnProp() {
				this.props.push({
					x: 80 + Math.random() * (this.world.w - 160),
					y: 80 + Math.random() * (this.world.h - 160),
					r: 22,
					type: ['magnet', 'double', 'shield', 'rocket', 'box', 'ticket'][Math.floor(Math.random() * 6)]
				})
			},
			spawnAI(i) {
				const names = ['奶茶松鼠', '晚八人', '食堂猎手', '图书馆王', '跑腿侠', '卷王', '摸鱼星人', '拼团仔', '快递站长', '校园锦鲤']
				this.ais.push({
					x: 120 + Math.random() * (this.world.w - 240),
					y: 120 + Math.random() * (this.world.h - 240),
					r: 30 + Math.random() * 28,
					mass: 30 + Math.random() * 28,
					speed: 2.2 + Math.random() * 1.6,
					name: names[i] || 'AI松鼠',
					tx: 0,
					ty: 0,
					color: ['#FFB84D', '#7DE2FF', '#A8F7C1', '#F7A8D8', '#C8B6FF'][i % 5]
				})
			},
			movePlayer() {
				if (!this.pointer) return
				const wx = this.camera.x + this.pointer.x
				const wy = this.camera.y + this.pointer.y
				const dx = wx - this.player.x
				const dy = wy - this.player.y
				const dist = Math.sqrt(dx * dx + dy * dy)
				if (dist > 4) {
					const speed = (this.effects.rocket ? 7.2 : this.player.speed) * Math.max(0.55, 42 / this.player.r)
					this.player.x += dx / dist * speed
					this.player.y += dy / dist * speed
				}
				this.keepInWorld(this.player)
			},
			moveAI() {
				this.ais.forEach(ai => {
					let target = this.nearestFood(ai)
					if (!target || Math.random() < 0.006) {
						ai.tx = Math.random() * this.world.w
						ai.ty = Math.random() * this.world.h
						target = { x: ai.tx, y: ai.ty }
					}
					const dx = target.x - ai.x
					const dy = target.y - ai.y
					const dist = Math.sqrt(dx * dx + dy * dy) || 1
					ai.x += dx / dist * ai.speed * Math.max(0.55, 42 / ai.r)
					ai.y += dy / dist * ai.speed * Math.max(0.55, 42 / ai.r)
					this.keepInWorld(ai)
				})
			},
			nearestFood(role) {
				let best = null
				let bestD = 999999
				this.items.forEach(it => {
					const d = Math.abs(it.x - role.x) + Math.abs(it.y - role.y)
					if (d < bestD) {
						bestD = d
						best = it
					}
				})
				return best
			},
			updateCamera() {
				this.camera.x = Math.max(0, Math.min(this.world.w - this.w, this.player.x - this.w / 2))
				this.camera.y = Math.max(0, Math.min(this.world.h - this.h, this.player.y - this.h / 2))
			},
			moveDrops() {
				for (let i = this.drops.length - 1; i >= 0; i--) {
					const d = this.drops[i]
					d.x += d.vx
					d.y += d.vy
					d.vx *= 0.94
					d.vy *= 0.94
					d.life--
					if (d.life <= 0) {
						this.spawnNut(d.x, d.y, 'normal')
						this.drops.splice(i, 1)
					}
				}
			},
			keepInWorld(o) {
				o.x = Math.max(o.r, Math.min(this.world.w - o.r, o.x))
				o.y = Math.max(o.r, Math.min(this.world.h - o.r, o.y))
			},
			checkCollisions(now) {
				this.hitWorldItems(this.player, this.items, (it) => {
					const multi = this.effects.double ? 2 : 1
					this.score += it.score * multi
					this.counts[it.type]++
					this.grow(this.player, it.score * 0.55)
				})
				this.hitWorldItems(this.player, this.props, (it) => {
					this.counts.prop++
					const until = now + (it.type === 'rocket' ? 5000 : 15000)
					if (it.type === 'box') {
						this.effects[['magnet', 'double', 'shield', 'rocket'][Math.floor(Math.random() * 4)]] = until
					} else if (it.type === 'ticket') {
						this.counts.ticketBag++
						this.score += 8
						this.grow(this.player, 3)
					} else {
						this.effects[it.type] = until
					}
				})
				this.ais.forEach(ai => {
					this.hitWorldItems(ai, this.items, (it) => this.grow(ai, it.score * 0.5))
					this.resolveRoleHit(ai)
				})
				this.checkDropPickup()
			},
			hitWorldItems(role, list, cb) {
				for (let i = list.length - 1; i >= 0; i--) {
					const it = list[i]
					if (this.effects.magnet && role === this.player && list === this.items) {
						const mdx = role.x - it.x
						const mdy = role.y - it.y
						const md = Math.sqrt(mdx * mdx + mdy * mdy)
						if (md < 190) {
							it.x += mdx * 0.1
							it.y += mdy * 0.1
						}
					}
					const dx = it.x - role.x
					const dy = it.y - role.y
					if (Math.sqrt(dx * dx + dy * dy) < it.r + role.r * 0.72) {
						list.splice(i, 1)
						cb(it)
					}
				}
			},
			resolveRoleHit(ai) {
				const dx = ai.x - this.player.x
				const dy = ai.y - this.player.y
				const dist = Math.sqrt(dx * dx + dy * dy)
				if (dist > ai.r + this.player.r - 8) return
				if (this.effects.shield || this.effects.rocket) {
					this.effects.shield = 0
					this.dropNutRain(ai, 8)
					this.shrink(ai, 8)
					return
				}
				if (ai.r > this.player.r * 1.08) {
					const loss = Math.min(18, Math.max(6, Math.floor(this.player.mass * 0.22)))
					this.dropNutRain(this.player, loss)
					this.shrink(this.player, loss)
					this.counts.hit++
				} else if (this.player.r > ai.r * 1.12) {
					this.dropNutRain(ai, 10)
					this.shrink(ai, 10)
					this.score += 10
					this.grow(this.player, 5)
				}
			},
			checkDropPickup() {
				for (let i = this.drops.length - 1; i >= 0; i--) {
					const d = this.drops[i]
					const dx = d.x - this.player.x
					const dy = d.y - this.player.y
					if (Math.sqrt(dx * dx + dy * dy) < this.player.r + 10) {
						this.drops.splice(i, 1)
						this.score += 1
						this.counts.normal++
						this.grow(this.player, 0.5)
					}
				}
			},
			grow(role, v) {
				role.mass += v
				role.r = Math.min(96, 24 + Math.sqrt(role.mass) * 3.2)
			},
			shrink(role, v) {
				role.mass = Math.max(20, role.mass - v)
				role.r = Math.max(28, 24 + Math.sqrt(role.mass) * 3.2)
			},
			dropNutRain(role, count) {
				for (let i = 0; i < count; i++) {
					const a = Math.random() * Math.PI * 2
					const s = 2 + Math.random() * 5
					this.drops.push({
						x: role.x + Math.cos(a) * role.r,
						y: role.y + Math.sin(a) * role.r,
						vx: Math.cos(a) * s,
						vy: Math.sin(a) * s,
						r: 8,
						life: 18 + Math.floor(Math.random() * 18)
					})
				}
			},
			draw() {
				const c = this.ctx
				c.clearRect(0, 0, this.w, this.h)
				this.drawMap(c)
				this.items.forEach(it => this.drawNut(c, it))
				this.drops.forEach(it => this.drawNut(c, it, true))
				this.props.forEach(it => this.drawProp(c, it))
				this.ais.forEach(ai => this.drawSquirrel(c, ai, false))
				this.drawSquirrel(c, this.player, true)
				c.draw()
			},
			drawMap(c) {
				c.setFillStyle('#EAF8FF')
				c.fillRect(0, 0, this.w, this.h)
				const ox = -this.camera.x
				const oy = -this.camera.y
				for (let i = 0; i < 80; i++) {
					const x = (i * 173) % this.world.w + ox
					const y = (i * 119) % this.world.h + oy
					if (x > -80 && x < this.w + 80 && y > -80 && y < this.h + 80) {
						this.cloud(c, x, y, i)
					}
				}
				c.setStrokeStyle('rgba(255,255,255,0.55)')
				c.setLineWidth(1)
				for (let gx = -this.camera.x % 120; gx < this.w; gx += 120) {
					c.beginPath(); c.moveTo(gx, 0); c.lineTo(gx, this.h); c.stroke()
				}
				for (let gy = -this.camera.y % 120; gy < this.h; gy += 120) {
					c.beginPath(); c.moveTo(0, gy); c.lineTo(this.w, gy); c.stroke()
				}
			},
			screen(o) {
				return { x: o.x - this.camera.x, y: o.y - this.camera.y }
			},
			cloud(c, x, y, i) {
				const colors = ['rgba(255,184,77,0.16)', 'rgba(53,214,166,0.14)', 'rgba(143,124,255,0.13)']
				c.setFillStyle(colors[i % colors.length])
				c.beginPath()
				c.arc(x, y, 26 + (i % 4) * 8, 0, Math.PI * 2)
				c.fill()
			},
			drawNut(c, it, small) {
				const p = this.screen(it)
				const r = small ? 8 : it.r
				c.save()
				c.translate(p.x, p.y)
				c.rotate(0.5)
				c.beginPath()
				c.setFillStyle(it.color || '#B87932')
				c.ellipse(0, 0, r * 0.78, r, 0, 0, Math.PI * 2)
				c.fill()
				c.setStrokeStyle('rgba(255,255,255,0.68)')
				c.setLineWidth(2)
				for (let y = -r * 0.45; y <= r * 0.45; y += r * 0.32) {
					c.beginPath()
					c.moveTo(-r * 0.45, y)
					c.lineTo(r * 0.45, y + r * 0.16)
					c.stroke()
				}
				c.restore()
			},
			drawProp(c, it) {
				const p = this.screen(it)
				const label = { magnet: '磁', double: '2X', shield: '盾', rocket: '冲', box: '箱', ticket: '券' }[it.type] || '礼'
				c.beginPath()
				c.setFillStyle(it.type === 'ticket' ? '#FFB84D' : '#35D6A6')
				c.arc(p.x, p.y, it.r, 0, Math.PI * 2)
				c.fill()
				c.setFillStyle('#FFFFFF')
				c.setFontSize(12)
				c.fillText(label, p.x - (label.length > 1 ? 8 : 6), p.y + 4)
			},
			drawSquirrel(c, role, self) {
				const p = this.screen(role)
				if (p.x < -140 || p.x > this.w + 140 || p.y < -140 || p.y > this.h + 140) return
				const r = role.r
				if (self && this.effects.shield) {
					c.beginPath()
					c.setFillStyle('rgba(125,226,255,0.25)')
					c.arc(p.x, p.y, r + 16, 0, Math.PI * 2)
					c.fill()
				}
				c.beginPath()
				c.setFillStyle(self ? '#FFFFFF' : (role.color || '#FFE5AE'))
				c.arc(p.x, p.y, r + 8, 0, Math.PI * 2)
				c.fill()
				c.drawImage(this.squirrelImg, p.x - r, p.y - r, r * 2, r * 2)
				if (role.r > 62) {
					c.setFillStyle('#FFB84D')
					c.beginPath()
					c.moveTo(p.x - 18, p.y - r - 10)
					c.lineTo(p.x - 6, p.y - r - 30)
					c.lineTo(p.x + 4, p.y - r - 10)
					c.lineTo(p.x + 18, p.y - r - 30)
					c.lineTo(p.x + 24, p.y - r - 8)
					c.fill()
				}
				c.setFillStyle('#12342F')
				c.setFontSize(self ? 13 : 11)
				const name = role.name || '松鼠'
				c.fillText(name.slice(0, 5), p.x - Math.min(30, name.length * 6), p.y + r + 20)
			},
			onTouch(e) {
				if (!this.playing || !e.touches || !e.touches.length) return
				this.pointer = { x: e.touches[0].x, y: e.touches[0].y }
			},
			finishGame() {
				this.playing = false
				this.stopLoop()
				const duration = Math.max(1, Math.floor((Date.now() - this.startTs) / 1000))
				this.tui.request('/app/planet/adventure/submit', 'POST', {
					userId: this.userId,
					regionId: this.regionId,
					sessionId: this.session.sessionId,
					normalCount: this.counts.normal,
					goldenCount: this.counts.golden,
					diamondCount: this.counts.diamond,
					rainbowCount: this.counts.rainbow,
					ticketBagCount: this.counts.ticketBag,
					propUseCount: this.counts.prop,
					hitCount: this.counts.hit,
					score: this.score,
					durationSeconds: duration
				}).then(res => {
					if (res.code == 200 && res.result) {
						this.result.rankNo = res.result.rankNo
					} else {
						this.tui.toast(res.message)
					}
					this.result.show = true
					this.loadHome()
				})
			},
			closeResult() {
				this.result.show = false
			},
			goBack() {
				uni.navigateBack()
			}
		}
	}
</script>

<style lang="scss" scoped>
	.adv { min-height: 100vh; background: linear-gradient(155deg, #EAF8FF, #F3FFF4 55%, #FFF5D9); position: relative; overflow: hidden; }
	.nav { position: fixed; top: 0; left: 0; right: 0; height: 44px; z-index: 20; display: flex; align-items: flex-end; justify-content: center; padding-bottom: 8rpx; box-sizing: content-box; }
	.nav-back { position: absolute; left: 20rpx; bottom: 0; width: 70rpx; height: 44px; display: flex; align-items: center; justify-content: center; color: #12342F; font-size: 54rpx; }
	.nav-title { color: #12342F; font-size: 34rpx; font-weight: 900; }
	.game { width: 100vw; height: 100vh; }
	.hud { position: fixed; left: 22rpx; right: 22rpx; z-index: 10; display: flex; gap: 14rpx; }
	.hud view { flex: 1; height: 78rpx; border-radius: 26rpx; background: rgba(255,255,255,0.76); display: flex; flex-direction: column; align-items: center; justify-content: center; box-shadow: 0 12rpx 30rpx rgba(79,183,255,0.12); }
	.hud text:first-child { color: #22B889; font-size: 30rpx; font-weight: 900; }
	.hud text:last-child { color: #6B817D; font-size: 20rpx; }
	.panel { position: fixed; inset: 0; z-index: 30; background: rgba(18,52,47,0.18); backdrop-filter: blur(8px); display: flex; align-items: center; justify-content: center; padding: 42rpx; }
	.panel-card { width: 100%; padding: 42rpx; border-radius: 44rpx; background: linear-gradient(155deg, rgba(255,255,255,0.96), rgba(241,255,249,0.82)); box-shadow: 0 28rpx 70rpx rgba(53,214,166,0.2); text-align: center; }
	.panel-kicker { color: #FF9C42; font-size: 22rpx; font-weight: 900; letter-spacing: 2rpx; }
	.panel-title { margin-top: 12rpx; color: #12342F; font-size: 40rpx; font-weight: 900; }
	.panel-sub, .rule { margin-top: 14rpx; color: #6B817D; font-size: 25rpx; line-height: 1.5; }
	.event, .my { margin-top: 18rpx; padding: 18rpx; border-radius: 24rpx; background: rgba(255,255,255,0.72); color: #42635E; font-size: 24rpx; }
	.start-btn { margin-top: 30rpx; height: 86rpx; line-height: 86rpx; border-radius: 999rpx; background: linear-gradient(135deg, #35D6A6, #4FB7FF); color: #fff; font-size: 30rpx; font-weight: 900; box-shadow: 0 18rpx 40rpx rgba(53,214,166,0.22); }
	.rank { position: fixed; right: 18rpx; top: 210rpx; width: 230rpx; max-height: 420rpx; z-index: 12; padding: 18rpx; border-radius: 28rpx; background: rgba(255,255,255,0.62); backdrop-filter: blur(6px); }
	.rank-title { color: #12342F; font-size: 24rpx; font-weight: 900; margin-bottom: 10rpx; }
	.rank-list { max-height: 350rpx; }
	.rank-row { display: flex; align-items: center; margin-top: 10rpx; }
	.rank-no { width: 28rpx; color: #22B889; font-size: 20rpx; font-weight: 900; }
	.rank-avatar { width: 34rpx; height: 34rpx; border-radius: 50%; margin-right: 8rpx; }
	.rank-mid { flex: 1; display: flex; flex-direction: column; min-width: 0; }
	.rank-mid text { font-size: 18rpx; color: #42635E; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
	.rank-score { color: #12342F; font-size: 20rpx; font-weight: 900; }
</style>
