const {
  formatExportStamp,
  isCancelError,
  loadSelectedFile,
  saveTextFileToChat
} = require('../../repositories/file.js')
const {
  getWxApi
} = require('../../utils/platform-utils.js')
const {
  parseHexInteger
} = require('../../utils/base-utils.js')
const transport = require('../../transport/ble-core.js')
const settingsService = require('../../store/settings-store.js')
const modbusClient = require('../../protocols/modbus-rtu/client.js')
const {
  DATA_TYPE_OPTIONS,
  REGISTER_TYPE_OPTIONS,
  cloneImportedGroup,
  decodeRegisterFromWordCache,
  decodeRegisterValue,
  formatCoilDisplayValue,
  formatRegisterValue,
  getDataType,
  getGroupEncodedWords,
  getRegisterEncodedWords,
  getRegisterJsonValue,
  getRegisterWordsFromWordCache,
  getRegisterWriteValueText,
  isAddressRangeOverflow,
  isBitRegisterType,
  isByteRegister,
  normalizeGroup,
  normalizeGroupConfig,
  parseCoilValue,
  registerTypeIsBit,
  splitWordSpans,
  validateRegisterValue
} = require('../../domain/generic-modbus/model.js')
const {
  parseStructDefinition: parseStructDefinitionSource
} = require('../../domain/generic-modbus/struct-parser.js')

const STORAGE_KEY = 'generic-modbus-groups-json'
const JSON_DOCUMENT_TYPE = 'generic-modbus-rtu'
const JSON_SCHEMA_VERSION = 2

let initialized = false
const subscribers = []
let state = {
  genericModbusDataTypeOptions: DATA_TYPE_OPTIONS,
  genericModbusGroups: [],
  genericModbusRegisterTypeOptions: REGISTER_TYPE_OPTIONS
}

function notify() {
  const nextState = getState()

  subscribers.slice().forEach((subscriber) => {
    subscriber(nextState)
  })
}

function setState(changedData, options = {}) {
  state = {
    ...state,
    ...changedData
  }

  if (options.persist !== false) persistGroups()
  notify()
}

function resolveMaxPacketLength(value) {
  const settings = settingsService.getState()
  const numberValue = Number(value === undefined ? settings.genericModbusMaxPacketLength : value)
  if (Number.isFinite(numberValue) && Math.round(numberValue) === 0) return 0
  if (Number.isFinite(numberValue) && numberValue > 0) return Math.round(numberValue)

  return 64
}

function getWriteSpanMaxQuantity(totalQuantity, maxPacketLength) {
  if (maxPacketLength === 0) return Math.max(1, totalQuantity)

  return Math.max(1, modbusClient.getMaxWriteMultipleRegisterQuantity(maxPacketLength))
}

function toPersistedGroups(groups) {
  return groups.map((group) => ({
    layout: group.layout,
    name: group.name,
    registerType: group.registerType,
    startAddress: group.startAddress,
    quantity: group.quantity,
    registers: group.registers.map((register) => ({
      dataType: register.dataType,
      defaultValue: register.defaultValue,
      isStructField: register.isStructField,
      name: register.name,
      maxValue: register.maxValue,
      minValue: register.minValue,
      textByteLength: register.textByteLength,
      remark: register.remark,
      unit: register.unit,
      value: getRegisterJsonValue(register)
    }))
  }))
}

function toJsonData(groups = state.genericModbusGroups, options = {}) {
  const jsonData = {
    groups: toPersistedGroups(groups),
    type: JSON_DOCUMENT_TYPE,
    version: JSON_SCHEMA_VERSION
  }

  if (options.includeExportedAt) {
    jsonData.exportedAt = new Date().toISOString()
  }

  return jsonData
}

function toJsonText(groups = state.genericModbusGroups, options = {}) {
  return JSON.stringify(toJsonData(groups, options), null, 2)
}

