<template>
  <view class="word-cloud-container">
    <!-- canvas组件，适配小程序 -->
  <canvas 
    type="2d" 
    ref="wordCloudCanvas" 
    id="wordCloudCanvas" 
    class="word-cloud-canvas"
    :style="{width: canvasWidth + 'px', height: canvasHeight + 'px'}"
  ></canvas>
  </view>
</template>

<script>
export default {
  name: "WordCloud",
  props: {
    // 词云数据 [{text: '关键词', value: 100}, ...]
    wordData: {
      type: Array,
      required: true,
      default: () => []
    },
    // 画布宽度
    width: {
      type: Number,
      default: 300
    },
    // 画布高度
    height: {
      type: Number,
      default: 300
    },
    // 文字颜色列表（分层配色）
    colorList: {
      type: Array,
      default: () => ['#60A5FA', '#FEC200', '#EF4444'] // 外圈、中间、中心
    },
    // 新增：字号配置，让组件更灵活
    fontSizeConfig: {
      type: Object,
      default: () => ({
        minSize: 12,    // 最小字号
        maxSize: 40,    // 最大字号
        scaleFactor: 0.1 // 缩放因子，越大字号差异越明显
      })
    }
  },
  data() {
    return {
      canvasWidth: this.width,
      canvasHeight: this.height,
      ctx: null, // canvas 2d 上下文
      placedWords: [] // 已放置的文字信息（用于碰撞检测）
    };
  },
  watch: {
    wordData: {
      handler() {
        this.drawWordCloud();
      },
      deep: true
    }
  },
  mounted() {
    this.initCanvas();
  },
  methods: {
    // 初始化canvas
    async initCanvas() {
      // 修复点1：增加延迟，确保canvas节点渲染完成（兼容小程序渲染时机）
      await new Promise(resolve => setTimeout(resolve, 50));
      
      // 适配小程序获取canvas上下文
      const query = uni.createSelectorQuery().in(this);
      // 修复点2：使用ref选择器（.ref-wordCloudCanvas）替代ID选择器，或给canvas加id
      query.select('.word-cloud-canvas') // 改用class选择器，与模板中canvas的class对应
        .fields({ node: true, size: true })
        .exec(async (res) => {
          // 修复点3：增加空值判断，避免res[0]为null时报错
          if (!res || !res[0] || !res[0].node) {
            console.error('获取canvas节点失败，请检查canvas是否正确渲染');
            return;
          }
          
          const canvas = res[0].node;
          let ctx = null;
          
          // 修复点4：兼容不同小程序平台的canvas 2d上下文获取
          try {
            ctx = canvas.getContext('2d');
          } catch (e) {
            console.warn('获取2d上下文失败，尝试兼容处理', e);
            // 部分小程序平台需要先初始化canvas 2d
            ctx = uni.createCanvasContext('wordCloudCanvas', this);
          }
          
          if (!ctx) {
            console.error('无法获取canvas 2d上下文');
            return;
          }
          
          // 设置canvas尺寸
          const dpr = uni.getSystemInfoSync().pixelRatio || 1;
          canvas.width = this.canvasWidth * dpr;
          canvas.height = this.canvasHeight * dpr;
          ctx.scale(dpr, dpr);
          
          this.ctx = ctx;
          this.drawWordCloud();
        });
    },
    
    // 绘制词云核心方法
    drawWordCloud() {
      if (!this.ctx || !this.wordData.length) return;
      
      // 清空画布和已放置文字记录
      this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
      this.placedWords = [];
      
      // 按权重排序（权重越大，文字越大）
      const sortedWords = [...this.wordData].sort((a, b) => b.value - a.value);
      
      // 计算value的最大值和最小值，用于归一化
      const values = sortedWords.map(item => item.value);
      this.valueMax = Math.max(...values);
      this.valueMin = Math.min(...values);
      
      // 逐个绘制文字
      sortedWords.forEach((word, index) => {
        this.placeWord(word, index);
      });
    },
    
    // 放置单个文字（核心：碰撞检测，确保不重叠）
    placeWord(word, index) {
      const ctx = this.ctx;
      // 优化1：提高最大尝试次数（从50→150），让更多文字能找到位置
      const maxAttempts = 150; 
      const { minSize, maxSize, scaleFactor } = this.fontSizeConfig;
      
      // ===== 核心优化：重新设计字号计算逻辑 =====
      // 1. 归一化value（0-1区间）
      let normalizedValue = 1;
      if (this.valueMax !== this.valueMin) {
        normalizedValue = (word.value - this.valueMin) / (this.valueMax - this.valueMin);
      }
      
      // 2. 计算字号：基于归一化值，确保最小到最大的平滑过渡
      // 公式：最小字号 + (最大字号 - 最小字号) * 归一化值 * 缩放因子
      const fontSize = Math.min(
        minSize + (maxSize - minSize) * normalizedValue * scaleFactor,
        maxSize
      );
      // ==========================================
      
      // 旋转角度：-60° 到 60°
      const rotateAngle = (Math.random() - 0.5) * 120 * Math.PI / 180;
      
      // 设置字体样式
      ctx.font = `${fontSize}px sans-serif`;
      
      // 获取文字宽度和高度（用于碰撞检测）
      const textWidth = ctx.measureText(word.text || word.name).width; // 兼容text/name字段
      // 优化3：更精准的文字高度估算（从1.2→1.05），减少无效空间
      const textHeight = fontSize * 1.05; 
      
      // 尝试放置文字，直到找到不重叠的位置
      for (let i = 0; i < maxAttempts; i++) {
        // 优化4：扩大随机位置范围（从0.2-0.8→0.05-0.95），利用边缘空间
        const x = this.canvasWidth * 0.05 + Math.random() * this.canvasWidth * 0.9;
        const y = this.canvasHeight * 0.05 + Math.random() * this.canvasHeight * 0.9;
        
        // 碰撞检测：检查当前位置是否与已放置的文字重叠（传入间距容差）
        const isOverlap = this.checkOverlap(x, y, textWidth, textHeight, rotateAngle, 2); // 间距容差2px
        
        if (!isOverlap) {
          // ===== 核心修改：按位置分层设置颜色 =====
          // 1. 计算画布中心坐标
          const centerX = this.canvasWidth / 2;
          const centerY = this.canvasHeight / 2;
          
          // 2. 计算当前文字位置到中心的距离（欧几里得距离）
          const distance = Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2));
          
          // 3. 计算画布最大半径（中心到角落的距离）
          const maxDistance = Math.sqrt(Math.pow(centerX, 2) + Math.pow(centerY, 2));
          
          // 4. 按距离分三层分配颜色
          let color;
          if (distance > maxDistance * 0.66) {
            // 外圈（距离>2/3最大半径）：#60A5FA
            color = this.colorList[0];
          } else if (distance > maxDistance * 0.33) {
            // 中间层（距离>1/3最大半径）：#FEC200
            color = this.colorList[1];
          } else {
            // 中心层（距离≤1/3最大半径）：#EF4444
            color = this.colorList[2];
          }
          // =========================================
          
          // 设置文字颜色
          ctx.fillStyle = color;
          
          // 无重叠，绘制文字
          this.drawTextAtPosition(word.text || word.name, x, y, rotateAngle, fontSize); 
          
          // 记录已放置的文字信息（用于后续碰撞检测）
          this.placedWords.push({
            x, y, width: textWidth, height: textHeight, angle: rotateAngle
          });
          break;
        }
      }
    },
    
    // 碰撞检测：检查当前文字是否与已放置的文字重叠（新增间距容差参数）
    checkOverlap(x, y, width, height, angle, gap = 2) {
      // 简化碰撞检测：使用包围盒检测（旋转后的矩形包围盒）
      const currentRect = this.getBoundingRect(x, y, width, height, angle, gap);
      
      for (const placed of this.placedWords) {
        const placedRect = this.getBoundingRect(placed.x, placed.y, placed.width, placed.height, placed.angle, gap);
        
        // 轴对齐包围盒（AABB）碰撞检测
        if (
          currentRect.left < placedRect.right &&
          currentRect.right > placedRect.left &&
          currentRect.top < placedRect.bottom &&
          currentRect.bottom > placedRect.top
        ) {
          return true; // 重叠
        }
      }
      return false; // 不重叠
    },
    
    // 获取旋转后文字的包围盒（新增间距容差参数，缩小包围盒）
    getBoundingRect(x, y, width, height, angle, gap = 2) {
      // 计算旋转后的四个顶点
      const cos = Math.cos(angle);
      const sin = Math.sin(angle);
      
      // 优化5：缩小包围盒（减去间距容差），让文字间距更小
      const halfW = (width - gap) / 2;
      const halfH = (height - gap) / 2;
      
      // 四个顶点坐标（相对于中心）
      const points = [
        { x: -halfW, y: -halfH },
        { x: -halfW, y: halfH },
        { x: halfW, y: halfH },
        { x: halfW, y: -halfH }
      ];
      
      // 旋转并平移到实际位置
      const rotatedPoints = points.map(point => ({
        x: x + point.x * cos - point.y * sin,
        y: y + point.x * sin + point.y * cos
      }));
      
      // 计算包围盒的最小/最大坐标
      const left = Math.min(...rotatedPoints.map(p => p.x));
      const right = Math.max(...rotatedPoints.map(p => p.x));
      const top = Math.min(...rotatedPoints.map(p => p.y));
      const bottom = Math.max(...rotatedPoints.map(p => p.y));
      
      return { left, right, top, bottom };
    },
    
    // 在指定位置绘制旋转后的文字
    drawTextAtPosition(text, x, y, angle, fontSize) {
      const ctx = this.ctx;
      
      // 保存当前画布状态
      ctx.save();
      
      // 平移到文字中心位置
      ctx.translate(x, y);
      // 旋转
      ctx.rotate(angle);
      
      // 绘制文字（居中对齐）
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText(text, 0, 0);
      
      // 恢复画布状态
      ctx.restore();
    }
  }
};
</script>

<style scoped>
.word-cloud-container {
  width: 100%;
  height: 100%;
}
.word-cloud-canvas {
  width: 100%;
  height: 100%;
}
</style>
