const {
  formatFixedValue
} = require('../../utils/number-format.js')
const {
  clampInteger,
  createId,
  normalizeTextValue,
  padHex
} = require('../../utils/base-utils.js')
const {
  bytesToWords,
  getByteFromWord,
  trimTrailingNullBytes,
  wordsToBytes
} = require('../../utils/binary-utils.js')

const MAX_MODBUS_ADDRESS = 0xFFFF
const MAX_GENERIC_MODBUS_ITEMS = 256
const DEFAULT_TEXT_BYTE_LENGTH = 32
const MAX_TEXT_BYTE_LENGTH = 32
const REGISTER_TYPE_OPTIONS = [
  {
    functionCode: 0x03,
    key: 'holding',
    label: '保持寄存器',
    writable: true
  },
  {
    functionCode: 0x01,
    key: 'coil',
    label: '线圈',
    writable: true
  },
  {
    functionCode: 0x02,
    key: 'discrete',
    label: '离散输入状态',
    writable: false
  },
  {
    functionCode: 0x04,
    key: 'input',
    label: '输入寄存器',
    writable: false
  }
]
const DATA_TYPE_OPTIONS = [
  {
    byteLength: 1,
    key: 'int8_t',
    label: 'int8_t',
    kind: 'number',
    wordCount: 1
  },
  {
    byteLength: 1,
    key: 'uint8_t',
    label: 'uint8_t',
    kind: 'number',
    wordCount: 1
  },
  {
    byteLength: 2,
    key: 'int16_t',
    label: 'int16_t',
    kind: 'number',
    wordCount: 1
  },
  {
    byteLength: 2,
    key: 'uint16_t',
    label: 'uint16_t',
    kind: 'number',
    wordCount: 1
  },
  {
    byteLength: 4,
    key: 'int32_t',
    label: 'int32_t',
    kind: 'number',
    wordCount: 2
  },
  {
    byteLength: 4,
    key: 'uint32_t',
    label: 'uint32_t',
    kind: 'number',
    wordCount: 2
  },
  {
    byteLength: 4,
    key: 'float',
    label: 'float',
    kind: 'number',
    wordCount: 2
  },
  {
    byteLength: 32,
    key: 'utf8',
    label: 'UTF-8',
    kind: 'text',
    maxByteLength: MAX_TEXT_BYTE_LENGTH,
    wordCount: 16
  },
  {
    byteLength: 32,
    key: 'ascii',
    label: 'ASCII',
    kind: 'text',
    maxByteLength: MAX_TEXT_BYTE_LENGTH,
    wordCount: 16
  },
  {
    byteLength: 2,
    key: 'hex',
    label: 'HEX',
    kind: 'hex',
    wordCount: 1
  }
]
const DEFAULT_REGISTER_TYPE = REGISTER_TYPE_OPTIONS[0].key
const DEFAULT_DATA_TYPE = 'uint16_t'
const GROUP_LAYOUT_REGISTER = 'register'
const GROUP_LAYOUT_STRUCT = 'struct'

function normalizeAddress(value, fallback = 0) {
  if (typeof value === 'number') {
    return Number.isFinite(value) ? clampInteger(value, 0, MAX_MODBUS_ADDRESS, fallback) : fallback
  }

  const text = String(value === undefined || value === null ? '' : value).trim()
  if (!text) return fallback

  const hexText = text.toUpperCase().startsWith('0X') ? text.slice(2) : text
  if (/^[0-9A-F]+$/i.test(hexText)) {
    const parsedHex = parseInt(hexText, 16)
    return Number.isFinite(parsedHex) ? clampInteger(parsedHex, 0, MAX_MODBUS_ADDRESS, fallback) : fallback
  }

  const numberValue = Number(text)
  return Number.isFinite(numberValue) ? clampInteger(numberValue, 0, MAX_MODBUS_ADDRESS, fallback) : fallback
}

function parseConfigAddress(value) {
  if (typeof value === 'number') {
    return clampInteger(value, 0, MAX_MODBUS_ADDRESS, 0)
  }

  const text = String(value === undefined || value === null ? '' : value).trim()
  const hexText = text.toUpperCase().startsWith('0X') ? text.slice(2) : text

  if (!/^[0-9A-F]{1,4}$/i.test(hexText)) {
    throw new Error('寄存器起始地址无效')
  }

  return parseInt(hexText, 16)
}

function parseConfigQuantity(value, maxQuantity) {
  const text = String(value === undefined || value === null ? '' : value).trim()
  const quantity = Number(text)

  if (!Number.isInteger(quantity) || quantity < 1 || quantity > maxQuantity) {
    throw new Error(`寄存器数量需为 1 - ${maxQuantity}`)
  }

  return quantity
}

