<template>
  <view v-if="show" @click="focusTap" @touchmove.stop.prevent :style="{ '--path': path, '--duration': duration + 'ms' }"
    class="clip-container">
    <view @click.stop="maskTap" class="clip-box"></view>
    <view @click.stop class="guide-box animate__animated"
      :style="[msgStyles]">
      <view v-if="$slots.message" class="">
        <slot name="message" :msg="list[index].msg"></slot>
      </view>
      <view v-else class="msg-label">
        {{ list[index].msg }}
      </view>

      <view class="step-box">
        <view @click="lastStep" class="step-btn" :class="{ 'opacity-0': index === 0 }">
          上一步
        </view>
        <view @click="nextStep" class="step-btn next-btn">
          下一步
        </view>
        <view @click="skipStep" class="step-btn">
          跳过
        </view>
      </view>
    </view>
  </view>
</template>
<script>
const path_default = `
		polygon(
			0% 0%, 
			0% 0%, 
			100% 0%, 
			100% 100%, 
			0% 100%, 
			0% 0%, 
			0% 0%, 
			0% 100%, 
			100% 100%, 
			100% 0%
		)
	`

/**
 * guide 引导弹窗
 * @description 引导组件，用于教学提示、用户操作引导等内容，支持自定义组件节点自动聚焦和手动设置相对位置聚焦。组件只提供容器，内部内容由用户自定义
 * @tutorial //git地址
 * @property {Boolean}			show				是否展示弹窗 (默认 false )
 * @property {Number}			index				当前步骤索引(默认 0 )
 * @property {String | Number}	duration			动画时长
 * @property {String}			unit				换算单位
 * @property {Array}			list				步骤列表
 * @property {Object}			{
 * 								@property{String}	ref					要聚焦的子组件的ref
 * 								@property{String}	target				当前组件/子组件中的选择器标识
 * 								@property{String}	position			提示框展示位置'top'/'bottom'
 * 								@property{String}	msg					提示框文字
 * 								@property{String}	msgStyles			提示框自定义样式
 * 								@property{String}	width				自定义聚焦范围的宽度
 * 								@property{String}	height				自定义聚焦范围的高度
 * 								@property{String}	left				自定义聚焦范围的左侧偏移量 left/right仅生效一个	使用自定义聚焦后ref、target属性将失效
 * 								@property{String}	right				自定义聚焦范围的右侧偏移量 left/right仅生效一个	使用自定义聚焦后ref、target属性将失效
 * 								@property{String}	top					自定义聚焦范围的上侧偏移量 top/bottom仅生效一个	使用自定义聚焦后ref、target属性将失效
 * 								@property{String}	bottom				自定义聚焦范围的下侧偏移量 top/bottom仅生效一个	使用自定义聚焦后ref、target属性将失效
                }					list步骤列表内部参数说明
 * @event {Function} open   	弹出层打开
 * @event {Function} close  	弹出层收起
 * @event {Function} focus  	聚焦区域点击事件
 * @event {Function} mask		遮罩区域点击事件
 * @event {Function} next   	执行下一步
 * @event {Function} last   	执行上一步
 * @event {Function} skip   	跳过所有步骤
 * @event {Function} finish 	结束引导
 * @event {Function} getQuery	获取兄弟组件布局查询对象
 * @example <popup-guide :show="guideShow" :list="guideList" :index="guideIndex" @next="guideNext" @last="guideLast" @skip="guideSkip" @finish="guideFinish" ></popup-guide>
 */
