const {
  getWxApi,
  isCancelError
} = require('../utils/base-utils.js')
const {
  formatBytes
} = require('../utils/binary-utils.js')

function formatExportStamp(date = new Date()) {
  const pad = (value, length = 2) => String(value).padStart(length, '0')

  return [
    date.getFullYear(),
    pad(date.getMonth() + 1),
    pad(date.getDate()),
    '-',
    pad(date.getHours()),
    pad(date.getMinutes()),
    pad(date.getSeconds())
  ].join('')
}

function normalizeExtensions(extensions = []) {
  return extensions
    .map((extension) => String(extension || '').trim().replace(/^\./, '').toLowerCase())
    .filter(Boolean)
}

function escapeRegExp(text) {
  return String(text).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}

function getRuntimeInfo() {
  const wxApi = getWxApi()

  try {
    if (typeof wxApi.getDeviceInfo === 'function') return wxApi.getDeviceInfo() || {}
  } catch (error) {}

  try {
    if (typeof wxApi.getSystemInfoSync === 'function') return wxApi.getSystemInfoSync() || {}
  } catch (error) {}

  return {}
}

function getRuntimePlatform() {
  const info = getRuntimeInfo()
  return String(info.platform || info.system || '').toLowerCase()
}

function isPcRuntime() {
  const platform = getRuntimePlatform()

  return /windows|mac|devtools/.test(platform)
}

function getPathFileName(filePath) {
  const text = String(filePath || '').split(/[\\/]/).pop() || ''

  try {
    return decodeURIComponent(text)
  } catch (error) {
    return text
  }
}

function getFileName(file, fallback = '未命名文件') {
  const name = file && (file.name || file.fileName)
  if (name) return String(name)

  return getPathFileName(getFilePath(file)) || fallback
}

function getFilePath(file) {
  return file && (file.path || file.tempFilePath || file.filePath)
    ? (file.path || file.tempFilePath || file.filePath)
    : ''
}

function getFileSize(file) {
  const size = Number(file && file.size)

  return Number.isFinite(size) && size >= 0 ? size : 0
}

function normalizeSelectedFiles(result, fallbackName = '未命名文件') {
  const sourceFiles = Array.isArray(result && result.tempFiles)
    ? result.tempFiles
    : []
  const pathFiles = Array.isArray(result && result.tempFilePaths)
    ? result.tempFilePaths.map((filePath) => ({ path: filePath }))
    : []
  const directFile = result && (result.path || result.tempFilePath || result.filePath)
    ? [result]
    : []

  return sourceFiles.concat(pathFiles, directFile)
    .map((file, index) => {
      const path = getFilePath(file)

      return {
        file,
        name: getFileName(file, index === 0 ? fallbackName : `${fallbackName}-${index + 1}`),
        path,
        size: getFileSize(file)
      }
    })
    .filter((file) => !!file.path)
}

function getFirstSelectedFile(result, fallbackName = '未命名文件') {
  const files = normalizeSelectedFiles(result, fallbackName)
  const fileInfo = files[0]
  if (!fileInfo) throw new Error('没有选择文件')

  return fileInfo
}

function assertFileExtension(fileInfo, extensions = [], message = '文件格式不符') {
  const normalizedExtensions = normalizeExtensions(extensions)
  if (!normalizedExtensions.length) return

  const nameText = `${fileInfo && fileInfo.name ? fileInfo.name : ''} ${fileInfo && fileInfo.path ? fileInfo.path : ''}`
  const matched = normalizedExtensions.some((extension) => (
    new RegExp(`\\.${escapeRegExp(extension)}$`, 'i').test(nameText)
  ))

  if (!matched) throw new Error(message)
}

function chooseMessageFile(options = {}) {
  const wxApi = getWxApi()
  const extensions = normalizeExtensions(options.extensions || options.extension || [])
  const type = extensions.length ? 'file' : (options.type || 'all')

  return new Promise((resolve, reject) => {
    if (typeof wxApi.chooseMessageFile !== 'function') {
      reject(new Error('当前微信版本不支持从聊天记录选择文件，请升级微信或将文件转发到文件传输助手后重试'))
      return
    }

    const chooseOptions = {
      count: options.count || 1,
      type,
      success: resolve,
      fail: reject
    }

    if (extensions.length) chooseOptions.extension = extensions
    wxApi.chooseMessageFile(chooseOptions)
  })
}

function chooseLocalFile(options = {}) {
  const wxApi = getWxApi()
  const extensions = normalizeExtensions(options.extensions || options.extension || [])
  const type = extensions.length ? 'file' : (options.type || 'all')

  return new Promise((resolve, reject) => {
    if (typeof wxApi.chooseFile !== 'function') {
      reject(new Error(options.unsupportedMessage || '当前微信环境不支持本地文件选择，请将文件发送到文件传输助手后从聊天记录选择'))
      return
    }

    const chooseOptions = {
      count: options.count || 1,
      type,
      success: resolve,
      fail: reject
    }

    if (extensions.length) chooseOptions.extension = extensions
    wxApi.chooseFile(chooseOptions)
  })
}

async function chooseFile(source = 'message', options = {}) {
  const normalizedSource = source === 'local' || source === 'auto' ? source : 'message'
  // PC 微信更接近本地文件选择，移动端更稳定的是从聊天记录取文件。
  const preferLocal = normalizedSource === 'local' || (normalizedSource === 'auto' && isPcRuntime())
  const firstChooser = preferLocal ? chooseLocalFile : chooseMessageFile
  const fallbackChooser = preferLocal ? chooseMessageFile : chooseLocalFile

  try {
    return await firstChooser(options)
  } catch (error) {
    if (isCancelError(error) || options.fallback === false) throw error
    if (normalizedSource !== 'auto' && options.fallbackToOtherSource === false) throw error

    try {
      return await fallbackChooser(options)
    } catch (fallbackError) {
      if (isCancelError(fallbackError)) throw fallbackError

      const firstMessage = error && (error.errMsg || error.message || error)
      const fallbackMessage = fallbackError && (fallbackError.errMsg || fallbackError.message || fallbackError)
      throw new Error([
        firstMessage || '文件选择失败',
        fallbackMessage || ''
      ].filter(Boolean).join('；'))
    }
  }
}

