import { ref, nextTick } from 'vue'
import { storeToRefs } from 'pinia'
import { parse, type Shape, type Element, type ChartItem, type BaseElement } from 'pptxtojson'
import { nanoid } from 'nanoid'
import { useSlidesStore } from '@/store'
import { lang } from '@/main'
import { decrypt } from '@/utils/crypto'
import { type ShapePoolItem, SHAPE_LIST, SHAPE_PATH_FORMULAS } from '@/configs/shapes'
import useAddSlidesOrElements from '@/hooks/useAddSlidesOrElements'
import useSlideHandler from '@/hooks/useSlideHandler'
import useHistorySnapshot from './useHistorySnapshot'
import message from '@/utils/message'
import { getSvgPathRange } from '@/utils/svgPathParser'
//import utifUrl from '/UTIF.js';
import type {
  Slide,
  TableCellStyle,
  TableCell,
  ChartType,
  SlideBackground,
  PPTShapeElement,
  PPTLineElement,
  PPTImageElement,
  ShapeTextAlign,
  PPTTextElement,
  PPTVideoElement,
  PPTAudioElement,
  ChartOptions,
  Gradient,
} from '@/types/slides'

const convertFontSizePtToPx = (html: string, ratio: number, autoFit: any) => {
  if (autoFit?.fontScale && autoFit?.type == "text") { ratio = ratio * autoFit.fontScale / 100; }
  // return html;
  return html.replace(/\s*([\d.]+)pt/g, (match, p1) => {
    return `${(parseFloat(p1) * ratio - 1) | 0}px `
  })

}

const getStyle = (htmlString: string) => {
  // return html;
  // 1. 创建 DOMParser 实例
  const parser = new DOMParser()
  // 2. 解析 HTML 字符串为文档对象
  const doc = parser.parseFromString(htmlString, 'text/html')
  // 3. 获取 p 元素
  const p = doc.querySelector('p')
  // 4. 读取 style 属性（内联样式字符串）
  const styleAttr = p?.getAttribute('allstyle')
  console.log(styleAttr) // 输出完整的 style 字符串
  return styleAttr || ''

}

