<template>
	<view class="room-page">
		<view class="glow glow-a"></view>
		<view class="glow glow-b"></view>
		<view class="nav" :style="{ paddingTop: menuButtonInfo.top + 'px' }">
			<view class="back" @tap="back">‹</view>
			<view class="nav-avatar-wrap" @tap="openTargetProfile">
				<image class="nav-avatar-img" v-if="companion.avatarUrl" :src="companion.avatarUrl" mode="aspectFill"></image>
				<view class="nav-avatar" v-else>{{ companion.avatar }}</view>
			</view>
			<view class="room-user" @tap="openTargetProfile">
				<view class="room-name">{{ companion.name }}</view>
				<view class="room-meta">{{ roomModeText }} · 可以随时回来继续聊</view>
			</view>
			<view class="more" @tap="showSafety = true">···</view>
		</view>

		<view class="radio-room">
			<view class="radio-orbit"></view>
			<view class="radio-core">FM</view>
			<view class="radio-copy">
				<view class="radio-title">电台陪伴中</view>
				<view class="radio-sub">你们已经安静陪伴了 {{ silentMinutes }} 分钟</view>
			</view>
		</view>

		<view class="timer-card">
			<view>
				<view class="timer-label">长期陪伴频道</view>
				<view class="timer-title">{{ companion.prompt }}</view>
			</view>
			<view class="timer-pill">已连接</view>
		</view>

		<view class="silent-modes">
			<view class="silent-chip" v-for="item in silentModes" :key="item" @tap="sendPresence(item)">
				{{ item }}
			</view>
		</view>

		<scroll-view class="messages" scroll-y :style="messagesStyle" :scroll-into-view="scrollIntoView" :scroll-with-animation="scrollAnimation"
			upper-threshold="80" @scrolltoupper="loadOlderMessages">
			<view class="load-more" v-if="loadingMessages">正在加载更早消息...</view>
			<view class="load-more" v-else-if="!hasMoreMessages && messages.length">没有更早的消息了</view>
			<view class="system-line">你们被放进同一个频道。不说话也算陪伴。</view>
			<view class="msg-row" v-for="item in messages" :key="item.domId"
				:id="item.domId"
				:class="item.mine ? 'mine' : 'other'">
				<image class="avatar-img" v-if="!item.mine && companion.avatarUrl" :src="companion.avatarUrl" mode="aspectFill" @tap="openTargetProfile"></image>
				<view class="avatar" :class="roomMode" v-else-if="!item.mine" @tap="openTargetProfile">{{ companion.avatar }}</view>
				<view class="bubble image-bubble" v-if="item.messageType === 2" :class="{ muted: item.pending, blocked: item.blocked }">
					<image class="chat-image" :src="item.content" mode="aspectFill" @tap="previewImage(item.content)"></image>
				</view>
				<view class="bubble emoji-bubble" v-else-if="item.messageType === 3" :class="{ muted: item.pending, blocked: item.blocked }">{{ item.content }}</view>
				<view class="bubble voice-bubble" v-else-if="item.messageType === 4" :class="{ muted: item.pending, blocked: item.blocked, playing: playingVoiceUrl === item.content }"
					@tap="playVoice(item)">
					<text class="voice-wave">{{ playingVoiceUrl === item.content ? '▮▮▮' : '▮▯▮' }}</text>
					<text>{{ item.mediaDuration || 1 }}''</text>
				</view>
				<view class="bubble" v-else :class="{ muted: item.pending, blocked: item.blocked }">{{ item.content }}</view>
			</view>
			<view class="hesitate-line right" v-if="typingHint">{{ typingHint }}</view>
		</scroll-view>

		<view class="presence-row" :style="{ bottom: presenceBottom }">
			<view class="presence-chip" v-for="item in presenceActions" :key="item" @tap="sendPresence(item)">
				{{ item }}
			</view>
		</view>

		<view class="emoji-panel" v-if="showEmoji" :style="{ bottom: emojiPanelBottom }">
			<view class="emoji-item" v-for="item in emojis" :key="item" @tap="sendEmoji(item)">{{ item }}</view>
		</view>

		<view class="input-bar" :style="{ bottom: inputBarBottom }">
			<view class="tool-btn" @tap="toggleEmoji">☺</view>
			<view class="tool-btn" @tap="voiceMode = !voiceMode">{{ voiceMode ? '键' : '语' }}</view>
			<input class="input" v-if="!voiceMode" v-model="draft" placeholder="轻轻说一句，或保持安静"
				:adjust-position="false" :cursor-spacing="20" @focus="handleInputFocus" @blur="handleInputBlur" />
			<view class="voice-hold" v-else :class="{ recording: recording, cancelling: voiceCancelling }"
				@touchstart.prevent="startRecord" @touchmove.prevent="moveRecord" @touchend="stopRecord" @touchcancel="cancelRecord">
				{{ voiceHoldText }}
			</view>
			<view class="tool-btn" @tap="chooseImage">＋</view>
			<view class="send" v-if="!voiceMode" @tap="sendMessage">发送</view>
		</view>

		<view class="safety-mask" v-if="showSafety" @tap="showSafety = false">
			<view class="safety-panel" @tap.stop>
				<view class="safety-title">安全与退出</view>
				<view class="safety-item" @tap="report">举报不适内容</view>
				<view class="safety-item" @tap="block">拉黑这个对象</view>
				<view class="safety-item quiet" @tap="finishRoom">提前结束陪伴</view>
			</view>
		</view>

		<view class="profile-mask" v-if="showTargetProfile" @tap="showTargetProfile = false">
			<view class="profile-panel" @tap.stop>
				<image class="profile-avatar-img" v-if="targetProfile.avatarUrl" :src="targetProfile.avatarUrl" mode="aspectFill"></image>
				<view class="profile-avatar" v-else>{{ targetProfile.avatarText || companion.avatar }}</view>
				<view class="profile-name">{{ targetProfile.anonymousName || companion.name }}</view>
				<view class="profile-mode">{{ targetProfile.currentMode === 'e' ? 'e 人 · 轻轻热闹' : 'i 人 · 安静陪伴' }}</view>
				<view class="profile-info-grid">
					<view class="profile-info">
						<text class="profile-info-label">性别</text>
						<text>{{ genderText(targetProfile.gender) }}</text>
					</view>
					<view class="profile-info">
						<text class="profile-info-label">想遇见</text>
						<text>{{ targetText(targetProfile.targetModePreference, targetProfile.targetGenderPreference) }}</text>
					</view>
				</view>
				<view class="profile-intro">{{ targetProfile.intro || companion.prompt || '这个人还没有写介绍。' }}</view>
				<view class="profile-tags" v-if="targetProfile.interestTags && targetProfile.interestTags.length">
					<text v-for="tag in targetProfile.interestTags" :key="tag">{{ tag }}</text>
				</view>
				<view class="profile-close" @tap="showTargetProfile = false">继续聊天</view>
			</view>
		</view>
	</view>
