///|
pub struct NinePatch {
  name : String
  outer : Rect
  center : Rect
  left : Int
  right : Int
  top : Int
  bottom : Int
} derive(Debug, Eq, ToJson, FromJson)

///|
pub fn Slice::to_nine_patch(self : Slice) -> NinePatch? {
  match self.center {
    None => None
    Some(center) =>
      Some({
        name: self.name,
        outer: self.bounds,
        center,
        left: center.x - self.bounds.x,
        right: self.bounds.right() - center.right(),
        top: center.y - self.bounds.y,
        bottom: self.bounds.bottom() - center.bottom(),
      })
  }
}

///|
pub fn Slice::key_at(self : Slice, frame : Int) -> SliceKey? {
  match self.keys.search_by(fn(key) { key.frame == frame }) {
    Some(index) => Some(self.keys[index])
    None => None
  }
}

///|
pub fn Slice::to_nine_patch_at(self : Slice, frame : Int) -> NinePatch? {
  let key = match self.key_at(frame) {
    Some(key) => key
    None => return self.to_nine_patch()
  }
  match key.center {
    None => None
    Some(center) =>
      Some({
        name: self.name,
        outer: key.bounds,
        center,
        left: center.x - key.bounds.x,
        right: key.bounds.right() - center.right(),
        top: center.y - key.bounds.y,
        bottom: key.bounds.bottom() - center.bottom(),
      })
  }
}

///|
pub fn collect_nine_patches(sheet : SpriteSheet) -> Array[NinePatch] {
  let patches : Array[NinePatch] = []
  for slice in sheet.slices {
    match slice.to_nine_patch() {
      Some(patch) => patches.push(patch)
      None => ()
    }
  }
  patches
}

///|
pub fn collect_nine_patches_at(
  sheet : SpriteSheet,
  frame : Int,
) -> Array[NinePatch] {
  let patches : Array[NinePatch] = []
  for slice in sheet.slices {
    match slice.to_nine_patch_at(frame) {
      Some(patch) => patches.push(patch)
      None => ()
    }
  }
  patches
}

///|
pub fn attach_collision_slices(sheet : SpriteSheet) -> SpriteSheet {
  let mut result = sheet
  for slice in sheet.slices {
    if slice.name.contains("collision") ||
      slice.name.contains("hitbox") ||
      slice.name.contains("hurtbox") {
      for key in slice.keys {
        match result.frame_at(key.frame) {
          Some(frame) =>
            result = result.with_box(
              filename=frame.filename,
              name=slice.name,
              rect=key.bounds,
            )
          None => ()
        }
      }
    }
  }
  result
}

///|
pub fn SourceFrame::with_box(
  self : SourceFrame,
  name~ : String,
  rect~ : Rect,
) -> SourceFrame {
  let boxes = self.boxes.copy()
  boxes.push({ name, rect })
  { ..self, boxes, }
}

///|
pub fn SpriteSheet::with_box(
  self : SpriteSheet,
  filename~ : String,
  name~ : String,
  rect~ : Rect,
) -> SpriteSheet {
  let frames : Array[SourceFrame] = []
  for frame in self.frames {
    if frame.filename == filename {
      frames.push(frame.with_box(name~, rect~))
    } else {
      frames.push(frame)
    }
  }
  { ..self, frames, }
}