function parseJsonGroups(jsonText) {
  const parsed = typeof jsonText === 'string' ? JSON.parse(jsonText) : jsonText
  const groups = Array.isArray(parsed)
    ? parsed
    : (Array.isArray(parsed && parsed.groups) ? parsed.groups : parsed && parsed.genericModbusGroups)

  if (parsed && parsed.type && parsed.type !== JSON_DOCUMENT_TYPE) {
    throw new Error('JSON 文件不是通用Modbus配置')
  }

  if (parsed && parsed.version && parsed.version !== JSON_SCHEMA_VERSION) {
    throw new Error('JSON 版本不兼容')
  }

  if (!Array.isArray(groups)) {
    throw new Error('JSON 中没有找到寄存器组数组')
  }

  return groups
}

function readStoredGroups() {
  const wxApi = getWxApi()
  if (typeof wxApi.getStorageSync !== 'function') return []

  try {
    const jsonText = wxApi.getStorageSync(STORAGE_KEY)
    if (jsonText) return parseJsonGroups(jsonText).map(cloneImportedGroup)
  } catch (error) {
    return []
  }

  return []
}

function persistGroups() {
  const wxApi = getWxApi()
  if (typeof wxApi.setStorageSync !== 'function') return

  try {
    wxApi.setStorageSync(STORAGE_KEY, toJsonText())
  } catch (error) {}
}

function init() {
  if (initialized) return

  state = {
    ...state,
    genericModbusGroups: readStoredGroups().map(normalizeGroup)
  }
  initialized = true
}

function getState() {
  return {
    ...state,
    genericModbusDataTypeOptions: DATA_TYPE_OPTIONS,
    genericModbusRegisterTypeOptions: REGISTER_TYPE_OPTIONS
  }
}

function subscribe(subscriber) {
  if (typeof subscriber !== 'function') return () => {}

  init()
  subscribers.push(subscriber)
  subscriber(getState())

  return () => {
    const index = subscribers.indexOf(subscriber)
    if (index >= 0) subscribers.splice(index, 1)
  }
}

function getShareFileName() {
  return `generic-modbus-rtu-${formatExportStamp()}.json`
}

async function importJsonFromMessageFile() {
  try {
    const file = await loadSelectedFile('message', {
      encoding: 'utf8',
      extensionMessage: '请选择 .json 寄存器配置文件',
      extensions: ['json'],
      fallbackName: 'generic-modbus.json'
    })
    const jsonText = file.text
    const importedGroups = parseJsonGroups(jsonText).map(cloneImportedGroup).map(normalizeGroup)
    if (!importedGroups.length) throw new Error('JSON 中没有可导入的寄存器组')

    setState({
      genericModbusGroups: state.genericModbusGroups.concat(importedGroups)
    })

    return importedGroups.length
  } catch (error) {
    const message = error && error.message ? error.message : '导入通用Modbus配置失败'
    transport.showCommandAlert('通用Modbus导入', message)
    return 0
  }
}

async function saveJsonToChat() {
  try {
    if (!state.genericModbusGroups.length) {
      throw new Error('没有可保存的寄存器组')
    }

    const jsonText = toJsonText(state.genericModbusGroups, {
      includeExportedAt: true
    })

    await saveTextFileToChat(getShareFileName(), jsonText)

    return state.genericModbusGroups.length
  } catch (error) {
    const message = error && error.message ? error.message : '保存通用Modbus配置失败'

    if (!isCancelError(error)) {
      transport.showCommandAlert('通用Modbus保存', message)
    }

    return 0
  }
}

