const {
  notifyPageToast
} = require('../utils/page-toast.js')
const {
  bytesToUtf8Text
} = require('../utils/binary-utils.js')
const {
  DEFAULT_MAX_FRAME_BYTES,
  arrayBufferToHex,
  buildCharacteristicText,
  formatBluetoothError,
  formatFrameHex,
  getCharacteristicRole,
  hasTargetCharacteristic,
  hexToArrayBuffer,
  inferPacketSize,
  isConnectionLostError,
  isTargetUuid,
  normalizeMaxFrameBytes,
  resolvePacketSize,
  validateHex
} = require('./ble-utils.js')
const {
  DEFAULT_MAX_LOG_COUNT,
  appendLog,
  createClearLogsState,
  createLogItem
} = require('./ble-logs.js')
const {
  createProtocolHelperRegistry
} = require('./protocol-helper-registry.js')
const {
  createBleDeviceRegistry
} = require('./ble-device-registry.js')

const SCAN_TIMEOUT = 15000
const CONNECT_TIMEOUT = 10000
const RSSI_REFRESH_INTERVAL = 2000
const RESPONSE_TIMEOUT = 1000
const MAX_RESPONSE_BUFFER_BYTES = 128

const state = {
  adapterAvailable: false,
  adapterOpened: false,
  characteristicText: '未选择',
  connectedDevice: null,
  connectedServiceCount: 0,
  connectingDeviceId: '',
  devices: [],
  errorText: '',
  isAwaitingResponse: false,
  isConnecting: false,
  isDiscovering: false,
  isSending: false,
  logScrollTarget: '',
  logs: [],
  rxCount: 0,
  sendHex: '',
  sendQueueLength: 0,
  systemTip: '',
  signalText: '',
  txCount: 0,
  writeCharacteristicId: '',
  writeServiceId: '',
  writeType: ''
}

let initialized = false
let scanTimer = null
let rssiTimer = null
let isReadingRssi = false
let pendingRequest = null
let sendQueue = []
let isProcessingSendQueue = false
let sendQueueGeneration = 0
let sendJobSequence = 0
let logSequence = 0
const subscribers = []
const rawResponseSubscribers = []
const protocolHelperRegistry = createProtocolHelperRegistry()
const deviceRegistry = createBleDeviceRegistry()

function configureProtocolHelpers(helpers = {}) {
  protocolHelperRegistry.configure(helpers)
}

function setState(changedData) {
  Object.assign(state, changedData)
  subscribers.slice().forEach((subscriber) => {
    subscriber(getState())
  })
}

function getState() {
  return {
    ...state,
    devices: state.devices.slice(),
    logs: state.logs.slice()
  }
}

function subscribe(subscriber) {
  if (typeof subscriber !== 'function') return () => {}

  subscribers.push(subscriber)
  subscriber(getState())

  return () => {
    const index = subscribers.indexOf(subscriber)
    if (index >= 0) subscribers.splice(index, 1)
  }
}

function subscribeRawResponse(subscriber) {
  if (typeof subscriber !== 'function') return () => {}

  rawResponseSubscribers.push(subscriber)

  return () => {
    const index = rawResponseSubscribers.indexOf(subscriber)
    if (index >= 0) rawResponseSubscribers.splice(index, 1)
  }
}

function callWx(apiName, params = {}) {
  return new Promise((resolve, reject) => {
    const api = wx[apiName]

    if (typeof api !== 'function') {
      reject(new Error(`${apiName} 不可用`))
      return
    }

    api({
      ...params,
      success: resolve,
      fail: reject
    })
  })
}

function getResponseBufferHint(expected, options = {}) {
  if (Number.isFinite(Number(options.responseBufferHint))) {
    return Math.max(0, Math.round(Number(options.responseBufferHint)))
  }

  const getHint = protocolHelperRegistry.get('getResponseBufferHint')
  if (typeof getHint === 'function') {
    return Math.max(0, Math.round(Number(getHint(expected)) || 0))
  }

  return 0
}

