<template>
	<!-- #ifndef APP-NVUE -->
	<view v-show="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick">
		<slot></slot>
	</view>
	<!-- #endif -->
	<!-- #ifdef APP-NVUE -->
	<view v-if="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick">
		<slot></slot>
	</view>
	<!-- #endif -->
</template>

<script>
	import { createAnimation } from './createAnimation'

	/**
	 * Transition 过渡动画
	 * @description 简单过渡动画组件
	 * @tutorial https://ext.dcloud.net.cn/plugin?id=985
	 * @property {Boolean} show = [false|true] 控制组件显示或隐藏
	 * @property {Array|String} modeClass = [fade|slide-top|slide-right|slide-bottom|slide-left|zoom-in|zoom-out] 过渡动画类型
	 *  @value fade 渐隐渐出过渡
	 *  @value slide-top 由上至下过渡
	 *  @value slide-right 由右至左过渡
	 *  @value slide-bottom 由下至上过渡
	 *  @value slide-left 由左至右过渡
	 *  @value zoom-in 由小到大过渡
	 *  @value zoom-out 由大到小过渡
	 * @property {Number} duration 过渡动画持续时间
	 * @property {Object} styles 组件样式，同 css 样式，注意带’-‘连接符的属性需要使用小驼峰写法如：`backgroundColor:red`
	 */
	export default {
		name: 'uniTransition',
		emits: ['click', 'change'],
		props: {
			show: {
				type: Boolean,
				default: false
			},
			modeClass: {
				type: [Array, String],
				default () {
					return 'fade'
				}
			},
			duration: {
				type: Number,
				default: 300
			},
			styles: {
				type: Object,
				default () {
					return {}
				}
			},
			customClass: {
				type: String,
				default: ''
			},
			onceRender: {
				type: Boolean,
				default: false
			},
		},
		data() {
			return {
				isShow: false,
				transform: '',
				opacity: 0,
				animationData: {},
				durationTime: 300,
				config: {}
			}
		},
		watch: {
			show: {
				handler(newVal) {
					if (newVal) {
						this.open()
					} else {
						// 避免上来就执行 close,导致动画错乱
						if (this.isShow) {
							this.close()
						}
					}
				},
				immediate: true
			}
		},
		computed: {
			// 生成样式数据
			stylesObject() {
				let styles = {
					...this.styles,
					'transition-duration': this.duration / 1000 + 's'
				}
				let transform = ''
				for (let i in styles) {
					let line = this.toLine(i)
					transform += line + ':' + styles[i] + ';'
				}
				return transform
			},
			// 初始化动画条件
			transformStyles() {
				return 'transform:' + this.transform + ';' + 'opacity:' + this.opacity + ';' + this.stylesObject
			}
		},
		created() {
			// 动画默认配置
			this.config = {
				duration: this.duration,
				timingFunction: 'ease',
				transformOrigin: '50% 50%',
				delay: 0
			}
			this.durationTime = this.duration
		},
		methods: {
			/**
			 *  ref 触发 初始化动画
			 */
			init(obj = {}) {
				if (obj.duration) {
					this.durationTime = obj.duration
				}
				this.animation = createAnimation(Object.assign(this.config, obj), this)
			},
			/**
			 * 点击组件触发回调
			 */
			onClick() {
				this.$emit('click', {
					detail: this.isShow
				})
			},
			/**
			 * ref 触发 动画分组
			 * @param {Object} obj
			 */
			step(obj, config = {}) {
				if (!this.animation) return this
				Object.keys(obj).forEach(key => {
					const value = obj[key]
					if (typeof this.animation[key] === 'function') {
						Array.isArray(value) ?
							this.animation[key](...value) :
							this.animation[key](value)
					}
				})
				this.animation.step(config)
				return this
			},
			/**
			 *  ref 触发 执行动画
			 */
			run(fn) {
				if (!this.animation) return
				this.animation.run(fn)
			},
			// 开始过度动画
			open() {
				clearTimeout(this.timer)
				this.isShow = true
				// 新增初始状态重置逻辑（关键）
				this.transform = this.styleInit(false).transform || ''
				this.opacity = this.styleInit(false).opacity || 0

				// 确保动态样式已经生效后，执行动画，如果不加 nextTick ，会导致 wx 动画执行异常
				this.$nextTick(() => {
					// TODO 定时器保证动画完全执行，目前有些问题，后面会取消定时器
					this.timer = setTimeout(() => {
						this.animation = createAnimation(this.config, this)
						this.tranfromInit(false).step()
						this.animation.run(() => {
							// #ifdef APP-NVUE
							this.transform = this.styleInit(false).transform || ''
							this.opacity = this.styleInit(false).opacity || 1
							// #endif
							// #ifndef APP-NVUE
							this.transform = ''
							this.opacity = this.styleInit(false).opacity || 1
							// #endif
							this.$emit('change', {
								detail: this.isShow
							})
						})
					}, 80)
				})
			},
			// 关闭过度动画
			close(type) {
				if (!this.animation) return
				this.tranfromInit(true)
					.step()
					.run(() => {
						this.isShow = false
						this.animationData = null
						this.animation = null
						let { opacity, transform } = this.styleInit(false)
						this.opacity = opacity || 1
						this.transform = transform
						this.$emit('change', {
							detail: this.isShow
						})
					})
			},
			// 处理动画开始前的默认样式
			styleInit(type) {
				let styles = { transform: '', opacity: 1 }
				const buildStyle = (type, mode) => {
					const value = this.animationType(type)[mode] // 直接使用 type 控制状态
					if (mode.startsWith('fade')) {
						styles.opacity = value
					} else {
						styles.transform += value + ' '
					}
				}

				if (typeof this.modeClass === 'string') {
					buildStyle(type, this.modeClass)
				} else {
					this.modeClass.forEach(mode => buildStyle(type, mode))
				}
				return styles
			},
			// 处理内置组合动画
			tranfromInit(type) {
				let buildTranfrom = (type, mode) => {
					let aniNum = null
					if (mode === 'fade') {
						aniNum = type ? 0 : 1
					} else {
						aniNum = type ? '-100%' : '0'
						if (mode === 'zoom-in') {
							aniNum = type ? 0.8 : 1
						}
						if (mode === 'zoom-out') {
							aniNum = type ? 1.2 : 1
						}
						if (mode === 'slide-right') {
							aniNum = type ? '100%' : '0'
						}
						if (mode === 'slide-bottom') {
							aniNum = type ? '100%' : '0'
						}
					}
					this.animation[this.animationMode()[mode]](aniNum)
				}
				if (typeof this.modeClass === 'string') {
					buildTranfrom(type, this.modeClass)
				} else {
					this.modeClass.forEach(mode => {
						buildTranfrom(type, mode)
					})
				}

				return this.animation
			},
			animationType(type) {
				return {
					fade: type ? 1 : 0,
					'slide-top': `translateY(${type ? '0' : '-100%'})`,
					'slide-right': `translateX(${type ? '0' : '100%'})`,
					'slide-bottom': `translateY(${type ? '0' : '100%'})`,
					'slide-left': `translateX(${type ? '0' : '-100%'})`,
					'zoom-in': `scaleX(${type ? 1 : 0.8}) scaleY(${type ? 1 : 0.8})`,
					'zoom-out': `scaleX(${type ? 1 : 1.2}) scaleY(${type ? 1 : 1.2})`
				}
			},
			// 内置动画类型与实际动画对应字典
			animationMode() {
				return {
					fade: 'opacity',
					'slide-top': 'translateY',
					'slide-right': 'translateX',
					'slide-bottom': 'translateY',
					'slide-left': 'translateX',
					'zoom-in': 'scale',
					'zoom-out': 'scale'
				}
			},
			// 驼峰转中横线
			toLine(name) {
				return name.replace(/([A-Z])/g, '-$1').toLowerCase()
			}
		}
	}
</script>

<style></style>
