<template>
	<view class="guid-full-screen-mask" @touchmove.prevent.stop v-if="showGuide">
		<view class="element-highlight-container" :style="[highlightStyle]"></view>
		<view class="step-tips-arrow-container" :style="[tipsArrowPositionStyle]"
			:class="stepTipsShow ? '' : 'step-tips-hide'">
			<slot name="arrow" :scope="currentEleConfig" v-if="customArrow">
			</slot>
			<view class="step-tips-arrow" :style="[tipsArrowStyle]" v-else></view>
		</view>
		<view class="step-tips" :class="stepTipsShow ? '' : 'step-tips-hide'" :style="[tipsWidthStyle,tipsStyle]">
			<scroll-view class="step-tips-content" :style="[tipsContentStyle]" scroll-y>
				<slot name="tips" :scope="currentStepExportInfo">
					<view>
						<rich-text :nodes="currentEleConfig.tips || ''"></rich-text>
					</view>
				</slot>
			</scroll-view>
			<slot name="btns" v-if="!isOuterBtn" :scope="currentStepExportInfo">
				<view class="step-tips-bottom">
					<view v-if="showSkip" class="step-tips-jump" :style="[skipStyle]" @click="beforeSkip">{{
						skipText
					}}</view>
					<view class="step-tips-btn" :style="[nextStyle]" @click="beforeNext">{{ nextText }}</view>
				</view>
			</slot>
		</view>
		<view class="step-tips-btn-outer-container" v-if="isOuterBtn" :class="stepTipsShow ? '' : 'step-tips-hide'"
			:style="[outerBtnStyle]">
			<slot name="btns" :scope="currentStepExportInfo">
				<view class="step-tips-btn-outer">
					<view v-if="showSkip" class="step-tips-btn-outer-jump" :style="[skipStyle]" @click="beforeSkip">{{
            skipText
          }}</view>
					<view class="step-tips-btn-outer-next" :style="[nextStyle]" @click="beforeNext">{{
            nextText
          }}</view>
				</view>
			</slot>
		</view>
	</view>
</template>
<!--
  新手引导组件
