///|
fn normalize_index(idx : Int32, len : Int32) -> Int32 {
  if idx < 0 {
    len + idx
  } else {
    idx
  }
}

///|
fn clamp_index(value : Int32, len : Int32) -> Int32 {
  if value < 0 {
    0
  } else if value > len {
    len
  } else {
    value
  }
}

///|
fn slice_bounds(start : Int32?, end : Int32?, len : Int32) -> (Int32, Int32) {
  let first = clamp_index(
    start.map(n => normalize_index(n, len)).unwrap_or(0),
    len,
  )
  let last = clamp_index(
    end.map(n => normalize_index(n, len)).unwrap_or(len),
    len,
  ).max(first)
  (first, last)
}

///|
fn slice_value(input : Json, start : Int32?, end : Int32?) -> Json {
  match input {
    Array(arr) => {
      let (first, last) = slice_bounds(start, end, arr.length())
      Json::array(arr[first:last].to_owned())
    }
    String(s) => {
      let (first, last) = slice_bounds(start, end, s.char_length())
      let out = StringBuilder()
      for (i, c) in s.iter().mapi((i, c) => (i, c)) {
        if i >= first && i < last {
          out.write_char(c)
        }
      }
      Json::string(out.to_string())
    }
    _ => null
  }
}

///|
fn slice_segment(segment : Map[String, Json]) -> (Int32?, Int32?) {
  fn bound(v : Json?) -> Int32? {
    match v {
      Some(Number(n, ..)) => Some(n.to_int())
      _ => None
    }
  }
  (bound(segment.get("start")), bound(segment.get("end")))
}