const paramsPageState = require('../../utils/params-page-state')
const paramsService = require('../../utils/params-service')
const controlService = require('../../utils/control-service')
const genericModbusService = require('../../utils/generic-modbus-service')
const {
  createGenericModbusPoller
} = require('../../utils/generic-modbus-poller')
const settingsService = require('../../utils/settings-service')
const themeService = require('../../utils/theme-service')
const {
  createGenericGroupConfig,
  createGenericGroupDialogState,
  createGenericModbusDialogState,
  createGenericRegisterChangedData,
  createGenericRegisterDialogState,
  findGenericGroup,
  findGenericRegister,
  getCombinedGroupKeys,
  getCombinedGroupLabel,
  getControlViewState,
  getGenericDialogDataTypeState,
  getGenericOption,
  getGroupLabel,
  getPageState,
  getSettingsPageState,
  getVisiblePageState,
  hasWritableGroupChanges,
  resolveActiveParamView
} = require('../../utils/params-view-model')
const syncService = require('../../utils/sync-service')
const {
  createPageToast
} = require('../../utils/page-toast')

Page({
  data: {
    ...getPageState(),
    activeParamView: '',
    genericModbusDialog: createGenericModbusDialogState()
  },

  onTabItemTap() {
    this.backToParamsHome()
  },

  onLoad() {
    this.pageToast = createPageToast(this, this.data)
    this.genericModbusPoller = createGenericModbusPoller(() => this.data)
    this.genericModbusTouchStarts = {}
    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) => {
      this.setData(genericState)
    })
    this.unsubscribeSettings = settingsService.subscribe((settingsState) => {
      const nextState = getSettingsPageState(this.data, settingsState)
      const activeParamView = nextState.activeParamView

      this.setData(nextState)
      if (activeParamView === 'genericModbus') {
        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)
    this.pageToast.showFromState(pageState)
    this.scheduleVisibleGenericAutoReads()
  },

  onHide() {
    if (this.pageToast) {
      this.pageToast.setActive(false)
    }
    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()

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

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

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

    const activeParamView = resolveActiveParamView('', this.data)
    this.setData({
      activeParamView
    })
    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

    this.updateGenericModbusDialog({
      [field]: event.detail.value
    })
  },

  onGenericDraftTypeChange(event) {
    const registerTypeIndex = Number(event.detail.value)
    const registerType = getGenericOption(this.data.genericModbusRegisterTypeOptions, registerTypeIndex)

    this.updateGenericModbusDialog({
      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()
      }
      return
    }

    if (mode === 'editGroup') {
      const group = genericModbusService.updateGroupConfig(dialog.groupId, createGenericGroupConfig(dialog))
      if (group) {
        if (this.pageToast) this.pageToast.show(`${group.name}已更新`)
        this.closeGenericModbusDraft()
      }
      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)
    }
  },

  deleteGenericModbusGroup(event) {
    const groupId = event.currentTarget.dataset.groupId
    this.clearGenericAutoTimer(groupId)
    genericModbusService.removeGroup(groupId)
    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)
  }
})