function getResponseBufferLimit(expected, options = {}) {
  const responseLength = getResponseBufferHint(expected, options)
  const maxFrameBytes = options.maxFrameBytes
  const frameLimit = normalizeMaxFrameBytes(maxFrameBytes)

  if (frameLimit === 0) {
    return Math.max(MAX_RESPONSE_BUFFER_BYTES, responseLength + 8)
  }

  return Math.max(MAX_RESPONSE_BUFFER_BYTES, frameLimit + 8, responseLength + 8)
}

function validateDmaFrameLength(bytes, options = {}) {
  const maxFrameBytes = normalizeMaxFrameBytes(options.maxFrameBytes)
  if (maxFrameBytes === 0) return ''

  if (bytes.length > maxFrameBytes) {
    return `发送帧长度 ${bytes.length} 字节，超过最大包长 ${maxFrameBytes} 字节限制`
  }

  if (!options.expected) return ''

  const responseLength = getResponseBufferHint(options.expected, options)

  if (responseLength > maxFrameBytes) {
    return `预计返回帧长度 ${responseLength} 字节，超过最大包长 ${maxFrameBytes} 字节限制`
  }

  return ''
}

function addLog(direction, payload, note = '', extras = {}) {
  logSequence += 1
  const logItem = createLogItem(direction, payload, note, extras, logSequence)
  const nextLogs = appendLog(state.logs, logItem, DEFAULT_MAX_LOG_COUNT)

  setState({
    logScrollTarget: logItem.id,
    logs: nextLogs
  })
}

function getReceiveCrcState(rawBytes) {
  if (!rawBytes || rawBytes.length < 4) return ''

  const inspectReceivedBytes = protocolHelperRegistry.get('inspectReceivedBytes')
  if (typeof inspectReceivedBytes === 'function') {
    const note = inspectReceivedBytes(rawBytes, {
      pendingRequest: pendingRequest ? pendingRequest.expected : null
    })

    if (note) return note
  }

  return pendingRequest ? '片段' : ''
}

function showCommandAlert(title, content) {
  const message = content || title || '操作失败'

  notifyPageToast(message, 'error')
  setState({
    errorText: message
  })
}

function clearScanTimer() {
  if (!scanTimer) return

  clearTimeout(scanTimer)
  scanTimer = null
}

async function stopScan() {
  clearScanTimer()

  try {
    await callWx('stopBluetoothDevicesDiscovery')
  } catch (error) {
    if (error.errCode !== 10000) {
      setState({
        errorText: formatBluetoothError(error)
      })
    }
  }

  setState({
    isDiscovering: false
  })
}

function resetScanTimer() {
  clearScanTimer()

  scanTimer = setTimeout(() => {
    stopScan()

    if (!state.devices.length) {
      setState({
        systemTip: '安卓真机请确认系统定位已开启，并允许微信使用附近设备或位置信息。'
      })
    }
  }, SCAN_TIMEOUT)
}

function mergeDevices(devices) {
  const changed = deviceRegistry.mergeDevices(devices)
  if (!changed) return

  setState({
    devices: deviceRegistry.getDeviceList()
  })
}

function clearPendingRequest() {
  if (!pendingRequest) return null

  const pending = pendingRequest
  clearTimeout(pendingRequest.timer)
  pendingRequest = null
  setState({
    isAwaitingResponse: false
  })

  return pending
}

function cancelPendingRequest() {
  const pending = clearPendingRequest()

  if (pending) {
    pending.resolve(false)
  }
}

function clearSendQueue() {
  if (!sendQueue.length) return

  const queuedJobs = sendQueue.splice(0)
  queuedJobs.forEach((job) => {
    job.resolve(false)
  })
  setState({
    sendQueueLength: 0
  })
}

function resetSendRuntimeState() {
  sendQueueGeneration += 1
  cancelPendingRequest()
  clearSendQueue()
  isProcessingSendQueue = false
  setState({
    isAwaitingResponse: false,
    isSending: false,
    sendQueueLength: 0
  })
}

function clearConnectedState(changedData = {}) {
  stopRssiRefresh()
  resetSendRuntimeState()
  setState({
    characteristicText: '未选择',
    connectedDevice: null,
    connectedServiceCount: 0,
    connectingDeviceId: '',
    isConnecting: false,
    signalText: '',
    writeCharacteristicId: '',
    writeServiceId: '',
    writeType: '',
    ...changedData
  })
}