function getRegisterType(typeKey) {
  return REGISTER_TYPE_OPTIONS.find((item) => item.key === typeKey) || REGISTER_TYPE_OPTIONS[0]
}

function getRegisterTypeIndex(typeKey) {
  return Math.max(0, REGISTER_TYPE_OPTIONS.findIndex((item) => item.key === getRegisterType(typeKey).key))
}

function getDataType(dataType) {
  return DATA_TYPE_OPTIONS.find((item) => item.key === dataType)
    || DATA_TYPE_OPTIONS.find((item) => item.key === DEFAULT_DATA_TYPE)
    || DATA_TYPE_OPTIONS[0]
}

function getDataTypeIndex(dataType) {
  return Math.max(0, DATA_TYPE_OPTIONS.findIndex((item) => item.key === getDataType(dataType).key))
}

function normalizeTextByteLength(value, fallback = DEFAULT_TEXT_BYTE_LENGTH) {
  const numberValue = Number(value)
  const rounded = Number.isFinite(numberValue) ? Math.round(numberValue) : fallback

  return Math.min(Math.max(rounded, 1), MAX_TEXT_BYTE_LENGTH)
}

function alignEvenByteLength(byteLength) {
  const length = Math.max(1, Math.round(Number(byteLength) || 1))

  return length % 2 === 0 ? length : length + 1
}

function getRegisterTextByteLength(register = {}) {
  return normalizeTextByteLength(register.textByteLength, DEFAULT_TEXT_BYTE_LENGTH)
}

function isStructLayout(layout) {
  return layout === GROUP_LAYOUT_STRUCT
}

function getRegisterByteLength(dataType, register = {}) {
  const type = getDataType(dataType)
  if (type.kind === 'text') {
    const byteLength = getRegisterTextByteLength(register)

    return isStructLayout(register.layout) ? byteLength : alignEvenByteLength(byteLength)
  }

  return type.byteLength || ((type.wordCount || 1) * 2)
}

function getRegisterWordCount(dataType, register = {}) {
  return Math.max(1, Math.ceil(getRegisterByteLength(dataType, register) / 2))
}

function getRegisterWordCountAtOffset(dataType, byteOffset, register = {}) {
  const byteLength = getRegisterByteLength(dataType, register)
  return Math.max(1, Math.ceil((byteOffset + byteLength) / 2))
}

function getEncodeByteLimit(register) {
  return isTextRegister(register.dataType) ? getRegisterTextByteLength(register) : getRegisterByteLength(register.dataType, register)
}

function isTextRegister(dataType) {
  return getDataType(dataType).kind === 'text'
}

function isByteRegister(dataType) {
  const key = getDataType(dataType).key

  return key === 'int8_t' || key === 'uint8_t'
}

function isBitRegisterType(registerType) {
  return registerType === 'coil' || registerType === 'discrete'
}

function isHexRegister(dataType) {
  return getDataType(dataType).key === 'hex'
}

function isNumericRegister(dataType) {
  return getDataType(dataType).kind === 'number'
}

function supportsRange(dataType) {
  return isNumericRegister(dataType) || isHexRegister(dataType)
}

function supportsUnit(dataType) {
  return isNumericRegister(dataType)
}

function padWordHex(value) {
  return Number(value || 0).toString(16).toUpperCase().padStart(4, '0')
}

function formatRawWordText(words = []) {
  if (!Array.isArray(words) || !words.length) return '--'

  return words.map((word) => `0x${padWordHex(word)}`).join(' ')
}

function formatAddressRange(startAddress, wordCount) {
  const address = normalizeAddress(startAddress, 0)
  const count = Math.max(1, Number(wordCount) || 1)
  const endAddress = address + count - 1
  const safeEndAddress = Math.min(endAddress, MAX_MODBUS_ADDRESS)
  const overflowText = endAddress > MAX_MODBUS_ADDRESS ? '+' : ''

  if (count <= 1) return `0x${padWordHex(address)}`

  return `0x${padWordHex(address)}-0x${padWordHex(safeEndAddress)}${overflowText}`
}

function formatRegisterAddressText(address, byteOffset, byteLength, registerType) {
  if (isBitRegisterType(registerType)) return `0x${padHex(address)}`
  if (byteLength === 1) return `0x${padHex(address)}${byteOffset === 0 ? 'H' : 'L'}`

  return `0x${padHex(address)}`
}

function isAddressRangeOverflow(startAddress, wordCount) {
  const address = normalizeAddress(startAddress, 0)
  const count = Math.max(1, Number(wordCount) || 1)

  return address + count - 1 > MAX_MODBUS_ADDRESS
}

