const {
  bytesToAutoText,
  bytesToHex,
  formatHexNumber,
  readUint16BE,
  readUint16LE,
  readUint32BE,
  readUint32LE,
  trimTrailingNullBytes
} = require('../../utils/binary-utils.js')

const CODE_INFO_TLV_HEADER_BYTE_LENGTH = 2
const CODE_INFO_ENTRY_NAME_LENGTH_BYTE_LENGTH = 1
const CODE_INFO_ENTRY_MAX_NAME_BYTE_LENGTH = 0xFF
const CODE_INFO_TLV = {
  RAW_VARIABLE: 0x01,
  INT8_VARIABLE: 0x02,
  UINT8_VARIABLE: 0x03,
  INT16_VARIABLE: 0x04,
  UINT16_VARIABLE: 0x05,
  INT32_VARIABLE: 0x06,
  UINT32_VARIABLE: 0x07,
  FLOAT32_VARIABLE: 0x08,
  DOUBLE_VARIABLE: 0x09,
  INT64_VARIABLE: 0x0A,
  UINT64_VARIABLE: 0x0B,
  INT128_VARIABLE: 0x0C,
  UINT128_VARIABLE: 0x0D,
  INT256_VARIABLE: 0x0E,
  UINT256_VARIABLE: 0x0F,
  RAW_ARRAY: 0x10,
  INT8_ARRAY: 0x11,
  UINT8_ARRAY: 0x12,
  INT16_ARRAY: 0x13,
  UINT16_ARRAY: 0x14,
  INT32_ARRAY: 0x15,
  UINT32_ARRAY: 0x16,
  FLOAT32_ARRAY: 0x17,
  DOUBLE_ARRAY: 0x18,
  INT64_ARRAY: 0x19,
  UINT64_ARRAY: 0x1A,
  INT128_ARRAY: 0x1B,
  UINT128_ARRAY: 0x1C,
  INT256_ARRAY: 0x1D,
  UINT256_ARRAY: 0x1E,
  TEXT_ARRAY: 0x1F,
  ENUM_VARIABLE: 0x20,
  STRUCT_INSTANCE: 0x21,
  ENUM_ARRAY: 0x22,
  STRUCT_ARRAY: 0x23,
  CAVE_FREQ: 0x40,
  REF_VOLT: 0x41,
  AMP_GAIN: 0x42,
  RS_SHUNT: 0x43,
  BUS_DIV: 0x44,
  ALONG_DIV: 0x45,
  CHIP_MODEL: 0x46,
  MODEL: 0x47
}
const CODE_INFO_BOARD_TLV_NAMES = {
  [CODE_INFO_TLV.CAVE_FREQ]: 'cave_freq',
  [CODE_INFO_TLV.REF_VOLT]: 'ref_volt',
  [CODE_INFO_TLV.AMP_GAIN]: 'amp_gain',
  [CODE_INFO_TLV.RS_SHUNT]: 'rs_shunt',
  [CODE_INFO_TLV.BUS_DIV]: 'bus_div',
  [CODE_INFO_TLV.ALONG_DIV]: 'along_div',
  [CODE_INFO_TLV.CHIP_MODEL]: 'chip_model',
  [CODE_INFO_TLV.MODEL]: 'model'
}
const CODE_INFO_ENTRY_KIND = {
  STRUCT: 0x00,
  VARIABLE: 0x01,
  ENUM: 0x02,
  ARRAY: 0x03
}
const CODE_INFO_ENTRY_KIND_TEXT = {
  [CODE_INFO_ENTRY_KIND.STRUCT]: 'struct',
  [CODE_INFO_ENTRY_KIND.VARIABLE]: 'variable',
  [CODE_INFO_ENTRY_KIND.ENUM]: 'enum',
  [CODE_INFO_ENTRY_KIND.ARRAY]: 'array'
}
const CODE_INFO_ENTRY_NAME_ROLE = {
  DEFINITION: 'definition',
  INSTANCE: 'instance',
  VARIABLE: 'variable'
}
const CODE_INFO_ENTRY_TYPE_DEFINITIONS = {
  [CODE_INFO_TLV.RAW_VARIABLE]: {
    dataType: 'raw',
    entryKind: CODE_INFO_ENTRY_KIND.VARIABLE,
    name: 'raw_variable',
    valueTypeName: 'raw'
  },
  [CODE_INFO_TLV.INT8_VARIABLE]: {
    dataType: 'int8_t',
    entryKind: CODE_INFO_ENTRY_KIND.VARIABLE,
    expectedByteLength: 1,
    name: 'int8_variable',
    valueTypeName: 'int8_t'
  },
  [CODE_INFO_TLV.UINT8_VARIABLE]: {
    dataType: 'uint8_t',
    entryKind: CODE_INFO_ENTRY_KIND.VARIABLE,
    expectedByteLength: 1,
    name: 'uint8_variable',
    valueTypeName: 'uint8_t'
  },
  [CODE_INFO_TLV.INT16_VARIABLE]: {
    dataType: 'int16_t',
    entryKind: CODE_INFO_ENTRY_KIND.VARIABLE,
    expectedByteLength: 2,
    name: 'int16_variable',
    valueTypeName: 'int16_t'
  },
  [CODE_INFO_TLV.UINT16_VARIABLE]: {
    dataType: 'uint16_t',
    entryKind: CODE_INFO_ENTRY_KIND.VARIABLE,
    expectedByteLength: 2,
    name: 'uint16_variable',
    valueTypeName: 'uint16_t'
  },
  [CODE_INFO_TLV.INT32_VARIABLE]: {
    dataType: 'int32_t',
    entryKind: CODE_INFO_ENTRY_KIND.VARIABLE,
    expectedByteLength: 4,
    name: 'int32_variable',
    valueTypeName: 'int32_t'
  },
  [CODE_INFO_TLV.UINT32_VARIABLE]: {
    dataType: 'uint32_t',
    entryKind: CODE_INFO_ENTRY_KIND.VARIABLE,
    expectedByteLength: 4,
    name: 'uint32_variable',
    valueTypeName: 'uint32_t'
  },
  [CODE_INFO_TLV.FLOAT32_VARIABLE]: {
    dataType: 'float',
    entryKind: CODE_INFO_ENTRY_KIND.VARIABLE,
    expectedByteLength: 4,
    name: 'float32_variable',
    valueTypeName: 'float32'
  },
  [CODE_INFO_TLV.DOUBLE_VARIABLE]: {
    dataType: 'double',
    entryKind: CODE_INFO_ENTRY_KIND.VARIABLE,
    expectedByteLength: 8,
    name: 'double_variable',
    valueTypeName: 'double'
  },
  [CODE_INFO_TLV.INT64_VARIABLE]: {
    dataType: 'int64_t',
    entryKind: CODE_INFO_ENTRY_KIND.VARIABLE,
    expectedByteLength: 8,
    name: 'int64_variable',
    valueTypeName: 'int64_t'
  },
  [CODE_INFO_TLV.UINT64_VARIABLE]: {
    dataType: 'uint64_t',
    entryKind: CODE_INFO_ENTRY_KIND.VARIABLE,
    expectedByteLength: 8,
    name: 'uint64_variable',
    valueTypeName: 'uint64_t'
  },
  [CODE_INFO_TLV.INT128_VARIABLE]: {
    dataType: 'int128_t',
    entryKind: CODE_INFO_ENTRY_KIND.VARIABLE,
    expectedByteLength: 16,
    name: 'int128_variable',
    valueTypeName: 'int128_t'
  },
  [CODE_INFO_TLV.UINT128_VARIABLE]: {
    dataType: 'uint128_t',
    entryKind: CODE_INFO_ENTRY_KIND.VARIABLE,
    expectedByteLength: 16,
    name: 'uint128_variable',
    valueTypeName: 'uint128_t'
  },
  [CODE_INFO_TLV.INT256_VARIABLE]: {
    dataType: 'int256_t',
    entryKind: CODE_INFO_ENTRY_KIND.VARIABLE,
    expectedByteLength: 32,
    name: 'int256_variable',
    valueTypeName: 'int256_t'
  },
  [CODE_INFO_TLV.UINT256_VARIABLE]: {
    dataType: 'uint256_t',
    entryKind: CODE_INFO_ENTRY_KIND.VARIABLE,
    expectedByteLength: 32,
    name: 'uint256_variable',
    valueTypeName: 'uint256_t'
  },
  [CODE_INFO_TLV.ENUM_VARIABLE]: {
    dataType: '',
    entryKind: CODE_INFO_ENTRY_KIND.ENUM,
    name: 'enum_variable',
    valueTypeName: 'enum'
  },
  [CODE_INFO_TLV.STRUCT_INSTANCE]: {
    dataType: 'raw',
    entryKind: CODE_INFO_ENTRY_KIND.STRUCT,
    name: 'struct_instance',
    valueTypeName: 'struct'
  },
  [CODE_INFO_TLV.RAW_ARRAY]: {
    dataType: 'raw',
    entryKind: CODE_INFO_ENTRY_KIND.ARRAY,
    name: 'raw_array',
    valueTypeName: 'raw'
  },
  [CODE_INFO_TLV.INT8_ARRAY]: {
    dataType: 'int8_t',
    entryKind: CODE_INFO_ENTRY_KIND.ARRAY,
    expectedByteLength: 1,
    name: 'int8_array',
    valueTypeName: 'int8_t'
  },
  [CODE_INFO_TLV.UINT8_ARRAY]: {
    dataType: 'uint8_t',
    entryKind: CODE_INFO_ENTRY_KIND.ARRAY,
    expectedByteLength: 1,
    name: 'uint8_array',
    valueTypeName: 'uint8_t'
  },
  [CODE_INFO_TLV.INT16_ARRAY]: {
    dataType: 'int16_t',
    entryKind: CODE_INFO_ENTRY_KIND.ARRAY,
    expectedByteLength: 2,
    name: 'int16_array',
    valueTypeName: 'int16_t'
  },
  [CODE_INFO_TLV.UINT16_ARRAY]: {
    dataType: 'uint16_t',
    entryKind: CODE_INFO_ENTRY_KIND.ARRAY,
    expectedByteLength: 2,
    name: 'uint16_array',
    valueTypeName: 'uint16_t'
  },
  [CODE_INFO_TLV.INT32_ARRAY]: {
    dataType: 'int32_t',
    entryKind: CODE_INFO_ENTRY_KIND.ARRAY,
    expectedByteLength: 4,
    name: 'int32_array',
    valueTypeName: 'int32_t'
  },
  [CODE_INFO_TLV.UINT32_ARRAY]: {
    dataType: 'uint32_t',
    entryKind: CODE_INFO_ENTRY_KIND.ARRAY,
    expectedByteLength: 4,
    name: 'uint32_array',
    valueTypeName: 'uint32_t'
  },
  [CODE_INFO_TLV.FLOAT32_ARRAY]: {
    dataType: 'float',
    entryKind: CODE_INFO_ENTRY_KIND.ARRAY,
    expectedByteLength: 4,
    name: 'float32_array',
    valueTypeName: 'float32'
  },
  [CODE_INFO_TLV.DOUBLE_ARRAY]: {
    dataType: 'double',
    entryKind: CODE_INFO_ENTRY_KIND.ARRAY,
    expectedByteLength: 8,
    name: 'double_array',
    valueTypeName: 'double'
  },
  [CODE_INFO_TLV.INT64_ARRAY]: {
    dataType: 'int64_t',
    entryKind: CODE_INFO_ENTRY_KIND.ARRAY,
    expectedByteLength: 8,
    name: 'int64_array',
    valueTypeName: 'int64_t'
  },
  [CODE_INFO_TLV.UINT64_ARRAY]: {
    dataType: 'uint64_t',
    entryKind: CODE_INFO_ENTRY_KIND.ARRAY,
    expectedByteLength: 8,
    name: 'uint64_array',
    valueTypeName: 'uint64_t'
  },
  [CODE_INFO_TLV.INT128_ARRAY]: {
    dataType: 'int128_t',
    entryKind: CODE_INFO_ENTRY_KIND.ARRAY,
    expectedByteLength: 16,
    name: 'int128_array',
    valueTypeName: 'int128_t'
  },
  [CODE_INFO_TLV.UINT128_ARRAY]: {
    dataType: 'uint128_t',
    entryKind: CODE_INFO_ENTRY_KIND.ARRAY,
    expectedByteLength: 16,
    name: 'uint128_array',
    valueTypeName: 'uint128_t'
  },
  [CODE_INFO_TLV.INT256_ARRAY]: {
    dataType: 'int256_t',
    entryKind: CODE_INFO_ENTRY_KIND.ARRAY,
    expectedByteLength: 32,
    name: 'int256_array',
    valueTypeName: 'int256_t'
  },
  [CODE_INFO_TLV.UINT256_ARRAY]: {
    dataType: 'uint256_t',
    entryKind: CODE_INFO_ENTRY_KIND.ARRAY,
    expectedByteLength: 32,
    name: 'uint256_array',
    valueTypeName: 'uint256_t'
  },
  [CODE_INFO_TLV.TEXT_ARRAY]: {
    dataType: 'text',
    entryKind: CODE_INFO_ENTRY_KIND.ARRAY,
    expectedByteLength: 1,
    name: 'text_array',
    valueTypeName: 'text'
  },
  [CODE_INFO_TLV.ENUM_ARRAY]: {
    dataType: '',
    entryKind: CODE_INFO_ENTRY_KIND.ARRAY,
    name: 'enum_array',
    valueTypeName: 'enum'
  },
  [CODE_INFO_TLV.STRUCT_ARRAY]: {
    dataType: 'raw',
    entryKind: CODE_INFO_ENTRY_KIND.ARRAY,
    name: 'struct_array',
    valueTypeName: 'struct'
  }
}
const CODE_INFO_MEMORY_AREA = {
  DATA: 0x01,
  IDATA: 0x02,
  XDATA: 0x03,
  CODE: 0x04,
  ADDR32: 0x07
}
const CODE_INFO_MEMORY_AREA_NAMES = {
  [CODE_INFO_MEMORY_AREA.DATA]: 'DATA',
  [CODE_INFO_MEMORY_AREA.IDATA]: 'IDATA',
  [CODE_INFO_MEMORY_AREA.XDATA]: 'XDATA',
  [CODE_INFO_MEMORY_AREA.CODE]: 'CODE',
  [CODE_INFO_MEMORY_AREA.ADDR32]: 'ADDR32'
}
const MEMORY_ENDIAN = {
  BIG: 'big',
  LITTLE: 'little'
}

