<!-- 使用 type="home" 属性设置首页，其他页面不需要设置，默认为page；推荐使用json5，更强大，且允许注释 -->
<route lang="json5">
{
  style: {
    navigationBarTitleText: '远程打印',
  },
}
</route>

<template>
  <view class="page-form">
    <!-- 选择打印机--获取打印参数--上传打印 -->
    <wd-form ref="form" :model="formData">
      <wd-cell-group custom-class="form-group" border>
        <wd-cell title="上传文件"
                 title-width="100px"
                 required
                 prop="fileList"
                 custom-class="my-cell">
          <view class="file-list">
            <view v-for="(item, index) of fileList"
                  :key="item.filePath"
                  class="file-item"
                  @click="previewFile(item.name, item.filePath)">
              <view class="item-icon">
                <wd-img
                  :src="getFileIcon(item.name)"
                  :width="20"
                  :height="20"
                  mode="aspectFit"
                ></wd-img>
              </view>
              <view class="item-name">{{ item.name }}</view>
              <view class="item-del" @click.stop="removeFile(index)">
                <wd-img
                  :src="delSrc"
                  :width="20"
                  :height="20"
                  mode="aspectFit"
                ></wd-img>
              </view>
            </view>

            <!-- 选择文件: 支持多选多个 (系统文件另外传入) -->
            <wd-button v-if="accept != 'all'" size="small" @click="selectFile()">选择文件</wd-button>
          </view>
        </wd-cell>

        <wd-select-picker
          v-model="formData.printer"
          label="打印机"
          label-width="100px"
          type="radio"
          :columns="printerList"
          value-key="id"
          label-key="showName"
          :max="1"
          :show-confirm="false"
          filterable
          placeholder="请选择打印机"
          prop="printer"
          :rules="[{ required: true, message: '请选择打印机' }]"
          @change="handlePrinterChange"
        />
        <!-- 动态属性 -->
        <template v-for="option of printerOptions" :key="option.key">
          <!-- type: select 下拉选项 -->
          <wd-select-picker
            v-if="option.type == 'select'"
            v-model="formData[option.key]"
            :label="option.text"
            label-width="100px"
            type="radio"
            :columns="option.values"
            value-key="key"
            label-key="text"
            :max="1"
            :show-confirm="false"
            filterable
            :placeholder="`请选择${option.text}`"
            :prop="option.key"
            :rules="[{ required: true, message: `请选择${option.text}` }]"
          />
          <!-- type: radio 单选项 -->
          <wd-cell v-if="option.type == 'radio'"
                   :title="option.text"
                   title-width="100px"
                   custom-class="my-cell"
                   :prop="option.key"
                   :rules="[{ required: true, message: `请选择${option.text}` }]">
            <wd-radio-group v-model="formData[option.key]"
                            shape="dot"
                            inline>
              <wd-radio v-for="item of option.values"
                        :key="item.key"
                        :value="item.key">
                {{ item.text }}
              </wd-radio>
            </wd-radio-group>
          </wd-cell>
          <!-- type: rang 范围值 传值 方式为str,end -->
          <wd-cell v-if="option.type == 'rang'"
                   :title="option.text"
                   title-width="100px"
                   custom-class="my-cell"
                   :prop="option.key">
            <!-- TODO: 需增加 option.min option.max 控制; 默认全部 -->
            <!-- 输入框: 从 xx 到 xx -->
            <view style="text-align: left">
              <view class="inline-txt" style="margin-left: 0">从</view>
              <wd-input
                no-border
                custom-style="display: inline-block; width: 70px; vertical-align: middle"
                placeholder="起始值"
                v-model="formData[option.key][0]"
              />
              <view class="inline-txt">到</view>
              <wd-input
                no-border
                custom-style="display: inline-block; width: 70px; vertical-align: middle"
                placeholder="结束值"
                v-model="formData[option.key][1]"
              />
            </view>
          </wd-cell>
          <!-- type: number 数字 -->
          <wd-cell v-if="option.type == 'number'"
                   :title="option.text"
                   title-width="100px"
                   custom-class="my-cell"
                   :rules="[{ required: true, message: `请输入${option.text}` }]">
            <wd-input-number v-model="formData[option.key]"
                             :min="option.min"
                             :max="option.max"
                             :step="1"
                             step-strictly
                             input-width="100px" />
          </wd-cell>
        </template>
      </wd-cell-group>

      <view class="form-footer">
        <wd-button type="primary" size="large" block @click="handleSubmit()">确认打印</wd-button>
      </view>
    </wd-form>    
  </view>
