<template>
	<transition name="zoom-in-top">
		<div class="audio-preview-wrapper" v-show="visible">
			<img class="audio-background" :src="musicImgUrl" alt="背景图" />
			<!-- 右上角操作 -->
			<div class="operate-box">
				<a-tooltip placement="bottom">
					<template #title>
						<div style="line-height: 2">
							操作提示: <br />
							1. 按 Esc 键可退出查看；<br />
							2. 支持键盘控制：<br />
							&nbsp;&nbsp;空格 - 暂停/播放<br />
							&nbsp;&nbsp;左方向键 - 播放上一个<br />
							&nbsp;&nbsp;右方向键 - 播放下一个<br />
							&nbsp;&nbsp;上方向键 - 音量调大<br />
							&nbsp;&nbsp;下方向键 - 音量减小<br />
						</div>
					</template>
					<BulbOutlined class="tip-icon" />
				</a-tooltip>
				<CloseOutlined class="close-icon" title="关闭（Escape）" @click="handleClosePreview" />
			</div>
			<audio
				ref="audioRef"
				:src="activeFileObj.fileUrl"
				controls
				style="position: fixed; top: 0; left: 0; display: none"
				@loadedmetadata="handleLoadedmetadata"
				@timeupdate="handleTimeUpdate"
				@ended="handleChangeAudioIndex('next')"
			></audio>
			<div class="audio-list-wrapper">
				<!-- 音频列表 -->
				<ul class="audio-list">
					<li class="audio-list-header">
						<span class="name">音频名称</span>
						<span class="audio-size">大小</span>
						<span class="path">路径</span>
					</li>
					<div class="audio-list-body">
						<li
							class="audio-item"
							v-for="(item, index) in audioList"
							:key="index"
							:class="[activeIndex === index ? 'active' : '']"
							:title="isPlay ? '暂停' : '播放'"
							@click="handleChangeAudioIndex('manual', index)"
						>
							<span class="name">
								<span class="sequence" v-show="activeIndex !== index">
									{{ index + 1 }}
								</span>
								<img class="wave" :src="activePlayIcon" alt="波浪动图" v-show="activeIndex === index && isPlay" />
								<i class="no-wave anticon anticon-bar-chart" v-show="activeIndex === index && !isPlay"></i>
								<span class="text">{{ item.fileName }}.{{ item.extendName }}</span>
							</span>
							<i class="play-icon iconfont icon-icon-7" v-show="activeIndex === index && !isPlay"></i>
							<i class="pause-icon iconfont icon-icon-3" v-show="activeIndex === index && isPlay"></i>
							<a class="download" :href="$file.getDownloadFilePath(item)" target="_blank" title="下载">
								<i class="download-icon anticon anticon-download"></i>
							</a>
							<i
								class="share-icon anticon anticon-share-alt"
								title="分享"
								@click.stop="
									$openDialog.shareFile({
										fileInfo: [
											{
												userFileId: item.userFileId
											}
										]
									})
								"
							></i>
							<span class="audio-size">{{ $file.calculateFileSize(item.fileSize) }}</span>
							<span class="path">{{ item.filePath }}</span>
						</li>
					</div>
				</ul>
				<!-- 歌曲图片和歌词 -->
				<div class="img-and-lyrics">
					<img class="audio-img" :src="musicImgUrl" alt="歌曲图片" />
					<div class="audio-name">{{ activeFileObj.fileName }}.{{ activeFileObj.extendName }}</div>
					<div class="album-artist" v-show="audioInfo.artist">歌手：{{ audioInfo.artist }}</div>
					<div class="album-name" v-if="audioInfo.album">专辑：{{ audioInfo.album }}</div>
					<ul
						class="lyrics-list"
						ref="lyricsListRef"
						:class="{ one: lyricsList.length === 1 }"
						v-if="lyricsList.length"
					>
						<li
							class="lyrics-item"
							ref="lyricsLineRef"
							v-for="(item, index) in lyricsList"
							:key="index"
							:class="{
								active: currentLyricsLineIndex === index
							}"
							@click="handleChangeProgress(transferTimeToSeconds(item.time))"
						>
							{{ item.text }}
						</li>
					</ul>
				</div>
			</div>
			<!-- 底部音乐控件 -->
			<div class="control-wrapper">
				<div class="control-left">
					<StepBackwardOutlined
						class="operate-icon"
						title="上一个（按左方向键）"
						@click="handleChangeAudioIndex('pre')"
					/>
					<PlayCircleOutlined
						class="operate-icon play-icon"
						v-show="!isPlay"
						title="播放（按空格键）"
						@click="handleClickPlayIcon"
					/>
					<PauseCircleOutlined
						class="operate-icon pause-icon"
						v-show="isPlay"
						title="暂停（按空格键）"
						@click="handleClickPauseIcon"
					/>
					<StepForwardOutlined
						class="operate-icon"
						title="下一个（按右方向键）"
						@click="handleChangeAudioIndex('next')"
					/>
					<a-slider
						class="progress-bar control-item"
						v-model:value="currentTime"
						:step="progressStep"
						:max="audioInfo.duration"
						:tip-formatter="(val) => transferSecondsToTime(val)"
						@mousedown="isDrop = true"
						@mouseup="isDrop = false"
						@change="handleChangeProgress"
					/>
					<span class="time control-item"
						>{{ transferSecondsToTime(currentTime) }} / {{ transferSecondsToTime(audioInfo.duration) }}</span
					>
				</div>
				<div class="control-right">
					<ReloadOutlined
						class="operate-icon cycle-type"
						:title="cycleTypeMap[String(cycleType)].text"
						@click="handleChangeCycleType"
					/>
					<a
						class="operate-icon download-link"
						:href="$file.getDownloadFilePath(activeFileObj)"
						target="_blank"
						title="下载"
					>
						<DownloadOutlined class="download-icon" />
					</a>
					<ShareAltOutlined
						class="operate-icon share-icon"
						title="分享"
						@click.stop="
							$openDialog.shareFile({
								fileInfo: [
									{
										userFileId: activeFileObj.userFileId
									}
								]
							})
						"
					/>
					<template v-if="volume === 0">
						<AudioMutedOutlined class="operate-icon volume-icon control-item" @click="handleClickVolumeIcon" />
					</template>
					<template v-else>
						<SoundOutlined class="operate-icon volume-icon control-item" @click="handleClickVolumeIcon" />
					</template>
					<a-slider
						class="volume-bar control-item"
						v-model:value="volume"
						:step="0.01"
						:max="1"
						:tip-formatter="(val) => Math.floor(val * 100)"
						title="可按上下方向键调节音量"
						@change="handleChangeVolumeBar"
					/>
				</div>
			</div>
		</div>
	</transition>