function toBytes(bytes) {
  return Array.prototype.slice.call(bytes || []).map((byte) => Number(byte) & 0xFF)
}

function normalizeMemoryEndian(value, fallback = MEMORY_ENDIAN.LITTLE) {
  const text = String(value || '').trim().toLowerCase()
  if (text === 'little' || text === 'le' || text === '1') return MEMORY_ENDIAN.LITTLE
  if (text === 'big' || text === 'be' || text === '0') return MEMORY_ENDIAN.BIG

  return fallback
}

function applyMemoryEndian(bytes = [], memoryEndian = MEMORY_ENDIAN.LITTLE) {
  const source = Array.isArray(bytes) ? bytes.slice() : []

  return normalizeMemoryEndian(memoryEndian) === MEMORY_ENDIAN.LITTLE
    ? source.reverse()
    : source
}

function readFloat(bytes, offset, memoryEndian = MEMORY_ENDIAN.LITTLE) {
  if (offset + 3 >= bytes.length) return 0

  const buffer = new ArrayBuffer(4)
  const view = new DataView(buffer)
  const source = applyMemoryEndian(bytes.slice(offset, offset + 4), memoryEndian)
  for (let index = 0; index < 4; index += 1) {
    view.setUint8(index, source[index] || 0)
  }

  return view.getFloat32(0, false)
}