function stopRssiRefresh() {
  if (rssiTimer) {
    clearInterval(rssiTimer)
    rssiTimer = null
  }
  isReadingRssi = false
}

function applyRssiUpdate(deviceId, rssi) {
  if (!state.connectedDevice || state.connectedDevice.deviceId !== deviceId || typeof rssi !== 'number') {
    return
  }

  const result = deviceRegistry.applyRssiUpdate(deviceId, rssi, state.connectedDevice)
  if (!result || !result.updatedDevice) return

  setState({
    connectedDevice: result.updatedDevice,
    signalText: result.updatedDevice.signalText || '',
    devices: result.deviceList
  })
}

async function refreshConnectedRssi() {
  const { connectedDevice } = state
  if (!connectedDevice || typeof wx === 'undefined' || typeof wx.getBLEDeviceRSSI !== 'function') return
  if (isReadingRssi) return

  isReadingRssi = true
  try {
    const result = await callWx('getBLEDeviceRSSI', {
      deviceId: connectedDevice.deviceId
    })
    if (!state.connectedDevice || state.connectedDevice.deviceId !== connectedDevice.deviceId) return
    applyRssiUpdate(connectedDevice.deviceId, result && result.RSSI)
  } catch (error) {
    if (isConnectionLostError(error)) {
      clearConnectedState({
        errorText: formatBluetoothError(error)
      })
    }
  } finally {
    isReadingRssi = false
  }
}

function startRssiRefresh() {
  stopRssiRefresh()

  if (!state.connectedDevice || typeof wx === 'undefined' || typeof wx.getBLEDeviceRSSI !== 'function') {
    return
  }

  refreshConnectedRssi()
  rssiTimer = setInterval(() => {
    refreshConnectedRssi()
  }, RSSI_REFRESH_INTERVAL)
}

function finishPendingRequest(resolveValue) {
  const pending = clearPendingRequest()

  if (pending) {
    pending.resolve(resolveValue)
  }
}

function consumePendingResponseBuffer() {
  const pending = pendingRequest
  if (!pending || !Array.isArray(pending.responseBuffer)) return

  const responseReader = pending.responseReader || protocolHelperRegistry.get('readResponseFromBuffer')
  if (typeof responseReader !== 'function') {
    const content = `${pending.label} 未配置响应解析器，已丢弃`
    addLog('SYS', content)
    finishPendingRequest(false)
    if (pending.showModal) {
      showCommandAlert('通讯异常', content)
    }
    return
  }

  const result = responseReader(pending.responseBuffer, pending.expected, {
    maxFrameBytes: pending.expected && pending.expected.maxFrameBytes
  })
  if (!result || result.status === 'pending') return

  if (result.status === 'frame-too-long') {
    const content = `${pending.label} 返回帧长度 ${result.responseLength} 字节，超过最大包长 ${result.frameLimit} 字节限制，已丢弃`
    addLog('SYS', content)
    finishPendingRequest(false)
    if (pending.showModal) {
      showCommandAlert('通讯异常', content)
    }
    return
  }

  if (result.status === 'invalid') {
    const content = `${pending.label} 收到无效响应帧，已丢弃`
    addLog('SYS', content)
    finishPendingRequest(false)
    if (pending.showModal) {
      showCommandAlert('通讯异常', content)
    }
    return
  }

  if (result.status === 'exception') {
    const content = result.message || `${pending.label} 收到异常响应帧`
    addLog('SYS', content)
    finishPendingRequest(false)
    if (pending.showModal) {
      showCommandAlert('设备返回故障帧', content)
    }
    return
  }

  if (result.status === 'mismatch') {
    const content = `${pending.label} 收到不匹配响应，已丢弃`
    addLog('SYS', content)
    finishPendingRequest(false)
    if (pending.showModal) {
      showCommandAlert('通讯异常', content)
    }
    return
  }

  if (result.status !== 'complete') {
    const content = `${pending.label} 收到未知响应状态，已丢弃`
    addLog('SYS', content)
    finishPendingRequest(false)
    if (pending.showModal) {
      showCommandAlert('通讯异常', content)
    }
    return
  }

  finishPendingRequest(result.response)

  if (pending.responseBuffer.length) {
    consumePendingResponseBuffer()
  }
}

