const DEFAULT_PACKET_SIZE = 20
const DEFAULT_MAX_FRAME_BYTES = 64
const TARGET_BLE_UUIDS = ['FFE0', 'FFE1']

const MODULE_PACKET_SIZES = [
  {
    packetSize: 0,
    patterns: [/HC[-_ ]?05/i]
  },
  {
    packetSize: 320,
    patterns: [/BT[-_ ]?24/i, /\bBT24\b/i]
  }
]

const bluetoothErrorMap = {
  10000: '蓝牙模块未初始化，请重新扫描',
  10001: '蓝牙不可用，请开启手机蓝牙',
  10002: '未找到指定设备，请重新扫描',
  10003: '连接失败，请靠近设备后重试',
  10004: '未发现设备服务',
  10005: '未发现设备特征值',
  10006: '当前连接已断开',
  10007: '当前特征值不支持此操作',
  10008: '系统蓝牙异常，请稍后重试',
  10009: '当前系统不支持 BLE',
  10012: '蓝牙操作超时，请重试',
  10013: '设备 ID 无效，请重新扫描'
}

function formatBluetoothError(error) {
  if (!error) return '操作失败'

  const message = bluetoothErrorMap[error.errCode]
  if (message) return message

  return error.errMsg || error.message || '蓝牙操作失败'
}

function formatSignalText(RSSI) {
  return typeof RSSI === 'number' ? `${RSSI} dBm` : '--'
}

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

  return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}.${pad(date.getMilliseconds(), 3)}`
}

function inferPacketSize(device = {}) {
  const text = [device.displayName, device.name, device.localName]
    .map((value) => String(value || ''))
    .join(' ')
    .toUpperCase()

  for (const item of MODULE_PACKET_SIZES) {
    const matchedPattern = (item.patterns || []).some((pattern) => pattern.test(text))
    if (matchedPattern) {
      return item.packetSize
    }
  }

  return DEFAULT_PACKET_SIZE
}

function resolvePacketSize(packetSize, frameLength) {
  if (packetSize === 0) return frameLength || DEFAULT_PACKET_SIZE
  if (Number.isInteger(packetSize) && packetSize > 0) return packetSize

  return DEFAULT_PACKET_SIZE
}

function normalizeUuid(value) {
  return String(value || '').replace(/-/g, '').toUpperCase()
}

function isTargetUuid(value) {
  const uuid = normalizeUuid(value)

  return TARGET_BLE_UUIDS.some((target) => uuid.indexOf(target) >= 0)
}

function hasTargetAdvertisedUuid(device) {
  return (device.advertisServiceUUIDs || []).some(isTargetUuid)
}

function mergeAdvertisedServiceUUIDs(left = [], right = []) {
  const uuidMap = {}
  const uuids = []

  left.concat(right).forEach((uuid) => {
    const key = normalizeUuid(uuid)
    if (!key || uuidMap[key]) return

    uuidMap[key] = true
    uuids.push(uuid)
  })

  return uuids
}

function normalizeDevice(device, options = {}) {
  const advertisServiceUUIDs = device.advertisServiceUUIDs || []
  const displayName = String(device.name || device.localName || '').trim() || '未命名设备'
  const packetSize = inferPacketSize({
    displayName,
    localName: device.localName,
    name: device.name
  })
  const isTargetAdvertised = hasTargetAdvertisedUuid({
    advertisServiceUUIDs
  })
  const now = Number(options.now) || Date.now()

  return {
    deviceId: device.deviceId,
    name: device.name || '',
    localName: device.localName || '',
    RSSI: device.RSSI,
    advertisServiceUUIDs,
    displayName,
    isTargetAdvertised,
    packetSize,
    signalText: formatSignalText(device.RSSI),
    serviceText: advertisServiceUUIDs.length ? advertisServiceUUIDs.join(', ') : '未广播服务',
    targetText: isTargetAdvertised ? '广播含目标 UUID' : '',
    lastSeenAt: now
  }
}

function normalizeHex(value) {
  return String(value || '')
    .replace(/0x/gi, '')
    .replace(/[\s,;:_-]/g, '')
    .toUpperCase()
}

function validateHex(value) {
  const trimmed = String(value || '').trim()
  const withoutPrefix = trimmed.replace(/0x/gi, '')
  const compact = normalizeHex(trimmed)

  if (!compact) return '请输入要发送的十六进制数据'
  if (/[^0-9a-fA-F\s,;:_-]/.test(withoutPrefix)) return '只支持十六进制字符'
  if (compact.length % 2 !== 0) return '十六进制长度必须为偶数'

  return ''
}

function normalizeMaxFrameBytes(maxFrameBytes) {
  const numberValue = Number(maxFrameBytes)
  if (Number.isFinite(numberValue) && Math.round(numberValue) === 0) return 0
  if (Number.isFinite(numberValue) && numberValue > 0) return Math.round(numberValue)

  return DEFAULT_MAX_FRAME_BYTES
}

function hexToArrayBuffer(hexText) {
  const hex = normalizeHex(hexText)
  const buffer = new ArrayBuffer(hex.length / 2)
  const view = new Uint8Array(buffer)

  for (let index = 0; index < view.length; index += 1) {
    view[index] = parseInt(hex.substr(index * 2, 2), 16)
  }

  return buffer
}

function arrayBufferToHex(buffer) {
  if (!buffer) return ''

  return Array.prototype.map.call(new Uint8Array(buffer), (item) => item.toString(16).padStart(2, '0')).join(' ').toUpperCase()
}

function formatFrameHex(bytes) {
  return Array.prototype.map.call(bytes || [], (item) => Number(item || 0).toString(16).padStart(2, '0')).join(' ').toUpperCase()
}

function getCharacteristicRole(properties = {}) {
  const canWrite = !!(properties.write || properties.writeNoResponse)
  const canNotify = !!(properties.notify || properties.indicate)

  if (canWrite && canNotify) return '收发'
  if (canWrite) return '发送'
  if (canNotify) return '接收'
  if (properties.read) return '读取'
  return '其他'
}

function buildCharacteristicText(serviceId, characteristicId) {
  if (!serviceId || !characteristicId) return '未选择'

  return `${serviceId.slice(0, 8)} / ${characteristicId.slice(0, 8)}`
}

function hasTargetCharacteristic(discovery) {
  return (discovery.services || []).some((service) => (
    isTargetUuid(service.uuid) || (service.characteristics || []).some((item) => isTargetUuid(item.uuid))
  ))
}

function isConnectionLostError(error) {
  if (!error) return false
  if ([10000, 10001, 10006, 10013].includes(error.errCode)) return true

  const message = String(error.errMsg || error.message || '').toLowerCase()
  return message.includes('disconnect') || message.includes('not connected')
}

module.exports = {
  DEFAULT_MAX_FRAME_BYTES,
  DEFAULT_PACKET_SIZE,
  arrayBufferToHex,
  buildCharacteristicText,
  formatBluetoothError,
  formatFrameHex,
  formatSignalText,
  formatTime,
  getCharacteristicRole,
  hasTargetAdvertisedUuid,
  hasTargetCharacteristic,
  hexToArrayBuffer,
  inferPacketSize,
  isConnectionLostError,
  isTargetUuid,
  mergeAdvertisedServiceUUIDs,
  normalizeDevice,
  normalizeHex,
  normalizeMaxFrameBytes,
  normalizeUuid,
  resolvePacketSize,
  validateHex
}
