const {
  normalizeTextValue
} = require('../../utils/base-utils.js')
const {
  bytesToWords
} = require('../../utils/binary-utils.js')
const {
  MAX_MODBUS_ADDRESS
} = require('./constants.js')
const {
  decodeRegisterValue,
  encodeBitFieldIntoBytes,
  encodeRegisterBytes,
  encodeRegisterWords,
  isBitFieldRegister,
  validateRegisterValue: validateCodecRegisterValue
} = require('./value-codec.js')

function normalizeAddress(value, fallback = 0) {
  if (typeof value === 'number') {
    return Number.isFinite(value) ? Math.max(0, Math.min(Math.round(value), MAX_MODBUS_ADDRESS)) : 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) ? Math.max(0, Math.min(parsedHex, MAX_MODBUS_ADDRESS)) : fallback
  }

  const numberValue = Number(text)
  return Number.isFinite(numberValue) ? Math.max(0, Math.min(Math.round(numberValue), MAX_MODBUS_ADDRESS)) : fallback
}

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 getRegisterBytesFromByteCache(register, byteCache) {
  const bytes = []
  for (let offset = 0; offset < register.byteLength; offset += 1) {
    const byte = byteCache[register.address + offset]
    if (byte === undefined) return null
    bytes.push(Number(byte) & 0xFF)
  }

  return bytes
}

function getRegisterWordsFromByteCache(register, byteCache) {
  const bytes = getRegisterBytesFromByteCache(register, byteCache)
  if (!bytes) return null

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

function decodeRegisterFromBytes(register, bytes) {
  if (!Array.isArray(bytes) || bytes.length < register.byteLength) return null

  return decodeRegisterValue(
    {
      ...register,
      byteOffset: 0,
      memoryEndian: register.memoryEndian
    },
    bytesToWords(bytes.length % 2 === 0 ? bytes : bytes.concat(0))
  )
}

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

  return decodeRegisterValue(register, words)
}

function decodeRegisterFromByteCache(register, byteCache) {
  const bytes = getRegisterBytesFromByteCache(register, byteCache)
  if (!bytes) return null

  return decodeRegisterFromBytes(register, bytes)
}

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

function getRegisterEncodedBytes(register) {
  return encodeRegisterBytes({
    ...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 getGroupEncodedBytes(group, baseBytes = null) {
  const byteLength = Math.max(2, Number(group && group.paddedByteLength) || ((Number(group && group.wordQuantity) || 1) * 2))
  const bytes = Array.isArray(baseBytes)
    ? baseBytes.slice(0, byteLength).map((byte) => Number(byte) & 0xFF)
    : []
  const registers = Array.isArray(group && group.registers) ? group.registers : []

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

  registers.forEach((register) => {
    const byteStart = getRegisterByteStart(register, group.startAddress)

    if (isBitFieldRegister(register)) {
      const encodedBytes = encodeBitFieldIntoBytes(
        {
          ...register,
          inputValue: getRegisterWriteValueText(register)
        },
        bytes,
        byteStart
      )

      if (!Array.isArray(encodedBytes)) {
        throw new Error(`${register.name || '位域'} 没有有效写入值`)
      }
      return
    }

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

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

  return bytes
}

function getGroupEncodedWords(group, baseBytes = null) {
  return bytesToWords(getGroupEncodedBytes(group, baseBytes))
}

function validateRegisterValue(register, value) {
  return validateCodecRegisterValue(
    register,
    value === undefined || value === null ? getRegisterWriteValueText(register) : value
  )
}

module.exports = {
  decodeRegisterFromByteCache,
  decodeRegisterFromBytes,
  decodeRegisterFromWordCache,
  getGroupEncodedBytes,
  getGroupEncodedWords,
  getRegisterBytesFromByteCache,
  getRegisterEncodedBytes,
  getRegisterEncodedWords,
  getRegisterWordsFromByteCache,
  getRegisterWordsFromWordCache,
  getRegisterWriteValueText,
  splitWordSpans,
  validateRegisterValue
}