function handleReceivedResponseBytes(bytes) {
  if (!pendingRequest || !Array.isArray(bytes) || !bytes.length) return

  pendingRequest.responseBuffer = pendingRequest.responseBuffer.concat(bytes)
  const bufferLimit = pendingRequest.responseBufferLimit || MAX_RESPONSE_BUFFER_BYTES
  if (pendingRequest.responseBuffer.length > bufferLimit) {
    const pending = pendingRequest
    const content = `${pending.label} 返回数据超过缓冲区，已丢弃`

    addLog('SYS', content)
    finishPendingRequest(false)
    if (pending.showModal) {
      showCommandAlert('通讯异常', content)
    }
    return
  }

  consumePendingResponseBuffer()
}

function createPendingRequest(label, expected, options = {}) {
  return new Promise((resolve) => {
    const timer = setTimeout(() => {
      const pending = clearPendingRequest()
      if (!pending) return

      addLog('SYS', `${label} 超时`)
      if (options.showModal !== false) {
        showCommandAlert('通讯超时', `${label} 1秒内没有收到回复`)
      }
      resolve(false)
    }, options.timeout || RESPONSE_TIMEOUT)

    pendingRequest = {
      expected,
      label,
      resolve,
      timer,
      responseBufferLimit: getResponseBufferLimit(expected, options),
      responseReader: typeof options.responseReader === 'function' ? options.responseReader : null,
      showModal: options.showModal !== false,
      responseBuffer: []
    }
    setState({
      isAwaitingResponse: true
    })
  })
}

function init() {
  if (initialized) return

  wx.onBluetoothDeviceFound((res) => {
    mergeDevices(res.devices || [])
  })

  wx.onBluetoothAdapterStateChange((res) => {
    setState({
      adapterAvailable: !!res.available,
      isDiscovering: !!res.discovering
    })

    if (!res.available) {
      clearScanTimer()
      clearConnectedState({
        adapterAvailable: false,
        adapterOpened: false,
        errorText: '请开启手机蓝牙后重新扫描',
        isDiscovering: false,
        sendQueueLength: 0
      })
    }
  })

  wx.onBLEConnectionStateChange((res) => {
    const { connectedDevice } = state
    if (!connectedDevice || connectedDevice.deviceId !== res.deviceId) return

    if (!res.connected) {
      addLog('SYS', '连接已断开')
      clearConnectedState({
        errorText: '',
        sendQueueLength: 0
      })
    }
  })

  wx.onBLECharacteristicValueChange((res) => {
    const hex = arrayBufferToHex(res.value)
    const byteLength = res.value ? res.value.byteLength : 0
    const rawBytes = Array.prototype.slice.call(new Uint8Array(res.value || new ArrayBuffer(0)))
    const crcState = getReceiveCrcState(rawBytes)

    setState({
      rxCount: state.rxCount + byteLength
    })
    addLog('RX', hex, crcState, {
      payloadBytes: rawBytes,
      payloadText: bytesToUtf8Text(rawBytes)
    })
    rawResponseSubscribers.slice().forEach((subscriber) => {
      subscriber(rawBytes, res)
    })
    handleReceivedResponseBytes(rawBytes)
  })

  initialized = true
}

async function getAuthSetting() {
  return callWx('getSetting')
    .then((res) => res.authSetting || {})
    .catch(() => ({}))
}

function showPermissionModal(title, content) {
  return new Promise((resolve, reject) => {
    wx.showModal({
      title,
      content,
      confirmText: '去设置',
      success: async (res) => {
        if (!res.confirm) {
          reject(new Error('用户取消授权'))
          return
        }

        try {
          await callWx('openSetting')
          resolve()
        } catch (error) {
          reject(error)
        }
      },
      fail: reject
    })
  })
}

