<template>
	<view class="up-poster">
		<!-- canvas用于绘制海报 -->
		<canvas 
			v-if="showCanvas" 
			class="up-poster__hidden-canvas" 
			:canvas-id="canvasId" 
			:id="canvasId"
			:style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }">
		</canvas>
		<!-- 隐藏的二维码组件，用于生成二维码图片 -->
		<up-qrcode 
			ref="qrCode" 
			:val="qrCodeValue" 
			:size="qrCodeSize" 
			:margin="0" 
            :loadMake="false"
			background="#ffffff"
			foreground="#000000"
			:class="['up-poster__hidden-qrcode', qrCodeShow ? '' : 'up-poster__hidden-qrcode--hidden']"
		/>
	</view>
</template>

<script>
/**
 * Poster 海报组件
 * @description 用于生成海报的组件，支持文本、图片、二维码等元素
 * @tutorial https://ijry.github.io/uview-plus/components/poster.html
 * 
 * @property {Object} json 海报配置JSON数据
 * @property {Object} json.css 海报容器样式
 * @property {Array}  json.views 海报元素列表
 * @property {String} json.views.type 元素类型(text/image/qrcode/view)
 * @property {String} json.views.text 文本内容(仅text类型)
 * @property {String} json.views.src 图片地址(仅image/qrcode类型)
 * @property {Object} json.views.css 元素样式
 * 
 * @example <up-poster :json="posterJson"></up-poster>
 */