</template>

<script setup>
	import { ref, computed, watch, onUnmounted, getCurrentInstance } from 'vue'
	import { getFileDetail } from '@/api/myResource/file.js'
	import { Base64 } from 'js-base64'
	import waveGif from '@/assets/images/myResource/audio/wave.gif'
	import musicImg from '@/assets/images/myResource/file/file_music.png'
	import {
		BulbOutlined,
		CloseOutlined,
		StepBackwardOutlined,
		PlayCircleOutlined,
		PauseCircleOutlined,
		StepForwardOutlined,
		ReloadOutlined,
		DownloadOutlined,
		ShareAltOutlined,
		SoundOutlined,
		AudioMutedOutlined
	} from '@ant-design/icons-vue'
	const { proxy } = getCurrentInstance()

	// 定义响应式数据
	const visible = ref(false) // 音频预览组件是否可见
	const activeIndex = ref(0) // 当前打开的音频索引
	const activePlayIcon = ref(waveGif)
	const cycleType = ref(1) // 音频播放的循环模式
	const isPlay = ref(false) // 是否正在播放
	const currentTime = ref(0) // 当前播放的秒
	const isDrop = ref(false) // 是否正在拖拽播放进度滑块
	const volume = ref(0) // 音量
	const audioInfo = ref({}) // 音频信息
	const lyricsList = ref([]) // 歌词列表
	const currentLyricsLineIndex = ref(0) // 当前高亮的歌词行索引，从 0 开始
	const loading = ref(false) // 加载状态
	const audioRef = ref(null) // 音频元素引用
	const lyricsListRef = ref(null) // 歌词列表引用
	const lyricsLineRef = ref([]) // 歌词行引用
	const defaultIndex = ref(0) // 默认索引

	// 音频循环模式和图标对应的 Map
	const cycleTypeMap = {
		1: {
			icon: 'icon-xunhuanbofang',
			text: '列表循环'
		},
		2: {
			icon: 'icon-danquxunhuan1',
			text: '单曲循环'
		},
		3: {
			icon: 'icon-suijibofang1',
			text: '随机播放'
		}
	}
	// 定义 props 接收父组件传递的参数
	const props = defineProps({
		audioList: {
			type: Array,
			default: () => []
		},
		defaultIndex: {
			type: Number,
			default: 0
		},
		callback: {
			type: Function,
			default: () => {}
		}
	})

	// 计算属性
	// 当前显示的文件信息
	const activeFileObj = computed(() => {
		const res = props.audioList.length ? props.audioList[activeIndex.value] : {}
		return res
	})

	// 隐藏的 audio 标签
	const audioElement = computed(() => {
		return audioRef.value
	})

	// 歌曲封面
	const musicImgUrl = computed(() => {
		return audioInfo.value.albumImage ? `data:image/jpeg;base64,${audioInfo.value.albumImage}` : musicImg
	})

	// 播放进度条步长
	const progressStep = computed(() => {
		return audioInfo.value.duration / 100
	})

	// 监听音频预览组件状态
	watch(visible, (newValue) => {
		if (newValue) {
			activeIndex.value = defaultIndex.value
			getFileDetailData()
			// 添加键盘相关事件
			document.addEventListener('keyup', handleAddKeyupEvent)
		} else {
			// 移除键盘相关事件
			document.removeEventListener('keyup', handleAddKeyupEvent)
		}
	})

	// 监听当前索引变化
	watch(activeIndex, () => {
		getFileDetailData()
	})

	// 组件卸载时移除事件监听
	onUnmounted(() => {
		document.removeEventListener('keyup', handleAddKeyupEvent)
	})

	/**
	 * DOM 绑定 Esc 键、左方向键、右方向键的键盘按下事件
	 * @param {event} event 事件
	 */
	function handleAddKeyupEvent(event) {
		switch (event.code) {
			// 关闭预览
			case 'Escape': {
				handleClosePreview()
				break
			}
			// 切换到上一个
			case 'ArrowLeft': {
				handleChangeAudioIndex('pre')
				break
			}
			// 切换到下一个
			case 'ArrowRight': {
				handleChangeAudioIndex('next')
				break
			}
			// 音量调大
			case 'ArrowUp': {
				volume.value = volume.value === 1 ? 1 : volume.value + 0.1
				volume.value = Number(volume.value.toFixed(1))
				handleChangeVolumeBar(volume.value)
				break
			}
			// 音量调小
			case 'ArrowDown': {
				volume.value = volume.value === 0 ? 0 : volume.value - 0.1
				volume.value = Number(volume.value.toFixed(1))
				handleChangeVolumeBar(volume.value)
				break
			}
			// 暂停/播放
			case 'Space': {
				handleChangeAudioIndex('manual', activeIndex.value)
				break
			}
		}
	}

	/**
	 * 获取文件信息
	 */
	function getFileDetailData() {
		handleClickPauseIcon()
		loading.value = true
		getFileDetail({ userFileId: activeFileObj.value.userFileId })
			.then((res) => {
				loading.value = false
				if (res.success) {
					audioInfo.value = {
						...res.data.music,
						duration: res.data.music.trackLength
					}
					if (!audioInfo.value.lyrics) {
						lyricsList.value = []
						return
					}
					// Base64 解码为 lrc 格式的歌词文件
					let lyricsStr = Base64.decode(audioInfo.value.lyrics)
					if (lyricsStr.includes('[offset:0]')) {
						// 有歌词，从标志位 [offset:0] 下一行开始截取
						lyricsStr = lyricsStr.split('[offset:0]')[1]
					}
					lyricsList.value = lyricsStr
						.split('\n')
						.map((item, index) => {
							// 检查行是否包含 [ 和 ] 字符
							if (item && item.includes('[') && item.includes(']')) {
								try {
									const parts = item.split('[')
									if (parts.length > 1) {
										const line = parts[1].split(']')
										if (line.length > 1) {
											return {
												time: line[0], //  当前行歌词开始播放的秒数
												text: line[1].trim() //  当前歌词文本
											}
										}
									}
								} catch (error) {
									console.error('解析歌词行出错:', item, error)
								}
							}
							// 如果解析失败或格式不符合要求，返回null
							return null
						})
						.filter((item) => item !== null && item.text !== '')
					lyricsList.value = lyricsList.value.map((item, index) => {
						return {
							...item,
							// 当前行歌词起始秒数
							startSeconds: transferTimeToSeconds(item.time),
							// 当前行歌词结束秒数
							endSeconds:
								index < lyricsList.value.length - 1
									? transferTimeToSeconds(lyricsList.value[index + 1].time)
									: audioInfo.value.duration
						}
					})
					// 当切换完歌曲时，歌词重新滚动到顶部
					lyricsListRef.value.scrollTo({
						top: 0,
						behavior: 'smooth'
					})
					currentLyricsLineIndex.value = 0
				}
			})
			.catch(() => {
				loading.value = false
			})
	}

	/**
	 * 获取播放器参数
	 */
	function handleLoadedmetadata(event) {
		const audioDom = event.target
		volume.value = audioDom.volume || 0.5
		currentTime.value = audioDom.currentTime
		handleClickPlayIcon()
	}

	/**
	 * 将秒转化为时分秒
	 * @param {number} duration 总秒数
	 */
	function transferSecondsToTime(duration) {
		const hour = Math.floor(duration / 3600)
		const minutes = Math.floor(duration / 60)
		const seconds = Math.ceil(duration % 60)
		return `${hour < 10 ? `0${hour}` : hour}:${minutes < 10 ? `0${minutes}` : minutes}:${
			seconds < 10 ? `0${seconds}` : seconds
		}`
	}

	/**
	 * 将分秒转化为秒
	 * @param {string} time 分秒，格式 00:00
	 */
	function transferTimeToSeconds(time) {
		const timeList = time.split('.')[0].split(':')
		return Number(timeList[1]) + Number(timeList[0]) * 60
	}

	/**
	 * 当前播放时间改变时触发
	 */
	function handleTimeUpdate(event) {
		// 如果正在拖拽进度滑块，函数结束，不计算当前时间
		if (isDrop.value) return
		currentTime.value = event.target.currentTime
		if (lyricsList.value.length) {
			// 遍历歌词，当前秒对应的歌词整行添加高亮效果
			lyricsList.value.forEach((item, index) => {
				if (
					item.startSeconds <= currentTime.value &&
					currentTime.value < item.endSeconds &&
					currentLyricsLineIndex.value !== index
				) {
					// 确定高亮歌词行索引
					currentLyricsLineIndex.value = index
					// 使高亮歌词行永远保持在第二行
					if (currentLyricsLineIndex.value > 2) {
						// 平滑滚动
						lyricsListRef.value.scrollTo({
							top: lyricsLineRef.value[index].clientHeight * (index - 2),
							behavior: 'smooth'
						})
					}
				}
			})
		}
	}

	/**
	 * 拖动播放进度滑块触发
	 */
	function handleChangeProgress(progress) {
		audioElement.value.currentTime = progress
		isDrop.value = false
	}

	/**
	 * 切换循环播放类型
	 */
	function handleChangeCycleType() {
		if (cycleType.value === 3) {
			cycleType.value = 1
		} else if (cycleType.value >= 1) {
			cycleType.value++
		}
	}

	/**
	 * 点击播放图标触发
	 * @description 开始播放音频
	 */
	function handleClickPlayIcon() {
		isPlay.value = true
		audioElement.value.play()
	}

	/**
	 * 点击暂停图标触发
	 * @description 暂停音频
	 */
	function handleClickPauseIcon() {
		isPlay.value = false
		audioElement.value.pause()
	}

	/**
	 * 切换、暂停或播放歌曲
	 * @param {string} type pre - 上一首 | next - 下一首 | manual 手动切换
	 * @param {number} index 手动切换的音频索引，从 0 开始
	 */
	function handleChangeAudioIndex(type, index) {
		// 如果当前手动切换
		if (type === 'manual') {
			if (activeIndex.value === index) {
				if (isPlay.value) {
					handleClickPauseIcon()
				} else {
					handleClickPlayIcon()
				}
			} else {
				activeIndex.value = index
			}
		} else {
			handleClickPauseIcon()
			// 判断当前循环播放类型
			switch (cycleType.value) {
				case 3: {
					let newActiveIndex = 0
					do {
						newActiveIndex = Math.floor(Math.random() * (props.audioList.length - 1)) + 1
					} while (activeIndex.value === newActiveIndex)
					activeIndex.value = newActiveIndex
					break
				}
				default: {
					if (type === 'pre') {
						if (activeIndex.value === 0) {
							activeIndex.value = props.audioList.length - 1
						} else {
							activeIndex.value--
						}
					} else if (type === 'next') {
						if (activeIndex.value === props.audioList.length - 1) {
							activeIndex.value = 0
						} else {
							activeIndex.value++
						}
					}
					break
				}
			}
		}
	}

	/**
	 * 点击音量图标
	 */
	function handleClickVolumeIcon() {
		volume.value = volume.value === 0 ? 0.5 : 0
		handleChangeVolumeBar(volume.value)
	}

	/**
	 * 音量滑块改变时触发
	 */
	function handleChangeVolumeBar(value) {
		audioElement.value.volume = Number(value.toFixed(1))
	}

	/**
	 * 关闭音频预览
	 */
	function handleClosePreview() {
		visible.value = false
		props.callback('cancel')
	}

	// 暴露给父组件的方法和属性
	defineExpose({
		visible
	})
