const {
  createParameterDialogState,
  findParameterGroup,
  findParameterRegister,
  getActiveParameterGroup,
  getPageState,
  getSettingsPageState,
  getTransportPageState,
  getVisiblePageState,
  resolveActiveParamView
} = require('../../features/parameter-groups/view-model.js')
const {
  createDialogHandlers,
  createParameterGroupPoller,
  parameterGroupService
} = require('../../features/parameter-groups/index.js')
const settingsService = require('../../store/settings-store.js')
const themeService = require('../../store/theme-store.js')
const transport = require('../../transport/ble-core.js')
const {
  createPageToast
} = require('../../utils/page-toast.js')
const {
  PARAMETER_REGISTER_DRAG_THRESHOLD_PX,
  buildActiveParameterRegisterRows,
  clampIndex,
  getFallbackDragRowOffsetPx,
  getWindowWidth,
  resolveDragTargetIndex
} = require('../../features/parameter-groups/drag-view-model.js')

function getParameterGroupsFromState(state = {}) {
  return state.parameterGroups || []
}

function isReadableParameterGroup(group = {}, data = {}) {
  if (!group || group.addressOverflow) return false
  if (data.isStorageAccessProtocol) return !!group.sourceMemoryArea

  return !!group.functionCode
}

Page({
  data: {
    ...getPageState(),
    activeParamView: 'parameterGroups',
    activeParameterGroupId: '',
    activeParameterRegisterRows: [],
    parameterDialog: createParameterDialogState()
  },

  onTabItemTap() {
    this.backToParamsHome()
  },

  onLoad() {
    this.pageToast = createPageToast(this, this.data)
    this.parameterGroupPoller = createParameterGroupPoller(() => this.data)
    this.parameterGroupTouchStarts = {}
    this.parameterWindowWidth = getWindowWidth()
    parameterGroupService.init()
    themeService.init()
    settingsService.init()
    this.unsubscribeTheme = themeService.subscribe((themeState) => {
      this.setData(themeState)
    })
    this.unsubscribeTransport = transport.subscribe((transportState) => {
      this.setData(getTransportPageState(transportState))
      if (transportState.connectedDevice) {
        setTimeout(() => this.scheduleVisibleParameterAutoReads(), 0)
      } else {
        this.clearParameterAutoTimers()
      }
    })
    this.unsubscribeParameterGroups = parameterGroupService.subscribe((parameterState) => {
      const activeParameterGroup = getActiveParameterGroup(getParameterGroupsFromState(parameterState), this.data.activeParameterGroupId)
      this.setData({
        ...parameterState,
        activeParameterGroup,
        activeParameterRegisterRows: buildActiveParameterRegisterRows(activeParameterGroup, this.parameterRegisterDrag),
        activeParamView: this.data.activeParamView === 'parameterGroup' && !activeParameterGroup
          ? 'parameterGroups'
          : this.data.activeParamView
      })
    })
    this.unsubscribeSettings = settingsService.subscribe((settingsState) => {
      const protocolChanged = this.data.protocolMode && this.data.protocolMode !== settingsState.protocolMode
      const nextState = getSettingsPageState(this.data, settingsState)
      const activeParamView = protocolChanged ? 'parameterGroups' : nextState.activeParamView
      const parameterGroups = getParameterGroupsFromState(parameterGroupService.getState())

      const activeParameterGroupId = protocolChanged ? '' : this.data.activeParameterGroupId
      const activeParameterGroup = getActiveParameterGroup(parameterGroups, activeParameterGroupId)
      const safeActiveView = activeParamView === 'parameterGroup' && !activeParameterGroup
        ? 'parameterGroups'
        : activeParamView
      this.clearParameterAutoTimers()
      this.setData({
        ...nextState,
        parameterGroups,
        activeParameterGroupId,
        activeParamView: safeActiveView,
        activeParameterGroup,
        activeParameterRegisterRows: buildActiveParameterRegisterRows(activeParameterGroup, this.parameterRegisterDrag)
      })
      if (safeActiveView === 'parameterGroups' || safeActiveView === 'parameterGroup') {
        setTimeout(() => this.scheduleVisibleParameterAutoReads(), 0)
      } else {
        this.clearParameterAutoTimers()
      }
    })
  },

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

    const pageState = getVisiblePageState(this.data)
    this.setData({
      ...pageState,
      activeParameterGroup: getActiveParameterGroup(getParameterGroupsFromState(pageState), this.data.activeParameterGroupId),
      activeParameterRegisterRows: buildActiveParameterRegisterRows(
        getActiveParameterGroup(getParameterGroupsFromState(pageState), this.data.activeParameterGroupId),
        this.parameterRegisterDrag
      )
    })
    this.pageToast.showFromState(pageState)
    this.scheduleVisibleParameterAutoReads()
  },

  onHide() {
    if (this.pageToast) {
      this.pageToast.setActive(false)
    }
    this.clearParameterRegisterDrag()
    this.clearParameterAutoTimers()
  },

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

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

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

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

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

    this.clearParameterAutoTimers()
  },

  openParamView(event) {
    if (this.pageToast) this.pageToast.clear()
    this.closeParameterDraft()
    this.clearParameterRegisterDrag()

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

    this.setData({
      activeParamView
    })
    if (activeParamView === 'parameterGroups') {
      setTimeout(() => this.scheduleVisibleParameterAutoReads(), 0)
    }
  },

  openParameterGroup(event) {
    const groupId = event.currentTarget.dataset.groupId
    const group = findParameterGroup(getParameterGroupsFromState(this.data), groupId)
    if (!group) return

    if (this.pageToast) this.pageToast.clear()
    this.closeParameterDraft()
    this.setData({
      activeParameterGroup: group,
      activeParameterGroupId: groupId,
      activeParamView: 'parameterGroup',
      activeParameterRegisterRows: buildActiveParameterRegisterRows(group, this.parameterRegisterDrag)
    })
  },

  backToParamsHome() {
    if (this.pageToast) this.pageToast.clear()
    this.closeParameterDraft()
    this.clearParameterRegisterDrag()
    this.clearParameterAutoTimers()

    const activeParamView = resolveActiveParamView('', this.data)
    this.setData({
      activeParameterGroup: null,
      activeParameterGroupId: '',
      activeParamView,
      activeParameterRegisterRows: []
    })
    if (activeParamView === 'parameterGroups') {
      setTimeout(() => this.scheduleVisibleParameterAutoReads(), 0)
    }
  },

  noop() {},

  ...createDialogHandlers(parameterGroupService),

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

  async completeParameterStructs() {
    const result = await parameterGroupService.completeStructInstanceGroupsWithStructFile()
    if (result && result.completedCount && this.pageToast) {
      this.pageToast.show(`已补全 ${result.completedCount} 个结构体/枚举项`)
    }
  },

  clearStorageAccessGroups() {
    if (!this.data.isStorageAccessProtocol) return

    this.clearParameterAutoTimers()
    parameterGroupService.clearStorageAccessGroups()
    this.setData({
      activeParameterGroup: null,
      activeParameterGroupId: '',
      activeParameterRegisterRows: [],
      activeParamView: 'parameterGroups'
    })
    if (this.pageToast) this.pageToast.show('已清除结构体组')
  },

  async saveParameterGroupsJson() {
    const result = await parameterGroupService.saveJsonToChat()
    const count = Number(result && result.count) || 0
    if (count && this.pageToast) {
      const actionText = result.method === 'disk' ? '已保存' : '已发送'
      this.pageToast.show(`${actionText} ${count} 个寄存器组`)
    }
  },

  toggleParameterGroup(event) {
    const groupId = event.currentTarget.dataset.groupId
    if (this.parameterGroupLongPressGuard === groupId) {
      this.parameterGroupLongPressGuard = ''
      return
    }
    const group = findParameterGroup(getParameterGroupsFromState(this.data), groupId)
    if (!group) return

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

  onParameterRegisterValueInput(event) {
    parameterGroupService.updateRegisterValue(
      event.currentTarget.dataset.groupId,
      Number(event.currentTarget.dataset.index),
      event.detail.value
    )
  },

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

    if (!this.data.isStorageAccessProtocol || !this.data.connectedDevice) return

    const group = findParameterGroup(getParameterGroupsFromState(this.data), groupId)
    const register = group && group.registers ? group.registers[registerIndex] : null
    if (!group || !register || !register.isDirty || !group.writable || group.addressOverflow) return

    this.clearParameterAutoTimers()
    const ok = await parameterGroupService.writeRegister(groupId, registerIndex)
    if (this.data.parameterAutoPollEnabled) {
      this.scheduleParameterAutoPoll(this.data.parameterPollInterval || 100)
    }
    if (ok && this.pageToast) {
      this.pageToast.show(`${register.name || '变量'}已写入`)
    }
  },

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

    const protocolMode = this.data.protocolMode
    const groups = getParameterGroupsFromState(this.data)
      .filter((group) => isReadableParameterGroup(group, this.data))
    if (!groups.length) {
      if (this.pageToast) this.pageToast.show('没有可读取的参数组')
      return
    }

    this.clearParameterAutoTimers()

    let successCount = 0
    for (const group of groups) {
      if (!this.data.connectedDevice || this.data.protocolMode !== protocolMode) break

      const ok = await parameterGroupService.readGroup(group.id, {
        maxPacketLength: this.data.parameterMaxPacketLength,
        protocolMode
      })
      if (ok) successCount += 1
    }

    if (this.data.parameterAutoPollEnabled) {
      this.scheduleParameterAutoPoll(this.data.parameterPollInterval || 100)
    }
    if (this.pageToast) {
      this.pageToast.show(`读取完成 ${successCount}/${groups.length} 个`)
    }
  },

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

    const groupId = event.currentTarget.dataset.groupId
    const ok = await parameterGroupService.readGroup(groupId, {
      maxPacketLength: this.data.parameterMaxPacketLength
    })
    if (ok && this.pageToast) this.pageToast.show('参数组读取完成')
  },

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

    const groupId = event.currentTarget.dataset.groupId
    const ok = await parameterGroupService.writeGroup(groupId)
    if (ok && this.pageToast) this.pageToast.show('参数组写入完成')
  },

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

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

  onParameterGroupTouchEnd(event) {
    const groupId = event.currentTarget.dataset.groupId
    const group = findParameterGroup(getParameterGroupsFromState(this.data), groupId)
    const touch = (event.changedTouches || [])[0]
    const startX = this.parameterGroupTouchStarts[groupId]
    if (!groupId || !group || group.expanded || !touch || !Number.isFinite(startX)) return

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

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

    const groupId = event.currentTarget.dataset.groupId
    const index = Number(event.currentTarget.dataset.index)
    const activeParameterGroup = findParameterGroup(getParameterGroupsFromState(this.data), groupId)
    if (!groupId || !activeParameterGroup || !Number.isInteger(index)) return

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

    if (this.data.activeParameterGroupId === groupId) {
      this.setData({
        activeParameterRegisterRows: buildActiveParameterRegisterRows(activeParameterGroup, this.parameterRegisterDrag)
      })
    }

    this.measureParameterRegisterRows(this.parameterRegisterDrag)
  },

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

    const drag = this.parameterRegisterDrag
    const group = findParameterGroup(getParameterGroupsFromState(this.data), drag.groupId)
    if (!group) return

    const translateY = Math.round(touch.clientY - drag.startY)
    const moved = Math.abs(translateY) > PARAMETER_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.activeParameterGroupId === group.id) {
      this.setData({
        activeParameterRegisterRows: buildActiveParameterRegisterRows(group, drag)
      })
    }
  },

  onParameterRegisterDragEnd(event) {
    const drag = this.parameterRegisterDrag
    this.parameterRegisterDrag = null
    if (!drag || !drag.groupId) return

    const group = findParameterGroup(getParameterGroupsFromState(this.data), drag.groupId)
    if (!group) return

    if (this.data.activeParameterGroupId === group.id) {
      this.setData({
        activeParameterRegisterRows: buildActiveParameterRegisterRows(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 = parameterGroupService.reorderRegister(drag.groupId, drag.index, targetIndex)
    if (!updatedGroup) return

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

    if (this.data.activeParameterGroupId === updatedGroup.id) {
      this.setData({
        activeParameterGroup: updatedGroup,
        activeParameterRegisterRows: buildActiveParameterRegisterRows(updatedGroup, null)
      })
    }
  },

  onParameterRegisterDragCancel() {
    const drag = this.parameterRegisterDrag
    this.parameterRegisterDrag = null
    if (!drag || !drag.groupId) return

    const group = findParameterGroup(getParameterGroupsFromState(this.data), drag.groupId)
    if (!group || this.data.activeParameterGroupId !== group.id) return

    this.setData({
      activeParameterRegisterRows: buildActiveParameterRegisterRows(group, null)
    })
  },

  deleteParameterGroup(event) {
    const groupId = event.currentTarget.dataset.groupId
    this.clearParameterAutoTimer(groupId)
    parameterGroupService.removeGroup(groupId)
    if (this.data.activeParameterGroupId === groupId) {
      this.setData({
        activeParameterGroup: null,
        activeParameterGroupId: '',
        activeParamView: 'parameterGroups'
      })
    }
    if (this.pageToast) this.pageToast.show('寄存器组已删除')
  },

  clearParameterAutoTimer(groupId) {
    if (this.parameterGroupPoller) this.parameterGroupPoller.clearTimer(groupId)
  },

  clearParameterAutoTimers() {
    if (this.parameterGroupPoller) this.parameterGroupPoller.clearAll()
  },

  scheduleVisibleParameterAutoReads() {
    if (this.parameterGroupPoller) this.parameterGroupPoller.scheduleVisible()
  },

  scheduleParameterAutoPoll(delay) {
    if (this.parameterGroupPoller) this.parameterGroupPoller.schedule(delay)
  },

  clearParameterRegisterDrag() {
    if (!this.parameterRegisterDrag) return

    const drag = this.parameterRegisterDrag
    this.parameterRegisterDrag = null
    const group = findParameterGroup(getParameterGroupsFromState(this.data), drag.groupId)

    this.setData({
      activeParameterRegisterRows: buildActiveParameterRegisterRows(group, null)
    })
  },

  measureParameterRegisterRows(dragReference) {
    const query = this.createSelectorQuery()
    query.selectAll('.generic-register-row').boundingClientRect((rects) => {
      if (!this.parameterRegisterDrag || this.parameterRegisterDrag !== 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 = findParameterGroup(getParameterGroupsFromState(this.data), dragReference.groupId)
      if (!group || this.data.activeParameterGroupId !== group.id) return

      this.setData({
        activeParameterRegisterRows: buildActiveParameterRegisterRows(group, dragReference)
      })
    }).exec()
  }
})
