<template>
	<view class="lime-echart" :style="customStyle" v-if="canvasId" ref="limeEchart">
		<!-- #ifndef APP-NVUE -->
		<canvas
			class="lime-echart__canvas"
			v-if="use2dCanvas"
			type="2d"
			:id="canvasId"
			:style="canvasStyle"
			:disable-scroll="isDisableScroll"
			@touchstart="touchStart"
			@touchmove="touchMove"
			@touchend="touchEnd"
		/>
		<canvas
			class="lime-echart__canvas"
			v-else-if="isPc"
			:style="canvasStyle"
			:id="canvasId"
			:canvas-id="canvasId"
			:disable-scroll="isDisableScroll"
			@mousedown="touchStart"
			@mousemove="touchMove"
			@mouseup="touchEnd"
		/>
		<canvas
			class="lime-echart__canvas"
			v-else
			:width="nodeWidth"
			:height="nodeHeight"
			:style="canvasStyle"
			:canvas-id="canvasId"
			:id="canvasId"
			:disable-scroll="isDisableScroll"
			@touchstart="touchStart"
			@touchmove="touchMove"
			@touchend="touchEnd"
		/>
		<canvas v-if="isOffscreenCanvas" :style="offscreenStyle" :canvas-id="offscreenCanvasId"></canvas>
		<!-- #endif -->
		<!-- #ifdef APP-NVUE -->
		<web-view
			class="lime-echart__canvas"
			:id="canvasId"
			:style="canvasStyle"
			:webview-styles="webviewStyles"
			ref="webview"
			src="/uni_modules/lime-echart/static/index.html"
			@pagefinish="finished = true"
			@onPostMessage="onMessage"
		></web-view>
		<!-- #endif -->
	</view>
</template>

