const {
  normalizeTextValue
} = require('../../utils/base-utils.js')
const {
  bytesToWords,
  wordsToBytes
} = require('../../utils/binary-utils.js')
const {
  alignEvenByteLength,
  getBitFieldByteLength,
  getBitFieldMaxValue,
  getDataType,
  getDataTypeIndex,
  getEncodeByteLimit,
  getRegisterByteLength,
  getRegisterTextByteLength,
  getRegisterValueTypeLabel,
  getRegisterWordCount,
  getRegisterWordCountAtOffset,
  isBitFieldRegister,
  isBitRegisterType,
  isByteRegister,
  isHexRegister,
  isNumericRegister,
  isRawRegister,
  isTextRegister,
  normalizeBitOffset,
  normalizeBitWidth,
  normalizeTextByteLength,
  supportsRange,
  supportsUnit
} = require('./value-types.js')
const {
  decodeTextBytes,
  encodeTextBytes
} = require('./value-text.js')
const {
  bytesToFloatValue,
  bytesToSignedInteger,
  bytesToUnsignedInteger,
  floatToBytes,
  formatFloatValue,
  formatHexValue,
  formatIntegerValue,
  parseNumberText,
  unsignedIntegerToBytes,
  validateNumericValue
} = require('./value-number.js')

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 formatRawByteText(bytes = []) {
  if (!Array.isArray(bytes) || !bytes.length) return '--'

  return bytes.map((byte) => `0x${(Number(byte) & 0xFF).toString(16).toUpperCase().padStart(2, '0')}`).join(' ')
}

function formatRawByteTextWithDefault(bytes = [], byteLength = 1) {
  const safeLength = Math.max(1, Math.floor(Number(byteLength) || 1))
  const source = Array.isArray(bytes) ? bytes : []

  return Array.from({ length: safeLength }, (_, index) => (
    `0x${(Number(source[index] || 0) & 0xFF).toString(16).toUpperCase().padStart(2, '0')}`
  )).join(' ')
}

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 normalizeEnumOptions(register = {}) {
  return (Array.isArray(register.enumOptions) ? register.enumOptions : [])
    .map((option) => ({
      label: String(option && (option.label || option.name) || '').trim(),
      name: String(option && (option.name || option.label) || '').trim(),
      value: Number(option && option.value)
    }))
    .filter((option) => option.label && Number.isFinite(option.value))
}

function findEnumOptionByText(register, valueText) {
  const text = normalizeTextValue(valueText).trim()
  if (!text) return null

  const normalizedText = text.toLowerCase()

  return normalizeEnumOptions(register).find((option) => (
    option.name.toLowerCase() === normalizedText
    || option.label.toLowerCase() === normalizedText
    || `${option.label}(${option.value})`.toLowerCase() === normalizedText
    || `${option.label} (${option.value})`.toLowerCase() === normalizedText
    || `${option.label}（${option.value}）`.toLowerCase() === normalizedText
  )) || null
}

function parseEnumValueText(register, valueText) {
  const option = findEnumOptionByText(register, valueText)

  return option ? option.value : null
}

function formatEnumValue(register, rawValue) {
  const numberValue = Number(rawValue)
  if (!Number.isFinite(numberValue)) return ''

  const option = normalizeEnumOptions(register).find((item) => Number(item.value) === numberValue)
  if (!option) return ''

  return `${option.label} (${numberValue})`
}

function parseBitFieldValue(register, valueText) {
  const text = normalizeTextValue(valueText).trim()
  const bitWidth = normalizeBitWidth(register.bitWidth)
  let parsed = parseEnumValueText(register, text)
  if (parsed === null) parsed = parseNumberText(text, 'uint32_t')
  const maxValue = getBitFieldMaxValue(register)

  if (parsed === null && bitWidth === 1) parsed = parseCoilValue(text)
  if (parsed === null) return null
  if (Math.round(parsed) !== parsed || parsed < 0 || parsed > maxValue) {
    throw new Error(`${register.name || '位域'} 超出 0 - ${maxValue} 范围`)
  }

  return Math.round(parsed)
}

function decodeBitFieldBytes(register, bytes = []) {
  const bitOffset = normalizeBitOffset(register.bitOffset)
  const bitWidth = normalizeBitWidth(register.bitWidth)
  let byteIndex = 0
  let currentBitOffset = bitOffset
  let multiplier = 1
  let remaining = bitWidth
  let value = 0

  while (remaining > 0 && byteIndex < bytes.length) {
    const take = Math.min(8 - currentBitOffset, remaining)
    const mask = (1 << take) - 1
    const part = ((Number(bytes[byteIndex]) & 0xFF) >> currentBitOffset) & mask

    value += part * multiplier
    multiplier *= Math.pow(2, take)
    remaining -= take
    byteIndex += 1
    currentBitOffset = 0
  }

  return remaining > 0 ? null : value
}

