const {
  controlService,
  createGenericGroupConfig,
  createGenericGroupDialogState,
  createGenericModbusDialogState,
  createGenericRegisterChangedData,
  createGenericRegisterDialogState,
  findGenericGroup,
  findGenericRegister,
  getActiveGenericGroup,
  getCombinedGroupKeys,
  getCombinedGroupLabel,
  getControlViewState,
  getGenericDialogDataTypeState,
  getGenericOption,
  getGroupLabel,
  getPageState,
  getSettingsPageState,
  getVisiblePageState,
  hasWritableGroupChanges,
  paramsPageState,
  paramsService,
  resolveActiveParamView,
  syncService
} = require('../../features/motor-control/index.js')
const {
  createGenericModbusPoller,
  genericModbusService
} = require('../../features/generic-modbus/index.js')
const settingsService = require('../../store/settings-store.js')
const themeService = require('../../store/theme-store.js')
const {
  createPageToast
} = require('../../utils/page-toast.js')

const GENERIC_REGISTER_DRAG_THRESHOLD_PX = 12
const GENERIC_REGISTER_ROW_HEIGHT_RPX = 112

function clampIndex(value, min, max) {
  return Math.min(Math.max(value, min), max)
}

function getWindowWidth() {
  try {
    if (typeof wx !== 'undefined' && wx && typeof wx.getWindowInfo === 'function') {
      const info = wx.getWindowInfo()
      if (info && Number.isFinite(info.windowWidth)) return info.windowWidth
    }
  } catch (error) {}

  try {
    if (typeof wx !== 'undefined' && wx && typeof wx.getSystemInfoSync === 'function') {
      const info = wx.getSystemInfoSync()
      if (info && Number.isFinite(info.windowWidth)) return info.windowWidth
    }
  } catch (error) {}

  return 375
}

function rpxToPx(rpx, windowWidth) {
  return Math.round(Number(rpx || 0) * Number(windowWidth || 375) / 750)
}

function getFallbackDragRowOffsetPx(windowWidth) {
  return Math.max(44, rpxToPx(GENERIC_REGISTER_ROW_HEIGHT_RPX, windowWidth))
}

function resolveDragTargetIndex(drag, currentY, totalCount) {
  if (!drag || !Number.isInteger(totalCount) || totalCount <= 0) return 0

  const sourceIndex = clampIndex(Number(drag.index) || 0, 0, totalCount - 1)
  const rowCenters = Array.isArray(drag.rowCenters) ? drag.rowCenters : []
  const sourceCenter = Number(rowCenters[sourceIndex])

  if (rowCenters.length === totalCount && Number.isFinite(sourceCenter)) {
    const currentCenter = sourceCenter + (Number(currentY) - Number(drag.startY || currentY))
    let targetIndex = sourceIndex

    if (currentCenter >= sourceCenter) {
      for (let index = sourceIndex + 1; index < totalCount; index += 1) {
        if (currentCenter > Number(rowCenters[index])) targetIndex = index
      }
    } else {
      for (let index = sourceIndex - 1; index >= 0; index -= 1) {
        if (currentCenter < Number(rowCenters[index])) targetIndex = index
      }
    }

    return clampIndex(targetIndex, 0, totalCount - 1)
  }

  const rowOffset = Math.max(1, Number(drag.rowOffset) || 1)
  const step = Math.round((Number(currentY) - Number(drag.startY || currentY)) / rowOffset)

  return clampIndex(sourceIndex + step, 0, totalCount - 1)
}