function readTlvText(bytes) {
  return bytesToAutoText(trimTrailingNullBytes(bytes)).trim()
}

function normalizeCodeInfoAddressWidth(value) {
  const numberValue = Number(value)
  if (numberValue === 16) return 16
  if (numberValue === 32) return 32

  throw new Error('CodeInfo 描述符地址长度必须为 16 或 32')
}

function formatAddress(address, addressWidth = 32) {
  const numberValue = Math.max(0, Math.floor(Number(address) || 0))
  const hexWidth = normalizeCodeInfoAddressWidth(addressWidth) === 16 ? 4 : 8

  return `0x${formatHexNumber(numberValue, hexWidth)}`
}

function normalizeTypeName(value, fallback) {
  const text = String(value || '').trim()

  return text || fallback
}

function getEntryKindText(entryKind) {
  return CODE_INFO_ENTRY_KIND_TEXT[entryKind] || 'unknown'
}

function getEntryTypeDefinition(type) {
  return CODE_INFO_ENTRY_TYPE_DEFINITIONS[Number(type) & 0xFF] || null
}

function getTlvName(type) {
  const tlvType = Number(type) & 0xFF
  const entryDefinition = getEntryTypeDefinition(tlvType)
  if (entryDefinition && entryDefinition.name) return entryDefinition.name
  if (CODE_INFO_BOARD_TLV_NAMES[tlvType]) return CODE_INFO_BOARD_TLV_NAMES[tlvType]

  return `tlv_0x${formatHexNumber(tlvType, 2)}`
}

