///|
pub(all) struct Bounds {
  left : Double
  top : Double
  width : Double
  height : Double
} derive(Eq, Debug)

///|
pub(all) enum Placement {
  BottomStart
  BottomCenter
  BottomEnd
  TopStart
  TopCenter
  TopEnd
  LeftStart
  LeftCenter
  LeftEnd
  RightStart
  RightCenter
  RightEnd
} derive(Eq, Debug)

///|
/// Flip on the main axis, then shift inside the measured viewport.
pub fn place(
  anchor : Bounds,
  surface : Bounds,
  viewport : Bounds,
  placement : Placement,
  gap? : Double = 8.0,
  align_offset? : Double = 0.0,
) -> Bounds {
  let horizontal = placement is LeftStart ||
    placement is LeftCenter ||
    placement is LeftEnd ||
    placement is RightStart ||
    placement is RightCenter ||
    placement is RightEnd
  let end = placement is BottomEnd ||
    placement is TopEnd ||
    placement is LeftEnd ||
    placement is RightEnd
  let center = placement is BottomCenter ||
    placement is TopCenter ||
    placement is LeftCenter ||
    placement is RightCenter
  let above = placement is TopStart ||
    placement is TopCenter ||
    placement is TopEnd
  let left = placement is LeftStart ||
    placement is LeftCenter ||
    placement is LeftEnd
  let below_y = anchor.top + anchor.height + gap
  let above_y = anchor.top - surface.height - gap
  let right_x = anchor.left + anchor.width + gap
  let left_x = anchor.left - surface.width - gap
  let mut y = if horizontal {
    anchor.top +
    (if end {
      anchor.height - surface.height
    } else if center {
      (anchor.height - surface.height) / 2.0
    } else {
      0.0
    }) +
    align_offset
  } else if above {
    above_y
  } else {
    below_y
  }
  let mut x = if horizontal {
    if left {
      left_x
    } else {
      right_x
    }
  } else if end {
    anchor.left + anchor.width - surface.width
  } else if center {
    anchor.left + (anchor.width - surface.width) / 2.0
  } else {
    anchor.left
  }
  if horizontal {
    if left && left_x < viewport.left {
      x = right_x
    }
    if !left && right_x + surface.width > viewport.left + viewport.width {
      x = left_x
    }
  } else {
    x = x + align_offset
    if above && above_y < viewport.top {
      y = below_y
    }
    if !above && below_y + surface.height > viewport.top + viewport.height {
      y = above_y
    }
  }
  {
    left: x
    .max(viewport.left)
    .min((viewport.left + viewport.width - surface.width).max(viewport.left)),
    top: y
    .max(viewport.top)
    .min((viewport.top + viewport.height - surface.height).max(viewport.top)),
    width: surface.width.min(viewport.width),
    height: surface.height.min(viewport.height),
  }
}

///|
/// Resize one adjacent pair without changing total size or unrelated panes.
pub fn resize_pair(
  sizes : Array[Double],
  index : Int,
  delta : Double,
  minimums : Array[Double],
  maximums : Array[Double],
) -> Array[Double] {
  guard index >= 0 && index + 1 < sizes.length() else { return sizes.copy() }
  let total = sizes[index] + sizes[index + 1]
  let low = minimums
    .get(index)
    .unwrap_or(0.0)
    .max(total - maximums.get(index + 1).unwrap_or(100.0))
  let high = maximums
    .get(index)
    .unwrap_or(100.0)
    .min(total - minimums.get(index + 1).unwrap_or(0.0))
  guard low <= high else { return sizes.copy() }
  let next = sizes.copy()
  next[index] = (sizes[index] + delta).clamp(min=low, max=high)
  next[index + 1] = total - next[index]
  next
}

///|
pub fn page_count(total : Int, page_size : Int) -> Int {
  if total <= 0 {
    1
  } else {
    (total + page_size.max(1) - 1) / page_size.max(1)
  }
}