function encodeAsciiBytes(text, byteLimit = 32) {
  const bytes = []
  const stringValue = normalizeTextValue(text)

  for (let index = 0; index < stringValue.length; index += 1) {
    const code = stringValue.charCodeAt(index)
    if (code > 0x7F) {
      throw new Error('ASCII 文本只能包含 0x00 - 0x7F 字符')
    }
    bytes.push(code)
    if (bytes.length > byteLimit) break
  }

  if (bytes.length > byteLimit) {
    throw new Error(`长文本最长 ${byteLimit} 字节`)
  }

  return bytes
}

function encodeUtf8Bytes(text, byteLimit = 32) {
  const bytes = []
  const encoded = encodeURIComponent(normalizeTextValue(text))

  for (let index = 0; index < encoded.length; index += 1) {
    const char = encoded[index]
    if (char === '%') {
      const byte = parseInt(encoded.slice(index + 1, index + 3), 16)
      if (!Number.isFinite(byte)) break
      bytes.push(byte & 0xFF)
      index += 2
    } else {
      bytes.push(char.charCodeAt(0) & 0xFF)
    }
    if (bytes.length > byteLimit) break
  }

  if (bytes.length > byteLimit) {
    throw new Error(`长文本最长 ${byteLimit} 字节`)
  }

  return bytes
}

function decodeAsciiBytes(bytes = []) {
  return String.fromCharCode.apply(null, trimTrailingNullBytes(bytes).map((byte) => byte & 0xFF))
}

function decodeUtf8Bytes(bytes = []) {
  const trimmed = trimTrailingNullBytes(bytes)
  if (!trimmed.length) return ''

  let encoded = ''

  trimmed.forEach((byte) => {
    encoded += `%${(byte & 0xFF).toString(16).padStart(2, '0').toUpperCase()}`
  })

  try {
    return decodeURIComponent(encoded)
  } catch (error) {
    return decodeAsciiBytes(trimmed)
  }
}

function encodeTextBytes(text, dataType, byteLimit = MAX_TEXT_BYTE_LENGTH) {
  const normalizedType = getDataType(dataType).key

  if (normalizedType === 'ascii') return encodeAsciiBytes(text, byteLimit)
  return encodeUtf8Bytes(text, byteLimit)
}

function decodeTextBytes(bytes, dataType) {
  const normalizedType = getDataType(dataType).key

  return normalizedType === 'ascii'
    ? decodeAsciiBytes(bytes)
    : decodeUtf8Bytes(bytes)
}

function formatIntegerValue(value, dataType) {
  const type = getDataType(dataType).key
  const numberValue = Number(value)

  if (!Number.isFinite(numberValue)) return '--'
  if (type === 'int8_t') return String(((Math.round(numberValue) << 24) >> 24))
  if (type === 'uint8_t') return String(Math.round(numberValue) & 0xFF)
  if (type === 'int16_t') return String(((Math.round(numberValue) << 16) >> 16))
  if (type === 'uint16_t') return String(Math.round(numberValue) & 0xFFFF)
  if (type === 'int32_t') return String((Math.round(numberValue) | 0))
  if (type === 'uint32_t') return String(Math.round(numberValue) >>> 0)

  return String(Math.round(numberValue))
}

function formatHexValue(value) {
  const numberValue = Number(value)
  if (!Number.isFinite(numberValue)) return '--'

  return `0x${padWordHex(Math.round(numberValue) & 0xFFFF)}`
}

function formatFloatValue(value) {
  return formatFixedValue(value, 6).replace(/(\.\d*?)0+$/, '$1').replace(/\.$/, '')
}

function parseIntegerText(value) {
  const text = String(value === undefined || value === null ? '' : value).trim()
  if (!text) return null

  const isHex = /^[-+]?0x[0-9a-f]+$/i.test(text) || /^0x[0-9a-f]+$/i.test(text)
  const parsed = isHex ? parseInt(text, 16) : Number(text)

  return Number.isFinite(parsed) ? parsed : null
}

function parseHexText(value) {
  const text = String(value === undefined || value === null ? '' : value).trim()
  if (!text) return null

  const hexText = text.toUpperCase().startsWith('0X') ? text.slice(2) : text
  if (/^[0-9A-F]{1,4}$/i.test(hexText)) {
    const parsedHex = parseInt(hexText, 16)
    return Number.isFinite(parsedHex) ? parsedHex : null
  }

  return null
}

function getRegisterValueTypeLabel(dataType) {
  return getDataType(dataType).label
}

function getMaxQuantity() {
  return MAX_GENERIC_MODBUS_ITEMS
}

function parseCoilValue(value) {
  const text = String(value === undefined || value === null ? '' : value).trim()
  if (!text || text === '--') return null
  if (['1', 'true', 'TRUE', 'on', 'ON', '开'].includes(text)) return 1
  if (['0', 'false', 'FALSE', 'off', 'OFF', '关'].includes(text)) return 0

  const coilValue = Number(text)
  return Number.isFinite(coilValue) ? (coilValue ? 1 : 0) : null
}

