///|
/// FrameState tracks the lifecycle of a frame slot in the pool.
/// Transitions: Free 鈫?Acquired 鈫?Filled 鈫?Sending 鈫?Sent 鈫?Free
///              Free 鈫?Receiving 鈫?Received 鈫?Free
pub(all) enum FrameState {
  Free
  Acquired
  Filled
  Sending
  Sent
  Receiving
  Received
} derive(Eq, Debug)

///|
/// FrameRef is a lightweight value-type reference to a frame slot.
/// No heap allocation 鈥?just 4 integers identifying the slice of data.
pub(all) struct FrameRef {
  pool_id : Int
  slot : Int
  offset : Int
  length : Int
} derive(Eq, Debug)

///|
/// FramePool provides pre-allocated frame buffers that eliminate
/// per-cycle heap allocation in the PDO hot path.
///
/// Inspired by EtherCrab's PduStorage (atomic frame pool) and
/// CherryECAT's DMA descriptor ring (static buffer reuse).
pub(all) struct FramePool {
  id : Int
  frames : FixedArray[FixedArray[Byte]]
  states : FixedArray[FrameState]
  lengths : FixedArray[Int]
  capacity : Int
  frame_size : Int
  mut next_scan : Int
}

///|
/// Default number of frame slots in a pool.
pub let default_pool_capacity : Int = 16

///|
/// Create a new FramePool with the given capacity and frame size.
/// All buffers are pre-allocated; no further allocation occurs during operation.
pub fn FramePool::new(
  capacity? : Int = default_pool_capacity,
  frame_size? : Int = max_frame_size,
  id? : Int = 0,
) -> FramePool {
  let frames : FixedArray[FixedArray[Byte]] = FixedArray::makei(capacity, fn(
    _i,
  ) {
    FixedArray::make(frame_size, b'\x00')
  })
  let states = FixedArray::make(capacity, Free)
  let lengths = FixedArray::make(capacity, 0)
  { id, frames, states, lengths, capacity, frame_size, next_scan: 0 }
}

///|
/// Acquire a free frame slot from the pool.
/// Scans from the last allocation point (round-robin) for O(1) amortized.
/// Returns EcError if no free slots are available.
pub fn FramePool::acquire(self : FramePool) -> FrameRef raise EcError {
  let start = self.next_scan
  for i = 0; i < self.capacity; i = i + 1 {
    let idx = (start + i) % self.capacity
    if self.states[idx] == Free {
      self.states[idx] = Acquired
      self.lengths[idx] = 0
      self.next_scan = (idx + 1) % self.capacity
      return { pool_id: self.id, slot: idx, offset: 0, length: 0 }
    }
  }
  raise EcError::Internal("FramePool: no free slots")
}

///|
/// Get the writable buffer for an acquired frame slot.
/// The caller fills this buffer with frame data, then calls mark_filled.
pub fn FramePool::buffer(self : FramePool, fref : FrameRef) -> FixedArray[Byte] {
  self.frames[fref.slot]
}

///|
/// Mark a frame as filled with the given data length.
pub fn FramePool::mark_filled(
  self : FramePool,
  fref : FrameRef,
  length : Int,
) -> FrameRef {
  self.states[fref.slot] = Filled
  self.lengths[fref.slot] = length
  { ..fref, length, }
}

///|
/// Mark a frame as being sent by the platform backend.
pub fn FramePool::mark_sending(self : FramePool, fref : FrameRef) -> Unit {
  self.states[fref.slot] = Sending
}

///|
/// Mark a frame as sent (transmission complete).
pub fn FramePool::mark_sent(self : FramePool, fref : FrameRef) -> Unit {
  self.states[fref.slot] = Sent
}

///|
/// Acquire a slot for receiving into.
pub fn FramePool::acquire_rx(self : FramePool) -> FrameRef raise EcError {
  let fref = self.acquire()
  self.states[fref.slot] = Receiving
  fref
}

///|
/// Mark a receive slot as having received data.
pub fn FramePool::mark_received(
  self : FramePool,
  fref : FrameRef,
  length : Int,
) -> FrameRef {
  self.states[fref.slot] = Received
  self.lengths[fref.slot] = length
  { ..fref, length, }
}

