const {
  buildReadFrame,
  buildWriteMultipleRegistersFrame,
  buildWriteSingleCoilFrame,
  buildWriteSingleRegisterFrame,
  getMaxReadQuantity,
  getMaxWriteMultipleRegisterQuantity
} = require('./frame.js')
const settingsService = require('../../store/settings-store.js')
const transport = require('../../transport/ble-core.js')
const {
  addCoilReadValues,
  addWordReadValues
} = require('../../utils/register-value-utils.js')

function getSharedSlaveAddress(title = '从机地址错误') {
  try {
    return settingsService.getModbusSlaveAddress()
  } catch (error) {
    transport.showCommandAlert(title, error.message)
    return null
  }
}

function formatAddress(value) {
  return Number(value || 0).toString(16).toUpperCase()
}

function getChunkLabel(label, chunks, chunk) {
  if (!label || chunks.length <= 1) return label

  return `${label} ${formatAddress(chunk.address)}-${formatAddress(chunk.address + chunk.quantity - 1)}`
}

function splitQuantity(startAddress, quantity, maxQuantity) {
  const chunks = []
  let address = Number(startAddress) || 0
  let remaining = Math.max(0, Math.floor(Number(quantity) || 0))
  const chunkLimit = Math.max(1, Math.floor(Number(maxQuantity) || remaining || 1))

  while (remaining > 0) {
    const chunkQuantity = Math.min(remaining, chunkLimit)
    chunks.push({
      address,
      quantity: chunkQuantity
    })
    address += chunkQuantity
    remaining -= chunkQuantity
  }

  return chunks
}

function getReadChunks(functionCode, startAddress, quantity, options = {}) {
  const maxQuantity = getMaxReadQuantity(functionCode, options.maxFrameBytes)

  return splitQuantity(startAddress, quantity, maxQuantity || quantity)
}

async function sendReadChunk(slaveAddress, functionCode, chunk, label, kind, options = {}) {
  return transport.sendManagedFrame(
    buildReadFrame(slaveAddress, functionCode, chunk.address, chunk.quantity, {
      maxFrameBytes: options.maxFrameBytes
    }),
    label,
    {
      address: chunk.address,
      functionCode,
      kind,
      quantity: chunk.quantity,
      slaveAddress
    },
    {
      maxFrameBytes: options.maxFrameBytes,
      showModal: options.showModal
    }
  )
}

async function readSpans(slaveAddress, functionCode, spans, label, kind, options = {}) {
  const readValues = {
    coils: {},
    words: {}
  }
  const normalizedSpans = (spans || []).filter((span) => span && span.quantity > 0)

  for (const span of normalizedSpans) {
    const chunks = getReadChunks(functionCode, span.address, span.quantity, options)

    for (const chunk of chunks) {
      const response = await sendReadChunk(
        slaveAddress,
        functionCode,
        chunk,
        getChunkLabel(label, chunks, chunk),
        kind,
        options
      )
      if (!response) return null

      if (functionCode === 0x01 || functionCode === 0x02) {
        addCoilReadValues(readValues, chunk.address, chunk.quantity, response)
      } else {
        addWordReadValues(readValues, chunk.address, response)
      }

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

  return readValues
}

async function readRegisterWords(slaveAddress, functionCode, startAddress, quantity, label, kind, options = {}) {
  const words = []
  const chunks = getReadChunks(functionCode, startAddress, quantity, options)

  for (const chunk of chunks) {
    const response = await sendReadChunk(
      slaveAddress,
      functionCode,
      chunk,
      getChunkLabel(label, chunks, chunk),
      kind,
      options
    )
    if (!response) return null

    const chunkWords = response.words || []
    chunkWords.forEach((word, index) => {
      words[chunk.address - startAddress + index] = Number(word) & 0xFFFF
    })

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

  return words
}

async function readBitValues(slaveAddress, functionCode, startAddress, quantity, label, kind, options = {}) {
  const result = await readSpans(
    slaveAddress,
    functionCode,
    [{ address: startAddress, quantity }],
    label,
    kind,
    options
  )

  return result ? result.coils : null
}

async function readSingleHoldingWord(slaveAddress, address, label = '读取配对寄存器', kind = 'holding-word-read') {
  const words = await readRegisterWords(slaveAddress, 0x03, address, 1, label, kind)

  return words && Number.isInteger(words[0]) ? words[0] & 0xFFFF : null
}

function writeSingleCoil(slaveAddress, address, checked, label, kind = 'coil-write', options = {}) {
  const coilValue = checked ? 0xFF00 : 0x0000

  return transport.sendManagedFrame(
    buildWriteSingleCoilFrame(slaveAddress, address, checked),
    label,
    {
      address,
      functionCode: 0x05,
      kind,
      quantity: 1,
      slaveAddress,
      value: coilValue
    },
    {
      maxFrameBytes: options.maxFrameBytes,
      showModal: options.showModal
    }
  )
}

function writeSingleRegister(slaveAddress, address, value, label, kind = 'register-write', options = {}) {
  return transport.sendManagedFrame(
    buildWriteSingleRegisterFrame(slaveAddress, address, value),
    label,
    {
      address,
      functionCode: 0x06,
      kind,
      quantity: 1,
      slaveAddress,
      value
    },
    {
      maxFrameBytes: options.maxFrameBytes,
      showModal: options.showModal
    }
  )
}

function writeMultipleRegisters(slaveAddress, address, values, label, kind = 'registers-write', options = {}) {
  return transport.sendManagedFrame(
    buildWriteMultipleRegistersFrame(slaveAddress, address, values, {
      maxFrameBytes: options.maxFrameBytes
    }),
    label,
    {
      address,
      functionCode: 0x10,
      kind,
      quantity: values.length,
      slaveAddress
    },
    {
      maxFrameBytes: options.maxFrameBytes,
      showModal: options.showModal
    }
  )
}

module.exports = {
  getReadChunks,
  getSharedSlaveAddress,
  getMaxReadQuantity,
  readBitValues,
  readRegisterWords,
  readSingleHoldingWord,
  readSpans,
  splitQuantity,
  getMaxWriteMultipleRegisterQuantity,
  writeMultipleRegisters,
  writeSingleCoil,
  writeSingleRegister
}