function getNumericRange(dataType) {
  const type = getDataType(dataType).key

  if (type === 'int8_t') return { max: 127, min: -128 }
  if (type === 'uint8_t') return { max: 0xFF, min: 0 }
  if (type === 'int16_t') return { max: 32767, min: -32768 }
  if (type === 'uint16_t') return { max: 0xFFFF, min: 0 }
  if (type === 'int32_t') return { max: 2147483647, min: -2147483648 }
  if (type === 'uint32_t') return { max: 0xFFFFFFFF, min: 0 }
  if (type === 'hex') return { max: 0xFFFF, min: 0 }

  return { max: Number.POSITIVE_INFINITY, min: Number.NEGATIVE_INFINITY }
}

function parseNumberText(value, dataType) {
  const text = String(value === undefined || value === null ? '' : value).trim()
  if (!text || text === '--') return null

  if (getDataType(dataType).key === 'float') {
    const parsed = Number(text)
    return Number.isFinite(parsed) ? parsed : null
  }
  if (isHexRegister(dataType)) return parseHexText(text)

  return parseIntegerText(text)
}

function parseRangeBoundary(value, dataType, label) {
  const text = String(value === undefined || value === null ? '' : value).trim()
  if (!text) return null

  const parsed = parseNumberText(text, dataType)
  if (parsed === null) {
    throw new Error(`${label}无效`)
  }

  return parsed
}

function validateNumericValue(register, value) {
  const dataType = getDataType(register.dataType).key
  const range = getNumericRange(dataType)
  const numberValue = Number(value)
  if (!Number.isFinite(numberValue)) return false

  if (dataType !== 'float' && Math.round(numberValue) !== numberValue) {
    throw new Error(`${register.name || '寄存器'} 需要整数`)
  }
  if (numberValue < range.min || numberValue > range.max) {
    throw new Error(`${register.name || '寄存器'} 超出 ${dataType} 范围`)
  }

  const minValue = parseRangeBoundary(register.minValue, dataType, `${register.name || '寄存器'} 最小值`)
  const maxValue = parseRangeBoundary(register.maxValue, dataType, `${register.name || '寄存器'} 最大值`)
  if (minValue !== null && numberValue < minValue) {
    throw new Error(`${register.name || '寄存器'} 小于限制最小值`)
  }
  if (maxValue !== null && numberValue > maxValue) {
    throw new Error(`${register.name || '寄存器'} 大于限制最大值`)
  }

  return true
}

function floatToWords(value) {
  const buffer = new ArrayBuffer(4)
  const view = new DataView(buffer)

  view.setFloat32(0, Number(value), false)

  return [view.getUint16(0, false), view.getUint16(2, false)]
}

function wordsToFloat(words) {
  if (!Array.isArray(words) || words.length < 2) return null

  const buffer = new ArrayBuffer(4)
  const view = new DataView(buffer)

  view.setUint16(0, Number(words[0]) & 0xFFFF, false)
  view.setUint16(2, Number(words[1]) & 0xFFFF, false)

  return view.getFloat32(0, false)
}

function floatToBytes(value) {
  const buffer = new ArrayBuffer(4)
  const view = new DataView(buffer)

  view.setFloat32(0, Number(value), false)

  return [
    view.getUint8(0),
    view.getUint8(1),
    view.getUint8(2),
    view.getUint8(3)
  ]
}

function bytesToFloatValue(bytes) {
  if (!Array.isArray(bytes) || bytes.length < 4) return null

  const buffer = new ArrayBuffer(4)
  const view = new DataView(buffer)

  for (let index = 0; index < 4; index += 1) {
    view.setUint8(index, Number(bytes[index]) & 0xFF)
  }

  return view.getFloat32(0, false)
}

function unsignedIntegerToBytes(value, byteLength) {
  let numberValue = Math.round(Number(value) || 0)
  const bytes = []

  if (numberValue < 0) {
    numberValue += Math.pow(2, byteLength * 8)
  }

  for (let index = byteLength - 1; index >= 0; index -= 1) {
    bytes[index] = numberValue & 0xFF
    numberValue = Math.floor(numberValue / 0x100)
  }

  return bytes
}

function bytesToUnsignedInteger(bytes) {
  return bytes.reduce((value, byte) => ((value * 0x100) + (Number(byte) & 0xFF)), 0)
}

function bytesToSignedInteger(bytes) {
  const unsignedValue = bytesToUnsignedInteger(bytes)
  const signLimit = Math.pow(2, bytes.length * 8 - 1)
  const fullRange = Math.pow(2, bytes.length * 8)

  return unsignedValue >= signLimit ? unsignedValue - fullRange : unsignedValue
}

