/**
 * 原插件地址
 * https://ext.dcloud.net.cn/plugin?id=17764
 */

import { UXCreateAnimationOptions } from '../../types/types.uts'
// #ifdef APP-IOS
import { Ref } from 'vue'
// #endif

const defaultOption : UXCreateAnimationOptions = {
	duration: 400,
	timingFunction: 'linear',
	delay: 0,
	transformOrigin: '50% 50%'
}

function toCamelCase(str : string) : string {
	return str
		.replace(/(?:^\w|[A-Z]|\b\w)/g, (word : string, index : number, _ : string) : string => {
			return index == 0 ? word.toLowerCase() : word.toUpperCase();
		})
		.replace(/[-_\s]+/g, '');
}
function camelToKebabCase(str : string) : string {
	return str
		.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
		.replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
		.toLowerCase();
}


function addUnit(value : any) : any {
	const regex = new RegExp("^(-)?\\d+(\\.\\d+)?$")
	const numberType = ['number', 'Int', 'UInt', 'Long', 'Float', 'Double']
	if (typeof value == 'string' && regex.test(value as string)) {
		return `${value}px`
	} else if (numberType.includes(typeof value)) {
		return `${value}px`
	}
	return value
}
// #ifdef APP-IOS

// #endif
const transitionProperty = [
	'width', 'height', 'left', 'top', 'bottom', 'right', 'opacity', 'background-color', 'transform',
	'border-color', 'border-left-color', 'border-top-color', 'border-right-color', 'border-bottom-color',
	'margin', 'margin-left', 'margin-top', 'margin-right', 'margin-bottom',
	'padding', 'padding-left', 'padding-top', 'padding-right', 'padding-bottom',
	// #ifdef WEB 
	'skew', 'skewX', 'skewY', 'matrix', 'matrix3d' //'perspective',
	// #endif
]
const transitionSettings = ['transition-delay', 'transition-duration', 'transition-timing-function', 'transform-origin']

/**
 * Step 类表示一个动画步骤
 */
class Step {
	styles : Map<string, any>
	transform : Map<string, any>
	options : UXCreateAnimationOptions = {} as UXCreateAnimationOptions

	root : Step | null = null
	prev : Step | null = null
	next : Step | null = null

	stepIndex : number = 0
	currentStepIndex : number = 0
	stepCount : number = 1

	loop : number = 1
	loopCount : number = 1
	isPlaying : boolean = false
	timer : number = 0
	onFinish : () => void = () => { }
	onUpdate : () => void = () => { }
	/**
	 * 构造函数
	 * @param options 动画选项
	 * @param stepIndex 步骤索引
	 */
	constructor(options : UXCreateAnimationOptions, stepIndex : number = 0) {
		this.styles = new Map<string, any>()
		this.transform = new Map<string, any>()
		this.stepIndex = stepIndex;
		this.updateOption(options)
	}
	/**
	 * 更新动画选项
	 * @param options 动画选项
	 */
	updateOption(options : UXCreateAnimationOptions) {
		for (let key in options) {
			const item = options[key]
			this.options[key] = item ?? defaultOption[key]
		}
	}
	/**
	 * 添加一个新的动画步骤
	 * @param options 动画选项
	 * @returns 新的动画步骤
	 */
	addStep(options : UXCreateAnimationOptions) : Step {
		if (this.next == null) {
			const root = this.root ?? this;
			this.next = new Step(options, this.stepIndex + 1);
			this.next!.prev = this;
			this.next!.root = root;
			return this.next!
		} else {
			return this.next!.addStep(options);
		}
	}
	/**
	 * 停止动画
	 */
	stop() {
		const root = this.root ?? this
		root.isPlaying = false
		clearTimeout(root.timer)
	}

