///|
/// ExpectedSubmoduleBlockReq — the controller's expected module/submodule
/// layout, matched by the device against its real configuration during Connect.
/// Structure mirrors the C# reference chain
/// `ExpectedSubmoduleBlockReq` → `IoSlotInformation` → `IoSubslotInformation`
/// → `IoDataInformation`, all big-endian.

///|
/// DataDescription kinds (submodule direction).
pub let data_description_input : Int = 1

///|
pub let data_description_output : Int = 2

///|
/// One submodule data description: direction, data length, and the IOCS/IOPS
/// status byte lengths (1 each by default).
pub(all) struct DataDescription {
  data_description : Int
  data_length : Int
  iocs_length : Int
  iops_length : Int
} derive(Eq, Debug)

///|
/// Submodule entry within a slot.
pub(all) struct ExpectedSubmodule {
  subslot_number : Int
  submodule_ident : Int
  submodule_properties : Int
  data_descriptions : Array[DataDescription]
} derive(Eq, Debug)

///|
/// Slot entry within an API. `api` is the C# `IoSlotInformation.ID` field
/// (the API number) emitted first on the wire.
pub(all) struct ExpectedSlot {
  api : Int
  slot_number : Int
  module_ident : Int
  module_properties : Int
  submodules : Array[ExpectedSubmodule]
} derive(Eq, Debug)

///|
/// ExpectedSubmoduleBlockReq body: a count-prefixed list of slot entries.
pub(all) struct ExpectedSubmoduleBlockReq {
  apis : Array[ExpectedSlot]
} derive(Eq, Debug)

///|
fn encode_data_description(output : Array[Byte], dd : DataDescription) -> Unit {
  push_u16_be(output, dd.data_description)
  push_u16_be(output, dd.data_length)
  output.push((dd.iocs_length & 0xFF).to_byte())
  output.push((dd.iops_length & 0xFF).to_byte())
}

///|
fn encode_expected_submodule(
  output : Array[Byte],
  sub : ExpectedSubmodule,
) -> Unit {
  push_u16_be(output, sub.subslot_number)
  push_u32_be(output, sub.submodule_ident)
  push_u16_be(output, sub.submodule_properties)
  for dd in sub.data_descriptions {
    encode_data_description(output, dd)
  }
}

///|
fn encode_expected_slot(output : Array[Byte], slot : ExpectedSlot) -> Unit {
  push_u32_be(output, slot.api)
  push_u16_be(output, slot.slot_number)
  push_u32_be(output, slot.module_ident)
  push_u16_be(output, slot.module_properties)
  push_u16_be(output, slot.submodules.length())
  for sub in slot.submodules {
    encode_expected_submodule(output, sub)
  }
}

///|
/// Encode the ExpectedSubmodule block body (without the IO block header).
pub fn encode_expected_submodule_block_req(
  req : ExpectedSubmoduleBlockReq,
) -> Bytes {
  let output : Array[Byte] = []
  push_u16_be(output, req.apis.length())
  for slot in req.apis {
    encode_expected_slot(output, slot)
  }
  Bytes::from_array(output)
}