function buildActiveGenericRegisterRows(group, dragState) {
  if (!group || !Array.isArray(group.registers)) return []

  const drag = dragState && dragState.groupId === group.id ? dragState : null
  const activeIndex = drag ? clampIndex(Number(drag.index) || 0, 0, group.registers.length - 1) : -1
  const targetIndex = drag ? clampIndex(Number(drag.targetIndex) || activeIndex, 0, group.registers.length - 1) : -1
  const rowOffset = drag ? Math.max(1, Math.round(Number(drag.rowOffset) || 0)) : 0
  const translateY = drag ? Math.round(Number(drag.translateY) || 0) : 0

  return group.registers.map((register, index) => {
    const row = {
      ...register,
      sourceIndex: index,
      dragClass: '',
      dragHandleClass: '',
      dragStyle: ''
    }

    if (!drag) return row

    const isActive = index === activeIndex
    let shiftY = 0

    if (drag.moved && rowOffset) {
      if (activeIndex < targetIndex && index > activeIndex && index <= targetIndex) {
        shiftY = -rowOffset
      } else if (activeIndex > targetIndex && index >= targetIndex && index < activeIndex) {
        shiftY = rowOffset
      }
    }

    if (isActive) {
      row.dragClass = drag.moved ? 'is-dragging' : 'is-drag-armed'
      row.dragHandleClass = drag.moved ? 'is-dragging' : 'is-drag-armed'
      row.dragStyle = drag.moved
        ? `transform: translate3d(0, ${translateY}px, 0) scale(1.02); z-index: 8;`
        : 'z-index: 3;'
      return row
    }

    if (shiftY) {
      row.dragClass = shiftY > 0 ? 'is-shift-down' : 'is-shift-up'
      row.dragStyle = `transform: translate3d(0, ${shiftY}px, 0);`
    }

    return row
  })
}