	/**
	 * 等待指定时间
	 * @param time 等待时间
	 * @returns Promise
	 */
	private sleep(time : number = 0) : Promise<boolean> {
		return new Promise((resolve) => {
			setTimeout(function () {
				resolve(true)
			}, time);
		})
	}
	/**
	 * 播放动画
	 * @param element 动画元素
	 */
	async play(element : UniElement, stepIndex : number = -1) {
		if (stepIndex > -1) {
			this.getStepByIndex(stepIndex)?.play(element)
			return
		}

		const duration = this.options.duration ?? 1
		const delay = this.options.delay ?? 0;
		const root = this.root ?? this
		root.isPlaying = true
		await this.sleep(this.stepIndex == 0 ? 100 : 0)

		// element.offsetHeight;
		for (let key in this.options) {
			const name = key == 'transformOrigin' ? key : toCamelCase(`transition-${key}`)
			let value = this.options[key]
			if (['delay', 'duration'].includes(key) && ['number', 'Int'].includes(typeof value)) {
				value = `${value}ms`
			}
			element.style.setProperty(camelToKebabCase(name), value)
		}
		// element.style.setProperty('transition-property', 'all')
		// await this.sleep()
		// element.offsetHeight
		let transformStr = ((element.style.getPropertyValue('transform') ?? '') as string).replace('none','')
		this.transform.forEach((value, key) => {
			const regex = new RegExp(`${key}\\([^)]*\\)`);
			if (regex.test(transformStr)) {
				// 如果存在，则修改 key 的 value
				transformStr = transformStr.replace(regex, `${key}(${value})`);
			} else {
				transformStr += ` ${key}(${value})`
			}
		})
		this.styles.forEach((value, key) => {
			element.style.setProperty(key, value)
		})
		element.style.setProperty('transform', transformStr)
		if (this.next != null && this.next!.styles.size != 0) {
			root.currentStepIndex = this.next!.stepIndex
			// await this.sleep(duration + delay)
			root.timer = setTimeout(() => {
				this.next?.play(element)
			}, duration + delay)

		} else if (root.loop == -1 || root.loopCount < root.loop) {
			// await this.sleep(duration + delay)
			root.currentStepIndex = 0
			root.timer = setTimeout(() => {
				root.onUpdate()
				root.loopCount++
				root.play(element)
			}, duration + delay)
		} else {
			let callbackWrapper : UniCallbackWrapper | null = null
			const transitionend = (_ : UniEvent) => {
				root.isPlaying = false
				root.loopCount = 1
				root.currentStepIndex = 0
				root.onFinish();
				// #ifdef WEB
				element.removeEventListener('transitionend', transitionend)
				// #endif
				// #ifdef APP
				if (callbackWrapper == null) return
				element.removeEventListener('transitionend', callbackWrapper!)
				// #endif
			}
			callbackWrapper = element.addEventListener('transitionend', transitionend)
		}
	}
	/**
	 * 获取第一个步骤
	 * @returns 第一个步骤
	 */
	getFirstStep() : Step | null {
		if (this.prev == null) {
			return this;
		}
		return this.prev!.getFirstStep();
	}
	/**
	 * 获取最后一个步骤
	 * @returns 最后一个步骤
	 */
	getLastStep() : Step | null {
		if (this.next == null) {
			return this;
		}
		return this.next!.getLastStep();
	}
	/**
	 * 根据索引获取步骤
	 * @param index 步骤索引
	 * @returns 对应索引的步骤
	 */
	getStepByIndex(index : number) : Step | null {
		if (this.stepIndex == index) {
			return this;
		}

		if (this.next != null && this.stepIndex < index) {
			return this.next!.getStepByIndex(index);
		}

		if (this.prev != null && this.stepIndex > index) {
			return this.prev!.getStepByIndex(index);
		}

		return null;
	}
}

/**
 * Animation 类
 */
