const {
  isCancelError,
  loadGroupsFromMessageFile,
  saveGroupsToChat
} = require('./persistence.js')
const {
  mergeImportedGroups
} = require('./import-merge.js')
const {
  completeStructInstanceGroups,
  parseStructDefinition
} = require('./struct-completion.js')
const storageAccessService = require('../storage-access/service.js')
const storageAccessIo = require('./storage-access-io.js')
const modbusIo = require('./modbus-io.js')
const settingsService = require('../../store/settings-store.js')
const store = require('./store.js')
const stateMappers = require('./state-mappers.js')
const {
  loadSelectedFile
} = require('../../repositories/file.js')
const transport = require('../../transport/ble-core.js')
const {
  DATA_TYPE_OPTIONS,
  REGISTER_TYPE_OPTIONS,
  cloneImportedGroup,
  isAddressRangeOverflow,
  normalizeGroup,
  normalizeGroupConfig,
  validateRegisterValue
} = require('../../domain/parameter-groups/model.js')
const {
  findGroup,
  getGroups,
  getState,
  init,
  setGroups,
  setStorageCodeInfo,
  switchProtocolMode,
  subscribe,
  updateGroups
} = store

let initialized = false

function getActiveProtocolMode() {
  return settingsService.getState().protocolMode
}

function isStorageAccessProtocolMode(protocolMode) {
  return settingsService.isStorageAccessProtocol(protocolMode)
}

function initParameterGroups() {
  settingsService.init()
  init(getActiveProtocolMode())
  switchProtocolMode(getActiveProtocolMode(), {
    notify: false
  })

  if (initialized) return

  settingsService.subscribe((settingsState) => {
    switchProtocolMode(settingsState.protocolMode)
  })
  initialized = true
}

async function importJsonFromMessageFile() {
  try {
    const importedGroups = (await loadGroupsFromMessageFile()).map(normalizeGroup)
    if (!importedGroups.length) throw new Error('JSON 中没有可导入的寄存器组')
    const merged = mergeImportedGroups(getGroups(), importedGroups)

    setGroups(merged.groups)

    return merged.changedCount || 0
  } catch (error) {
    const message = error && error.message ? error.message : '导入参数组配置失败'
    transport.showCommandAlert('参数组导入', message)
    return 0
  }
}

function completeStructInstanceGroupsWithStructSource(sourceText, options = {}) {
  const completed = completeStructInstanceGroups(getGroups(), sourceText, options)

  if (!completed.completedCount) {
    throw new Error('没有找到可匹配的结构体实例')
  }

  setGroups(completed.groups)

  return completed
}

function mergeImportedGroupsIntoState(importedGroups = [], options = {}) {
  const normalizedGroups = importedGroups.map(cloneImportedGroup).map(normalizeGroup)
  const merged = mergeImportedGroups(getGroups(), normalizedGroups, options)

  if (normalizedGroups.length) {
    setGroups(merged.groups)
  }

  return merged
}

async function completeStructInstanceGroupsWithStructFile(options = {}) {
  try {
    const file = await loadSelectedFile('message', {
      encoding: 'utf8',
      extensionMessage: '请选择 .h 或 .c 结构体定义文件',
      extensions: ['h', 'c', 'txt'],
      fallbackName: 'structs.h'
    })

    return completeStructInstanceGroupsWithStructSource(file.text, options)
  } catch (error) {
    const message = error && error.message ? error.message : '结构体补全失败'
    transport.showCommandAlert('结构体补全', message)

    return {
      completedCount: 0,
      skippedCount: 0,
      structCount: 0,
      variableCount: 0
    }
  }
}

async function saveJsonToChat() {
  try {
    return saveGroupsToChat(getGroups())
  } catch (error) {
    const message = error && error.message ? error.message : '保存参数组配置失败'

    if (!isCancelError(error)) {
      transport.showCommandAlert('参数组保存', message)
    }

    return 0
  }
}

async function syncFromStorageAccessCodeInfo(options = {}) {
  const result = await storageAccessService.syncCodeInfo(options)
  if (!result || !result.ok) return result

  setStorageCodeInfo(result.codeInfo)
  const merged = mergeImportedGroupsIntoState(result.importedGroups || [], {
    preserveExistingRemarks: true,
    preserveExistingStructLayout: true
  })

  return {
    ...result,
    addedGroups: merged.addedGroupCount,
    addedRegisters: merged.addedRegisterCount,
    updatedGroups: merged.updatedGroupCount,
    updatedRegisters: merged.updatedRegisterCount
  }
}

