import { throttle, debounce, getFileNameFromUrl } from "@/util";
import { ElMessage, ElMessageBox, ElLoading } from "element-plus";
import { confirm } from "@/helper/message";
import { jsPDF } from 'jspdf'
import html2canvas from "html2canvas";
import JSZip from 'jszip'
import { saveAs } from 'file-saver'
import { casePhotodel } from "@/store/case";
/**
 * Canvas 照片排版编辑器类（拖拽不重绘 + 纯坐标移动版）
 * 核心：1. 拖拽仅改坐标，松开再重绘 2. 纯Canvas坐标移动 3. 无transform
 */
export class CanvasPhotoEditor {
  constructor(canvas, options = {}) {
    // 基础配置
    this.canvas = canvas
    this.ctx = canvas.getContext('2d')
    this.ctxOld = null
    this.rqWidth = 370
    this.rqHeight = 250
    this.pageWidth = options.pageWidth || 600
    this.pageHeight = options.pageHeight || 840
    this.pageMargin = options.pageMargin || 4
    this.imgMargin = 10
    this.titleHieght = 50
    this.canvasBgColor = options.canvasBgColor || '#efefef'
    this.pageBgColor = options.pageBgColor || '#ffffff'
    this.selectedPageColor = options.selectedPageColor || '#26559B'
    this.photos = options.photos || []
    this.show = options.show || false // true 纯显示，不编辑
    this.pageCount = options.pageCount || 0 // true 纯显示，不编辑

    // 响应式数据
    this._layoutMode = 'double'//全局的页面布局
    this._pages = [{
      list: [null],
      layoutMode: this._layoutMode,//排版模式
      coordinate: [],//坐标信息
    }]
    this._selectedPageItem = {
      index: -1,
      pageIndex: -1,
    } //选中页面的图片
    this._scale = options.scale || 0.8
    this._scaleMin = 0.1
    this._scaleMax = 5.0
    this._selectedPageIndex = -1


    // 核心：绘制坐标偏移（替代transform）
    this.drawOffsetX = 0
    this.drawOffsetY = 0

    // 拖拽状态
    this.isDragging = false
    this.dragStartX = 0
    this.dragStartY = 0
    this.lastDrawOffsetX = 0
    this.lastDrawOffsetY = 0
    // 页面拖拽排序
    this.isPageDragging = false
    this.dragPageStartY = 0
    this.dragPageMoved = false
    // 动态标引状态
    this.tempArrow = {
      start: null,    // { x, y }
      end: null,      // { x, y }
      drawing: false  // 是否正在绘制
    };
    //修改pages页码
    this.dragPageData = {
      drawing: false,//是否正在绘制页面移动
      index: -1,
      start: null,    // { x, y }
      end: null,      // { x, y }
      movedIndex: -1,//移动到的索引位置
    }
    //标引状态
    this.indexing = false//是否开启
    this.indexingNum = 0 //0 未点击  1 已生成开始点位 2 结束点位已生成
    this.indexingStartX = 0
    this.indexingStartY = 0
    this.indexingEndX = 0
    this.indexingEndY = 0
    this.indexingList = []//是否开启
    this.indexingLineList = []//绘制线条
    this.isLineDel = false//删除标引
    this.clearIndexing = options.clearIndexing//清除父级标引状态
    // 图片缓存
    this.imgCache = new Map()
    this.setImgTitle = {}
    // 操作缓存
    this.history = []
    this.isHistory = true
    this.currentIndex = -1;   // 当前在第几步
    // 绑定事件
    this.handleMouseDown = this.handleMouseDown.bind(this)
    this.handleMouseMove = this.handleMouseMove.bind(this)
    this.handleMouseUp = this.handleMouseUp.bind(this)
    this.handleMouseLeave = this.handleMouseLeave.bind(this)
    this.handleWheel = this.handleWheel.bind(this)

    // 初始化
    this.updata = options.updata
    this.init()
  }

  // --- 数据 getter/setter ---
  get pages() { return this._pages }
  set pages(newPages) {
    console.log('isArrayEqual1', this.indexingLineList, newPages)

    // const isSave = this._pages.length !== newPages.length?true:!this._pages.some((ele, index) => this.isArrayEqual(ele.list, newPages[index].list))
    this._pages = newPages
    this.resizeCanvas()
    this.drawAllPages() // 仅页面数据变化时重绘
    if (this.isHistory) {
      this.saveHistory()
    }
  }
  // --- 数据 getter/setter ---
  get selectedPageItem() { return this._selectedPageItem }
  set selectedPageItem(newItem) {
    this._selectedPageItem = { ...newItem }
    this.updata({
      selectedPageItem: this._selectedPageItem,
      selectedPageIndex: this.selectedPageIndex,
      historylength: this.history.length,
      currentIndex: this.currentIndex
    })
  }
  get scale() { return this._scale }
  set scale(newScale) {
    this._scale = Math.max(this._scaleMin, Math.min(this._scaleMax, newScale))
    this.drawAllPages() // 缩放时必须重绘（无法通过坐标偏移实现）
  }

  get selectedPageIndex() { return this._selectedPageIndex }
  set selectedPageIndex(newIndex) {
    this._selectedPageIndex = newIndex
    this.updata({
      selectedPageItem: this._selectedPageItem,
      selectedPageIndex: newIndex,
      historylength: this.history.length,
      currentIndex: this.currentIndex
    })
  }

  get layoutMode() { return this._layoutMode }
  set layoutMode(mode) {
    this._layoutMode = mode
    this.drawDragPreview()
  }

  // --- 初始化 ---
  init() {
    // 绑定事件
    this.canvas.addEventListener('wheel', this.handleWheel)//滚轮事件
    this.canvas.addEventListener('mousedown', this.handleMouseDown)//按下鼠标按键时触发
    document.addEventListener('mousemove', this.handleMouseMove)//移动鼠标时触发
    document.addEventListener('mouseup', this.handleMouseUp)//释放鼠标按键时触发
    document.addEventListener('mouseleave', this.handleMouseLeave)//离开触发

    // 初始化画布
    this.resizeCanvas()
    this.resetPosition()
    this.drawDragPreview() // 仅初始化时重绘一次
    this.saveHistory()
  }

  bindScrollWrapper(wrapper) {
    this.scrollWrapper = wrapper
    // this.drawDragPreview() // 仅初始化时重绘一次
    this.resizeCanvas()//更换画布和页面大小
    // 监听容器尺寸变化
    const resizeObserver = new ResizeObserver(() => {
      this.resetPosition()
    })
    resizeObserver.observe(wrapper)
  }

  destroy() {
    this.canvas.removeEventListener('wheel', this.handleWheel)//滚轮事件
    this.canvas.removeEventListener('mousedown', this.handleMouseDown)//按下鼠标按键时触发
    document.removeEventListener('mousemove', this.handleMouseMove)//移动鼠标时触发
    document.removeEventListener('mouseup', this.handleMouseUp)//释放鼠标按键时触发
    document.removeEventListener('mouseleave', this.handleMouseLeave)//离开触发
    this.imgCache.clear()
  }

  // --- 调整画布尺寸 ---
  resizeCanvas() {
    if (!this.canvas || !this.scrollWrapper) return

    // Canvas铺满容器
    this.canvas.width = this.scrollWrapper.clientWidth || this.pageWidth
    this.canvas.height = this.scrollWrapper.clientHeight || this.pageHeight

    // 移除所有transform样式
    this.canvas.style.transform = 'none'
    this.canvas.style.transformOrigin = '0 0'
  }

  // --- 核心：拖拽仅改坐标，不重绘 ---
  async handleMouseDown(e) {
    this.isDragging = true
    console.log('handleMouseDown', e.button, this.tempArrow)
    if (e.button === 2 && this.indexing) { // 右键
      //结束标引
      this.clearIndexing()
      e.preventDefault()
      return
    }
    if (e.target !== this.canvas) return
    // ==========================================
    // 🔥 页面拖拽排序：按下时记录
    // ==========================================
    const rect = this.canvas.getBoundingClientRect()
    const mouseX = (e.clientX - rect.left - this.drawOffsetX) / this.scale
    const mouseY = (e.clientY - rect.top - this.drawOffsetY) / this.scale
    if (this.dragPageData.drawing) {
      this.dragPageData.end = { x: mouseX, y: mouseY }
      this.drawAllPages()
      return
    }
    // 记录拖拽起始状态
    if (this.indexing) {
      if (this.indexingNum == 0) return this.starIindexing(e)
      if (this.indexingNum == 1) return this.endIindexing(e)
      return
    }
    const pageIndex = this.getPageIndexByClick(e)
    console.log('getPageIndexByClick', pageIndex, this.pages[pageIndex])

    if(!this.show && pageIndex > -1 && this.setImgTitle.titleIndex>-1 && this.pages[pageIndex].list && this.pages[pageIndex].list[this.setImgTitle.titleIndex]){//点击标题进行修改标题名称
      let item = this.pages[pageIndex].item[this.setImgTitle.titleIndex]
      if(!item.id) return
      return ElMessageBox.prompt("", "修改名称", {
        confirmButtonText: "确定",
        customClass: "promptClass",
        cancelButtonText: "取消",
        showClose: true,
        inputValue: item.name,
        inputValidator: (value) => {
          if (!value) {
            return false;
          }
          return true;
        },
        inputErrorMessage: "请输入名称",
      })
        .then(async ({ value }) => {
          this.pages[pageIndex].item[this.setImgTitle.titleIndex] = {
            ...item,
            name: value,
          }
          ElMessage({
            type: "success",
            message: `修改成功`,
          });
          this.drawAllPages()
          return
        })
        .catch(() => {
        });
    }
    const oldPageIndex = this.selectedPageIndex; // 保存旧选中页
    if (!this.show && pageIndex != undefined && pageIndex != -1 && (pageIndex !== this.selectedPageIndex || this._selectedPageItem.pageIndex != -1)) {
      // 1. 清除旧页面的边框
      // this.clearOldBorder(oldPageIndex);
      this.selectedPageIndex = pageIndex
    } else {
      this.selectedPageIndex = -1
      this.selectedPageItem = {
        ...this.selectedPageItem,
        index: -1,
        pageIndex: -1,
      }
    }
    if(this.isDragging && oldPageIndex == pageIndex && pageIndex == this.selectedPageIndex && pageIndex == this.selectedPageItem.pageIndex && pageIndex != -1 && this.selectedPageItem.index == -1){
      this.dragPageData.start = { x: mouseX, y: mouseY }
      this.dragPageData.drawing = true;
      this.dragPageData.index = pageIndex;
      this.dragPageData.movedIndex = pageIndex;
      this.canvas.style.cursor = 'copy'
      this.drawAllPages()
      return
    }
    this.dragStartX = e.clientX
    this.dragStartY = e.clientY
    this.lastDrawOffsetX = this.drawOffsetX
    this.lastDrawOffsetY = this.drawOffsetY
    this.canvas.style.cursor = 'grabbing'
    console.log('pageIndex', this.pages, this._selectedPageItem, this.selectedPageItem, pageIndex)
    this.drawAllPages()
  }