export default () => {
  const slidesStore = useSlidesStore()
  const { slides, theme, viewportRatio, title, viewportSize } = storeToRefs(useSlidesStore())

  const { addHistorySnapshot } = useHistorySnapshot()
  const { addSlidesFromData } = useAddSlidesOrElements()
  const { isEmptySlide } = useSlideHandler()

  const exporting = ref(false)

  // 导入JSON文件
  const importJSON = (files: FileList, cover = false) => {
    const file = files[0]

    const reader = new FileReader()
    reader.addEventListener('load', () => {
      try {
        const { slides } = JSON.parse(reader.result as string)
        if (cover) {
          slidesStore.updateSlideIndex(0)
          slidesStore.setSlides(slides)
          addHistorySnapshot()
        }
        else if (isEmptySlide.value) {
          slidesStore.setSlides(slides)
          addHistorySnapshot()
        }
        else addSlidesFromData(slides)
      }
      catch {
        message.error(lang.ssFileReadFail)
      }
    })
    reader.readAsText(file)
  }

  // 直接读取JSON功能，暴露到window.readJSON
  const readJSON = (jsonData: string | any, cover = false) => {
    try {
      console.log('readJSON 开始执行:', { jsonData, cover })

      let parsedData
      if (typeof jsonData === 'string') {
        parsedData = JSON.parse(jsonData)
        console.log('解析字符串后的数据:', parsedData)
      }
      else {
        parsedData = jsonData
      }

      // 提取所有可能的数据
      const slides = parsedData.slides || parsedData
      const title = parsedData.title
      const theme = parsedData.theme
      const width = parsedData.width
      const height = parsedData.height
      const viewportRatio = parsedData.viewportRatio || (height && width ? height / width : undefined)

      console.log('提取的数据:', { slides: slides.length, title, theme, width, height, viewportRatio })

      // 更新幻灯片数据
      if (cover) {
        console.log('覆盖模式：更新幻灯片数据')
        slidesStore.updateSlideIndex(0)
        slidesStore.setSlides(slides)
        addHistorySnapshot()
      }
      else if (isEmptySlide.value) {
        console.log('空幻灯片模式：更新幻灯片数据')
        slidesStore.setSlides(slides)
        addHistorySnapshot()
      }
      else {
        console.log('添加模式：添加幻灯片数据')
        addSlidesFromData(slides)
      }

      // 同步更新其他相关内容
      if (title !== undefined) {
        console.log('正在更新标题:', title)
        slidesStore.setTitle(title)
        console.log('标题更新完成')
      }

      if (theme !== undefined) {
        console.log('正在更新主题:', theme)
        slidesStore.setTheme(theme)
        console.log('主题更新完成')
      }

      // 更新视口尺寸（如果提供了的话）
      if (width !== undefined && height !== undefined) {
        console.log('正在触发视口尺寸更新事件:', { width, height, viewportRatio })

        // 同时也要更新slidesStore中的相关数据
        if (slidesStore.setViewportSize) {
          console.log('正在更新store中的视口尺寸')
          slidesStore.setViewportSize(width)
          if (slidesStore.setViewportRatio && viewportRatio !== undefined) {
            slidesStore.setViewportRatio(viewportRatio)
            console.log('视口比例已更新:', viewportRatio)
          }
        }

        window.dispatchEvent(new CustomEvent('viewportSizeUpdated', {
          detail: { width, height, viewportRatio }
        }))
        console.log('视口尺寸更新事件已触发')
      }

      // 导入成功后，触发画布尺寸更新
      // 使用 nextTick 确保DOM更新完成后再触发
      console.log('开始触发画布尺寸更新事件...')
      nextTick(() => {
        console.log('DOM更新完成，触发 slidesDataUpdated 事件')
        // 触发自定义事件，通知需要更新画布尺寸的组件
        window.dispatchEvent(new CustomEvent('slidesDataUpdated', {
          detail: {
            slides,
            cover,
            title,
            theme,
            width,
            height,
            viewportRatio,
            timestamp: Date.now()
          }
        }))
        console.log('slidesDataUpdated 事件已触发')

        // 检查并调整幻灯片索引，确保在有效范围内
        const newSlideCount = slides.length
        const currentIndex = slidesStore.slideIndex
        if (currentIndex >= newSlideCount) {
          console.log('调整幻灯片索引:', currentIndex, '->', Math.max(0, newSlideCount - 1))
          slidesStore.updateSlideIndex(Math.max(0, newSlideCount - 1))
        }

        console.log('画布尺寸更新事件处理完成')
      })

      console.log('readJSON 执行成功')
      return { success: true, slides, title, theme, width, height, viewportRatio }
    }
    catch (error) {
      console.error('readJSON 执行失败:', error)
      const errorMsg = lang.ssJsonReadFail
      message.error(errorMsg)
      return { success: false, error: errorMsg, details: error }
    }
  }

  // 导出JSON文件
  const exportJSON2 = () => {
    const json = {
      title: title.value,
      width: viewportSize.value,
      height: viewportSize.value * viewportRatio.value,
      theme: theme.value,
      slides: slides.value,
    }
    return json
  }

  // 优化暴露到 window 对象的方式，避免重复赋值
  if (typeof window !== 'undefined') {
    const win = window as any
    if (!win.exportJSON) win.exportJSON = exportJSON2
    if (!win.readJSON) win.readJSON = readJSON
  }

  // 导入pptist文件
  const importSpecificFile = (files: FileList, cover = false) => {
    const file = files[0]

    const reader = new FileReader()
    reader.addEventListener('load', () => {
      try {
        const { slides } = JSON.parse(decrypt(reader.result as string))
        if (cover) {
          slidesStore.updateSlideIndex(0)
          slidesStore.setSlides(slides)
          addHistorySnapshot()
        }
        else if (isEmptySlide.value) {
          slidesStore.setSlides(slides)
          addHistorySnapshot()
        }
        else addSlidesFromData(slides)
      }
      catch {
        message.error(lang.ssFileReadFail)
      }
    })
    reader.readAsText(file)
  }

  const rotateLine = (line: PPTLineElement, angleDeg: number) => {
    const { start, end } = line

    const angleRad = angleDeg * Math.PI / 180

    const midX = (start[0] + end[0]) / 2
    const midY = (start[1] + end[1]) / 2

    const startTransX = start[0] - midX
    const startTransY = start[1] - midY
    const endTransX = end[0] - midX
    const endTransY = end[1] - midY

    const cosA = Math.cos(angleRad)
    const sinA = Math.sin(angleRad)

    const startRotX = startTransX * cosA - startTransY * sinA
    const startRotY = startTransX * sinA + startTransY * cosA

    const endRotX = endTransX * cosA - endTransY * sinA
    const endRotY = endTransX * sinA + endTransY * cosA

    const startNewX = startRotX + midX
    const startNewY = startRotY + midY
    const endNewX = endRotX + midX
    const endNewY = endRotY + midY

    const beforeMinX = Math.min(start[0], end[0])
    const beforeMinY = Math.min(start[1], end[1])

    const afterMinX = Math.min(startNewX, endNewX)
    const afterMinY = Math.min(startNewY, endNewY)

    const startAdjustedX = startNewX - afterMinX
    const startAdjustedY = startNewY - afterMinY
    const endAdjustedX = endNewX - afterMinX
    const endAdjustedY = endNewY - afterMinY

    const startAdjusted: [number, number] = [startAdjustedX, startAdjustedY]
    const endAdjusted: [number, number] = [endAdjustedX, endAdjustedY]
    const offset = [afterMinX - beforeMinX, afterMinY - beforeMinY]

    return {
      start: startAdjusted,
      end: endAdjusted,
      offset,
    }
  }

  const parseLineElement = (el: Shape, ratio: number) => {
    let start: [number, number] = [0, 0]
    let end: [number, number] = [0, 0]

    if (!el.isFlipV && !el.isFlipH) { // 右下
      start = [0, 0]
      end = [el.width, el.height]
    }
    else if (el.isFlipV && el.isFlipH) { // 左上
      start = [el.width, el.height]
      end = [0, 0]
    }
    else if (el.isFlipV && !el.isFlipH) { // 右上
      start = [0, el.height]
      end = [el.width, 0]
    }
    else { // 左下
      start = [el.width, 0]
      end = [0, el.height]
    }

    const data: PPTLineElement = {
      type: 'line',
      id: nanoid(10),
      width: +((el.borderWidth || 1) * ratio).toFixed(2),
      left: el.left,
      top: el.top,
      start,
      end,
      style: el.borderType,
      color: el.borderColor,
      points: ['', /straightConnector/.test(el.shapType) ? 'arrow' : '']
    }
    if (el.rotate) {
      const { start, end, offset } = rotateLine(data, el.rotate)

      data.start = start
      data.end = end
      data.left = data.left + offset[0]
      data.top = data.top + offset[1]
    }
    if (/bentConnector/.test(el.shapType)) {
      data.broken2 = [
        Math.abs(data.start[0] - data.end[0]) / 2,
        Math.abs(data.start[1] - data.end[1]) / 2,
      ]
    }
    if (/curvedConnector/.test(el.shapType)) {
      const cubic: [number, number] = [
        Math.abs(data.start[0] - data.end[0]) / 2,
        Math.abs(data.start[1] - data.end[1]) / 2,
      ]
      data.cubic = [cubic, cubic]
    }

    return data
  }

  const flipGroupElements = (elements: BaseElement[], axis: 'x' | 'y') => {
    const minX = Math.min(...elements.map(el => el.left))
    const maxX = Math.max(...elements.map(el => el.left + el.width))
    const minY = Math.min(...elements.map(el => el.top))
    const maxY = Math.max(...elements.map(el => el.top + el.height))

    const centerX = (minX + maxX) / 2
    const centerY = (minY + maxY) / 2

    return elements.map(element => {
      const newElement = { ...element }

      if (axis === 'y') newElement.left = 2 * centerX - element.left - element.width
      if (axis === 'x') newElement.top = 2 * centerY - element.top - element.height

      return newElement
    })
  }

  const calculateRotatedPosition = (
    x: number,
    y: number,
    w: number,
    h: number,
    ox: number,
    oy: number,
    k: number,
  ) => {
    const radians = k * (Math.PI / 180)

    const containerCenterX = x + w / 2
    const containerCenterY = y + h / 2

    const relativeX = ox - w / 2
    const relativeY = oy - h / 2

    const rotatedX = relativeX * Math.cos(radians) + relativeY * Math.sin(radians)
    const rotatedY = -relativeX * Math.sin(radians) + relativeY * Math.cos(radians)

    const graphicX = containerCenterX + rotatedX
    const graphicY = containerCenterY + rotatedY

    return { x: graphicX, y: graphicY }
  }

  /**
   * 将 base64 字符串或 Blob 转换为 File 对象
   */
  
  const dataToFile = async (data: string | Blob, filename: string, videoMimeType: string): File => {
    if (typeof data === 'string') {
      // 1. 通过 fetch 获取 Blob 数据
      const response = await fetch(data)
      if (!response.ok) {
        throw new Error(`Failed to fetch blob: ${response.statusText}`)
      }
      const blob = await response.blob()

      // 2. 将 Blob 转换为 File 对象
      //    如果原 Blob 有 type，会自动保留；否则可手动指定 videoMimeType
      const file = new File([blob], filename, { type: videoMimeType || blob.type })
      return file
    }
    else if (data instanceof Blob) {
      return new File([data], filename, { type: data.type })
    }
    throw new Error('Unsupported data type')
  }
  
/*
  // 你原有的 dataToFile 函数保持不变
  const dataToFile = async (data: string | Blob, filename: string, videoMimeType: string): Promise<File> => {
    if (typeof data === 'string') {
      const response = await fetch(data);
      if (!response.ok) {
        throw new Error(`Failed to fetch blob: ${response.statusText}`);
      }
      const blob = await response.blob();
      const mime = videoMimeType || blob.type;
      return new File([blob], filename, { type: mime });
    } else if (data instanceof Blob) {
      return new File([data], filename, { type: data.type });
    }
    throw new Error('Unsupported data type');
  };


  const convertVideoToMP4 = async (
    videoSource: string | Blob,
    outputFilename: string = `video_${Date.now()}.mp4`
  ): Promise<File> => {
    // 1. 检查浏览器支持
    const supported = await canEncode();
    if (!supported) {
      throw new Error('当前浏览器不支持 WebCodecs，请使用最新 Chrome/Edge 并确保 HTTPS');
    }

    // 2. 转为 File
    const inputFile = await dataToFile(videoSource, 'input.mp4', 'video/mp4');

    // 3. 创建 VideoFile 对象（webcodecs-encoder 的输入包装）
    //const videoFile = new VideoFile(inputFile);
    const videoFile = {
      file: inputFile,
      type: 'video/mp4'
    };
    // 4. 执行编码
    const encodedData = await encode(videoFile, {
      quality: 'high',
      video: {
        codec: 'av1',
        bitrate: 2_000_000,
        hardwareAcceleration: 'prefer-hardware'
      },
      audio: false,           // 显式禁用音频编码
      container: 'mp4',
      onProgress: (progress) => console.log(progress)
    });

    // 5. 返回 File
    return new File([encodedData], outputFilename, { type: 'video/mp4' });
  };
*/


  /*
  const makeWhiteTransparent = async (
    data: string | Blob,
    filename: string,
    options?: { tolerance?: number }
  ): Promise<File> => {
    const tolerance = options?.tolerance ?? 30 // 容差值，控制哪些颜色被视为白色
    const distThreshold = options?.tolerance ?? 50;

    // 1. 将输入数据统一为 Blob 或可直接用于加载的 URL
    let imageUrl: string
    let blob: Blob

    if (typeof data === 'string') {
      // 如果是 Base64，直接用作 src（data URL）
      imageUrl = data.startsWith('data:') ? data : `data:image/png;base64,${data}`
    }
    else if (data instanceof Blob) {
      // 如果是 Blob，创建对象 URL
      imageUrl = URL.createObjectURL(data)
      blob = data // 暂存，后续释放 URL 用
    }
    else {
      throw new Error('Unsupported data type')
    }

    // 2. 加载图像到 Image 元素
    const img = await new Promise<HTMLImageElement>((resolve, reject) => {
      const image = new Image()
      image.onload = () => resolve(image)
      image.onerror = reject
      image.src = imageUrl
      // 如果图像来自跨域，可能需要设置 crossOrigin
      // image.crossOrigin = 'anonymous';
    })

    // 3. 创建 Canvas 并绘制图像
    const canvas = document.createElement('canvas')
    canvas.width = img.width
    canvas.height = img.height
    const ctx = canvas.getContext('2d')!
    ctx.drawImage(img, 0, 0)

    // 4. 获取像素数据并处理
    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
    const dataArray = imageData.data

    for (let i = 0; i < dataArray.length; i += 4) {
      const r = dataArray[i]
      const g = dataArray[i + 1]
      const b = dataArray[i + 2]

      // 判断颜色是否接近白色（RGB 都大于 255 - tolerance）
      if (r > 255 - tolerance && g > 255 - tolerance && b > 255 - tolerance) {
        dataArray[i + 3] = 0 // 设置 Alpha 为 0（完全透明）
      }
    }

    // 5. 将修改后的像素放回 Canvas
    ctx.putImageData(imageData, 0, 0)

    // 6. 将 Canvas 转换为 PNG Blob
    const outputBlob = await new Promise<Blob>((resolve) =>
      canvas.toBlob((blob) => resolve(blob!), 'image/png')
    )

    // 7. 清理对象 URL（如果之前创建过）
    if (typeof data !== 'string') {
      URL.revokeObjectURL(imageUrl)
    }

    // 8. 返回 File 对象
    return new File([outputBlob], filename, { type: 'image/png' })
  }
  */

  /**
 * 将图片中的白色背景变为透明
 * @param data 图片数据（Base64字符串 或 Blob）
 * @param filename 输出文件名
 * @param options 可选配置
 * @param options.tolerance 颜色距离容差（默认50，值越大越多的浅色被变透明）
 * @param options.removeMatte 是否去除白色边缘（默认true，可改善白边）
 * @returns 处理后的 PNG 格式 File 对象
 */
  const makeWhiteTransparent = async (
    data: string | Blob,
    filename: string,
    options?: { tolerance?: number }
  ): Promise<File> => {
    const tolerance = options?.tolerance ?? 15

    // ----- 辅助函数：将输入统一转换为 { blob, mime } -----
    async function getBlobAndMime(input: string | Blob): Promise<{ blob: Blob; mime: string }> {
      // 1. 已经是 Blob
      if (input instanceof Blob) {
        return { blob: input, mime: input.type }
      }

      // 2. 处理字符串
      if (input.startsWith('data:')) {
        // data URL → 通过 fetch 获取 Blob（自动获得正确的 MIME 类型）
        const response = await fetch(input)
        const blob = await response.blob()
        return { blob, mime: blob.type }
      } 
      // 纯 base64 字符串 → 按原逻辑默认当作 PNG
      const binary = atob(input)
      const bytes = new Uint8Array(binary.length)
      for (let i = 0; i < binary.length; i++) {
        bytes[i] = binary.charCodeAt(i)
      }
      // 默认 MIME 为 image/png（与原函数行为一致）
      const blob = new Blob([bytes], { type: 'image/png' })
      return { blob, mime: 'image/png' }
      
    }

    // 获取统一的 blob 和实际 MIME 类型
    const { blob, mime } = await getBlobAndMime(data)

    // ----- 非 PNG 格式：直接返回原始文件（不处理透明）-----
    if (mime !== 'image/png') {
      return new File([blob], filename, { type: mime })
    }

    // ----- PNG 格式：执行白色变透明处理 -----
    // 1. 创建对象 URL 用于加载图片
    const imageUrl = URL.createObjectURL(blob)
    const needRevoke = true
  
    // 2. 加载图像
    const img = await new Promise<HTMLImageElement>((resolve, reject) => {
      const image = new Image()
      image.onload = () => resolve(image)
      image.onerror = reject
      // Blob URL 不需要设置 crossOrigin
      image.src = imageUrl
    })

    const canvas = document.createElement('canvas')
    try {
      canvas.width = img.width
      canvas.height = img.height
      const ctx = canvas.getContext('2d')!
      ctx.drawImage(img, 0, 0)

      // 3. 获取像素数据，将接近白色的像素设为透明
      const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
      const dataArray = imageData.data

      for (let i = 0; i < dataArray.length; i += 4) {
        const r = dataArray[i]
        const g = dataArray[i + 1]
        const b = dataArray[i + 2]

        const dr = r - 255
        const dg = g - 255
        const db = b - 255
        const dist = Math.sqrt(dr * dr + dg * dg + db * db)

        if (dist <= tolerance) {
          dataArray[i + 3] = 0 // 完全透明
        }
      }

      ctx.putImageData(imageData, 0, 0)
    }
    finally {
      if (needRevoke) {
        URL.revokeObjectURL(imageUrl)
      }
    }

    // 4. 导出为 PNG Blob
    const outputBlob = await new Promise<Blob>((resolve, reject) => {
      canvas.toBlob((blob) => {
        if (blob) resolve(blob)
        else reject(new Error('Canvas toBlob failed'))
      }, 'image/png')
    })

    return new File([outputBlob], filename, { type: 'image/png' })
  }

  /**
     * 上传 File 到 S3，返回公开访问的 URL
     */
  const uploadFileToS3 = (file: File): Promise<string> => {
    return new Promise((resolve, reject) => {
      if (typeof window === 'undefined' || !window.AWS) {
        reject(new Error('AWS SDK not available'))
        return
      }

      const credentials = {
        accessKeyId: 'AKIATLPEDU37QV5CHLMH',
        secretAccessKey: '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',
      }
      window.AWS.config.update(credentials)
      window.AWS.config.region = 'cn-northwest-1'

      const bucket = new window.AWS.S3({
        params: { Bucket: 'ccrb' }, httpOptions: {
          timeout: 600000 // 10分钟超时
        }
      })
      const ext = file.name.split('.').pop() || 'bin'
      const key = `${file.name.split('.')[0]}_${Date.now()}.${ext}`

      const params = {
        Key: 'pptto/' + key,
        ContentType: file.type,
        Body: file,
        ACL: 'public-read',
      }
      const options = {
        partSize: 5 * 1024 * 1024, // 2GB 分片，可酌情调小
        queueSize: 2,
        leavePartsOnError: true,
      }

      bucket
        .upload(params, options)
        .promise()
        .then(data => resolve(data.Location))
        .catch(err => reject(err))
    })
  }

  /*
    // 导入PPTX文件
    const importPPTXFile = (files: FileList, options?: { cover?: boolean; fixedViewport?: boolean; signal?: AbortSignal }) => {
      console.log('导入', files)
      const defaultOptions = {
        cover: false,
        fixedViewport: false, 
      }
      const { cover, fixedViewport, signal } = { ...defaultOptions, ...options }
  
      const file = files[0]
      if (!file) return
  
      exporting.value = true
  
      const shapeList: ShapePoolItem[] = []
      for (const item of SHAPE_LIST) {
        shapeList.push(...item.children)
      }
      
      const reader = new FileReader()
      reader.onload = async (e: ProgressEvent<FileReader>) => {
        // 检查是否已取消
        if (signal?.aborted) {
          exporting.value = false
          return
        }
        
        let json = null
        try {
          json = await parse(e.target!.result as ArrayBuffer)
        }
        catch (error) {
          exporting.value = false
          console.log('导入PPTX文件失败:', error)
          message.error('无法正确读取 / 解析该文件')
          return
        }
  
        let ratio = 96 / 72;
        //let ratio = 1
        const width = json.size.width
        
        if (fixedViewport) ratio = 1000 / width
        else slidesStore.setViewportSize(width * ratio)
  
        slidesStore.setTheme({ themeColors: json.themeColors })
  
        const slides: Slide[] = []
        for (const item of json.slides) {
          const { type, value } = item.fill
          let background: SlideBackground
          if (type === 'image') {
            background = {
              type: 'image',
              image: {
                src: value.picBase64,
                size: 'cover',
              },
            }
          }
          else if (type === 'gradient') {
            background = {
              type: 'gradient',
              gradient: {
                type: value.path === 'line' ? 'linear' : 'radial',
                colors: value.colors.map(item => ({
                  ...item,
                  pos: parseInt(item.pos),
                })),
                rotate: value.rot + 90,
              },
            }
          }
          else {
            background = {
              type: 'solid',
              color: value || '#fff',
            }
          }
  
          const slide: Slide = {
            id: nanoid(10),
            elements: [],
            background,
            remark: item.note || '',
          }
  
          const parseElements = (elements: Element[]) => {
            
            const sortedElements = elements.sort((a, b) => a.order - b.order)
            console.log(sortedElements)
  
            for (const el of sortedElements) {
              const originWidth = el.width || 1
              const originHeight = el.height || 1
              const originLeft = el.left
              const originTop = el.top
  
              el.width = el.width * ratio
              el.height = el.height * ratio
              el.left = el.left * ratio
              el.top = el.top * ratio
  
              if (el.type === 'text') {
                const textEl: PPTTextElement = {
                  type: 'text',
                  id: nanoid(10),
                  width: el.width,
                  height: el.height,
                  left: el.left,
                  top: el.top,
                  rotate: el.rotate,
                  defaultFontName: theme.value.fontName,
                  defaultColor: theme.value.fontColor,
                  content: convertFontSizePtToPx(el.content, ratio),
                  lineHeight: 1,
                  outline: {
                    color: el.borderColor,
                    width: +(el.borderWidth * ratio).toFixed(2),
                    style: el.borderType,
                  },
                  fill: el.fill.type === 'color' ? el.fill.value : '',
                  vertical: el.isVertical,
                }
                if (el.shadow) {
                  textEl.shadow = {
                    h: el.shadow.h * ratio,
                    v: el.shadow.v * ratio,
                    blur: el.shadow.blur * ratio,
                    color: el.shadow.color,
                  }
                }
                slide.elements.push(textEl)
              }
              else if (el.type === 'image') {
                const element: PPTImageElement = {
                  type: 'image',
                  id: nanoid(10),
                  src: el.src,
                  width: el.width,
                  height: el.height,
                  left: el.left,
                  top: el.top,
                  fixedRatio: true,
                  rotate: el.rotate,
                  flipH: el.isFlipH,
                  flipV: el.isFlipV,
                }
                if (el.borderWidth) {
                  element.outline = {
                    color: el.borderColor,
                    width: +(el.borderWidth * ratio).toFixed(2),
                    style: el.borderType,
                  }
                }
                const clipShapeTypes = ['roundRect', 'ellipse', 'triangle', 'rhombus', 'pentagon', 'hexagon', 'heptagon', 'octagon', 'parallelogram', 'trapezoid']
                if (el.rect) {
                  element.clip = {
                    shape: (el.geom && clipShapeTypes.includes(el.geom)) ? el.geom : 'rect',
                    range: [
                      [
                        el.rect.l || 0,
                        el.rect.t || 0,
                      ],
                      [
                        100 - (el.rect.r || 0),
                        100 - (el.rect.b || 0),
                      ],
                    ]
                  }
                }
                else if (el.geom && clipShapeTypes.includes(el.geom)) {
                  element.clip = {
                    shape: el.geom,
                    range: [[0, 0], [100, 100]]
                  }
                }
                slide.elements.push(element)
              }
              else if (el.type === 'math') {
                slide.elements.push({
                  type: 'image',
                  id: nanoid(10),
                  src: el.picBase64,
                  width: el.width,
                  height: el.height,
                  left: el.left,
                  top: el.top,
                  fixedRatio: true,
                  rotate: 0,
                })
              }
              else if (el.type === 'audio') {
                slide.elements.push({
                  type: 'audio',
                  id: nanoid(10),
                  src: el.blob,
                  width: el.width,
                  height: el.height,
                  left: el.left,
                  top: el.top,
                  rotate: 0,
                  fixedRatio: false,
                  color: theme.value.themeColors[0],
                  loop: false,
                  autoplay: false,
                })
              }
              else if (el.type === 'video') {
                slide.elements.push({
                  type: 'video',
                  id: nanoid(10),
                  src: (el.blob || el.src)!,
                  width: el.width,
                  height: el.height,
                  left: el.left,
                  top: el.top,
                  rotate: 0,
                  autoplay: false,
                })
              }
              else if (el.type === 'shape') {
                if (el.shapType === 'line' || /Connector/.test(el.shapType)) {
                  const lineElement = parseLineElement(el, ratio)
                  slide.elements.push(lineElement)
                }
                else {
                  const shape = shapeList.find(item => item.pptxShapeType === el.shapType)
  
                  const vAlignMap: { [key: string]: ShapeTextAlign } = {
                    'mid': 'middle',
                    'down': 'bottom',
                    'up': 'top',
                  }
  
                  const gradient: Gradient | undefined = el.fill?.type === 'gradient' ? {
                    type: el.fill.value.path === 'line' ? 'linear' : 'radial',
                    colors: el.fill.value.colors.map(item => ({
                      ...item,
                      pos: parseInt(item.pos),
                    })),
                    rotate: el.fill.value.rot,
                  } : undefined
  
                  const pattern: string | undefined = el.fill?.type === 'image' ? el.fill.value.picBase64 : undefined
  
                  const fill = el.fill?.type === 'color' ? el.fill.value : ''
                  
                  const element: PPTShapeElement = {
                    type: 'shape',
                    id: nanoid(10),
                    width: el.width,
                    height: el.height,
                    left: el.left,
                    top: el.top,
                    viewBox: [200, 200],
                    path: 'M 0 0 L 200 0 L 200 200 L 0 200 Z',
                    fill,
                    gradient,
                    pattern,
                    fixedRatio: false,
                    rotate: el.rotate,
                    outline: {
                      color: el.borderColor,
                      width: +(el.borderWidth * ratio).toFixed(2),
                      style: el.borderType,
                    },
                    text: {
                      content: convertFontSizePtToPx(el.content, ratio),
                      defaultFontName: theme.value.fontName,
                      defaultColor: theme.value.fontColor,
                      align: vAlignMap[el.vAlign] || 'middle',
                    },
                    flipH: el.isFlipH,
                    flipV: el.isFlipV,
                  }
                  if (el.shadow) {
                    element.shadow = {
                      h: el.shadow.h * ratio,
                      v: el.shadow.v * ratio,
                      blur: el.shadow.blur * ratio,
                      color: el.shadow.color,
                    }
                  }
      
                  if (shape) {
                    element.path = shape.path
                    element.viewBox = shape.viewBox
      
                    if (shape.pathFormula) {
                      element.pathFormula = shape.pathFormula
                      element.viewBox = [el.width, el.height]
      
                      const pathFormula = SHAPE_PATH_FORMULAS[shape.pathFormula]
                      if ('editable' in pathFormula && pathFormula.editable) {
                        element.path = pathFormula.formula(el.width, el.height, pathFormula.defaultValue)
                        element.keypoints = pathFormula.defaultValue
                      }
                      else element.path = pathFormula.formula(el.width, el.height)
                    }
                  }
                  else if (el.path && el.path.indexOf('NaN') === -1) {
                    const { maxX, maxY } = getSvgPathRange(el.path)
                    element.path = el.path
                    element.viewBox = [maxX || originWidth, maxY || originHeight]
                  }
                  if (el.shapType === 'custom') {
                    if (el.path!.indexOf('NaN') !== -1) {
                      if (element.width === 0) element.width = 0.1
                      if (element.height === 0) element.height = 0.1
                      element.path = el.path!.replace(/NaN/g, '0')
                    }
                    else {
                      element.special = true
                      element.path = el.path!
                    }
                    const { maxX, maxY } = getSvgPathRange(element.path)
                    element.viewBox = [maxX || originWidth, maxY || originHeight]
                  }
      
                  if (element.path) slide.elements.push(element)
                }
              }
              else if (el.type === 'table') {
                const row = el.data.length
                const col = el.data[0].length
  
                const style: TableCellStyle = {
                  fontname: theme.value.fontName,
                  color: theme.value.fontColor,
                }
                const data: TableCell[][] = []
                for (let i = 0; i < row; i++) {
                  const rowCells: TableCell[] = []
                  for (let j = 0; j < col; j++) {
                    const cellData = el.data[i][j]
  
                    let textDiv: HTMLDivElement | null = document.createElement('div')
                    textDiv.innerHTML = cellData.text
                    const p = textDiv.querySelector('p')
                    const align = p?.style.textAlign || 'left'
  
                    const span = textDiv.querySelector('span')
                    const fontsize = span?.style.fontSize ? (parseInt(span?.style.fontSize) * ratio).toFixed(1) + 'px' : ''
                    const fontname = span?.style.fontFamily || ''
                    const color = span?.style.color || cellData.fontColor
  
                    rowCells.push({
                      id: nanoid(10),
                      colspan: cellData.colSpan || 1,
                      rowspan: cellData.rowSpan || 1,
                      text: textDiv.innerText,
                      style: {
                        ...style,
                        align: ['left', 'right', 'center'].includes(align) ? (align as 'left' | 'right' | 'center') : 'left',
                        fontsize,
                        fontname,
                        color,
                        bold: cellData.fontBold,
                        backcolor: cellData.fillColor,
                      },
                    })
                    textDiv = null
                  }
                  data.push(rowCells)
                }
  
                const allWidth = el.colWidths.reduce((a, b) => a + b, 0)
                const colWidths: number[] = el.colWidths.map(item => item / allWidth)
  
                const firstCell = el.data[0][0]
                const border = firstCell.borders.top ||
                  firstCell.borders.bottom ||
                  el.borders.top ||
                  el.borders.bottom ||
                  firstCell.borders.left ||
                  firstCell.borders.right ||
                  el.borders.left ||
                  el.borders.right
                const borderWidth = border?.borderWidth || 0
                const borderStyle = border?.borderType || 'solid'
                const borderColor = border?.borderColor || '#eeece1'
  
                slide.elements.push({
                  type: 'table',
                  id: nanoid(10),
                  width: el.width,
                  height: el.height,
                  left: el.left,
                  top: el.top,
                  colWidths,
                  rotate: 0,
                  data,
                  outline: {
                    width: +(borderWidth * ratio || 2).toFixed(2),
                    style: borderStyle,
                    color: borderColor,
                  },
                  cellMinHeight: el.rowHeights[0] ? el.rowHeights[0] * ratio : 36,
                })
              }
              else if (el.type === 'chart') {
                let labels: string[]
                let legends: string[]
                let series: number[][]
  
                if (el.chartType === 'scatterChart' || el.chartType === 'bubbleChart') {
                  labels = el.data[0].map((item, index) => `坐标${index + 1}`)
                  legends = ['X', 'Y']
                  series = el.data
                }
                else {
                  const data = el.data as ChartItem[]
                  labels = Object.values(data[0].xlabels)
                  legends = data.map(item => item.key)
                  series = data.map(item => item.values.map(v => v.y))
                }
  
                const options: ChartOptions = {}
  
                let chartType: ChartType = 'bar'
  
                switch (el.chartType) {
                  case 'barChart':
                  case 'bar3DChart':
                    chartType = 'bar'
                    if (el.barDir === 'bar') chartType = 'column'
                    if (el.grouping === 'stacked' || el.grouping === 'percentStacked') options.stack = true
                    break
                  case 'lineChart':
                  case 'line3DChart':
                    if (el.grouping === 'stacked' || el.grouping === 'percentStacked') options.stack = true
                    chartType = 'line'
                    break
                  case 'areaChart':
                  case 'area3DChart':
                    if (el.grouping === 'stacked' || el.grouping === 'percentStacked') options.stack = true
                    chartType = 'area'
                    break
                  case 'scatterChart':
                  case 'bubbleChart':
                    chartType = 'scatter'
                    break
                  case 'pieChart':
                  case 'pie3DChart':
                    chartType = 'pie'
                    break
                  case 'radarChart':
                    chartType = 'radar'
                    break
                  case 'doughnutChart':
                    chartType = 'ring'
                    break
                  default:
                }
  
                slide.elements.push({
                  type: 'chart',
                  id: nanoid(10),
                  chartType: chartType,
                  width: el.width,
                  height: el.height,
                  left: el.left,
                  top: el.top,
                  rotate: 0,
                  themeColors: el.colors.length ? el.colors : theme.value.themeColors,
                  textColor: theme.value.fontColor,
                  data: {
                    labels,
                    legends,
                    series,
                  },
                  options,
                })
              }
              else if (el.type === 'group') {
                let elements: BaseElement[] = el.elements.map(_el => {
                  let left = _el.left + originLeft
                  let top = _el.top + originTop
  
                  if (el.rotate) {
                    const { x, y } = calculateRotatedPosition(originLeft, originTop, originWidth, originHeight, _el.left, _el.top, el.rotate)
                    left = x
                    top = y
                  }
  
                  const element = {
                    ..._el,
                    left,
                    top,
                  }
                  if (el.isFlipH && 'isFlipH' in element) element.isFlipH = true
                  if (el.isFlipV && 'isFlipV' in element) element.isFlipV = true
  
                  return element
                })
                if (el.isFlipH) elements = flipGroupElements(elements, 'y')
                if (el.isFlipV) elements = flipGroupElements(elements, 'x')
                parseElements(elements)
              }
              else if (el.type === 'diagram') {
                const elements = el.elements.map(_el => ({
                  ..._el,
                  left: _el.left + originLeft,
                  top: _el.top + originTop,
                }))
                parseElements(elements)
              }
            }
          }
          parseElements([...item.elements, ...item.layoutElements])
          slides.push(slide)
        }
  
        if (cover) {
          slidesStore.updateSlideIndex(0)
          slidesStore.setSlides(slides)
          addHistorySnapshot()
        }
        else if (isEmptySlide.value) {
          slidesStore.setSlides(slides)
          addHistorySnapshot()
        }
        else addSlidesFromData(slides)
  
        exporting.value = false
      }
      reader.readAsArrayBuffer(file)
      
      // 监听取消信号
      signal?.addEventListener('abort', () => {
        reader.abort()
        exporting.value = false
      })
      
      // 监听取消信号
      signal?.addEventListener('abort', () => {
        reader.abort()
        exporting.value = false
      })
    }
  */

  const importPPTXFile = (files: FileList, options?: { cover?: boolean; fixedViewport?: boolean; signal?: AbortSignal, onclose?: () => void }) => {
    console.log('导入', files)
    const defaultOptions = {
      cover: false,
      fixedViewport: false,
    }
    const { cover, fixedViewport, signal, onclose } = { ...defaultOptions, ...options }

    const file = files[0]
    if (!file) return

    exporting.value = true // 假设 exporting 是一个全局 ref

    // 预加载形状库（用于后续形状匹配）
    const shapeList: ShapePoolItem[] = []
    for (const item of SHAPE_LIST) {
      shapeList.push(...item.children)
    }

    const reader = new FileReader()
    reader.onload = async (e: ProgressEvent<FileReader>) => {
      // 检查是否已取消
      if (signal?.aborted) {
        exporting.value = false
        return
      }

      let json = null
      try {
        json = await parse(e.target!.result as ArrayBuffer)
      }
      catch (error) {
        exporting.value = false
        console.log('导入PPTX文件失败:', error)
        message.error(lang.ssFileReadFail)
        return
      }

      if (signal?.aborted) {
        exporting.value = false
        return
      }

      // 计算缩放比例
      let ratio = 96 / 72 // PPTX 默认 72 DPI，屏幕 96 DPI
      const width = json.size.width
      const height = json.size.height
      const viewportRatio = json.size.viewportRatio || (height && width ? height / width : undefined)
      if (fixedViewport) {
        ratio = 1000 / width // 固定视口宽度为 1000px
      }
      else {
        slidesStore.setViewportSize(width * ratio) // 调整画布大小
      }

      // 设置主题色
      slidesStore.setTheme({ themeColors: json.themeColors })

      const slides: Slide[] = []

      // 收集当前幻灯片内所有上传任务
      const uploadTasks: Promise<void>[] = []

      // 遍历每一张幻灯片
      for (const item of json.slides) {
        // ----- 解析背景 -----
        const { type, value } = item.fill
        let background: SlideBackground
        if (type === 'image') {
          // 背景图片也可能需要上传（但 PPTX 背景图通常是内嵌 base64）
          // 这里为了简化，暂不处理背景图片上传，如有需要可类似元素上传
          background = {
            type: 'image',
            image: {
              src: value.picBase64,
              size: 'cover',
            },
          }
        }
        else if (type === 'gradient') {
          background = {
            type: 'gradient',
            gradient: {
              type: value.path === 'line' ? 'linear' : 'radial',
              colors: value.colors.map(item => ({
                ...item,
                pos: parseInt(item.pos),
              })),
              rotate: value.rot + 90,
            },
          }
        }
        else {
          background = {
            type: 'solid',
            color: value || '#fff',
          }
        }

        const slide: Slide = {
          id: nanoid(10),
          elements: [],
          background,
          remark: item.note || '',
        }

        // ----- 解析元素（递归函数）-----
        const parseElements = async (elements: any[], pelements: any = null) => {
          // 按绘制顺序排序
          const sortedElements = elements.sort((a, b) => a.order - b.order)
          console.log(sortedElements)

          for (const el of sortedElements) {
            // 保存原始尺寸用于后续可能的路径计算
            const originWidth = el.width || 1
            const originHeight = el.height || 1
            const originLeft = el.left
            const originTop = el.top
            // 保存原始尺寸用于后续可能的路径计算
            const poriginWidth = pelements?.width
            const poriginHeight = pelements?.height
            const poriginLeft = pelements?.left
            const poriginTop = pelements?.top

            // 应用缩放
            el.width = el.width * ratio
            el.height = el.height * ratio
            el.left = el.left * ratio
            el.top = el.top * ratio

            if (el.type === 'text') {
              const vAlignMap: { [key: string]: ShapeTextAlign } = {
                mid: 'middle',
                down: 'bottom',
                up: 'top',
              }

              const textEl: PPTTextElement = {
                type: 'text',
                id: nanoid(10),
                width: el.width,
                height: el.height,
                left: el.left,
                top: el.top,
                rotate: el.rotate,
                defaultFontName: theme.value.fontName,
                defaultColor: theme.value.fontColor,
                content: convertFontSizePtToPx(el.content, ratio, el.autoFit),
                style: getStyle(convertFontSizePtToPx(el.content, ratio, el.autoFit)),
                lineHeight: 1.15,
                align: vAlignMap[el.vAlign] || 'middle',
                outline: {
                  color: el.borderColor,
                  width: +(el.borderWidth * ratio).toFixed(2),
                  style: el.borderType,
                },
                fill: el.fill.type === 'color' ? el.fill.value : '',
                vertical: el.isVertical,
              }
              if (el.shadow) {
                textEl.shadow = {
                  h: el.shadow.h * ratio,
                  v: el.shadow.v * ratio,
                  blur: el.shadow.blur * ratio,
                  color: el.shadow.color,
                }
              }
              slide.elements.push(textEl)
            }
            // ---------- 图片 ----------
            if (el.type === 'image') {
              const element: PPTImageElement = {
                type: 'image',
                id: nanoid(10),
                src: el.src, // 可能是 base64 或已有 URL
                width: el.width,
                height: el.height,
                left: el.left,
                top: el.top,
                fixedRatio: true,
                rotate: el.rotate,
                flipH: el.isFlipH,
                flipV: el.isFlipV,
              }

              // 边框
              if (el.borderWidth) {
                element.outline = {
                  color: el.borderColor,
                  width: +(el.borderWidth * ratio).toFixed(2),
                  style: el.borderType,
                }
              }

              // 裁剪（形状剪裁）
              const clipShapeTypes = ['roundRect', 'ellipse', 'triangle', 'rhombus', 'pentagon', 'hexagon', 'heptagon', 'octagon', 'parallelogram', 'trapezoid']
              if (el.rect) {
                element.clip = {
                  shape: (el.geom && clipShapeTypes.includes(el.geom)) ? el.geom : 'rect',
                  range: [
                    [el.rect.l || 0, el.rect.t || 0],
                    [100 - (el.rect.r || 0), 100 - (el.rect.b || 0)],
                  ],
                }
              }
              else if (el.geom && clipShapeTypes.includes(el.geom)) {
                element.clip = {
                  shape: el.geom,
                  range: [[0, 0], [100, 100]],
                }
              }
              /*
                            // 如果 src 是 base64，触发上传
                            if (el.src && typeof el.src === 'string' && el.src.startsWith('data:')) {
                              const uploadTask = (async () => {
                                try {
                                  const file = await makeWhiteTransparent(el.src, `image_${Date.now()}.png`)
                                  if (file) {
                                    const url = await uploadFileToS3(file)
                                    element.src = url // 替换为远程 URL
                                    const slidesStore = useSlidesStore()
                                    slidesStore.updateElement({ id: element.id, props: { src: url } })
                                  }
                                }
                                catch (error) {
                                  console.error('Image upload failed:', error)
                                  // 失败时保留原 base64（或可置空）
                                }
                              })()
                              uploadTasks.push(uploadTask)
                            }
              */
              slide.elements.push(element)
            }
            else if (el.type === 'math') {
              const element: PPTImageElement = {
                type: 'image',
                id: nanoid(10),
                src: el.picBase64,
                width: el.width,
                height: el.height,
                left: el.left,
                top: el.top,
                fixedRatio: true,
                rotate: 0,
              }
/*
              // 如果 src 是 base64，触发上传
              if (el.picBase64 && typeof el.picBase64 === 'string' && el.picBase64.startsWith('data:')) {
                const uploadTask = (async () => {
                  try {
                    const file = makeWhiteTransparent(el.picBase64, `image_${Date.now()}.png`)
                    if (file) {
                      const url = await uploadFileToS3(file)
                      element.src = url // 替换为远程 URL
                      const slidesStore = useSlidesStore()
                      slidesStore.updateElement({ id: element.id, props: { src: url } })
                    }
                  }
                  catch (error) {
                    console.error('Image upload failed:', error)
                    // 失败时保留原 base64（或可置空）
                  }
                })()
                uploadTasks.push(uploadTask)
              }
*/

              slide.elements.push(element)

            }
            // ---------- 音频 ----------
            else if (el.type === 'audio') {
              const element: PPTAudioElement = {
                type: 'audio',
                id: nanoid(10),
                src: el.blob,
                width: el.width,
                height: el.height,
                left: el.left,
                top: el.top,
                rotate: 0,
                fixedRatio: false,
                color: theme.value.themeColors[0],
                loop: false,
                autoplay: false,
              }
              const localData = el.blob || (el.src && typeof el.src === 'string' && el.src.startsWith('data:') ? el.src : null)

              if (localData) {
                const uploadTask = (async () => {
                  try {
                    const file = await dataToFile(localData, `audio_${Date.now()}.mp3`, 'audio/mpeg')
                    if (file) {
                      const url = await uploadFileToS3(file)
                      element.src = url
                      const slidesStore = useSlidesStore()
                      slidesStore.updateElement({ id: element.id, props: { src: url } })
                    }
                  }
                  catch (error) {
                    console.error('Audio upload failed:', error)
                  }
                })()
                uploadTasks.push(uploadTask)
              }

              slide.elements.push(element)
            }

            // ---------- 视频 ----------
            else if (el.type === 'video') {
              const element: PPTVideoElement = {
                type: 'video',
                id: nanoid(10),
                src: (el.blob || el.src)!,
                width: el.width,
                height: el.height,
                left: el.left,
                top: el.top,
                rotate: 0,
                autoplay: false,
              }
/*
              const localData = el.blob || (el.src && typeof el.src === 'string' && el.src.startsWith('data:') ? el.src : null)
              if (localData) {
                const uploadTask = (async () => {
                  try {
                    const file = await dataToFile(localData, `video_${Date.now()}.mp4`, 'video/mp4')
                    if (file) {
                      const url = await uploadFileToS3(file)
                      element.src = url
                      const slidesStore = useSlidesStore()
                      slidesStore.updateElement({ id: element.id, props: { src: url } })
                    }
                  }
                  catch (error) {
                    console.error('Video upload failed:', error)
                  }
                })()
                uploadTasks.push(uploadTask)
              }
*/
              slide.elements.push(element)
            }


            // ---------- 形状 ----------
            else if (el.type === 'shape') {
              if (el.shapType === 'line' || /Connector/.test(el.shapType)) {
                // 线条元素（单独处理）
                const lineElement = parseLineElement(el, ratio)
                slide.elements.push(lineElement)
              }
              else {
                const shape = shapeList.find(item => item.pptxShapeType === el.shapType)

                const vAlignMap: { [key: string]: ShapeTextAlign } = {
                  mid: 'middle',
                  down: 'bottom',
                  up: 'top',
                }

                const gradient: Gradient | undefined = el.fill?.type === 'gradient'
                  ? {
                    type: el.fill.value.path === 'line' ? 'linear' : 'radial',
                    colors: el.fill.value.colors.map(item => ({
                      ...item,
                      pos: parseInt(item.pos),
                    })),
                    rotate: el.fill.value.rot,
                  }
                  : undefined

                const pattern: string | undefined = el.fill?.type === 'image' ? el.fill.value.picBase64 : undefined
                const fill = el.fill?.type === 'color' ? el.fill.value : ''
                const style = getStyle(convertFontSizePtToPx(el.content, ratio, el.autoFit)) + (el.pathBBox.pWidth ? ';width:' + (el.pathBBox.pWidth * ratio) + 'px;height:' + (el.pathBBox.pHeight * ratio) + 'px;' : '') // 设置字体的样式等，这里由于不支持的样式在里面会过滤
                const element: PPTShapeElement = {
                  type: 'shape',
                  id: nanoid(10),
                  width: el.width,
                  height: el.height,
                  left: el.left,
                  top: el.top,
                  viewBox: [200, 200],
                  path: 'M 0 0 L 200 0 L 200 200 L 0 200 Z',
                  fill,
                  gradient,
                  pattern,
                  fixedRatio: false,
                  rotate: el.rotate,
                  pathBBox: el.pathBBox,
                  outline: {
                    color: el.borderColor,
                    width: +(el.borderWidth * ratio).toFixed(2),
                    style: el.borderType,
                  },
                  text: {
                    content: convertFontSizePtToPx(el.content, ratio, el.autoFit),
                    style: style,
                    defaultFontName: theme.value.fontName,
                    defaultColor: theme.value.fontColor,
                    align: vAlignMap[el.vAlign] || 'middle',
                  },
                  flipH: el.isFlipH,
                  flipV: el.isFlipV,
                }

                if (el.shadow) {
                  element.shadow = {
                    h: el.shadow.h * ratio,
                    v: el.shadow.v * ratio,
                    blur: el.shadow.blur * ratio,
                    color: el.shadow.color,
                  }
                }

                if (shape) {
                  element.path = shape.path
                  // const { maxX, maxY } = getSvgPathRange(el.path);
                  element.viewBox = shape.viewBox
                  // element.viewBox = [originWidth || maxX, originHeight || maxY];
                  if (shape.pathFormula) {
                    element.pathFormula = shape.pathFormula
                    element.viewBox = [el.width, el.height]
                    // element.viewBox = [poriginWidth || originWidth || maxX, poriginHeight || originHeight || maxY];  
                    const pathFormula = SHAPE_PATH_FORMULAS[shape.pathFormula]
                    if ('editable' in pathFormula && pathFormula.editable) {
                      element.path = pathFormula.formula(el.width, el.height, pathFormula.defaultValue)
                      element.keypoints = pathFormula.defaultValue
                    }
                    else {
                      element.path = pathFormula.formula(el.width, el.height)
                    }
                  }
                }
                else if (el.path && el.path.indexOf('NaN') === -1) {
                  const { maxX, maxY } = getSvgPathRange(el.path)
                  element.path = el.path
                  element.viewBox = poriginWidth ? [maxX, maxY] : [originWidth, originHeight]
                  // element.viewBox = [originWidth || maxX, originHeight || maxY];  
                  // element.viewBox = originWidth? [(originWidth/(poriginWidth||1)), (originHeight/(poriginHeight||1))] : [maxX, maxY];
                  // element.viewBox = [poriginWidth || maxX, poriginHeight || maxY];
                }

                if (el.shapType === 'custom') {
                  if (el.path!.indexOf('NaN') !== -1) {
                    if (element.width === 0) element.width = 0.1
                    if (element.height === 0) element.height = 0.1
                    element.path = el.path!.replace(/NaN/g, '0')
                  }
                  const { maxX, maxY } = getSvgPathRange(element.path)
                  element.viewBox = poriginWidth ? [maxX, maxY] : [originWidth, originHeight]
                  // element.viewBox = [originWidth || maxX, originHeight || maxY];  
                  // element.viewBox = [poriginWidth || originWidth || maxX, poriginHeight || originHeight || maxY];  
                  // element.viewBox = [poriginWidth || originWidth || maxX, poriginHeight || originHeight || maxY];  
                  // element.viewBox = [Math.max(maxX, originWidth), Math.max(maxY, originHeight)];
                  // element.viewBox = [originWidth, originHeight];
                }

                if (element.path) slide.elements.push(element)
              }
            }

            // ---------- 表格 ----------
            else if (el.type === 'table') {
              const row = el.data.length
              const col = el.data[0].length

              const style: TableCellStyle = {
                fontname: theme.value.fontName,
                color: theme.value.fontColor,
              }
              const data: TableCell[][] = []

              for (let i = 0; i < row; i++) {
                const rowCells: TableCell[] = []
                for (let j = 0; j < col; j++) {
                  const cellData = el.data[i][j]

                  let textDiv: HTMLDivElement | null = document.createElement('div')
                  textDiv.innerHTML = cellData.text
                  const p = textDiv.querySelector('p')
                  const align = p?.style.textAlign || 'left'

                  const span = textDiv.querySelector('span')
                  const fontsize = span?.style.fontSize
                    ? (parseInt(span?.style.fontSize) * ratio).toFixed(1) + 'px'
                    : ''
                  const fontname = span?.style.fontFamily || ''
                  const color = span?.style.color || cellData.fontColor

                  rowCells.push({
                    id: nanoid(10),
                    colspan: cellData.colSpan || 1,
                    rowspan: cellData.rowSpan || 1,
                    text: textDiv.innerText,
                    style: {
                      ...style,
                      align: ['left', 'right', 'center'].includes(align) ? (align as 'left' | 'right' | 'center') : 'left',
                      fontsize,
                      fontname,
                      color,
                      bold: cellData.fontBold,
                      backcolor: cellData.fillColor,
                    },
                  })
                  textDiv = null
                }
                data.push(rowCells)
              }

              const allWidth = el.colWidths.reduce((a, b) => a + b, 0)
              const colWidths: number[] = el.colWidths.map(item => item / allWidth)

              const firstCell = el.data[0][0]
              const border = firstCell.borders.top ||
                firstCell.borders.bottom ||
                el.borders.top ||
                el.borders.bottom ||
                firstCell.borders.left ||
                firstCell.borders.right ||
                el.borders.left ||
                el.borders.right
              const borderWidth = border?.borderWidth || 0
              const borderStyle = border?.borderType || 'solid'
              const borderColor = border?.borderColor || '#eeece1'

              slide.elements.push({
                type: 'table',
                id: nanoid(10),
                width: el.width,
                height: el.height,
                left: el.left,
                top: el.top,
                colWidths,
                rotate: 0,
                data,
                outline: {
                  width: +(borderWidth * ratio || 2).toFixed(2),
                  style: borderStyle,
                  color: borderColor,
                },
                cellMinHeight: el.rowHeights[0] ? el.rowHeights[0] * ratio : 36,
              })
            }

            // ---------- 图表 ----------
            else if (el.type === 'chart') {
              let labels: string[]
              let legends: string[]
              let series: number[][]

              if (el.chartType === 'scatterChart' || el.chartType === 'bubbleChart') {
                labels = el.data[0].map((_, index) => lang.ssCoord.replace(/\*/g, (index + 1)))
                legends = ['X', 'Y']
                series = el.data
              }
              else {
                const data = el.data as ChartItem[]
                labels = Object.values(data[0].xlabels)
                legends = data.map(item => item.key)
                series = data.map(item => item.values.map(v => v.y))
              }

              const options: ChartOptions = {}

              let chartType: ChartType = 'bar'
              switch (el.chartType) {
                case 'barChart':
                case 'bar3DChart':
                  chartType = 'bar'
                  if (el.barDir === 'bar') chartType = 'column'
                  if (el.grouping === 'stacked' || el.grouping === 'percentStacked') options.stack = true
                  break
                case 'lineChart':
                case 'line3DChart':
                  if (el.grouping === 'stacked' || el.grouping === 'percentStacked') options.stack = true
                  chartType = 'line'
                  break
                case 'areaChart':
                case 'area3DChart':
                  if (el.grouping === 'stacked' || el.grouping === 'percentStacked') options.stack = true
                  chartType = 'area'
                  break
                case 'scatterChart':
                case 'bubbleChart':
                  chartType = 'scatter'
                  break
                case 'pieChart':
                case 'pie3DChart':
                  chartType = 'pie'
                  break
                case 'radarChart':
                  chartType = 'radar'
                  break
                case 'doughnutChart':
                  chartType = 'ring'
                  break
                default:
              }

              slide.elements.push({
                type: 'chart',
                id: nanoid(10),
                chartType,
                width: el.width,
                height: el.height,
                left: el.left,
                top: el.top,
                rotate: 0,
                themeColors: el.colors.length ? el.colors : theme.value.themeColors,
                textColor: theme.value.fontColor,
                data: {
                  labels,
                  legends,
                  series,
                },
                options,
              })
            }

            // ---------- 组合 ----------
            else if (el.type === 'group') {
              // 先将子元素坐标转换到画布绝对坐标
              let elements: BaseElement[] = el.elements.map((_el: any) => {
                let left = _el.left + originLeft
                let top = _el.top + originTop

                if (el.rotate) {
                  const { x, y } = calculateRotatedPosition(
                    originLeft, originTop, originWidth, originHeight,
                    _el.left, _el.top, el.rotate
                  )
                  left = x
                  top = y
                }

                const element = {
                  ..._el,
                  left,
                  top,
                }
                if (el.isFlipH && 'isFlipH' in element) element.isFlipH = true
                if (el.isFlipV && 'isFlipV' in element) element.isFlipV = true

                return element
              })

              if (el.isFlipH) elements = flipGroupElements(elements, 'y')
              if (el.isFlipV) elements = flipGroupElements(elements, 'x')

              // 递归解析子元素（注意：子元素的上传任务会加入同一个 uploadTasks 数组）
              await parseElements(elements, el)
            }

            // ---------- 图表组合（SmartArt）----------
            else if (el.type === 'diagram') {
              const elements = el.elements.map((_el: any) => ({
                ..._el,
                left: _el.left + originLeft,
                top: _el.top + originTop,
              }))
              await parseElements(elements, el)
            }
          }
        }

        // 开始解析当前幻灯片的所有元素（包括布局元素）
        await parseElements([...item.elements, ...item.layoutElements])

        // 幻灯片构建完成，加入数组
        slides.push(slide)
      }

      // 根据选项将幻灯片插入 store
      if (cover) {
        slidesStore.updateSlideIndex(0)
        slidesStore.setSlides(slides)
        addHistorySnapshot()
      }
      else if (isEmptySlide.value) {
        slidesStore.setSlides(slides)
        addHistorySnapshot()
      }
      else {
        addSlidesFromData(slides)
      }

      // 等待当前幻灯片内所有上传任务完成
      // await Promise.all(uploadTasks)
      Promise.all(uploadTasks)

      exporting.value = false
      onclose?.()
      /*
        // 更新视口尺寸（如果提供了的话）
        if (width !== undefined && height !== undefined) {
          console.log('正在触发视口尺寸更新事件:', { width, height, viewportRatio })
          
          // 同时也要更新slidesStore中的相关数据
          if (slidesStore.setViewportSize) {
            console.log('正在更新store中的视口尺寸')
            slidesStore.setViewportSize(width)
            if (slidesStore.setViewportRatio && viewportRatio !== undefined) {
              slidesStore.setViewportRatio(viewportRatio)
              console.log('视口比例已更新:', viewportRatio)
            }
          }
          
          window.dispatchEvent(new CustomEvent('viewportSizeUpdated', { 
            detail: { width, height, viewportRatio }
          }))
          console.log('视口尺寸更新事件已触发')
        }
        
        // 导入成功后，触发画布尺寸更新
        // 使用 nextTick 确保DOM更新完成后再触发
        console.log('开始触发画布尺寸更新事件...')
        nextTick(() => {
          console.log('DOM更新完成，触发 slidesDataUpdated 事件')
          // 触发自定义事件，通知需要更新画布尺寸的组件
          window.dispatchEvent(new CustomEvent('slidesDataUpdated', { 
            detail: { 
              slides, 
              cover,
              title,
              theme,
              width,
              height,
              viewportRatio,
              timestamp: Date.now()
            } 
          }))
          console.log('slidesDataUpdated 事件已触发')
          
          // 检查并调整幻灯片索引，确保在有效范围内
          const newSlideCount = slides.length
          const currentIndex = slidesStore.slideIndex
          if (currentIndex >= newSlideCount) {
            console.log('调整幻灯片索引:', currentIndex, '->', Math.max(0, newSlideCount - 1))
            slidesStore.updateSlideIndex(Math.max(0, newSlideCount - 1))
          }
          
          console.log('画布尺寸更新事件处理完成')
          
  
        })
  */

    }

    reader.readAsArrayBuffer(file)
  }

  const getFile = (url: string): Promise<{ data: any }> => {
    return new Promise((resolve, reject) => {
      // 检查 AWS SDK 是否可用
      if (typeof window !== 'undefined' && !window.AWS) {
        reject(new Error('AWS SDK not available'))
        return
      }

      const credentials = {
        accessKeyId: 'AKIATLPEDU37QV5CHLMH',
        secretAccessKey: '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',
      } // 秘钥形式的登录上传

      window.AWS.config.update(credentials)
      window.AWS.config.region = 'cn-northwest-1' // 设置区域

      const s3 = new window.AWS.S3({ params: { Bucket: 'ccrb' } })

      // 解析文件名
      const bucketUrl = 'https://ccrb.s3.cn-northwest-1.amazonaws.com.cn/'
      if (!url.startsWith(bucketUrl)) {
        reject(new Error('Invalid S3 URL format'))
        return
      }

      const name = decodeURIComponent(url.split(bucketUrl)[1])
      // const name = url.split(bucketUrl)[1]
      console.log('aws-name:', name)

      if (!name) {
        reject(new Error('Could not extract file name from URL'))
        return
      }

      const params = {
        Bucket: 'ccrb',
        Key: name,
      }

      s3.getObject(params, (err: any, data: any) => {
        if (err) {
          console.error('S3 getObject error:', err, err.stack)
          reject(err)
        }
        else {
          console.log('S3 getObject success:', data)
          resolve({ data: data.Body })
        }
      })
    })
  }


  const getFile2 = (url: string): Promise<{ data: any }> => {
    return new Promise((resolve, reject) => {
      console.log('直接使用原始 URL 获取文件:', url)

      // 直接使用 fetch 获取文件，浏览器会自动处理 URL 解码
      fetch(url)
        .then(response => {
          if (!response.ok) {
            console.error('HTTP 错误:', response.status, response.statusText)
            throw new Error(`HTTP error! status: ${response.status}`)
          }
          console.log('文件获取成功，大小:', response.headers.get('content-length'))
          return response.arrayBuffer()
        })
        .then(buffer => {
          console.log('文件内容读取成功，大小:', buffer.byteLength)
          resolve({ data: buffer })
        })
        .catch(error => {
          console.error('Fetch error:', error)
          reject(error)
        })
    })
  }

  return {
    importSpecificFile,
    importJSON,
    importPPTXFile,
    readJSON,
    exportJSON2,
    exporting,
    getFile,
    getFile2,
    dataToFile,
    uploadFileToS3
  }
}