///|
/// Small string helpers. Kept local on purpose: pulling in a dependency for
/// `replace_all` would make the reproducibility story harder to audit.
pub fn replace_all(s : String, from : String, to : String) -> String {
  if from == "" {
    return s
  }
  let cs = s.to_array()
  let fs = from.to_array()
  let n = cs.length()
  let m = fs.length()
  let sb = StringBuilder()
  let mut i = 0
  while i < n {
    let mut matched = false
    if i + m <= n {
      let mut k = 0
      let mut same = true
      while k < m {
        if cs[i + k] != fs[k] {
          same = false
        }
        k = k + 1
      }
      matched = same
    }
    if matched {
      sb.write_string(to)
      i = i + m
    } else {
      sb.write_char(cs[i])
      i = i + 1
    }
  }
  sb.to_string()
}

///|
pub fn join_lines(lines : Array[String]) -> String {
  let sb = StringBuilder()
  for i, l in lines {
    if i > 0 {
      sb.write_string("\n")
    }
    sb.write_string(l)
  }
  sb.to_string()
}

///|
/// Split on a single separator character. Empty segments are preserved so that
/// `a,,b` stays diagnosable instead of silently collapsing.
pub fn split_on(s : String, sep : Char) -> Array[String] {
  let out : Array[String] = []
  let sb = StringBuilder()
  for c in s {
    if c == sep {
      out.push(sb.to_string())
      let _ = sb.reset()
    } else {
      sb.write_char(c)
    }
  }
  out.push(sb.to_string())
  out
}

///|
pub fn trim(s : String) -> String {
  let cs = s.to_array()
  let n = cs.length()
  let mut start = 0
  while start < n &&
        (cs[start] == ' ' || cs[start] == '\t' || cs[start] == '\r') {
    start = start + 1
  }
  let mut end = n
  while end > start &&
        (cs[end - 1] == ' ' || cs[end - 1] == '\t' || cs[end - 1] == '\r') {
    end = end - 1
  }
  let sb = StringBuilder()
  for i in start.. Int32? {
  let t = trim(s)
  let cs = t.to_array()
  if cs.length() == 0 {
    return None
  }
  let mut neg = false
  let mut i = 0
  if cs[0] == '-' || cs[0] == '+' {
    neg = cs[0] == '-'
    i = 1
  }
  if i >= cs.length() {
    return None
  }
  let mut value : Int64 = 0L
  let mut ok = true
  while i < cs.length() {
    let c = cs[i]
    if c < '0' || c > '9' {
      ok = false
    } else {
      value = value * 10L + (c.to_int() - '0'.to_int()).to_int64()
    }
    i = i + 1
  }
  if !ok {
    return None
  }
  if neg {
    Some((-value).to_int())
  } else {
    Some(value.to_int())
  }
}

///|
test "replace_all handles repeated and absent patterns" {
  inspect(replace_all("a{poly}b{poly}", "{poly}", "X"), content="aXbX")
  inspect(replace_all("nothing here", "{poly}", "X"), content="nothing here")
  inspect(replace_all("{poly}", "{poly}", ""), content="")
}

///|
test "replace_all does not loop on empty needle" {
  inspect(replace_all("abc", "", "X"), content="abc")
}