  async handleMouseMove(e) {
    const drawing = this.tempArrow?.drawing
    const rect = this.canvas.getBoundingClientRect()
    const mouseX = (e.clientX - rect.left - this.drawOffsetX) / this.scale
    const mouseY = (e.clientY - rect.top - this.drawOffsetY) / this.scale
    if (this.indexing && drawing) {
      const PhotoIndex = this.getPhotoPositionInPage(e)
      console.log('PhotoIndex', PhotoIndex, this.tempArrow)
      this.tempArrow.end = {...PhotoIndex, x: mouseX, y: mouseY }
      this.drawAllPages()
      return
    }
    if(this.dragPageData.drawing){
      let endIndex = Math.round(mouseX / this.pageWidth)
      this.dragPageData.end = { x: mouseX, y: mouseY }
      let newEndIndex = endIndex<-1?-1:endIndex>this.pages.length-1?this.pages.length:endIndex
      if(newEndIndex != this.dragPageData.movedIndex){
        await this.handleClearLine()
        this.dragPageData.movedIndex = newEndIndex
        this.drawAllPages()
      }
      return
    }
    if (!this.isDragging) return
    // 仅更新坐标偏移，不触发重绘（核心优化）
    const deltaX = e.clientX - this.dragStartX
    const deltaY = e.clientY - this.dragStartY
    this.drawOffsetX = this.lastDrawOffsetX + deltaX
    this.drawOffsetY = this.lastDrawOffsetY + deltaY
    if(this.indexing && this.indexingNum == 1){
      // 绘制中途轨迹
    }
    // 【关键】通过Canvas临时绘制偏移效果（替代重绘），避免拖拽时画面静止
    this.drawAllPages()
  }
   handleClearLine(){
    throttle(async ()=>{
      if(this.indexingLineList.length){
        await this.checkIndexing('修改页面排序将会清除所有标引是否继续？')
        this.drawAllPages()
        return
      }
    }, 2000)

  }

  async handleMouseUp(e) {
    this.isDragging = false
    this.canvas.style.cursor = 'grab'
    if(this.dragPageData.drawing && this.dragPageData.index != this.dragPageData.movedIndex){//鼠标释放左键时，移动页面到指定位置）
      // 末尾必须加分号！！！
      console.log('handleMouseUpdragPageData', this.dragPageData)
      let newPagelist = this.moveItem(this.pages, this.dragPageData.index, this.dragPageData.movedIndex)
      this.pages = newPagelist;
      this.dragPageData.drawing = false//退出page编辑模式
      this.dragPageData.movedIndex = -1
      this.drawAllPages()
    }
    this.dragPageData.drawing = false
    this.dragPageData.movedIndex = -1
    this.dragPageData.index = 0
    // 松开鼠标后仅重绘一次，确认最终位置
    // this.drawDragPreview()
    // 选中页面（仅点击时重绘一次）
    console.log('pageIndex', this.indexing, this.indexingNum)
  }
  moveItem(arr, fromIndex, toIndex) {
    let newtoIndex = fromIndex < toIndex ? toIndex-1 : toIndex;
    const newArr = [...arr]
    const item = newArr.splice(fromIndex, 1)[0]
    newArr.splice(newtoIndex, 0, item)
    return newArr
  }
  handleMouseLeave() {
    console.log('handleMouseLeave', this.isDragging)
    this.isDragging = false
    this.dragPageData.drawing = false
    this.dragPageData.movedIndex = 0
    this.canvas.style.cursor = 'grab'
    // 离开画布后重绘一次
    this.drawDragPreview()
  }

  // --- 缩放逻辑（缩放必须重绘，无法通过坐标偏移实现）---
  handleWheel(e) {
    console.log('handleWheel', e.target)
    e.preventDefault()

    const rect = this.canvas.getBoundingClientRect()
    const mouseX = e.clientX - rect.left
    const mouseY = e.clientY - rect.top

    const delta = e.deltaY > 0 ? -0.1 : 0.1
    const oldScale = this.scale
    const newScale = Math.max(this._scaleMin, Math.min(this._scaleMax, oldScale + delta))
    const scaleDiff = newScale / oldScale

    // 调整偏移量，保证缩放中心在鼠标位置
    this.drawOffsetX = (mouseX - (mouseX - this.drawOffsetX) * scaleDiff)
    this.drawOffsetY = (mouseY - (mouseY - this.drawOffsetY) * scaleDiff)

    this.scale = newScale
  }
  //绘制标引起点
  starIindexing(e) {
    const rect = this.canvas.getBoundingClientRect()
    const mouseX = (e.clientX - rect.left - this.drawOffsetX) / this.scale
    const mouseY = (e.clientY - rect.top - this.drawOffsetY) / this.scale
    const PhotoIndex = this.getPhotoPositionInPage(e)
    console.log('indexingLineList', this.indexingLineList)
    let isLine = this.indexingLineList.findIndex(item => {
      let points = item.points || [];
      
      // 点数量不足2个，直接不匹配
      if (points.length < 2) return false;

      // 遍历所有相邻两点，判断鼠标是否在任意一段上
      for (let i = 0; i < points.length - 1; i++) {
        const p1 = points[i];
        const p2 = points[i + 1];
        
        // 只要有一段满足，就返回 true
        if (this.isPointOnLine(mouseX, mouseY, p1.x, p1.y, p2.x, p2.y)) {
          return true;
        }
      }

      // 所有线段都不满足
      return false;
    });
    this.isLineDel = false
    if (isLine != -1) {//选中已生成标引
      // 阻止事件冒泡和默认行为，避免鼠标事件持续触发
      e.stopPropagation();
      e.preventDefault();
      this.isLineDel = true
      return ElMessageBox.confirm("确定删除当前选中标引？", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }).then(async () => {
        this.indexingLineList.splice(isLine, 1);
        this.indexingLineList = [...this.indexingLineList];
        this.drawAllPages()
        ElMessage({
          type: "success",
          message: "删除成功",
        });
        this.isLineDel = false
      });
    } else {
      this.isLineDel = false
    }
    console.log('indexing', this.indexing, PhotoIndex, isLine)
    if (PhotoIndex.itemIndex == -1) {
      ElMessage.error("请选中对应的图片进行标引操作！");
      return
    }