async function ensureBluetoothAuthorized() {
  const authSetting = await getAuthSetting()

  if (authSetting['scope.bluetooth']) return

  if (authSetting['scope.bluetooth'] === false) {
    await showPermissionModal('需要蓝牙权限', '请在设置中允许使用蓝牙，用于扫描并连接附近设备。')
    return
  }

  try {
    await callWx('authorize', {
      scope: 'scope.bluetooth'
    })
  } catch (error) {
    await showPermissionModal('需要蓝牙权限', '请在设置中允许使用蓝牙，用于扫描并连接附近设备。')
  }
}

async function ensureAndroidLocationAuthorized() {
  const systemInfo = wx.getSystemInfoSync ? wx.getSystemInfoSync() : wx.getDeviceInfo()
  if (systemInfo.platform !== 'android') return

  const authSetting = await getAuthSetting()
  if (authSetting['scope.userLocation']) return

  setState({
    systemTip: '安卓系统扫描 BLE 设备通常需要开启系统定位权限。'
  })

  if (authSetting['scope.userLocation'] === false) {
    await showPermissionModal('需要定位权限', '安卓系统要求定位权限开启后才能搜索附近 BLE 设备。')
    return
  }

  try {
    await callWx('authorize', {
      scope: 'scope.userLocation'
    })
  } catch (error) {
    await showPermissionModal('需要定位权限', '安卓系统要求定位权限开启后才能搜索附近 BLE 设备。')
  }
}

async function openAdapter() {
  if (state.adapterOpened) {
    try {
      const adapterState = await callWx('getBluetoothAdapterState')

      setState({
        adapterAvailable: !!adapterState.available,
        isDiscovering: !!adapterState.discovering
      })

      if (adapterState.available) return
    } catch (error) {
      setState({
        adapterAvailable: false,
        adapterOpened: false
      })
    }
  }

  try {
    await callWx('openBluetoothAdapter', {
      mode: 'central'
    })

    const adapterState = await callWx('getBluetoothAdapterState')

    setState({
      adapterAvailable: !!adapterState.available,
      adapterOpened: true,
      isDiscovering: !!adapterState.discovering
    })

    if (!adapterState.available) {
      throw {
        errCode: 10001,
        errMsg: 'bluetooth adapter not available'
      }
    }
  } catch (error) {
    if (error.errCode === 10001) {
      setState({
        adapterOpened: true,
        adapterAvailable: false
      })
    }

    throw error
  }
}

async function startDiscovery() {
  try {
    await callWx('startBluetoothDevicesDiscovery', {
      allowDuplicatesKey: true,
      interval: 600,
      powerLevel: 'high'
    })
  } catch (error) {
    await callWx('startBluetoothDevicesDiscovery', {
      allowDuplicatesKey: true,
      interval: 600
    })
  }
}

async function startScan() {
  if (state.isConnecting) return

  deviceRegistry.clear()
  setState({
    devices: [],
    errorText: ''
  })

  try {
    init()
    await ensureBluetoothAuthorized()
    await ensureAndroidLocationAuthorized()
    await openAdapter()
    await startDiscovery()

    setState({
      isDiscovering: true
    })

    resetScanTimer()
    addLog('SYS', '开始扫描 BLE 设备')
  } catch (error) {
    clearScanTimer()
    setState({
      isDiscovering: false,
      errorText: formatBluetoothError(error)
    })
  }
}

function clearDevices() {
  deviceRegistry.clear()
  setState({
    devices: [],
    errorText: ''
  })
}

async function closeConnectedDevice(nextDeviceId, options = {}) {
  const { connectedDevice } = state

  if (!connectedDevice) {
    resetSendRuntimeState()
    return
  }
  if (connectedDevice.deviceId === nextDeviceId && !options.force) return

  resetSendRuntimeState()

  try {
    await callWx('closeBLEConnection', {
      deviceId: connectedDevice.deviceId
    })
  } catch (error) {
    if (error.errCode !== 10006) throw error
  }

  clearConnectedState()
}