function addGroupFromConfig(config = {}) {
  let groupConfig

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

  if (isAddressRangeOverflow(groupConfig.startAddress, groupConfig.quantity)) {
    transport.showCommandAlert('通用Modbus添加', '地址范围超出 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('通用Modbus添加', '地址范围超出 0xFFFF')
    return null
  }

  setState({
    genericModbusGroups: state.genericModbusGroups.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('通用Modbus更新', error.message || '寄存器组配置无效')
    return null
  }

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

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

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

  setState({
    genericModbusGroups: state.genericModbusGroups.map((item) => (
      item.id === groupId ? updatedGroup : item
    ))
  })

  return updatedGroup
}

function updateGroups(mapper) {
  setState({
    genericModbusGroups: state.genericModbusGroups.map((group, index) => normalizeGroup(mapper(group, index)))
  })
}

function findGroup(groupId) {
  return state.genericModbusGroups.find((group) => group.id === groupId)
}

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) {
  setState({
    genericModbusGroups: state.genericModbusGroups.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
  })

  setState({
    genericModbusGroups: state.genericModbusGroups.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)
}

function parseStructDefinition(sourceText) {
  return parseStructDefinitionSource(sourceText)
}

async function readGroup(groupId, options = {}) {
  const group = findGroup(groupId)
  const slaveAddress = modbusClient.getSharedSlaveAddress()
  if (!group || slaveAddress === null) return false
  if (group.addressOverflow) {
    transport.showCommandAlert('通用Modbus读取', '寄存器地址范围超出 0xFFFF')
    return false
  }

  const totalQuantity = Math.max(1, group.wordQuantity || group.quantity || 0)
  const maxPacketLength = resolveMaxPacketLength(options.maxPacketLength)
  const wordCache = {}

  const values = await modbusClient.readSpans(
    slaveAddress,
    group.functionCode,
    [{
      address: group.startAddress,
      quantity: totalQuantity
    }],
    group.name || '通用Modbus读取',
    'generic-modbus-read',
    {
      maxFrameBytes: maxPacketLength,
      showModal: options.showModal !== false
    }
  )
  if (!values) return false

  if (isBitRegisterType(group.registerType)) {
    Object.keys(values.coils || {}).forEach((addressText) => {
      wordCache[parseHexInteger(addressText)] = Number(values.coils[addressText]) ? 1 : 0
    })
  } else {
    Object.keys(values.words || {}).forEach((addressText) => {
      wordCache[parseHexInteger(addressText)] = Number(values.words[addressText]) & 0xFFFF
    })
  }

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

    const nextRegisters = item.registers.map((register) => {
      const rawWords = registerTypeIsBit(register) ? [] : getRegisterWordsFromWordCache(register, wordCache)
      const rawValue = registerTypeIsBit(register)
        ? decodeRegisterFromWordCache(register, wordCache)
        : (rawWords ? decodeRegisterValue(register, rawWords) : null)
      const displayValue = rawValue === null || rawValue === undefined
        ? '--'
        : (registerTypeIsBit(register)
          ? formatCoilDisplayValue(rawValue)
          : formatRegisterValue(register, rawValue))

      return {
        ...register,
        displayValue,
        inputValue: item.writable ? displayValue : register.inputValue,
        isDirty: false,
        rawValue,
        rawWords: rawWords || []
      }
    })

    return {
      ...item,
      registers: nextRegisters
    }
  })

  return true
}

