///| String utility functions shared across packages.

///|
/// Convert an integer mode to its octal string representation.
pub fn to_octal_string(value : Int) -> String {
  if value == 0 {
    return "0"
  }
  let digits : Array[Char] = []
  let mut v = value
  while v > 0 {
    let d = v % 8
    digits.push((d + '0'.to_int()).unsafe_to_char())
    v = v / 8
  }
  let rev = digits.rev()
  let sb = StringBuilder::new()
  for c in rev {
    sb.write_char(c)
  }
  sb.to_string()
}

///|
/// Convert a file mode integer to its canonical string (e.g. 33188 → "100644").
pub fn mode_to_string(mode : Int) -> String {
  if mode == 33188 {
    "100644"
  } else {
    to_octal_string(mode)
  }
}

///|
/// Decode bytes to string using UTF-8 lossy decoding.
pub fn decode_bytes(data : Bytes) -> String {
  @utf8.decode_lossy(data[:])
}

///|
/// Trim whitespace (space, tab, newline, carriage return) from both ends.
pub fn trim_string(s : String) -> String {
  let mut start = 0
  let mut end = s.length()
  while start < end {
    let c = s.unsafe_get(start)
    if c == ' ' || c == '\t' || c == '\n' || c == '\r' {
      start += 1
    } else {
      break
    }
  }
  while end > start {
    let c = s.unsafe_get(end - 1)
    if c == ' ' || c == '\t' || c == '\n' || c == '\r' {
      end -= 1
    } else {
      break
    }
  }
  if start == 0 && end == s.length() {
    s
  } else {
    String::unsafe_substring(s, start~, end~)
  }
}