function getRegisterDataBytes(register, words) {
  const dataType = getDataType(register.dataType).key
  const byteLength = getRegisterByteLength(dataType, register)
  const byteOffset = Math.max(0, Math.floor(Number(register.byteOffset) || 0))
  const sourceBytes = wordsToBytes(words, Math.max(0, (Array.isArray(words) ? words.length : 0) * 2))

  return sourceBytes.slice(byteOffset, byteOffset + byteLength)
}

function encodeRegisterBytes(register) {
  const dataType = getDataType(register.dataType).key
  const valueText = normalizeTextValue(register.inputValue)
  const byteLength = getRegisterByteLength(dataType, register)

  if (isTextRegister(dataType)) {
    const byteLimit = getEncodeByteLimit(register)
    const bytes = encodeTextBytes(valueText, dataType, byteLimit)
    const paddedBytes = bytes.slice()

    while (paddedBytes.length < byteLength) {
      paddedBytes.push(0)
    }

    return paddedBytes.slice(0, byteLength)
  }

  const numberValue = parseNumberText(valueText, dataType)
  if (numberValue === null) return null
  validateNumericValue(register, numberValue)

  if (dataType === 'float') return floatToBytes(numberValue)

  const rounded = Math.round(numberValue)
  if (dataType === 'int8_t' || dataType === 'uint8_t') return [rounded & 0xFF]
  if (dataType === 'int16_t' || dataType === 'uint16_t' || dataType === 'hex') {
    return unsignedIntegerToBytes(rounded, 2)
  }
  if (dataType === 'int32_t' || dataType === 'uint32_t') {
    return unsignedIntegerToBytes(rounded, 4)
  }

  return unsignedIntegerToBytes(rounded, byteLength)
}

function encodeRegisterWords(register) {
  const dataType = getDataType(register.dataType).key
  const bytes = encodeRegisterBytes(register)

  if (!Array.isArray(bytes)) return null
  if (isByteRegister(dataType)) return [bytes[0] & 0xFF]

  return bytesToWords(bytes.length % 2 === 0 ? bytes : bytes.concat(0))
}

function decodeRegisterValue(register, words) {
  const dataType = getDataType(register.dataType).key

  if (!Array.isArray(words) || words.length < getRegisterWordCount(dataType, register)) return null

  const bytes = getRegisterDataBytes(register, words)
  const byteLength = getRegisterByteLength(dataType, register)
  if (bytes.length < byteLength) return null

  if (isTextRegister(dataType)) {
    return decodeTextBytes(bytes.slice(0, getEncodeByteLimit(register)), dataType)
  }
  if (dataType === 'float') {
    return bytesToFloatValue(bytes)
  }
  if (dataType === 'int8_t') {
    const byteValue = bytes[0] & 0xFF
    return byteValue & 0x80 ? byteValue - 0x100 : byteValue
  }
  if (dataType === 'uint8_t') {
    return bytes[0] & 0xFF
  }
  if (dataType === 'int16_t') {
    return bytesToSignedInteger(bytes.slice(0, 2))
  }
  if (dataType === 'uint16_t') {
    return bytesToUnsignedInteger(bytes.slice(0, 2))
  }
  if (dataType === 'hex') {
    return bytesToUnsignedInteger(bytes.slice(0, 2))
  }

  if (dataType === 'int32_t') {
    return bytesToSignedInteger(bytes.slice(0, 4))
  }

  return bytesToUnsignedInteger(bytes.slice(0, 4))
}

function formatRegisterValue(register, rawValue) {
  if (rawValue === null || rawValue === undefined) return '--'

  const dataType = getDataType(register.dataType).key
  if (isTextRegister(dataType)) return normalizeTextValue(rawValue)
  if (dataType === 'hex') return formatHexValue(rawValue)
  if (dataType === 'float') return formatFloatValue(rawValue)

  return formatIntegerValue(rawValue, dataType)
}

function formatCoilDisplayValue(value) {
  return Number(value) ? '1' : '0'
}

function getRegisterSavedValue(register) {
  if (register.inputValue !== undefined && register.inputValue !== null) return normalizeTextValue(register.inputValue)
  if (register.value !== undefined && register.value !== null) return normalizeTextValue(register.value)

  return null
}

function normalizeRegisterDataType(register, registerType) {
  if (isBitRegisterType(registerType)) return DEFAULT_DATA_TYPE

  return getDataType(register.dataType || register.type || DEFAULT_DATA_TYPE).key
}