<script>
// #ifdef VUE3
// #ifdef APP-PLUS
global = {}
// #endif
// #endif
// #ifndef APP-NVUE
import {Canvas, setCanvasCreator, dispatch} from './canvas';
import { compareVersion, wrapTouch, devicePixelRatio ,sleep} from './utils';
// #endif
// #ifdef APP-NVUE
import { base64ToPath, sleep } from './utils';
// #endif
const charts = {}
const echartsObj = {}
export default {
	name: 'lime-echart',
	props: {
		// #ifdef MP-WEIXIN || MP-TOUTIAO
		type: {
			type: String,
			default: '2d'
		},
		// #endif
		// #ifdef APP-NVUE
		webviewStyles: Object,
		// hybrid: Boolean,
		// #endif
		customStyle: String,
		isDisableScroll: Boolean,
		isClickable: {
			type: Boolean,
			default: true
		},
		enableHover: Boolean,
		beforeDelay: {
			type: Number,
			default: 30
		}
	},
	data() {
		return {
			// #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
			use2dCanvas: true,
			// #endif
			// #ifndef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
			use2dCanvas: false,
			// #endif
			width: null,
			height: null,
			nodeWidth: null,
			nodeHeight: null,
			canvasNode: null,
			config: {},
			inited: false,
			finished: false,
			file: '',
			platform: '',
			isPc: false,
			isDown: false,
			isOffscreenCanvas: false,
			offscreenWidth: 0,
			offscreenHeight: 0
		};
	},
	computed: {
		canvasId() {
			return `lime-echart${this._ && this._.uid || this._uid}`
		},
		offscreenCanvasId() {
			return `${this.canvasId}_offscreen`
		},
		offscreenStyle() {
			return `width:${this.offscreenWidth}px;height: ${this.offscreenHeight}px; position: fixed; left: 99999px; background: red`
		},
		canvasStyle() {
			return  this.width && this.height ? ('width:' + this.width + 'px;height:' + this.height + 'px') : ''
		}
	},
	beforeDestroy() {
		this.clear()
		this.dispose()
		// #ifdef H5
		if(this.isPc) {
			document.removeEventListener('mousewheel')
		}
		// #endif
	},
	created() {
		// #ifdef H5
		if(!('ontouchstart' in window)) {
			this.isPc = true
			document.addEventListener('mousewheel', (e) => {
				if(this.chart) {
					const touch = this.getTouch(e)
					const handler = this.chart.getZr().handler;
					dispatch.call(handler, 'mousewheel', touch)
				}
			})
		}
		// #endif
		// #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
		const { SDKVersion, version, platform, environment } = uni.getSystemInfoSync();
		// #endif
		// #ifdef MP-WEIXIN
		this.isPC = /windows/i.test(platform)
		this.use2dCanvas = this.type === '2d' && compareVersion(SDKVersion, '2.9.2') >= 0 && !((/ios/i.test(platform) && /7.0.20/.test(version)) || /wxwork/i.test(environment)) && !this.isPC;
		// #endif
		// #ifdef MP-TOUTIAO
		this.isPC = /devtools/i.test(platform)
		this.use2dCanvas = this.type === '2d' && compareVersion(SDKVersion, '1.78.0') >= 0;
		// #endif
		// #ifdef MP-ALIPAY
		this.use2dCanvas = this.type === '2d' && compareVersion(my.SDKVersion, '2.7.0') >= 0;
		// #endif
	},
	mounted() {
		this.$nextTick(() => {
			this.$emit('finished')
		})
	},
	methods: {
		// #ifdef APP-NVUE
		onMessage(e) {
			const res = e?.detail?.data[0] || null;
			if (res?.event) {
				if(res.event === 'inited') {
					this.inited = true
				}
				this.$emit(res.event, JSON.parse(res.data));
			} else if(res?.file){
				this.file = res.data
			} else if(!res[0] && JSON.stringify(res[0]) != '{}'){
				console.error(res);
			} else {
				console.log(...res)
			}
		},
		// #endif
		setChart(callback) {
			if(!this.chart) {
				console.warn(`组件还未初始化，请先使用 init`)
				return
			}
			if(typeof callback === 'function' && this.chart) {
				callback(this.chart);
			}
			// #ifdef APP-NVUE
			if(typeof callback === 'function') {
				this.$refs.webview.evalJs(`setChart(${JSON.stringify(callback.toString())}, ${JSON.stringify(this.roptions)})`);
			}
			// #endif
		},
		setOption() {
			if (!this.chart || !this.chart.setOption) {
				console.warn(`组件还未初始化，请先使用 init`)
				return
			}
			// #ifndef APP-NVUE
			this.chart.setOption(...arguments);
			// #endif
			// #ifdef APP-NVUE
			this.$refs.webview.evalJs(`setOption(${JSON.stringify(arguments)})`);
			// #endif
		},
		showLoading() {
			if(this.chart) {
				// #ifndef APP-NVUE
				this.chart.showLoading(...arguments)
				// #endif
				// #ifdef APP-NVUE
				this.$refs.webview.evalJs(`showLoading(${JSON.stringify(arguments)})`);
				// #endif
			}
		},
		hideLoading() {
			if(this.chart) {
				// #ifndef APP-NVUE
				this.chart.hideLoading()
				// #endif
				// #ifdef APP-NVUE
				this.$refs.webview.evalJs(`hideLoading()`);
				// #endif
			}
		},
		clear() {
			if(this.chart) {
				// #ifndef APP-NVUE
				this.chart.clear()
				// #endif
				// #ifdef APP-NVUE
				this.$refs.webview.evalJs(`clear()`);
				// #endif
			}
		},
		dispose() {
			if(this.chart) {
				// #ifndef APP-NVUE
				this.chart.dispose()
				// #endif
				// #ifdef APP-NVUE
				this.$refs.webview.evalJs(`dispose()`);
				// #endif
			}
		},
		resize(size) {
			if(size && size.width && size.height) {
				this.height = size.height
				this.width = size.width
				if(this.chart) {this.chart.resize(size)}
				// #ifdef APP-NVUE
				this.$refs.webview.evalJs(`resize(${size})`);
				// #endif
			} else {
				this.$nextTick(() => {
					// #ifndef APP-NVUE
					uni.createSelectorQuery()
						.in(this)
						.select(`.lime-echart`)
						.boundingClientRect()
						.exec(res => {
							if (res) {
								let { width, height } = res[0];
								this.width = width = width || 300;
								this.height = height = height || 300;
								this.chart.resize({width, height})
							}
						});
					// #endif
					// #ifdef APP-NVUE
					this.$refs.webview.evalJs(`resize()`);
					// #endif
				})
				
			}
			
		},
		canvasToTempFilePath(args = {}) {
			// #ifndef APP-NVUE
			const { use2dCanvas, canvasId, canvasNode } = this;
			return new Promise((resolve, reject) => {
				const copyArgs = Object.assign({
					canvasId,
					success: resolve,
					fail: reject
				}, args);
				if (use2dCanvas) {
					delete copyArgs.canvasId;
					copyArgs.canvas = canvasNode;
				}
				uni.canvasToTempFilePath(copyArgs, this);
			});
			// #endif
			// #ifdef APP-NVUE
			this.file = ''
			this.$refs.webview.evalJs(`canvasToTempFilePath()`);
			return new Promise((resolve, reject) => {
				this.$watch('file', async (file) => {
					if(file) {
						const tempFilePath = await base64ToPath(file)
						resolve(args.success({tempFilePath}))
					} else {
						reject(args.fail({error: ``}))
					}
				})
			})
			// #endif
		},
		async init(echarts, ...args) {
			// #ifdef APP-NVUE
			if(arguments && !arguments.length) {
				console.error('缺少参数：init(theme?:string, opts?: object, callback: function)')
				return
			}
			// #endif
			// #ifndef APP-NVUE
			if(arguments && arguments.length < 2) {
				console.error('缺少参数：init(echarts, theme?:string, opts?: object, callback: function)')
				return
			}
			// #endif
			let theme=null,opts={},callback;
			
			Array.from(arguments).forEach(item => {
				if(typeof item === 'function') {
					callback = item
				}
				if(['string'].includes(typeof item)) {
					theme = item
				}
				if(typeof item === 'object') {
					opts = item
				}
			})
			
			if(this.beforeDelay) {
				await sleep(this.beforeDelay)
			}
			let config = await this.getContext();
			// #ifndef APP-NVUE
			if(typeof callback === 'function') {
				setCanvasCreator(echarts, config)
				this.chart = echarts.init(config.canvas, theme, Object.assign({}, config, opts))
				callback(this.chart)
			} else {
				console.error('callback 非 function')
			}
			// #endif
			// #ifdef APP-NVUE
			if(callback) {
				this.chart = {
					setOption: (options) => {
						this.roptions = options
					}
				}
				callback(this.chart)
				this.$refs.webview.evalJs(`init(${JSON.stringify(callback.toString())}, ${JSON.stringify(this.roptions)}, ${JSON.stringify(opts)}, ${theme})`)
			} else {
				console.error('callback 非 function')
			}
			// #endif
		},
		getContext() {
			// #ifdef APP-NVUE
			if(this.finished) {
				return Promise.resolve(this.finished)
			}
			return new Promise(resolve => {
				this.$watch('finished', (val) => {
					if(val) {
						resolve(this.finished)
					}
				})
			})
			// #endif
			// #ifndef APP-NVUE
			const { use2dCanvas} = this;
			let dpr = devicePixelRatio
			if (use2dCanvas) {
				return new Promise(resolve => {
					uni.createSelectorQuery()
						.in(this)
						.select(`#${this.canvasId}`)
						.fields({
							node: true,
							size: true
						})
						.exec(res => {
							let { node, width, height } = res[0];
							this.width = width = width || 300;
							this.height = height = height || 300;
							const ctx = node.getContext('2d');
							const canvas = new Canvas(ctx, this, true, node);
							this.canvasNode = node
							resolve({ canvas, width, height, devicePixelRatio: dpr, node });
						});
				});
			}
			return new Promise(resolve => {
				uni.createSelectorQuery()
					.in(this)
					.select(`#${this.canvasId}`)
					.boundingClientRect()
					.exec(res => {
						if (res) {
							let { width, height } = res[0];
							this.width = width = width || 300;
							this.height = height = height || 300;
							// #ifdef MP-TOUTIAO
							dpr = !this.isPC ? devicePixelRatio : 1// 1.25
							// #endif
							// #ifndef MP-ALIPAY || MP-TOUTIAO
							dpr = this.isPC ? devicePixelRatio : 1
							// #endif
							// #ifdef MP-ALIPAY || MP-LARK
							dpr = devicePixelRatio
							// #endif
							this.rect = res[0]
							this.nodeWidth = width * dpr;
							this.nodeHeight = height * dpr;
							const ctx = uni.createCanvasContext(this.canvasId, this);
							const canvas =  new Canvas(ctx, this, false);
							resolve({ canvas, width, height, devicePixelRatio: dpr });
						}
					});
			});
			// #endif
		},
		// #ifndef APP-NVUE
		getRelative(e) {
			return {x: e.pageX - this.rect.left, y: e.pageY - this.rect.top, wheelDelta: e.wheelDelta}
		},
		getTouch(e) {
			return e.touches && e.touches[0] && e.touches[0].x ? e.touches[0] : this.getRelative(e);
		},
		touchStart(e) {
			this.isDown = true
			if (this.chart && ((e.touches.length > 0 || e.touches['0'])  && e.type != 'mousemove' || e.type == 'mousedown')) {
				const touch = this.getTouch(e)
				this.startX = touch.x
				this.startY = touch.y
				this.startT = new Date()
				const handler = this.chart.getZr().handler;
				dispatch.call(handler, 'mousedown', touch)
				dispatch.call(handler, 'mousemove', touch)
				handler.processGesture(wrapTouch(e), 'start');
				clearTimeout(this.endTimer);
			}
		},
		touchMove(e) {
			if(this.isPc && this.enableHover && !this.isDown) {this.isDown = true}
			if (this.chart && ((e.touches.length > 0 || e.touches['0']) && e.type != 'mousemove' || e.type == 'mousemove' && this.isDown)) {
				const handler = this.chart.getZr().handler;
				dispatch.call(handler, 'mousemove', this.getTouch(e))
				handler.processGesture(wrapTouch(e), 'change');
			}
		},
		touchEnd(e) {
			this.isDown = false
			if (this.chart) {
				const {x} = e.changedTouches && e.changedTouches[0] || {}
				const touch = (x ? e.changedTouches[0] : this.getRelative(e)) || {};
				const handler = this.chart.getZr().handler;
				const isClick = Math.abs(touch.x - this.startX) < 10 && new Date() - this.startT < 200;
				dispatch.call(handler, 'mouseup', touch)
				handler.processGesture(wrapTouch(e), 'end');
				if(isClick) {
					dispatch.call(handler, 'click', touch)
				} else {
					this.endTimer = setTimeout(() => {
						dispatch.call(handler, 'mousemove', {x: 999999999,y: 999999999});
						dispatch.call(handler, 'mouseup', {x: 999999999,y: 999999999});
					},50)
				}
			}
		}
		// #endif
	}
};
</script>
<style scoped>
.lime-echart {
	position: relative;
	/* #ifndef APP-NVUE */
	width: 100%;
	height: 100%;
	/* #endif */
	/* #ifdef APP-NVUE */
	flex: 1;
	/* #endif */
}
.lime-echart__canvas {
	/* #ifndef APP-NVUE */
	width: 100%;
	height: 100%;
	/* #endif */
	/* #ifdef APP-NVUE */
	flex: 1;
	/* #endif */
}
</style>
