<template>
	<view class="lime-painter" ref="limepainter">
		<view v-if="canvasId && size" :style="styles">
			<!-- #ifndef APP-NVUE -->
			<canvas class="lime-painter__canvas" v-if="use2dCanvas" :id="canvasId" type="2d" :style="size"></canvas>
			<canvas class="lime-painter__canvas" v-else :id="canvasId" :canvas-id="canvasId" :style="size"
				:width="boardWidth * dpr" :height="boardHeight * dpr" :hidpi="hidpi"></canvas>

			<!-- #endif -->
			<!-- #ifdef APP-NVUE -->
			<web-view :style="size" ref="webview"
				src="/uni_modules/lime-painter/hybrid/html/index.html"
				class="lime-painter__canvas" @pagefinish="onPageFinish" @error="onError" @onPostMessage="onMessage">
			</web-view>
			<!-- #endif -->
		</view>
		<slot />
	</view>
</template>

<script>
	import { parent } from '../common/relation'
	import props from './props'
	import {toPx, base64ToPath, pathToBase64, isBase64, sleep, getImageInfo }from './utils';
	//  #ifndef APP-NVUE
	import { canIUseCanvas2d, isPC} from './utils';
	import Painter from './painter';
	// import Painter from '@painter'
	const nvue = {}
	//  #endif
	//  #ifdef APP-NVUE
	import nvue from './nvue'
	//  #endif
	export default {
		name: 'lime-painter',
		mixins: [props, parent('painter'), nvue],
		data() {
			return {
				use2dCanvas: false,
				canvasHeight: 150,
				canvasWidth: null,
				parentWidth: 0,
				inited: false,
				progress: 0,
				firstRender: 0,
				done: false,
				tasks: []
			};
		},
		computed: {
			styles() {
				return `${this.size}${this.customStyle||''};` + (this.hidden && 'position: fixed; left: 1500rpx;')
			},
			canvasId() {
				return `l-painter${this._ && this._.uid || this._uid}`
			},
			size() {
				if (this.boardWidth && this.boardHeight) {
					return `width:${this.boardWidth}px; height: ${this.boardHeight}px;`;
				}
			},
			dpr() {
				return this.pixelRatio || uni.getSystemInfoSync().pixelRatio;
			},
			boardWidth() {
				const {width = 0} = (this.elements && this.elements.css) || this.elements || this
				const w = toPx(width||this.width)
				return w || Math.max(w, toPx(this.canvasWidth));
			},
			boardHeight() {
				const {height = 0} = (this.elements && this.elements.css) || this.elements || this
				const h = toPx(height||this.height)
				return h || Math.max(h, toPx(this.canvasHeight));
			},
			hasBoard() {
				return this.board && Object.keys(this.board).length
			},
			elements() {
				return this.hasBoard ? this.board : JSON.parse(JSON.stringify(this.el))
			}
		},
		created() {
			this.use2dCanvas = this.type === '2d' && canIUseCanvas2d() && !isPC
		},
		async mounted() {
			await sleep(30)
			await this.getParentWeith()
			this.$nextTick(() => {
				setTimeout(() => {
					this.$watch('elements', this.watchRender, {
						deep: true,
						immediate: true
					});
				}, 30)
			})
		},
		// #ifdef VUE3
		unmounted() {
			this.done = false
			this.inited = false
			this.firstRender = 0
			this.progress = 0
			this.painter = null
			clearTimeout(this.rendertimer)
		},
		// #endif
		// #ifdef VUE2
		destroyed() {
			this.done = false
			this.inited = false
			this.firstRender = 0
			this.progress = 0
			this.painter = null
			clearTimeout(this.rendertimer)
		},
		// #endif
		methods: {
			async watchRender(val, old) {
				if (!val || !val.views || (!this.firstRender ? !val.views.length : !this.firstRender) || !Object.keys(val).length || JSON.stringify(val) == JSON.stringify(old)) return;
				this.firstRender = 1
				this.progress = 0
				this.done = false
				clearTimeout(this.rendertimer)
				this.rendertimer = setTimeout(() => {
					this.render(val);
				}, this.beforeDelay)
			},
			async setFilePath(path, param) {
				let filePath = path
				const {pathType = this.pathType} =  param || this
				if (pathType == 'base64' && !isBase64(path)) {
					filePath = await pathToBase64(path)
				} else if (pathType == 'url' && isBase64(path)) {
					filePath = await base64ToPath(path)
				}
				if (param && param.isEmit) {
					this.$emit('success', filePath);
				}
				return filePath
			},
			async getSize(args) {
				const {width} = args.css || args
				const {height} = args.css || args
				if (!this.size) {
					if (width || height) {
						this.canvasWidth = width || this.canvasWidth
						this.canvasHeight = height || this.canvasHeight
						await sleep(30);
					} else {
						await this.getParentWeith()
					}
				}
			},
			canvasToTempFilePathSync(args) {
				// this.stopWatch && this.stopWatch()
				// this.stopWatch = this.$watch('done', (v) => {
				// 	if (v) {
				// 		this.canvasToTempFilePath(args)
				// 		this.stopWatch && this.stopWatch()
				// 	}
				// }, {
				// 	immediate: true
				// })
				this.tasks.push(args)
				if(this.done){
					this.runTask()
				}
			},
			runTask(){
				while(this.tasks.length){
					const task = this.tasks.shift()	
					 this.canvasToTempFilePath(task)
				}
			},
			// #ifndef APP-NVUE
			getParentWeith() {
				return new Promise(resolve => {
					uni.createSelectorQuery()
						.in(this)
						.select(`.lime-painter`)
						.boundingClientRect()
						.exec(res => {
							const {width, height} = res[0]||{}
							this.parentWidth = Math.ceil(width||0)
							this.canvasWidth = this.parentWidth || 300
							this.canvasHeight = height || this.canvasHeight||150
							resolve(res[0])
						})
				})
			},
			async render(args = {}) {
				if(!Object.keys(args).length) {
					return console.error('空对象')
				}
				this.progress = 0
				this.done = false
				// #ifdef APP-NVUE
				this.tempFilePath.length = 0
				// #endif
				await this.getSize(args)
				const ctx = await this.getContext();
				
				let {
					use2dCanvas,
					boardWidth,
					boardHeight,
					canvas,
					afterDelay
				} = this;
				if (use2dCanvas && !canvas) {
					return Promise.reject(new Error('canvas 没创建'));
				}
				this.boundary = {
					top: 0,
					left: 0,
					width: boardWidth,
					height: boardHeight
				};
				this.painter = null
				if (!this.painter) {
					const {width} = args.css || args
					const {height} = args.css || args
					if(!width && this.parentWidth) {
						Object.assign(args, {width: this.parentWidth})
					}
					const param = {
						context: ctx,
						canvas,
						width: boardWidth,
						height: boardHeight,
						pixelRatio: this.dpr,
						useCORS: this.useCORS,
						createImage: getImageInfo.bind(this),
						performance: this.performance,
						listen: {
							onProgress: (v) => {
								this.progress = v
								this.$emit('progress', v)
							},
							onEffectFail: (err) => {
								this.$emit('faill', err)
							}
						}
					}
					this.painter = new Painter(param)
				} 
				try{
					// vue3 赋值给data会引起图片无法绘制
					const { width, height } = await this.painter.source(JSON.parse(JSON.stringify(args)))
					this.boundary.height = this.canvasHeight = height
					this.boundary.width = this.canvasWidth = width
					await sleep(this.sleep);
					await this.painter.render()
					await new Promise(resolve => this.$nextTick(resolve));
					if (!use2dCanvas) {
						await this.canvasDraw();
					}
					if (afterDelay && use2dCanvas) {
						await sleep(afterDelay);
					}
					this.$emit('done');
					this.done = true
					if (this.isCanvasToTempFilePath) {
						this.canvasToTempFilePath()
							.then(res => {
								this.$emit('success', res.tempFilePath)
							})
							.catch(err => {
								this.$emit('fail', new Error(JSON.stringify(err)));
							});
					}
					this.runTask()
					return Promise.resolve({
						ctx,
						draw: this.painter,
						node: this.node
					});
				}catch(e){
					//TODO handle the exception
				}
				
			},
			canvasDraw(flag = false) {
				return new Promise((resolve, reject) => this.ctx.draw(flag, () => setTimeout(() => resolve(), this
					.afterDelay)));
			},
			async getContext() {
				if (!this.canvasWidth) {
					this.$emit('fail', 'painter no size')
					console.error('[lime-painter]: 给画板或父级设置尺寸')
					return Promise.reject();
				}
				if (this.ctx && this.inited) {
					return Promise.resolve(this.ctx);
				}
				const { type, use2dCanvas, dpr, boardWidth, boardHeight } = this;
				const _getContext = () => {
					return new Promise(resolve => {
						uni.createSelectorQuery()
							.in(this)
							.select(`#${this.canvasId}`)
							.boundingClientRect()
							.exec(res => {
								if (res) {
									const ctx = uni.createCanvasContext(this.canvasId, this);
									if (!this.inited) {
										this.inited = true;
										this.use2dCanvas = false;
										this.canvas = res;
									}
									
									// 钉钉小程序框架不支持 measureText 方法，用此方法 mock
									if (!ctx.measureText) {
										function strLen(str) {
											let len = 0;
											for (let i = 0; i < str.length; i++) {
												if (str.charCodeAt(i) > 0 && str.charCodeAt(i) < 128) {
													len++;
												} else {
													len += 2;
												}
											}
											return len;
										}
										ctx.measureText = text => {
											let fontSize = ctx.state && ctx.state.fontSize || 12;
											const font = ctx.__font
											if (font && fontSize == 12) {
												fontSize = parseInt(font.split(' ')[3], 10);
											}
											fontSize /= 2;
											return {
												width: strLen(text) * fontSize
											};
										}
									}
									
									// #ifdef MP-ALIPAY
									ctx.scale(dpr, dpr);
									// #endif
									this.ctx = ctx
									resolve(this.ctx);
								} else {
									console.error('[lime-painter] no node')
								}
							});
					});
				};
				if (!use2dCanvas) {
					return _getContext();
				}
				return new Promise(resolve => {
					uni.createSelectorQuery()
						.in(this)
						.select(`#${this.canvasId}`)
						.node()
						.exec(res => {
							let {node: canvas} = res && res[0]||{};
							if(canvas) {
								const ctx = canvas.getContext(type);
								if (!this.inited) {
									this.inited = true;
									this.use2dCanvas = true;
									this.canvas = canvas;
								}
								this.ctx = ctx
								resolve(this.ctx);
							} else {
								console.error('[lime-painter]: no size')
							}
						});
				});
			},
			canvasToTempFilePath(args = {}) {
				return new Promise(async (resolve, reject) => {
					const { use2dCanvas, canvasId, dpr, fileType, quality } = this;
					const success = async (res) => {
						try {
							const tempFilePath = await this.setFilePath(res.tempFilePath || res, args)
							const result = Object.assign(res, {tempFilePath})
							args.success && args.success(result)
							resolve(result)
						} catch (e) {
							this.$emit('fail', e)
						}
					}
					
					let { top: y = 0, left: x = 0, width, height } = this.boundary || this;
					// let destWidth = width * dpr;
					// let destHeight = height * dpr;
					// #ifdef MP-ALIPAY
					// width = destWidth;
					// height = destHeight;
					// #endif
					
					const copyArgs = Object.assign({
						// x,
						// y,
						// width,
						// height,
						// destWidth,
						// destHeight,
						canvasId,
						id: canvasId,
						fileType,
						quality,
					}, args, {success});
					// if(this.isPC || use2dCanvas) {
					// 	copyArgs.canvas = this.canvas
					// }
					if (use2dCanvas) {
						copyArgs.canvas = this.canvas
						try{
							// #ifndef MP-ALIPAY
							const oFilePath = this.canvas.toDataURL(`image/${args.fileType||fileType}`.replace(/pg/, 'peg'), args.quality||quality)
							if(/data:,/.test(oFilePath)) {
								uni.canvasToTempFilePath(copyArgs, this);
							} else {
								const tempFilePath = await this.setFilePath(oFilePath, args)
								args.success && args.success({tempFilePath})
								resolve({tempFilePath})
							}
							// #endif
							// #ifdef MP-ALIPAY
							this.canvas.toTempFilePath(copyArgs)
							// #endif
						}catch(e){
							args.fail && args.fail(e)
							reject(e)
						}
					} else {
						// #ifdef MP-ALIPAY
						if(this.ctx.toTempFilePath) {
							// 钉钉
							const ctx = uni.createCanvasContext(canvasId);
							ctx.toTempFilePath(copyArgs);
						} else {
							my.canvasToTempFilePath(copyArgs);
						}
						// #endif
						// #ifndef MP-ALIPAY
						uni.canvasToTempFilePath(copyArgs, this);
						// #endif
					}
				})
			}
			// #endif
		}
	};
</script>
<style>
	.lime-painter,
	.lime-painter__canvas {
		// #ifndef APP-NVUE
		width: 100%;
		// #endif
		// #ifdef APP-NVUE
		flex: 1;
		// #endif
	}
</style>
// cache bust 2024-07