</template>

<script lang="ts" setup>
import { useToast, useMessage } from 'wot-design-uni'
import { getUserHubPrints, getUserHubAttr, getInvoiceBatch } from '@/service/api'
import { getEnvBaseUrl, redirectToUpload, reLaunchToHome } from '@/utils'
import { useUserStore } from '@/store'

const userStore = useUserStore()
const token = userStore.token
const baseUrl = getEnvBaseUrl()
const toast = useToast()
const message = useMessage()
// 系统文件--all, 聊天文件--msg, 相册--image, 发票--invoice
const accept = ref("all")
const fileList = ref([])
const form = ref()
const formData: any = ref({
  printer: "",
})
const printerList: any = ref([])
const printerOptions: any = ref([])
const allowType = ['txt', 'pdf', 'doc', 'docx', 'xlsx', 'xls', 'ppt', 'pptx', 'gif', 'png', 'jpg', 'jpeg', 'webp']
const maxSize = 10 * 1024 * 1024

// 文件类型
const imageSrc = '/static/images/image.png'
const docSrc = '/static/images/doc.png'
const xlsSrc = '/static/images/xls.png'
const pptSrc = '/static/images/ppt.png'
const pdfSrc = '/static/images/pdf.png'
const txtSrc = '/static/images/txt.png'
const otherSrc = '/static/images/other.png'
const delSrc = '/static/images/deleteIcon.png'
// 文件类型图片匹配
const srcMap = {
  'doc': docSrc,
  'docx': docSrc,
  'xls': xlsSrc,
  'xlsx': xlsSrc,
  'ppt': pptSrc,
  'pptx': pptSrc,
  'png': imageSrc,
  'jpg': imageSrc,
  'jpeg': imageSrc,
  'gif': imageSrc,
  'webp': imageSrc,
  'txt': txtSrc,
  'pdf': pdfSrc,
}

// 上传成功的任务
const successPrint = ref([])
// 上传失败的任务
const failedFiles = ref([])

defineOptions({
  name: 'Print',
})

// 获取打印机
function getPrinterList() {
  let params = { id: "" }
  getUserHubPrints(params)
    .then((res: any) => {
      if (res.code === 0 && res.body) {
        printerList.value = res.body || []
        printerList.value.forEach(item => {
          item.showName = `${item.name} (${item.userHubName})`
        })
      }
    })
    .catch((e) => {})
}

// 处理更换打印机
function handlePrinterChange(item: any) {
  printerOptions.value = []
  formData.value = {
    printer: item.value
  }
  let obj = printerList.value.find(i => i.id == item.value) || {}
  let params = {
    id: obj.userHubId,
    printer: obj.name,
  }
  getUserHubAttr(params)
    .then((res: any) => {
      if (res.code === 0 && res.body) {
        printerOptions.value = res.body || []
        let _formData = {
          printer: item.value,
          userHubId: obj.userHubId,
          printerName: obj.name,
        }
        if (printerOptions.value.length) {
          printerOptions.value.forEach(option => {
            switch(option.type) {
              case "rang":
                _formData[option.key] = option.def ? option.def.split(',') : [option.min, '']
                break;
              default:
                _formData[option.key] = option.def
            }
          });
          formData.value = _formData
        }
      }
    })
    .catch((e) => {
    })
    .finally(() => {})
}

// 获取文件格式
function getFileType(fileName) {
  let type = ""
  const lastIndex = fileName.lastIndexOf('.')
  if (lastIndex != -1) {
    type = fileName.substring(lastIndex + 1)
  }
  return type
}