function readMemoryEndianUint16(bytes, memoryEndian) {
  return normalizeMemoryEndian(memoryEndian) === MEMORY_ENDIAN.LITTLE
    ? readUint16LE(bytes, 0)
    : readUint16BE(bytes, 0)
}

function readMemoryEndianUint32(bytes, memoryEndian) {
  return normalizeMemoryEndian(memoryEndian) === MEMORY_ENDIAN.LITTLE
    ? readUint32LE(bytes, 0)
    : readUint32BE(bytes, 0)
}

function readNameField(valueBytes, offset, role) {
  if (offset >= valueBytes.length) {
    throw new Error('CodeInfo 内存入口 TLV 名称字段缺失')
  }

  const byteLength = valueBytes[offset] & 0xFF
  const nameOffset = offset + CODE_INFO_ENTRY_NAME_LENGTH_BYTE_LENGTH
  const nextOffset = nameOffset + byteLength
  if (byteLength > CODE_INFO_ENTRY_MAX_NAME_BYTE_LENGTH || nextOffset > valueBytes.length) {
    throw new Error(`CodeInfo 内存入口 TLV 名称长度无效，声明 ${byteLength} 字节，实际 ${Math.max(0, valueBytes.length - nameOffset)} 字节`)
  }

  return {
    field: {
      byteLength,
      role,
      text: readTlvText(valueBytes.slice(nameOffset, nextOffset))
    },
    nextOffset
  }
}

function ensureNoTrailingBytes(valueBytes, offset) {
  if (offset !== valueBytes.length) {
    throw new Error('CodeInfo 内存入口 TLV 长度与字段定义不一致')
  }
}

function resolveCodeInfoByteLength(sourceLength, options = {}) {
  const expectedLength = Number(
    options.codeInfoByteLength === undefined ? options.byteLength : options.codeInfoByteLength
  )

  if (!Number.isFinite(expectedLength) || expectedLength <= 0) return sourceLength
  if (sourceLength < expectedLength) {
    throw new Error('CodeInfo 数据长度小于描述符声明长度')
  }

  return Math.floor(expectedLength)
}

function parseEntryPrefix(valueBytes, options = {}) {
  const addressWidth = normalizeCodeInfoAddressWidth(options.addressWidth)
  const memoryEndian = normalizeMemoryEndian(options.memoryEndian)
  const addressByteLength = addressWidth === 16 ? 2 : 4
  const minPrefixByteLength = (addressWidth === 16 ? 1 : 0) + addressByteLength + 2
  if (valueBytes.length < minPrefixByteLength) {
    throw new Error(`CodeInfo 内存入口 TLV 长度无效，至少需要 ${minPrefixByteLength} 字节`)
  }

  let cursor = 0
  let memoryArea = 'ADDR32'
  let memoryAreaCode = CODE_INFO_MEMORY_AREA.ADDR32

  if (addressWidth === 16) {
    memoryAreaCode = valueBytes[cursor] & 0xFF
    memoryArea = CODE_INFO_MEMORY_AREA_NAMES[memoryAreaCode] || ''
    if (!memoryArea || memoryAreaCode === CODE_INFO_MEMORY_AREA.ADDR32) {
      throw new Error('CodeInfo 16 位地址入口存储区域无效')
    }
    cursor += 1
  }

  const byteAddr = addressByteLength === 2
    ? readMemoryEndianUint16(valueBytes.slice(cursor, cursor + addressByteLength), memoryEndian)
    : readMemoryEndianUint32(valueBytes.slice(cursor, cursor + addressByteLength), memoryEndian)
  cursor += addressByteLength

  const byteLength = readMemoryEndianUint16(valueBytes.slice(cursor, cursor + 2), memoryEndian)
  cursor += 2

  return {
    addressByteLength,
    addressWidth,
    byteAddr,
    byteLength,
    cursor,
    memoryArea,
    memoryAreaCode
  }
}

function getEntryFallbackName(entryKindText, index) {
  if (entryKindText === 'enum') return `enum_${index + 1}`
  if (entryKindText === 'array') return `array_${index + 1}`
  if (entryKindText === 'variable') return `var_${index + 1}`

  return `struct_${index + 1}`
}

function createEntryBase(prefix, index, type, entryKind, names, extra = {}) {
  const entryKindText = getEntryKindText(entryKind)
  const fallbackName = getEntryFallbackName(entryKindText, index)
  const definitionField = names.find((name) => name.role === CODE_INFO_ENTRY_NAME_ROLE.DEFINITION)
  const instanceField = names.find((name) => name.role === CODE_INFO_ENTRY_NAME_ROLE.INSTANCE)
    || names.find((name) => name.role === CODE_INFO_ENTRY_NAME_ROLE.VARIABLE)
  const definitionName = normalizeTypeName(definitionField && definitionField.text, '')
  const instanceName = normalizeTypeName(instanceField && instanceField.text, definitionName || fallbackName)
  const symbolName = entryKind === CODE_INFO_ENTRY_KIND.VARIABLE
    ? normalizeTypeName(instanceField && instanceField.text, fallbackName)
    : instanceName
  const typeName = definitionName || extra.valueTypeName || symbolName

  return {
    addressByteLength: prefix.addressByteLength,
    addressWidth: prefix.addressWidth,
    byteAddr: prefix.byteAddr,
    byteLength: prefix.byteLength,
    definitionName,
    entryKind,
    entryKindText,
    index,
    instanceName,
    isArrayEntry: entryKind === CODE_INFO_ENTRY_KIND.ARRAY,
    isEnumEntry: entryKind === CODE_INFO_ENTRY_KIND.ENUM,
    isStructEntry: entryKind === CODE_INFO_ENTRY_KIND.STRUCT,
    isVariableEntry: entryKind === CODE_INFO_ENTRY_KIND.VARIABLE,
    memType: prefix.memoryAreaCode,
    memoryArea: prefix.memoryArea || 'UNKNOWN',
    nameByteLength: names.reduce((total, name) => total + Number(name.byteLength || 0), 0),
    nameFields: names.map((name) => ({
      byteLength: name.byteLength,
      role: name.role,
      text: name.text
    })),
    rawByteLength: 0,
    sourceAddressText: formatAddress(prefix.byteAddr, prefix.addressWidth),
    symbolName,
    tlvType: Number(type) & 0xFF,
    typeName,
    ...extra
  }
}