</script>

<style lang="less" scoped>
	@import '@/style/myResource/varibles.less';
	@import '@/style/myResource/mixins.less';

	.audio-preview-wrapper {
		background: @PrimaryText;
		position: fixed;
		top: 0;
		left: 0;
		width: 100vw;
		height: 100vh;
		z-index: 3;
		color: @BorderBase;

		.audio-background {
			position: fixed;
			top: -50%;
			left: 0;
			width: 100vw;
			height: auto;
			filter: blur(65px);
			opacity: 0.6;
			z-index: -1;
		}

		.operate-box {
			position: fixed;
			top: 16px;
			right: 32px;
			display: flex;
			align-items: center;

			.tip-icon,
			.close-icon {
				margin-left: 16px;
				cursor: pointer;

				&:hover {
					color: @Warning;
				}
			}

			.tip-icon {
				font-size: 26px;
			}

			.close-icon {
				font-size: 30px;
			}
		}

		.audio-list-wrapper {
			margin: 0 auto;
			width: 85%;
			height: calc(100vh - 120px);
			padding-top: 32px;
			display: flex;
			justify-content: space-between;

			.audio-list {
				flex: 1;
				list-style: none;

				.audio-list-body {
					height: calc(100% - 56px);
					overflow: auto;
					.setScrollbar(8px, transparent, rgba(0, 0, 0, 0.3));
				}

				.audio-list-header,
				.audio-item {
					border-radius: 8px;
					display: flex;
					justify-content: space-between;
					align-items: center;
					height: 56px;
					cursor: pointer;
					padding: 0 16px;

					&:hover {
						background: rgba(0, 0, 0, 0.1);
					}

					&.active {
						background: rgba(0, 0, 0, 0.1);
						color: @Warning;
					}

					.name {
						flex: 1;

						.sequence {
							display: inline-block;
							margin-right: 8px;
							width: 14px;
							text-align: center;
						}

						.wave {
							margin-right: 10px;
							width: 12px;
							height: 12px;
						}

						.no-wave {
							margin-right: 6px;
							font-size: 16px;
						}
					}

					.play-icon,
					.pause-icon,
					.download-icon,
					.share-icon {
						margin-right: 16px;
						font-size: 22px;
						cursor: pointer;

						&:hover {
							color: @Warning;
						}
					}

					.download {
						color: inherit;

						&:hover {
							color: @Warning;
						}
					}

					.audio-size {
						width: 120px;
						padding-right: 24px;
						text-align: right;
					}

					.path {
						min-width: 120px;
					}
				}

				.audio-list-header {
					padding-right: 24px;

					.name {
						padding-left: 18px;
					}
				}
			}

			.img-and-lyrics {
				padding: 8px 0 0 16px;
				width: 340px;
				display: flex;
				flex-direction: column;
				align-items: center;
				text-align: center;

				.audio-img {
					margin-bottom: 16px;
					width: 160px;
					height: 160px;
				}

				.audio-name {
					margin-bottom: 8px;
					font-size: 18px;
					line-height: 2;
				}

				.album-artist,
				.album-name {
					margin-bottom: 8px;
				}

				.lyrics-list {
					width: 100%;
					flex: 1;
					overflow: auto;
					.setScrollbar(6px, transparent, rgba(0, 0, 0, 0.3));
					-webkit-mask-image: linear-gradient(
						180deg,
						hsla(0, 0%, 100%, 0) 0,
						hsla(0, 0%, 100%, 0.6) 15%,
						#fff 25%,
						#fff 75%,
						hsla(0, 0%, 100%, 0.6) 85%,
						hsla(0, 0%, 100%, 0)
					);

					&.one {
						.lyrics-item {
							margin-top: 40px;
						}
					}

					.lyrics-item {
						line-height: 40px;
						cursor: pointer;

						&:not(.active):hover {
							color: #fff;
						}

						&.active {
							color: @Warning;
						}
					}
				}
			}
		}

		.control-wrapper {
			margin: 0 auto;
			width: 85%;
			height: 120px;
			padding: 24px 0 32px 0;
			display: flex;
			justify-content: space-between;
			align-items: center;

			.control-left {
				flex: 1;
				height: 100%;
				display: flex;
				align-items: center;
				text-align: center;
				padding-left: 8px;

				.operate-icon {
					margin-right: 16px;
					font-size: 40px;
					cursor: pointer;

					&:hover {
						color: @Warning;
					}
				}

				.progress-bar {
					margin-right: 16px;
					flex: 1;
				}
			}

			.control-right {
				width: 340px;
				font-size: 24px;
				display: flex;
				justify-content: center;
				align-items: center;
				font-size: 32px;

				.operate-icon {
					margin-right: 16px;
					cursor: pointer;

					&:nth-last-of-type {
						margin-right: 0;
					}

					&:hover {
						color: @Warning;
					}

					&.download-link {
						font-size: 32px;
						color: inherit;

						&:hover {
							.download-icon {
								color: @Warning;
							}
						}
					}
				}

				.volume-icon {
					margin-right: 8px;
				}

				.volume-bar {
					width: 100px;
				}
			}
		}
	}
</style>
