const {
  bytesToHex,
  bytesToUtf8Text,
  trimTrailingNullBytes
} = require('../../utils/binary-utils.js')

const CODE_INFO_TLV_HEADER_BYTE_LENGTH = 2
const CODE_INFO_ENTRY_NAME_BYTE_LENGTH = 32
const CODE_INFO_TLV = {
  DATA_STRUCT: 0x01,
  DATA_VARIABLE: 0x02,
  IDATA_STRUCT: 0x03,
  IDATA_VARIABLE: 0x04,
  XDATA_STRUCT: 0x05,
  XDATA_VARIABLE: 0x06,
  ADDR32_STRUCT: 0x07,
  ADDR32_VARIABLE: 0x08,
  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_TLV_NAMES = {
  [CODE_INFO_TLV.DATA_STRUCT]: 'data_struct',
  [CODE_INFO_TLV.DATA_VARIABLE]: 'data_var',
  [CODE_INFO_TLV.IDATA_STRUCT]: 'idata_struct',
  [CODE_INFO_TLV.IDATA_VARIABLE]: 'idata_var',
  [CODE_INFO_TLV.XDATA_STRUCT]: 'xdata_struct',
  [CODE_INFO_TLV.XDATA_VARIABLE]: 'xdata_var',
  [CODE_INFO_TLV.ADDR32_STRUCT]: 'addr32_struct',
  [CODE_INFO_TLV.ADDR32_VARIABLE]: 'addr32_var',
  [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
}
const CODE_INFO_ENTRY_KIND_TEXT = {
  [CODE_INFO_ENTRY_KIND.STRUCT]: 'struct',
  [CODE_INFO_ENTRY_KIND.VARIABLE]: 'variable'
}
const CODE_INFO_ENTRY_LAYOUTS = {
  [CODE_INFO_TLV.DATA_STRUCT]: {
    addressWidth: 16,
    entryKind: CODE_INFO_ENTRY_KIND.STRUCT,
    memoryArea: 'DATA'
  },
  [CODE_INFO_TLV.DATA_VARIABLE]: {
    addressWidth: 16,
    entryKind: CODE_INFO_ENTRY_KIND.VARIABLE,
    memoryArea: 'DATA'
  },
  [CODE_INFO_TLV.IDATA_STRUCT]: {
    addressWidth: 16,
    entryKind: CODE_INFO_ENTRY_KIND.STRUCT,
    memoryArea: 'IDATA'
  },
  [CODE_INFO_TLV.IDATA_VARIABLE]: {
    addressWidth: 16,
    entryKind: CODE_INFO_ENTRY_KIND.VARIABLE,
    memoryArea: 'IDATA'
  },
  [CODE_INFO_TLV.XDATA_STRUCT]: {
    addressWidth: 16,
    entryKind: CODE_INFO_ENTRY_KIND.STRUCT,
    memoryArea: 'XDATA'
  },
  [CODE_INFO_TLV.XDATA_VARIABLE]: {
    addressWidth: 16,
    entryKind: CODE_INFO_ENTRY_KIND.VARIABLE,
    memoryArea: 'XDATA'
  },
  [CODE_INFO_TLV.ADDR32_STRUCT]: {
    addressWidth: 32,
    entryKind: CODE_INFO_ENTRY_KIND.STRUCT,
    memoryArea: 'ADDR32'
  },
  [CODE_INFO_TLV.ADDR32_VARIABLE]: {
    addressWidth: 32,
    entryKind: CODE_INFO_ENTRY_KIND.VARIABLE,
    memoryArea: 'ADDR32'
  }
}
const MEMORY_ENDIAN = {
  BIG: 'big',
  LITTLE: 'little'
}

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

function readUint16(bytes, offset) {
  if (offset + 1 >= bytes.length) return 0

  return (((bytes[offset] || 0) << 8) | (bytes[offset + 1] || 0)) & 0xFFFF
}

function readUint32(bytes, offset) {
  if (offset + 3 >= bytes.length) return 0

  return (
    ((bytes[offset] || 0) * 0x1000000)
    + (((bytes[offset + 1] || 0) << 16) >>> 0)
    + (((bytes[offset + 2] || 0) << 8) >>> 0)
    + (bytes[offset + 3] || 0)
  ) >>> 0
}

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

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

  return view.getFloat32(0, false)
}

function readTlvText(bytes) {
  return bytesToUtf8Text(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 getEntryLayout(type) {
  const entryType = Number(type) & 0xFF
  const layout = CODE_INFO_ENTRY_LAYOUTS[entryType]
  if (!layout) return null

  const addressWidth = layout.addressWidth
  const addressByteLength = addressWidth === 16 ? 2 : 4
  const valueByteLength = addressByteLength + 2 + CODE_INFO_ENTRY_NAME_BYTE_LENGTH

  return {
    addressByteLength,
    addressWidth,
    entryKind: layout.entryKind,
    hasMemoryType: false,
    memoryArea: layout.memoryArea,
    nameByteLength: CODE_INFO_ENTRY_NAME_BYTE_LENGTH,
    nameOffset: addressByteLength + 2,
    valueByteLength
  }
}

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

  return `0x${numberValue.toString(16).toUpperCase().padStart(hexWidth, '0')}`
}

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

  return text || fallback
}

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

function normalizeMemoryEndian(value) {
  const text = String(value || '').trim().toLowerCase()
  if (text === 'little' || text === 'le' || text === '1') return MEMORY_ENDIAN.LITTLE

  return MEMORY_ENDIAN.BIG
}

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 parseMemoryEntry(valueBytes, index, type) {
  const layout = getEntryLayout(type)
  if (!layout) return null
  if (valueBytes.length !== layout.valueByteLength) {
    throw new Error(`CodeInfo 内存入口 TLV 长度无效，期望 ${layout.valueByteLength} 字节`)
  }

  const memType = 0
  const addressOffset = 0
  const byteAddr = layout.addressByteLength === 2
    ? readUint16(valueBytes, addressOffset)
    : readUint32(valueBytes, addressOffset)
  const byteLength = readUint16(valueBytes, addressOffset + layout.addressByteLength)
  const nameOffset = layout.nameOffset
  const entryKind = layout.entryKind
  const entryKindText = getEntryKindText(entryKind)
  const typeName = normalizeTypeName(
    readTlvText(valueBytes.slice(nameOffset, nameOffset + layout.nameByteLength)),
    `${entryKindText === 'variable' ? 'var' : 'struct'}_${index + 1}`
  )
  const memoryArea = layout.memoryArea || 'UNKNOWN'

  return {
    addressByteLength: layout.addressByteLength,
    addressWidth: layout.addressWidth,
    byteAddr,
    byteLength,
    entryKind,
    entryKindText,
    index,
    isStructEntry: entryKind === CODE_INFO_ENTRY_KIND.STRUCT,
    isVariableEntry: entryKind === CODE_INFO_ENTRY_KIND.VARIABLE,
    memType,
    memoryArea,
    rawByteLength: valueBytes.length,
    sourceAddressText: formatAddress(byteAddr, layout.addressWidth),
    tlvType: Number(type) & 0xFF,
    typeName
  }
}

function applyCodeInfoTlvValue(target, type, valueBytes) {
  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 = readUint16(valueBytes, 0)
      break
    case CODE_INFO_TLV.BUS_DIV:
      if (valueBytes.length < 4) throw new Error('CodeInfo bus_div TLV 长度无效')
      target.busDiv = readFloat(valueBytes, 0)
      break
    case CODE_INFO_TLV.ALONG_DIV:
      if (valueBytes.length < 4) throw new Error('CodeInfo along_div TLV 长度无效')
      target.alongDiv = readFloat(valueBytes, 0)
      break
    case CODE_INFO_TLV.CHIP_MODEL:
      target.chipModel = readTlvText(valueBytes)
      break
    case CODE_INFO_TLV.MODEL:
      target.model = readTlvText(valueBytes)
      break
    default:
      if (type >= 0x20 && type < 0x40) {
        target.customTlvCount = (Number(target.customTlvCount) || 0) + 1
      }
      break
  }
}