export default {
  data() {
    return {
      path: path_default,
      msgStyles: {
        opacity: '0' // 初始时隐藏，避免闪烁
      },
      isFirstShow: true // 标记是否是第一次显示
    }
  },
  props: {
    // 是否展示
    show: {
      default: false
    },
    // 步骤列表
    list: {
      default: []
    },
    // 当前步骤索引
    index: {
      default: 0
    },
    // 动画时长
    duration: {
      default: 500
    },
    // 换算单位
    unit: {
      default: 'rpx'
    }
  },
  emits: ['open', 'close', 'focus', 'mask', 'next', 'last', 'skip', 'finish', 'getQuery'],
  watch: {
    index: {
      handler(newVal) {
        if (newVal != undefined) {
          this.getCurrentPath()
        }
      }
    },
    show: {
      handler(newVal) {
        if (newVal == true) {
          this.isFirstShow = true; // 重置为第一次显示
          this.getCurrentPath()
          this.$emit('open', this.callBackData())
        }
        if (newVal == false) {
          this.$emit('close', this.callBackData())
        }
      }
    }
  },
  methods: {
    pathInit() {
      this.path = path_default
    },
    testNumber(value) {
      return /^[\+-]?(\d+\.?\d*|\.\d+|\d\.\d+e\+\d+)$/.test(value)
    },
    addUnit(value = '', unit = this.unit) {
      value = String(value)
      return this.testNumber(value) ? `${value}${unit}` : value
    },
    isEmpty(val) {
      return val === null || val === undefined || val === ''
    },
    async getCurrentPath() {
      let maxWidth
      // 优化6: 使用requestAnimationFrame确保在正确的时机执行动画
      await this.$nextTick(() => { })

      let {
        ref,
        target,

        width,
        height,

        top,
        left,
        right,
        bottom,

        gap,
        position,
        msgStyles

      } = this.list[this.index]
      let x1, x2, y1, y2

      if (target) {

        uni.getSystemInfo({
          success(res) {
            // #ifndef H5
            maxWidth = res.screenWidth
            // #endif
            // #ifdef H5
            maxWidth = res.windowWidth
            // #endif
          }
        })
        // console.log(maxWidth,'maxWidth')

        const targetDom = await new Promise((resolve, reject) => {
          if (ref) {
            this.$emit('getQuery', function () {
              uni.createSelectorQuery()
                .in(this.$refs[ref])
                .select(target)
                .boundingClientRect(data => {
                  resolve(data)
                }).exec()
            })
          } else {
            uni.createSelectorQuery()
              .select(target)
              .boundingClientRect(data => {
                resolve(data)
              }).exec()
          }
        })
        // console.log(targetDom,'targetDom')
        width = targetDom.width
        height = targetDom.height
        top = targetDom.top
        left = targetDom.left
        right = maxWidth - targetDom.right
        //+1像素修正元素处于屏幕正中间时的msg相对位置
        if (left > right + 1) {
          left = ''
        } else {
          right = ''
        }
        bottom = targetDom.bottom
        // console.log(left,'left',right,'right')


        x1 = !this.isEmpty(left) ? `calc(${this.addUnit(left, 'px')} + ${this.addUnit(width, 'px')})` : `calc(100% - ${this.addUnit(right, 'px')})`
        x2 = !this.isEmpty(left) ? `${this.addUnit(left, 'px')}` : `calc(100% - ${this.addUnit(right, 'px')} - ${this.addUnit(width, 'px')})`

        y1 = !this.isEmpty(top) ? `${this.addUnit(top, 'px')}` : `calc(100% - ${this.addUnit(bottom, 'px')} - ${this.addUnit(height, 'px')})`
        y2 = !this.isEmpty(top) ? `calc(${this.addUnit(top, 'px')} + ${this.addUnit(height, 'px')})` : `calc(100% - ${this.addUnit(bottom, 'px')})`

      } else {
        x1 = !this.isEmpty(left) ? `calc(${this.addUnit(left)} + ${this.addUnit(width)})` : `calc(100% - ${this.addUnit(right)})`
        x2 = !this.isEmpty(left) ? `${this.addUnit(left)}` : `calc(100% - ${this.addUnit(right)} - ${this.addUnit(width)})`

        y1 = !this.isEmpty(top) ? `${this.addUnit(top)}` : `calc(100% - ${this.addUnit(bottom)} - ${this.addUnit(height)})`
        y2 = !this.isEmpty(top) ? `calc(${this.addUnit(top)} + ${this.addUnit(height)})` : `calc(100% - ${this.addUnit(bottom)})`
      }

      // 优化7: 使用requestAnimationFrame替代setTimeout，确保动画在浏览器重绘前执行
      await new Promise((resolve) => {
        // #ifdef H5
        if (typeof requestAnimationFrame !== 'undefined') {
          requestAnimationFrame(() => {
            requestAnimationFrame(() => {
              resolve()
            })
          })
        } else {
          setTimeout(() => {
            resolve()
          }, 50)
        }
        // #endif
        // #ifndef H5
        setTimeout(() => {
          resolve()
        }, 50)
        // #endif
      })

      this.path = `
					polygon(
						0% 0%,
						0% ${y1},
						${x1} ${y1},
						${x1} ${y2},
						${x2} ${y2},
						${x2} ${y1},
						0% ${y1},
						0% 100%,
						100% 100%, 
						100% 0%
					)
				`
      // console.log(this.path,'path.value')
      //设置msg样式
      const msgDom = await new Promise((resolve, reject) => {
        uni.createSelectorQuery().in(this).select('.guide-box').boundingClientRect(data => {
          resolve(data)
        }).exec()
      })

      let animationName
      gap = gap || 20
      position = position || 'top'

      if (target) {
        switch (position) {
          case 'top':
            if (!this.isEmpty(top)) {
              top = `calc(${this.addUnit(top, 'px')} - ${this.addUnit(gap)} - ${this.addUnit(msgDom.height, 'px')})`
            } else {
              top = `calc(100% - ${this.addUnit(bottom, 'px')} - ${this.addUnit(height, 'px')} - ${this.addUnit(gap)} - ${this.addUnit(msgDom.height, 'px')})`
            }
            animationName = 'backInDown'
            break;
          case 'bottom':
            if (!this.isEmpty(top)) {
              top = `calc(${this.addUnit(top, 'px')} + ${this.addUnit(height, 'px')} + ${this.addUnit(gap)})`
            } else {
              top = `calc(100% - ${this.addUnit(bottom, 'px')} + ${this.addUnit(gap)} + ${this.addUnit(msgDom.height, 'px')})`
            }
            animationName = 'backInUp'
            break;
          default:
            break;
        }

      } else {
        switch (position) {
          case 'top':
            if (top) {
              top = `calc(${this.addUnit(top)} - ${this.addUnit(gap)} - ${this.addUnit(msgDom.height, 'px')})`
            } else {
              top = `calc(100% - ${this.addUnit(bottom)} - ${this.addUnit(height)} - ${this.addUnit(gap)} - ${this.addUnit(msgDom.height, 'px')})`
            }
            animationName = 'backInDown'
            break;
          case 'bottom':
            if (top) {
              top = `calc(${this.addUnit(top)} + ${this.addUnit(height)} + ${this.addUnit(gap)})`
            } else {
              top = `calc(100% - ${this.addUnit(bottom)} + ${this.addUnit(gap)} + ${this.addUnit(msgDom.height, 'px')})`
            }
            animationName = 'backInUp'
            break;
          default:
            break;
        }

      }
      let boxLeft = left;
      let boxRight = right;
      if (!boxLeft) {
        boxLeft = maxWidth - right - msgDom.width
      }
      if (boxLeft < 10) {
        boxLeft = 10; // 和边界留出10px的距离
      }
      if (!boxRight) {
        boxRight = maxWidth - left - msgDom.width
      }
      if (boxRight > maxWidth - 10) {
        boxRight = maxWidth - 10; // 和边界留出10px的距离
      }
      left = target ? this.addUnit(left, 'px') : this.addUnit(left)
      right = target ? this.addUnit(right, 'px') : this.addUnit(right)
      
      // 优化8: 只在第一次显示时需要淡入动画，切换步骤时直接显示
      if (this.isFirstShow) {
        // 第一次显示：初始设置opacity为0，等待clip-box动画完成后再淡入
        this.msgStyles = {
          'top': top,
          'left': this.addUnit(boxLeft, 'px'),
          'right': this.addUnit(boxRight, 'px'),
          'opacity': '0', // 初始时隐藏，等待clip-box动画完成
          'transition': `top ${this.duration}ms, left ${this.duration}ms, right ${this.duration}ms, bottom ${this.duration}ms, opacity 200ms`,
          // 不使用位移动画，避免从顶部/底部滑入的效果
          'animation-name': 'none',
          '-webkit-animation-name': 'none',

          ...msgStyles
        }
        // 等待clip-box聚焦动画完成后，再淡入显示guide-box
        setTimeout(() => {
          this.msgStyles = {
            ...this.msgStyles,
            'opacity': '1'
          };
          this.isFirstShow = false; // 标记已经显示过
        }, this.duration);
      } else {
        // 切换步骤：直接显示，只改变位置
        this.msgStyles = {
          'top': top,
          'left': this.addUnit(boxLeft, 'px'),
          'right': this.addUnit(boxRight, 'px'),
          'opacity': '1', // 切换步骤时保持显示
          'transition': `top ${this.duration}ms, left ${this.duration}ms, right ${this.duration}ms, bottom ${this.duration}ms, opacity 200ms`,
          'animation-name': 'none',
          '-webkit-animation-name': 'none',

          ...msgStyles
        }
      }
    },
    nextStep() {
      if (this.list[this.index + 1]) {
        this.$emit('next', this.callBackData())
        this.$emit('update:index', this.index + 1)
      } else {
        // 最后一步，隐藏guide-box
        this.msgStyles = {
          ...this.msgStyles,
          'animation-name': 'none',
          '-webkit-animation-name': 'none'
        };
        // 等待浏览器重绘后，设置opacity为0触发过渡动画
        this.$nextTick(() => {
          // 在微信小程序中，需要额外的延迟确保视图更新
          setTimeout(() => {
            this.msgStyles = {
              ...this.msgStyles,
              'opacity': '0'
            };
            // 等待guide-box淡出完成（200ms）后再执行clip-box动画
            setTimeout(() => {
              this.pathInit()
              setTimeout(() => {
                this.isFirstShow = true; // 重置标志，下次打开时重新淡入
                this.$emit('finish', this.callBackData())
                this.$emit('update:show', false)
              }, this.duration)
            }, 200);
          }, 20);
        });
      }
    },
    lastStep() {
      this.$emit('update:index', this.index - 1)
      this.$emit('last', this.callBackData())
    },
    skipStep() {
      // 跳过引导，隐藏guide-box
      this.msgStyles = {
        ...this.msgStyles,
        'animation-name': 'none',
        '-webkit-animation-name': 'none'
      };
      // 等待浏览器重绘后，设置opacity为0触发过渡动画
      this.$nextTick(() => {
        // 在微信小程序中，需要额外的延迟确保视图更新
        setTimeout(() => {
          this.msgStyles = {
            ...this.msgStyles,
            'opacity': '0'
          };
          // 等待guide-box淡出完成（200ms）后再执行clip-box动画
          setTimeout(() => {
            this.pathInit()
            setTimeout(() => {
              this.isFirstShow = true; // 重置标志，下次打开时重新淡入
              this.$emit('update:show', false)
              this.$emit('skip', this.callBackData())
            }, this.duration)
          }, 200);
        }, 20);
      });
    },
    focusTap() {
      this.$emit('focus', this.callBackData())
    },
    maskTap() {
      this.$emit('mask', this.callBackData())
    },
    callBackData() {
      return {
        index: this.index,
        value: this.list[this.index]
      }
    }
  },


}