async function writeGroup(groupId) {
  const group = findGroup(groupId)
  const slaveAddress = modbusClient.getSharedSlaveAddress()
  const maxPacketLength = resolveMaxPacketLength()
  if (!group || slaveAddress === null) return false
  if (!group.writable) {
    transport.showCommandAlert('通用Modbus写入', '当前寄存器组为只读')
    return false
  }
  if (group.addressOverflow) {
    transport.showCommandAlert('通用Modbus写入', '寄存器地址范围超出 0xFFFF')
    return false
  }

  const writtenRegisters = []

  if (group.registerType === 'coil') {
    for (let index = 0; index < group.registers.length; index += 1) {
      const register = group.registers[index]
      const coilValue = parseCoilValue(getRegisterWriteValueText(register))

      if (coilValue === null) {
        transport.showCommandAlert('通用Modbus写入', `${register.name || `寄存器 ${index + 1}`} 没有有效写入值`)
        return false
      }

      const response = await modbusClient.writeSingleCoil(
        slaveAddress,
        group.startAddress + index,
        !!coilValue,
        register.name || group.name || '通用Modbus写入',
        'generic-modbus-coil-write',
        {
          maxFrameBytes: maxPacketLength
        }
      )
      if (!response) return false

      writtenRegisters.push({
        rawValue: coilValue,
        rawWords: [],
        displayValue: formatCoilDisplayValue(coilValue)
      })
    }
  } else {
    let words

    try {
      words = group.isStructLayout
        ? getGroupEncodedWords(group)
        : Array.from({ length: Math.max(1, group.wordQuantity || 1) }, () => 0)

      if (!group.isStructLayout) {
        for (let index = 0; index < group.registers.length; index += 1) {
          const register = group.registers[index]
          const registerWords = getRegisterEncodedWords(register)

          if (!Array.isArray(registerWords) || !registerWords.length) {
            throw new Error(`${register.name || `寄存器 ${index + 1}`} 没有有效写入值`)
          }

          const dataType = getDataType(register.dataType).key
          const relativeAddress = Math.max(0, register.address - group.startAddress)
          if (isByteRegister(dataType)) {
            const byteValue = Number(registerWords[0]) & 0xFF
            const currentWord = words[relativeAddress] || 0
            words[relativeAddress] = register.byteOffset === 0
              ? (((byteValue << 8) | (currentWord & 0x00FF)) & 0xFFFF)
              : (((currentWord & 0xFF00) | byteValue) & 0xFFFF)
          } else {
            for (let offset = 0; offset < register.registerCount; offset += 1) {
              words[relativeAddress + offset] = Number(registerWords[offset]) & 0xFFFF
            }
          }
        }
      }
    } catch (error) {
      transport.showCommandAlert('通用Modbus写入', error.message || '寄存器组没有有效写入值')
      return false
    }

    const writtenWordCache = words.reduce((cache, word, offset) => {
      cache[group.startAddress + offset] = word
      return cache
    }, {})

    group.registers.forEach((register) => {
      const rawWords = getRegisterWordsFromWordCache(register, writtenWordCache) || []
      const rawValue = decodeRegisterValue(register, rawWords)
      const displayValue = formatRegisterValue(register, rawValue)

      writtenRegisters.push({
        rawWords,
        rawValue,
        displayValue
      })
    })

    const maxWriteQuantity = getWriteSpanMaxQuantity(words.length, maxPacketLength)
    const spans = splitWordSpans(group.startAddress, words.length, maxWriteQuantity)
    let cursor = 0

    for (const span of spans) {
      const spanWords = words.slice(cursor, cursor + span.quantity)
      cursor += span.quantity

      const response = await modbusClient.writeMultipleRegisters(
        slaveAddress,
        span.address,
        spanWords,
        group.name || '通用Modbus写入',
        'generic-modbus-write',
        {
          maxFrameBytes: maxPacketLength
        }
      )
      if (!response) return false
    }
  }

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

    let writtenIndex = 0

    return {
      ...item,
      registers: item.registers.map((register) => {
        const written = writtenRegisters[writtenIndex] || {}
        writtenIndex += 1
        const hasDisplayValue = Object.prototype.hasOwnProperty.call(written, 'displayValue')
        const hasRawValue = Object.prototype.hasOwnProperty.call(written, 'rawValue')
        const hasRawWords = Object.prototype.hasOwnProperty.call(written, 'rawWords')

        return {
          ...register,
          displayValue: hasDisplayValue ? written.displayValue : register.displayValue,
          inputValue: hasDisplayValue ? written.displayValue : register.inputValue,
          isDirty: false,
          rawValue: hasRawValue ? written.rawValue : register.rawValue,
          rawWords: hasRawWords ? written.rawWords : register.rawWords
        }
      })
    }
  })

  return true
}

module.exports = {
  DATA_TYPE_OPTIONS,
  REGISTER_TYPE_OPTIONS,
  addGroupFromConfig,
  getState,
  importJsonFromMessageFile,
  init,
  parseStructDefinition,
  readGroup,
  removeGroup,
  reorderRegister,
  saveJsonToChat,
  setGroupDeleteVisible,
  setGroupExpanded,
  subscribe,
  updateGroupConfig,
  updateRegister,
  updateRegisterValue,
  validateRegisterInputValue,
  writeGroup
}