function parseCodeInfoTlvs(bytes) {
  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: CODE_INFO_TLV_NAMES[type] || `tlv_0x${type.toString(16).toUpperCase().padStart(2, '0')}`,
      rawHex: bytesToHex(valueBytes, ' '),
      type
    }
    info.tlvItems.push(item)
    const entry = parseMemoryEntry(valueBytes, info.entries.length, type)
    if (entry) {
      info.entries.push(entry)
    } else {
      applyCodeInfoTlvValue(info, type, valueBytes)
    }
    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 tlvInfo = parseCodeInfoTlvs(source)
  const entries = tlvInfo.entries
  const entryCount = entries.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,
    byteLength: codeInfoByteLength,
    entryCount,
    maxPacketLength,
    memoryEndian: normalizeMemoryEndian(options.memoryEndian),
    memoryEndianRaw: Number(options.memoryEndianRaw || options.memoryEndianMark || 0) & 0xFFFF,
    rawHex: bytesToHex(source, ' '),
    structEntryAddressByteLength: entryAddressByteLengths.length === 1 ? entryAddressByteLengths[0] : 0,
    structCount: entryCount,
    structTable: entries,
    tableByteLength: entries.reduce((total, entry) => total + Number(entry.rawByteLength || 0), 0),
    tableCapacity: entryCount,
    tableRemainderByteLength: 0,
    tlvByteLength: source.length,
    truncated: false
  }
}

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'),
      sourceAddress: entry.byteAddr + offset,
      sourceAddressByteLength: entry.addressByteLength,
      sourceAddressText: formatAddress(entry.byteAddr + offset, entry.addressWidth),
      sourceAddressWidth: entry.addressWidth,
      sourceByteLength: 1,
      sourceEntryKind: entry.entryKindText,
      sourceMemoryArea: entry.memoryArea,
      sourceMemoryClass: entry.memoryArea,
      sourceSymbolName: entry.typeName,
      sourceSymbolType: entry.typeName
    })
  }

  return registers
}