    this.indexingNum = 1
    this.indexingList.push(PhotoIndex)
    this.indexingStartX = mouseX
    this.indexingStartY = mouseY
    this.tempArrow.start = { ...PhotoIndex, x: mouseX, y: mouseY }
    this.tempArrow.drawing = true;
    console.log('indexing', this.indexing, PhotoIndex, mouseX, mouseY)
  }
  /**
   * 结束标引操作，计算鼠标在画布上的相对坐标并保存起始位置
   * @param {MouseEvent} e - 鼠标事件对象
   */
  endIindexing(e) {
    const PhotoIndex = this.getPhotoPositionInPage(e)
    if (PhotoIndex.itemIndex == -1) {
      ElMessage.error("请选中标引结束的图片操作！");
      return
    }
    const oldPhotoIndex = this.indexingList[0]
    console.log('oldPhotoIndex', oldPhotoIndex, this.indexingList)
    if (oldPhotoIndex.pageIndex == PhotoIndex.pageIndex && oldPhotoIndex.itemIndex == PhotoIndex.itemIndex) {
      ElMessage.error("标引开始和结束图片不能相同！");
      return
    }
    console.log('indexing1111', this.indexing, oldPhotoIndex.pageIndex - PhotoIndex.pageIndex)
    if (Math.abs(oldPhotoIndex.pageIndex - PhotoIndex.pageIndex) > 1) {
      ElMessage.error("标引不支持跨页面！");
      return
    }
    this.indexingNum = 2
    const rect = this.canvas.getBoundingClientRect()
    const mouseX = (e.clientX - rect.left - this.drawOffsetX) / this.scale
    const mouseY = (e.clientY - rect.top - this.drawOffsetY) / this.scale
    let coordinate = this.pages[PhotoIndex.pageIndex].coordinate[PhotoIndex.itemIndex]
    console.log('coordinate', PhotoIndex, oldPhotoIndex)
    this.indexingEndX = mouseX
    this.indexingEndY = mouseY
    let startInfo = this.indexingList[0]
    this.indexingList.push(PhotoIndex)

    let endIindexing1 = {//默认同页面
      x: this.indexingStartX,
      y: startInfo.itemIndex > PhotoIndex.itemIndex ? (coordinate.y + coordinate.height + 3) : coordinate.y -3,
    }
    if (startInfo.pageIndex != PhotoIndex.pageIndex && startInfo.count == PhotoIndex.count && startInfo.itemIndex == PhotoIndex.itemIndex) {
      //不同页码 item 相同
      endIindexing1 = {//同数量同item
        x: startInfo.pageIndex > PhotoIndex.pageIndex ? (coordinate.x + coordinate.width + 3) : coordinate.x -3,
        y: this.indexingStartY,
      }
    }else if(startInfo.pageIndex != PhotoIndex.pageIndex){
      endIindexing1 = {//不同同item页面
        x: startInfo.pageIndex > PhotoIndex.pageIndex ? (coordinate.x + coordinate.width + 3) : coordinate.x -3,
        y: this.indexingEndY,
      }
    }
    let points = [{
      x: this.indexingStartX,
      y: this.indexingStartY,
    }, endIindexing1]
    if(PhotoIndex.pageIndex != oldPhotoIndex.pageIndex && (startInfo.count != PhotoIndex.count || startInfo.itemIndex != PhotoIndex.itemIndex)){//需要折现换行
      let addPage = PhotoIndex.pageIndex>oldPhotoIndex.pageIndex?PhotoIndex.pageIndex:oldPhotoIndex.pageIndex
      let newX = addPage*(this.pageWidth + this.pageMargin)
      points.splice(1, 0, { x: newX, y: this.indexingStartY }, { x: newX, y: endIindexing1.y })
    }
    this.tempArrow.start = null
    this.tempArrow.end = null
    this.tempArrow.drawing = false;
    this.drawGuideLine(points, coordinate, this.indexingList)
    this.drawAllPages()
    console.log('tempArrow', this.indexing, this.tempArrow)
  }
  // --- 拖拽预览：轻量级绘制，仅更新偏移（替代全量重绘）---
  drawDragPreview() {
    // 快速绘制当前状态（跳过图片，提升流畅度）
    throttle(this.drawAllPages, 50)
  }
  // --- 拖拽预览：轻量级绘制，仅更新偏移（替代全量重绘）---
  /**
   * 绘制所有标引线
   *
   * 遍历标引线列表，逐条绘制每条标引线到画布上
   */
  drawGuideLineAll(val) {
    if (this.indexingLineList.length) {
      for (let i = 0; i < this.indexingLineList.length; i++) {
        const { points, coordinate, indexingList } = this.indexingLineList[i]
        this.drawGuideLine(points, coordinate, indexingList, val)
      }
    }
  }
  
  // 绘制临时标引
  drawInterimLine(points) {//val 不是重绘才保存历史记录，否则是重绘就不保存了
    console.log('drawInterimLine', ...points)
    let newpPoints = [...points]

    const ctx = this.ctx;
    let color = '#ff0000';
    let lineWidth = 2;
    // 1. 保存当前上下文状态（避免污染）
    ctx.save();

    // 2. 应用和全量绘制一致的偏移/缩放（保证坐标对齐）
    ctx.translate(this.drawOffsetX, this.drawOffsetY);
    ctx.scale(this.scale, this.scale);

    if (points.length < 2) return;
    let start = points[0]
    let end = points[1]
    if(start.pageIndex != end.pageIndex && end.itemIndex != -1 && !(start.count == end.count && start.itemIndex == end.itemIndex)){//同页码 同item
      let addPage = end.pageIndex>start.pageIndex?end.pageIndex:start.pageIndex
      let newX = addPage*(this.pageWidth + this.pageMargin)
      newpPoints.splice(1, 0, { x: newX, y: start.y }, { x: newX, y: end.y })
    }
    ctx.strokeStyle = color;
    ctx.fillStyle = color;
    ctx.lineWidth = lineWidth;
    ctx.lineCap = 'round';
    ctx.lineJoin = 'round';

    // 绘制折线
    ctx.beginPath();
    ctx.moveTo(newpPoints[0].x, newpPoints[0].y);
    for (let i = 1; i < newpPoints.length; i++) {
      ctx.lineTo(newpPoints[i].x, newpPoints[i].y);
    }
    ctx.stroke();
    ctx.restore();
  }
  // 绘制标引
  drawGuideLine(points, coordinate, indexingList, val = false) {//val 不是重绘才保存历史记录，否则是重绘就不保存了
    const ctx = this.ctx;
    let color = '#ff0000';
    let lineWidth = 2;
    // 1. 保存当前上下文状态（避免污染）
    ctx.save();

    // 2. 应用和全量绘制一致的偏移/缩放（保证坐标对齐）
    ctx.translate(this.drawOffsetX, this.drawOffsetY);
    ctx.scale(this.scale, this.scale);

    if (points.length < 2) return;

    ctx.strokeStyle = color;
    ctx.fillStyle = color;
    ctx.lineWidth = lineWidth;
    ctx.lineCap = 'round';
    ctx.lineJoin = 'round';

    // 绘制折线
    ctx.beginPath();
    ctx.moveTo(points[0].x, points[0].y);
    for (let i = 1; i < points.length; i++) {
      ctx.lineTo(points[i].x, points[i].y);
    }
    ctx.stroke();

    // 绘制起点圆点
    const first = points[0];
    const last = points[points.length - 1];
    // ctx.beginPath();
    // ctx.arc(first.x, first.y, 4, 0, Math.PI * 2);
    // ctx.fill();
    // ctx.restore();
    // 绘制T边
    let startInfo = indexingList[0]
    let endInfo = indexingList[1]
    ctx.beginPath();
    if (startInfo.pageIndex == endInfo.pageIndex) {//判断上下就行
      ctx.moveTo(coordinate.x, last.y);
      ctx.lineTo((coordinate.x + coordinate.width), last.y
    );
    } else {//左右
      ctx.moveTo(last.x, coordinate.y);
      ctx.lineTo(last.x, (coordinate.y + coordinate.height));
    }
    ctx.stroke();
    ctx.restore();
    if (this.indexing && !val) {
      this.indexingLineList.push({ points, coordinate, indexingList })
      this.indexingNum = 0
      this.indexingList = []
      // this.saveHistory()
    }
    // drawArrow(ctx, last2.x, last2.y, last.x, last.y, color);
    // this.indexingNum = 0
  }

  // --- 计算点击的页面标引 ---
  getPageIndexByClick(e) {
    const rect = this.canvas.getBoundingClientRect()
    const mouseX = (e.clientX - rect.left - this.drawOffsetX) / this.scale
    const mouseY = (e.clientY - rect.top - this.drawOffsetY) / this.scale
    for (let i = 0; i < this.pages.length; i++) {
      const pageStartX = i * (this.pageWidth + this.pageMargin)
      const pageEndX = pageStartX + this.pageWidth

      if (
        mouseX >= pageStartX && mouseX <= pageEndX &&
        mouseY >= 0 && mouseY <= this.pageHeight
      ) {
        let newindex = this.pages[i].coordinate.findIndex(item => this.isPointInRect(mouseX, mouseY, item))
        let newTitlte = this.pages[i].coordinate.findIndex(item => this.isPointInRect(mouseX, mouseY, {...item, y: item.y + item.height, height: this.titleHieght + this.imgMargin}))
        if (!this.indexing) {//绘图模式不更新选中项，绘图模式下不触发选中事件
          this.selectedPageItem = {
            index: newindex != -1 && !!(this.pages[i].list[newindex]) ? newindex : -1,
            titleIndex: newTitlte,
            pageIndex: i,
            // title:
            ...this.pages[i].coordinate[newindex]
          }
        }
        this.setImgTitle = {
          titleIndex: newTitlte,
          pageIndex: i,
        }
        return i
      }
    }
    return -1
  }
  isPointInRect(px, py, item = {}) {
    let rectX = item.x, rectY = item.y, rectWidth, rectHeight
    // 核心判断逻辑：点的坐标在矩形的上下左右边界之间
    const inXRange = px >= item.x && px <= item.x + item.width;
    const inYRange = py >= item.y && py <= item.y + item.height;
    return inXRange && inYRange;
  }
  // --- 计算拖拽到页面内的位置 ---
  getPhotoPositionInPage(e) {
    const pageIndex = this.getPageIndexByClick(e)
    if (pageIndex === -1) return { pageIndex: -1, itemIndex: -1 }
    const selectPage = this.pages[pageIndex]
    const layoutMode = selectPage.layoutMode || this.layoutMode
    const rect = this.canvas.getBoundingClientRect()
    const mouseY = (e.clientY - rect.top - this.drawOffsetY) / this.scale
    const mouseX = (e.clientX - rect.left - this.drawOffsetX) / this.scale
    const layout = this.getItemSize(layoutMode)
    let itemIndex = -1
    if (layoutMode === 'double') {
      itemIndex = this.isPointInRect(mouseX, mouseY, selectPage.coordinate[0]) ? 0 : this.isPointInRect(mouseX, mouseY, selectPage.coordinate[1]) ? 1 : -1
    } else {
      itemIndex = this.isPointInRect(mouseX, mouseY, selectPage.coordinate[0]) ? 0 : -1
    }
    return { pageIndex, itemIndex, layoutMode, count: layout.count }
  }
  /**
   * 判断点是否在线段上
   * @param {number} px 鼠标点 X
   * @param {number} py 鼠标点 Y
   * @param {number} x1 线段起点 X
   * @param {number} y1 线段起点 Y
   * @param {number} x2 线段终点 X
   * @param {number} y2 线段终点 Y
   * @param {number} r 允许的误差范围（线宽容错）
   * @return {boolean}
   */
  isPointOnLine(px, py, x1, y1, x2, y2, r = 10) {
    // 1. 计算点到线段的垂直距离
    const A = px - x1;
    const B = py - y1;
    const C = x2 - x1;
    const D = y2 - y1;

    const dot = A * C + B * D;
    const lenSq = C * C + D * D;

    // 线段长度为 0 时
    if (lenSq === 0) {
      return Math.hypot(px - x1, py - y1) < r;
    }

    // 计算投影比例
    let param = dot / lenSq;

    // 最近点
    let xx, yy;
    if (param < 0) {
      xx = x1; yy = y1;
    } else if (param > 1) {
      xx = x2; yy = y2;
    } else {
      xx = x1 + param * C;
      yy = y1 + param * D;
    }

    // 2. 判断距离是否小于容错值
    return Math.hypot(px - xx, py - yy) < r;
  }
  // --- 页面尺寸计算 ---
  getTotalCanvasWidth() {
    return this.pages.length * (this.pageWidth + this.pageMargin) - this.pageMargin
  }

  getItemSize(value) {
    const layoutMode = value || this.layoutMode
    const padding = 20
    if (layoutMode === 'single') {
      return {
        width: this.rqWidth,//this.pageWidth - 2 * padding,
        height: this.rqHeight,
        x: (this.pageWidth - this.rqWidth) / 2,
        y: (this.pageHeight - this.rqHeight) / 2,
        count: 1
      }
    } else if (layoutMode === 'landscape') {
      return {
        width: this.rqHeight,//this.pageWidth - 2 * padding,
        height: this.rqWidth,
        x: (this.pageWidth - this.rqHeight) / 2,
        y: (this.pageHeight - this.rqWidth) / 2,
        count: 1
      }
    } else {
      const itemHeight = (this.pageHeight - 3 * padding - 40) / 2
      return {
        width: this.rqWidth,//this.pageWidth - 2 * padding,
        height: this.rqHeight,
        x: (this.pageWidth - this.rqWidth) / 2,
        y1: 87,
        y2: 418,
        count: 2
      }
    }
  }
  getCoordinate(pageX, layout) {//生成每个页的图片坐标信息
    let list = [{ x: pageX + layout.x, y: layout.count == 1 ? layout.y : layout.y1, width: layout.width, height: layout.height }]
    if (layout.y2) {
      list.push({ x: pageX + layout.x, y: layout.y2, width: layout.width, height: layout.height })
    }
    return list
  }
  //绘制选中页码或者图片的边框
  // drawAllPagesBorder(){
  //   const ctx = this.ctxOld
  //   // 页面边框
  //   // 应用坐标偏移和缩放
  //   ctx.strokeStyle = this.selectedPageColor
  //   ctx.lineWidth = 4 / this.scale 
  //   const pageX = this.selectedPageIndex * (this.pageWidth + this.pageMargin)
  //   ctx.strokeRect(pageX, 0, this.pageWidth, this.pageHeight)
  //   console.log('drawAllPagesBorder', pageX, 0, this.pageWidth, this.pageHeight, this.selectedPageIndex)
  // }
  // 改造后：独立绘制边框，可单独调用
  drawAllPagesBorder(pageIndex) {
    if (!this.ctx || this.selectedPageIndex === -1) return;

    const ctx = this.ctx;
    // 1. 保存当前上下文状态（避免污染）
    ctx.save();

    // 2. 应用和全量绘制一致的偏移/缩放（保证坐标对齐）
    ctx.translate(this.drawOffsetX, this.drawOffsetY);
    ctx.scale(this.scale, this.scale);

    // 3. 先清除原边框区域（避免边框重叠）
    const pageX = this.selectedPageIndex * (this.pageWidth + this.pageMargin);
    // 清除页面边框区域（多扩一点，避免残留）
    // ctx.clearRect(
    //   pageX - 5 / this.scale, 
    //   -5 / this.scale, 
    //   this.pageWidth + 10 / this.scale, 
    //   this.pageHeight + 10 / this.scale
    // );
    ctx.strokeStyle = pageIndex === this.selectedPageIndex ? this.selectedPageColor : this.canvasBgColor;
    // 5. 绘制选中图片边框（如果有）
    if (this._selectedPageItem) {
      ctx.lineWidth = 4 / this.scale;
      ctx.strokeRect(
        this._selectedPageItem.x,
        this._selectedPageItem.y,
        this._selectedPageItem.width,
        this._selectedPageItem.height
      );
    } else {
      // 4. 绘制选中页面边框
      ctx.lineWidth = 4 / this.scale;
      ctx.strokeRect(pageX, 0, this.pageWidth, this.pageHeight);
    }
    // 6. 恢复上下文状态
    ctx.restore();
  }
  // 清除指定页面的旧边框（切换选中页时调用）
  clearOldBorder(oldPageIndex) {
    if (!this.ctx || oldPageIndex === -1) return;

    const ctx = this.ctx;
    ctx.save();
    ctx.translate(this.drawOffsetX, this.drawOffsetY);
    ctx.scale(this.scale, this.scale);

    const pageX = oldPageIndex * (this.pageWidth + this.pageMargin);
    // 清除旧边框区域
    // ctx.clearRect(
    //   pageX - 5 / this.scale, 
    //   -5 / this.scale, 
    //   this.pageWidth + 10 / this.scale, 
    //   this.pageHeight + 10 / this.scale
    // );

    ctx.restore();
  }
  // --- 全量重绘（仅在必要时调用）---
  drawAllPages(photos1 = []) {
    if (photos1.length) {
      this.photos = photos1.map(ele => {
        return {
          ...ele,
          layoutMode: this.layoutMode,//排版模式
          coordinate: [],//坐标信息
          name: ele.name || getFileNameFromUrl(ele.url)
        }
      })
    }
    if (!this.ctx) return
    const ctx = this.ctx
    const canvasWidth = this.canvas.width
    const canvasHeight = this.canvas.height
    // 清空画布
    ctx.clearRect(0, 0, canvasWidth, canvasHeight)

    ctx.save()
    // 应用坐标偏移和缩放
    ctx.translate(this.drawOffsetX, this.drawOffsetY)
    ctx.scale(this.scale, this.scale)

    // 绘制背景
    ctx.fillStyle = this.canvasBgColor
    ctx.fillRect(
      -this.drawOffsetX / this.scale,
      -this.drawOffsetY / this.scale,
      canvasWidth / this.scale,
      canvasHeight / this.scale
    )
    // 绘制所有页面（包含图片）
    this.pages.forEach((pagePhotos, pageIndex) => {
      const pageX = pageIndex * (this.pageWidth + this.pageMargin)

      // 页面背景
      ctx.fillStyle = this.pageBgColor
      ctx.fillRect(pageX, 0, this.pageWidth, this.pageHeight)

      // 页面边框
      if (this._selectedPageItem.index == -1) {
        ctx.strokeStyle = pageIndex === this.selectedPageIndex
          ? this.selectedPageColor
          : '#dddddd'
        ctx.lineWidth = pageIndex === this.selectedPageIndex ? 4 * this.scale : 2 * this.scale
        ctx.strokeRect(pageX, 0, this.pageWidth, this.pageHeight)
      }

      // 页码
      ctx.fillStyle = '#666666'
      ctx.font = `${16 * this.scale}px sans-serif`
      ctx.textAlign = 'right'
      ctx.fillText(
        `第 ${pageIndex + 1 + this.pageCount} 页`,
        pageX + this.pageWidth - (20*this.scale),
        this.pageHeight - (20*this.scale)
      )

      // 绘制图片（重构核心：调用独立方法）
      const layoutModePages = pagePhotos.layoutMode || this.layoutMode
      const layout = this.getItemSize(layoutModePages)
      pagePhotos.coordinate = this.getCoordinate(pageX, layout)
      pagePhotos.item = pagePhotos.item || new Array(layout.count).fill(null);
      console.log('getCoordinate', pagePhotos)
      let newList = this.padArrayLength(pagePhotos.list, layout.count)
      newList.forEach((photoId, itemIndex) => {
        let itemY = layoutModePages === 'single' || layoutModePages === 'landscape'
          ? layout.y
          : (itemIndex === 0 ? layout.y1 : layout.y2)
        // 图片占位框
        ctx.fillStyle = '#ffffff'
        ctx.fillRect(pageX + layout.x, itemY, layout.width, layout.height)
        // coordinate.push({x: pageX + layout.x, y: itemY, width: layout.width, height:layout.height})
        ctx.strokeStyle = '#e5e7eb'
        ctx.lineWidth = 1 / this.scale
        ctx.strokeRect(pageX + layout.x, itemY, layout.width, layout.height)
        // 设置填充颜色
        ctx.fillStyle = '#D9D9D9'; // 黄色
        // 绘制填充矩形
        ctx.fillRect(pageX + layout.x, itemY, layout.width, layout.height);
        // 说明文字占位框
        if (this._selectedPageItem && this._selectedPageItem.pageIndex === pageIndex && this._selectedPageItem.index === itemIndex) {

          ctx.strokeStyle = this.selectedPageColor
          ctx.lineWidth = 2 / this.scale
          ctx.strokeRect(pageX + layout.x - 1, itemY - 1, layout.width + 2, layout.height + 2)
          ctx.strokeStyle = '#e5e7eb'

        }
        let imgItem = this.photos.find(p => p.id === photoId)
        pagePhotos.item[itemIndex] = {
          ...imgItem,
          ...pagePhotos.item[itemIndex],
        }
        console.log('this.s', imgItem, photoId, this.photos)
        this.renderSinglePhoto(ctx, pageX, itemY, layout, pagePhotos.item[itemIndex])
      })
    })
    ctx.restore()
    console.log('this.dragPageData', this.dragPageData)
    if(this.tempArrow.drawing && this.indexing){//绘制实时鼠标标引
        this.drawInterimLine([this.tempArrow.start,this.tempArrow.end])
    }
    if(this.dragPageData.drawing && this.dragPageData.movedIndex > -1){//绘制拖拽标志
        this.drawPlusIcon(this.dragPageData.movedIndex)
    }
    this.drawGuideLineAll(true)//绘制标引
  }

  // 新增：独立渲染单张图片（确保裁剪上下文独立）
  /**
   * 在Canvas上渲染单个图片
   *
   * @param {CanvasRenderingContext2D} ctx - Canvas渲染上下文
   * @param {number} pageX - 页面起始X坐标
   * @param {number} itemY - 项目起始Y坐标
   * @param {Object} layout - 布局配置，包含宽高和位置信息
   * @param {string} photoId - 图片ID
   */
  /**
   * 在指定位置绘制单张照片
   *
   * @param {CanvasRenderingContext2D} ctx - Canvas 2D 绘图上下文
   * @param {number} pageX - 页面起始 X 坐标
   * @param {number} itemY - 项目起始 Y 坐标
   * @param {Object} layout - 布局配置对象，包含 x、y、width、height 等属性
   * @param {string} photoId - 照片的唯一标识 ID
   */
  renderSinglePhoto(ctx, pageX, itemY, layout, photo = {}) {
    // if (!photo) return

    let img = this.imgCache.get(photo.id)
    if (!img && photo && photo.url) {
      img = new Image()
      img.crossOrigin = 'anonymous'
      // 仅首次绑定onload，避免重复绑定
      img.onload = () => {
        // 加载完成后触发全量重绘（保证使用最新状态）
        this.drawAllPages()
      }
      img.onerror = (err) => {
      }
      img.src = photo.url
      this.imgCache.set(photo.id, img)
      return
    }
    // 1. 保存当前上下文（独立于外层）
    ctx.save();
    // 3. 设置裁剪区域（仅当前图片容器）
    ctx.beginPath();
    ctx.rect(pageX + layout.x, itemY, layout.width, layout.height);
    ctx.clip();
    //添加说明文字
    // 3. 绘制文字（基于图片实际区域居中，且在裁剪区内）
    const text = photo && photo.name || '说明文字';
    // 文字居中坐标：图片实际显示区域的正中心

    // 文字样式（适配缩放）
    const fontSize = 14;
    ctx.font = `${fontSize}px sans-serif`;
    ctx.fillStyle = 'red'; // 白色文字更醒目
    ctx.textAlign = 'center'; // 水平居中
    ctx.textBaseline = 'middle'; // 垂直居中

    // 可选：绘制半透明背景遮罩（避免文字和图片内容重叠看不清）
    const textWidth = (ctx.measureText(text).width / 2);
    const textHeight = fontSize;
    // 4. 绘制图片（超出裁剪区域的部分会被隐藏）
    // if(this.layoutMode === 'landscape'){
    //   ctx.rotate(Math.PI / 2);  
    // }
    // 5. 恢复上下文（仅影响当前图片，不污染全局）
    // 4. 恢复上下文（裁剪失效）
    // 图片未加载完成则跳过
    const jxx = pageX + layout.x;
    console.log('this.dragPageData', img, photo)
    if (!img || !img.complete || img.width === 0 || img.height === 0) {//无图

    } else {
      // 重新计算绘制参数（确保使用最新状态）
      const scaleX = layout.width / img.width;
      const scaleY = layout.height / img.height;
      const coverScale = Math.min(scaleX, scaleY);
      const scaledWidth = img.width * coverScale;
      const scaledHeight = img.height * coverScale;
      const offsetXInLayout = (layout.width - scaledWidth) / 2;
      const offsetYInLayout = (layout.height - scaledHeight) / 2;
      const drawX = pageX + layout.x + offsetXInLayout;
      const drawY = itemY + offsetYInLayout;
      const textCenterX = scaledWidth; // 图片水平中点
      const textCenterY = drawY + scaledHeight + this.imgMargin; // 图片垂直中点
      ctx.drawImage(img, drawX, drawY, scaledWidth, scaledHeight);
    }
    // 【核心】严格隔离裁剪上下文
    ctx.restore();
    ctx.setLineDash([]);
    // 绘制文字（最后绘制文字，避免被背景覆盖）
    ctx.fillStyle = '#8C8C8C';
    let textX = layout.width //textWidth > (this.rqWidth / 2) ? drawX : drawX + (textWidth / 2)
    // ctx.fillText(text, jxx + textWidth + (layout.width / 2), itemY + layout.height + 30, layout.width);
    this.drawCenteredTextWithEllipsis(ctx, text, jxx + (layout.width / 2), itemY + layout.height + 40, layout.width - 20, 24, 2, '16px Microsoft Yahei')
    ctx.fillStyle = 'rgba(0, 0, 0, 1)'; // 半透明黑色背景
    ctx.setLineDash([1, 1]);
    ctx.strokeRect(
      jxx,
      itemY + layout.height + this.imgMargin,
      layout.width,
      this.titleHieght
    );
    ctx.setLineDash([]);

  }

  // --- 页面操作 ---
  autoLayout(selectedPhotos=[]) {
    let newList = this.pages.flatMap(item => item.list)
    console.log('newList', newList)

    const layout = this.getItemSize()
    let newPages = []
    const pageX = this.pages.length * (this.pageWidth + this.pageMargin)
    let currentPage = {
      list: [],
      layoutMode: this.layoutMode, //页码布局类型
      // coordinate: this.getCoordinate(pageX, layout), //坐标信息
    }
    this._pages = this._pages.map(ele =>({...ele, layoutMode: this.layoutMode, }))//coordinate: this.getCoordinate(pageX, layout)
    if(selectedPhotos.length == 0){
      let list = []
      const newArr = [...newList, ...selectedPhotos]
      newArr.forEach((photoId, photoIndex) => {
        list.push(photoId)
        console.log('newArr', list,layout, photoId)
        if(list.length == layout.count){
            newPages.push({...currentPage, list: list, })
            list = []
        }else if(photoIndex == newArr.length -1){//最后一条直接写入
            newPages.push({...currentPage, list: list, })
        }
      })
    }else{
      //先填充为空的位置
      let countId = 0
      newPages = [...this.pages]//遍历旧数据
      newPages.map(ele => {
        let itemList = this.padArrayLength(ele.list, ele.layoutMode == 'double'?2:1)
        let eleItem = this.padArrayLength(ele.item, ele.layoutMode == 'double'?2:1)
        if(countId >= selectedPhotos.length) return
        console.log('newArr', itemList, eleItem, selectedPhotos, countId)
        itemList.map((element,index) => {
          if(!element){
            itemList[index] = selectedPhotos[countId]
            eleItem[index] = this.photos.find(p => p.id === selectedPhotos[countId])
            countId++
          }
        })
        ele.list = itemList
        ele.item = eleItem
      })
      if (countId < selectedPhotos.length) {//剩余的直接填充到最后
        const newArr = selectedPhotos.slice(countId)
        let list = []
        newArr.forEach((photoId, photoIndex) => {
          list.push(photoId)
          if(list.length == layout.count){
              newPages.push({...currentPage, list: list, })
              list = []
          }else if(photoIndex == newArr.length -1){//最后一条直接写入
              newPages.push({...currentPage, list: list, })
              list = []
          }
        })
      }
    }
        console.log('newArr', newPages)
    // if(newPages.length > 0 && newPages.length < this._pages.length){//需要补位
    //   newPages = newPages.concat(Array(this._pages.length - newPages.length).fill({...currentPage, list: layout.count == 2 ? [null, null] : [null]}));
    // }
    this.pages = newPages
    // newPages.length || selectedPhotos.length ? newPages : this._pages
    this.resetPosition()
    return this.pages
  }

  insertBlankPage(direction) {
    //direction true 右边 false 左边
    console.log(this.selectedPageIndex, direction, 'selectedPageIndex')
    const layout = this.getItemSize()
    // if (this.selectedPageIndex === -1) return this.pages
    const newPages = [...this.pages]
    if(direction === undefined){
      this.pages = [...newPages, {
        list: new Array(layout.count).fill(null),
        layoutMode: this.layoutMode, //页码布局类型
        coordinate: [], //坐标信息
      }]
    }else{
      const PageIndex = direction ? this.selectedPageIndex + 1 : this.selectedPageIndex;
      newPages.splice(PageIndex, 0, {
        list: [],
        layoutMode: this.layoutMode, //页码布局类型
        coordinate: [], //坐标信息
      })
      if (!direction) this.selectedPageIndex++
      this.pages = newPages
    }
    // this.resetPosition()
    return this.pages
  }
  setPageType(direction) {
    //direction true 右边 false 左边

    // if (this.selectedPageIndex === -1) return this.pages
    let newPages = [...this.pages]
    let newPageItem = {}
    const PageIndex = this.selectedPageIndex;
    let list = newPages[PageIndex] && newPages[PageIndex].list?.filter(i => i) || []
    newPages[PageIndex].layoutMode = direction
    if (list.length == 2 && direction != 'double') {//两页改成一页
      newPageItem = {
        coordinate: [newPages[PageIndex].coordinate[1]],
        item: [newPages[PageIndex].item[1]],
        layoutMode: direction,
        list: [list[1]],
      }
      newPages[PageIndex].list = list
      newPages[PageIndex].list.length = 1
      newPages[PageIndex].item.length = 1
      newPages.splice(PageIndex+1, 0, newPageItem)
    }
    if(direction == 'double' && list.length != 2){
      let newPageList = {
        list: [],
        item: []
      }
      newPages.map((ele,index) => {
        if(index> PageIndex){
          let itemInfo = {
            list: [...ele.list],
            item: [...ele.item],
          }
          if(ele.layoutMode == 'double'){
            if(itemInfo.list.length == 1){itemInfo.list.push(null)}
            if(itemInfo.item.length == 1){itemInfo.item.push({})}
          }
          newPageList.list.push(...itemInfo.list)
          newPageList.item.push(...itemInfo.item)
        }
      })

      newPages[PageIndex].list[1] = newPageList.list[0]
      newPages[PageIndex].item[1] = newPageList.item[0]
      let numberId = 1
      for (var i=PageIndex+1;i<newPages.length;i++){
          let newPageslist = [newPageList.list[numberId]]
          let newPagesitem = [newPageList.item[numberId]]
          numberId++
          if(newPages[i].layoutMode == 'double'){
            newPageslist.push(newPageList.list[numberId])
            newPagesitem.push(newPageList.item[numberId])
            numberId++
          }
          newPages[i]= {
            ...newPages[i],
            list: newPageslist,
            item: newPagesitem,
          }
      }
    }
    if (!direction) this.selectedPageIndex++
    this.pages = newPages
    // this.resetPosition()
    return this.pages
  }
  deleteSelectedPage(backFun) {
    console.log(this.selectedPageItem, 'selectedPageItem', this.pages)
    if (this.selectedPageItem.index == -1 &&( this.selectedPageIndex === -1 || this.pages.length <= 1)) return backFun()
    const newPages = [...this.pages]
    if(this.selectedPageItem.index == -1){//删除整页
          newPages.splice(this.selectedPageIndex, 1)
    }else{//删除单个图片
      newPages.forEach((ele, index) => {
        if(index == this.selectedPageIndex){
          ele.list[this.selectedPageItem.index] = null
          ele.item[this.selectedPageItem.index] = null
          // ele.list.splice(this.selectedPageItem.index, 1)
        }
      })
    }
    this.pages = newPages
    this.selectedPageIndex = Math.min(this.selectedPageIndex, this.pages.length - 1)
    if(this.selectedPageItem.index == -1){
      // this.resetPosition()
    }
    return this.pages
  }

  // --- 重置位置（居中）---
  resetPosition() {
    if (!this.scrollWrapper) return

    const totalWidth = this.getTotalCanvasWidth() * this.scale
    const totalHeight = this.pageHeight * this.scale
    const wrapperWidth = this.scrollWrapper.clientWidth
    const wrapperHeight = this.scrollWrapper.clientHeight

    // 计算居中坐标偏移
    this.drawOffsetX = (wrapperWidth - totalWidth) / 2
    this.drawOffsetY = (wrapperHeight - totalHeight) / 2

    this.drawAllPages() // 重置位置时重绘一次
  }

  // --- 重置缩放 ---
  resetZoom() {
    this.scale = 1.0
    this.resetPosition()
  }
  isActiveImg() {
    return this._selectedPageItem.index !== -1 || false
  }
  /**
 * 补全数组到指定长度
 * @param {Array} arr - 原始数组
 * @param {number} targetLength - 目标长度
 * @param {any} fillValue - 补位值（支持函数，动态生成）
 * @returns {Array} 补全后的新数组
 */
  padArrayLength(arr = [], targetLength, fillValue = null) {
    // 若原数组长度≥目标长度，直接返回原数组的拷贝（避免修改原数组）
    if (arr.length >= targetLength) {
      arr.length = targetLength;
      return [...arr]
    }

    const needAdd = targetLength - arr.length;
    let fillArr;
    // 如果补位值是函数，动态生成；否则填充固定值
    if (typeof fillValue === 'function') {
      fillArr = Array.from({ length: needAdd }, (_, i) => fillValue(i, arr.length));
    } else {
      fillArr = new Array(needAdd).fill(fillValue);
    }

    return [...arr, ...fillArr];
  }
  /**
   * Canvas 绘制多行居中文字，超出指定行数显示省略号
   * @param {CanvasRenderingContext2D} ctx - Canvas 上下文
   * @param {string} text - 要绘制的文字
   * @param {number} x - 文本块的中心X坐标（整体居中的基准）
   * @param {number} y - 文本块的中心Y坐标（整体居中的基准）
   * @param {number} maxWidth - 文本块的最大宽度（超出换行）
   * @param {number} lineHeight - 行高
   * @param {number} maxLines - 最大显示行数（超出显示省略号）
   * @param {string} font - 字体样式（如 '16px Microsoft Yahei'）
   */
  drawCenteredTextWithEllipsis(ctx, text, x, y, maxWidth, lineHeight, maxLines, font='14pt 宋体') {
    // 1. 设置字体样式（必须先设置，否则measureText计算不准确）
    ctx.font = font;
    ctx.textBaseline = 'top'; // 基准线设为顶部，方便计算行位置
    ctx.textAlign = 'center'; // 水平居中
    // ctx.textBaseline = 'middle'; // 垂直居中

    // 2. 拆分文字为多行（处理换行和截断）
    let words = text.split(''); // 按字符拆分（兼容中文）
    let line = ''; // 当前行文字
    let lines = []; // 最终的行数组
    let isTruncated = false; // 是否需要截断

    for (let i = 0; i < words.length; i++) {
      let testLine = line + words[i];
      let metrics = ctx.measureText(testLine);
      let testWidth = metrics.width;

      // 如果当前测试行宽度超过最大宽度，就换行
      if (testWidth > maxWidth && i > 0) {
        lines.push(line);
        line = words[i];
        // 超出最大行数，停止拆分并标记截断
        if (lines.length >= maxLines) {
          isTruncated = true;
          break;
        }
      } else {
        line = testLine;
      }
    }

    // 处理最后一行
    if (!isTruncated) {
      lines.push(line);
      // 如果最后一行超出行数，仍需截断
      if (lines.length > maxLines) {
        isTruncated = true;
        lines = lines.slice(0, maxLines);
      }
    }

    // 3. 处理省略号（仅当截断时）
    if (isTruncated) {
      // 从最后一行末尾删除字符，直到能放下省略号
      let lastLine = lines[maxLines - 1];
      while (ctx.measureText(lastLine + '...').width > maxWidth && lastLine.length > 0) {
        lastLine = lastLine.slice(0, -1);
      }
      lines[maxLines - 1] = lastLine + '...';
    }

    // 4. 计算整体居中的起始位置
    // 文本块的总高度 = 实际显示行数 * 行高
    const totalTextHeight = lines.length * lineHeight;
    // 起始Y = 中心Y - 总高度/2（垂直居中）
    const startY = y - totalTextHeight / 2;
    // 每行的起始X = 中心X - maxWidth/2（水平居中，基于文本块宽度）
    const startX = x - maxWidth / 2;
    // 5. 逐行绘制文字（最终居中效果）
    ctx.textAlign = 'center'; // 保持left，基于startX绘制
    for (let i = 0; i < lines.length; i++) {
      const currentY = startY + i * lineHeight;
      ctx.fillText(lines[i], x, currentY);
    }
  }
  saveHistory() {
    // 清理当前位置之后的“重做记录”
    this.history = this.history.slice(0, this.currentIndex + 1);
    // 重点：用 getImageData 保存整个画布像素
    const imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
    this.history.push({
      imageData: imageData,
      pages: [...this.pages], // 保存当前页码信息，以便恢复时使用
      indexingLineList: [...this.indexingLineList], // 保存标引线信息，以便恢复时使用
    });
    this.currentIndex++;
    if (this.history.length > 5) {
      this.history.shift(); // 删除最早的一条
      // this.currentIndex--;
    }
    this.updata({
      selectedPageItem: this._selectedPageItem,
      selectedPageIndex: this.selectedPageIndex,
      historylength: this.history.length,
      currentIndex: this.currentIndex
    })
  }
  undo(currentIndex, type) {
    let newCurrentIndex = type ? (currentIndex - 1) : (currentIndex + 1)
    const { pages, indexingLineList } = this.history[newCurrentIndex]
    this.indexingLineList = indexingLineList
    this.isHistory = false //回退不保存
    this.pages = pages
    this.currentIndex = newCurrentIndex
    // this.tempArrow.start = null
    // this.tempArrow.end = null
    // this.tempArrow.drawing = false;
    // this.indexingNum = 0
    // this.indexingList = []
    this.updata({
      selectedPageItem: this._selectedPageItem,
      selectedPageIndex: this.selectedPageIndex,
      historylength: this.history.length,
      currentIndex: this.currentIndex
    })
    this.isHistory = true
  }
  isArrayEqual(arr1, arr2) {
    if (arr1.length !== arr2.length) return false;
    for (let i = 0; i < arr1.length; i++) {
      if (arr1[i] !== arr2[i]) return false;
    }
    return true;
  }



  exportPagesToPDF(paperType = "a4", name) {
    const loading = ElLoading.service({
      lock: true,
      text: "正在导出超清PDF，请稍候...",
      background: "rgba(0, 0, 0, 0.7)",
    });
    const originalState = {
      scale: this.scale,
      drawOffsetX: this.drawOffsetX,
      drawOffsetY: this.drawOffsetY,
      selectedPageIndex: this.selectedPageIndex,
      isHistory: this.isHistory,
    };

    try {
      this.isHistory = false;
      const DPR = 3;

      const rules = {
        a4: { perSheet: 1, orient: "portrait", format: "a4" },
        a3: { perSheet: 2, orient: "landscape", format: "a3" },
        four: { perSheet: 4, orient: "landscape", format: [728, 297]},
      };
      const { perSheet, orient, format } = rules[paperType];
      const pdf = new jsPDF({ orientation: orient, unit: "mm", format });
      const pdfW = pdf.internal.pageSize.getWidth();
      const pdfH = pdf.internal.pageSize.getHeight();
      const groups = [];
      for (let i = 0; i < this.pages.length; i += perSheet) {
        groups.push(this.pages.slice(i, i + perSheet));
      }

      groups.forEach((group, sheetIndex) => {
        if (sheetIndex > 0) pdf.addPage();

        group.forEach((_, idxInSheet) => {
          const pageIndex = sheetIndex * perSheet + idxInSheet;
          const page = this.pages[pageIndex];

          // 离屏画布
          const pageCanvas = document.createElement("canvas");
          pageCanvas.width = this.pageWidth * DPR;
          pageCanvas.height = this.pageHeight * DPR;
          const ctx = pageCanvas.getContext("2d");
          ctx.scale(DPR, DPR);

          // 背景
          ctx.fillStyle = "#fff";
          ctx.fillRect(0, 0, this.pageWidth, this.pageHeight);

          const layout = this.getItemSize(page.layoutMode);
          const coords = this.getCoordinate(0, layout);

          coords.forEach((coord, q) => {
            const photoId = page.list[q];
            let imgItem = this.photos.find(p => p.id === photoId)
            const photo = {
              ...imgItem,
              ...page.item[q],
            }

            // 相框底板
            ctx.fillStyle = "#D9D9D9";
            ctx.fillRect(coord.x, coord.y, coord.width, coord.height);
            ctx.strokeStyle = "#eee";
            ctx.strokeRect(coord.x, coord.y, coord.width, coord.height);

            if (photo) {
              const img = this.imgCache.get(photo.id);
              if (img && img.complete) {
                // 图片裁切（不超出相框）
                ctx.save();
                ctx.beginPath();
                ctx.rect(coord.x, coord.y, coord.width, coord.height);
                ctx.clip();

                const s = Math.min(coord.width / img.width, coord.height / img.height);
                let w = img.width * s;
                const h = img.height * s;
                let x = coord.x + (coord.width - w) / 2;
                const y = coord.y + (coord.height - h) / 2;
                if(paperType == 'four'){
                  w = w / (724/840)
                  x = x * (724/840)
                }
                ctx.drawImage(img, x, y, w, h);

                ctx.restore();
              }

              // 文字
              ctx.fillStyle = "#000";
              const text = photo.name || "说明文字";
              this.drawCenteredTextWithEllipsis(
                ctx, text,
                coord.x + coord.width / 2,
                coord.y + coord.height + 40,
                coord.width - 20, 24, 2, "14pt 宋体"
              );

              // 虚线框
              ctx.setLineDash([1, 1]);
              ctx.strokeRect(coord.x, coord.y + coord.height + this.imgMargin, coord.width, this.titleHieght);
              ctx.setLineDash([]);
            }
          });
          // 页码
          ctx.fillStyle = '#666666'
          ctx.font = `${16}px sans-serif`
          ctx.textAlign = 'right'
          ctx.fillText(
            `第 ${pageIndex + 1 + this.pageCount} 页`,
            this.pageWidth - (20),
            this.pageHeight - (20)
          )
          // ==========================================
          // 🔥 1:1 还原你原 drawGuideLine 标引逻辑
          // ==========================================
          this.indexingLineList.forEach(line => {
            const { points, coordinate, indexingList } = line;
            const startInfo = indexingList[0];
            const endInfo = indexingList[1];

            // 当前正在导出的是哪一页
            const currentIsStart = startInfo.pageIndex === pageIndex;
            const currentIsEnd = endInfo.pageIndex === pageIndex;
            if (!currentIsStart && !currentIsEnd) return;

            ctx.save();
            ctx.strokeStyle = '#ff0000';
            ctx.fillStyle = '#ff0000';
            ctx.lineWidth = 2;
            ctx.lineCap = 'round';
            ctx.lineJoin = 'round';

            // 页面全局偏移（核心修正）
            const pageOffsetX = pageIndex * (this.pageWidth + this.pageMargin);

            // 绘制连线
            ctx.beginPath();
            points.forEach((p, i) => {
              const x = p.x - pageOffsetX;
              const y = p.y;
              i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
            });
            ctx.stroke();

            // 起点圆点（只在起点页画）
            // if (currentIsStart) {
            //   const first = points[0];
            //   const fx = first.x - pageOffsetX;
            //   ctx.beginPath();
            //   ctx.arc(fx, first.y, 4, 0, Math.PI * 2);
            //   ctx.fill();
            // }

            // T型端线（只在终点页画）
            if (currentIsEnd) {
              const last = points[points.length - 1];
              ctx.beginPath();
              if (startInfo.pageIndex === endInfo.pageIndex) {
                ctx.moveTo(coordinate.x - pageOffsetX, last.y);
                ctx.lineTo(coordinate.x - pageOffsetX + coordinate.width, last.y);
              } else {
                ctx.moveTo(last.x - pageOffsetX, coordinate.y);
                ctx.lineTo(last.x - pageOffsetX, coordinate.y + coordinate.height);
              }
              ctx.stroke();
            }

            ctx.restore();
          });

          const imgData = pageCanvas.toDataURL("image/png", 1.0);

          // PDF 位置
          let x, y, w, h;
          if (paperType === "a4") {
            [x, y, w, h] = [0, 0, pdfW, pdfH];
          } else if (paperType === "a3") {
            w = pdfW / 2; h = pdfH; x = idxInSheet * w; y = 0;
          } else {
            w = pdfW / 4; h = pdfH;
            x = idxInSheet * w;
            y = 0;
          }

          pdf.addImage(imgData, "PNG", x, y, w, h);
        });
      });
      let fileName = name || "完整导出_" + Date.now();
      pdf.save(fileName + `.pdf`);
      ElMessage.success("PDF导出成功！");
      return true
    } catch (err) {
      console.error(err);
      if(err?.message === 'Invalid string length'){
        alert("图片数量较多导致内存占用过高，建议分段制作再导出。");
      }else{
        ElMessage.error("导出失败");
      }
      return false
    } finally {
      Object.assign(this, originalState);
      // this.drawAllPages();
      loading.close();
    }
  }