function readFile(filePath, options = {}) {
  const wxApi = getWxApi()

  return new Promise((resolve, reject) => {
    if (typeof wxApi.getFileSystemManager !== 'function') {
      reject(new Error('当前微信版本不支持读取文件'))
      return
    }

    const fs = wxApi.getFileSystemManager()
    const readOptions = {
      filePath,
      success: (res) => resolve(res.data),
      fail: reject
    }

    if (options.encoding) readOptions.encoding = options.encoding
    fs.readFile(readOptions)
  })
}

function toUint8Array(data) {
  if (data instanceof Uint8Array) return data
  if (data instanceof ArrayBuffer) return new Uint8Array(data)
  if (ArrayBuffer.isView(data)) {
    return new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
  }

  return new Uint8Array(data || new ArrayBuffer(0))
}

async function loadSelectedFile(source = 'message', options = {}) {
  const result = await chooseFile(source, options)
  const fileInfo = getFirstSelectedFile(result, options.fallbackName || '未命名文件')

  assertFileExtension(fileInfo, options.extensions || options.extension || [], options.extensionMessage || '文件格式不符')

  const data = await readFile(fileInfo.path, {
    encoding: options.encoding
  })
  const bytes = options.encoding ? null : toUint8Array(data)

  return {
    ...fileInfo,
    bytes,
    data,
    size: bytes ? bytes.length : (fileInfo.size || String(data || '').length),
    sizeText: formatBytes(bytes ? bytes.length : (fileInfo.size || String(data || '').length)),
    text: options.encoding ? String(data || '') : ''
  }
}

function getUserDataFilePath(fileName) {
  const wxApi = getWxApi()
  const userDataPath = wxApi.env && wxApi.env.USER_DATA_PATH

  if (!userDataPath) throw new Error('当前微信版本不支持生成文件')

  return `${userDataPath}/${fileName}`
}

function writeTextFile(filePath, data, encoding = 'utf8') {
  const wxApi = getWxApi()

  return new Promise((resolve, reject) => {
    if (typeof wxApi.getFileSystemManager !== 'function') {
      reject(new Error('当前微信版本不支持生成文件'))
      return
    }

    const fs = wxApi.getFileSystemManager()
    if (typeof fs.writeFileSync === 'function') {
      try {
        fs.writeFileSync(filePath, data, encoding)
        resolve(filePath)
      } catch (error) {
        reject(error)
      }
      return
    }

    if (typeof fs.writeFile !== 'function') {
      reject(new Error('当前微信版本不支持生成文件'))
      return
    }

    fs.writeFile({
      data,
      encoding,
      filePath,
      fail: reject,
      success: () => resolve(filePath)
    })
  })
}

function saveFileToDisk(filePath) {
  const wxApi = getWxApi()

  return new Promise((resolve, reject) => {
    if (typeof wxApi.saveFileToDisk !== 'function') {
      reject(new Error('当前微信环境不支持保存到电脑磁盘'))
      return
    }

    wxApi.saveFileToDisk({
      filePath,
      fail: reject,
      success: resolve
    })
  })
}

function shareFileToChat(filePath, fileName) {
  const wxApi = getWxApi()

  return new Promise((resolve, reject) => {
    if (typeof wxApi.shareFileMessage !== 'function') {
      reject(new Error('当前微信版本不支持发送文件到聊天'))
      return
    }

    wxApi.shareFileMessage({
      fileName,
      filePath,
      success: resolve,
      fail: reject
    })
  })
}

async function exportFile(filePath, fileName, options = {}) {
  const wxApi = getWxApi()
  const preferDisk = options.target === 'disk'
    || (options.target !== 'chat' && isPcRuntime() && typeof wxApi.saveFileToDisk === 'function')

  if (preferDisk && typeof wxApi.saveFileToDisk === 'function') {
    try {
      await saveFileToDisk(filePath)

      return {
        method: 'disk'
      }
    } catch (error) {
      if (isCancelError(error) || typeof wxApi.shareFileMessage !== 'function') throw error
    }
  }

  if (typeof wxApi.shareFileMessage === 'function') {
    await shareFileToChat(filePath, fileName)

    return {
      method: 'chat'
    }
  }

  if (typeof wxApi.saveFileToDisk === 'function') {
    await saveFileToDisk(filePath)

    return {
      method: 'disk'
    }
  }

  throw new Error('当前微信环境不支持导出文件，请升级微信后重试')
}

async function saveTextFileToChat(fileName, data) {
  const filePath = getUserDataFilePath(fileName)

  await writeTextFile(filePath, data, 'utf8')
  const result = await exportFile(filePath, fileName)

  return {
    filePath,
    ...result
  }
}

module.exports = {
  assertFileExtension,
  chooseFile,
  chooseLocalFile,
  chooseMessageFile,
  exportFile,
  formatBytes,
  formatExportStamp,
  getFirstSelectedFile,
  getRuntimePlatform,
  getUserDataFilePath,
  isCancelError,
  isPcRuntime,
  loadSelectedFile,
  normalizeSelectedFiles,
  readFile,
  saveFileToDisk,
  saveTextFileToChat,
  shareFileToChat,
  toUint8Array,
  writeTextFile
}
