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

const FIXED_HEADER_BYTE_LENGTH = 44
const STRUCT_ENTRY_MIN_BYTE_LENGTH = 5
const MEMORY_TYPE_AREAS = {
  0x01: 'DATA',
  0x02: 'IDATA',
  0x03: 'XDATA',
  0x04: 'CODE'
}

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 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 readAscii(bytes, offset, byteLength) {
  const source = trimTrailingNullBytes(bytes.slice(offset, offset + byteLength))

  return source
    .map((byte) => (byte >= 0x20 && byte <= 0x7E ? String.fromCharCode(byte) : ''))
    .join('')
    .trim()
}

function readUtf8OrAscii(bytes, offset, byteLength) {
  return bytesToUtf8Text(bytes.slice(offset, offset + byteLength)).trim()
}

function formatAddress(address) {
  return `0x${Number(address || 0).toString(16).toUpperCase().padStart(4, '0')}`
}

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

  return text || fallback
}

function parseStructEntry(bytes, offset, entryLength, index) {
  const byteAddr = readUint16(bytes, offset)
  const byteLength = readUint16(bytes, offset + 2)
  const memType = bytes[offset + 4] & 0xFF
  const nameByteLength = Math.max(0, entryLength - STRUCT_ENTRY_MIN_BYTE_LENGTH)
  const typeName = normalizeTypeName(
    readAscii(bytes, offset + STRUCT_ENTRY_MIN_BYTE_LENGTH, nameByteLength),
    `struct_${index + 1}`
  )
  const memoryArea = MEMORY_TYPE_AREAS[memType] || 'UNKNOWN'

  return {
    byteAddr,
    byteLength,
    index,
    memType,
    memoryArea,
    sourceAddressText: formatAddress(byteAddr),
    typeName
  }
}

function parseCodeInfo(bytes) {
  const source = toBytes(bytes)
  if (source.length < FIXED_HEADER_BYTE_LENGTH) {
    throw new Error('info 信息块长度不足，无法解析 Modbus_Code_Info_t 固定头')
  }

  const byteLength = readUint16(source, 0)
  const structCount = readUint16(source, 40)
  const structEntryLength = readUint16(source, 42)
  if (structEntryLength < STRUCT_ENTRY_MIN_BYTE_LENGTH) {
    throw new Error('info 信息块 struct_entry_len 无效')
  }

  const availableTableBytes = Math.max(0, source.length - FIXED_HEADER_BYTE_LENGTH)
  const tableCapacity = Math.floor(availableTableBytes / structEntryLength)
  const safeStructCount = Math.min(structCount, tableCapacity)
  const structTable = []

  for (let index = 0; index < safeStructCount; index += 1) {
    structTable.push(parseStructEntry(
      source,
      FIXED_HEADER_BYTE_LENGTH + index * structEntryLength,
      structEntryLength,
      index
    ))
  }

  return {
    alongDiv: readFloat(source, 12),
    ampGain: readUint16(source, 4),
    busDiv: readFloat(source, 8),
    byteLength,
    caveFreq: source[2] & 0xFF,
    chipModel: readAscii(source, 16, 8),
    model: readUtf8OrAscii(source, 24, 16),
    rawHex: bytesToHex(source, ' '),
    refVolt: (source[3] & 0xFF) / 10,
    refVoltRaw: source[3] & 0xFF,
    rsShunt: readUint16(source, 6),
    structCount,
    structEntryLength,
    structTable,
    tableCapacity,
    truncated: safeStructCount < structCount
  }
}

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) & 0xFFFF,
      sourceAddressText: formatAddress((entry.byteAddr + offset) & 0xFFFF),
      sourceByteLength: 1,
      sourceMemoryArea: entry.memoryArea,
      sourceMemoryClass: entry.memoryArea,
      sourceSymbolName: entry.typeName,
      sourceSymbolType: entry.typeName
    })
  }

  return registers
}

function createGroupsFromCodeInfo(codeInfo, options = {}) {
  const maxRegisters = Math.max(1, Number(options.maxRegistersPerGroup) || 256)
  const groups = []

  codeInfo.structTable.forEach((entry) => {
    if (!entry.byteLength || entry.memoryArea === 'UNKNOWN') 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) & 0xFFFF,
        byteLength: chunkLength
      }
      const suffix = entry.byteLength > maxRegisters ? ` #${Math.floor(offset / maxRegisters) + 1}` : ''

      groups.push({
        addressUnit: 'byte',
        layout: 'struct',
        name: `${entry.typeName}${suffix}`,
        quantity: chunkLength,
        registerType: entry.memoryArea === 'CODE' ? 'input' : 'holding',
        registers: createRegistersForByteSpan(chunkEntry),
        sourceAddress: chunkEntry.byteAddr,
        sourceAddressText: formatAddress(chunkEntry.byteAddr),
        sourceByteLength: chunkLength,
        sourceMemoryArea: entry.memoryArea,
        sourceMemoryClass: entry.memoryArea,
        sourceSegment: 'Modbus_Code_Info_t',
        sourceSegmentModule: '',
        sourceSymbolName: entry.typeName,
        sourceSymbolType: entry.typeName,
        startAddress: formatAddress(chunkEntry.byteAddr)
      })
    }
  })

  return groups
}

module.exports = {
  FIXED_HEADER_BYTE_LENGTH,
  createGroupsFromCodeInfo,
  parseCodeInfo
}