export class Animation {
	private options : UXCreateAnimationOptions = {} as UXCreateAnimationOptions
	private steps : Step | null
	private _steps : Step | null = null
	private currentStep : Step | null
	private element : UniElement | null = null
	private cacheStyles : Map<string, any> = new Map<string, any>()
	onFinish : () => void = () => { }
	constructor(options : UXCreateAnimationOptions) {
		this.updateOption(options)
		this.steps = new Step(this.options)
		this.steps!.onFinish = () => {
			this.onFinish()
		}
		this.currentStep = this.steps

		// #ifdef WEB
		try {
			const directions = ['top', 'left', 'right', 'bottom'];
			directions.forEach((key) => {
				const name = `--lime-ani-${key}`;
				window.CSS.registerProperty({
					name,
					syntax: '<length-percentage>',
					inherits: false,
					initialValue: '0'
				})
			})
		} catch (error) {
			if (error instanceof DOMException) {
				// console.error(`Failed to register property`);
			} else {
				throw error;
			}
		}
		// #endif
	}
	/**
	 * 更新选项
	 * @param options 创建动画选项
	 */
	private updateOption(options : UXCreateAnimationOptions) {
		for (let key in options) {
			const item = options[key]
			this.options[key] = item ?? defaultOption[key]
		}
	}
	/**
	 * 更新 transform 属性
	 * @param property transform 属性名
	 * @param value transform 属性值
	 */
	private updateTransform(property : string, value : string) {
		// 设置更新后的 transform 属性值
		this.currentStep?.styles!.set('transform', '');
		this.currentStep?.transform!.set(property, value)
	}
	/**
	  * 设置透明度
	  * @param value 透明度值
	  * @returns Animation 实例
	  */
	opacity(value : number) : Animation {
		this.currentStep?.styles!.set('opacity', value)
		return this
	}
	/**
	 * 设置背景颜色
	 * @param color 颜色值
	 * @returns Animation 实例
	 */
	backgroundColor(color : string) : Animation {
		this.currentStep?.styles!.set('background-color', color)
		return this
	}
	width(length : any) : Animation {
		return this.add('width', length)
	}
	height(length : any) : Animation {
		return this.add('height', length)
	}
	top(length : any) : Animation {
		return this.add('top', length)
	}
	left(length : any) : Animation {
		return this.add('left', length)
	}
	right(length : any) : Animation {
		return this.add('right', length)
	}
	bottom(length : any) : Animation {
		return this.add('bottom', length)
	}
	rotateX(deg : number) : Animation {
		this.updateTransform('rotateX', `${deg}deg`);
		return this
	}
	rotateY(deg : number) : Animation {
		this.updateTransform('rotateY', `${deg}deg`);
		return this
	}
	rotateZ(deg : number) : Animation {
		this.updateTransform('rotateZ', `${deg}deg`);
		return this
	}
	rotate(deg : number) : Animation {
		this.updateTransform('rotate', `${deg}deg`);
		return this
	}
	scaleX(number : number) : Animation {
		this.updateTransform('scaleX', `${number.toString()}`)
		return this
	}
	scaleY(number : number) : Animation {
		this.updateTransform('scaleY', `${number.toString()}`)
		return this
	}
	scaleZ(number : number) : Animation {
		this.updateTransform('scaleZ', `${number.toString()}`)
		return this
	}
	scale(scaleX : number) : Animation
	scale(scaleX : number, scaleY : number = 0) : Animation {
		const _scaleY = scaleY == 0 ? scaleX : scaleY;
		this.updateTransform('scale', `${scaleX},${_scaleY}`);
		return this
	}
	// #ifdef WEB
	skewX(number : number) : Animation {
		this.updateTransform('skewX', `${number.toString()}deg`)
		return this
	}
	skewY(number : number) : Animation {
		this.updateTransform('skewY', `${number.toString()}deg`)
		return this
	}
	skew(skewX : number) : Animation
	skew(skewX : number, skewY : number = 0) : Animation {
		const _skewY = skewY == 0 ? skewX : skewY;
		this.updateTransform('skew', `${skewX}deg,${_skewY}deg`);
		return this
	}
	//matrix3d
	matrix(a : number, b : number, c : number, d : number, tx : number, ty : number) : Animation {
		this.updateTransform('skew', `${a},${b},${c},${d},${tx},${ty}`);
		return this
	}
	// #endif
	translateX(length : any) : Animation {
		const value = addUnit(length)
		this.updateTransform('translateX', `${value}`)
		return this
	}
	translateY(length : any) : Animation {
		const value = addUnit(length)
		this.updateTransform('translateY', `${value}`)
		return this
	}
	translate(x : any, y : any) : Animation {
		const _x = addUnit(x)
		const _y = addUnit(y)
		this.updateTransform('translate', `${_x},${_y}`);
		return this
	}
	margin(value : any) : Animation {
		if (typeof value == 'string') {
			return this.add('margin', value)
		} else if (Array.isArray(value)) {
			return this.add('margin', (value as any[]).join(' '))
		}
		return this
	}
	marginLeft(value : any) : Animation {
		return this.add('marginLeft', value)
	}
	marginRight(value : any) : Animation {
		return this.add('marginRight', value)
	}
	marginTop(value : any) : Animation {
		return this.add('marginTop', value)
	}
	marginBottom(value : any) : Animation {
		return this.add('marginBottom', value)
	}
	padding(value : any) : Animation {
		if (typeof value == 'string') {
			return this.add('padding', value)
		} else if (Array.isArray(value)) {
			return this.add('padding', (value as any[]).join(' '))
		}
		return this
	}
	paddingLeft(value : any) : Animation {
		return this.add('paddingLeft', value)
	}
	paddingRight(value : any) : Animation {
		return this.add('paddingRight', value)
	}
	paddingTop(value : any) : Animation {
		return this.add('paddingTop', value)
	}
	paddingBottom(value : any) : Animation {
		return this.add('paddingBottom', value)
	}
	borderColor(color : string) : Animation {
		return this.add('borderColor', color)
	}
	borderTopColor(color : string) : Animation {
		return this.add('borderTopColor', color)
	}
	borderBottomColor(color : string) : Animation {
		return this.add('borderBottomColor', color)
	}
	borderLeftColor(color : string) : Animation {
		return this.add('borderLeftColor', color)
	}
	borderRightColor(color : string) : Animation {
		return this.add('borderRightColor', color)
	}
	/**
	 * 添加动画属性
	 * @param type 动画属性类型
	 * @param value 动画属性值
	 * @returns Animation 实例
	 */
	add(type : string, value : any) : Animation {
		const _v = addUnit(value)
		if (['opacity', 'transform', 'background-color', 'backgroundColor'].includes(type)) {
			this.currentStep?.styles!.set(type, value)
		} else {
			this.currentStep?.styles!.set(type, _v)
		}
		return this
	}
	step(options : UTSJSONObject | null = null) : Animation {
		if (options != null) {
			const _options = {} as UXCreateAnimationOptions
			for (let key in this.options) {
				const value = options[key]
				if (value != null) {
					_options[key] = value
				}
			}
			this.currentStep?.updateOption(_options)
		}
		this.currentStep = this.currentStep?.addStep(this.options)
		return this
	}
	/**
	 * 设置初始样式
	 * @param element 元素
	 */
	private setCacheStyles(element : UniElement) {
		// @ts-ignore
		// #ifndef APP-IOS
		// element.style.cssText.split(';').forEach(style => {
		// 	if (style.includes(':')) {
		// 		const [property, value] = style.split(':');
		// 		if(!transitionProperty.includes(property)){
		// 			if(property == 'backgroundColor'){
		// 				 this.cacheStyles.set(property, value)
		// 			}
		// 		}
		// 	}
		// })
		// #endif
		transitionProperty.forEach(property => {
			const value = this.getCSSValue(element, property) //element.style.getPropertyValue(property)
			if (value != null) {
				this.cacheStyles.set(property, value)
			}
		})
		transitionSettings.forEach(property => {
			const value = this.getCSSValue(element, property) //element.style.getPropertyValue(property)
			if (value != null) {
				this.cacheStyles.set(property, value)
			}
		})
		// this.cacheStyles.set('transform', '')
	}
	getCSSValue(el : UniElement, prop : string):any|null {
		const uppercasePropName = prop.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
		// #ifdef WEB
		// @ts-ignore
		const style = getComputedStyle(el) ?? el.style
		const value = style.getPropertyValue(uppercasePropName) || style[uppercasePropName]
		// #endif
		// #ifndef WEB
		const value = el.style.getPropertyValue(uppercasePropName)
		// #endif
		return value
	}
	/**
	  * 重置样式
	  * @param element 元素
	  */
	private resetStyles(element : UniElement) {
		// @ts-ignore
		// #ifndef APP-IOS
		// element.style.cssText.split(';').forEach(style => {
		// 	if (style.includes(':')) {
		// 		const property = style.split(':')[0].trim();
		// 		if (property == 'transform-origin') {
		// 			element.style.setProperty(property, '50% 50%')
		// 		} else if (property == 'transition-duration') {
		// 			element.style.setProperty(property, '1ms')
		// 		} else {
		// 			element.style.setProperty(property, this.cacheStyles.get(property) ?? '')
		// 		}
		// 	}
		// })
		// #endif
		const directions = ['top', 'left', 'right', 'bottom'];
		transitionSettings.forEach(property => {
			const value = this.cacheStyles.get(property)
			if (property == 'transition-duration') {
				element.style.setProperty(property, value ?? '0ms')
			} else if (value != null) {
				element.style.setProperty(property, value)
			}
		})
		transitionProperty.forEach((property) => {
			const value = this.cacheStyles.get(property)
			if (value != null || directions.includes(property) || property == 'transform') {
				element.style.setProperty(property, value ?? '')
			}
		})
		element.style.setProperty('transition-property', transitionProperty.join(','))
		// #ifdef WEB
		directions.forEach(key => {
			element.style.setProperty(`${key}`, `var(--lime-ani-${key})`)
		})
		// #endif

		element.getBoundingClientRect()
	}
	export(element : UniElement, loopCount : number) : UTSJSONObject
	export(element : Ref<UniElement | null>, loopCount : number) : UTSJSONObject
	export(element : any | null = null, loopCount : number = 1) : UTSJSONObject {
		this._steps = this.steps
		this.steps = new Step(this.options)
		this.currentStep = this.steps
		this.steps!.onFinish = () => {
			this.onFinish()
		}
		// @ts-ignore
		this.play(element, loopCount)
		return {}
	}