///|
/// Release a frame slot back to the pool for reuse.
pub fn FramePool::release(self : FramePool, fref : FrameRef) -> Unit {
  self.states[fref.slot] = Free
  self.lengths[fref.slot] = 0
}

///|
/// Get the current state of a frame slot.
pub fn FramePool::state(self : FramePool, fref : FrameRef) -> FrameState {
  self.states[fref.slot]
}

///|
/// Get the effective data length for a frame slot.
pub fn FramePool::data_length(self : FramePool, fref : FrameRef) -> Int {
  self.lengths[fref.slot]
}

///|
/// Count the number of free slots available.
pub fn FramePool::free_count(self : FramePool) -> Int {
  let mut count = 0
  for i = 0; i < self.capacity; i = i + 1 {
    if self.states[i] == Free {
      count = count + 1
    }
  }
  count
}

///|
/// Reset all slots to Free state. Used for pool reinitialization.
pub fn FramePool::reset(self : FramePool) -> Unit {
  for i = 0; i < self.capacity; i = i + 1 {
    self.states[i] = Free
    self.lengths[i] = 0
  }
  self.next_scan = 0
}

///|
/// Write a single byte into a frame buffer at the given offset.
pub fn FramePool::write_byte(
  self : FramePool,
  fref : FrameRef,
  offset : Int,
  value : Byte,
) -> Unit {
  self.frames[fref.slot][offset] = value
}

///|
/// Read a single byte from a frame buffer at the given offset.
pub fn FramePool::read_byte(
  self : FramePool,
  fref : FrameRef,
  offset : Int,
) -> Byte {
  self.frames[fref.slot][offset]
}

///|
/// Copy data from a FixedArray into a frame buffer at the given offset.
pub fn FramePool::write_bytes(
  self : FramePool,
  fref : FrameRef,
  offset : Int,
  src : FixedArray[Byte],
  src_offset : Int,
  count : Int,
) -> Unit {
  let buf = self.frames[fref.slot]
  for i = 0; i < count; i = i + 1 {
    buf[offset + i] = src[src_offset + i]
  }
}

///|
/// Copy data from a frame buffer into a FixedArray at the given offset.
pub fn FramePool::read_bytes(
  self : FramePool,
  fref : FrameRef,
  offset : Int,
  dst : FixedArray[Byte],
  dst_offset : Int,
  count : Int,
) -> Unit {
  let buf = self.frames[fref.slot]
  for i = 0; i < count; i = i + 1 {
    dst[dst_offset + i] = buf[offset + i]
  }
}

///|
/// Write a 16-bit little-endian value into a frame buffer.
pub fn FramePool::write_u16_le(
  self : FramePool,
  fref : FrameRef,
  offset : Int,
  value : Int,
) -> Unit {
  let buf = self.frames[fref.slot]
  buf[offset] = (value & 0xFF).to_byte()
  buf[offset + 1] = ((value >> 8) & 0xFF).to_byte()
}

///|
/// Read a 16-bit little-endian value from a frame buffer.
pub fn FramePool::read_u16_le(
  self : FramePool,
  fref : FrameRef,
  offset : Int,
) -> Int {
  let buf = self.frames[fref.slot]
  buf[offset].to_int() | (buf[offset + 1].to_int() << 8)
}

///|
/// Write a 32-bit little-endian value into a frame buffer.
pub fn FramePool::write_u32_le(
  self : FramePool,
  fref : FrameRef,
  offset : Int,
  value : Int,
) -> Unit {
  let buf = self.frames[fref.slot]
  buf[offset] = (value & 0xFF).to_byte()
  buf[offset + 1] = ((value >> 8) & 0xFF).to_byte()
  buf[offset + 2] = ((value >> 16) & 0xFF).to_byte()
  buf[offset + 3] = ((value >> 24) & 0xFF).to_byte()
}

///|
/// Read a 32-bit little-endian value from a frame buffer.
pub fn FramePool::read_u32_le(
  self : FramePool,
  fref : FrameRef,
  offset : Int,
) -> Int {
  let buf = self.frames[fref.slot]
  buf[offset].to_int() |
  (buf[offset + 1].to_int() << 8) |
  (buf[offset + 2].to_int() << 16) |
  (buf[offset + 3].to_int() << 24)
}