-->
<script>
	import guidanceMixins from './mixins/index.js'
	export default {
		mixins: guidanceMixins,
		props: {
			// 总体配置
			baseConfig: {
				type: Object,
				default: () => ({})
			},
			// 步骤信息
			steps: {
				type: Array,
				default: () => []
			},
		},
		data() {
			return {
				// 系统信息
				systemInfo: uni.getSystemInfoSync(),
				// 引导组件实例
				instance: null,
				// 是否显示引导
				showGuide: false,
				// 当前步骤
				currentStep: -1,
				// 当前步骤元素信息
				currentEleInfo: null,
				// 实际渲染后的元素信息
				currentEleAfterRenderInfo: null,
				// 提示框展示位置
				tipsPosition: '',
				// 显示步骤提示弹框
				stepTipsShow: false,
				
				//存储临时配置，用于更改配置
				tempBaseConfig: null,
				// 步骤列表
				stepList: null
			}
		},
		computed: {
			// 合并后的总体配置
			realBaseConfig() {
				return {
					scrollTime: 100,
					customArrow: false,
					autoRotateArrow: true,
					showSkip: true,
					tipMaxWidth: '70vw',
					...(this.tempBaseConfig || {}),
				}
			},
			// 当前步骤元素配置
			currentEleConfig() {
				return this.stepList[this.currentStep] || {}
			},
			// 高亮元素样式
			highlightStyle() {
				if (!this.currentEleInfo) return {}
				const {
					top,
					left,
					width,
					height
				} = this.currentEleInfo
				const {
					style,
					offset
				} = this.currentEleConfig

				return {
					'--bgcolor': 'rgba(0, 0, 0, 0.5)',
					...((this.realBaseConfig && this.realBaseConfig.highligthStyle) || {}),
					...(style || {}),
					top: top + ((offset && offset.top) || 0) + 'px',
					left: left + ((offset && offset.left) || 0) + 'px',
					width: width + 'px',
					height: height + 'px',
				}
			},
			// 高亮元素信息
			highlightElementInfo() {
				const _highlightStyle = this.highlightStyle
				const needFields = ['top', 'left', 'width', 'height']
				const info = {}
				for (const field of needFields) {
					info[field] = _highlightStyle[field] ? parseFloat(_highlightStyle[field].split('px')[0]) : 0
				}
				return info
			},
			// 提示框最大宽度
			tipsWidthStyle() {
				return {
					'--max-width': this.realBaseConfig.tipMaxWidth || '70vw'
				}
			},
			// 提示框内容样式（最大高度）
			tipsContentStyle() {
				if (!this.tipsPosition) return {};
				
				const { top, height } = this.highlightElementInfo || {}
				const { tipContentMaxHeight } = this.realBaseConfig || {}
				const { tipContentMaxHeight: currentTipContentMaxHeight } = this.currentEleConfig || {}
				const defaultMaxHeight = this.tipsPosition == 'bottom' 
					? `calc(100vh - ${top || 0}px - ${height || 0}px - 30rpx - 60px)`
					: `calc(${top || 0}px - 30rpx - 60px)`
				const maxHeight = currentTipContentMaxHeight || tipContentMaxHeight || defaultMaxHeight
				
				return {
					'max-height': maxHeight
				}
			},
			// 当前步骤暴露出去的信息
			currentStepExportInfo() { 
				return {
          currentEle: this.currentEleConfig,
          highlightEle: this.highlightElementInfo,
          tipsEle: this.tipsElementInfo,
          outerBtnEle: this.outerBtnDomInfo,
          tipsPosition:this.tipsPosition,
          currentStep:this.currentStep,
        }
			}
		},
		watch: {
			steps: {
				handler(val) {
					this.stepList = val || []
				},
				immediate: true
			},
			baseConfig: {
				handler(val) {
					this.tempBaseConfig = val || {}
				},
				immediate: true
			}
		},
		created() {
			this.instance = this
			this.systemInfo = uni.getSystemInfoSync()
		},
		methods: {
			// 设置配置，主要用于小程序参数中不能直接传递函数的问题
			setConfig(steps, baseConfig = {}) {
				this.stepList = steps || []
				this.tempBaseConfig = baseConfig || {}
			},
			// 获取配置
			getBaseConfigByKey(key, isBoolean = false) {
				const stepConf = this.currentEleConfig || {}
				const baseConf = this.realBaseConfig || {}
				const val = stepConf[key]
				if (!isBoolean) {
					return val || baseConf[key] || false
				}
				if (typeof val != 'undefined' && typeof val != null) {
					return val
				}
				return baseConf[key] || false
			},
			// 延迟
			sleep(time) {
				return new Promise((resolve) => {
					setTimeout(() => {
						resolve(null)
					}, time)
				})
			},
			// 获取步骤元素信息
			async getHighlightElementInfo(step = 0) {
				if (!this.stepList[step]) return null
				const {
					el
				} = this.stepList[step]
				let info = await this.getDomInfo(el, this.getContext(this.stepList[step], this.instance), true)
				return info
			},
			// 检查高亮元素是否全部在可视区域内，如果不在，则进行页面滚动
			async checkHighlightElementInScreen() {
				const scrollTime = this.realBaseConfig.scrollTime
				let info = await this.getHighlightElementInfo(this.currentStep)
				// 窗口高度，h5 取屏幕高度，其他取窗口高度
				let wHeight = this.systemInfo.windowHeight
				// #ifdef H5
				wHeight = this.systemInfo.screenHeight
				// #endif
				const {
					top,
					bottom,
					scrollTop
				} = info

				let isScroll = false
				const gutter = 20
				if (top - scrollTop < 0) {
					uni.pageScrollTo({
						scrollTop: top - scrollTop - gutter,
						duration: scrollTime
					})
					isScroll = true
				} else if (bottom > wHeight) {
					uni.pageScrollTo({
						scrollTop: bottom - wHeight + scrollTop + gutter,
						duration: scrollTime
					})
					isScroll = true
				}
				if (isScroll) {
					await this.sleep(scrollTime * 1.5)
					info = await this.getHighlightElementInfo(this.currentStep)
				}
				this.currentEleInfo = info
			},
			// 判断提示框展示位置（上/下）
			async checkTipsPosition() {
				const res = await this.getDomInfo('.element-highlight-container', this.instance)
				this.currentEleAfterRenderInfo = res
				if (res.top + res.height / 2 > this.systemInfo.screenHeight / 2) {
					this.tipsPosition = 'top'
				} else {
					this.tipsPosition = 'bottom'
				}
			},
			// 计算各元素信息
			async calcElementsInfo() {
				await this.sleep(300)
				this.checkTipsPosition()
				await this.getTipsDomInfo()
				this.customArrow && this.getArrowDomInfo()
				this.isOuterBtn && this.getOuterBtnDomInfo()
				this.stepTipsShow = true
			},
			// 检查下一步
			checkStep(currentStep) {
				const step = currentStep < this.stepList.length - 1 ? currentStep : -1
				if (step == -1) {
					this.beforeFinished()
					return step
				}
				return step
			}, 
			// 下一步
			async next() {
				const step = this.checkStep(this.currentStep)
				if(step == -1) return;
				this.stepChange(step + 1)
				await this.checkHighlightElementInScreen()
				this.calcElementsInfo()
			},
			// 跳转步骤
			skipStep(stepIndex = undefined, config = {}) {
				// 不传或传空，默认为当前步骤的下一步
				if(stepIndex === '' || stepIndex == null || typeof stepIndex == 'undefined'){
					stepIndex = this.currentStep + 1
				}
				let formatStepIndex = Number(stepIndex)
				if(isNaN(formatStepIndex)) {
					console.error(`请传入正确的步骤索引：${stepIndex}`)
					return
				}
				formatStepIndex = formatStepIndex >= 0 ? formatStepIndex : -1
				if(formatStepIndex == -1) {
					console.error(`步骤索引应大于等于0：${stepIndex}`)
					return
				}
				
				const {globalBeforeNextDisabled, beforeNextHandleDisabled} = config || {}
				// 下一步函数
				const next = () => {
					if(this.checkStep(formatStepIndex) == -1) return;
					this.start(formatStepIndex)
				}
				// 执行全局守卫函数
				const handleStepBeforeNext = () => {
					this.handleStepBeforeNext({
						to: this.stepList[formatStepIndex] || null,
						next: next,
					}, next)
				}
				// 执行当前步骤守卫函数
				const handleBeforeNext = () => {
					this.beforeNext({
						to: this.stepList[formatStepIndex] || null,
						next: next,
					}, next)
				}
				
				// 不执行前置守卫
				if (globalBeforeNextDisabled && beforeNextHandleDisabled) {
					next()
				} else if (globalBeforeNextDisabled && !beforeNextHandleDisabled) {
					// 不执行全局前置守卫，执行当前步骤前置守卫
					handleStepBeforeNext()
				} else if (!globalBeforeNextDisabled && beforeNextHandleDisabled) {
					// 执行全局前置守卫，不执行当前步骤前置守卫
					handleBeforeNext()
				} else {
					// 都执行
					this.handleStepBeforeNext({
						to: this.stepList[formatStepIndex] || null,
						next: handleBeforeNext,
					}, handleBeforeNext)
				}
			},
			// 完成引导
			finish() {
				this.beforeFinished()
			},
			handleFinished() {
				this.showGuide = false
				this.reset()
				this.$emit('finished')
			},
			// 跳过
			skip() {
				this.beforeFinished()
			},
			handleSkip() {
				this.showGuide = false
				this.reset()
				this.$emit('skiped')
			},
			// 步骤改变
			stepChange(step = 0) {
				this.stepTipsShow = false
				this.currentStep = step
				this.$emit('next', step, this.currentStepExportInfo)
				// this.afterNext()
			},
			// 开始引导
			async start(step = 0) {
				this.showGuide = true
				this.stepChange(step)
				await this.checkHighlightElementInScreen()
				this.calcElementsInfo()
			},
			// 重置
			reset() {
				this.currentStep = 0
				this.stepTipsShow = false
				this.currentEleInfo = undefined
				this.currentEleAfterRenderInfo = undefined
			}
		}
	}