function normalizeRegister(register, group, index, address, byteOffset = 0) {
  const registerType = getRegisterType(group.registerType).key
  const layout = isStructLayout(group.layout) ? GROUP_LAYOUT_STRUCT : GROUP_LAYOUT_REGISTER
  const dataType = normalizeRegisterDataType(register, registerType)
  const textByteLength = isTextRegister(dataType)
    ? normalizeTextByteLength(register.textByteLength, DEFAULT_TEXT_BYTE_LENGTH)
    : ''
  const defaultValue = normalizeTextValue(register.defaultValue)
  const savedValue = getRegisterSavedValue(register)
  const inputValue = savedValue === null ? defaultValue : savedValue
  const rawValue = register.rawValue === undefined ? null : register.rawValue
  const byteLength = isBitRegisterType(registerType) ? 1 : getRegisterByteLength(dataType, { layout, textByteLength })
  const registerCount = isBitRegisterType(registerType) ? 1 : getRegisterWordCountAtOffset(dataType, byteOffset, { layout, textByteLength })
  const canShowUnit = !isBitRegisterType(registerType) && supportsUnit(dataType)
  const rawWords = Array.isArray(register.rawWords)
    ? register.rawWords.slice(0, registerCount).map((word) => Number(word) & 0xFFFF)
    : []
  const rawValueText = rawValue === null
    ? '--'
    : (isBitRegisterType(registerType) ? formatCoilDisplayValue(rawValue) : formatRawWordText(rawWords))
  const displayValue = rawValue === null
    ? (inputValue.trim() ? inputValue : '--')
    : formatRegisterValue({ ...register, dataType, byteOffset }, rawValue)

  return {
    address,
    addressRangeText: isBitRegisterType(registerType)
      ? `0x${padHex(address)}`
      : formatAddressRange(address, registerCount),
    addressText: formatRegisterAddressText(address, byteOffset, byteLength, registerType),
    byteLength,
    byteLengthText: isBitRegisterType(registerType)
      ? '1bit'
      : (textByteLength && textByteLength !== byteLength ? `${textByteLength}B/占${byteLength}B` : `${byteLength}B`),
    byteStart: Math.max(0, Math.floor(Number(register.byteStart) || 0)),
    dataType,
    dataTypeIndex: getDataTypeIndex(dataType),
    dataTypeText: getRegisterValueTypeLabel(dataType),
    defaultValue,
    displayValue,
    id: register.id || createId('gm-reg'),
    inputType: isTextRegister(dataType) ? 'text' : 'text',
    inputValue,
    isStructField: layout === GROUP_LAYOUT_STRUCT || !!register.isStructField,
    layout,
    isDirty: !!register.isDirty,
    maxValue: normalizeTextValue(register.maxValue),
    minValue: normalizeTextValue(register.minValue),
    name: register.name || `寄存器 ${index + 1}`,
    rawValue,
    rawValueText,
    rawWords,
    registerCount,
    byteOffset,
    registerType,
    showDataType: !isBitRegisterType(registerType),
    showRange: !isBitRegisterType(registerType) && supportsRange(dataType),
    showTextLength: !isBitRegisterType(registerType) && isTextRegister(dataType),
    showUnit: canShowUnit,
    textByteLength,
    unit: canShowUnit ? normalizeTextValue(register.unit).trim() : '',
    remark: register.remark || ''
  }
}

