///|
fn lower(text : String) -> String {
  text.to_lower()
}

///|
fn clean(text : String) -> String {
  text.trim().replace_all(old="\t", new=" ").to_owned()
}

///|
fn parse_digits(text : String) -> Int? {
  let trimmed = text.trim().to_owned()
  if trimmed.is_empty() {
    return None
  }
  let mut value = 0
  for ch in trimmed {
    let code = ch.to_int()
    if code < 48 || code > 57 {
      return None
    }
    value = value * 10 + code - 48
  }
  Some(value)
}

///|
fn csv_count(text : String) -> Int {
  if text.trim().is_empty() {
    return 0
  }
  let mut count = 0
  for part in text.split(",") {
    if !part.trim().is_empty() {
      count += 1
    }
  }
  count
}

///|
fn clamp_score(score : Int) -> Int {
  if score < 0 {
    0
  } else if score > 100 {
    100
  } else {
    score
  }
}

///|
fn severity_text(severity : Severity) -> String {
  if severity == Pass {
    "pass"
  } else if severity == Warn {
    "warn"
  } else {
    "fail"
  }
}

///|
fn json_escape(text : String) -> String {
  text
  .replace_all(old="\\", new="\\\\")
  .replace_all(old="\"", new="\\\"")
  .replace_all(old="\n", new="\\n")
}

///|
fn split_tag_value(item : String) -> (String, String) {
  let parts = item.split("=").to_array()
  if parts.length() < 2 {
    ("", "")
  } else {
    (lower(parts[0].trim().to_owned()), parts[1].trim().to_owned())
  }
}

///|
fn tag_value(text : String, key : String) -> String {
  let wanted = lower(key)
  for segment in text.split(";") {
    let item = clean(segment.to_owned())
    if item.is_empty() {
      continue
    }
    let (tag, value) = split_tag_value(item)
    if tag == wanted {
      return value
    }
  }
  ""
}

///|
fn has_tag(text : String, key : String) -> Bool {
  !tag_value(text, key).is_empty()
}

///|
fn colon_value(text : String, key : String) -> String {
  let wanted = lower(key)
  for line in text.split("\n") {
    let item = line.to_owned().trim().to_owned()
    if item.is_empty() {
      continue
    }
    let parts = item.split(":").to_array()
    if parts.length() < 2 {
      continue
    }
    let tag = lower(parts[0].trim().to_owned())
    if tag == wanted {
      return parts[1].trim().to_owned()
    }
  }
  ""
}

///|
fn colon_values(text : String, key : String) -> Array[String] {
  let wanted = lower(key)
  let values = Array::new()
  for line in text.split("\n") {
    let item = line.to_owned().trim().to_owned()
    if item.is_empty() {
      continue
    }
    let parts = item.split(":").to_array()
    if parts.length() < 2 {
      continue
    }
    let tag = lower(parts[0].trim().to_owned())
    if tag == wanted {
      values.push(parts[1].trim().to_owned())
    }
  }
  values
}

///|
fn list_contains_token(text : String, token : String) -> Bool {
  let wanted = lower(token)
  for part in text.split(":") {
    if lower(part.to_owned().trim().to_owned()) == wanted {
      return true
    }
  }
  for part in text.split(",") {
    if lower(part.to_owned().trim().to_owned()) == wanted {
      return true
    }
  }
  false
}

///|
fn csv_contains_token(text : String, token : String) -> Bool {
  let wanted = lower(token)
  for part in text.split(",") {
    if lower(part.to_owned().trim().to_owned()) == wanted {
      return true
    }
  }
  false
}

///|
fn starts_with_https_url(text : String) -> Bool {
  lower(text.trim().to_owned()).has_prefix("https://")
}

///|
fn contains_svg_path(text : String) -> Bool {
  let item = lower(text.trim().to_owned())
  item.contains(".svg")
}

///|
fn rough_base64_bits(text : String) -> Int {
  let mut useful = 0
  for ch in text.trim().to_owned() {
    let code = ch.to_int()
    let digit = code >= 48 && code <= 57
    let upper = code >= 65 && code <= 90
    let lower_letter = code >= 97 && code <= 122
    if digit || upper || lower_letter || ch == '+' || ch == '/' {
      useful += 1
    }
  }
  useful * 6
}

///|
fn bool_text(value : Bool) -> String {
  if value {
    "true"
  } else {
    "false"
  }
}

///|
fn append_json_string(
  out : StringBuilder,
  key : String,
  value : String,
) -> Unit {
  out.write_string("\"")
  out.write_string(json_escape(key))
  out.write_string("\":\"")
  out.write_string(json_escape(value))
  out.write_string("\"")
}

///|
fn append_json_int(out : StringBuilder, key : String, value : Int) -> Unit {
  out.write_string("\"")
  out.write_string(json_escape(key))
  out.write_string("\":")
  out.write_string(value.to_string())
}

///|
fn append_json_bool(out : StringBuilder, key : String, value : Bool) -> Unit {
  out.write_string("\"")
  out.write_string(json_escape(key))
  out.write_string("\":")
  out.write_string(bool_text(value))
}

///|
fn normalize_domain(text : String) -> String {
  lower(text.trim().to_owned())
}

///|
fn has_wildcard_host(text : String) -> Bool {
  text.trim().to_owned().contains("*")
}

///|
fn positive_or_zero(value : Int) -> Int {
  if value < 0 {
    0
  } else {
    value
  }
}

///|
fn max_int(a : Int, b : Int) -> Int {
  if a > b {
    a
  } else {
    b
  }
}

///|
fn min_int(a : Int, b : Int) -> Int {
  if a < b {
    a
  } else {
    b
  }
}