</script>


<style scoped lang="scss">
	.guid-full-screen-mask {
		position: fixed;
		top: 0;
		left: 0;
		width: 100vw;
		height: 100vh;
		z-index: 999999;
		animation: fadeIn 0.3s;

		.element-highlight-container {
			position: absolute;
			box-shadow: 0px 0px 0px 5000px var(--bgcolor);
			border-radius: 16rpx;
			box-sizing: content-box;
			transition: all 0.3s;
		}
	}

	.step-tips-arrow-container {
		position: absolute;
		z-index: 10000;
		top: 0;
		left: 100vw;

		// transition: opacity 0.3s;
		.step-tips-arrow {
			height: 0;
			width: 0;
			border-right: var(--arrow-width) solid transparent;
			border-left: var(--arrow-width) solid transparent;
		}
	}

	.step-tips {
		background: #fff;
		// min-width: 50px;
		max-width: var(--max-width);
		animation: fadeIn 0.3s;
		// box-shadow: 0px 2px 9px 0px rgba(0, 0, 0, 0.1);
		position: absolute;
		top: 0;
		left: -100vw;
		z-index: 10001;
		padding: 15rpx 20rpx;
		font-size: 28rpx;
		border-radius: 12rpx;
		box-sizing: border-box;

		// transition: opacity 0.3s;
		.step-tips-bottom {
			font-size: 26rpx;
			margin-top: 24rpx;
			display: flex;
			justify-content: flex-end;
			align-items: center;

			.step-tips-jump {
				margin-right: 24rpx;
			}

			.step-tips-btn {
				padding: 8rpx 20rpx;
				background: #1cbbb4;
				color: #fff;
				border-radius: 8rpx;
				text-align: center;
			}
		}
	}

	.step-tips-hide {
		opacity: 0 !important;
	}

	.step-tips-btn-outer-container {
		position: absolute;

		.step-tips-btn-outer {
			font-size: 26rpx;
			display: flex;
			justify-content: flex-end;
			align-items: center;
		}

		.step-tips-btn-outer-jump {
			color: #fff;
			margin-right: 24rpx;
		}

		.step-tips-btn-outer-next {
			font-size: 28rpx;
			min-width: 210rpx;
			padding: 16rpx 24rpx;
			background: #1cbbb4;
			color: #fff;
			border-radius: 8rpx;
			text-align: center;
		}
	}

	@keyframes fadeIn {
		0% {
			opacity: 0;
		}

		100% {
			opacity: 1;
		}
	}
</style>