// 获取文件图标
function getFileIcon(fileName) {
  return srcMap[getFileType(fileName)] || otherSrc
}

// 处理选择文件
function selectFile() {
  switch(accept.value) {
    case "msg":
      uni.chooseMessageFile({
        count: 10,
        type: "all",
        success (res) {
          console.log('chooseMessageFile res: ', res);
          if (res.errMsg == "chooseMessageFile:ok") {
            let failList = []
            res.tempFiles.forEach(item => {
              if (item.size > maxSize || !allowType.includes(getFileType(item.name))) {
                failList.push(item)
              } else {
                fileList.value.push({
                  name: item.name,
                  filePath: item.path,
                })
              }
            })
            if (failList.length > 0) toast.warning(`文件大小限制为10MB, 文件格式仅支持 ${allowType.join('、')}， 所选文件有 ${failList.length} 格式不符合`)
          }
        },
        fail () {
          toast.warning("选择微信文件失败, 请重试")
        }
      })
      break;
    case "image":
      uni.chooseImage({
        count: 10,
        sourceType: ['album', 'camera'],
        sizeType: ['original', 'compressed'],
        extension: ['png', 'jpg', 'jpeg', 'gif', 'webp'],
        success (res) {
          console.log('chooseImage res: ', res);
          if (res.errMsg == "chooseImage:ok") {
            let failList = []
            res.tempFiles.forEach((item, index) => {
              if (item.size > maxSize) {
                failList.push(item)
              } else {
                fileList.value.push({
                  // name: item.path.substring(item.path.lastIndexOf("/") + 1),
                  name: `图片-${new Date().getTime() + index}.${getFileType(item.path)}`,
                  filePath: item.path,
                })
              }
            })
            if (failList.length > 0) toast.warning(`文件大小限制为10MB, 所选文件有 ${failList.length} 格式不符合`)
          }
        },
        fail () {
          toast.warning("选择相册图片失败, 请重试")
        }
      })
      break;
    case "invoice":
      uni.chooseInvoice({
        success (res) {
          if (res.errMsg == "chooseInvoice:ok") {
            let list = JSON.parse(res.invoiceInfo)
            if (list && list.length > 0) {
              let params = {
                cardId: list.map(i => i.card_id).join(","),
                encryptCode: list.map(i => i.encrypt_code).join(","),
              }
              getInvoiceBatch(params).then(res => {
                if (res.code == 0 && res.body) {
                  console.log('InvoiceBatch res.body: ', res.body);
                  // 遍历下载pdf 存储在 fileList 打印
                  res.body.forEach(item => {
                    downloadFile(`发票-${item.payee}.png`, item.userInfo.pdfUrl)
                  });
                }
              })
            }
          }
        },
        fail () {
          toast.warning("选择微信发票失败, 请重试")
        }
      })
      break;
  }
}

// 下载文件(用于处理发票)
function downloadFile(fileName, fileUrl) {
  uni.downloadFile({
    url: fileUrl,
    filePath: uni.env.USER_DATA_PATH + '/' + fileName,
    success: (res) => {
      fileList.value.push({
        name: fileName,
        filePath: res.filePath,
      })
    },
    fail () {
      toast.warning("下载文件失败, 请重试")
    }
  })
}

// 预览文件 (区分文件&图片)
function previewFile(fileName, filePath) {
  let fileType = getFileType(fileName)
  if (['png', 'jpg', 'jpeg', 'webp'].includes(fileType)) {
    // 图片预览
    let imgUrl = filePath
    uni.previewImage({
      current: 0,
      urls: [imgUrl],
    })
  } else {
    // 文件预览
    uni.openDocument({
      filePath: filePath,
      success () {},
      fail () {
        toast.warning("打开文件失败, 请重试")
      }
    })
  }
}

// 移除文件
function removeFile(index) {
  fileList.value.splice(index, 1)
  if (accept.value == "all" && fileList.value.length == 0) {
    message.confirm({
      msg: "文件已全部移除, 是否重新选择?",
      closeOnClickModal: false,
    }).then(() => {
      redirectToUpload()
    }).catch((error) => {
      reLaunchToHome()
    })
  }
}

