<template>
	<view class="markdown">
		<view class="content">
			<ua-markdown ref="markdown" :source="content" />
		</view>
		<view v-if="!last || !globalStore.chatLoading" class="options">
			<uni-load-more v-if="audioStatus == 'loading'" class="option-icon" iconType="circle" :show-text="false"
				status="loading" color="#7E8AA2" :icon-size="16" @tap="playAudio" />
			<image v-else-if="audioStatus == 'play'" class="option-icon" mode="aspectFit"
				:src="getStaticImageUrl('/images/audio-play.gif')" @tap="playAudio" />
			<image v-else-if="audioStatus == 'pause'" class="option-icon" mode="aspectFit"
				:src="getStaticImageUrl('/images/audio-pause.png')" @tap="playAudio" />
			<image v-else class="option-icon" mode="aspectFit" :src="getStaticImageUrl('/images/trumpet.png')"
				@tap="playAudio" />
			<image class="option-icon" mode="aspectFit" :src="getStaticImageUrl('/images/copy.png')"
				@tap="copyContent" />
			<view class="separate"></view>
			<image class="option-icon" mode="aspectFit"
				:src="getStaticImageUrl(likeFlag ? '/images/praise-1.png' : '/images/praise.png')" @tap="likeClick" />
			<image class="option-icon" mode="aspectFit"
				:src="getStaticImageUrl(stepFlag ? '/images/trample-1.png' : '/images/trample.png')" @tap="stepClick" />
		</view>
	</view>
</template>