function normalizeGroup(group) {
  const registerType = getRegisterType(group.registerType || group.type || DEFAULT_REGISTER_TYPE)
  const startAddress = normalizeAddress(group.startAddress, 0)
  const maxQuantity = getMaxQuantity(registerType.key)
  const sourceRegisters = Array.isArray(group.registers) ? group.registers : []
  const layout = isStructLayout(group.layout) ? GROUP_LAYOUT_STRUCT : GROUP_LAYOUT_REGISTER
  const hasExplicitQuantity = group.quantity !== undefined && group.quantity !== null && group.quantity !== ''
  const quantity = hasExplicitQuantity
    ? clampInteger(group.quantity, 1, maxQuantity, 1)
    : clampInteger(sourceRegisters.length || 1, 1, maxQuantity, 1)
  const baseGroup = {
    deleteVisible: !!group.deleteVisible,
    expanded: group.expanded === true,
    id: group.id || createId('gm-group'),
    isStructLayout: layout === GROUP_LAYOUT_STRUCT,
    layout,
    name: String(group.name || group.groupName || '寄存器组').trim() || '寄存器组',
    quantity,
    registerType: registerType.key,
    startAddress,
    touchStartX: 0
  }
  const registers = []
  let nextAddress = startAddress
  let nextByteOffset = 0

  for (let index = 0; index < quantity; index += 1) {
    const sourceRegister = sourceRegisters[index] || {}
    let normalizedSourceRegister = sourceRegister
    const dataType = normalizeRegisterDataType(sourceRegister, baseGroup.registerType)
    const textByteLength = isTextRegister(dataType)
      ? normalizeTextByteLength(sourceRegister.textByteLength, DEFAULT_TEXT_BYTE_LENGTH)
      : ''
    const isBitRegister = isBitRegisterType(baseGroup.registerType)
    let address = startAddress + index
    let byteOffset = 0

    if (!isBitRegister) {
      const byteLength = getRegisterByteLength(dataType, { layout, textByteLength })
      if (layout !== GROUP_LAYOUT_STRUCT && !isByteRegister(dataType) && nextByteOffset % 2 !== 0) {
        nextByteOffset += 1
      }

      address = startAddress + Math.floor(nextByteOffset / 2)
      byteOffset = nextByteOffset % 2
      normalizedSourceRegister = {
        ...sourceRegister,
        byteStart: nextByteOffset
      }
      nextByteOffset += byteLength
    }

    const register = normalizeRegister(normalizedSourceRegister, baseGroup, index, address, byteOffset)
    registers.push(register)
    if (isBitRegister) nextAddress += register.registerCount
  }

  const byteLength = isBitRegisterType(baseGroup.registerType)
    ? Math.max(1, nextAddress - startAddress)
    : Math.max(1, nextByteOffset)
  const paddedByteLength = isBitRegisterType(baseGroup.registerType)
    ? byteLength
    : alignEvenByteLength(byteLength)
  const wordQuantity = isBitRegisterType(baseGroup.registerType)
    ? Math.max(1, nextAddress - startAddress)
    : Math.max(1, paddedByteLength / 2)
  const addressOverflow = isAddressRangeOverflow(startAddress, wordQuantity)
  const endAddress = startAddress + wordQuantity - 1

  return {
    ...baseGroup,
    addressRangeText: formatAddressRange(startAddress, wordQuantity),
    addressOverflow,
    addressWarningText: addressOverflow ? '地址超出 0xFFFF' : '',
    endAddressText: addressOverflow ? `0x${padHex(MAX_MODBUS_ADDRESS)}+` : `0x${padHex(endAddress)}`,
    functionCode: registerType.functionCode,
    isReadOnly: !registerType.writable,
    byteLength,
    maxQuantity,
    registerTypeIndex: getRegisterTypeIndex(registerType.key),
    registerTypeText: registerType.label,
    registers,
    paddedByteLength,
    startAddressText: `0x${padHex(startAddress)}`,
    wordQuantity,
    writable: registerType.writable
  }
}

function normalizeGroupConfig(config = {}) {
  const registerType = config.registerTypeIndex !== undefined && config.registerTypeIndex !== null
    ? (REGISTER_TYPE_OPTIONS[Number(config.registerTypeIndex)] || REGISTER_TYPE_OPTIONS[0])
    : getRegisterType(config.registerType || config.type || DEFAULT_REGISTER_TYPE)
  const maxQuantity = getMaxQuantity(registerType.key)

  return {
    layout: isStructLayout(config.layout) ? GROUP_LAYOUT_STRUCT : GROUP_LAYOUT_REGISTER,
    name: String(config.name || config.groupName || '寄存器组').trim() || '寄存器组',
    quantity: parseConfigQuantity(config.quantity, maxQuantity),
    registerType: registerType.key,
    startAddress: parseConfigAddress(config.startAddress)
  }
}

function getRegisterJsonValue(register) {
  if (register.inputValue !== undefined && register.inputValue !== null) {
    return normalizeTextValue(register.inputValue)
  }

  if (register.defaultValue !== undefined && register.defaultValue !== null) {
    return normalizeTextValue(register.defaultValue)
  }

  if (register.displayValue !== undefined && register.displayValue !== null && register.displayValue !== '--') {
    return normalizeTextValue(register.displayValue)
  }

  return ''
}

function normalizeImportedRegisterDataType(register) {
  const dataType = register.dataType || register.type || DEFAULT_DATA_TYPE

  return getDataType(dataType).key
}

function cloneImportedGroup(group) {
  return {
    layout: isStructLayout(group.layout) ? GROUP_LAYOUT_STRUCT : GROUP_LAYOUT_REGISTER,
    name: group.name,
    quantity: group.quantity,
    registerType: group.registerType || group.type || DEFAULT_REGISTER_TYPE,
    registers: (Array.isArray(group.registers) ? group.registers : []).map((register) => ({
      dataType: normalizeImportedRegisterDataType(register),
      defaultValue: register.defaultValue,
      inputValue: register.inputValue,
      maxValue: register.maxValue,
      minValue: register.minValue,
      name: register.name,
      isStructField: !!register.isStructField,
      textByteLength: register.textByteLength,
      remark: register.remark,
      unit: register.unit,
      value: register.value
    })),
    startAddress: group.startAddress
  }
}