function validateEntryByteLength(prefix, entryDefinition, label) {
  const expectedByteLength = Number(entryDefinition && entryDefinition.expectedByteLength)
  if (!Number.isFinite(expectedByteLength) || expectedByteLength <= 0) return
  if (Number(prefix.byteLength) !== expectedByteLength) {
    throw new Error(`CodeInfo ${label} 字节长度与 TYPE 定义不一致`)
  }
}

function parseVariableEntry(valueBytes, index, type, entryDefinition, options = {}) {
  const prefix = parseEntryPrefix(valueBytes, options)
  let cursor = prefix.cursor

  const nameResult = readNameField(valueBytes, cursor, CODE_INFO_ENTRY_NAME_ROLE.VARIABLE)
  cursor = nameResult.nextOffset
  ensureNoTrailingBytes(valueBytes, cursor)
  validateEntryByteLength(prefix, entryDefinition, '变量入口')

  const entry = createEntryBase(
    prefix,
    index,
    type,
    CODE_INFO_ENTRY_KIND.VARIABLE,
    [nameResult.field],
    {
      dataType: entryDefinition.dataType || 'raw',
      valueTypeName: entryDefinition.valueTypeName || 'raw'
    }
  )

  return {
    ...entry,
    rawByteLength: valueBytes.length
  }
}

function parseEnumEntry(valueBytes, index, type, entryDefinition, options = {}) {
  const prefix = parseEntryPrefix(valueBytes, options)
  let cursor = prefix.cursor
  const definitionResult = readNameField(valueBytes, cursor, CODE_INFO_ENTRY_NAME_ROLE.DEFINITION)
  cursor = definitionResult.nextOffset
  const instanceResult = readNameField(valueBytes, cursor, CODE_INFO_ENTRY_NAME_ROLE.INSTANCE)
  cursor = instanceResult.nextOffset
  ensureNoTrailingBytes(valueBytes, cursor)

  const entry = createEntryBase(
    prefix,
    index,
    type,
    CODE_INFO_ENTRY_KIND.ENUM,
    [definitionResult.field, instanceResult.field],
    {
      dataType: entryDefinition.dataType || getIntegerDataTypeForByteLength(prefix.byteLength),
      valueTypeName: entryDefinition.valueTypeName || 'enum'
    }
  )

  return {
    ...entry,
    rawByteLength: valueBytes.length
  }
}

function parseStructEntry(valueBytes, index, type, entryDefinition, options = {}) {
  const prefix = parseEntryPrefix(valueBytes, options)
  let cursor = prefix.cursor
  const definitionResult = readNameField(valueBytes, cursor, CODE_INFO_ENTRY_NAME_ROLE.DEFINITION)
  cursor = definitionResult.nextOffset
  const instanceResult = readNameField(valueBytes, cursor, CODE_INFO_ENTRY_NAME_ROLE.INSTANCE)
  cursor = instanceResult.nextOffset
  ensureNoTrailingBytes(valueBytes, cursor)

  const entry = createEntryBase(
    prefix,
    index,
    type,
    CODE_INFO_ENTRY_KIND.STRUCT,
    [definitionResult.field, instanceResult.field],
    {
      dataType: entryDefinition.dataType || 'raw',
      valueTypeName: entryDefinition.valueTypeName || 'struct'
    }
  )

  return {
    ...entry,
    rawByteLength: valueBytes.length
  }
}

function readArrayDimensions(valueBytes, cursor, dimCount, memoryEndian) {
  const dimensions = []

  for (let index = 0; index < dimCount; index += 1) {
    if (cursor + 2 > valueBytes.length) {
      throw new Error('CodeInfo 数组维度字段长度无效')
    }
    const length = readMemoryEndianUint16(valueBytes.slice(cursor, cursor + 2), memoryEndian)
    if (!length) throw new Error('CodeInfo 数组维度长度必须大于 0')
    dimensions.push(length)
    cursor += 2
  }

  return {
    dimensions,
    nextOffset: cursor
  }
}