<script>
	import {
		useGlobalStore
	} from '@/stores/global';
	import {
		RequestApi
	} from '@/api/requestApi';
	import PCMAudioPlayer from '@/utils/PCMAudioPlayer';
	import {
		safeTruncate,
		displayLength,
		isEmpty
	} from '@/utils/string.extensions';

	export default {
		name: 'SsChatReplyMarkdown',
		props: {
			chatId: {
				type: String,
				default: ''
			},
			content: {
				type: String,
				default: ''
			},
			title: {
				type: String,
				default: ''
			},
			last: {
				type: Boolean,
				default: false
			}
		},
		data() {
			return {
				globalStore: useGlobalStore(),
				mpHtmlOptions: {
					markdown: true,
					sanitize: true,
					highlight: code => {
						return this.$hljs.highlightAuto(code).value
					},
					styles: {
						'code': 'padding: 3px 5px; border-radius: 4px;',
						'table': 'border-collapse: collapse;',
						'th, td': 'border: 1px solid #ddd; padding: 8px;'
					}
				},
				likeFlag: false,
				stepFlag: false,
				loading: false,
				audioStatus: 'wait',
				ttsAudioPlayer: null,
				ttsToken: null,
				WS: null,
				ttsBuffer: null,
				ttsPaused: false,
			}
		},
		computed: {
			playChatId() {
				return this.globalStore.playChatId ?? '';
			},
			appHide() {
				return this.globalStore.appHide ?? '';
			}
		},
		mounted() {
			this.initAudioPlayer();
		},
		destroyed() {
			if (this.WS) {
				this.WS?.close();
			}
		},
		onUnload() {
			if (this.WS) {
				this.WS?.close();
			}
		},
		onHide() {
			console.log('隐藏...')
			this.pauseAudio();
		},
		methods: {
			previewImage(src) {
				uni.previewImage({
					urls: [src],
					current: src
				})
			},
			copyContent() {
				if (this.$refs.markdown) {
					this.$refs.markdown.copyContent();
				}
			},
			likeClick() {
				if (this.loading) return;
				this.loading = true;
				RequestApi.LikeOrStep(this.chatId, this.likeFlag ? 1 : 2).then(result => {
					uni.showToast({
						title: this.likeFlag ? '取消成功' : '点赞成功，感谢你的反馈',
						icon: 'none'
					});
					this.likeFlag = !this.likeFlag;
					if (this.likeFlag) {
						this.stepFlag = false;
					}
				}).finally(() => {
					this.loading = false;
				});
			},
			stepClick() {
				if (this.loading) return;
				this.loading = true;
				RequestApi.LikeOrStep(this.chatId, this.stepFlag ? 1 : 3).then(result => {
					uni.showToast({
						title: this.stepFlag ? '取消成功' : '操作成功，感谢你的反馈',
						icon: 'none'
					});
					this.stepFlag = !this.stepFlag;
					if (this.stepFlag) {
						this.likeFlag = false;
					}
				}).finally(() => {
					this.loading = false;
				});
			},
			initAudioPlayer() {
				try {
					this.ttsAudioPlayer = new PCMAudioPlayer({
						sampleRate: 24000,
						channels: 1,
						bitDepth: 16,
						littleEndian: true,
						loop: false
					});
					// 播放完成回调
					this.ttsAudioPlayer.setOnPlaybackComplete(() => {
            this.globalStore.setPlayChatId('');
						this.audioStatus = 'stop';
					});
					// 播放暂停回调
					this.ttsAudioPlayer.setOnPause(() => {
						console.log('TTS播放暂停');
						this.pauseAudio();
					})
					return true;
				} catch (err) {
					console.log('TTS初始化失败', err)
					return false;
				}
			},
			playAudio() {
				if (this.$refs.markdown) {
					const content = this.getContent();
					if (content !== '' && this.audioStatus) {
						this.globalStore.setPlayChatId(this.chatId);
						switch (this.audioStatus.toLowerCase()) {
							case 'wait':
								this.audioStatus = 'loading'
								this.loadTextToSpeech()
								break;
							case 'loading':
								this.audioStatus = 'wait'
								break;
							case 'play':
								this.pauseAudio()
								break;
							case 'pause':
								if (this.ttsAudioPlayer) {
									this.ttsPaused = false;
									this.audioStatus = 'play';
									this.ttsAudioPlayer.resume();
								}
								break;
							case 'stop':
								if (this.ttsAudioPlayer) {
									this.ttsPaused = false;
									this.audioStatus = 'play';
									this.ttsAudioPlayer.replay();
								}
								break;
							default:
								uni.showToast({
									title: '操作方式错误',
									icon: 'none'
								});
						}
					}
				}
			},
			pauseAudio() {
				// TTS 播放处理
				if (this.ttsAudioPlayer) {
					this.ttsPaused = true;
					this.ttsAudioPlayer.pause()
					this.$nextTick(() => {
						this.audioStatus = 'pause'
					});
				}
			},
			stopAudit() {
				if (this.ttsAudioPlayer) {
					this.ttsPaused = false;
					this.ttsAudioPlayer.stop();
					this.$nextTick(() => {
						this.audioStatus = 'wait'
					});
				}
			},
			async loadTextToSpeech() {
				try {
					const result = await RequestApi.getTtsToken();
					this.ttsToken = result.oauthToken;
					if (this.ttsToken) {
						if (this.connectWsSpeech()) {
							console.log('connect ws')
						} else {
							this.audioStatus = 'wait';
							uni.showToast({
								title: '语音服务器连接失败',
								icon: 'none'
							});
						}
					}
					return true;
				} catch (err) {
					uni.showToast({
						title: err.message,
						icon: 'none'
					});
					this.audioStatus = 'wait';
				}
			},
			connectWsSpeech() {
				if (isEmpty(this.ttsToken)) {
					return false;
				}
				try {
					const url = `wss://ws.coze.cn/v1/audio/speech`;
					this.WS = uni.connectSocket({
						url: url,
						header: {
							'Authorization': `Bearer ${this.ttsToken}`,
							'content-type': 'application/json'
						},
						success: () => {
							console.log('WebSocket连接创建成功');
						},
						fail: () => {
							console.error('WebSocket连接创建失败', err);
						}
					});
					this.setupSocketListeners();
					return true;
				} catch (err) {
					console.log('TTS连接失败', err)
					this.audioStatus = 'wait'
					return false
				}
			},
			setupSocketListeners() {
				this.WS.onOpen(() => {
					console.log('WebSocket连接成功');
				});
				this.WS.onMessage((event) => {
					const message = JSON.parse(event.data);
					if (message.event_type === 'speech.created') {
						this.ttsBuffer = message;
						this.ttsSpeechUpdate();
						this.ttsSendMessage(this.getContent());
					}
					if (message.event_type === 'speech.audio.update') {
						this.handleTTSAudio(message.data.delta)
					}
				});
				this.WS.onError((err) => {
					console.error('WebSocket连接错误', err);
				});
				this.WS.onClose(() => {
					console.log('TTS连接关闭');
				});
				return true;
			},
			isValidBase64(str) {
				const base64Regex = /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$/;
				if (!base64Regex.test(str) || str.length % 4 !== 0) {
					return false;
				}

				try {
					// 微信小程序环境下使用uni.base64ToArrayBuffer和uni.arrayBufferToBase64
					const buffer = uni.base64ToArrayBuffer(str);
					const reencoded = uni.arrayBufferToBase64(buffer);
					return reencoded === str;
				} catch (e) {
					return false;
				}
			},
			getContent() {
				let content = this.$refs.markdown.extractPlainText(this.content);
				content = content.replace(/<[^>]*>/gi, '');
				return content.trim();
			},
			async handleTTSAudio(audioData) {
				if (!this.isValidBase64(audioData)) {
					console.log('无效的base64数据', audioData);
				}
				try {
					// 播放Base64音频数据
					this.ttsAudioPlayer.feedBase64(audioData, !this.ttsPaused);
					if (!this.ttsPaused) {
						this.audioStatus = 'play'
					}
				} catch (err) {
					console.log('TTS音频播放失败', err)
				}
				return true;
			},
			ttsSpeechUpdate() {
				if (this.WS && this.ttsBuffer) {
					this.WS?.send({
						data: JSON.stringify({
							"id": this.ttsBuffer.id,
							"event_type": "speech.update",
							"data": {
								"output_audio": {
									"codec": "pcm",
									"speech_rate": 0,
									"voice_id": "7426725529589596187"
								}
							}
						})
					});
				}
			},
			ttsSendMessage(content, start = 0, length = 50) {
				if (!this.ttsBuffer) {
					return false;
				}
				let l = displayLength(content);
				let msg = safeTruncate(content, start, length, '');
				this.WS?.send({
					data: JSON.stringify({
						"id": this.ttsBuffer.id,
						"event_type": "input_text_buffer.append",
						"data": {
							"delta": msg
						}
					})
				});
				if (l > start + length + 1) {
					this.ttsSendMessage(content, start + length, length);
				}
			}
		},
		watch: {
			playChatId(val) {
				if (val !== this.chatId) {
					this.pauseAudio();
					this.stopAudit();
				}
			},
			appHide(val) {
				if (this.chatId == this.playChatId) {
					if (val) {
						this.pauseAudio();
					} else {
						if (this.audioStatus == 'pause') {
							this.playAudio();
						}
					}
				} else {
					if (val) {
						this.stopAudit();
					}
				}
			}
		}
	};
</script>

<style lang="scss">
	.markdown {
		margin-bottom: 32rpx;

		h1,
		h2,
		h3,
		h4,
		strong {
			color: #101333;
			font-weight: bold;
		}

		.content {
			margin-bottom: 16rpx;
		}

		.options {
			display: flex;

			align-items: center;

			.option-icon {
				width: 32rpx;
				height: 32rpx;
				margin-right: 16rpx;
				cursor: pointer;
			}

			.separate {
				display: inline-block;
				width: 1px;
				height: 32rpx;
				align-self: flex-start;
				background-color: #7e8aa2;
				margin-right: 16rpx;
			}
		}
	}
</style>