async function discoverCharacteristics(deviceId) {
  const serviceResult = await callWx('getBLEDeviceServices', {
    deviceId
  })
  const services = []
  let writeServiceId = ''
  let writeCharacteristicId = ''
  let writeType = ''
  let notifyServiceId = ''
  let notifyCharacteristicId = ''

  for (const service of serviceResult.services || []) {
    const characteristicResult = await callWx('getBLEDeviceCharacteristics', {
      deviceId,
      serviceId: service.uuid
    })
    const characteristics = (characteristicResult.characteristics || []).map((item) => ({
      uuid: item.uuid,
      role: getCharacteristicRole(item.properties),
      properties: item.properties || {}
    }))

    services.push({
      uuid: service.uuid,
      primary: service.isPrimary,
      characteristics
    })

    characteristics.forEach((item) => {
      const isPreferredService = isTargetUuid(service.uuid)
      const isPreferredCharacteristic = isTargetUuid(item.uuid)
      const canWrite = item.properties.write || item.properties.writeNoResponse
      const canNotify = item.properties.notify || item.properties.indicate

      if (isPreferredService && isPreferredCharacteristic && canWrite) {
        writeServiceId = service.uuid
        writeCharacteristicId = item.uuid
        writeType = item.properties.write ? 'write' : 'writeNoResponse'
      }

      if (isPreferredService && isPreferredCharacteristic && canNotify) {
        notifyServiceId = service.uuid
        notifyCharacteristicId = item.uuid
      }

      if (!writeCharacteristicId && canWrite) {
        writeServiceId = service.uuid
        writeCharacteristicId = item.uuid
        writeType = item.properties.write ? 'write' : 'writeNoResponse'
      }

      if (!notifyCharacteristicId && canNotify) {
        notifyServiceId = service.uuid
        notifyCharacteristicId = item.uuid
      }
    })
  }

  return {
    services,
    writeServiceId,
    writeCharacteristicId,
    writeType,
    notifyServiceId,
    notifyCharacteristicId
  }
}

async function enableNotify(deviceId, serviceId, characteristicId) {
  try {
    await callWx('notifyBLECharacteristicValueChange', {
      deviceId,
      serviceId,
      characteristicId,
      state: true
    })
    addLog('SYS', `已开启通知 ${characteristicId}`)
    return true
  } catch (error) {
    addLog('SYS', `开启通知失败：${formatBluetoothError(error)}`)
    if (isConnectionLostError(error)) {
      throw error
    }
    return false
  }
}

async function connectDeviceById(deviceId) {
  const device = deviceRegistry.getDevice(deviceId)

  if (!device || state.isConnecting) return

  resetSendRuntimeState()
  setState({
    connectingDeviceId: deviceId,
    errorText: '',
    isConnecting: true
  })

  try {
    await stopScan()
    await closeConnectedDevice(deviceId, {
      force: state.connectedDevice && state.connectedDevice.deviceId === deviceId
    })
    await openAdapter()
    await callWx('createBLEConnection', {
      deviceId,
      timeout: CONNECT_TIMEOUT
    })

    const discovery = await discoverCharacteristics(deviceId)
    const notifyEnabled = discovery.notifyServiceId && discovery.notifyCharacteristicId
      ? await enableNotify(deviceId, discovery.notifyServiceId, discovery.notifyCharacteristicId)
      : false
    const isTargetDevice = hasTargetCharacteristic(discovery)
    const connectedDevice = deviceRegistry.markConnectedDevice(deviceId, {
      isTargetDevice
    }) || {
      ...device,
      isTargetDevice,
      packetSize: device.packetSize || inferPacketSize(device),
      targetText: isTargetDevice ? '已发现目标特征' : device.targetText
    }

    setState({
      characteristicText: buildCharacteristicText(discovery.writeServiceId, discovery.writeCharacteristicId),
      connectedDevice,
      connectedServiceCount: discovery.services.length,
      connectingDeviceId: '',
      errorText: discovery.writeServiceId
        ? (notifyEnabled ? '' : '已连接，但未成功开启通知，可能收不到设备回复')
        : '已连接，但未找到可写特征值',
      isConnecting: false,
      writeCharacteristicId: discovery.writeCharacteristicId,
      writeServiceId: discovery.writeServiceId,
      writeType: discovery.writeType,
      devices: deviceRegistry.getDeviceList()
    })

    startRssiRefresh()
    addLog('SYS', `已连接 ${device.displayName}`)
  } catch (error) {
    resetSendRuntimeState()
    setState({
      connectingDeviceId: '',
      isConnecting: false,
      errorText: formatBluetoothError(error)
    })
  }
}