function encodeBitFieldIntoBytes(register, bytes, byteStart = 0) {
  const valueText = normalizeTextValue(register.inputValue)
  let value = parseBitFieldValue(register, valueText)
  const bitOffset = normalizeBitOffset(register.bitOffset)
  const bitWidth = normalizeBitWidth(register.bitWidth)
  let byteIndex = Math.max(0, Math.floor(Number(byteStart) || 0))
  let currentBitOffset = bitOffset
  let remaining = bitWidth

  if (value === null) return null

  while (remaining > 0) {
    const take = Math.min(8 - currentBitOffset, remaining)
    const mask = (1 << take) - 1
    const shiftedMask = (mask << currentBitOffset) & 0xFF
    const part = value & mask

    if (byteIndex >= bytes.length) return null
    bytes[byteIndex] = ((Number(bytes[byteIndex]) & 0xFF) & (~shiftedMask & 0xFF))
      | ((part << currentBitOffset) & shiftedMask)

    value = Math.floor(value / Math.pow(2, take))
    remaining -= take
    byteIndex += 1
    currentBitOffset = 0
  }

  return bytes
}

function encodeBitFieldBytes(register) {
  const bytes = Array.from({ length: getBitFieldByteLength(register) }, () => 0)

  return encodeBitFieldIntoBytes(register, bytes, 0)
}

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)
  const memoryEndian = register.memoryEndian || 'big'

  if (isBitFieldRegister(register)) {
    return encodeBitFieldBytes(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)
  }
  if (isRawRegister(dataType)) return null

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

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

  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, memoryEndian)
  }
  if (dataType === 'int32_t' || dataType === 'uint32_t') {
    return unsignedIntegerToBytes(rounded, 4, memoryEndian)
  }

  return unsignedIntegerToBytes(rounded, byteLength, memoryEndian)
}

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
  const memoryEndian = register.memoryEndian || 'big'

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

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

  if (isBitFieldRegister(register)) {
    return decodeBitFieldBytes(register, bytes)
  }

  if (isTextRegister(dataType)) {
    return decodeTextBytes(bytes.slice(0, getEncodeByteLimit(register)), dataType)
  }
  if (isRawRegister(dataType)) {
    return bytes.slice(0, byteLength)
  }
  if (dataType === 'float') {
    return bytesToFloatValue(bytes, memoryEndian)
  }
  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), memoryEndian)
  }
  if (dataType === 'uint16_t') {
    return bytesToUnsignedInteger(bytes.slice(0, 2), memoryEndian)
  }
  if (dataType === 'hex') {
    return bytesToUnsignedInteger(bytes.slice(0, 2), memoryEndian)
  }

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

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

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

  const dataType = getDataType(register.dataType).key
  if (isRawRegister(dataType)) return formatRawByteText(Array.isArray(rawValue) ? rawValue : [])

  const enumValue = formatEnumValue(register, rawValue)
  if (enumValue) return enumValue

  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 registerTypeIsBit(register) {
  return !!register && isBitRegisterType(register.registerType)
}

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

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

  if (isBitFieldRegister(register)) {
    if (parseBitFieldValue(register, valueText) === null) {
      throw new Error(`${register.name || '位域'} 输入值无效`)
    }
    return true
  }

  const dataType = getDataType(register.dataType).key
  if (isRawRegister(dataType)) {
    throw new Error(`${register.name || '寄存器'} 需要先配置数据类型`)
  }
  if (isTextRegister(dataType)) {
    encodeTextBytes(valueText, dataType, getEncodeByteLimit(register))
    return true
  }

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

  return validateNumericValue(register, numberValue)
}

module.exports = {
  alignEvenByteLength,
  decodeRegisterValue,
  encodeBitFieldIntoBytes,
  encodeRegisterBytes,
  encodeRegisterWords,
  encodeTextBytes,
  formatCoilDisplayValue,
  formatRawByteText,
  formatRawByteTextWithDefault,
  formatRawWordText,
  formatRegisterValue,
  normalizeEnumOptions,
  parseEnumValueText,
  getBitFieldByteLength,
  getBitFieldMaxValue,
  getDataType,
  getDataTypeIndex,
  getEncodeByteLimit,
  getRegisterByteLength,
  getRegisterTextByteLength,
  getRegisterValueTypeLabel,
  getRegisterWordCount,
  getRegisterWordCountAtOffset,
  isBitFieldRegister,
  isBitRegisterType,
  isByteRegister,
  isHexRegister,
  isNumericRegister,
  isRawRegister,
  isTextRegister,
  normalizeBitOffset,
  normalizeBitWidth,
  normalizeTextByteLength,
  parseCoilValue,
  parseNumberText,
  registerTypeIsBit,
  supportsRange,
  supportsUnit,
  validateRegisterValue
}
