const storageAccessProtocol = require('../../protocols/storage-access/index.js')
const settingsService = require('../../store/settings-store.js')
const transport = require('../../transport/ble-core.js')

let syncedDeviceCaps = {
  addressWidth: 0,
  maxPacketLength: 0,
  memoryEndian: ''
}

function getConfiguredMaxPacketLength(value) {
  const settings = settingsService.getState()
  const numberValue = Number(value === undefined ? settings.parameterMaxPacketLength : value)

  if (Number.isFinite(numberValue) && Math.round(numberValue) === 0) return 0
  if (Number.isFinite(numberValue) && numberValue > 0) return Math.round(numberValue)

  return 64
}

function resolveMaxPacketLength(value) {
  const configuredMaxPacketLength = getConfiguredMaxPacketLength(value)
  const deviceMaxPacketLength = Number(syncedDeviceCaps.maxPacketLength || 0)

  if (!Number.isFinite(deviceMaxPacketLength) || deviceMaxPacketLength <= 0) return configuredMaxPacketLength
  if (configuredMaxPacketLength === 0) return Math.round(deviceMaxPacketLength)

  return Math.min(configuredMaxPacketLength, Math.round(deviceMaxPacketLength))
}

function normalizeProtocolIoOptions(options = {}) {
  const maxPacketLength = options.maxPacketLength === undefined ? options.maxFrameBytes : options.maxPacketLength

  return {
    expectedByteLength: options.expectedByteLength,
    maxFrameBytes: options.useDeviceCaps === false
      ? getConfiguredMaxPacketLength(maxPacketLength)
      : resolveMaxPacketLength(maxPacketLength),
    onChunk: options.onChunk,
    showModal: options.showModal !== false
  }
}

function updateSyncedDeviceCaps(caps = {}) {
  const addressWidth = Number(caps.addressWidth)
  const maxPacketLength = Number(caps.maxPacketLength)
  const memoryEndian = String(caps.memoryEndian || '').trim().toLowerCase()

  syncedDeviceCaps = {
    addressWidth: addressWidth === 16 || addressWidth === 32 ? addressWidth : 0,
    maxPacketLength: Number.isFinite(maxPacketLength) && maxPacketLength > 0
      ? Math.round(maxPacketLength)
      : 0,
    memoryEndian: memoryEndian === 'little' ? 'little' : (memoryEndian === 'big' ? 'big' : '')
  }
}

function getSyncedDeviceCaps() {
  return {
    ...syncedDeviceCaps
  }
}

async function readMemory(area, startAddress, byteLength, label, kind, options = {}) {
  const protocolIoOptions = normalizeProtocolIoOptions(options)
  const normalizedArea = storageAccessProtocol.normalizeMemoryArea(area)
  const bytes = []
  const chunks = storageAccessProtocol.getReadChunks(startAddress, byteLength, {
    ...protocolIoOptions,
    area: normalizedArea
  })

  for (const chunk of chunks) {
    const response = await transport.sendManagedFrame(
      storageAccessProtocol.buildReadFrame(normalizedArea, chunk.address, chunk.quantity, {
        maxFrameBytes: protocolIoOptions.maxFrameBytes
      }),
      storageAccessProtocol.getChunkLabel(label, chunks, chunk),
      storageAccessProtocol.createExpected(normalizedArea, chunk.address, chunk.quantity, false, kind),
      {
        maxFrameBytes: protocolIoOptions.maxFrameBytes,
        showModal: protocolIoOptions.showModal
      }
    )
    if (!response) return null

    const dataBytes = Array.isArray(response.dataBytes) ? response.dataBytes : []
    dataBytes.forEach((byte, index) => {
      bytes[chunk.address - startAddress + index] = Number(byte) & 0xFF
    })

    if (typeof protocolIoOptions.onChunk === 'function') {
      protocolIoOptions.onChunk(response, chunk)
    }
  }

  return bytes
}

async function writeMemory(area, startAddress, bytes, label, kind, options = {}) {
  const protocolIoOptions = normalizeProtocolIoOptions(options)
  const normalizedArea = storageAccessProtocol.normalizeMemoryArea(area)
  const chunks = storageAccessProtocol.getWriteChunks(startAddress, bytes, {
    ...protocolIoOptions,
    area: normalizedArea
  })

  for (const chunk of chunks) {
    const response = await transport.sendManagedFrame(
      storageAccessProtocol.buildWriteFrame(normalizedArea, chunk.address, chunk.dataBytes, {
        maxFrameBytes: protocolIoOptions.maxFrameBytes
      }),
      storageAccessProtocol.getChunkLabel(label, chunks, chunk),
      storageAccessProtocol.createExpected(normalizedArea, chunk.address, chunk.quantity, true, kind),
      {
        maxFrameBytes: protocolIoOptions.maxFrameBytes,
        showModal: protocolIoOptions.showModal
      }
    )
    if (!response) return false

    if (typeof protocolIoOptions.onChunk === 'function') {
      protocolIoOptions.onChunk(response, chunk)
    }
  }

  return true
}

async function executeControl(operation, dataBytes, label, kind, options = {}) {
  const protocolIoOptions = normalizeProtocolIoOptions(options)
  const response = await transport.sendManagedFrame(
    storageAccessProtocol.buildControlFrame(operation, dataBytes),
    label,
    storageAccessProtocol.createControlExpected(operation, kind, {
      expectedByteLength: protocolIoOptions.expectedByteLength
    }),
    {
      maxFrameBytes: protocolIoOptions.maxFrameBytes,
      showModal: protocolIoOptions.showModal
    }
  )

  return response || null
}

module.exports = {
  executeControl,
  getSyncedDeviceCaps,
  normalizeProtocolIoOptions,
  readMemory,
  resolveMaxPacketLength,
  updateSyncedDeviceCaps,
  writeMemory
}