async function disconnectDevice() {
  const { connectedDevice } = state
  if (!connectedDevice) return

  try {
    await callWx('closeBLEConnection', {
      deviceId: connectedDevice.deviceId
    })
  } catch (error) {
    if (error.errCode !== 10006) {
      setState({
        errorText: formatBluetoothError(error)
      })
      return
    }
  }

  addLog('SYS', '主动断开连接')
  clearConnectedState({
    errorText: '',
    sendQueueLength: 0
  })
}

async function refreshNativeConnectionState() {
  if (!state.connectedDevice || typeof wx.getConnectedBluetoothDevices !== 'function') return true

  try {
    const services = state.writeServiceId ? [state.writeServiceId] : []
    const result = await callWx('getConnectedBluetoothDevices', {
      services
    })
    const isConnected = (result.devices || []).some((device) => device.deviceId === state.connectedDevice.deviceId)

    if (isConnected) return true

    addLog('SYS', '蓝牙连接状态已失效')
    clearConnectedState({
      errorText: '蓝牙连接已失效，请重新连接'
    })
    return false
  } catch (error) {
    if (isConnectionLostError(error)) {
      clearConnectedState({
        errorText: formatBluetoothError(error)
      })
      return false
    }

    return true
  }
}

function handleAppHide() {
  clearScanTimer()
  stopRssiRefresh()
  resetSendRuntimeState()
  if (state.isDiscovering) {
    stopScan()
  }
}

async function handleAppShow() {
  if (!state.connectedDevice) return

  init()
  const connected = await refreshNativeConnectionState()
  if (connected && state.connectedDevice) {
    startRssiRefresh()
  }
}

function setSendHex(sendHex) {
  setState({
    sendHex,
    errorText: ''
  })
}

function clearInput() {
  setState({
    sendHex: '',
    errorText: ''
  })
}

function clearLogs() {
  setState(createClearLogsState())
}

function enqueueSendFrame(hexFrame, source, options = {}) {
  if (!state.connectedDevice) {
    setState({
      errorText: '请先连接蓝牙透传设备'
    })
    return Promise.resolve(false)
  }

  if (!state.writeServiceId || !state.writeCharacteristicId) {
    setState({
      errorText: '当前设备没有可写特征值'
    })
    return Promise.resolve(false)
  }

  const errorText = validateHex(hexFrame)

  if (errorText) {
    setState({
      errorText
    })
    return Promise.resolve(false)
  }

  const buffer = hexToArrayBuffer(hexFrame)
  const bytes = new Uint8Array(buffer)
  const dmaFrameLengthError = options.skipDmaCheck ? '' : validateDmaFrameLength(bytes, options)

  if (dmaFrameLengthError) {
    setState({
      errorText: dmaFrameLengthError
    })
    return Promise.resolve(false)
  }

  return new Promise((resolve) => {
    sendJobSequence += 1
    sendQueue.push({
      id: sendJobSequence,
      hexFrame,
      options,
      resolve,
      source
    })

    setState({
      sendQueueLength: sendQueue.length
    })

    processSendQueue()
  })
}

async function processSendQueue() {
  if (isProcessingSendQueue) return

  const generation = sendQueueGeneration
  isProcessingSendQueue = true

  try {
    while (sendQueue.length && generation === sendQueueGeneration) {
      const job = sendQueue.shift()

      setState({
        sendQueueLength: sendQueue.length
      })

      let result = false
      try {
        result = await executeSendFrame(job.hexFrame, job.source, job.options)
      } catch (error) {
        cancelPendingRequest()
        setState({
          errorText: error.message || '发送失败'
        })
      }
      job.resolve(result)

      if (!state.connectedDevice) {
        clearSendQueue()
        break
      }
    }
  } finally {
    if (generation === sendQueueGeneration) {
      isProcessingSendQueue = false
    }
  }
}

