///|
/// Maximum number of PDUs that can fit in a single Ethernet frame.
/// Conservative estimate based on max frame size and minimum PDU overhead.
pub let max_pdus_per_frame : Int = 15

///|
/// FrameBuilder accumulates PDUs and builds frames efficiently.
/// Uses a running index counter for PDU correlation.
pub(all) struct FrameBuilder {
  mut next_index : Int
  pending : Array[Pdu]
  mut pending_size : Int
} derive(Debug)

///|
/// Create a new FrameBuilder.
pub fn FrameBuilder::new() -> FrameBuilder {
  { next_index: 0, pending: [], pending_size: 0 }
}

///|
/// Reset the builder for a new cycle, keeping the index counter.
pub fn FrameBuilder::reset(self : FrameBuilder) -> Unit {
  self.pending.clear()
  self.pending_size = 0
}

///|
/// Calculate whether adding a PDU with the given data length would exceed frame limits.
fn FrameBuilder::would_exceed(self : FrameBuilder, data_len : Int) -> Bool {
  let new_size = ethernet_header_len +
    ecat_header_len +
    self.pending_size +
    pdu_header_len +
    data_len +
    wkc_len
  new_size > @hal.max_frame_size || self.pending.length() >= max_pdus_per_frame
}

///|
/// Add a PDU to the pending list. Returns the assigned index for correlation.
/// Raises ConfigError if frame capacity would be exceeded.
pub fn FrameBuilder::add(
  self : FrameBuilder,
  command : EcCommand,
  address : UInt,
  data : Bytes,
) -> Byte raise @hal.EcError {
  if self.would_exceed(data.length()) {
    raise @hal.EcError::Internal("frame capacity exceeded")
  }
  let index = (self.next_index % 256).to_byte()
  self.next_index = self.next_index + 1
  let len_flags = data.length().reinterpret_as_uint() & 0x7FFU
  self.pending.push({
    header: { command, index, address, len_flags, irq: 0U },
    data,
    wkc: 0U,
  })
  self.pending_size = self.pending_size +
    pdu_header_len +
    data.length() +
    wkc_len
  index
}

///|
/// Build and return the assembled EcFrame from pending PDUs.
/// Resets the builder afterward.
pub fn FrameBuilder::build(self : FrameBuilder) -> EcFrame {
  let frame : EcFrame = {
    dst_mac: broadcast_mac,
    src_mac: default_master_mac,
    pdus: self.pending.copy(),
  }
  self.reset()
  frame
}

///|
/// Return the number of pending PDUs.
pub fn FrameBuilder::length(self : FrameBuilder) -> Int {
  self.pending.length()
}

///|
/// Check if the builder has no pending PDUs.
pub fn FrameBuilder::is_empty(self : FrameBuilder) -> Bool {
  self.pending.is_empty()
}

///|
/// Find a PDU in a decoded response frame by its index byte.
pub fn find_pdu_by_index(frame : EcFrame, index : Byte) -> Pdu? {
  for pdu in frame.pdus {
    if pdu.header.index == index {
      return Some(pdu)
    }
  }
  None
}