exportPagesAsImages(paperType = "a4", name, fileType = 'pdf') {
  const loading = ElLoading.service({
    lock: true,
    text: "正在生成图片...",
    background: "rgba(0, 0, 0, 0.7)",
  });

  const DPR = 3;
  const zip = new JSZip();

  // 完全和你 exportPagesToPDF 保持一致的规则
  const rules = {
    a4: { perSheet: 1 },
    a3: { perSheet: 2 },
    four: { perSheet: 4 },
  };
  const perSheet = rules[paperType]?.perSheet || 1;

  try {
    // 分组：几页拼一张图（a4=1，a3=2，four=4）
    const groups = [];
    for (let i = 0; i < this.pages.length; i += perSheet) {
      groups.push(this.pages.slice(i, i + perSheet));
    }

    // 遍历每组 → 生成一张图片
    for (let groupIndex = 0; groupIndex < groups.length; groupIndex++) {
      const group = groups[groupIndex];

      // 创建画布（和PDF导出尺寸逻辑一致）
      const pageCanvas = document.createElement("canvas");
      let pageWidth = this.pageWidth
      if(paperType === "four"){
        pageWidth = pageWidth / (724/840)
      }
      pageCanvas.width = pageWidth * DPR * (paperType === "a3" ? 2 : paperType === "a4" ? 1 : 4);
      pageCanvas.height = this.pageHeight * DPR * 1;
      const ctx = pageCanvas.getContext("2d");
      ctx.scale(DPR, DPR);

      // 白色背景
      ctx.fillStyle = "#fff";
      ctx.fillRect(0, 0, pageCanvas.width, pageCanvas.height);

      // 绘制本组页面
      group.forEach((page, idxInSheet) => {
        const pageIndex = groupIndex * perSheet + idxInSheet;
        let offsetX = 0;
        let offsetY = 0;

        if (paperType === "a3") offsetX = idxInSheet * this.pageWidth;
        if (paperType === "four") {
          offsetX = idxInSheet * pageWidth;
          // offsetY = idxInSheet * this.pageHeight;
        }

        ctx.save();
        ctx.translate(offsetX, offsetY);

        // ==========================================
        // 🔥 直接复用你 PDF 里一模一样的绘制逻辑
        // ==========================================
        const layout = this.getItemSize(page.layoutMode);
        const coords = this.getCoordinate(0, layout);

        coords.forEach((coord, i) => {
          const photoId = page.list[i];
          let imgItem = this.photos.find(p => p.id === photoId)
          const photo = {
            ...imgItem,
            ...page.item[i],
          }

          ctx.fillStyle = "#D9D9D9";
          ctx.fillRect(coord.x, coord.y, coord.width, coord.height);
          ctx.strokeStyle = "#eee";
          ctx.strokeRect(coord.x, coord.y, coord.width, coord.height);

          if (photo) {
            const img = this.imgCache.get(photo.id);
            if (img && img.complete) {
              ctx.save();
              ctx.beginPath();
              ctx.rect(coord.x, coord.y, coord.width, coord.height);
              ctx.clip();

              const s = Math.min(coord.width / img.width, coord.height / img.height);
              const w = img.width * s;
              const h = img.height * s;
              const x = coord.x + (coord.width - w) / 2;
              const y = coord.y + (coord.height - h) / 2;
              ctx.drawImage(img, x, y, w, h);
              ctx.restore();
            }

            // 文字
            ctx.fillStyle = "#000";
            const text = photo.name || "说明文字";
            this.drawCenteredTextWithEllipsis(
              ctx, text,
              coord.x + coord.width / 2,
              coord.y + coord.height + 40,
              coord.width - 20, 24, 2, "14pt 宋体"
            );

            ctx.setLineDash([1, 1]);
            ctx.strokeRect(coord.x, coord.y + coord.height + this.imgMargin, coord.width, this.titleHieght);
            ctx.setLineDash([]);
          }
        });
          // 页码
          ctx.fillStyle = '#666666'
          ctx.font = `${16}px sans-serif`
          ctx.textAlign = 'right'
          ctx.fillText(
            `第 ${pageIndex + 1 + this.pageCount} 页`,
            this.pageWidth - (20),
            this.pageHeight - (20)
          )
          // ==========================================
          // 🔥 1:1 还原你原 drawGuideLine 标引逻辑
          // ==========================================
          this.indexingLineList.forEach(line => {
            const { points, coordinate, indexingList } = line;
            const startInfo = indexingList[0];
            const endInfo = indexingList[1];

            // 当前正在导出的是哪一页
            const currentIsStart = startInfo.pageIndex === pageIndex;
            const currentIsEnd = endInfo.pageIndex === pageIndex;
            if (!currentIsStart && !currentIsEnd) return;

            ctx.save();
            ctx.strokeStyle = '#ff0000';
            ctx.fillStyle = '#ff0000';
            ctx.lineWidth = 2;
            ctx.lineCap = 'round';
            ctx.lineJoin = 'round';

            // 页面全局偏移（核心修正）
            const pageOffsetX = pageIndex * (pageWidth + this.pageMargin);

            // 绘制连线
            ctx.beginPath();
            points.forEach((p, i) => {
              const x = p.x - pageOffsetX;
              const y = p.y;
              i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
            });
            ctx.stroke();

            // 起点圆点（只在起点页画）
            // if (currentIsStart) {
            //   const first = points[0];
            //   const fx = first.x - pageOffsetX;
            //   ctx.beginPath();
            //   ctx.arc(fx, first.y, 4, 0, Math.PI * 2);
            //   ctx.fill();
            // }

            // T型端线（只在终点页画）
            if (currentIsEnd) {
              const last = points[points.length - 1];
              ctx.beginPath();
              if (startInfo.pageIndex === endInfo.pageIndex) {
                ctx.moveTo(coordinate.x - pageOffsetX, last.y);
                ctx.lineTo(coordinate.x - pageOffsetX + coordinate.width, last.y);
              } else {
                ctx.moveTo(last.x - pageOffsetX, coordinate.y);
                ctx.lineTo(last.x - pageOffsetX, coordinate.y + coordinate.height);
              }
              ctx.stroke();
            }

            ctx.restore();
          });

        ctx.restore();
      });

      // 转图片并加入 ZIP
      const base64 = pageCanvas.toDataURL("image/jpeg");
      zip.file(`第${groupIndex + 1}张.jpg`, base64.split(",")[1], { base64: true });
    }

    // 下载
    const filename = name || "照片卷";
    zip.generateAsync({ type: "blob" }).then(blob => {
      saveAs(blob, `${filename}.zip`);
      ElMessage.success("导出成功！");
    }).catch(() => {
      ElMessage.error("导出失败");
    });
    // const blob = await zip.generateAsync({ type: "blob" });
    // saveAs(blob, `${filename}.zip`);
    // ElMessage.success("导出成功！");
    return true
  } catch (err) {
    console.error(err);
    ElMessage.error("导出失败");
    return false
  } finally {
    loading.close();
  }
}
/**
 * 截取 Canvas 指定区域并返回图片 base64
 * @param {HTMLCanvasElement} canvas - 原画布
 * @param {number} x - 截取区域左上角 x
 * @param {number} y - 截取区域左上角 y
 * @param {number} width - 截取宽度
 * @param {number} height - 截取高度
 * @returns {string} 图片 dataURL
 */