function addGroupFromConfig(config = {}) {
  let groupConfig

  try {
    groupConfig = normalizeGroupConfig(config)
  } catch (error) {
    transport.showCommandAlert('参数组添加', error.message || '寄存器组配置无效')
    return null
  }

  if (isAddressRangeOverflow(groupConfig.startAddress, groupConfig.quantity)) {
    transport.showCommandAlert('参数组添加', '地址范围超出 0xFFFF')
    return null
  }

  const registers = Array.isArray(config.registers) ? config.registers : []
  const group = normalizeGroup({
    ...groupConfig,
    layout: config.layout,
    ...(registers.length ? { registers } : {}),
    expanded: false
  })

  if (group.addressOverflow) {
    transport.showCommandAlert('参数组添加', '地址范围超出 0xFFFF')
    return null
  }

  setGroups(getGroups().concat(group))

  return group
}

function updateGroupConfig(groupId, config = {}) {
  const group = findGroup(groupId)
  if (!group) return null

  let nextConfig
  try {
    nextConfig = normalizeGroupConfig({
      ...group,
      ...config
    })
  } catch (error) {
    transport.showCommandAlert('参数组更新', error.message || '寄存器组配置无效')
    return null
  }

  if (isAddressRangeOverflow(nextConfig.startAddress, nextConfig.quantity)) {
    transport.showCommandAlert('参数组更新', '地址范围超出 0xFFFF')
    return null
  }

  const registers = Array.isArray(config.registers) ? config.registers : group.registers
  const updatedGroup = normalizeGroup({
    ...group,
    ...nextConfig,
    registers
  })

  if (updatedGroup.addressOverflow) {
    transport.showCommandAlert('参数组更新', '地址范围超出 0xFFFF')
    return null
  }

  setGroups(getGroups().map((item) => (
    item.id === groupId ? updatedGroup : item
  )))

  return updatedGroup
}

function setGroupExpanded(groupId, expanded) {
  updateGroups((group) => group.id === groupId
    ? {
      ...group,
      deleteVisible: false,
      expanded
    }
    : group)
}

function setGroupDeleteVisible(groupId, deleteVisible) {
  updateGroups((group) => group.id === groupId
    ? {
      ...group,
      deleteVisible
    }
    : group)
}

function removeGroup(groupId) {
  setGroups(getGroups().filter((group) => group.id !== groupId))
}

function reorderRegister(groupId, fromIndex, toIndex) {
  const group = findGroup(groupId)
  if (!group) return null
  if (group.isStructLayout) return group

  const registers = group.registers.slice()
  const sourceIndex = Number(fromIndex)
  const targetIndex = Number(toIndex)
  if (!Number.isInteger(sourceIndex) || !Number.isInteger(targetIndex)) return null
  if (sourceIndex < 0 || sourceIndex >= registers.length) return null

  const safeTargetIndex = Math.min(Math.max(targetIndex, 0), registers.length - 1)
  if (safeTargetIndex === sourceIndex) return group

  const moved = registers.splice(sourceIndex, 1)[0]
  registers.splice(safeTargetIndex, 0, moved)

  const updatedGroup = normalizeGroup({
    ...group,
    quantity: registers.length,
    registers
  })

  setGroups(getGroups().map((item) => (
    item.id === groupId ? updatedGroup : item
  )))

  return updatedGroup
}

function updateRegister(groupId, registerIndex, changedData) {
  updateGroups((group) => {
    if (group.id !== groupId) return group
    const shouldResetReadState = Object.prototype.hasOwnProperty.call(changedData, 'dataType')
      || Object.prototype.hasOwnProperty.call(changedData, 'textByteLength')

    return {
      ...group,
      registers: group.registers.map((register, currentIndex) => (
        currentIndex === registerIndex
          ? {
            ...register,
            ...(shouldResetReadState ? { rawValue: null, rawWords: [] } : {}),
            ...changedData
          }
          : register
      ))
    }
  })
}

function updateRegisterValue(groupId, registerIndex, value) {
  updateRegister(groupId, registerIndex, {
    inputValue: value,
    isDirty: true
  })
}

