///|
fn normalized_surface_present_frame_index(frame_index : Int) -> Int {
  Int::max(0, frame_index)
}

///|
fn surface_present_mod(value : Int, divisor : Int) -> Int {
  let divisor = Int::max(1, divisor)
  value - value / divisor * divisor
}

///|
fn normalized_surface_present_buffer_index(
  target : SurfaceTargetDescriptor,
  frame_index : Int,
  buffer_index : Int,
) -> Int {
  if !target.is_presentable() {
    0
  } else {
    surface_present_mod(
      if buffer_index < 0 {
        frame_index
      } else {
        buffer_index
      },
      target.max_frames_in_flight,
    )
  }
}

///|
/// Per-frame presentation scheduling metadata for a presentable target.
pub(all) struct SurfacePresentDescriptor {
  target : SurfaceTargetDescriptor
  frame_index : Int
  buffer_index : Int
  present_mode : SurfacePresentMode?
} derive(Debug, Eq)

///|
pub fn SurfacePresentDescriptor::new(
  target : SurfaceTargetDescriptor,
  frame_index? : Int = 0,
  buffer_index? : Int = -1,
) -> SurfacePresentDescriptor {
  let frame_index = normalized_surface_present_frame_index(frame_index)
  {
    target,
    frame_index,
    buffer_index: normalized_surface_present_buffer_index(
      target, frame_index, buffer_index,
    ),
    present_mode: if target.is_presentable() {
      target.present_mode
    } else {
      None
    },
  }
}

///|
pub fn SurfaceTargetDescriptor::present_descriptor(
  self : SurfaceTargetDescriptor,
  frame_index? : Int = 0,
  buffer_index? : Int = -1,
) -> SurfacePresentDescriptor {
  SurfacePresentDescriptor::new(self, frame_index~, buffer_index~)
}

///|
/// Per-frame surface finalization metadata before a backend flushes/submits work.
pub(all) struct SurfaceFinalizationDescriptor {
  target : SurfaceTargetDescriptor
  frame_index : Int
} derive(Debug, Eq)

///|
pub fn SurfaceFinalizationDescriptor::new(
  target : SurfaceTargetDescriptor,
  frame_index? : Int = 0,
) -> SurfaceFinalizationDescriptor {
  { target, frame_index: normalized_surface_present_frame_index(frame_index) }
}

///|
pub fn SurfaceTargetDescriptor::finalization_descriptor(
  self : SurfaceTargetDescriptor,
  frame_index? : Int = 0,
) -> SurfaceFinalizationDescriptor {
  SurfaceFinalizationDescriptor::new(self, frame_index~)
}

///|
pub fn SurfaceFinalizationDescriptor::is_finalizable(
  self : SurfaceFinalizationDescriptor,
) -> Bool {
  !self.target.resource_key().is_empty()
}

///|
pub fn SurfaceFinalizationDescriptor::requires_gpu_context(
  self : SurfaceFinalizationDescriptor,
) -> Bool {
  self.target.surface.is_gpu()
}

///|
pub fn SurfacePresentDescriptor::is_presentable(
  self : SurfacePresentDescriptor,
) -> Bool {
  self.present_mode is Some(_) && self.target.is_presentable()
}

///|
pub fn SurfacePresentDescriptor::prefers_vsync(
  self : SurfacePresentDescriptor,
) -> Bool {
  match self.present_mode {
    Some(mode) => mode.prefers_vsync()
    None => false
  }
}

///|
pub fn SurfacePresentDescriptor::allows_tearing(
  self : SurfacePresentDescriptor,
) -> Bool {
  match self.present_mode {
    Some(mode) => mode.allows_tearing()
    None => false
  }
}