import {
	rpx2px
} from '../../libs/function/index.js';
export default {
	name: 'up-poster',
	props: {
		json: {
			type: Object,
			default: () => ({})
		}
	},
	data() {
		return {
			canvasId: 'u-poster-canvas-' + Date.now(),
			showCanvas: false,
			canvasWidth: 0,
			canvasHeight: 0,
			// 二维码相关数据
			qrCodeValue: '',
			qrCodeSize: 200,
			qrCodeShow: false,
			// 存储多个二维码的数据
			qrCodeMap: new Map()
		}
	},
	computed: {
		// 根据传入的css生成文本样式
		getTextStyle() {
			return (css) => {
				const style = {};
				if (css.color) style.color = css.color;
				if (css.fontSize) style.fontSize = css.fontSize;
				if (css.fontWeight) style.fontWeight = css.fontWeight;
				if (css.lineHeight) style.lineHeight = css.lineHeight;
				if (css.textAlign) style.textAlign = css.textAlign;
				return style;
			}
		}
	},
	methods: {
		/**
		 * 导出海报图片
		 * @description 根据json配置生成海报并导出为临时图片路径
		 * @returns {Promise<Object>} 返回包含图片信息的对象
		 * @author jry ijry@qq.com
		 */
		async exportImage() {
			return new Promise(async(resolve, reject) => {
                try {
                    // 获取海报尺寸信息
                    const posterSize = this.json.css;
                    // 将rpx转换为px
                    const width = this.convertRpxToPx(posterSize.width || '750rpx');
                    const height = this.convertRpxToPx(posterSize.height || '1114rpx');
                    
                    // 设置canvas尺寸
                    this.canvasWidth = width;
                    this.canvasHeight = height;
                    this.showCanvas = true;
                    
                    // 等待DOM更新
                    await this.$nextTick();
                    
                    // 创建canvas上下文
                    const ctx = uni.createCanvasContext(this.canvasId, this);
                    
                    // 绘制背景
                    if (posterSize.background) {
                        // 支持渐变背景色
						if (posterSize.background.includes('linear-gradient') || posterSize.background.includes('radial-gradient')) {
							this.drawGradientBackground(ctx, posterSize, 0, 0, width, height);
						} else {
							ctx.setFillStyle(posterSize.background);
                            ctx.fillRect(0, 0, width, height);
						}
                    }
                    
                    // 绘制所有元素
                    for (const item of this.json.views) {
                        await this.drawItem(ctx, item, width, height);
                    }
                    
                    // 绘制到canvas
                    ctx.draw(false, () => {
                        // 等待绘制完成
                        setTimeout(() => {
                            // 导出图片
                            uni.canvasToTempFilePath({
                                canvasId: this.canvasId,
                                success: (res) => {
                                    // 隐藏canvas
                                    this.showCanvas = false;
                                    // 返回图片路径
                                    resolve({
                                        width: width,
                                        height: height,
                                        path: res.tempFilePath,
                                        // H5下添加blob格式
                                        blob: this.dataURLToBlob(res.tempFilePath)
                                    });
                                },
                                fail: (err) => {
                                    // 隐藏canvas
                                    this.showCanvas = false;
                                    reject(new Error('导出图片失败: ' + JSON.stringify(err)));
                                }
                            }, this);
                        }, 300);
                    });
                    
                    // 超时处理
                    setTimeout(() => {
                        this.showCanvas = false;
                        reject(new Error('导出图片超时'));
                    }, 10000);
                } catch (error) {
                    this.showCanvas = false;
                    reject(error);
                }
			});
		},
		
		/**
		 * 绘制单个元素
		 * @description 根据元素类型绘制文本、图片、矩形或二维码到canvas
		 * @param {Object} ctx canvas上下文
		 * @param {Object} item 元素配置信息
		 * @param {Number} canvasWidth canvas宽度
		 * @param {Number} canvasHeight canvas高度
		 * @returns {Promise} 绘制完成的Promise
		 * @author jry ijry@qq.com
		 */
		async drawItem(ctx, item, canvasWidth, canvasHeight) {
			const css = item.css || {};
			const left = this.convertRpxToPx(css.left || '0rpx');
			const top = this.convertRpxToPx(css.top || '0rpx');
			const width = this.convertRpxToPx(css.width || '0rpx');
			const height = this.convertRpxToPx(css.height || '0rpx');
			
			switch (item.type) {
				case 'view':
					// 绘制矩形背景
					if (css.background) {
						// 支持渐变背景色
						if (css.background.includes('linear-gradient') || css.background.includes('radial-gradient')) {
							this.drawGradientBackground(ctx, css, left, top, width, height);
						} else {
							ctx.setFillStyle(css.background);
							// 处理圆角
							if (css.radius) {
								const radius = this.convertRpxToPx(css.radius);
								this.drawRoundRect(ctx, left, top, width, height, radius, css.background);
							} else {
								ctx.fillRect(left, top, width, height);
							}
						}
					}
					break;
					
				case 'text':
					// 设置文本样式
					if (css.color) ctx.setFillStyle(css.color);
					if (css.fontSize) {
						const fontSize = this.convertRpxToPx(css.fontSize);
						ctx.setFontSize(fontSize);
					}
					if (css.fontWeight) {
						ctx.setLineWidth(css.fontWeight === 'bold' ? 2 : 1);
					}
					
					// 处理文本换行
					if (css.lineClamp) {
						this.drawTextWithLineClamp(ctx, item.text, left, top, width, css);
					} else {
						// 修复：文本垂直居中对齐问题
						const textBaseLine = css.fontSize ? this.convertRpxToPx(css.fontSize) / 2 : 10;
						ctx.fillText(item.text, left, top + textBaseLine);
					}
					break;
					
				case 'image':
					// 绘制图片
					return new Promise((resolve) => {
						uni.getImageInfo({
							src: item.src,
							success: (res) => {
								// console.log('图片加载成功: ' + item.src, res);
								// 处理圆角
								if (css.radius) {
									const radius = this.convertRpxToPx(css.radius);
									this.clipRoundRect(ctx, left, top, width, height, radius);
								}
								// 不能用item.src，要用res.path。
								ctx.drawImage(res.path, left, top, width, height);
								// 恢复剪切区域
								ctx.restore();
								resolve();
							},
							fail: (e) => {
								// 图片加载失败时绘制占位符
								ctx.setFillStyle('#f5f5f5');
								ctx.fillRect(left, top, width, height);
								console.log('图片加载失败: ' + item.src, e);
								resolve();
							}
						});
					});
					
				case 'qrcode':
					// 绘制二维码
					if (item.text) {
						// 使用u-qrcode生成二维码图片
						const qrCodeImageUrl = await this.generateQRCode(item.text, width, height);
						return new Promise((resolve) => {
							uni.getImageInfo({
								src: qrCodeImageUrl,
								success: (res) => {
									ctx.drawImage(res.path, left, top, width, height);
									resolve();
								},
								fail: () => {
									// 二维码加载失败时绘制占位符
									ctx.setFillStyle('#f5f5f5');
									ctx.fillRect(left, top, width, height);
									ctx.setFillStyle('#999');
									ctx.setFontSize(12);
									ctx.setTextAlign('center');
									ctx.fillText('QR', left + width/2, top + height/2);
									ctx.setTextAlign('left');
									resolve();
								}
							});
						});
					} else {
						// 绘制二维码占位符
						ctx.setFillStyle('#f5f5f5');
						ctx.fillRect(left, top, width, height);
						ctx.setFillStyle('#999');
						ctx.setFontSize(12);
						ctx.setTextAlign('center');
						ctx.fillText('QR', left + width/2, top + height/2);
						ctx.setTextAlign('left');
					}
					break;
			}
		},
		
		/**
		 * 绘制圆角矩形
		 * @description 绘制指定位置和尺寸的圆角矩形
		 * @param {Object} ctx canvas上下文
		 * @param {Number} x x坐标
		 * @param {Number} y y坐标
		 * @param {Number} width 宽度
		 * @param {Number} height 高度
		 * @param {Number} radius 圆角半径
		 * @param {String} fillColor 填充颜色
		 * @author jry ijry@qq.com
		 */
		drawRoundRect(ctx, x, y, width, height, radius, fillColor) {
			ctx.save();
			ctx.beginPath();
			ctx.moveTo(x + radius, y);
			ctx.lineTo(x + width - radius, y);
			ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
			ctx.lineTo(x + width, y + height - radius);
			ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
			ctx.lineTo(x + radius, y + height);
			ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
			ctx.lineTo(x, y + radius);
			ctx.quadraticCurveTo(x, y, x + radius, y);
			ctx.closePath();
			if (fillColor) {
				ctx.setFillStyle(fillColor);
				ctx.fill();
			}
			ctx.restore();
		},
		
		/**
		 * 裁剪圆角矩形区域
		 * @description 在canvas上创建圆角矩形裁剪区域
		 * @param {Object} ctx canvas上下文
		 * @param {Number} x x坐标
		 * @param {Number} y y坐标
		 * @param {Number} width 宽度
		 * @param {Number} height 高度
		 * @param {Number} radius 圆角半径
		 * @author jry ijry@qq.com
		 */
		clipRoundRect(ctx, x, y, width, height, radius) {
			ctx.save();
			ctx.beginPath();
			ctx.arc(x + radius, y + radius, radius, Math.PI, Math.PI * 1.5);
			ctx.lineTo(x + width - radius, y);
			ctx.arc(x + width - radius, y + radius, radius, Math.PI * 1.5, Math.PI * 2);
			ctx.lineTo(x + width, y + height - radius);
			ctx.arc(x + width - radius, y + height - radius, radius, 0, Math.PI * 0.5);
			ctx.lineTo(x + radius, y + height);
			ctx.arc(x + radius, y + height - radius, radius, Math.PI * 0.5, Math.PI);
			ctx.closePath();
			ctx.clip();
		},
		
		/**
		 * 绘制带行数限制的文本
		 * @description 绘制可控制最大行数的文本，超出部分显示省略号
		 * @param {Object} ctx canvas上下文
		 * @param {String} text 文本内容
		 * @param {Number} x x坐标
		 * @param {Number} y y坐标
		 * @param {Number} maxWidth 最大宽度
		 * @param {Object} css 样式配置
		 * @author jry ijry@qq.com
		 */
		drawTextWithLineClamp(ctx, text, x, y, maxWidth, css) {
            const lineClamp = parseInt(css.lineClamp) || 1;
            const lineHeight = css.lineHeight ? this.convertRpxToPx(css.lineHeight) : 20;
            const lines = [];
            let currentLine = '';
            
            for (let i = 0; i < text.length; i++) {
                const char = text[i];
                const testLine = currentLine + char;
                const metrics = ctx.measureText(testLine);
                
                if (metrics.width > maxWidth && currentLine !== '') {
                    lines.push(currentLine);
                    currentLine = char;
                    
                    // 如果已达最大行数，添加省略号并结束
                    if (lines.length === lineClamp) {
                        if (metrics.width > maxWidth) {
                            // 添加省略号
                            let fitLine = currentLine.substring(0, currentLine.length - 1);
                            while (ctx.measureText(fitLine + '...').width > maxWidth && fitLine.length > 0) {
                                fitLine = fitLine.substring(0, fitLine.length - 1);
                            }
                            lines[lines.length - 1] = fitLine + '...';
                        }
                        break;
                    }
                } else {
                    currentLine = testLine;
                }
                
                // 处理最后一行
                if (i === text.length - 1 && lines.length < lineClamp) {
                    lines.push(currentLine);
                }
            }
            
            // 绘制每一行
            for (let i = 0; i < lines.length; i++) {
                // 修复：正确计算文本垂直位置
                const textBaseLine = css.fontSize ? this.convertRpxToPx(css.fontSize) / 2 : 10;
                ctx.fillText(lines[i], x, y + (i * lineHeight) + textBaseLine);
            }
        },
		
		/**
		 * 生成二维码图片
		 * @description 根据文本内容生成二维码图片URL
		 * @param {String} text 二维码内容
		 * @param {Number} width 二维码宽度
		 * @param {Number} height 二维码高度
		 * @returns {Promise<String>} 二维码图片URL
		 * @author jry ijry@qq.com
		 */
		generateQRCode(text, width, height) {
			return new Promise((resolve) => {
				// 为每个二维码生成唯一标识
				const qrCodeKey = `${text}_${width}_${height}`;
				
				// 检查是否已经生成过该二维码
				if (this.qrCodeMap.has(qrCodeKey)) {
					resolve(this.qrCodeMap.get(qrCodeKey));
					return;
				}
				
				// 使用 u-qrcode 组件生成二维码
				try {
					// 设置二维码参数
					this.qrCodeValue = text;
					this.qrCodeSize = Math.max(width, height);
					this.qrCodeShow = true;
					
					// 等待DOM更新
					this.$nextTick(() => {
						// 获取二维码组件实例并导出图片
						if (this.$refs.qrCode) {
							// 延迟一点时间确保二维码渲染完成
							setTimeout(() => {
								// 调用 u-qrcode 的 toTempFilePath 方法导出图片
								this.$refs.qrCode.toTempFilePath({
									success: (res) => {
										// 缓存二维码图片路径
										this.qrCodeMap.set(qrCodeKey, res.tempFilePath);
										this.qrCodeShow = false;
										resolve(res.tempFilePath);
									},
									fail: (err) => {
										console.error('二维码生成失败:', err);
										this.qrCodeShow = false;
									}
								});
							}, 300);
						} else {
							// 如果没有 u-qrcode 组件，返回占位符
							this.qrCodeShow = false;
						}
					});
				} catch (error) {
					console.error('生成二维码出错:', error);
					this.qrCodeShow = false;
				}
			});
		},
		
		/**
		 * 将rpx单位转换为px
		 * @description 根据屏幕密度将rpx单位转换为px单位
		 * @param {String|Number} rpxValue rpx值
		 * @returns {Number} 转换后的px值
		 * @author jry ijry@qq.com
		 */
		convertRpxToPx(rpxValue) {
			if (typeof rpxValue === 'number') return rpxValue;
			
			// 使用rpx2px方法
			if (typeof rpxValue === 'string' && rpxValue.endsWith('rpx')) {
				const value = parseFloat(rpxValue);
				return rpx2px(value);
			}
			
			return parseFloat(rpxValue) || 0;
		},
		
		/**
		 * 绘制渐变背景
		 * @description 绘制线性渐变或径向渐变背景
		 * @param {Object} ctx canvas上下文
		 * @param {Object} css 样式配置
		 * @param {Number} left 左边距
		 * @param {Number} top 上边距
		 * @param {Number} width 宽度
		 * @param {Number} height 高度
		 * @author jry ijry@qq.com
		 */
		drawGradientBackground(ctx, css, left, top, width, height) {
			const background = css.background;
			let gradient = null;
			
			// 处理线性渐变
			if (background.includes('linear-gradient')) {
				// 解析线性渐变角度和颜色
				const angleMatch = background.match(/linear-gradient\((\d+)deg/);
				const angle = angleMatch ? parseInt(angleMatch[1]) : 135;
				
				// 根据角度计算渐变起点和终点
				let startX = left, startY = top, endX = left + width, endY = top + height;
				
				// 简化的角度处理（支持常见角度）
				if (angle === 0) {
					startX = left;
					startY = top + height;
					endX = left;
					endY = top;
				} else if (angle === 90) {
					startX = left;
					startY = top;
					endX = left + width;
					endY = top;
				} else if (angle === 180) {
					startX = left;
					startY = top;
					endX = left;
					endY = top + height;
				} else if (angle === 270) {
					startX = left + width;
					startY = top;
					endX = left;
					endY = top;
				}
				
				gradient = ctx.createLinearGradient(startX, startY, endX, endY);
				
				// 解析颜色值
				const colorMatches = background.match(/#[0-9a-fA-F]+|rgba?\([^)]+\)/g);
				if (colorMatches && colorMatches.length >= 2) {
					// 添加渐变色点
					colorMatches.forEach((color, index) => {
						const stop = index / (colorMatches.length - 1);
						gradient.addColorStop(stop, color);
					});
				}
			} 
			// 处理径向渐变
			else if (background.includes('radial-gradient')) {
				// 径向渐变从中心开始
				const centerX = left + width / 2;
				const centerY = top + height / 2;
				const radius = Math.min(width, height) / 2;
				
				gradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, radius);
				
				// 解析颜色值
				const colorMatches = background.match(/#[0-9a-fA-F]+|rgba?\([^)]+\)/g);
				if (colorMatches && colorMatches.length >= 2) {
					// 添加渐变色点
					colorMatches.forEach((color, index) => {
						const stop = index / (colorMatches.length - 1);
						gradient.addColorStop(stop, color);
					});
				}
			}
			
			if (gradient) {
				ctx.setFillStyle(gradient);
				// 处理圆角
				if (css.radius) {
					const radius = this.convertRpxToPx(css.radius);
					this.drawRoundRect(ctx, left, top, width, height, radius, gradient);
				} else {
					ctx.fillRect(left, top, width, height);
				}
			}
		},
		
		/**
		 * 将dataURL转换为Blob
		 * @description H5环境下将base64格式的dataURL转换为Blob对象
		 * @param {String} dataURL base64格式的图片数据
		 * @returns {Blob} Blob对象
		 * @author jry ijry@qq.com
		 */
		dataURLToBlob(dataURL) {
			// 检查是否为H5环境且是base64数据
			// #ifdef H5
			if (dataURL && dataURL.startsWith('data:image')) {
				const parts = dataURL.split(';base64,');
				const contentType = parts[0].split(':')[1];
				const raw = window.atob(parts[1]);
				const rawLength = raw.length;
				const uInt8Array = new Uint8Array(rawLength);
				
				for (let i = 0; i < rawLength; ++i) {
					uInt8Array[i] = raw.charCodeAt(i);
				}
				
				return new Blob([uInt8Array], { type: contentType });
			}
			// #endif
			
			return null;
		},
	}
}
</script>

<style lang="scss" scoped>
.up-poster {
	position: relative;
	
	&__canvas {
		position: relative;
		overflow: hidden;
	}
	
	&__hidden-canvas {
		position: fixed;
		top: -10000px;
		left: -10000px;
		z-index: -1;
	}
	
	&__hidden-qrcode {
		position: fixed;
		top: -10000px;
		left: -10000px;
		z-index: -1;
		
		&--hidden {
			display: none;
		}
	}
}
</style>