
const { buildCommand, buildReadVersion, COMMANDS } = require('../../../../utils/w13Packet.js')
const FIXED_CONNECT_CMD = new Uint8Array([0xCC, 0xC0, 0x0C, 0x00, 0x4F, 0xC0, 0x01, 0x00, 0x02, 0x00, 0x0C, 0x08])
Page({
  data: {
    TabCur: 1, // 1: 蓝牙调试 2: 蓝牙升级
    DevName: '',
    bleName: '',
    // 预设占位数据，未获取真实设备信息时用于展示
    bleMac: '00:00:00:00:00:00',
    bleAMC: '-',
    bleVersion: '-',
    openDelay: 20,
    bathDelay: 20,
    logs: [],
    hexSend: false,
    hexShow: false,
    withTimestamp: false,
    wrapCRLF: false,
    sendText: '',
    importFileName: '',
    // BLE上下文（从上一页传入）
    deviceId: '',
    serviceId: '',
    txCharId: '',
    rxCharId: '',
    logList: [],
    timeUnits: ['时', '分', '秒'],
    hourValues: Array.from({ length: 24 }, (_, i) => i + 1),
    msValues: Array.from({ length: 60 }, (_, i) => i + 1),
    ports: [
      { name: '无卡取电 CH1', portLabel: '开门磁', alias: '开门磁', deviceType: 0, deviceAddr: 0, loop: 1, thresholdUp: 0, thresholdDown: 0, enabled: false, detectTime: 0, detectUnit: 0 },
      { name: '无卡取电 CH2', portLabel: '门口红外', alias: '门口红外', deviceType: 0, deviceAddr: 0, loop: 2, thresholdUp: 0, thresholdDown: 0, enabled: false, detectTime: 0, detectUnit: 0 },
      { name: '无卡取电 CH3', portLabel: '床头红外', alias: '床头红外', deviceType: 0, deviceAddr: 0, loop: 3, thresholdUp: 0, thresholdDown: 0, enabled: false, detectTime: 0, detectUnit: 0 },
      { name: '无卡取电 CH4', portLabel: '卫浴红外', alias: '卫浴红外', deviceType: 0, deviceAddr: 0, loop: 4, thresholdUp: 0, thresholdDown: 0, enabled: false, detectTime: 0, detectUnit: 0 }
    ],
    // 条件“有无人标记”选项，参考截图：无人至有人/短暂离开/长时间离开/有人至无人
    tagOptions: ['无人至有人', '短暂离开', '长时间离开', '有人至无人'],
    stateOptions: ['不判断', '触发', '释放', '开启', '关闭'],
    // 默认条件：与截图一致（组1..6，各1条）
    conditions: [
      { group: 1, seq: 1, tag: 0, cardPower: 0, doorMag: 1, irHall: 0, bathRadar: 0, bathroomRadar: 0, judgeTime: 0, judgeUnit: 2, timeout: 0, timeoutUnit: 2 },
      { group: 2, seq: 1, tag: 0, cardPower: 0, doorMag: 0, irHall: 1, bathRadar: 0, bathroomRadar: 0, judgeTime: 0, judgeUnit: 2, timeout: 20, timeoutUnit: 2 },
      { group: 2, seq: 2, tag: 0, cardPower: 0, doorMag: 0, irHall: 1, bathRadar: 0, bathroomRadar: 0, judgeTime: 0, judgeUnit: 2, timeout: 20, timeoutUnit: 2 },
      { group: 2, seq: 3, tag: 0, cardPower: 0, doorMag: 0, irHall: 1, bathRadar: 0, bathroomRadar: 0, judgeTime: 0, judgeUnit: 2, timeout: 20, timeoutUnit: 2 },
      { group: 3, seq: 1, tag: 0, cardPower: 0, doorMag: 0, irHall: 1, bathRadar: 0, bathroomRadar: 0, judgeTime: 0, judgeUnit: 2, timeout: 0, timeoutUnit: 2 },
      { group: 4, seq: 1, tag: 1, cardPower: 0, doorMag: 4, irHall: 2, bathRadar: 2, bathroomRadar: 2, judgeTime: 0, judgeUnit: 2, timeout: 2, timeoutUnit: 2 },
      { group: 5, seq: 1, tag: 2, cardPower: 0, doorMag: 4, irHall: 2, bathRadar: 2, bathroomRadar: 2, judgeTime: 2, judgeUnit: 1, timeout: 10, timeoutUnit: 1 },
      { group: 6, seq: 1, tag: 3, cardPower: 0, doorMag: 4, irHall: 2, bathRadar: 2, bathroomRadar: 2, judgeTime: 2, judgeUnit: 1, timeout: 10, timeoutUnit: 1 }
    ],
    // 二级菜单：按组折叠
    condGroups: [],
    // 雷达指示灯（bit0=门磁，bit1=卫浴，bit2=卧室，bit3=走廊）
    radarLights: [
      { key: 'door', label: '门磁', colorClass: 'gray', triggered: false },
      { key: 'bath', label: '卫浴', colorClass: 'gray', triggered: false },
      { key: 'bed', label: '卧室', colorClass: 'gray', triggered: false },
      { key: 'hall', label: '走廊', colorClass: 'gray', triggered: false }
    ]
  },

  onLoad(options) {
    const raw = options && (options.DevName || options.name) || ''
    // 处理通过 URL 传递的编码，避免中文显示为乱码
    let devName = ''
    try {
      devName = decodeURIComponent(raw)
    } catch (e) {
      devName = raw
    }
    if (!devName) devName = 'B13设备'
    const rawMac = options && options.mac ? options.mac : ''
    const bleMac = rawMac ? decodeURIComponent(rawMac) : '00:00:00:00:00:00'
    const deviceId = rawMac ? decodeURIComponent(rawMac) : ''
    const serviceId = options && options.serviceId ? decodeURIComponent(options.serviceId) : ''
    const txCharId = options && options.txCharId ? decodeURIComponent(options.txCharId) : ''
    const rxCharId = options && options.rxCharId ? decodeURIComponent(options.rxCharId) : ''
    this.setData({ DevName: devName, bleName: devName, bleMac, deviceId, serviceId, txCharId, rxCharId })
     // 页面进入时打印当前蓝牙连接状态
    this.logBleStatus() 
    // 构建条件组
      this.buildCondGroups()
      // 自动发现特征并启动雷达订阅/读取
      this.ensureBleChannels(() => {
        this.startRadarStatusWatch()
      })
    wx.setNavigationBarTitle({ title: devName })
    this.logBleStatus() 
   
  },

  onShow() {
    // 页面显示时也打印一次，方便返回/二次进入场景
    this.logBleStatus()
  },

  onUnload() {
    this.teardownBleListener()
  },

  // 顶部标签切换
  tabSelect(e) {
    const id = Number(e.currentTarget.dataset.id || 1)
    this.setData({ TabCur: id })
  },

  onOpenDelayChange(e) {
    this.setData({ openDelay: e.detail.value })
  },
  onBathDelayChange(e) {
    this.setData({ bathDelay: e.detail.value })
  },

  // 便捷示例：读版本号
  onSendReadVersion() {
    try {
      const pkt = buildReadVersion()
      const text = this.data.hexShow ? this.toHex(pkt) : `[${Array.from(pkt).join(', ')}]`
      this.appendLog('TX', `读版本号: ${text}`)
    } catch (err) {
      wx.showToast({ title: '构包失败', icon: 'none' })
    }
  },

  // 开始OTA升级（命令0x0B, P0=0x01）
  onStartOta() {
    try {
      const pkt = buildCommand(COMMANDS.OTA_START, [0x01])
      const text = this.toHex(pkt)
      this.appendLog('TX', `OTA开始: ${text}`)
      wx.showToast({ title: '已发送OTA开始', icon: 'success' })
    } catch (err) {
      wx.showToast({ title: '构包失败', icon: 'none' })
    }
  },

  // 开启蓝牙打印（命令0x0C，示例掩码0x1F）
  onEnableBleLog() {
    try {
      const mask = 0x1F
      const pkt = buildCommand(COMMANDS.ENABLE_BLE_LOG, [mask])
      const text = this.toHex(pkt)
      this.appendLog('TX', `开启打印(0x${mask.toString(16).toUpperCase()}): ${text}`)
      wx.showToast({ title: '已发送打印开关', icon: 'success' })
    } catch (err) {
      wx.showToast({ title: '构包失败', icon: 'none' })
    }
  },

  toHex(u8) {
    return Array.from(u8).map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(' ')
  },

  // 控制台输出当前蓝牙连接状态
  logBleStatus() {
    const { deviceId, serviceId, txCharId, rxCharId } = this.data || {}
    console.info(`[BLE] B13page enter: device=${deviceId || '-'} svc=${serviceId || '-'} tx=${txCharId || '-'} rx=${rxCharId || '-'}`)
    const onUnknown = () => console.info('[BLE] connection state: unknown (no deviceId or API missing)')
    if (!deviceId) {
      onUnknown()
      return
    }
    const logConnected = (connected) => console.info(`[BLE] connection state: ${connected ? 'connected' : 'disconnected'}`)
    // 先尝试新版接口
    if (typeof wx.getBLEConnectionState === 'function') {
      try {
        wx.getBLEConnectionState({
          deviceId,
          success: (res) => logConnected(!!(res && res.connected)),
          fail: () => console.warn('[BLE] get connection state failed')
        })
      } catch (e) {
        console.warn('[BLE] get connection state exception')
      }
      return
    }
    // 兼容旧端：检查已连接设备列表
    if (typeof wx.getConnectedBluetoothDevices === 'function') {
      try {
        wx.getConnectedBluetoothDevices({
          success: (res) => {
            const list = (res && res.devices) || []
            const hit = list.some(d => (d.deviceId || '').toUpperCase() === deviceId.toUpperCase())
            logConnected(hit)
          },
          fail: () => console.warn('[BLE] getConnectedBluetoothDevices failed')
        })
      } catch (e) {
        console.warn('[BLE] getConnectedBluetoothDevices exception')
      }
      return
    }
    onUnknown()
  },

  // 发送前确认当前蓝牙连接状态（兼容无 getBLEConnectionState 场景）
  ensureBleConnected(next, attempt = 0) {
    const { deviceId } = this.data || {}
    if (!deviceId) {
      wx.showToast({ title: '未连接BLE', icon: 'none' })
      return
    }

    const proceed = () => { if (typeof next === 'function') next() }

    // 优先使用 getBLEConnectionState
    if (typeof wx.getBLEConnectionState === 'function') {
      try {
        wx.getBLEConnectionState({
          deviceId,
          success: (res) => {
            const connected = !!(res && res.connected)
            if (!connected) {
              // 某些机型首次查询可能短暂返回断开，允许一次快速重试
              if (attempt < 1) {
                this.appendLog('WARN', 'BLE未连接，重试查询...')
                setTimeout(() => this.ensureBleConnected(next, attempt + 1), 250)
                return
              }
              this.appendLog('WARN', 'BLE未连接，取消发送')
              wx.showToast({ title: '蓝牙未连接', icon: 'none' })
              return
            }
            proceed()
          },
          fail: () => {
            this.appendLog('WARN', '查询BLE状态失败，尝试兜底')
            this._fallbackCheckConnected(deviceId, proceed)
          }
        })
      } catch (e) {
        this.appendLog('WARN', '查询BLE状态异常，尝试兜底')
        this._fallbackCheckConnected(deviceId, proceed)
      }
      return
    }

    // 兼容旧端：直接兜底检查
    this._fallbackCheckConnected(deviceId, proceed)
  },

  // 兜底检查：使用已连接设备列表；若接口不可用则直接继续
  _fallbackCheckConnected(deviceId, proceed) {
    if (typeof wx.getConnectedBluetoothDevices !== 'function') {
      proceed()
      return
    }
    try {
      wx.getConnectedBluetoothDevices({
        success: (res) => {
          const list = (res && res.devices) || []
          const norm = (s) => (s || '').replace(/-/g, '').toUpperCase()
          const target = norm(deviceId)
          const hit = list.some(d => norm(d.deviceId) === target)
          if (hit) {
            proceed()
            return
          }
          // 若列表为空或匹配不到，但已有 service/char，放行并记录警告（部分机型/安卓可能返回空列表）
          if (list.length === 0 || (this.data.serviceId && this.data.txCharId)) {
            this.appendLog('WARN', '未在已连接列表中找到，假定已连接尝试发送')
            proceed()
            return
          }
          this.appendLog('WARN', '未在已连接设备列表中找到，取消发送')
          wx.showToast({ title: '蓝牙未连接', icon: 'none' })
        },
        fail: () => {
          this.appendLog('WARN', '查询已连接设备失败，尝试继续发送')
          proceed()
        }
      })
    } catch (e) {
      this.appendLog('WARN', '查询已连接设备异常，尝试继续发送')
      proceed()
    }
  },

  ab2hex(buffer) {
    if (!buffer) return ''
    return Array.from(new Uint8Array(buffer)).map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(' ')
  },

  _matchUuid(uuid, needle) {
    if (!uuid || !needle) return false
    const u = String(uuid).replace(/-/g, '').toUpperCase()
    const n = String(needle).toUpperCase().replace(/^0X/, '')
    return u.includes(n)
  },

  hexToBytes(hex) {
    const clean = (hex || '').replace(/\s+/g, '').toUpperCase()
    if (clean.length === 0) return new Uint8Array(0)
    if (clean.length % 2 !== 0 || /[^0-9A-F]/.test(clean)) return null
    const out = new Uint8Array(clean.length / 2)
    for (let i = 0; i < clean.length; i += 2) out[i/2] = parseInt(clean.substr(i,2), 16)
    return out
  },

  strToBytes(str) {
    const s = String(str || '')
    const out = new Uint8Array(s.length)
    for (let i = 0; i < s.length; i++) out[i] = s.charCodeAt(i) & 0xFF
    return out
  },

  writeBleBytes(u8, label) {
    const { deviceId, serviceId, txCharId } = this.data || {}
    if (!deviceId) {
      wx.showToast({ title: '未连接BLE', icon: 'none' })
      return
    }
    const doWrite = () => {
      // 若缺少特征/服务，先自动发现再发送
      if (!serviceId || !txCharId) {
        this.ensureBleChannels(() => this.writeBleBytes(u8, label))
        return
      }
   
      const bytes = (u8 instanceof Uint8Array) ? u8 : new Uint8Array(u8 || [])
      this.appendLog('CFG', `device=${deviceId || '-'} svc=${serviceId || '-'} tx=${txCharId || '-'}`)
      // 发送完成时打印当前蓝牙连接状态
      this.logBleStatus()
      wx.writeBLECharacteristicValue({
        deviceId,
        serviceId,
        characteristicId: txCharId,
        value: bytes.buffer,
        success: () => {
          // 发送完成时打印当前蓝牙连接状态
          this.logBleStatus()
        },
        fail: (err) => {
          const msg = (err && err.errMsg) ? err.errMsg : '发送失败'
          this.appendLog('WARN', `${label || '发送'} ${msg}`)
          wx.showToast({ title: '发送失败', icon: 'none' })
        }
      })
    }

    // 写入前检查当前蓝牙连接状态（兼容旧接口）
    this.ensureBleConnected(doWrite)
  },

  ensureBleChannels(done) {
    const { deviceId, serviceId, txCharId, rxCharId } = this.data || {}
    // 若未携带 deviceId，尝试从系统已连接设备列表中兜底获取
    let devId = deviceId
    if (!devId && typeof wx.getConnectedBluetoothDevices === 'function') {
      try {
        wx.getConnectedBluetoothDevices({
          success: (res) => {
            const list = (res && res.devices) || []
            if (list.length > 0) {
              const first = list[0]
              devId = first && (first.deviceId || first.deviceId)
              if (devId) this.setData({ deviceId: devId })
              // 继续后续流程
              this.ensureBleChannels(done)
            } else {
              wx.showToast({ title: '未发现已连接设备', icon: 'none' })
            }
          },
          fail: () => wx.showToast({ title: '获取连接设备失败', icon: 'none' })
        })
      } catch (e) {
        wx.showToast({ title: '蓝牙接口异常', icon: 'none' })
      }
      return
    }
    if (!devId) {
      wx.showToast({ title: '未连接BLE', icon: 'none' })
      return
    }
    if (serviceId && txCharId && rxCharId) {
      if (typeof done === 'function') done()
      return
    }
    if (typeof wx.getBLEDeviceServices !== 'function') {
      wx.showToast({ title: 'BLE接口不可用', icon: 'none' })
      return
    }
    wx.showLoading({ title: '发现服务...', mask: true })
    wx.getBLEDeviceServices({
      deviceId: devId,
      success: (srvRes) => {
        const services = srvRes.services || []
        let found = false
        let pending = services.length
        const score = (s) => {
          const u = (s.uuid || '').toUpperCase()
          return u.includes('FFE') ? 2 : (s.isPrimary === true ? 1 : 0)
        }
        const sorted = services.slice().sort((a, b) => score(b) - score(a))
        sorted.forEach(s => {
          wx.getBLEDeviceCharacteristics({
            deviceId: devId,
            serviceId: s.uuid,
            success: (chRes) => {
              const chars = chRes.characteristics || []
              const ffe1 = chars.find(c => this._matchUuid(c.uuid, 'FFE1'))
              const ffe2 = chars.find(c => this._matchUuid(c.uuid, 'FFE2'))
              if (!found && ffe1 && ffe2) {
                found = true
                this.setData({ serviceId: s.uuid, txCharId: ffe1.uuid, rxCharId: ffe2.uuid })
                // 立即开启通知
                this.enableNotify()
                wx.hideLoading()
                if (typeof done === 'function') done()
              }
            },
            complete: () => {
              pending -= 1
              if (!found && pending === 0) {
                wx.hideLoading()
                wx.showToast({ title: '未找到FFE1/FFE2', icon: 'none' })
              }
            }
          })
        })
      },
      fail: () => {
        wx.hideLoading()
        wx.showToast({ title: '获取服务失败', icon: 'none' })
      }
    })
  },

  startRadarStatusWatch() {
    // 开启订阅（若已传入rx特征），保证能接收数据
    this.enableNotify()   
    this.setupBleListener()
    this.sendRadarStatusCommand(true)
  },

  enableNotify() {
    const { deviceId, serviceId, rxCharId } = this.data || {}
    if (!deviceId || !serviceId || !rxCharId || typeof wx.notifyBLECharacteristicValueChange !== 'function') {
      this.appendLog('WARN', '通知前置条件不足')
      return
    }
    this.appendLog('CFG', `enableNotify device=${deviceId} svc=${serviceId} rx=${rxCharId}`)
    const tryEnable = (attempt) => {
      try {
        wx.notifyBLECharacteristicValueChange({
          state: true,
          deviceId,
          serviceId,
          characteristicId: rxCharId,
          success: () => this.appendLog('UI', `已开启通知 device=${deviceId} svc=${serviceId} rx=${rxCharId}`),
          fail: (err) => {
            const code = (err && (err.errCode ?? err.code))
            const msg = (err && (err.errMsg || err.message)) || '未知原因'
            const detail = code !== undefined ? `code=${code} ${msg}` : msg
            this.appendLog('WARN', `开启通知失败(重试${attempt}) ${detail}`)
            wx.showToast({ title: '通知失败', icon: 'none' })
            if (attempt < 2) {
              // 触发一次服务特征刷新后再重试
              this.ensureBleChannels(() => setTimeout(() => tryEnable(attempt + 1), 120))
            }
          }
        })
      } catch (e) {
        const msg = (e && (e.errMsg || e.message)) || '异常'
        this.appendLog('WARN', `开启通知异常 ${msg}`)
        wx.showToast({ title: '通知异常', icon: 'none' })
      }
    }
    tryEnable(0)
  },

  sendRadarStatusCommand(enable) {
    try {
      const payload = [enable ? 0x01 : 0x02]
      const pkt = buildCommand(COMMANDS.RADAR_STATUS, payload)
       this.transmitPacket(pkt, `雷达状态${enable ? '开启读取' : '关闭读取'}`)
    } catch (err) {
      wx.showToast({ title: '雷达命令构建失败', icon: 'none' })
    }
  },

  transmitPacket(pkt, label) {
    
    const hex = this.toHex(pkt)
    this.appendLog('TX', `${label}: ${hex}`)
    // 如果页面接入了BLE连接参数，则尝试写入；未配置则仅记录日志
    const { deviceId, serviceId, txCharId } = this.data || {}
    if (!deviceId || !serviceId || !txCharId || typeof wx.writeBLECharacteristicValue !== 'function') return
    try {
      wx.writeBLECharacteristicValue({
        deviceId,
        serviceId,
        characteristicId: txCharId,
        value: (pkt && pkt.buffer) ? pkt.buffer : new Uint8Array(pkt || []).buffer,
        fail: (err) => {
          const code = err && (err.errCode ?? err.code)
          const msg = (err && (err.errMsg || err.message)) || '未知原因'
          const detail = code !== undefined ? `code=${code} ${msg}` : msg
          this.appendLog('WARN', `写入BLE失败 device=${deviceId} svc=${serviceId} tx=${txCharId} ${detail}`)
          wx.showToast({ title: '发送失败', icon: 'none' })
        }
      })
    } catch (err) {
      const msg = (err && (err.errMsg || err.message)) || '异常'
      this.appendLog('WARN', `写入BLE异常 ${msg}`)
    }
  },

  /**
   * 注册 BLE 通知监听
   * - 先取消旧监听，避免重复回调造成日志噪音或重复解析
   * - 仅处理目标 rxCharId 的通知（按 UUID 片段过滤，兼容 16/128 位 UUID）
   * - 兼容两类上报载荷：ArrayBuffer 与十六进制字符串
   * - 将原始数据规范化为 Uint8Array，记录日志后交由协议解析器处理
   */
  setupBleListener() {
    // 移除旧监听，防止重复触发（页面重复进入或多次初始化的场景）
    this.teardownBleListener()
    // 运行环境不支持通知回调则直接返回（避免报错）
    if (typeof wx.onBLECharacteristicValueChange !== 'function') return
    // 定义并缓存通知回调，便于后续 off 解绑
    this._onBleChange = (res) => {
      const { rxCharId } = this.data || {}
      // 过滤：如果能拿到通知的特征 ID，仅处理与当前订阅 rxCharId 匹配的通知
      if (rxCharId && res && res.characteristicId) {
        const cid = String(res.characteristicId).replace(/-/g, '').toUpperCase()
        const rx = String(rxCharId).replace(/-/g, '').toUpperCase()
        if (!cid.includes(rx)) return
      }
      // 数据抽取：res 可能包含 value(ArrayBuffer) 或自定义的十六进制字符串字段
      const buffer = res && res.value
      const hexStr = res && (res.hexStr || res.hex || res.data)
      let hex = ''
      let u8 = null
      // ArrayBuffer → Uint8Array，并生成十六进制视图文本
      if (buffer) {
        u8 = new Uint8Array(buffer)
        hex = this.ab2hex(buffer)
      } else if (typeof hexStr === 'string') {
        // 规范化十六进制字符串：去空格、转大写，并按字节切分
        hex = hexStr.replace(/\s+/g, '').toUpperCase().replace(/(..)/g, '$1 ').trim()
        const clean = hex.replace(/\s+/g, '')
        const arr = []
        for (let i = 0; i < clean.length; i += 2) arr.push(parseInt(clean.substr(i, 2), 16) || 0)
        u8 = Uint8Array.from(arr)
      }
      // 若解析失败（没有有效数据），直接忽略此次通知
      if (!u8) return
      // 按显示偏好记录日志（HEX 或原始文本）
      const viewText = this.data.hexShow ? hex : `[${hex}]`
      this.appendLog('RX', viewText)
      // 将规范化数据交给协议解析器，进行业务处理与 UI 更新
      this.handleIncomingPacket(u8)
    }
    // 注册系统 BLE 通知回调
    wx.onBLECharacteristicValueChange(this._onBleChange)
  },

  teardownBleListener() {
    if (this._onBleChange && typeof wx.offBLECharacteristicValueChange === 'function') {
      wx.offBLECharacteristicValueChange(this._onBleChange)
    }
    this._onBleChange = null
  },

  handleIncomingPacket(u8) {
    if (!u8 || u8.length < 11) return
    const headOk = u8[0] === 0xCC && u8[1] === 0xC0
    const frameType = u8[10]
    if (!headOk) return
    if (frameType === (COMMANDS.RADAR_STATUS & 0xFF)) {
      const parsed = this.parseRadarStatus(u8)
      if (parsed) {
        this.updateRadarLights(parsed.bits)
        this.appendLog('PARSE', `雷达状态: 有效端口${parsed.portCount} 有人标记=${parsed.human === 0x01 ? '有人' : '无人'} 位=0b${parsed.bits.toString(2).padStart(8, '0')}`)
      }
    }
  },

  parseRadarStatus(u8) {
    // u8: Head Len CRC Frame FramNum Type Params...
    if (!u8 || u8.length < 13) return null
    const params = u8.slice(11)
    const portCount = params[0] || 0
    const human = params[1]
    const bits = params[2] || 0
    return { portCount, human, bits }
  },

  updateRadarLights(bits) {
    const next = (this.data.radarLights || []).map((it, idx) => {
      const triggered = ((bits >> idx) & 0x01) === 1
      return {
        ...it,
        triggered,
        colorClass: triggered ? 'red' : 'green'
      }
    })
    this.setData({ radarLights: next })
  },

  // 数值约束
  clamp(v, min, max) {
    v = Number(v || 0)
    if (isNaN(v)) return min
    if (v < min) return min
    if (v > max) return max
    return v
  },
  clampDetectByUnit(unit, v) {
    // unit: 0=时(1..24) 1=分(1..60) 2=秒(1..60)
    const max = unit === 0 ? 24 : 60
    return this.clamp(v, 1, max)
  },

  // === 条件组（二级菜单）逻辑 ===
  buildCondGroups() {
    const map = new Map()
    const list = (this.data.conditions || []).slice().sort((a, b) => (a.group - b.group) || (a.seq - b.seq))
    list.forEach(it => {
      if (!map.has(it.group)) {
        map.set(it.group, { group: it.group, timeout: it.timeout, timeoutUnit: it.timeoutUnit, expanded: false, items: [] })
      }
      map.get(it.group).items.push({ ...it, expanded: true })
    })
    this.setData({ condGroups: Array.from(map.values()) })
  },
  onToggleGroup(e) {
    const idx = Number(e.currentTarget.dataset.idx)
    const groups = this.data.condGroups.slice()
    if (groups[idx]) groups[idx].expanded = !groups[idx].expanded
    this.setData({ condGroups: groups })
  },
  onGroupNumberInput(e) {
    const idx = Number(e.currentTarget.dataset.idx)
    const field = e.currentTarget.dataset.field
    let val = Number(e.detail.value || 0)
    const groups = this.data.condGroups.slice()
    if (groups[idx]) {
      if (field === 'timeout') {
        const unit = Number(groups[idx].timeoutUnit || 0)
        val = this.clampDetectByUnit(unit, val || 1)
      }
      groups[idx][field] = val
    }
    this.setData({ condGroups: groups })
  },
  onGroupPickerChange(e) {
    const idx = Number(e.currentTarget.dataset.idx)
    const field = e.currentTarget.dataset.field
    const val = Number(e.detail.value || 0)
    const groups = this.data.condGroups.slice()
    if (groups[idx]) {
      if (field === 'timeout') {
        // 选择的是索引，真实值=索引+1
        groups[idx].timeout = (val + 1)
      } else {
        groups[idx][field] = val
      }
      if (field === 'timeoutUnit') {
        const t = Number(groups[idx].timeout || 0)
        groups[idx].timeout = this.clampDetectByUnit(val, t || 1)
      }
    }
    this.setData({ condGroups: groups })
  },
  onItemNumberInput(e) {
    const gidx = Number(e.currentTarget.dataset.gidx)
    const iidx = Number(e.currentTarget.dataset.iidx)
    const field = e.currentTarget.dataset.field
    // 序号为只读，不允许通过输入更新
    if (field === 'seq') return
    let val = Number(e.detail.value || 0)
    const groups = this.data.condGroups.slice()
    const grp = groups[gidx]
    if (grp && grp.items[iidx]) {
      if (field === 'judgeTime') {
        const unit = Number(grp.items[iidx].judgeUnit || 0)
        val = this.clampDetectByUnit(unit, val || 1)
      }
      grp.items[iidx][field] = val
    }
    this.setData({ condGroups: groups })
  },
  onItemPickerChange(e) {
    const gidx = Number(e.currentTarget.dataset.gidx)
    const iidx = Number(e.currentTarget.dataset.iidx)
    const field = e.currentTarget.dataset.field
    const val = Number(e.detail.value || 0)
    const groups = this.data.condGroups.slice()
    const grp = groups[gidx]
    if (grp && grp.items[iidx]) {
      if (field === 'judgeTime') {
        // 选择的是索引，真实值=索引+1
        grp.items[iidx].judgeTime = (val + 1)
      } else {
        grp.items[iidx][field] = val
      }
      if (field === 'judgeUnit') {
        const t = Number(grp.items[iidx].judgeTime || 0)
        grp.items[iidx].judgeTime = this.clampDetectByUnit(val, t || 1)
      }
    }
    this.setData({ condGroups: groups })
  },
  onToggleItem(e) {
    const gidx = Number(e.currentTarget.dataset.gidx)
    const iidx = Number(e.currentTarget.dataset.iidx)
    const groups = this.data.condGroups.slice()
    const grp = groups[gidx]
    if (grp && grp.items[iidx]) {
      grp.items[iidx].expanded = !grp.items[iidx].expanded
    }
    this.setData({ condGroups: groups })
  },

  // 端口配置交互
  onPortAliasInput(e) {
    const idx = Number(e.currentTarget.dataset.idx)
    const val = String(e.detail.value || '')
    const list = this.data.ports.slice()
    if (list[idx]) list[idx].alias = val
    this.setData({ ports: list })
  },
  onPortNumberInput(e) {
    const idx = Number(e.currentTarget.dataset.idx)
    const field = e.currentTarget.dataset.field
    let val = Number(e.detail.value || 0)
    const list = this.data.ports.slice()
    if (list[idx]) {
      if (field === 'loop') {
        val = this.clamp(val, 1, 12)
      } else if (field === 'thresholdUp' || field === 'thresholdDown') {
        val = this.clamp(val, 0, 100)
      } else if (field === 'detectTime') {
        const unit = Number(list[idx].detectUnit || 0)
        val = this.clampDetectByUnit(unit, val)
      }
      list[idx][field] = val
    }
    this.setData({ ports: list })
  },
  onPortSwitch(e) {
    const idx = Number(e.currentTarget.dataset.idx)
    const field = e.currentTarget.dataset.field
    const checked = !!e.detail.value
    const list = this.data.ports.slice()
    if (list[idx]) list[idx][field] = checked
    this.setData({ ports: list })
  },
  onPortUnitChange(e) {
    const idx = Number(e.currentTarget.dataset.idx)
    const field = e.currentTarget.dataset.field
    const val = Number(e.detail.value || 0)
    const list = this.data.ports.slice()
    if (list[idx]) {
      list[idx][field] = val
      // 切换单位时同时校正检测时间范围
      const dt = Number(list[idx].detectTime || 0)
      list[idx].detectTime = this.clampDetectByUnit(val, dt || 1)
    }
    this.setData({ ports: list })
  },
  onSavePorts() {
    this.data.ports.forEach((p, i) => {
      const P0 = p.deviceType & 0xFF
      const P1 = p.deviceAddr & 0xFF
      const loopLE = [p.loop & 0xFF, (p.loop >>> 8) & 0xFF]
      const P4 = p.thresholdDown & 0xFF
      const P5 = (i + 1) & 0xFF
      const P6 = p.enabled ? 0x01 : 0x00
      const dtLE = [p.detectTime & 0xFF, (p.detectTime >>> 8) & 0xFF]
      const P9 = p.detectUnit & 0xFF
      const P10 = p.thresholdUp & 0xFF
      const payload = [P0, P1, ...loopLE, P4, P5, P6, ...dtLE, P9, P10]
      const pkt = buildCommand(COMMANDS.SET_CONDITION_2, payload, { frame: i + 1, framNum: this.data.ports.length })
      this.appendLog('TX', `端口配置[${p.name}]: ${this.toHex(pkt)}`)
    })
    wx.showToast({ title: '端口配置已发送', icon: 'success' })
  },

  // 读取端口配置（占位示例，后续可接入真实读取命令）
  onReadPorts() {
    this.appendLog('UI', '请求读取端口配置')
    wx.showToast({ title: '已请求读取端口配置', icon: 'none' })
  },

  // 条件配置交互
  onCondNumberInput(e) {
    const idx = Number(e.currentTarget.dataset.idx)
    const field = e.currentTarget.dataset.field
    const val = Number(e.detail.value || 0)
    const list = this.data.conditions.slice()
    if (list[idx]) list[idx][field] = val
    this.setData({ conditions: list })
  },
  onCondPickerChange(e) {
    const idx = Number(e.currentTarget.dataset.idx)
    const field = e.currentTarget.dataset.field
    const val = Number(e.detail.value || 0)
    const list = this.data.conditions.slice()
    if (list[idx]) list[idx][field] = val
    this.setData({ conditions: list })
  },
  onSaveConditions() {
    // 将二级菜单分组扁平化回 conditions
    const flat = []
    (this.data.condGroups || []).forEach(grp => {
      (grp.items || []).forEach(it => {
        flat.push({ ...it, group: grp.group, timeout: grp.timeout, timeoutUnit: grp.timeoutUnit })
      })
    })
    this.setData({ conditions: flat })
    flat.forEach((c, i) => {
      const P0 = c.tag & 0xFF
      const P1 = c.group & 0xFF
      const P2 = c.seq & 0xFF
      const jtLE = [c.judgeTime & 0xFF, (c.judgeTime >>> 8) & 0xFF]
      const P5 = c.judgeUnit & 0xFF
      const bit = (v) => (v === 1 ? 1 : 0)
      const b0 = bit(c.doorMag)
      const b1 = bit(c.irHall)
      const b2 = bit(c.bathRadar)
      const b3 = bit(c.bathroomRadar)
      const P6 = (b0 | (b1 << 1) | (b2 << 2) | (b3 << 3)) & 0xFF
      const P7 = 0x00
      const P8 = 0x00
      const P9 = 0x00
      const P10 = 0x00
      const toLE = [c.timeout & 0xFF, (c.timeout >>> 8) & 0xFF]
      const P13 = c.timeoutUnit & 0xFF
      const payload = [P0, P1, P2, ...jtLE, P5, P6, P7, P8, P9, P10, ...toLE, P13]
      const pkt = buildCommand(COMMANDS.SET_CONDITION_1, payload, { frame: i + 1, framNum: flat.length })
      this.appendLog('TX', `条件配置[组${c.group}/序${c.seq}]: ${this.toHex(pkt)}`)
    })
    wx.showToast({ title: '条件配置已发送', icon: 'success' })
  },

  // 功能栏示例事件
  onDeleteCondGroup() { this.appendLog('UI', '操作: 删除条件组'); wx.showToast({ title: '删除条件组', icon: 'none' }) },
  onDeleteCondition() { this.appendLog('UI', '操作: 删除条件'); wx.showToast({ title: '删除条件', icon: 'none' }) },
  onAddCondGroup() { this.appendLog('UI', '操作: 添加条件组'); wx.showToast({ title: '添加条件组', icon: 'none' }) },
  onAddCondition() { this.appendLog('UI', '操作: 添加条件'); wx.showToast({ title: '添加条件', icon: 'none' }) },
  onExport() { this.appendLog('UI', '操作: 导出'); wx.showToast({ title: '导出', icon: 'none' }) },

  onImport() {
    const now = new Date()
    const stamp = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`
    this.setData({ importFileName: `已导入 ${stamp}` })
    this.appendLog('UI', '操作: 导入')
    wx.showToast({ title: '导入', icon: 'none' })
  },

  // 一键下发：同时下发端口配置与条件配置
  onOneKeySend() {
    try {
      // 先发送端口配置
      this.onSavePorts()
      // 再发送条件配置
      this.onSaveConditions()
      wx.showToast({ title: '已一键下发', icon: 'success' })
      this.appendLog('UI', '操作: 一键下发')
    } catch (err) {
      wx.showToast({ title: '下发失败', icon: 'none' })
    }
  },

  onCheckboxChange(e) {
    const key = e.currentTarget.dataset.key
    if (!key) return
    const checked = (e.detail.value || []).includes(key)
    this.setData({ [key]: checked })
  },

  onInputChange(e) {
    this.setData({ sendText: e.detail.value })
  },

  /**
   * 发送蓝牙数据
   * 
   * 处理用户输入并发送到蓝牙设备：
   * 1. 检查输入内容是否为空
   * 2. 根据HEX发送模式转换数据格式
   * 3. 记录发送日志
   * 4. 通过蓝牙发送数据
   * 
   * @function onSend
   * @memberof B13page
   * @description 发送数据到已连接的蓝牙设备
   */
  onSend() {

    

    const content = this.data.sendText.trim()
    if (!content) {
      wx.showToast({ title: '请输入内容', icon: 'none' })
      return
    }

    let viewText = content
    let bytes = null
    if (this.data.hexSend) {
      const u8 = this.hexToBytes(content)
      if (!u8) {
        wx.showToast({ title: 'HEX格式错误', icon: 'none' })
        return
      }
      bytes = u8
      viewText = this.toHex(u8)
    } else {
      const text = this.data.wrapCRLF ? (content + '\r\n') : content
      bytes = this.strToBytes(text)
      viewText = text
    }

    // 记录日志（按显示偏好）
    const show = this.data.hexShow && bytes ? this.toHex(bytes) : viewText
    this.appendLog('TX', show)
    this.writeBleBytes(bytes, '发送')
    
    this.setData({ sendText: '' })
  },

  

  onClearLogs() {
    this.setData({ logList: [] })
  },

  /**
   * 追加一条日志到页面列表
   * - 可选带时间戳前缀（withTimestamp）
   * - 头插方式存储，最新在前
   */
  appendLog(direction, text) {
    const now = new Date()
    const timeStr = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`
    const finalText = this.data.withTimestamp ? `[${timeStr}] ${direction}: ${text}` : `${direction}: ${text}`
    const id = `${Date.now()}-${Math.random().toString(16).slice(2)}`
    const next = [{ id, time: timeStr, text: finalText }, ...this.data.logList]
    this.setData({ logList: next })
  }
})
