import { ElMessage } from 'element-plus'
import type { LODOPObject } from '@/utils/hooks/useLodop'

type LodopGetter = () => LODOPObject | null

export function downloadPDF(url: string): string {
  if (!/^https?:/i.test(url)) return url
  const xhr = new XMLHttpRequest()
  let arrayBuffer = false
  xhr.open('GET', url, false)
  if (xhr.overrideMimeType) {
    try {
      xhr.responseType = 'arraybuffer'
      arrayBuffer = true
    } catch {
      xhr.overrideMimeType('text/plain; charset=x-user-defined')
    }
  }
  xhr.send(null)
  const data = (xhr.response ||
    (xhr as unknown as { responseBody?: unknown }).responseBody) as
    | ArrayBuffer
    | string
  let dataArray: Uint8Array
  if (arrayBuffer) {
    dataArray = new Uint8Array(data as ArrayBuffer)
  } else {
    const text = data as string
    dataArray = new Uint8Array(text.length)
    for (let i = 0; i < dataArray.length; i++) dataArray[i] = text.charCodeAt(i)
  }
  const digits =
    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
  let strData = ''
  for (let i = 0, ii = dataArray.length; i < ii; i += 3) {
    const b1 = dataArray[i] & 0xff
    const b2 = dataArray[i + 1] & 0xff
    const b3 = dataArray[i + 2] & 0xff
    const d1 = b1 >> 2
    const d2 = ((b1 & 3) << 4) | (b2 >> 4)
    const d3 = i + 1 < ii ? ((b2 & 0xf) << 2) | (b3 >> 6) : 64
    const d4 = i + 2 < ii ? b3 & 0x3f : 64
    strData +=
      digits.charAt(d1) +
      digits.charAt(d2) +
      digits.charAt(d3) +
      digits.charAt(d4)
  }
  return 'data:application/pdf;base64,' + strData
}

export function createLodopCaller(getCLodop: LodopGetter) {
  let lodop: LODOPObject | null = null
  let lodopCallback: { [key: string]: (value: string) => void } = {}

  return (fn: (lodop: LODOPObject) => string) => {
    if (!lodop) {
      lodop = getCLodop()
      if (!lodop) return Promise.resolve('')
      lodop.On_Return_Remain = true
      lodop.SET_PRINT_MODE('CATCH_PRINT_STATUS', true)
      lodopCallback = {}
      lodop.On_Return = (id, value) => {
        const cb = lodopCallback[id]
        if (!cb) return
        delete lodopCallback[id]
        cb(value)
      }
    }

    return new Promise<string>((resolve) => {
      if (!lodop) {
        resolve('')
        return
      }
      const id = fn(lodop)
      lodopCallback[id] = resolve
    })
  }
}

interface PrintData {
  filePath?: string
  fileData?: string
}

interface PrintWithLodopOptions {
  getCLodop: LodopGetter
  printer: string
  data: PrintData
  callback?: (success: boolean) => void
  timeout?: number
  baseUrl?: string
}

export async function printWithLodop(options: PrintWithLodopOptions) {
  const {
    getCLodop,
    printer,
    data,
    callback,
    timeout = 30000,
    baseUrl = '',
  } = options
  const lodop = getCLodop()
  if (!lodop) return

  const lodopCall = createLodopCaller(getCLodop)
  lodop.PRINT_INIT('打印内容')
  const printerIndex = lodop.SET_PRINTER_INDEX(printer)
  if (!printerIndex) {
    ElMessage.error('打印机设置失败')
    callback?.(false)
    return
  }

  if (data.filePath) {
    const strURL = /^https?:/i.test(data.filePath)
      ? data.filePath
      : baseUrl + data.filePath
    lodop.ADD_PRINT_PDF(
      0,
      0,
      '100%',
      '100%',
      /^https?:/i.test(strURL) ? downloadPDF(strURL) : strURL,
    )
  } else {
    lodop.SEND_PRINT_RAWDATA(data.fileData || '')
  }

  if (lodop.CVERSION) {
    const startTime = Date.now()
    const jobCode = await lodopCall((instance) => {
      instance.SET_PRINT_MODE('CATCH_PRINT_STATUS', true)
      return instance.PRINT()
    })
    let pending = true
    while (pending) {
      const ok = await lodopCall((instance) =>
        instance.GET_VALUE('PRINT_STATUS_OK', jobCode),
      )
      if (ok == '1' || ok == '1.0') {
        callback?.(true)
        return
      }
      const exist = await lodopCall((instance) =>
        instance.GET_VALUE('PRINT_STATUS_EXIST', jobCode),
      )
      if (exist == '0' || exist == '0.0') {
        callback?.(true)
        return
      }
      await new Promise((r) => setTimeout(r, 500))
      if (Date.now() - startTime >= timeout) {
        ElMessage.error(`打印超时(${Math.floor(timeout / 1000)}秒)`)
        callback?.(false)
        pending = false
      }
    }
    return
  }

  lodop.PRINT()
  callback?.(false)
}
