///|
pub fn trim_ascii(text : String) -> String {
  if text.length() == 0 {
    return text
  }
  let mut start = 0
  let mut seen = false
  for index, ch in text {
    if !ascii_space(ch) {
      start = index
      seen = true
      break
    }
  }
  if !seen {
    return ""
  }
  let mut end = text.length()
  for index, ch in text {
    if !ascii_space(ch) {
      end = index + 1
    }
  }
  text.unsafe_substring(start~, end~)
}

///|
pub fn ascii_space(ch : Char) -> Bool {
  ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'
}

///|
pub fn split_char(text : String, sep : Char) -> Array[String] {
  let parts : Array[String] = []
  let mut start = 0
  for index, ch in text {
    if ch == sep {
      parts.push(text.unsafe_substring(start~, end=index))
      start = index + 1
    }
  }
  parts.push(text.unsafe_substring(start~, end=text.length()))
  parts
}

///|
pub fn split_lines(text : String) -> Array[String] {
  split_char(text, '\n')
}

///|
pub fn split_once(text : String, sep : String) -> Array[String] {
  match text.find(sep) {
    Some(index) =>
      [
        text.unsafe_substring(start=0, end=index),
        text.unsafe_substring(start=index + sep.length(), end=text.length()),
      ]
    None => [text]
  }
}

///|
pub fn strip_inline_comment(line : String) -> String {
  match line.find("#") {
    Some(index) => line.unsafe_substring(start=0, end=index)
    None => line
  }
}

///|
pub fn lower_ascii(text : String) -> String {
  let mut out = ""
  for _, ch in text {
    if ch.is_ascii_uppercase() {
      out = out + [ch.to_ascii_lowercase()]
    } else {
      out = out + [ch]
    }
  }
  out
}

///|
pub fn upper_ascii(text : String) -> String {
  let mut out = ""
  for _, ch in text {
    if ch.is_ascii_lowercase() {
      out = out + [ch.to_ascii_uppercase()]
    } else {
      out = out + [ch]
    }
  }
  out
}

///|
pub fn join_lines(lines : Array[String]) -> String {
  join_with(lines, "\n")
}

///|
pub fn join_with(values : Array[String], sep : String) -> String {
  let mut out = ""
  for index = 0; index < values.length(); index = index + 1 {
    if index > 0 {
      out = out + sep
    }
    out = out + values[index]
  }
  out
}

///|
pub fn parse_non_negative_int(text : String) -> Int? {
  let s = trim_ascii(text)
  if s.length() == 0 {
    return None
  }
  let mut total = 0
  for _, ch in s {
    if !ch.is_ascii_digit() {
      return None
    }
    total = total * 10 + ch.to_int() - '0'.to_int()
  }
  Some(total)
}

///|
pub fn contains_string(values : Array[String], value : String) -> Bool {
  for item in values {
    if item == value {
      return true
    }
  }
  false
}

///|
pub fn starts_with_any(text : String, prefixes : Array[String]) -> Bool {
  for prefix in prefixes {
    if text.has_prefix(prefix) {
      return true
    }
  }
  false
}

///|
pub fn normalize_path(path : String) -> String {
  if path == "" {
    "/"
  } else if path.has_prefix("/") {
    path
  } else {
    "/" + path
  }
}