///|
/// IOD Read Request Header.
pub(all) struct IODReadReqHeader {
  seq_number : Int
  ar_uuid : @rpc.Uuid
  api : Int
  slot_number : Int
  subslot_number : Int
  index : Int
  record_data_length : Int
} derive(Eq, Debug)

///|
/// Encode IOD Read Request Header (block body, 58 bytes).
pub fn encode_iod_read_req_header(req : IODReadReqHeader) -> Bytes {
  let output : Array[Byte] = []
  push_u16_be(output, req.seq_number)
  append_bytes(output, req.ar_uuid.to_dce_bytes_le())
  push_u32_be(output, req.api)
  push_u16_be(output, req.slot_number)
  push_u16_be(output, req.subslot_number)
  // 2 bytes padding
  push_u16_be(output, 0)
  push_u16_be(output, req.index)
  push_u32_be(output, req.record_data_length)
  // target_ar_uuid + reserved padding (24 bytes in the Read.req layout)
  for i = 0; i < 16; i = i + 1 {
    output.push(b'\x00')
  }
  for i = 0; i < 8; i = i + 1 {
    output.push(b'\x00')
  }
  Bytes::from_array(output)
}

///|
/// IOD Read Response Header.
pub(all) struct IODReadResHeader {
  seq_number : Int
  ar_uuid : @rpc.Uuid
  api : Int
  slot_number : Int
  subslot_number : Int
  index : Int
  record_data_length : Int
  additional_value1 : Int
  additional_value2 : Int
} derive(Eq, Debug)

///|
/// Parse IOD Read Response Header from body bytes.
pub fn parse_iod_read_res_header(
  data : Bytes,
  offset : Int,
) -> IODReadResHeader raise @frame.FrameError {
  guard data.length() >= offset + 38 else {
    raise @frame.FrameError::InvalidMacLength(data.length())
  }
  IODReadResHeader::{
    seq_number: read_u16_be(data, offset),
    ar_uuid: @rpc.Uuid::from_dce_bytes_le(data, offset + 2),
    api: read_u32_be(data, offset + 18),
    slot_number: read_u16_be(data, offset + 22),
    subslot_number: read_u16_be(data, offset + 24),
    index: read_u16_be(data, offset + 28),
    record_data_length: read_u32_be(data, offset + 30),
    additional_value1: read_u16_be(data, offset + 34),
    additional_value2: read_u16_be(data, offset + 36),
  }
}

///|
/// IOD Write Request Header.
pub(all) struct IODWriteReqHeader {
  seq_number : Int
  ar_uuid : @rpc.Uuid
  api : Int
  slot_number : Int
  subslot_number : Int
  index : Int
  record_data_length : Int
} derive(Eq, Debug)

///|
/// Encode IOD Write Request Header (block body, 38 bytes).
pub fn encode_iod_write_req_header(req : IODWriteReqHeader) -> Bytes {
  let output : Array[Byte] = []
  push_u16_be(output, req.seq_number)
  append_bytes(output, req.ar_uuid.to_dce_bytes_le())
  push_u32_be(output, req.api)
  push_u16_be(output, req.slot_number)
  push_u16_be(output, req.subslot_number)
  // 2 bytes padding
  push_u16_be(output, 0)
  push_u16_be(output, req.index)
  push_u32_be(output, req.record_data_length)
  // target_ar_uuid padding (16 bytes zeros)
  for i = 0; i < 16; i = i + 1 {
    output.push(b'\x00')
  }
  Bytes::from_array(output)
}

///|
/// Encode IOD Write Request Header plus RecordDataWrite bytes.
///
/// This mirrors the C# `ak` block used by `DceClient.Write`: the fixed header
/// is 58 bytes, followed by the user-specified record data. The surrounding IO
/// block length field must still exclude the user data bytes.
pub fn encode_iod_write_req_with_data(
  req : IODWriteReqHeader,
  record_data : Bytes,
) -> Bytes {
  let output : Array[Byte] = []
  push_u16_be(output, req.seq_number)
  append_bytes(output, req.ar_uuid.to_dce_bytes_le())
  push_u32_be(output, req.api)
  push_u16_be(output, req.slot_number)
  push_u16_be(output, req.subslot_number)
  // 2 bytes padding
  push_u16_be(output, 0)
  push_u16_be(output, req.index)
  push_u32_be(output, record_data.length())
  // target_ar_uuid + reserved padding (24 bytes in the Write.req layout)
  for i = 0; i < 24; i = i + 1 {
    output.push(b'\x00')
  }
  append_bytes(output, record_data)
  Bytes::from_array(output)
}

///|
/// IOD Control Request — used for PrmEnd, ApplicationReady, Release.
pub(all) struct IODControlReq {
  ar_uuid : @rpc.Uuid
  session_key : Int
  control_command : Int
  control_block_properties : Int
} derive(Eq, Debug)

///|
/// Encode IOD Control Request body.
pub fn encode_iod_control_req(req : IODControlReq) -> Bytes {
  let output : Array[Byte] = []
  // 2 bytes padding
  push_u16_be(output, 0)
  append_bytes(output, req.ar_uuid.to_dce_bytes_le())
  push_u16_be(output, req.session_key)
  // 2 bytes padding
  push_u16_be(output, 0)
  push_u16_be(output, req.control_command)
  push_u16_be(output, req.control_block_properties)
  Bytes::from_array(output)
}

///|
/// Parse IOD Control Request from body bytes.
pub fn parse_iod_control_req(
  data : Bytes,
  offset : Int,
) -> IODControlReq raise @frame.FrameError {
  guard data.length() >= offset + 26 else {
    raise @frame.FrameError::InvalidMacLength(data.length())
  }
  IODControlReq::{
    ar_uuid: @rpc.Uuid::from_dce_bytes_le(data, offset + 2),
    session_key: read_u16_be(data, offset + 18),
    control_command: read_u16_be(data, offset + 22),
    control_block_properties: read_u16_be(data, offset + 24),
  }
}

///|
/// Release Block Request.
pub(all) struct ReleaseBlockReq {
  ar_uuid : @rpc.Uuid
  session_key : Int
  control_command : Int
  control_block_properties : Int
} derive(Eq, Debug)

///|
/// Encode Release Block Request body (same layout as IOD Control).
pub fn encode_release_block_req(req : ReleaseBlockReq) -> Bytes {
  encode_iod_control_req(IODControlReq::{
    ar_uuid: req.ar_uuid,
    session_key: req.session_key,
    control_command: req.control_command,
    control_block_properties: req.control_block_properties,
  })
}

///|
/// Format IOD Read Request summary.
pub fn format_iod_read_req(req : IODReadReqHeader) -> String {
  let lines : Array[String] = []
  lines.push("seq=" + req.seq_number.to_string())
  lines.push("ar_uuid=" + req.ar_uuid.to_hex())
  lines.push("api=" + req.api.to_string())
  lines.push("slot=" + req.slot_number.to_string())
  lines.push("subslot=" + req.subslot_number.to_string())
  lines.push("index=0x" + @frame.uint16_to_hex(req.index))
  lines.push("record_data_length=" + req.record_data_length.to_string())
  lines.join("\n")
}

///|
/// Format IOD Control summary.
pub fn format_iod_control(req : IODControlReq) -> String {
  let lines : Array[String] = []
  lines.push("ar_uuid=" + req.ar_uuid.to_hex())
  lines.push("session_key=" + req.session_key.to_string())
  lines.push("control_command=" + control_command_label(req.control_command))
  lines.join("\n")
}