captureCanvasArea(canvas, x, y, width, height) {
  // 1. 创建一个临时小画布，大小就是你要截取的尺寸
  const tempCanvas = document.createElement('canvas');
  const tempCtx = tempCanvas.getContext('2d');
  
  tempCanvas.width = width;
  tempCanvas.height = height;

  // 2. 把原画布的指定区域 画到 临时画布
  tempCtx.drawImage(
    canvas,
    x, y, width, height,  // 原画布要截取的区域
    0, 0, width, height   // 临时画布的绘制位置
  );

  // 3. 导出图片
  return tempCanvas.toDataURL('image/png');
}
exportToPDF(paperType, name, fileType = 'pdf'){
  if(fileType == 'pdf'){
    this.exportPagesToPDF(paperType, name);
  }else{
    this.exportPagesAsImages(paperType, name);
  }
}
async checkIndexing(mes = '此操作将会清除所有标引是否继续？'){
  let length = this.indexingLineList.length;
  // try {
    if ( length && await ElMessageBox.confirm(mes, '提示') ) {
      this.saveHistory();
      this.indexingLineList = [];
      return true;
    } 
  // } catch (error) {
  //   return false
  // }
  return false
}
//绘制移动page的目的地提示
drawPlusIcon = (pageIndex) => {
    const ctx = this.ctx;
    const color = this.selectedPageColor;
    const lineWidth = 2;
    const size = 40 * this.scale;
    const r = 24 * this.scale 
    // 1. 保存当前上下文状态（避免污染）
    ctx.save();

    // 2. 应用和全量绘制一致的偏移/缩放（保证坐标对齐）
    ctx.translate(this.drawOffsetX, this.drawOffsetY);
    ctx.scale(this.scale, this.scale);
    const pageX = (pageIndex * (this.pageWidth + this.pageMargin)) - this.pageMargin / 2;
    const arcY = 0-(size/2)
    // 蓝色圆形
    ctx.beginPath();
    ctx.arc(pageX, arcY, r, 0, Math.PI * 2);
    ctx.fillStyle = color;
    ctx.fill();

    // 白色加号
    ctx.beginPath();
    ctx.moveTo(pageX - size / 3, arcY);
    ctx.lineTo(pageX + size / 3, arcY);
    ctx.moveTo(pageX, arcY - size / 3);
    ctx.lineTo(pageX, arcY + size / 3);
    ctx.strokeStyle = '#fff';
    ctx.lineWidth = lineWidth;
    ctx.lineCap = 'round';
    ctx.fill();
    ctx.stroke();

    ctx.beginPath();
    ctx.moveTo(pageX, 0);
    ctx.lineTo(pageX, this.pageHeight);
    ctx.strokeStyle = color;
    ctx.lineWidth = this.pageMargin;
    ctx.fill();

    ctx.stroke();
    ctx.restore();
  };
  insertItemToArray(arr, item, toIndex) {
    const newArr = [...arr]
    newArr.splice(toIndex, 0, item)
    return newArr
  }
}