///|
pub fn Rect::translate(self : Rect, dx : Int, dy : Int) -> Rect {
  { ..self, x: self.x + dx, y: self.y + dy }
}

///|
pub fn Rect::inset(self : Rect, amount : Int) -> Rect {
  let edge = amount * 2
  { x: self.x + amount, y: self.y + amount, w: self.w - edge, h: self.h - edge }
}

///|
pub fn Rect::expand(self : Rect, amount : Int) -> Rect {
  let edge = amount * 2
  { x: self.x - amount, y: self.y - amount, w: self.w + edge, h: self.h + edge }
}

///|
pub fn Rect::intersection(self : Rect, other : Rect) -> Rect? {
  let x = if self.x > other.x { self.x } else { other.x }
  let y = if self.y > other.y { self.y } else { other.y }
  let right = if self.right() < other.right() {
    self.right()
  } else {
    other.right()
  }
  let bottom = if self.bottom() < other.bottom() {
    self.bottom()
  } else {
    other.bottom()
  }
  let result = { x, y, w: right - x, h: bottom - y }
  if result.is_empty() {
    None
  } else {
    Some(result)
  }
}

///|
pub fn Rect::union(self : Rect, other : Rect) -> Rect {
  let x = if self.x < other.x { self.x } else { other.x }
  let y = if self.y < other.y { self.y } else { other.y }
  let right = if self.right() > other.right() {
    self.right()
  } else {
    other.right()
  }
  let bottom = if self.bottom() > other.bottom() {
    self.bottom()
  } else {
    other.bottom()
  }
  { x, y, w: right - x, h: bottom - y }
}

///|
pub fn Rect::overlaps(self : Rect, other : Rect) -> Bool {
  match self.intersection(other) {
    Some(_) => true
    None => false
  }
}

///|
pub fn Rect::touches(self : Rect, other : Rect) -> Bool {
  let horizontal = self.x <= other.right() && self.right() >= other.x
  let vertical = self.y <= other.bottom() && self.bottom() >= other.y
  horizontal && vertical
}

///|
pub fn Rect::clamp_to(self : Rect, bounds : Rect) -> Rect {
  let w = if self.w > bounds.w { bounds.w } else { self.w }
  let h = if self.h > bounds.h { bounds.h } else { self.h }
  let max_x = bounds.right() - w
  let max_y = bounds.bottom() - h
  let x0 = if self.x < bounds.x { bounds.x } else { self.x }
  let y0 = if self.y < bounds.y { bounds.y } else { self.y }
  let x = if x0 > max_x { max_x } else { x0 }
  let y = if y0 > max_y { max_y } else { y0 }
  { x, y, w, h }
}

///|
pub fn Rect::center(self : Rect) -> Point {
  { x: self.x + self.w / 2, y: self.y + self.h / 2 }
}

///|
pub fn Rect::is_inside(self : Rect, bounds : Rect) -> Bool {
  bounds.contains_rect(self)
}

///|
pub fn Rect::distance_to(self : Rect, other : Rect) -> Int {
  let dx = if self.right() < other.x {
    other.x - self.right()
  } else if other.right() < self.x {
    self.x - other.right()
  } else {
    0
  }
  let dy = if self.bottom() < other.y {
    other.y - self.bottom()
  } else if other.bottom() < self.y {
    self.y - other.bottom()
  } else {
    0
  }
  dx + dy
}

///|
pub fn rect_area_sum(rects : ArrayView[Rect]) -> Int {
  rects.fold(init=0, (total, rect) => total + rect.area())
}

///|
pub fn rect_bounds(rects : ArrayView[Rect]) -> Rect? {
  match rects.get(0) {
    None => None
    Some(first) => {
      let mut result = first
      for rect in rects[1:] {
        result = result.union(rect)
      }
      Some(result)
    }
  }
}

///|
pub fn rects_overlap_any(rects : ArrayView[Rect]) -> Bool {
  for i in 0.. Rect {
  let x = if rect.w < 0 { rect.x + rect.w } else { rect.x }
  let y = if rect.h < 0 { rect.y + rect.h } else { rect.y }
  {
    x,
    y,
    w: if rect.w < 0 {
      -rect.w
    } else {
      rect.w
    },
    h: if rect.h < 0 {
      -rect.h
    } else {
      rect.h
    },
  }
}

///|
pub fn snap_rect(rect : Rect, grid : Int) -> Rect {
  if grid <= 1 {
    return rect
  }
  let snap = fn(value : Int) { value / grid * grid }
  let right = (rect.right() + grid - 1) / grid * grid
  let bottom = (rect.bottom() + grid - 1) / grid * grid
  let x = snap(rect.x)
  let y = snap(rect.y)
  { x, y, w: right - x, h: bottom - y }
}

///|
pub fn Size::max(self : Size, other : Size) -> Size {
  {
    w: if self.w > other.w {
      self.w
    } else {
      other.w
    },
    h: if self.h > other.h {
      self.h
    } else {
      other.h
    },
  }
}

///|
pub fn Size::scale(self : Size, factor : Int) -> Size {
  { w: self.w * factor, h: self.h * factor }
}