Page({
  data: {
    ...getPageState(),
    activeParamView: '',
    activeGenericGroupId: '',
    activeGenericRegisterRows: [],
    genericModbusDialog: createGenericModbusDialogState()
  },

  onTabItemTap() {
    this.backToParamsHome()
  },

  onLoad() {
    this.pageToast = createPageToast(this, this.data)
    this.genericModbusPoller = createGenericModbusPoller(() => this.data)
    this.genericModbusTouchStarts = {}
    this.genericWindowWidth = getWindowWidth()
    controlService.init()
    genericModbusService.init()
    themeService.init()
    settingsService.init()
    this.unsubscribeSync = syncService.subscribe((syncState) => {
      if (!syncState.syncVersion || syncState.syncVersion === this.data.syncVersion) return

      const nextState = getPageState(
        syncService.getParamsSnapshot(),
        controlService.getState()
      )

      this.setData(nextState)
      this.pageToast.showFromState(nextState)
    })
    this.unsubscribeControl = controlService.subscribe((controlState) => {
      const nextState = getControlViewState(controlState)

      this.setData(nextState)
      this.pageToast.showFromState(nextState)
      if (nextState.connectedDevice) {
        this.scheduleVisibleGenericAutoReads()
      } else {
        this.clearGenericAutoTimers()
      }
    })
    this.unsubscribeTheme = themeService.subscribe((themeState) => {
      this.setData(themeState)
    })
    this.unsubscribeGenericModbus = genericModbusService.subscribe((genericState) => {
      const activeGenericGroup = getActiveGenericGroup(genericState.genericModbusGroups, this.data.activeGenericGroupId)
      this.setData({
        ...genericState,
        activeGenericGroup,
        activeGenericRegisterRows: buildActiveGenericRegisterRows(activeGenericGroup, this.genericModbusRegisterDrag),
        activeParamView: this.data.activeParamView === 'genericModbusGroup' && !activeGenericGroup
          ? 'genericModbus'
          : this.data.activeParamView
      })
    })
    this.unsubscribeSettings = settingsService.subscribe((settingsState) => {
      const nextState = getSettingsPageState(this.data, settingsState)
      const activeParamView = nextState.activeParamView

      const activeGenericGroup = getActiveGenericGroup(this.data.genericModbusGroups, this.data.activeGenericGroupId)
      const safeActiveView = activeParamView === 'genericModbusGroup' && !activeGenericGroup
        ? 'genericModbus'
        : activeParamView
      this.setData({
        ...nextState,
        activeParamView: safeActiveView,
        activeGenericGroup,
        activeGenericRegisterRows: buildActiveGenericRegisterRows(activeGenericGroup, this.genericModbusRegisterDrag)
      })
      if (safeActiveView === 'genericModbus' || safeActiveView === 'genericModbusGroup') {
        setTimeout(() => this.scheduleVisibleGenericAutoReads(), 0)
      } else {
        this.clearGenericAutoTimers()
      }
    })
  },

  onShow() {
    if (this.pageToast) {
      this.pageToast.setActive(true)
    }

    controlService.syncSharedInputs()

    const pageState = getVisiblePageState(this.data)
    this.setData({
      ...pageState,
      activeGenericGroup: getActiveGenericGroup(pageState.genericModbusGroups, this.data.activeGenericGroupId),
      activeGenericRegisterRows: buildActiveGenericRegisterRows(
        getActiveGenericGroup(pageState.genericModbusGroups, this.data.activeGenericGroupId),
        this.genericModbusRegisterDrag
      )
    })
    this.pageToast.showFromState(pageState)
    this.scheduleVisibleGenericAutoReads()
  },

  onHide() {
    if (this.pageToast) {
      this.pageToast.setActive(false)
    }
    this.clearGenericRegisterDrag()
    this.clearGenericAutoTimers()
  },

  onUnload() {
    if (this.pageToast) {
      this.pageToast.destroy()
      this.pageToast = null
    }

    if (this.unsubscribeSync) {
      this.unsubscribeSync()
      this.unsubscribeSync = null
    }

    if (this.unsubscribeControl) {
      this.unsubscribeControl()
      this.unsubscribeControl = null
    }

    if (this.unsubscribeTheme) {
      this.unsubscribeTheme()
      this.unsubscribeTheme = null
    }

    if (this.unsubscribeGenericModbus) {
      this.unsubscribeGenericModbus()
      this.unsubscribeGenericModbus = null
    }

    if (this.unsubscribeSettings) {
      this.unsubscribeSettings()
      this.unsubscribeSettings = null
    }

    this.clearGenericAutoTimers()
  },

  async onGroupRead(event) {
    if (!this.data.connectedDevice) return

    const groupKey = event.currentTarget.dataset.group
    const nextState = await paramsService.readGroup(this.data, groupKey)

    if (nextState) {
      this.setData(nextState)
      if (this.pageToast) this.pageToast.show(`${getGroupLabel(groupKey)}读取完成`)
    }
  },

  async onGroupWrite(event) {
    if (!this.data.connectedDevice) return

    const groupKey = event.currentTarget.dataset.group
    const written = await paramsService.writeGroup(this.data, groupKey)

    if (written) {
      this.setData(paramsPageState.clearGroupDirty(this.data, groupKey))
      if (this.pageToast) this.pageToast.show(`${getGroupLabel(groupKey)}写入完成`)
    }
  },

  async readCombinedGroups(viewKey) {
    if (!this.data.connectedDevice) return false

    const groupKeys = getCombinedGroupKeys(viewKey)
    let nextState = this.data

    for (const groupKey of groupKeys) {
      const updatedState = await paramsService.readGroup(nextState, groupKey)
      if (!updatedState) {
        if (nextState !== this.data) this.setData(nextState)
        return false
      }

      nextState = updatedState
      this.setData(nextState)
    }

    if (this.pageToast) this.pageToast.show(`${getCombinedGroupLabel(viewKey)}读取完成`)

    return true
  },

  async writeCombinedGroups(viewKey) {
    if (!this.data.connectedDevice) return false

    const groupKeys = getCombinedGroupKeys(viewKey)
    let nextState = this.data
    let writtenAny = false

    for (const groupKey of groupKeys) {
      if (!hasWritableGroupChanges(nextState, groupKey)) continue

      const written = await paramsService.writeGroup(nextState, groupKey)
      if (!written) {
        if (writtenAny) this.setData(nextState)
        return false
      }

      nextState = paramsPageState.clearGroupDirty(nextState, groupKey)
      writtenAny = true
      this.setData(nextState)
    }

    if (!writtenAny) {
      if (this.pageToast) this.pageToast.show('暂无需要写入的参数')
      return false
    }

    if (this.pageToast) this.pageToast.show(`${getCombinedGroupLabel(viewKey)}写入完成`)

    return true
  },

  readStartupManagement() {
    this.readCombinedGroups('startup')
  },

  writeStartupManagement() {
    this.writeCombinedGroups('startup')
  },

  readSpeedManagement() {
    this.readCombinedGroups('speed')
  },

  writeSpeedManagement() {
    this.writeCombinedGroups('speed')
  },

  onEstimatorUpdate() {
    this.setData(paramsPageState.refreshState(this.data))
    if (this.pageToast) this.pageToast.show('估算器参数更新完成')
  },

  openParamView(event) {
    if (this.pageToast) this.pageToast.clear()
    this.closeGenericModbusDraft()
    this.clearGenericRegisterDrag()

    const activeParamView = event.currentTarget.dataset.view
    if (!activeParamView) return

    this.setData({
      activeParamView
    })
    if (activeParamView === 'genericModbus') {
      setTimeout(() => this.scheduleVisibleGenericAutoReads(), 0)
    }
  },

  openGenericModbusGroup(event) {
    const groupId = event.currentTarget.dataset.groupId
    const group = findGenericGroup(this.data.genericModbusGroups, groupId)
    if (!group) return

    if (this.pageToast) this.pageToast.clear()
    this.closeGenericModbusDraft()
    this.setData({
      activeGenericGroup: group,
      activeGenericGroupId: groupId,
      activeParamView: 'genericModbusGroup',
      activeGenericRegisterRows: buildActiveGenericRegisterRows(group, this.genericModbusRegisterDrag)
    })
  },

  backToParamsHome() {
    if (this.pageToast) this.pageToast.clear()
    this.closeGenericModbusDraft()
    this.clearGenericRegisterDrag()
    this.clearGenericAutoTimers()

    const activeParamView = resolveActiveParamView('', this.data)
    this.setData({
      activeGenericGroup: null,
      activeGenericGroupId: '',
      activeParamView,
      activeGenericRegisterRows: []
    })
    if (activeParamView === 'genericModbus') {
      setTimeout(() => this.scheduleVisibleGenericAutoReads(), 0)
    }
  },

  onMotorParameterInput(event) {
    controlService.updateMotorParameterInput(
      Number(event.currentTarget.dataset.index),
      event.detail.value
    )
  },

  onMotorParameterBlur(event) {
    controlService.updateMotorParameterBlur(
      Number(event.currentTarget.dataset.index),
      event.detail.value
    )
  },

  writeMotorParameters() {
    if (!this.data.connectedDevice) return

    controlService.writeMotorParameters()
  },

  async readDriverPageParameters() {
    if (!this.data.connectedDevice) return

    await controlService.readDriverParameters()
    await controlService.readMotorParameters()
  },

  readStatus() {
    if (!this.data.canReadStatus) return

    controlService.readStatus()
  },

  onInputChange(event) {
    this.setData(paramsPageState.applyParameterInput(
      this.data,
      Number(event.currentTarget.dataset.index),
      event.detail.value
    ))
  },

  onAtoBandwidthInput(event) {
    this.setData(paramsPageState.applyAtoBandwidthInput(
      this.data,
      Number(event.currentTarget.dataset.index),
      event.detail.value
    ))
  },

  onDqGainInput(event) {
    this.setData(paramsPageState.applyDqGainInput(
      this.data,
      Number(event.currentTarget.dataset.index),
      event.detail.value
    ))
  },

  onSpeedLoopExtraInput(event) {
    this.setData(paramsPageState.applySpeedLoopExtraInput(
      this.data,
      Number(event.currentTarget.dataset.index),
      event.detail.value
    ))
  },

  onOilParameterInput(event) {
    this.setData(paramsPageState.applyOilParameterInput(
      this.data,
      Number(event.currentTarget.dataset.index),
      event.detail.value
    ))
  },

  onPrepositionParameterInput(event) {
    this.setData(paramsPageState.applyPrepositionParameterInput(
      this.data,
      Number(event.currentTarget.dataset.index),
      event.detail.value
    ))
  },

  onInputBlur(event) {
    this.setData(paramsPageState.applyInputBlur(
      this.data,
      event.currentTarget.dataset.inputGroup,
      Number(event.currentTarget.dataset.index),
      event.detail.value
    ))
  },

  onTailwindSwitchChange(event) {
    if (!this.data.connectedDevice) return

    const index = Number(event.currentTarget.dataset.index)
    const nextState = paramsPageState.applyTailwindSwitchChange(
      this.data,
      index,
      !!event.detail.value
    )

    this.setData(nextState)
    paramsService.writeSwitchRegister(nextState.tailwindSwitchRegisters[index]).then((written) => {
      if (written) {
        this.setData(paramsPageState.clearTailwindSwitchDirty(this.data, index))
        if (this.pageToast) this.pageToast.show(`${nextState.tailwindSwitchRegisters[index].name}写入完成`)
      }
    })
  },

  onProtectionSwitchChange(event) {
    if (!this.data.connectedDevice) return

    const index = Number(event.currentTarget.dataset.index)
    const nextState = paramsPageState.applyProtectionSwitchChange(
      this.data,
      index,
      !!event.detail.value
    )

    this.setData(nextState)
    paramsService.writeSwitchRegister(nextState.protectionSwitchRegisters[index]).then((written) => {
      if (written) {
        this.setData(paramsPageState.clearProtectionSwitchDirty(this.data, index))
        if (this.pageToast) this.pageToast.show(`${nextState.protectionSwitchRegisters[index].name}写入完成`)
      }
    })
  },

  onProtectionInputChange(event) {
    this.setData(paramsPageState.applyProtectionInput(
      this.data,
      Number(event.currentTarget.dataset.index),
      event.detail.value
    ))
  },

  noop() {},

  updateGenericModbusDialog(changedData) {
    this.setData({
      genericModbusDialog: {
        ...this.data.genericModbusDialog,
        ...changedData
      }
    })
  },

  openGenericModbusDraft(event) {
    const groupId = event && event.currentTarget && event.currentTarget.dataset
      ? event.currentTarget.dataset.groupId
      : ''
    const group = groupId ? findGenericGroup(this.data.genericModbusGroups, groupId) : null

    this.updateGenericModbusDialog(createGenericGroupDialogState(group))
  },

  closeGenericModbusDraft() {
    this.genericModbusGroupLongPressGuard = ''
    this.genericModbusRegisterLongPressGuard = ''
    this.updateGenericModbusDialog(createGenericModbusDialogState())
  },

  onGenericDraftInput(event) {
    const field = event.currentTarget.dataset.field
    if (!field) return

    const value = event.detail.value
    this.updateGenericModbusDialog({
      [field]: value,
      ...(field === 'structDefinition' ? {
        parsedStructRegisters: [],
        structParsedSummary: ''
      } : {})
    })
  },

  parseGenericStructDefinition() {
    const dialog = this.data.genericModbusDialog || createGenericModbusDialogState()
    const sourceText = dialog.structDefinition || ''
    if (!sourceText.trim()) {
      if (this.pageToast) this.pageToast.show('请先粘贴结构体定义', 'error')
      return
    }

    const registerType = getGenericOption(this.data.genericModbusRegisterTypeOptions, dialog.registerTypeIndex)
    if (registerType.key === 'coil' || registerType.key === 'discrete') {
      if (this.pageToast) this.pageToast.show('结构体解析仅支持寄存器类型', 'error')
      return
    }

    try {
      const parsed = genericModbusService.parseStructDefinition(sourceText)
      const inputRegisterIndex = Math.max(
        0,
        this.data.genericModbusRegisterTypeOptions.findIndex((item) => item.key === 'input')
      )
      const inputRegisterType = getGenericOption(this.data.genericModbusRegisterTypeOptions, inputRegisterIndex)
      this.updateGenericModbusDialog({
        groupName: parsed.name || dialog.groupName,
        parsedStructRegisters: parsed.registers,
        quantity: String(parsed.registers.length),
        registerTypeIndex: inputRegisterIndex,
        registerTypeText: inputRegisterType.label || '',
        structParsedSummary: `${parsed.structName} · ${parsed.registers.length} 个字段`
      })
      if (this.pageToast) this.pageToast.show('结构体解析完成')
    } catch (error) {
      if (this.pageToast) this.pageToast.show(error.message || '结构体解析失败', 'error')
    }
  },

  onGenericDraftTypeChange(event) {
    const registerTypeIndex = Number(event.detail.value)
    const registerType = getGenericOption(this.data.genericModbusRegisterTypeOptions, registerTypeIndex)
    const clearParsedStruct = registerType.key === 'coil' || registerType.key === 'discrete'

    this.updateGenericModbusDialog({
      ...(clearParsedStruct ? {
        parsedStructRegisters: [],
        structParsedSummary: ''
      } : {}),
      registerTypeIndex,
      registerTypeText: registerType.label || ''
    })
  },

  onGenericDialogDataTypeChange(event) {
    const dataTypeIndex = Number(event.detail.value)

    this.updateGenericModbusDialog(getGenericDialogDataTypeState(
      this.data.genericModbusDialog,
      this.data.genericModbusDataTypeOptions,
      dataTypeIndex
    ))
  },

  openGenericGroupEdit(event) {
    const groupId = event.currentTarget.dataset.groupId
    const group = findGenericGroup(this.data.genericModbusGroups, groupId)
    if (!group) return

    this.genericModbusGroupLongPressGuard = groupId

    this.updateGenericModbusDialog(createGenericGroupDialogState(group))
  },

  openGenericRegisterInfo(event) {
    const groupId = event.currentTarget.dataset.groupId
    const registerIndex = Number(event.currentTarget.dataset.index)
    const registerKey = `${groupId}:${registerIndex}`
    if (this.genericModbusRegisterLongPressGuard === registerKey) {
      this.genericModbusRegisterLongPressGuard = ''
      return
    }

    const {
      group,
      register
    } = findGenericRegister(this.data.genericModbusGroups, groupId, registerIndex)
    if (!register) return

    this.updateGenericModbusDialog(createGenericRegisterDialogState('viewRegister', group, register, registerIndex))
  },

  openGenericRegisterEdit(event) {
    const groupId = event.currentTarget.dataset.groupId
    const registerIndex = Number(event.currentTarget.dataset.index)
    const {
      group,
      register
    } = findGenericRegister(this.data.genericModbusGroups, groupId, registerIndex)
    if (!register) return

    this.genericModbusRegisterLongPressGuard = `${groupId}:${registerIndex}`
    this.updateGenericModbusDialog(createGenericRegisterDialogState('editRegister', group, register, registerIndex))
  },

  async confirmGenericModbusDialog() {
    const dialog = this.data.genericModbusDialog || createGenericModbusDialogState()
    const mode = dialog.mode

    if (mode === 'createGroup') {
      const group = genericModbusService.addGroupFromConfig(createGenericGroupConfig(dialog))
      if (group) {
        if (this.pageToast) this.pageToast.show(`${group.name}已添加`)
        this.closeGenericModbusDraft()
        this.setData({
          activeGenericGroup: group,
          activeGenericGroupId: group.id,
          activeParamView: 'genericModbusGroup',
          activeGenericRegisterRows: buildActiveGenericRegisterRows(group, this.genericModbusRegisterDrag)
        })
      }
      return
    }

    if (mode === 'editGroup') {
      const group = genericModbusService.updateGroupConfig(dialog.groupId, createGenericGroupConfig(dialog))
      if (group) {
        if (this.pageToast) this.pageToast.show(`${group.name}已更新`)
        this.closeGenericModbusDraft()
        if (this.data.activeGenericGroupId === group.id) {
          this.setData({
            activeGenericGroup: group,
            activeGenericRegisterRows: buildActiveGenericRegisterRows(group, this.genericModbusRegisterDrag)
          })
        }
      }
      return
    }

    if (mode === 'editRegister') {
      const changedData = createGenericRegisterChangedData(dialog, this.data.genericModbusDataTypeOptions)
      genericModbusService.updateRegister(dialog.groupId, dialog.registerIndex, changedData)
      if (this.pageToast) this.pageToast.show(`${dialog.name || '寄存器'}已更新`)
      this.closeGenericModbusDraft()
    }
  },

  async importGenericModbusJson() {
    const count = await genericModbusService.importJsonFromMessageFile()
    if (count && this.pageToast) this.pageToast.show(`已导入 ${count} 个寄存器组`)
  },

  async saveGenericModbusJson() {
    const count = await genericModbusService.saveJsonToChat()
    if (count && this.pageToast) this.pageToast.show(`已保存 ${count} 个寄存器组`)
  },

  toggleGenericModbusGroup(event) {
    const groupId = event.currentTarget.dataset.groupId
    if (this.genericModbusGroupLongPressGuard === groupId) {
      this.genericModbusGroupLongPressGuard = ''
      return
    }
    const group = findGenericGroup(this.data.genericModbusGroups, groupId)
    if (!group) return

    genericModbusService.setGroupExpanded(groupId, !group.expanded)
  },

  onGenericRegisterValueInput(event) {
    genericModbusService.updateRegisterValue(
      event.currentTarget.dataset.groupId,
      Number(event.currentTarget.dataset.index),
      event.detail.value
    )
  },

  onGenericRegisterValueBlur(event) {
    const groupId = event.currentTarget.dataset.groupId
    const registerIndex = Number(event.currentTarget.dataset.index)
    try {
      genericModbusService.validateRegisterInputValue(groupId, registerIndex, event.detail.value)
    } catch (error) {
      if (this.pageToast) this.pageToast.show(error.message || '输入值无效', 'error')
    }
  },

  async readGenericModbusGroup(event) {
    if (!this.data.connectedDevice) return

    const groupId = event.currentTarget.dataset.groupId
    const ok = await genericModbusService.readGroup(groupId, {
      maxPacketLength: this.data.genericModbusMaxPacketLength
    })
    if (ok && this.pageToast) this.pageToast.show('通用Modbus读取完成')
  },

  async writeGenericModbusGroup(event) {
    if (!this.data.connectedDevice) return

    const groupId = event.currentTarget.dataset.groupId
    const ok = await genericModbusService.writeGroup(groupId)
    if (ok && this.pageToast) this.pageToast.show('通用Modbus写入完成')
  },

  onGenericGroupTouchStart(event) {
    const groupId = event.currentTarget.dataset.groupId
    const touch = (event.changedTouches || [])[0]
    if (!groupId || !touch) return

    this.genericModbusTouchStarts[groupId] = touch.clientX
  },

  onGenericGroupTouchEnd(event) {
    const groupId = event.currentTarget.dataset.groupId
    const group = findGenericGroup(this.data.genericModbusGroups, groupId)
    const touch = (event.changedTouches || [])[0]
    const startX = this.genericModbusTouchStarts[groupId]
    if (!groupId || !group || group.expanded || !touch || !Number.isFinite(startX)) return

    const deltaX = touch.clientX - startX
    if (deltaX > 42) {
      genericModbusService.setGroupDeleteVisible(groupId, true)
    } else if (deltaX < -24) {
      genericModbusService.setGroupDeleteVisible(groupId, false)
    }
  },

  onGenericRegisterDragStart(event) {
    const touch = (event.changedTouches || [])[0]
    if (!touch) return

    const groupId = event.currentTarget.dataset.groupId
    const index = Number(event.currentTarget.dataset.index)
    const activeGenericGroup = findGenericGroup(this.data.genericModbusGroups, groupId)
    if (!groupId || !activeGenericGroup || !Number.isInteger(index)) return

    this.genericModbusRegisterDrag = {
      groupId,
      index,
      moved: false,
      rowCenters: [],
      rowOffset: getFallbackDragRowOffsetPx(this.genericWindowWidth),
      startY: touch.clientY,
      targetIndex: index,
      translateY: 0
    }

    if (this.data.activeGenericGroupId === groupId) {
      this.setData({
        activeGenericRegisterRows: buildActiveGenericRegisterRows(activeGenericGroup, this.genericModbusRegisterDrag)
      })
    }

    this.measureGenericRegisterRows(this.genericModbusRegisterDrag)
  },

  onGenericRegisterDragMove(event) {
    const touch = (event.changedTouches || [])[0]
    if (!touch || !this.genericModbusRegisterDrag) return

    const drag = this.genericModbusRegisterDrag
    const group = findGenericGroup(this.data.genericModbusGroups, drag.groupId)
    if (!group) return

    const translateY = Math.round(touch.clientY - drag.startY)
    const moved = Math.abs(translateY) > GENERIC_REGISTER_DRAG_THRESHOLD_PX
    const targetIndex = moved
      ? resolveDragTargetIndex(drag, touch.clientY, group.registers.length)
      : drag.index

    if (
      drag.translateY === translateY
      && drag.moved === moved
      && drag.targetIndex === targetIndex
    ) {
      return
    }

    drag.translateY = translateY
    drag.moved = moved
    drag.targetIndex = targetIndex

    if (this.data.activeGenericGroupId === group.id) {
      this.setData({
        activeGenericRegisterRows: buildActiveGenericRegisterRows(group, drag)
      })
    }
  },

  onGenericRegisterDragEnd(event) {
    const drag = this.genericModbusRegisterDrag
    this.genericModbusRegisterDrag = null
    if (!drag || !drag.groupId) return

    const group = findGenericGroup(this.data.genericModbusGroups, drag.groupId)
    if (!group) return

    if (this.data.activeGenericGroupId === group.id) {
      this.setData({
        activeGenericRegisterRows: buildActiveGenericRegisterRows(group, null)
      })
    }

    if (!drag.moved) return

    const targetIndex = clampIndex(
      Number(drag.targetIndex) || drag.index,
      0,
      group.registers.length - 1
    )
    if (targetIndex === drag.index) return

    const updatedGroup = genericModbusService.reorderRegister(drag.groupId, drag.index, targetIndex)
    if (!updatedGroup) return

    this.genericModbusRegisterLongPressGuard = `${drag.groupId}:${targetIndex}`
    setTimeout(() => {
      if (this.genericModbusRegisterLongPressGuard === `${drag.groupId}:${targetIndex}`) {
        this.genericModbusRegisterLongPressGuard = ''
      }
    }, 260)

    if (this.data.activeGenericGroupId === updatedGroup.id) {
      this.setData({
        activeGenericGroup: updatedGroup,
        activeGenericRegisterRows: buildActiveGenericRegisterRows(updatedGroup, null)
      })
    }
  },

  onGenericRegisterDragCancel() {
    const drag = this.genericModbusRegisterDrag
    this.genericModbusRegisterDrag = null
    if (!drag || !drag.groupId) return

    const group = findGenericGroup(this.data.genericModbusGroups, drag.groupId)
    if (!group || this.data.activeGenericGroupId !== group.id) return

    this.setData({
      activeGenericRegisterRows: buildActiveGenericRegisterRows(group, null)
    })
  },

  deleteGenericModbusGroup(event) {
    const groupId = event.currentTarget.dataset.groupId
    this.clearGenericAutoTimer(groupId)
    genericModbusService.removeGroup(groupId)
    if (this.data.activeGenericGroupId === groupId) {
      this.setData({
        activeGenericGroup: null,
        activeGenericGroupId: '',
        activeParamView: 'genericModbus'
      })
    }
    if (this.pageToast) this.pageToast.show('寄存器组已删除')
  },

  clearGenericAutoTimer(groupId) {
    if (this.genericModbusPoller) this.genericModbusPoller.clearTimer(groupId)
  },

  clearGenericAutoTimers() {
    if (this.genericModbusPoller) this.genericModbusPoller.clearAll()
  },

  scheduleVisibleGenericAutoReads() {
    if (this.genericModbusPoller) this.genericModbusPoller.scheduleVisible()
  },

  scheduleGenericAutoPoll(delay) {
    if (this.genericModbusPoller) this.genericModbusPoller.schedule(delay)
  },

  clearGenericRegisterDrag() {
    if (!this.genericModbusRegisterDrag) return

    const drag = this.genericModbusRegisterDrag
    this.genericModbusRegisterDrag = null
    const group = findGenericGroup(this.data.genericModbusGroups, drag.groupId)

    this.setData({
      activeGenericRegisterRows: buildActiveGenericRegisterRows(group, null)
    })
  },

  measureGenericRegisterRows(dragReference) {
    const query = this.createSelectorQuery()
    query.selectAll('.generic-register-row').boundingClientRect((rects) => {
      if (!this.genericModbusRegisterDrag || this.genericModbusRegisterDrag !== dragReference) return
      if (!Array.isArray(rects) || !rects.length) return

      dragReference.rowCenters = rects.map((rect) => Number(rect.top || 0) + Number(rect.height || 0) / 2)
      dragReference.rowOffset = Math.max(
        1,
        Math.round(Number((rects[dragReference.index] || {}).height) || dragReference.rowOffset || 0)
      )

      const group = findGenericGroup(this.data.genericModbusGroups, dragReference.groupId)
      if (!group || this.data.activeGenericGroupId !== group.id) return

      this.setData({
        activeGenericRegisterRows: buildActiveGenericRegisterRows(group, dragReference)
      })
    }).exec()
  }
})