</script>

<style lang="scss">
.shadow {
  box-shadow: 2rpx 2rpx 12rpx var(--shadow);
}

.clip-container {
  --path: '';
  --duration: '';
  --color: #31A0FC;

  position: absolute;
  /* #ifdef H5 */
  width: 750rpx;
  /* #endif */
  /* #ifndef H5 */
  width: 100%;
  /* #endif */
  height: 100vh;
  top: 0;
  left: 0;
  z-index: 9996;
}


.clip-box {

  box-sizing: border-box;
  width: 100%;
  height: 100vh;
  /* 优化1: 只对clip-path做过渡，避免transition: all */
  transition: clip-path var(--duration);

  /* 优化2: 提示浏览器即将变化的属性，提前优化 */
  will-change: clip-path;

  clip-path: var(--path);

  background-color: rgba(0, 0, 0, 0.4);

  /* 优化3: 强制开启硬件加速（如果浏览器支持） */
  transform: translateZ(0);
  -webkit-transform: translateZ(0);

}

.guide-box {
  // width: 50%;
  width: fit-content;
  max-width: 80%;
  /* 优化4: 明确指定需要过渡的属性，避免all */
  /* opacity使用更短的过渡时间(200ms)，确保在clip-box动画前快速隐藏 */
  transition: left var(--duration), right var(--duration), top var(--duration), bottom var(--duration), opacity 200ms;
  position: absolute;
  color: var(--color);
  background-color: #fff;
  border-radius: 12rpx;
  box-shadow: 2rpx 2rpx 2rpx #fff;
  box-sizing: border-box;
  padding: 20rpx;

  display: flex;
  flex-direction: column;

  /* 优化5: 开启硬件加速 */
  transform: translateZ(0);
  -webkit-transform: translateZ(0);
  will-change: top, left, right, bottom, opacity;
}