</template>

<script>
	import ieSocket from '@/common/ieSocket.js'
	import { finishIeRoom, reportIeRoom, blockIeUser, sendIePresence, pageIeMessages, sendIeMessage, getIeUserProfile } from '@/common/ieApi.js'
	import tui from '@/common/httpRequest.js'

	export default {
		data() {
			return {
				menuButtonInfo: {
					top: 24
				},
				roomMode: 'i',
				roomId: '',
				targetUserId: '',
				secondsLeft: 15 * 60,
				timer: null,
				pollTimer: null,
				draft: '',
				showSafety: false,
				showTargetProfile: false,
				targetProfile: {},
				showEmoji: false,
				voiceMode: false,
				typingHint: '',
				finishing: false,
				uploadingImage: false,
				uploadingVoice: false,
				recording: false,
				voiceCancelling: false,
				recordStartY: 0,
				recordStartTime: 0,
				recorderManager: null,
				innerAudioContext: null,
				playingVoiceUrl: '',
				voiceSwitching: false,
				keyboardHeight: 0,
				keyboardListener: null,
				messages: [],
				messagePage: 1,
				messagePageSize: 20,
				hasMoreMessages: true,
				loadingMessages: false,
				scrollIntoView: '',
				scrollAnimation: true,
				sendingMessage: false,
				emojis: ['🙂', '😄', '🥲', '😭', '😴', '🙌', '🌙', '☁️', '🍃', '✨', '💛', '🫶'],
				presenceActions: ['我在', '听着呢', '慢慢说', '抱一下空气'],
				silentModes: ['一起听歌', '一起倒计时', '一起自习', '一起失眠'],
				companions: {
					i: {
						name: '月台旁的影子',
						avatar: '月',
						prompt: '如果今晚可以把一个念头放在操场边，你想放什么？',
						firstMessage: '我刚从图书馆出来，路上风有点凉。'
					},
					e: {
						name: '便利店灯光',
						avatar: '光',
						prompt: '今晚聊一个不重要的小事，越轻越好。',
						firstMessage: '我刚买了夜宵，突然很想知道大家今晚都在干嘛。'
					}
				}
			}
		},
		computed: {
			companion() {
				return this.companions[this.roomMode] || this.companions.i
			},
			roomModeText() {
				return this.roomMode === 'i' ? '安静陪伴' : '轻轻热闹'
			},
			silentMinutes() {
				return Math.max(1, 15 - Math.ceil(this.secondsLeft / 60))
			},
			countdownText() {
				const minutes = String(Math.floor(this.secondsLeft / 60)).padStart(2, '0')
				const seconds = String(this.secondsLeft % 60).padStart(2, '0')
				return `${minutes}:${seconds}`
			},
			voiceHoldText() {
				if (!this.recording) return '按住说话'
				return this.voiceCancelling ? '松开取消' : '松开发送，上滑取消'
			},
			keyboardBottomPx() {
				return this.keyboardHeight > 0 ? (this.keyboardHeight + 8) + 'px' : '30rpx'
			},
			inputBarBottom() {
				return this.keyboardBottomPx
			},
			presenceBottom() {
				return this.keyboardHeight > 0 ? (this.keyboardHeight + 76) + 'px' : '132rpx'
			},
			emojiPanelBottom() {
				return this.keyboardHeight > 0 ? (this.keyboardHeight + 82) + 'px' : '128rpx'
			},
			messagesStyle() {
				return {
					paddingBottom: this.keyboardHeight > 0 ? (this.keyboardHeight + 140) + 'px' : '24rpx'
				}
			}
		},
		onLoad(options) {
			if (uni.getMenuButtonBoundingClientRect) {
				this.menuButtonInfo = uni.getMenuButtonBoundingClientRect()
			}
			if (options && options.mode) {
				this.roomMode = options.mode
			}
			this.roomId = options.roomId || ''
			this.targetUserId = options.targetUserId || ''
			if (options.name || options.avatar || options.avatarUrl || options.quote) {
				this.companions[this.roomMode] = {
					...this.companion,
					name: decodeURIComponent(options.name || this.companion.name),
					avatar: decodeURIComponent(options.avatar || this.companion.avatar),
					avatarUrl: decodeURIComponent(options.avatarUrl || ''),
					prompt: decodeURIComponent(options.quote || this.companion.prompt),
					firstMessage: ''
				}
			}
			this.initRecorder()
			this.initAudio()
			this.loadTargetProfile()
			this.loadLatestMessages()
			this.initSocket()
			this.startMessagePolling()
			this.bindKeyboardListener()
		},
		onUnload() {
			this.clearTimer()
			this.stopMessagePolling()
			this.unbindKeyboardListener()
			if (this.innerAudioContext) {
				this.innerAudioContext.destroy()
				this.innerAudioContext = null
			}
		},
		methods: {
			bindKeyboardListener() {
				if (!uni.onKeyboardHeightChange) return
				this.keyboardListener = (res = {}) => {
					this.keyboardHeight = res.height || 0
					this.$nextTick(() => {
						this.scrollToBottom(false)
					})
				}
				uni.onKeyboardHeightChange(this.keyboardListener)
			},
			unbindKeyboardListener() {
				if (!this.keyboardListener) return
				if (uni.offKeyboardHeightChange) {
					uni.offKeyboardHeightChange(this.keyboardListener)
				}
				this.keyboardListener = null
			},
			handleInputFocus(e) {
				const height = e && e.detail ? e.detail.height : 0
				if (height) {
					this.keyboardHeight = height
				}
				this.$nextTick(() => {
					this.scrollToBottom(false)
				})
			},
			handleInputBlur() {
				setTimeout(() => {
					this.keyboardHeight = 0
				}, 120)
			},
			startTimer() {
				// 已匹配的陪伴关系可持续聊天，不再自动倒计时结束。
			},
			clearTimer() {
				if (this.timer) {
					clearInterval(this.timer)
					this.timer = null
				}
			},
			normalizeMessage(item) {
				const currentUserId = String(uni.getStorageSync('id') || '')
				const messageId = item.id || item.messageId
				const clientMsgId = item.clientMsgId || ''
				const mine = item.mine !== undefined && item.mine !== null ? item.mine : (currentUserId && String(item.senderId) === currentUserId)
				return {
					domId: 'msg-' + (messageId || clientMsgId || Date.now() + '-' + Math.random().toString(16).slice(2)),
					messageId,
					clientMsgId,
					content: item.filteredContent || item.content || item.rawContent || '',
					messageType: item.messageType || 1,
					mediaDuration: item.mediaDuration,
					mediaSize: item.mediaSize,
					mediaFormat: item.mediaFormat,
					mine,
					pending: false,
					blocked: item.isBlocked === 1
				}
			},
			startMessagePolling() {
				this.stopMessagePolling()
				if (!this.roomId) return
				this.pollTimer = setInterval(() => {
					this.pullNewMessages()
				}, 2200)
			},
			stopMessagePolling() {
				if (this.pollTimer) {
					clearInterval(this.pollTimer)
					this.pollTimer = null
				}
			},
			async pullNewMessages() {
				if (!this.roomId || this.loadingMessages) return
				const page = await pageIeMessages(this.roomId, 1, this.messagePageSize)
				const latest = this.normalizePage(page)
				if (!latest.length) return
				const exists = new Set(this.messages.map(item => item.messageId || item.clientMsgId))
				const appended = latest.filter(item => !exists.has(item.messageId || item.clientMsgId))
				if (!appended.length) return
				this.messages = this.messages.concat(appended)
				this.scrollToBottom()
			},
			normalizePage(page) {
				if (!page) return []
				const records = Array.isArray(page) ? page : (page.records || [])
				return records.map(this.normalizeMessage).reverse()
			},
			async loadLatestMessages() {
				if (!this.roomId) return
				this.messagePage = 1
				this.loadingMessages = true
				try {
					const page = await pageIeMessages(this.roomId, this.messagePage, this.messagePageSize)
					const latest = this.normalizePage(page)
					this.messages = latest
					this.hasMoreMessages = page ? (page.current || 1) < (page.pages || 1) : false
					this.scrollToBottom(false)
				} finally {
					this.loadingMessages = false
				}
			},
			async loadOlderMessages() {
				if (!this.roomId || this.loadingMessages || !this.hasMoreMessages) return
				const anchor = this.messages.length ? this.messages[0].domId : ''
				this.loadingMessages = true
				const nextPage = this.messagePage + 1
				try {
					const page = await pageIeMessages(this.roomId, nextPage, this.messagePageSize)
					const older = this.normalizePage(page)
					const exists = new Set(this.messages.map(item => item.messageId || item.clientMsgId))
					const deduped = older.filter(item => !exists.has(item.messageId || item.clientMsgId))
					this.messages = deduped.concat(this.messages)
					this.messagePage = nextPage
					this.hasMoreMessages = page ? (page.current || nextPage) < (page.pages || nextPage) : false
					if (anchor) {
						this.scrollAnimation = false
						this.$nextTick(() => {
							this.scrollIntoView = anchor
							setTimeout(() => { this.scrollAnimation = true }, 120)
						})
					}
				} finally {
					this.loadingMessages = false
				}
			},
			scrollToBottom(animated = true) {
				this.$nextTick(() => {
					const last = this.messages[this.messages.length - 1]
					if (!last) return
					this.scrollAnimation = animated
					this.scrollIntoView = last.domId
				})
			},
			sendPresence(text) {
				if (!this.roomId) return
				const data = { roomId: Number(this.roomId), eventType: 'presence', eventText: text }
				ieSocket.sendPresence(data)
				sendIePresence(this.roomId, data)
				this.messages.push({ domId: 'presence-' + Date.now(), content: text, mine: true, clientMsgId: 'p-' + Date.now() })
				this.scrollToBottom()
			},
			toggleEmoji() {
				this.voiceMode = false
				this.showEmoji = !this.showEmoji
			},
			sendEmoji(emoji) {
				this.showEmoji = false
				this.sendChatContent(emoji, 3)
			},
			sendMessage() {
				if (!this.draft.trim()) {
					uni.showToast({
						title: '也可以先不说话',
						icon: 'none'
					})
					return
				}
				const content = this.draft.trim()
				this.draft = ''
				this.showEmoji = false
				this.sendChatContent(content, 1)
			},
			sendChatContent(content, messageType, media = {}) {
				if (!this.roomId) {
					uni.showToast({ title: '房间信息缺失', icon: 'none' })
					return
				}
				if (this.needWaitReply()) {
					uni.showToast({ title: '先等等对方回复吧，首次破冰最多发送 3 条', icon: 'none' })
					return
				}
				const clientMsgId = 'm-' + Date.now()
				this.messages.push({
					domId: 'msg-' + clientMsgId,
					clientMsgId,
					content,
					messageType,
					mediaDuration: media.mediaDuration,
					mediaSize: media.mediaSize,
					mediaFormat: media.mediaFormat,
					mine: true,
					pending: true
				})
				this.scrollToBottom()
				this.persistMessage({
					roomId: Number(this.roomId),
					clientMsgId,
					messageType,
					content,
					mediaDuration: media.mediaDuration,
					mediaSize: media.mediaSize,
					mediaFormat: media.mediaFormat
				})
			},
			needWaitReply() {
				const delivered = this.messages.filter(item => item && !item.blocked && item.messageType)
				if (delivered.some(item => !item.mine)) return false
				return delivered.filter(item => item.mine).length >= 3
			},
			async persistMessage(payload) {
				try {
					const ack = await sendIeMessage(this.roomId, payload)
					if (ack) {
						this.applyAck(ack)
						return
					}
					const msg = this.messages.find(item => item.clientMsgId === payload.clientMsgId)
					if (msg) {
						msg.pending = false
						msg.blocked = true
						msg.content = '发送失败，请稍后再试'
					}
				} catch (e) {
					const msg = this.messages.find(item => item.clientMsgId === payload.clientMsgId)
					if (msg) {
						msg.pending = false
						msg.blocked = true
						msg.content = (e && e.message) || '发送失败，请稍后再试'
					}
				}
			},
			applyAck(ack) {
				const msg = this.messages.find(item => item.clientMsgId === ack.clientMsgId)
				if (!msg) return
				msg.pending = false
				msg.messageId = ack.messageId
				msg.messageType = ack.messageType || msg.messageType || 1
				msg.mediaDuration = ack.mediaDuration || msg.mediaDuration
				msg.mediaSize = ack.mediaSize || msg.mediaSize
				msg.mediaFormat = ack.mediaFormat || msg.mediaFormat
				msg.blocked = ack.isBlocked === 1
				msg.content = msg.blocked ? '这句话没有送出，请换一种更温柔的说法。' : (ack.content || msg.content)
			},
			chooseImage() {
				if (this.uploadingImage) return
				uni.chooseImage({
					count: 1,
					sizeType: ['compressed'],
					sourceType: ['album', 'camera'],
					success: async (res) => {
						const filePath = res.tempFilePaths && res.tempFilePaths[0]
						if (!filePath) return
						await this.uploadAndSendImage(filePath)
					}
				})
			},
			async uploadAndSendImage(filePath) {
				this.uploadingImage = true
				try {
					const result = await tui.uploadFile('/upload/file', filePath)
					const imageUrl = typeof result === 'string' ? result : (result.url || result.fileUrl || result.path || result.fullPath || result)
					if (!imageUrl || typeof imageUrl !== 'string') {
						uni.showToast({ title: '图片上传失败', icon: 'none' })
						return
					}
					this.showEmoji = false
					this.sendChatContent(imageUrl, 2)
				} finally {
					this.uploadingImage = false
				}
			},
			previewImage(url) {
				if (!url) return
				uni.previewImage({ urls: [url], current: url })
			},
			initRecorder() {
				if (!uni.getRecorderManager) return
				this.recorderManager = uni.getRecorderManager()
				this.recorderManager.onStop(async (res) => {
					const cancelled = this.voiceCancelling
					this.recording = false
					this.voiceCancelling = false
					this.recordStartY = 0
					if (cancelled) {
						uni.showToast({ title: '已取消发送', icon: 'none' })
						return
					}
					if (!res.tempFilePath) return
					const duration = Math.max(1, Math.round((res.duration || (Date.now() - this.recordStartTime)) / 1000))
					if (duration < 1) {
						uni.showToast({ title: '说得太短啦', icon: 'none' })
						return
					}
					await this.uploadAndSendVoice(res.tempFilePath, duration, res.fileSize)
				})
				this.recorderManager.onError(() => {
					this.recording = false
					uni.showToast({ title: '录音失败，请检查授权', icon: 'none' })
				})
			},
			initAudio() {
				if (!uni.createInnerAudioContext) return
				this.innerAudioContext = uni.createInnerAudioContext()
				this.innerAudioContext.onEnded(() => { this.playingVoiceUrl = '' })
				this.innerAudioContext.onStop(() => { this.playingVoiceUrl = '' })
				this.innerAudioContext.onError(() => {
					if (this.voiceSwitching) return
					this.playingVoiceUrl = ''
					uni.showToast({ title: '语音播放失败', icon: 'none' })
				})
			},
			recordTouchPoint(event) {
				const touch = (event.touches && event.touches[0]) || (event.changedTouches && event.changedTouches[0])
				return touch ? touch.clientY || touch.pageY || 0 : 0
			},
			startRecord(event) {
				if (!this.recorderManager || this.uploadingVoice) return
				this.showEmoji = false
				this.recording = true
				this.voiceCancelling = false
				this.recordStartY = this.recordTouchPoint(event)
				this.recordStartTime = Date.now()
				this.recorderManager.start({
					duration: 60000,
					sampleRate: 16000,
					numberOfChannels: 1,
					encodeBitRate: 48000,
					format: 'mp3'
				})
			},
			moveRecord(event) {
				if (!this.recording) return
				const y = this.recordTouchPoint(event)
				if (!y || !this.recordStartY) return
				this.voiceCancelling = this.recordStartY - y > 70
			},
			stopRecord() {
				if (!this.recorderManager || !this.recording) return
				this.recorderManager.stop()
			},
			cancelRecord() {
				if (!this.recorderManager || !this.recording) return
				this.voiceCancelling = true
				this.recording = false
				this.recorderManager.stop()
			},
			async uploadAndSendVoice(filePath, duration, fileSize) {
				if (!filePath || this.uploadingVoice) return
				this.uploadingVoice = true
				try {
					const result = await tui.uploadFile('/upload/file', filePath)
					const voiceUrl = typeof result === 'string' ? result : (result.url || result.fileUrl || result.path || result.fullPath || result)
					if (!voiceUrl || typeof voiceUrl !== 'string') {
						uni.showToast({ title: '语音上传失败', icon: 'none' })
						return
					}
					this.sendChatContent(voiceUrl, 4, {
						mediaDuration: Math.min(duration, 60),
						mediaSize: fileSize,
						mediaFormat: 'mp3'
					})
				} finally {
					this.uploadingVoice = false
				}
			},
			playVoice(item) {
				if (!item || !item.content || !this.innerAudioContext) return
				if (this.playingVoiceUrl === item.content) {
					this.voiceSwitching = true
					this.innerAudioContext.stop()
					this.playingVoiceUrl = ''
					setTimeout(() => { this.voiceSwitching = false }, 200)
					return
				}
				this.voiceSwitching = true
				this.innerAudioContext.stop()
				setTimeout(() => {
					this.innerAudioContext.src = item.content
					this.playingVoiceUrl = item.content
					this.voiceSwitching = false
					this.innerAudioContext.play()
				}, 80)
			},
			genderText(gender) {
				if (gender === 'male') return '男生'
				if (gender === 'female') return '女生'
				return '未设置'
			},
			modePreferenceText(mode) {
				if (mode === 'i') return 'i 人'
				if (mode === 'e') return 'e 人'
				return '都可以'
			},
			genderPreferenceText(gender) {
				if (gender === 'male') return '男生'
				if (gender === 'female') return '女生'
				return '不限性别'
			},
			targetText(mode, gender) {
				return this.modePreferenceText(mode) + ' · ' + this.genderPreferenceText(gender)
			},
			async openTargetProfile() {
				await this.loadTargetProfile()
				if (!this.targetProfile || !this.targetProfile.userId) {
					this.targetProfile = {
						anonymousName: this.companion.name,
						avatarText: this.companion.avatar,
						avatarUrl: this.companion.avatarUrl,
						intro: this.companion.prompt,
						currentMode: this.roomMode,
						interestTags: []
					}
				}
				this.showTargetProfile = true
			},
			async loadTargetProfile() {
				if (!this.targetUserId) return
				const profile = await getIeUserProfile(this.targetUserId)
				if (!profile) return
				this.targetProfile = profile
				this.companions[this.roomMode] = {
					...this.companion,
					name: profile.anonymousName || this.companion.name,
					avatar: profile.avatarText || this.companion.avatar,
					avatarUrl: profile.avatarUrl || '',
					prompt: profile.intro || this.companion.prompt
				}
			},
			async report() {
				this.showSafety = false
				await reportIeRoom(this.roomId, { reportedUserId: this.targetUserId, reasonType: 'other', reasonText: '聊天内容不适' })
				uni.showToast({ title: '已收到举报', icon: 'none' })
			},
			async block() {
				this.showSafety = false
				if (this.targetUserId) await blockIeUser(this.targetUserId, '聊天中拉黑')
				uni.showToast({ title: '已拉黑', icon: 'none' })
			},
			async finishRoom() {
				if (this.finishing) return
				this.finishing = true
				this.clearTimer()
				if (this.roomId) await finishIeRoom(this.roomId)
				uni.redirectTo({
					url: '/package1/ieBrowser/chatList'
				})
			},
			initSocket() {
				if (!this.roomId) return
				ieSocket.resetHandlers()
				ieSocket.onAck((ack) => {
					this.applyAck(ack)
				})
				ieSocket.onMessage((msg) => {
					if (String(msg.roomId) !== String(this.roomId)) return
					if (this.messages.some(item => String(item.messageId) === String(msg.messageId))) return
					if (msg.clientMsgId && this.messages.some(item => item.clientMsgId === msg.clientMsgId)) {
						this.applyAck(msg)
						return
					}
					this.messages.push({
						domId: 'msg-' + (msg.messageId || Date.now()),
						messageId: msg.messageId,
						clientMsgId: msg.clientMsgId,
						messageType: msg.messageType || 1,
						mediaDuration: msg.mediaDuration,
						mediaSize: msg.mediaSize,
						mediaFormat: msg.mediaFormat,
						content: msg.content,
						mine: String(msg.senderId || '') === String(uni.getStorageSync('id') || '')
					})
					this.scrollToBottom()
				})
				ieSocket.onPresence((event) => {
					if (String(event.roomId) !== String(this.roomId)) return
					this.typingHint = event.eventText || '对方有一个轻轻回应'
					setTimeout(() => { this.typingHint = '' }, 1800)
				})
				ieSocket.onRoomEnd((room) => {
					if (String(room.id) !== String(this.roomId)) return
					uni.showToast({ title: '这段聊天已结束', icon: 'none' })
					this.clearTimer()
					this.finishing = true
					uni.redirectTo({ url: '/package1/ieBrowser/chatList' })
				})
				ieSocket.connect().then(() => {
					ieSocket.subscribeRoom(this.roomId)
				})
			},
			back() {
				uni.redirectTo({ url: '/package1/ieBrowser/index' })
			}
		}
	}