async function executeSendFrame(hexFrame, source, options = {}) {
  const {
    connectedDevice,
    writeCharacteristicId,
    writeServiceId,
    writeType
  } = state
  const errorText = validateHex(hexFrame)

  if (!connectedDevice) {
    setState({
      errorText: '请先连接蓝牙透传设备'
    })
    return false
  }

  if (!writeServiceId || !writeCharacteristicId) {
    setState({
      errorText: '当前设备没有可写特征值'
    })
    return false
  }

  if (errorText) {
    setState({
      errorText
    })
    return false
  }

  const buffer = hexToArrayBuffer(hexFrame)
  const bytes = new Uint8Array(buffer)
  const dmaFrameLengthError = options.skipDmaCheck ? '' : validateDmaFrameLength(bytes, options)

  if (dmaFrameLengthError) {
    setState({
      errorText: dmaFrameLengthError
    })
    return false
  }

  const chunkSize = resolvePacketSize(
    options.chunkSize === undefined ? connectedDevice.packetSize : options.chunkSize,
    bytes.length
  )
  const waitResponse = !!options.expected
  const responsePromise = waitResponse
    ? createPendingRequest(source, options.expected, options)
    : null

  setState({
    isSending: true,
    errorText: ''
  })

  try {
    for (let offset = 0; offset < bytes.length; offset += chunkSize) {
      const chunk = bytes.slice(offset, offset + chunkSize)

      await callWx('writeBLECharacteristicValue', {
        deviceId: connectedDevice.deviceId,
        serviceId: writeServiceId,
        characteristicId: writeCharacteristicId,
        value: chunk.buffer,
        writeType
      })
    }

    setState({
      txCount: state.txCount + bytes.length
    })
    addLog('TX', arrayBufferToHex(buffer), source, {
      payloadBytes: Array.prototype.slice.call(bytes),
      payloadText: bytesToUtf8Text(bytes)
    })

    if (waitResponse) {
      return responsePromise
    }

    return true
  } catch (error) {
    if (waitResponse) {
      cancelPendingRequest()
    }
    if (isConnectionLostError(error)) {
      clearConnectedState({
        errorText: formatBluetoothError(error)
      })
    } else {
      setState({
        errorText: formatBluetoothError(error)
      })
    }
    return false
  } finally {
    setState({
      isSending: false
    })
  }
}

function sendManagedFrame(frameBytes, label, expected, options = {}) {
  return enqueueSendFrame(formatFrameHex(frameBytes), label, {
    expected: expected ? {
      ...expected,
      maxFrameBytes: options.maxFrameBytes === undefined ? DEFAULT_MAX_FRAME_BYTES : options.maxFrameBytes
    } : expected,
    responseBufferHint: options.responseBufferHint,
    responseReader: options.responseReader,
    showModal: options.showModal !== false,
    timeout: options.timeout || RESPONSE_TIMEOUT,
    maxFrameBytes: options.maxFrameBytes === undefined ? DEFAULT_MAX_FRAME_BYTES : options.maxFrameBytes
  })
}

function sendRawFrameExact(frameBytes, source) {
  const bytes = frameBytes instanceof Uint8Array
    ? frameBytes
    : new Uint8Array(frameBytes || [])

  return enqueueSendFrame(formatFrameHex(Array.prototype.slice.call(bytes)), source, {
    chunkSize: 0,
    skipDmaCheck: true
  })
}

function sendHexFrame() {
  const errorText = validateHex(state.sendHex)

  const parseSendExpected = protocolHelperRegistry.get('parseSendExpected')
  const expected = errorText || typeof parseSendExpected !== 'function'
    ? null
    : parseSendExpected(Array.prototype.slice.call(new Uint8Array(hexToArrayBuffer(state.sendHex))))

  return enqueueSendFrame(state.sendHex, 'HEX', expected ? {
    expected
  } : {})
}

module.exports = {
  clearDevices,
  clearInput,
  clearLogs,
  configureProtocolHelpers,
  connectDeviceById,
  disconnectDevice,
  enqueueSendFrame,
  getState,
  handleAppHide,
  handleAppShow,
  init,
  sendHexFrame,
  sendManagedFrame,
  sendRawFrameExact,
  setSendHex,
  showCommandAlert,
  startScan,
  stopScan,
  subscribe,
  subscribeRawResponse
}