function createVariableRegisters(entry) {
  return [{
    byteStart: 0,
    dataType: 'raw',
    name: entry.typeName,
    sourceAddress: entry.byteAddr,
    sourceAddressByteLength: entry.addressByteLength,
    sourceAddressText: formatAddress(entry.byteAddr, entry.addressWidth),
    sourceAddressWidth: entry.addressWidth,
    sourceByteLength: entry.byteLength,
    sourceEntryKind: entry.entryKindText,
    sourceMemoryArea: entry.memoryArea,
    sourceMemoryClass: entry.memoryArea,
    sourceSymbolName: entry.typeName,
    sourceSymbolType: entry.typeName
  }]
}

function createCodeInfoContext(codeInfo = {}) {
  return {
    addressWidth: codeInfo.addressWidth,
    codeInfoAddressWidth: codeInfo.addressWidth,
    maxPacketLength: codeInfo.maxPacketLength,
    memoryEndian: codeInfo.memoryEndian,
    storageAddressWidth: codeInfo.addressWidth
  }
}

function createSourceMeta(entry, byteLength) {
  return {
    sourceAddress: entry.byteAddr,
    sourceAddressByteLength: entry.addressByteLength,
    sourceAddressText: formatAddress(entry.byteAddr, entry.addressWidth),
    sourceAddressWidth: entry.addressWidth,
    sourceByteLength: byteLength,
    sourceEntryKind: entry.entryKindText,
    sourceMemoryArea: entry.memoryArea,
    sourceMemoryClass: entry.memoryArea,
    sourceSymbolName: entry.typeName,
    sourceSymbolType: entry.typeName
  }
}

function createGroupFromEntry(entry, chunkLength, suffix = '', codeInfo = {}) {
  const isStructEntry = entry.entryKind === CODE_INFO_ENTRY_KIND.STRUCT
  const registers = isStructEntry
    ? createRegistersForByteSpan(entry)
    : createVariableRegisters(entry)

  return {
    addressUnit: 'byte',
    codeInfoContext: createCodeInfoContext(codeInfo),
    layout: isStructEntry ? 'struct' : 'register',
    name: `${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 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.isVariableEntry) return

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

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

  return groups
}

module.exports = {
  CODE_INFO_ENTRY_KIND,
  CODE_INFO_ENTRY_NAME_BYTE_LENGTH,
  CODE_INFO_TLV_HEADER_BYTE_LENGTH,
  CODE_INFO_TLV,
  MEMORY_ENDIAN,
  createGroupsFromCodeInfo,
  normalizeCodeInfoAddressWidth,
  parseCodeInfo
}
