const storageAccessProtocol = require('../../protocols/storage-access/index.js')
const transport = require('../../transport/ble-core.js')
const {
  formatHexNumber
} = require('../../utils/binary-utils.js')
const {
  createGroupsFromCodeInfo,
  parseCodeInfo
} = require('../../domain/storage-access/code-info-parser.js')
const {
  cloneImportedGroup,
  normalizeGroup
} = require('../../domain/parameter-groups/model.js')
const protocolIo = require('./protocol-io.js')

function showCodeInfoError(label, message, options = {}) {
  if (options.showModal === false) return

  transport.showCommandAlert(label, message)
}

async function readCodeInfoBlock(label = '同步CodeInfo', kind = 'storage-code-info-read', options = {}) {
  const protocolIoOptions = protocolIo.normalizeProtocolIoOptions({
    ...options,
    useDeviceCaps: false
  })
  const descriptorBytes = await protocolIo.readMemory(
    storageAccessProtocol.AREA.CODEINFO,
    storageAccessProtocol.CODE_INFO_DESCRIPTOR_ADDRESS,
    storageAccessProtocol.CODE_INFO_DESCRIPTOR_BYTE_LENGTH,
    label,
    `${kind}-descriptor`,
    {
      ...protocolIoOptions,
      showModal: false,
      useDeviceCaps: false
    }
  )
  if (!descriptorBytes) {
    showCodeInfoError(label, 'CodeInfo 描述符读取失败或超时', protocolIoOptions)
    return null
  }

  let descriptor
  try {
    descriptor = storageAccessProtocol.parseCodeInfoDescriptorBytes(descriptorBytes)
  } catch (error) {
    showCodeInfoError(label, error.message || 'CodeInfo 描述符长度无效', protocolIoOptions)
    return null
  }

  let codeInfoAddressWidth
  try {
    codeInfoAddressWidth = storageAccessProtocol.normalizeDescriptorAddressWidth(
      descriptor.codeInfoAddressWidth
    )
  } catch (error) {
    showCodeInfoError(label, error.message || 'CodeInfo 描述符地址长度无效', protocolIoOptions)
    return null
  }

  const codeInfoAddress = Number(descriptor.codeInfoAddress || 0)
  const codeInfoByteLength = Number(descriptor.codeInfoByteLength || 0)
  const codeInfoMaxPacketLength = Number(descriptor.codeInfoMaxPacketLength || 0) & 0xFFFF
  const codeInfoMemoryEndian = String(descriptor.codeInfoMemoryEndian || 'little').trim()
  const codeInfoMemoryType = codeInfoAddressWidth === 32
    ? storageAccessProtocol.AREA.ADDR32
    : storageAccessProtocol.AREA.CODE
  const codeInfoReadAddress = codeInfoAddressWidth === 32 ? codeInfoAddress : (codeInfoAddress & 0xFFFF)
  const codeInfoMaxFrameBytes = storageAccessProtocol.resolveDescriptorMaxFrameBytes(
    protocolIoOptions.maxFrameBytes,
    codeInfoMaxPacketLength
  )

  if (!codeInfoByteLength || codeInfoByteLength > 0xFFFF) {
    showCodeInfoError(label, 'CodeInfo 信息块长度无效', protocolIoOptions)
    return null
  }

  const codeInfoBytes = await protocolIo.readMemory(
    codeInfoMemoryType,
    codeInfoReadAddress,
    codeInfoByteLength,
    label,
    kind,
    {
      ...protocolIoOptions,
      maxFrameBytes: codeInfoMaxFrameBytes,
      memoryEndian: codeInfoMemoryEndian,
      useDeviceCaps: false
    }
  )
  if (!codeInfoBytes) return null

  return {
    codeInfoAddress,
    codeInfoAddressWidth,
    codeInfoByteLength,
    codeInfoBytes,
    codeInfoDescriptorBytes: descriptorBytes,
    codeInfoMaxPacketLength,
    codeInfoMemoryEndian,
    codeInfoMemoryEndianMark: Number(descriptor.codeInfoMemoryEndianMark || 0) & 0xFFFF,
    codeInfoMemoryType
  }
}

async function syncCodeInfo(options = {}) {
  const result = await readCodeInfoBlock(
    options.label || '同步CodeInfo',
    options.kind || 'storage-code-info-read',
    {
      maxPacketLength: options.maxPacketLength,
      showModal: options.showModal !== false
    }
  )
  if (!result) {
    return {
      ok: false
    }
  }

  const descriptorCaps = {
    addressWidth: result.codeInfoAddressWidth,
    codeInfoByteLength: result.codeInfoByteLength,
    maxPacketLength: result.codeInfoMaxPacketLength,
    memoryEndian: result.codeInfoMemoryEndian,
    memoryEndianMark: result.codeInfoMemoryEndianMark
  }
  let codeInfo
  let importedGroups
  try {
    codeInfo = parseCodeInfo(result.codeInfoBytes, descriptorCaps)
    importedGroups = createGroupsFromCodeInfo(codeInfo, options)
      .map(cloneImportedGroup)
      .map(normalizeGroup)
  } catch (error) {
    const errorText = error.message || 'CodeInfo 解析失败'

    showCodeInfoError(options.label || '同步CodeInfo', errorText, options)
    return {
      errorText,
      ok: false
    }
  }

  protocolIo.updateSyncedDeviceCaps(descriptorCaps)

  return {
    codeInfoAddress: result.codeInfoAddress,
    codeInfoAddressText: formatHexNumber(result.codeInfoAddress, 8),
    codeInfoAddressWidth: result.codeInfoAddressWidth,
    codeInfoByteLength: result.codeInfoByteLength,
    codeInfoByteLengthText: formatHexNumber(result.codeInfoByteLength, 8),
    codeInfoBytes: result.codeInfoBytes,
    codeInfo,
    codeInfoDescriptorBytes: result.codeInfoDescriptorBytes,
    codeInfoMaxPacketLength: result.codeInfoMaxPacketLength,
    codeInfoMemoryEndian: result.codeInfoMemoryEndian,
    codeInfoMemoryEndianMark: result.codeInfoMemoryEndianMark,
    codeInfoMemoryType: result.codeInfoMemoryType,
    entryCount: codeInfo.entryCount,
    enumCount: codeInfo.enumCount,
    groupCount: importedGroups.length,
    importedGroups,
    ok: true,
    arrayCount: codeInfo.arrayCount,
    structCount: codeInfo.structCount,
    variableCount: codeInfo.variableCount
  }
}

module.exports = {
  readCodeInfoBlock,
  syncCodeInfo
}