function splitWordSpans(startAddress, quantity, maxQuantity) {
  const spans = []
  let address = normalizeAddress(startAddress, 0)
  let remaining = Math.max(0, Math.floor(Number(quantity) || 0))

  while (remaining > 0) {
    const spanQuantity = Math.min(remaining, maxQuantity)

    spans.push({
      address,
      quantity: spanQuantity
    })

    address += spanQuantity
    remaining -= spanQuantity
  }

  return spans
}

function getRegisterWriteValueText(register) {
  if (register.inputValue !== undefined && register.inputValue !== null) return normalizeTextValue(register.inputValue)
  if (register.defaultValue !== undefined && register.defaultValue !== null) return normalizeTextValue(register.defaultValue)

  return ''
}

function getRegisterWordsFromWordCache(register, wordCache) {
  const words = []
  for (let offset = 0; offset < register.registerCount; offset += 1) {
    const word = wordCache[register.address + offset]
    if (word === undefined) return null
    words.push(word)
  }

  return words
}

function decodeRegisterFromWordCache(register, wordCache) {
  const words = getRegisterWordsFromWordCache(register, wordCache)
  if (!words) return null

  return decodeRegisterValue(register, words)
}

function getRegisterEncodedWords(register) {
  return encodeRegisterWords({
    ...register,
    inputValue: getRegisterWriteValueText(register)
  })
}

function getRegisterByteStart(register, groupStartAddress = 0) {
  if (Number.isFinite(Number(register.byteStart))) {
    return Math.max(0, Math.floor(Number(register.byteStart)))
  }

  return Math.max(0, ((Number(register.address) || 0) - (Number(groupStartAddress) || 0)) * 2 + (Number(register.byteOffset) || 0))
}

function getGroupEncodedWords(group) {
  const byteLength = Math.max(2, Number(group && group.paddedByteLength) || ((Number(group && group.wordQuantity) || 1) * 2))
  const bytes = Array.from({ length: byteLength }, () => 0)
  const registers = Array.isArray(group && group.registers) ? group.registers : []

  registers.forEach((register) => {
    const registerBytes = encodeRegisterBytes(register)
    if (!Array.isArray(registerBytes) || !registerBytes.length) {
      throw new Error(`${register.name || '寄存器'} 没有有效写入值`)
    }

    const byteStart = getRegisterByteStart(register, group.startAddress)
    for (let offset = 0; offset < register.byteLength; offset += 1) {
      if (byteStart + offset < bytes.length) {
        bytes[byteStart + offset] = Number(registerBytes[offset] || 0) & 0xFF
      }
    }
  })

  return bytesToWords(bytes)
}

function validateRegisterValue(register, value) {
  const valueText = normalizeTextValue(
    value === undefined || value === null ? getRegisterWriteValueText(register) : value
  ).trim()
  if (!valueText || valueText === '--') return true

  if (registerTypeIsBit(register)) {
    if (parseCoilValue(valueText) === null) {
      throw new Error(`${register.name || '线圈'} 只能填写 0 或 1`)
    }
    return true
  }

  const dataType = getDataType(register.dataType).key
  if (isTextRegister(dataType)) {
    encodeTextBytes(valueText, dataType, getEncodeByteLimit(register))
    return true
  }

  const numberValue = parseNumberText(valueText, dataType)
  if (numberValue === null) {
    throw new Error(`${register.name || '寄存器'} 输入值无效`)
  }

  return validateNumericValue(register, numberValue)
}

function registerTypeIsBit(register) {
  return !!register && isBitRegisterType(register.registerType)
}

module.exports = {
  DATA_TYPE_OPTIONS,
  DEFAULT_DATA_TYPE,
  DEFAULT_REGISTER_TYPE,
  GROUP_LAYOUT_REGISTER,
  GROUP_LAYOUT_STRUCT,
  MAX_MODBUS_ADDRESS,
  REGISTER_TYPE_OPTIONS,
  cloneImportedGroup,
  decodeRegisterFromWordCache,
  decodeRegisterValue,
  formatCoilDisplayValue,
  formatRegisterValue,
  getDataType,
  getRegisterEncodedWords,
  getGroupEncodedWords,
  getRegisterWordCount,
  getRegisterJsonValue,
  getRegisterWordsFromWordCache,
  getRegisterWriteValueText,
  isAddressRangeOverflow,
  isBitRegisterType,
  isByteRegister,
  isTextRegister,
  normalizeGroup,
  normalizeGroupConfig,
  normalizeRegister,
  parseCoilValue,
  registerTypeIsBit,
  splitWordSpans,
  validateRegisterValue
}