// 批量打印文件
async function handleBatchPrint() {
  try {
    failedFiles.value = []; // 记录上传失败的文件

    for (let i = 0; i < fileList.value.length; i++) {
      const fileName = fileList.value[i].name;
      const filePath = fileList.value[i].filePath;
      toast.loading({
        loadingType: 'ring',
        msg: `开始上传文件 ${i + 1}: ${fileName}`
      })

      try {
        // 上传文件并监听进度
        const uploadRes = await handlePrint(filePath, (progress) => {
          toast.loading({
            loadingType: 'ring',
            msg: `文件 ${i + 1} 上传进度: ${progress}%`
          })
        });
        toast.success(`文件 ${i + 1} 上传成功`)
      } catch (error) {
        toast.error(`文件 ${i + 1} 上传失败`)
        failedFiles.value.push(fileList.value[i]); // 记录失败的文件
      }
    }

    // 检查是否有失败的文件
    if (failedFiles.value.length > 0) {
      message.confirm({
        title: '上传失败',
        msg: `有 ${failedFiles.value.length} 个文件上传失败，是否重新上传？`,
        closeOnClickModal: false,
      }).then(async () => {
        await retryUploadFiles(failedFiles.value); // 重新上传失败的文件
      }).catch((error) => {
        msgConfirm("上传完成", "取消重新上传, 其余文件已上传成功!")
      })
    } else {
      toast.success("所有文件上传成功")
      msgConfirm("上传完成", "所有文件上传成功")
    }
  } catch(error) {
    console.log("上传过程中发生错误", error);
  }
}

async function retryUploadFiles(failedFiles) {
 const newFailedFiles = []; // 记录重新上传失败的文件

  // 逐个重新上传失败的文件
  for (let i = 0; i < failedFiles.length; i++) {
    const fileName = failedFiles[i].name;
    const filePath = failedFiles[i].filePath;
    toast.loading({
      loadingType: 'ring',
      msg: `重新上传文件 ${i + 1}: ${fileName}`
    })

    try {
      const uploadRes = await handlePrint(filePath, (progress) => {
        toast.loading({
          loadingType: 'ring',
          msg: `文件 ${i + 1} 上传进度: ${progress}%`
        })
      });
      toast.success(`文件 ${i + 1} 重新上传成功`)
    } catch (error) {
      toast.error(`文件 ${i + 1} 重新上传成功`)
      newFailedFiles.push(failedFiles[i]); // 记录重新上传失败的文件
    }
  }

  // 检查是否还有失败的文件
  if (newFailedFiles.length > 0) {
    // 修改当前文件列表
    fileList.value = newFailedFiles
    message.alert({
      title: '重新上传失败',
      msg: `有 ${newFailedFiles.length} 个文件重新上传失败，请检查网络或文件后重试。`,
    })
  } else {
    toast.success("所有失败文件重新上传成功")
    msgConfirm("上传完成", "所有失败文件重新上传成功!")
  }
}

// 打印文件 (单个打印任务)
function handlePrint(filePath, onProgress) {
  return new Promise((resolve, reject) => {
    // 构造提交数据
    let params = {
      id: formData.value.userHubId,
      printer: formData.value.printerName,
    }
    printerOptions.value.forEach(option => {
      switch(option.type) {
        case "rang":
          // 打印页数范围不传
          // params[option.key] = formData.value[option.key].join(",")
          break;
        default:
          params[option.key] = formData.value[option.key]
      }
    })
    const queryParams = Object.entries(params).map(([key, value]) => {
      return `${key}=${encodeURIComponent(value)}`;
    }).join('&');

    const uploadTask = uni.uploadFile({
      url: `${baseUrl}/sys/wx/userHub/print?${queryParams}`, // 上传接口地址
      filePath: filePath,
      name: 'file', // 文件对应的 key
      formData: {
        // 其他表单数据
      },
      success: (res) => {
        if (res.statusCode === 200 && JSON.parse(res.data).code === 0) {
          resolve(res.data);
        } else {
          reject(new Error(`上传失败，状态码: ${res.statusCode}`));
        }
      },
      fail: (err) => {
        reject(err);
      },
    });

    // 监听上传进度
    uploadTask.onProgressUpdate((res) => {
      if (onProgress) {
        onProgress(res.progress); // 回调上传进度
      }
    });
  });
}