function parseArrayEntry(valueBytes, index, type, entryDefinition, options = {}) {
  const prefix = parseEntryPrefix(valueBytes, options)
  const memoryEndian = normalizeMemoryEndian(options.memoryEndian)
  let cursor = prefix.cursor
  if (cursor + 5 > valueBytes.length) {
    throw new Error('CodeInfo 数组入口长度无效')
  }

  const elementByteLength = readMemoryEndianUint16(valueBytes.slice(cursor, cursor + 2), memoryEndian)
  cursor += 2
  const elementCount = readMemoryEndianUint16(valueBytes.slice(cursor, cursor + 2), memoryEndian)
  cursor += 2
  const dimensionCount = valueBytes[cursor] & 0xFF
  cursor += 1

  if (!elementByteLength) throw new Error('CodeInfo 数组元素字节长度必须大于 0')
  if (!elementCount) throw new Error('CodeInfo 数组元素数量必须大于 0')
  if (!dimensionCount) throw new Error('CodeInfo 数组维度数量必须大于 0')

  const dimensionsResult = readArrayDimensions(valueBytes, cursor, dimensionCount, memoryEndian)
  cursor = dimensionsResult.nextOffset
  const needsDefinitionName = type === CODE_INFO_TLV.ENUM_ARRAY || type === CODE_INFO_TLV.STRUCT_ARRAY
  let definitionResult = null
  if (needsDefinitionName) {
    definitionResult = readNameField(valueBytes, cursor, CODE_INFO_ENTRY_NAME_ROLE.DEFINITION)
    cursor = definitionResult.nextOffset
  }
  const instanceResult = readNameField(valueBytes, cursor, CODE_INFO_ENTRY_NAME_ROLE.INSTANCE)
  cursor = instanceResult.nextOffset
  ensureNoTrailingBytes(valueBytes, cursor)
  validateEntryByteLength({ byteLength: elementByteLength }, entryDefinition, '数组元素')

  const dimensionProduct = dimensionsResult.dimensions.reduce((total, value) => total * value, 1)
  if (dimensionProduct !== elementCount) {
    throw new Error('CodeInfo 数组维度乘积与元素数量不一致')
  }
  if (elementByteLength * elementCount !== prefix.byteLength) {
    throw new Error('CodeInfo 数组总字节长度与元素数量不一致')
  }

  const entry = createEntryBase(
    prefix,
    index,
    type,
    CODE_INFO_ENTRY_KIND.ARRAY,
    (definitionResult ? [definitionResult.field] : []).concat(instanceResult.field),
    {
      arrayDimensions: dimensionsResult.dimensions,
      arrayElementBaseIndex: 0,
      dataType: entryDefinition.dataType || 'raw',
      elementByteLength,
      elementCount,
      isEnumArrayEntry: type === CODE_INFO_TLV.ENUM_ARRAY,
      isStructArrayEntry: type === CODE_INFO_TLV.STRUCT_ARRAY,
      isTextArrayEntry: type === CODE_INFO_TLV.TEXT_ARRAY,
      valueTypeName: entryDefinition.valueTypeName || 'raw'
    }
  )

  return {
    ...entry,
    rawByteLength: valueBytes.length
  }
}

function parseMemoryEntry(valueBytes, index, type, options = {}) {
  const entryDefinition = getEntryTypeDefinition(type)
  if (!entryDefinition) return null

  switch (entryDefinition.entryKind) {
    case CODE_INFO_ENTRY_KIND.VARIABLE:
      return parseVariableEntry(valueBytes, index, type, entryDefinition, options)
    case CODE_INFO_ENTRY_KIND.ENUM:
      return parseEnumEntry(valueBytes, index, type, entryDefinition, options)
    case CODE_INFO_ENTRY_KIND.STRUCT:
      return parseStructEntry(valueBytes, index, type, entryDefinition, options)
    case CODE_INFO_ENTRY_KIND.ARRAY:
      return parseArrayEntry(valueBytes, index, type, entryDefinition, options)
    default:
      return null
  }
}

function applyCodeInfoTlvValue(target, type, valueBytes, options = {}) {
  const memoryEndian = normalizeMemoryEndian(options.memoryEndian)

  switch (type) {
    case CODE_INFO_TLV.CAVE_FREQ:
      if (valueBytes.length < 1) throw new Error('CodeInfo cave_freq TLV 长度无效')
      target.caveFreq = valueBytes[0] & 0xFF
      break
    case CODE_INFO_TLV.REF_VOLT:
      if (valueBytes.length < 1) throw new Error('CodeInfo ref_volt TLV 长度无效')
      target.refVoltRaw = valueBytes[0] & 0xFF
      target.refVolt = target.refVoltRaw / 10
      break
    case CODE_INFO_TLV.AMP_GAIN:
      if (valueBytes.length < 1) throw new Error('CodeInfo amp_gain TLV 长度无效')
      target.ampGain = valueBytes[0] & 0xFF
      break
    case CODE_INFO_TLV.RS_SHUNT:
      if (valueBytes.length < 2) throw new Error('CodeInfo rs_shunt TLV 长度无效')
      target.rsShunt = readMemoryEndianUint16(valueBytes, memoryEndian)
      break
    case CODE_INFO_TLV.BUS_DIV:
      if (valueBytes.length < 4) throw new Error('CodeInfo bus_div TLV 长度无效')
      target.busDiv = readFloat(valueBytes, 0, memoryEndian)
      break
    case CODE_INFO_TLV.ALONG_DIV:
      if (valueBytes.length < 4) throw new Error('CodeInfo along_div TLV 长度无效')
      target.alongDiv = readFloat(valueBytes, 0, memoryEndian)
      break
    case CODE_INFO_TLV.CHIP_MODEL:
      target.chipModel = readTlvText(valueBytes)
      break
    case CODE_INFO_TLV.MODEL:
      target.model = readTlvText(valueBytes)
      break
    default:
      break
  }
}

function parseCodeInfoTlvs(bytes, options = {}) {
  const info = {
    entries: [],
    tlvItems: []
  }
  let offset = 0

  while (offset < bytes.length) {
    if (offset + CODE_INFO_TLV_HEADER_BYTE_LENGTH > bytes.length) {
      throw new Error('CodeInfo TLV 项头长度无效')
    }

    const type = bytes[offset] & 0xFF
    const byteLength = bytes[offset + 1] & 0xFF
    const valueOffset = offset + CODE_INFO_TLV_HEADER_BYTE_LENGTH
    const nextOffset = valueOffset + byteLength
    if (nextOffset > bytes.length) {
      throw new Error('CodeInfo TLV 项长度超出声明范围')
    }

    const valueBytes = bytes.slice(valueOffset, nextOffset)
    const item = {
      byteLength,
      name: getTlvName(type),
      rawHex: bytesToHex(valueBytes, ' '),
      type
    }
    info.tlvItems.push(item)
    const entry = parseMemoryEntry(valueBytes, info.entries.length, type, options)
    if (entry) {
      info.entries.push(entry)
    } else {
      applyCodeInfoTlvValue(info, type, valueBytes, options)
    }
    offset = nextOffset
  }

  return info
}