function validateRegisterInputValue(groupId, registerIndex, value) {
  const group = findGroup(groupId)
  if (!group) return false

  const register = group.registers[registerIndex]
  if (!register) return false

  return validateRegisterValue(register, value)
}

async function readGroup(groupId, options = {}) {
  const protocolMode = options.protocolMode || getActiveProtocolMode()
  const group = findGroup(groupId, protocolMode)
  if (!group) return false
  if (group.addressOverflow) {
    transport.showCommandAlert('参数组读取', '地址范围超出 0xFFFF')
    return false
  }

  let wordCache = {}

  if (isStorageAccessProtocolMode(protocolMode) && storageAccessIo.isMemoryGroup(group)) {
    const memoryWordCache = await storageAccessIo.readMemoryGroup(group, options)
    if (!memoryWordCache) return false

    wordCache = stateMappers.normalizeNumericCache(memoryWordCache)
  } else {
    const modbusWordCache = await modbusIo.readGroup(group, options)
    if (!modbusWordCache) return false

    wordCache = stateMappers.normalizeNumericCache(modbusWordCache)
  }

  updateGroups((item) => {
    if (item.id !== groupId) return item

    return stateMappers.applyReadCacheToGroup(item, wordCache)
  }, {
    protocolMode
  })

  return true
}

async function writeRegister(groupId, registerIndex) {
  const protocolMode = getActiveProtocolMode()
  const group = findGroup(groupId, protocolMode)
  const register = group && group.registers ? group.registers[registerIndex] : null
  if (!group || !register) return false
  if (!group.writable) {
    transport.showCommandAlert('参数组写入', '当前变量为只读')
    return false
  }
  if (group.addressOverflow) {
    transport.showCommandAlert('参数组写入', '地址范围超出 0xFFFF')
    return false
  }

  const written = isStorageAccessProtocolMode(protocolMode) && storageAccessIo.isMemoryGroup(group)
    ? await storageAccessIo.writeMemoryRegister(group, register)
    : null
  if (!written) {
    if (!isStorageAccessProtocolMode(protocolMode) || !storageAccessIo.isMemoryGroup(group)) {
      return writeGroup(groupId, {
        protocolMode
      })
    }
    return false
  }

  updateGroups((item) => {
    if (item.id !== groupId) return item

    return stateMappers.applyWrittenSnapshotToGroupRegister(item, registerIndex, written)
  }, {
    protocolMode
  })

  return true
}

async function writeGroup(groupId, options = {}) {
  const protocolMode = options.protocolMode || getActiveProtocolMode()
  const group = findGroup(groupId, protocolMode)
  if (!group) return false
  if (!group.writable) {
    transport.showCommandAlert('参数组写入', '当前寄存器组为只读')
    return false
  }
  if (group.addressOverflow) {
    transport.showCommandAlert('参数组写入', '地址范围超出 0xFFFF')
    return false
  }

  const writtenRegisters = []

  if (isStorageAccessProtocolMode(protocolMode) && storageAccessIo.isMemoryGroup(group)) {
    const snapshots = await storageAccessIo.writeMemoryGroup(group)
    if (!snapshots) return false

    snapshots.forEach((snapshot) => {
      writtenRegisters.push(snapshot)
    })
  } else {
    const snapshots = await modbusIo.writeGroup(group, options)
    if (!snapshots) return false

    snapshots.forEach((snapshot) => {
      writtenRegisters.push(snapshot)
    })
  }

  updateGroups((item) => {
    if (item.id !== groupId) return item

    return stateMappers.applyWrittenSnapshotsToGroup(item, writtenRegisters)
  }, {
    protocolMode
  })

  return true
}

module.exports = {
  DATA_TYPE_OPTIONS,
  REGISTER_TYPE_OPTIONS,
  addGroupFromConfig,
  completeStructInstanceGroups,
  completeStructInstanceGroupsWithStructFile,
  completeStructInstanceGroupsWithStructSource,
  getState,
  importJsonFromMessageFile,
  init: initParameterGroups,
  mergeImportedGroups: mergeImportedGroupsIntoState,
  parseStructDefinition,
  readGroup,
  removeGroup,
  reorderRegister,
  saveJsonToChat,
  setGroupDeleteVisible,
  setGroupExpanded,
  subscribe,
  syncFromStorageAccessCodeInfo,
  updateGroupConfig,
  updateRegister,
  updateRegisterValue,
  validateRegisterInputValue,
  writeRegister,
  writeGroup
}