// 处理提交打印
function handleSubmit() {
  if (fileList.value.length == 0) {
    toast.warning('请先上传文件')
    return
  }
  form.value.validate().then(({ valid }) => {
    if (valid) {
      // 不区分单个/批量, 以批量打印处理
      handleBatchPrint()
    }
  })
}

// 打印完成后提示词
function msgConfirm(title="提示", msg="文件已上传成功!") {
  message.confirm({
    title,
    msg,
    confirmButtonText: "查看任务",
    cancelButtonText: "继续打印",
  }).then(() => {
    // 查看任务
    uni.redirectTo({
      url: `/pages/print/job?tab=0`,
    })
  }).catch(() => {
    // 继续打印
    failedFiles.value = []
    fileList.value = []
    printerOptions.value = []
    formData.value = {
      printer: ""
    }
    if (accept.value == "all") {
      redirectToUpload()
    }
  })
}

// 处理系统文件webview传入
function base64ToTempFilePath(fileName, base64Data, success, fail) {
  const fs = uni.getFileSystemManager()
  // const fileName = 'temp_' + Date.now() + '.png' // 自定义文件名，可根据需要修改
  const filePath = uni.env.USER_DATA_PATH + '/' + fileName
  const buffer = uni.base64ToArrayBuffer(base64Data)
  fs.writeFile({
    filePath,
    data: buffer,
    encoding: 'binary',
    success() {
      success && success(filePath)
    },
    fail() {
      fail && fail()
    }
  })
}

// 打开SSE webview页面
function openWebViewSSE() {
  // let list = JSON.stringify(fileList.value.map(item => item.name).join(",") || "")
  console.log('list: ', fileList.value);
  let list = JSON.stringify(fileList.value)
  const url = `https://service.1ai.ltd/webview-sse/index.html?token=${token}&list=${list}&t=${new Date().getTime()}`
  uni.navigateTo({
    url: `/pages/webview/sse?url=${encodeURIComponent(url)}`,
  })
}

onLoad((option) => {
  if (option && option.accept) {
    accept.value = option.accept
    // 处理系统文件--all: 从webview上传的文件
    if (option.accept == "all" && uni.getStorageSync('fileList')) {
      fileList.value = uni.getStorageSync('fileList');
      uni.removeStorageSync('fileList');

      fileList.value.forEach(item => {
        item.file = item.file.split('base64,')[1]
        base64ToTempFilePath(item.name, item.file, (filePath) => {
          console.log('转换成功，临时地址为:', filePath)
          item.filePath = filePath
        }, function() {
          toast.warning('文件转换失败，请重试')
        })
      })
    }
  }
  getPrinterList()
})
</script>

<style lang="scss" scoped>
:deep(.my-cell) {
  .wd-cell__value {
    text-align: left !important;
  }
}

:deep(.wd-radio-group) {
  padding: 0 !important;
  font-size: unset !important;
  .wd-radio {
    padding-top: 0 !important;
  }
}

:deep(.wd-upload__mask) {
  display: none !important;
}

:deep(.img-btn .wd-img__image) {
  width: 160rpx;
  height: 160rpx;
  border-radius: 40rpx;
}

.file-item {
  margin-bottom: 8rpx;
  display: flex;
  align-items: center;

  .item-icon {
    display: flex;
    width: 40rpx;
    height: 40rpx;
    margin-right: 8rpx;
  }
  .item-name {
    flex: 1;
    word-break: break-all;
  }
  .item-del {
    display: flex;
    width: 40rpx;
    height: 40rpx;
    margin-left: 8rpx;
  }
}

.inline-txt {
  display: inline-block;
  font-size: 28rpx;
  margin: 0 16rpx;
  color: rgba(0, 0, 0, 0.45);
  vertical-align: middle;
}
</style>

