///|
/// Small text helpers kept dependency-free so the core also works on wasm.
fn is_space_code(c : UInt16) -> Bool {
  let n = c.to_int()
  n == 32 || n == 9 || n == 10 || n == 13
}

///|
fn ascii_lower_char(c : Char) -> Char {
  c.to_ascii_lowercase()
}

///|
fn lower_ascii(value : String) -> String {
  let out = StringBuilder()
  for c in value {
    out.write_char(ascii_lower_char(c))
  }
  out.to_string()
}

///|
fn trim_ascii(value : String) -> String {
  let mut start = 0
  let mut finish = value.length()
  while start < finish && is_space_code(value.unsafe_get(start)) {
    start += 1
  }
  while finish > start && is_space_code(value.unsafe_get(finish - 1)) {
    finish -= 1
  }
  value[start:finish].to_owned()
}

///|
fn words(value : String) -> Array[String] {
  let result : Array[String] = []
  let mut current = StringBuilder()
  for c in value {
    if c == ' ' || c == '\t' || c == '\n' || c == '\r' {
      let part = trim_ascii(current.to_string())
      if part.length() > 0 {
        result.push(part)
      }
      current = StringBuilder()
    } else {
      current.write_char(c)
    }
  }
  let tail = trim_ascii(current.to_string())
  if tail.length() > 0 {
    result.push(tail)
  }
  result
}

///|
fn split_once(value : String, delimiter : String) -> (String, String) {
  match value.find(delimiter) {
    Some(index) =>
      (value[:index].to_owned(), value[index + delimiter.length():].to_owned())
    None => (value, "")
  }
}

///|
fn unquote(value : String) -> String {
  let clean = trim_ascii(value)
  if clean.length() >= 2 &&
    (
      (clean[0] == '"' && clean[clean.length() - 1] == '"') ||
      (clean[0] == '\'' && clean[clean.length() - 1] == '\'')
    ) {
    clean[1:clean.length() - 1].to_owned()
  } else {
    clean
  }
}

///|
fn bool_value(value : String, default : Bool) -> Bool {
  match lower_ascii(unquote(value)) {
    "true" => true
    "yes" => true
    "on" => true
    "1" => true
    "false" => false
    "no" => false
    "off" => false
    "0" => false
    _ => default
  }
}

///|
fn int_value(value : String, default : Int) -> Int {
  @string.parse_int(unquote(value)) catch {
    _ => default
  }
}

///|
fn array_contains(items : Array[String], wanted : String) -> Bool {
  for item in items {
    if item == wanted {
      break true
    }
  } nobreak {
    false
  }
}

///|
fn string_or_default(value : String, default : String) -> String {
  if value.length() == 0 {
    default
  } else {
    value
  }
}