function parseCodeInfo(bytes, options = {}) {
  const inputBytes = toBytes(bytes)
  const codeInfoByteLength = resolveCodeInfoByteLength(inputBytes.length, options)
  const source = inputBytes.slice(0, codeInfoByteLength)

  const addressWidth = normalizeCodeInfoAddressWidth(options.addressWidth)
  const maxPacketLength = Number(options.maxPacketLength || 0) & 0xFFFF
  const memoryEndian = normalizeMemoryEndian(options.memoryEndian)
  const tlvInfo = parseCodeInfoTlvs(source, { addressWidth, memoryEndian })
  const entries = tlvInfo.entries
  const entryCount = entries.length
  const structCount = entries.filter((entry) => entry.isStructEntry || entry.isStructArrayEntry).length
  const enumCount = entries.filter((entry) => entry.isEnumEntry || entry.isEnumArrayEntry).length
  const variableCount = entries.filter((entry) => entry.isVariableEntry).length
  const arrayCount = entries.filter((entry) => entry.isArrayEntry).length
  const entryAddressByteLengths = entries
    .map((entry) => Number(entry.addressByteLength) || 0)
    .filter((value, index, list) => value > 0 && list.indexOf(value) === index)

  return {
    ...tlvInfo,
    addressWidth,
    addressWidthRaw: addressWidth,
    arrayCount,
    byteLength: codeInfoByteLength,
    entryCount,
    enumCount,
    maxPacketLength,
    memoryEndian,
    memoryEndianRaw: Number(options.memoryEndianRaw || options.memoryEndianMark || 0) & 0xFFFF,
    rawHex: bytesToHex(source, ' '),
    structEntryAddressByteLength: entryAddressByteLengths.length === 1 ? entryAddressByteLengths[0] : 0,
    structCount,
    structTable: entries,
    tableByteLength: entries.reduce((total, entry) => total + Number(entry.rawByteLength || 0), 0),
    tableCapacity: entryCount,
    tableRemainderByteLength: 0,
    tlvByteLength: source.length,
    truncated: false,
    variableCount
  }
}

function getIntegerDataTypeForByteLength(byteLength) {
  const length = Number(byteLength)
  if (length === 1) return 'uint8_t'
  if (length === 2) return 'uint16_t'
  if (length === 4) return 'uint32_t'

  return 'raw'
}

function getArrayIndexPath(linearIndex, dimensions = []) {
  const safeDimensions = (Array.isArray(dimensions) ? dimensions : [])
    .map((value) => Math.max(1, Math.floor(Number(value) || 1)))
    .filter((value) => value > 0)
  if (!safeDimensions.length) return [Math.max(0, Math.floor(Number(linearIndex) || 0))]

  const indexes = Array.from({ length: safeDimensions.length }, () => 0)
  let remaining = Math.max(0, Math.floor(Number(linearIndex) || 0))
  for (let index = safeDimensions.length - 1; index >= 0; index -= 1) {
    indexes[index] = remaining % safeDimensions[index]
    remaining = Math.floor(remaining / safeDimensions[index])
  }

  return indexes
}

function formatArrayIndexPath(indexPath = []) {
  return (Array.isArray(indexPath) ? indexPath : [indexPath])
    .map((index) => `[${Math.max(0, Math.floor(Number(index) || 0))}]`)
    .join('')
}

function createRegisterSourceMeta(entry, byteStart, byteLength, extra = {}) {
  const sourceAddress = entry.byteAddr + Math.max(0, Math.floor(Number(byteStart) || 0))

  return {
    sourceAddress,
    sourceAddressByteLength: entry.addressByteLength,
    sourceAddressText: formatAddress(sourceAddress, entry.addressWidth),
    sourceAddressWidth: entry.addressWidth,
    sourceArrayDimensions: entry.arrayDimensions,
    sourceByteLength: byteLength,
    sourceDefinitionName: entry.definitionName,
    sourceElementByteLength: entry.elementByteLength,
    sourceElementCount: entry.elementCount,
    sourceElementType: entry.valueTypeName,
    sourceEntryKind: entry.entryKindText,
    sourceInstanceName: entry.instanceName,
    sourceMemoryArea: entry.memoryArea,
    sourceMemoryClass: entry.memoryArea,
    sourceSymbolName: entry.symbolName,
    sourceSymbolType: entry.typeName,
    sourceValueType: entry.valueTypeName,
    ...extra
  }
}

function createRegistersForByteSpan(entry) {
  const count = Math.max(1, Number(entry.byteLength) || 1)
  const registers = []

  for (let offset = 0; offset < count; offset += 1) {
    registers.push({
      byteStart: offset,
      dataType: 'uint8_t',
      isPlaceholderByteField: true,
      name: offset.toString(16).toUpperCase().padStart(2, '0'),
      ...createRegisterSourceMeta(entry, offset, 1)
    })
  }

  return registers
}

function createScalarRegister(entry) {
  const dataType = entry.dataType || (entry.isEnumEntry ? getIntegerDataTypeForByteLength(entry.byteLength) : 'raw')
  const register = {
    byteStart: 0,
    dataType,
    name: entry.symbolName,
    ...createRegisterSourceMeta(entry, 0, entry.byteLength)
  }
  if (dataType === 'text') {
    register.textByteLength = String(entry.byteLength)
  }

  return [register]
}