</script>

<style lang="scss" scoped>
	page {
		background: #f7f9ff;
	}

	.room-page {
		min-height: 100vh;
		padding: 0 28rpx 176rpx;
		box-sizing: border-box;
		color: #161b2e;
		background:
			radial-gradient(circle at 14% 10%, rgba(139, 124, 255, 0.2), rgba(139, 124, 255, 0) 280rpx),
			radial-gradient(circle at 82% 18%, rgba(169, 255, 231, .42), rgba(169, 255, 231, 0) 320rpx),
			linear-gradient(180deg, #fbfdff 0%, #eef4ff 64%, #fff4e8 100%);
		position: relative;
		overflow: hidden;
	}

	.room-page::before {
		content: '';
		position: absolute;
		inset: 0;
		pointer-events: none;
		opacity: .1;
		background-image: radial-gradient(circle, rgba(22,27,46,.7) 0 1rpx, transparent 1rpx);
		background-size: 44rpx 44rpx;
		animation: noiseFloat 8s steps(5) infinite;
	}

	.glow {
		position: absolute;
		border-radius: 50%;
		filter: blur(10rpx);
	}

	.glow-a {
		right: -120rpx;
		top: 210rpx;
		width: 300rpx;
		height: 300rpx;
		background: rgba(169, 255, 231, 0.34);
	}

	.glow-b {
		left: -150rpx;
		bottom: 180rpx;
		width: 360rpx;
		height: 360rpx;
		background: rgba(255, 184, 209, 0.22);
	}

	.nav {
		position: relative;
		z-index: 1;
		height: 96rpx;
		display: flex;
		align-items: center;
	}

	.back,
	.more {
		width: 62rpx;
		color: rgba(22, 27, 46, 0.62);
		font-size: 54rpx;
		line-height: 56rpx;
	}

	.more {
		text-align: right;
		font-size: 34rpx;
	}

	.nav-avatar-wrap {
		width: 66rpx;
		height: 66rpx;
		margin-right: 14rpx;
		border-radius: 50%;
	}

	.nav-avatar,
	.nav-avatar-img {
		width: 66rpx;
		height: 66rpx;
		border-radius: 50%;
	}

	.nav-avatar {
		text-align: center;
		line-height: 66rpx;
		color: #11162a;
		background: linear-gradient(145deg, #ffffff, #a9ffe7);
		font-size: 26rpx;
		font-weight: 800;
	}

	.room-user {
		flex: 1;
		text-align: left;
	}

	.room-name {
		font-size: 31rpx;
		font-weight: 800;
	}

	.room-meta {
		margin-top: 8rpx;
		color: rgba(22, 27, 46, 0.46);
		font-size: 21rpx;
	}

	.timer-card,
	.topic-card {
		position: relative;
		z-index: 1;
		border: 1rpx solid rgba(255, 255, 255, 0.78);
		background: rgba(255, 255, 255, 0.62);
		backdrop-filter: blur(24rpx);
		box-shadow: 0 20rpx 64rpx rgba(96, 112, 160, 0.12);
	}

	.timer-card {
		display: flex;
		align-items: center;
		justify-content: space-between;
		margin-top: 22rpx;
		padding: 30rpx;
		border-radius: 34rpx;
	}

	.radio-room {
		position: relative;
		z-index: 1;
		display: flex;
		align-items: center;
		margin-top: 18rpx;
		padding: 34rpx;
		border-radius: 44rpx;
		background: rgba(255,255,255,.58);
		border: 1rpx solid rgba(255,255,255,.78);
		backdrop-filter: blur(24rpx);
		box-shadow: 0 24rpx 72rpx rgba(96,112,160,.13);
		overflow: hidden;
	}

	.radio-orbit {
		position: absolute;
		right: -40rpx;
		top: -60rpx;
		width: 260rpx;
		height: 260rpx;
		border-radius: 50%;
		border: 1rpx solid rgba(139,124,255,.16);
		animation: radioSpin 16s linear infinite;
	}

	.radio-core {
		width: 108rpx;
		height: 108rpx;
		margin-right: 24rpx;
		border-radius: 50%;
		text-align: center;
		line-height: 108rpx;
		color: #11162a;
		background: linear-gradient(145deg, #ffffff, #a9ffe7);
		font-size: 30rpx;
		font-weight: 800;
		box-shadow: 0 18rpx 48rpx rgba(169,255,231,.3);
		animation: breathe 3s ease-in-out infinite;
	}

	.radio-copy {
		position: relative;
		z-index: 1;
	}

	.radio-title {
		font-size: 32rpx;
		font-weight: 800;
	}

	.radio-sub {
		margin-top: 10rpx;
		color: rgba(22,27,46,.52);
		font-size: 24rpx;
	}

	.silent-modes {
		position: relative;
		z-index: 1;
		display: flex;
		overflow-x: auto;
		white-space: nowrap;
		margin-top: 22rpx;
	}

	.silent-chip {
		flex-shrink: 0;
		margin-right: 14rpx;
		padding: 16rpx 24rpx;
		border-radius: 999rpx;
		color: rgba(22,27,46,.62);
		background: rgba(255,255,255,.62);
		border: 1rpx solid rgba(255,255,255,.72);
		backdrop-filter: blur(18rpx);
		font-size: 23rpx;
	}

	.timer-label,
	.topic-label {
		color: #6c69d8;
		font-size: 22rpx;
	}

	.timer-title {
		margin-top: 12rpx;
		font-size: 30rpx;
		font-weight: 800;
	}

	.timer-pill {
		width: 126rpx;
		height: 70rpx;
		border-radius: 999rpx;
		text-align: center;
		line-height: 70rpx;
		color: #09101f;
		background: #dffef4;
		font-size: 28rpx;
		font-weight: 800;
	}

	.topic-card {
		margin-top: 24rpx;
		padding: 26rpx;
		border-radius: 30rpx;
	}

	.topic-text {
		margin-top: 12rpx;
		color: rgba(22, 27, 46, 0.72);
		font-size: 27rpx;
		line-height: 42rpx;
	}

	.messages {
		position: relative;
		z-index: 1;
		height: 58vh;
		padding-top: 34rpx;
		padding-bottom: 24rpx;
		box-sizing: border-box;
	}

	.load-more {
		width: 360rpx;
		margin: 0 auto 24rpx;
		text-align: center;
		color: rgba(22, 27, 46, 0.34);
		font-size: 21rpx;
		line-height: 34rpx;
	}

	.system-line {
		width: 560rpx;
		margin: 0 auto 30rpx;
		text-align: center;
		color: rgba(22, 27, 46, 0.36);
		font-size: 22rpx;
		line-height: 34rpx;
	}

	.hesitate-line {
		width: 260rpx;
		margin: 0 auto 24rpx;
		padding: 8rpx 18rpx;
		border-radius: 999rpx;
		color: rgba(22,27,46,.36);
		background: rgba(255,255,255,.46);
		font-size: 20rpx;
		animation: fadeHint 2.8s ease-in-out infinite;
	}

	.hesitate-line.right {
		margin-right: 18rpx;
	}

	.msg-row {
		display: flex;
		align-items: flex-start;
		margin-bottom: 30rpx;
	}

	.msg-row.mine {
		justify-content: flex-end;
	}

	.avatar,
	.avatar-img {
		width: 72rpx;
		height: 72rpx;
		margin-right: 16rpx;
		border-radius: 50%;
	}

	.avatar {
		text-align: center;
		line-height: 72rpx;
		font-size: 27rpx;
		font-weight: 800;
	}

	.avatar.i {
		color: #f7f4ff;
		background: linear-gradient(145deg, #8b7cff, #dde7ff);
	}

	.avatar.e {
		color: #07101e;
		background: linear-gradient(145deg, #a9ffe7, #fff0b8);
	}

	.bubble {
		max-width: 500rpx;
		padding: 22rpx 26rpx;
		border: 1rpx solid rgba(255, 255, 255, 0.78);
		border-radius: 30rpx;
		background: rgba(255, 255, 255, 0.62);
		color: rgba(22, 27, 46, 0.72);
		font-size: 27rpx;
		line-height: 42rpx;
	}

	.mine .bubble {
		color: #09101f;
		background: #dffef4;
	}

	.bubble.muted {
		background: rgba(223, 254, 244, 0.88);
	}

	.image-bubble {
		padding: 8rpx;
		background: rgba(255, 255, 255, 0.72);
	}

	.chat-image {
		display: block;
		width: 260rpx;
		height: 260rpx;
		border-radius: 24rpx;
		background: rgba(22, 27, 46, 0.06);
	}

	.emoji-bubble {
		min-width: 72rpx;
		text-align: center;
		font-size: 48rpx;
		line-height: 62rpx;
	}

	.voice-bubble {
		min-width: 180rpx;
		display: flex;
		align-items: center;
		justify-content: space-between;
		font-weight: 800;
	}

	.voice-bubble.playing {
		box-shadow: 0 0 0 4rpx rgba(169, 255, 231, 0.32);
	}

	.voice-wave {
		margin-right: 24rpx;
		color: #6c69d8;
		letter-spacing: 4rpx;
	}

	.presence-row {
		position: fixed;
		left: 28rpx;
		right: 28rpx;
		bottom: 132rpx;
		z-index: 10;
		display: flex;
		overflow-x: auto;
		white-space: nowrap;
	}

	.presence-chip {
		flex-shrink: 0;
		height: 58rpx;
		line-height: 58rpx;
		padding: 0 22rpx;
		margin-right: 14rpx;
		border-radius: 999rpx;
		color: rgba(22, 27, 46, 0.58);
		background: rgba(255, 255, 255, 0.62);
		box-shadow: 0 12rpx 30rpx rgba(96, 112, 160, .1);
		font-size: 23rpx;
	}

	.input-bar {
		position: fixed;
		left: 24rpx;
		right: 24rpx;
		bottom: 30rpx;
		z-index: 10;
		height: 88rpx;
		padding: 10rpx;
		box-sizing: border-box;
		border: 1rpx solid rgba(255, 255, 255, 0.78);
		border-radius: 999rpx;
		background: rgba(255, 255, 255, 0.72);
		backdrop-filter: blur(18rpx);
		display: flex;
		align-items: center;
	}

	.emoji-panel {
		position: fixed;
		left: 24rpx;
		right: 24rpx;
		bottom: 128rpx;
		z-index: 11;
		display: flex;
		flex-wrap: wrap;
		padding: 22rpx 20rpx 12rpx;
		border: 1rpx solid rgba(255, 255, 255, 0.78);
		border-radius: 34rpx;
		background: rgba(255, 255, 255, 0.88);
		backdrop-filter: blur(20rpx);
		box-shadow: 0 18rpx 58rpx rgba(96, 112, 160, 0.16);
		box-sizing: border-box;
	}

	.emoji-item {
		width: 16.66%;
		height: 66rpx;
		text-align: center;
		line-height: 66rpx;
		font-size: 42rpx;
	}

	.input {
		flex: 1;
		height: 66rpx;
		padding: 0 24rpx;
		border-radius: 999rpx;
		color: #161b2e;
		background: rgba(238, 244, 255, 0.72);
		font-size: 25rpx;
	}

	.voice-hold {
		flex: 1;
		height: 62rpx;
		line-height: 62rpx;
		padding: 0 18rpx;
		border-radius: 999rpx;
		text-align: center;
		color: rgba(22, 27, 46, 0.62);
		background: rgba(238, 244, 255, 0.86);
		font-size: 25rpx;
		font-weight: 800;
	}

	.voice-hold.recording {
		color: #09101f;
		background: #ffe4ec;
	}

	.voice-hold.cancelling {
		color: #fff;
		background: #ff6b8a;
	}

	.tool-btn {
		width: 62rpx;
		height: 62rpx;
		border-radius: 50%;
		text-align: center;
		line-height: 62rpx;
		color: rgba(22, 27, 46, 0.58);
		background: rgba(238, 244, 255, 0.72);
		font-size: 34rpx;
		font-weight: 800;
	}

	.send {
		height: 66rpx;
		line-height: 66rpx;
		margin-left: 10rpx;
		padding: 0 26rpx;
		border-radius: 999rpx;
		color: #09101f;
		background: #a9ffe7;
		font-size: 24rpx;
		font-weight: 800;
	}

	.safety-mask {
		position: fixed;
		left: 0;
		right: 0;
		top: 0;
		bottom: 0;
		z-index: 20;
		display: flex;
		align-items: flex-end;
		padding: 28rpx;
		background: rgba(22, 27, 46, 0.18);
		backdrop-filter: blur(18rpx);
		box-sizing: border-box;
	}

	.safety-panel {
		width: 100%;
		padding: 30rpx;
		border-radius: 38rpx;
		background: rgba(255, 255, 255, 0.86);
		box-sizing: border-box;
	}

	.safety-title {
		margin-bottom: 18rpx;
		font-size: 30rpx;
		font-weight: 800;
	}

	.safety-item {
		height: 86rpx;
		line-height: 86rpx;
		border-top: 1rpx solid rgba(22, 27, 46, 0.08);
		color: rgba(22, 27, 46, 0.72);
		font-size: 26rpx;
	}

	.safety-item.quiet {
		color: #6c69d8;
	}

	.profile-mask {
		position: fixed;
		left: 0;
		right: 0;
		top: 0;
		bottom: 0;
		z-index: 32;
		display: flex;
		align-items: flex-end;
		padding: 34rpx;
		background: rgba(22, 27, 46, 0.16);
		backdrop-filter: blur(16rpx);
		box-sizing: border-box;
	}

	.profile-panel {
		width: 100%;
		padding: 42rpx 34rpx 34rpx;
		border-radius: 44rpx;
		background: rgba(255, 255, 255, 0.88);
		border: 1rpx solid rgba(255, 255, 255, 0.92);
		box-shadow: 0 30rpx 90rpx rgba(96, 112, 160, 0.18);
		text-align: center;
		box-sizing: border-box;
	}

	.profile-avatar,
	.profile-avatar-img {
		width: 132rpx;
		height: 132rpx;
		margin: 0 auto;
		border-radius: 50%;
	}

	.profile-avatar {
		line-height: 132rpx;
		color: #11162a;
		background: linear-gradient(145deg, #ffffff, #a9ffe7);
		font-size: 44rpx;
		font-weight: 800;
	}

	.profile-name {
		margin-top: 24rpx;
		font-size: 36rpx;
		font-weight: 800;
	}

	.profile-mode {
		margin-top: 10rpx;
		color: rgba(22, 27, 46, 0.48);
		font-size: 23rpx;
	}

	.profile-info-grid {
		display: flex;
		gap: 18rpx;
		margin-top: 26rpx;
	}

	.profile-info {
		flex: 1;
		padding: 18rpx 14rpx;
		border-radius: 24rpx;
		color: rgba(22, 27, 46, 0.72);
		background: rgba(238, 244, 255, 0.72);
		font-size: 24rpx;
		font-weight: 800;
	}

	.profile-info-label {
		display: block;
		margin-bottom: 8rpx;
		color: rgba(22, 27, 46, 0.38);
		font-size: 20rpx;
		font-weight: 600;
	}

	.profile-intro {
		margin-top: 26rpx;
		padding: 24rpx;
		border-radius: 28rpx;
		color: rgba(22, 27, 46, 0.68);
		background: rgba(238, 244, 255, 0.72);
		font-size: 25rpx;
		line-height: 40rpx;
	}

	.profile-tags {
		margin-top: 22rpx;
	}

	.profile-tags text {
		display: inline-block;
		height: 44rpx;
		line-height: 44rpx;
		padding: 0 16rpx;
		margin: 0 10rpx 10rpx 0;
		border-radius: 999rpx;
		color: #6c69d8;
		background: rgba(139, 124, 255, 0.1);
		font-size: 21rpx;
	}

	.profile-close {
		height: 82rpx;
		line-height: 82rpx;
		margin-top: 24rpx;
		border-radius: 999rpx;
		color: #11162a;
		background: #a9ffe7;
		font-size: 26rpx;
		font-weight: 800;
	}

	@keyframes noiseFloat {
		0%, 100% { transform: translate3d(0,0,0); }
		50% { transform: translate3d(12rpx,-8rpx,0); }
	}

	@keyframes radioSpin {
		from { transform: rotate(0); }
		to { transform: rotate(360deg); }
	}

	@keyframes breathe {
		0%, 100% { transform: scale(.98); }
		50% { transform: scale(1.05); }
	}

	@keyframes fadeHint {
		0%, 100% { opacity: .38; transform: translateY(0); }
		50% { opacity: .82; transform: translateY(-4rpx); }
	}
</style>
