///|
pub(all) struct RenderFramePacket {
  sequence : Int
  surface_generation : Int
  input : @render.RenderFrameInput
} derive(Eq, Debug, ToJson)

///|
pub extend RenderFramePacket with Debug::{to_repr}

///|
pub extend RenderFramePacket with Eq::{not_equal, equal}

///|
pub extend RenderFramePacket with ToJson::{to_json}

///|
pub(all) struct RenderFrameMailboxDiagnostics {
  submitted : Int
  started : Int
  completed : Int
  replaced_pending : Int
  rejected_generation : Int
  surface_generation : Int
  in_flight : Bool
  pending : Bool
  control_pending : Int
  control_submitted : Int
  control_drained : Int
} derive(Eq, Debug, ToJson)

///|
pub extend RenderFrameMailboxDiagnostics with Debug::{to_repr}

///|
pub extend RenderFrameMailboxDiagnostics with Eq::{not_equal, equal}

///|
pub extend RenderFrameMailboxDiagnostics with ToJson::{to_json}

///|
pub struct RenderFrameMailbox {
  mut surface_generation : Int
  mut next_sequence : Int
  mut in_flight : RenderFramePacket?
  mut pending : RenderFramePacket?
  mut submitted : Int
  mut started : Int
  mut completed : Int
  mut replaced_pending : Int
  mut rejected_generation : Int
  control_queue : Array[@render.RendererControlMessage]
  mut control_submitted : Int
  mut control_drained : Int
}

///|
pub fn RenderFramePacket::new(
  sequence~ : Int,
  surface_generation~ : Int,
  input : @render.RenderFrameInput,
) -> RenderFramePacket {
  {
    sequence,
    surface_generation,
    input: { ..input, commands: input.commands.copy(), },
  }
}

///|
pub fn RenderFrameMailbox::new(
  surface_generation? : Int = 1,
) -> RenderFrameMailbox {
  {
    surface_generation,
    next_sequence: 1,
    in_flight: None,
    pending: None,
    submitted: 0,
    started: 0,
    completed: 0,
    replaced_pending: 0,
    rejected_generation: 0,
    control_queue: [],
    control_submitted: 0,
    control_drained: 0,
  }
}

///|
/// Appends a control message to the ordered queue. Control messages are
/// never dropped and are drained FIFO by the renderer thread before
/// consuming the next frame. Returns the queue depth after submission so
/// callers can detect backpressure (e.g. a host that keeps submitting
/// resize without draining).
pub fn RenderFrameMailbox::submit_control(
  self : RenderFrameMailbox,
  message : @render.RendererControlMessage,
) -> Int {
  self.control_queue.push(message)
  self.control_submitted = self.control_submitted + 1
  self.control_queue.length()
}

///|
/// Returns true when at least one control message is pending. The
/// renderer thread should drain control messages before beginning the next
/// frame: a `Resize` may invalidate the current surface, and `Shutdown`
/// terminates the loop.
pub fn RenderFrameMailbox::has_control(self : RenderFrameMailbox) -> Bool {
  !self.control_queue.is_empty()
}

///|
/// Drains all pending control messages in submission order. The renderer
/// thread must process every returned message; none are dropped. A
/// `Shutdown` in the returned array signals loop termination.
pub fn RenderFrameMailbox::drain_control(
  self : RenderFrameMailbox,
) -> Array[@render.RendererControlMessage] {
  let drained = self.control_queue.copy()
  self.control_drained = self.control_drained + drained.length()
  self.control_queue.clear()
  drained
}

///|
/// Returns the number of pending control messages without draining.
pub fn RenderFrameMailbox::control_pending(self : RenderFrameMailbox) -> Int {
  self.control_queue.length()
}

///|
pub fn RenderFrameMailbox::submit(
  self : RenderFrameMailbox,
  input : @render.RenderFrameInput,
) -> Int {
  let sequence = self.next_sequence
  self.next_sequence = self.next_sequence + 1
  let packet = RenderFramePacket::new(
    sequence~,
    surface_generation=self.surface_generation,
    input,
  )
  self.submitted = self.submitted + 1
  if self.pending is Some(_) {
    self.replaced_pending = self.replaced_pending + 1
  }
  self.pending = Some(packet)
  sequence
}

///|
pub fn RenderFrameMailbox::begin_next(
  self : RenderFrameMailbox,
) -> RenderFramePacket? {
  if self.in_flight is Some(_) {
    return None
  }
  match self.pending {
    Some(packet) => {
      self.pending = None
      if packet.surface_generation != self.surface_generation {
        self.rejected_generation = self.rejected_generation + 1
        None
      } else {
        self.in_flight = Some(packet)
        self.started = self.started + 1
        Some(packet)
      }
    }
    None => None
  }
}

///|
pub fn RenderFrameMailbox::complete(
  self : RenderFrameMailbox,
  sequence : Int,
) -> Bool {
  match self.in_flight {
    Some(packet) =>
      if packet.sequence == sequence {
        self.in_flight = None
        self.completed = self.completed + 1
        true
      } else {
        false
      }
    None => false
  }
}

///|
pub fn RenderFrameMailbox::replace_surface_generation(
  self : RenderFrameMailbox,
  generation : Int,
) -> Unit {
  if generation != self.surface_generation {
    self.surface_generation = generation
    self.pending = None
  }
}

///|
pub fn RenderFrameMailbox::diagnostics(
  self : RenderFrameMailbox,
) -> RenderFrameMailboxDiagnostics {
  {
    submitted: self.submitted,
    started: self.started,
    completed: self.completed,
    replaced_pending: self.replaced_pending,
    rejected_generation: self.rejected_generation,
    surface_generation: self.surface_generation,
    in_flight: self.in_flight is Some(_),
    pending: self.pending is Some(_),
    control_pending: self.control_queue.length(),
    control_submitted: self.control_submitted,
    control_drained: self.control_drained,
  }
}