<template>
	<a-modal
		class="audio-preview-wrapper"
		v-model:visible="visible"
		:footer="null"
		:closable="false"
		:maskClosable="false"
		:width="'100%'"
		:bodyStyle="{ padding: 0 }"
		:centered="true"
	>
		<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>
				<question-circle-outlined class="tip-icon" />
			</a-tooltip>
			<close-outlined 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')"
		/>

		<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
						v-for="(item, index) in audioList"
						:key="index"
						class="audio-item"
						:class="{ active: activeIndex === index }"
						: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 el-icon-s-data" 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 el-icon-download"></i>
						</a>
						<i
							class="share-icon el-icon-share"
							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">
				<step-backward-outlined
					class="operate-icon"
					title="上一个（按左方向键）"
					@click="handleChangeAudioIndex('pre')"
				/>
				<play-circle-outlined
					v-if="!isPlay"
					class="operate-icon"
					title="播放（按空格键）"
					@click="handleClickPlayIcon"
				/>
				<pause-circle-outlined v-else class="operate-icon" title="暂停（按空格键）" @click="handleClickPauseIcon" />
				<step-forward-outlined
					class="operate-icon"
					title="下一个（按右方向键）"
					@click="handleChangeAudioIndex('next')"
				/>
				<a-slider
					class="progress-bar control-item"
					v-model:value="currentTime"
					:step="progressStep"
					:max="audioInfo.duration"
					:tooltip-visible="true"
					: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">
				<i
					class="operate-icon cycle-type iconfont"
					:class="cycleTypeMap[String(cycleType)].icon"
					:title="cycleTypeMap[String(cycleType)].text"
					@click="handleChangeCycleType"
				></i>
				<a
					class="operate-icon download-link"
					:href="$file.getDownloadFilePath(activeFileObj)"
					target="_blank"
					title="下载"
				>
					<i class="download-icon el-icon-download"></i>
				</a>
				<i
					class="operate-icon share-icon el-icon-share"
					title="分享"
					@click.stop="
						$openDialog.shareFile({
							fileInfo: [
								{
									userFileId: item.userFileId
								}
							]
						})
					"
				></i>
				<i
					class="operate-icon volume-icon control-item iconfont"
					:class="volume === 0 ? 'icon-jingyin01' : 'icon-yinliang101'"
					@click="handleClickVolumeIcon"
				></i>
				<el-slider
					class="volume-bar control-item"
					v-model="volume"
					:step="0.01"
					:max="1"
					:format-tooltip="(val) => Math.floor(val * 100)"
					height="100px"
					title="可按上下方向键调节音量"
					@input="handleChangeVolumeBar"
				></el-slider>
			</div>
		</div>
	</a-modal>
</template>

<script setup>
	import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
	import musicPng from '@/assets/images/myResource/file/file_music.png'
	import { message } from 'ant-design-vue'
	import {
		QuestionCircleOutlined,
		CloseOutlined,
		StepBackwardOutlined,
		StepForwardOutlined,
		PlayCircleOutlined,
		PauseCircleOutlined
	} from '@ant-design/icons-vue'
	import { getFileDetail } from '@/api/myResource/file'
	import { Base64 } from 'js-base64'

	const props = defineProps({
		visible: {
			type: Boolean,
			default: false
		},
		audioList: {
			type: Array,
			default: () => []
		},
		defaultIndex: {
			type: Number,
			default: 0
		},
		callback: {
			type: Function,
			default: () => {}
		}
	})

	const emit = defineEmits(['update:visible'])

	const visible = ref(props.visible)
	const activeIndex = ref(0)
	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)

	const activeFileObj = computed(() => {
		const res = props.audioList.length ? props.audioList[activeIndex.value] : {}
		return res
	})

	const audioElement = computed(() => {
		return refs.audioRef
	})

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

	const progressStep = computed(() => {
		return audioInfo.value.duration / 100
	})

	watch(
		() => props.visible,
		(newValue) => {
			visible.value = newValue
			if (newValue) {
				activeIndex.value = props.defaultIndex
				getFileDetailData()
				document.addEventListener('keyup', handleAddKeyupEvent)
			} else {
				document.removeEventListener('keyup', handleAddKeyupEvent)
			}
		}
	)

	watch(activeIndex, () => {
		getFileDetailData()
	})

	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
					}
					// Base64 解码为 lrc 格式的歌词文件
					let lyricsStr = Base64.decode(audioInfo.value.lyrics)
					if (lyricsStr.includes('[offset:0]')) {
						// 有歌词，从标志位 [offset:0] 下一行开始截取
						lyricsStr = lyricsStr.split('[offset:0]\n')[1]
					}
					lyricsList.value = lyricsStr
						.split('\n')
						.map((item) => {
							const line = item.split('[')[1].split(']')
							return {
								time: line[0], //  当前行歌词开始播放的秒数
								text: line[1] //  当前歌词文本
							}
						})
						.filter((item) => 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
						}
					})
					// 当切换完歌曲时，歌词重新滚动到顶部
					refs.lyricsListRef.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()
	}

	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
		}`
	}

	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) {
						// 平滑滚动
						refs.lyricsListRef.scrollTo({
							top: refs.lyricsLineRef[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++
		}
	}

	function handleClickPlayIcon() {
		isPlay.value = true
		audioElement.value.play()
	}

	function handleClickPauseIcon() {
		isPlay.value = false
		audioElement.value.pause()
	}

	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 activeIndex = 0
					do {
						activeIndex = Math.floor(Math.random() * (props.audioList.length - 1)) + 1
					} while (activeIndex === activeIndex.value)
					activeIndex.value = activeIndex
					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(volume) {
		audioElement.value.volume = Number(volume.toFixed(1))
	}

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

	onMounted(() => {
		visible.value = props.visible
		activeIndex.value = props.defaultIndex
	})

	onUnmounted(() => {
		document.removeEventListener('keyup', handleAddKeyupEvent)
	})

	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;
					// >>> .el-slider__runway {
					//   height: 2px;
					//   .el-slider__button-wrapper {
					//     top: -17px;
					//     .el-slider__button {
					//       border: none;
					//     }
					//   }
					//   .el-slider__bar {
					//     height: 100%;
					//     background: @Warning;
					//   }
					// }
				}
			}

			.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;
					// >>> .el-slider__runway {
					//   height: 2px;
					//   .el-slider__button-wrapper {
					//     top: -19px;
					//     .el-slider__button {
					//       border: none;
					//     }
					//   }
					//   .el-slider__bar {
					//     height: 100%;
					//     background: @Warning;
					//   }
					// }
				}
			}
		}
	}
</style>