function createArrayRegisters(entry) {
  if (entry.isStructArrayEntry) return createRegistersForByteSpan(entry)

  const dataType = entry.dataType || 'raw'
  if (dataType === 'text') {
    return [{
      byteStart: 0,
      dataType,
      name: entry.symbolName,
      textByteLength: String(entry.byteLength),
      ...createRegisterSourceMeta(entry, 0, entry.byteLength)
    }]
  }

  const count = Math.max(1, Number(entry.elementCount) || 1)
  const elementByteLength = Math.max(1, Number(entry.elementByteLength) || 1)
  const baseIndex = Math.max(0, Number(entry.arrayElementBaseIndex) || 0)
  const registers = []

  for (let index = 0; index < count; index += 1) {
    const linearIndex = baseIndex + index
    const indexPath = getArrayIndexPath(linearIndex, entry.arrayDimensions)
    const byteStart = index * elementByteLength
    registers.push({
      byteStart,
      dataType,
      name: `${entry.symbolName}${formatArrayIndexPath(indexPath)}`,
      ...createRegisterSourceMeta(entry, byteStart, elementByteLength, {
        sourceArrayIndex: linearIndex,
        sourceArrayIndexPath: indexPath,
        sourceSymbolName: `${entry.symbolName}${formatArrayIndexPath(indexPath)}`
      })
    })
  }

  return registers
}

function createCodeInfoContext(codeInfo = {}) {
  return {
    addressWidth: codeInfo.addressWidth,
    codeInfoAddressWidth: codeInfo.addressWidth,
    maxPacketLength: codeInfo.maxPacketLength,
    memoryEndian: codeInfo.memoryEndian,
    memoryEndianRaw: Number(codeInfo.memoryEndianRaw || 0) & 0xFFFF,
    storageAddressWidth: codeInfo.addressWidth
  }
}

function createSourceMeta(entry, byteLength) {
  return {
    addressUnit: 'byte',
    sourceAddress: entry.byteAddr,
    sourceAddressByteLength: entry.addressByteLength,
    sourceAddressText: formatAddress(entry.byteAddr, entry.addressWidth),
    sourceAddressWidth: entry.addressWidth,
    sourceArrayDimensions: entry.arrayDimensions,
    sourceByteLength: byteLength,
    sourceDefinitionName: entry.definitionName,
    sourceElementByteLength: entry.elementByteLength,
    sourceElementCount: entry.elementCount,
    sourceElementType: entry.valueTypeName,
    sourceEntryKind: entry.entryKindText,
    sourceInstanceName: entry.instanceName,
    sourceMemoryArea: entry.memoryArea,
    sourceMemoryClass: entry.memoryArea,
    sourceSymbolName: entry.symbolName,
    sourceSymbolType: entry.typeName,
    sourceValueType: entry.valueTypeName
  }
}

function createGroupFromEntry(entry, chunkLength, suffix = '', codeInfo = {}) {
  const isStructLike = entry.isStructEntry || entry.isStructArrayEntry
  const registers = isStructLike
    ? createRegistersForByteSpan(entry)
    : (entry.isArrayEntry ? createArrayRegisters(entry) : createScalarRegister(entry))

  return {
    codeInfoContext: createCodeInfoContext(codeInfo),
    layout: isStructLike ? 'struct' : 'register',
    name: `${entry.symbolName || entry.typeName}${suffix}`,
    quantity: registers.length,
    registerType: entry.memoryArea === 'CODE' ? 'input' : 'holding',
    registers,
    ...createSourceMeta(entry, chunkLength),
    sourceSegment: 'CodeInfo TLV',
    sourceSegmentModule: '',
    startAddress: formatAddress(entry.byteAddr, entry.addressWidth)
  }
}

function createEntryChunk(entry, offset, chunkLength) {
  const chunkEntry = {
    ...entry,
    byteAddr: entry.byteAddr + offset,
    byteLength: chunkLength
  }

  if (entry.isArrayEntry) {
    const elementByteLength = Math.max(1, Number(entry.elementByteLength) || 1)
    const elementOffset = Math.floor(offset / elementByteLength)
    const elementCount = Math.max(1, Math.floor(chunkLength / elementByteLength))
    chunkEntry.arrayElementBaseIndex = (Number(entry.arrayElementBaseIndex) || 0) + elementOffset
    chunkEntry.elementCount = elementCount
  }

  return chunkEntry
}

function getChunkStep(entry, maxRegisters) {
  if (!entry.isArrayEntry) return maxRegisters

  const elementByteLength = Math.max(1, Number(entry.elementByteLength) || 1)
  const elementsPerChunk = Math.max(1, Math.floor(maxRegisters / elementByteLength))

  return elementsPerChunk * elementByteLength
}

function createGroupsFromCodeInfo(codeInfo, options = {}) {
  const maxRegisters = Math.max(1, Number(options.maxRegistersPerGroup) || 256)
  const groups = []
  const entries = Array.isArray(codeInfo.entries) ? codeInfo.entries : codeInfo.structTable

  ;(Array.isArray(entries) ? entries : []).forEach((entry) => {
    if (!entry.byteLength || entry.memoryArea === 'UNKNOWN') return
    if (!entry.isStructEntry && !entry.isEnumEntry && !entry.isVariableEntry && !entry.isArrayEntry) return

    const chunkStep = getChunkStep(entry, maxRegisters)
    for (let offset = 0; offset < entry.byteLength; offset += chunkStep) {
      const chunkLength = Math.min(chunkStep, entry.byteLength - offset)
      const chunkEntry = createEntryChunk(entry, offset, chunkLength)
      const suffix = entry.byteLength > chunkStep ? ` #${Math.floor(offset / chunkStep) + 1}` : ''

      groups.push(createGroupFromEntry(chunkEntry, chunkLength, suffix, codeInfo))
    }
  })

  return groups
}

module.exports = {
  CODE_INFO_ENTRY_KIND,
  CODE_INFO_ENTRY_MAX_NAME_BYTE_LENGTH,
  CODE_INFO_ENTRY_NAME_LENGTH_BYTE_LENGTH,
  CODE_INFO_MEMORY_AREA,
  CODE_INFO_TLV_HEADER_BYTE_LENGTH,
  CODE_INFO_TLV,
  MEMORY_ENDIAN,
  createGroupsFromCodeInfo,
  normalizeCodeInfoAddressWidth,
  parseCodeInfo
}