	/**
	 * 播放动画
	 * @param element 元素，可选
	 */
	play() : void
	play(element : UniElement, loopCount : number) : void
	play(element : Ref<UniElement | null>, loopCount : number) : void
	play(element : any | null = null, loopCount : number | null = null) : void {
		if (this._steps != null && this._steps!.isPlaying) return
		const _loopCount = loopCount ?? this._steps?.loop ?? 1
		if (element == null) {
			this._play(null, _loopCount)
		} else if (element instanceof UniElement) {
			this._play(element, _loopCount)
		} else {
			//  #ifdef APP-ANDROID
			if (!(element instanceof Ref<UniElement | null>)) return
			// #endif
			// @ts-ignore
			watch(element, (el : UniElement) => {
				this._play(el, _loopCount)
			})
		}

	}
	private _play(element : UniElement | null, loopCount : number = 1) {
		if (this.element == null && element != null) {
			this.element = element
			this.setCacheStyles(this.element!)
		}

		if (this.element == null || this._steps == null || this._steps!.isPlaying) return
		if (this._steps!.currentStepIndex == 0) {
			this.resetStyles(this.element!)
		}
		this._steps!.loop = loopCount
		this._steps!.onUpdate = () => {
			this.resetStyles(this.element!)
		}
		this._steps!.play(this.element!, this._steps!.currentStepIndex)
	}

	/**
	 * 停止动画
	 */
	stop() {
		this._steps?.stop()
	}
	/**
	  * 销毁动画
	  */
	destroy() {
		this.stop();
		this.element = null;
		this.steps = null;
		this._steps = null;
		this.currentStep = null;
		this.cacheStyles.clear();
	}
}

export function createAnimation(options : UXCreateAnimationOptions = {} as UXCreateAnimationOptions) : Animation {
	// #ifdef APP||WEB
	return new Animation(options)
	// #endif
	// #ifndef APP||WEB
	// @ts-ignore
	return uni.createAnimation({ ...(options ?? {}) })
	// #endif
}