.msg-label {
  font-size: 28rpx;
  white-space: pre;
}

.step-box {
  margin-top: auto;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding: 20rpx 0 0 0;
  white-space: nowrap;
}

.step-btn {
  margin-left: 20rpx;
  box-shadow: 2rpx 2rpx 12rpx #ddd;
  padding: 12rpx 30rpx;
  border-radius: 4px;
  font-size: 24rpx;
  background-color: white;
}

.next-btn {
  color: white;
  background-color: var(--color);
}

@-webkit-keyframes backInDown {
  0% {
    -webkit-transform: translateY(-1200px);
    transform: translateY(-1200px);
    opacity: 0.7;
  }

  80% {
    -webkit-transform: translateY(0px);
    transform: translateY(0px);
    opacity: 0.7;
  }

  100% {
    -webkit-transform: scale(1);
    transform: scale(1);
    opacity: 1;
  }
}


@keyframes backInDown {
  0% {
    -webkit-transform: translateY(-1200px);
    transform: translateY(-1200px);
    opacity: 0.7;
  }

  80% {
    -webkit-transform: translateY(0px);
    transform: translateY(0px);
    opacity: 0.7;
  }

  100% {
    -webkit-transform: scale(1);
    transform: scale(1);
    opacity: 1;
  }
}

@-webkit-keyframes backInUp {
  0% {
    -webkit-transform: translateY(1200px);
    transform: translateY(1200px);
    opacity: 0.7;
  }

  80% {
    -webkit-transform: translateY(0px);
    transform: translateY(0px);
    opacity: 0.7;
  }

  100% {
    -webkit-transform: scale(1);
    transform: scale(1);
    opacity: 1;
  }
}

@keyframes backInUp {
  0% {
    -webkit-transform: translateY(1200px);
    transform: translateY(1200px);
    opacity: 0.7;
  }

  80% {
    -webkit-transform: translateY(0px);
    transform: translateY(0px);
    opacity: 0.7;
  }

  100% {
    -webkit-transform: scale(1);
    transform: scale(1);
    opacity: 1;
  }
}

.animate__animated {
  -webkit-animation-duration: var(--duration);
  animation-duration: var(--duration);
  -webkit-animation-duration: var(--duration);
  animation-duration: var(--duration);
  -webkit-animation-fill-mode: both;
  animation-fill-mode: both;
}
</style>