/// String utility functions for Rope operations
/// These functions handle UTF-16 strings as used by MoonBit

///|
/// Count the number of Unicode characters in a string
/// In MoonBit, this is simpler since String already stores UTF-16
pub fn count_chars(text : String) -> Int {
  // In MoonBit, we need to iterate through characters to handle surrogate pairs
  let mut count = 0
  for i = 0; i < text.length(); i = i + 1 {
    let ch_code = text[i]
    // Skip low surrogates (they are counted with their high surrogate)
    if ch_code < 0xDC00 || ch_code > 0xDFFF {
      count = count + 1
    }
  }
  count
}

///|
/// Count the number of line breaks in a string
pub fn count_line_breaks(text : String) -> Int {
  let mut count = 0
  let mut i = 0
  while i < text.length() {
    let ch_code = text[i]
    if ch_code == '\n'.to_int() {
      count = count + 1
    } else if ch_code == '\r'.to_int() {
      count = count + 1
      // Skip CRLF pairs - don't count LF after CR
      if i + 1 < text.length() && text[i + 1] == '\n'.to_int() {
        i = i + 1
      }
    }
    i = i + 1
  }
  count
}

///|
/// Convert character index to UTF-16 code unit index
pub fn char_to_utf16_cu_idx(text : String, char_idx : Int) -> Int {
  if char_idx <= 0 {
    return 0
  }
  let mut cu_idx = 0
  let mut char_count = 0
  while cu_idx < text.length() && char_count < char_idx {
    let ch_code = text[cu_idx]
    // Check if this is a high surrogate
    if ch_code >= 0xD800 && ch_code <= 0xDBFF {
      // Skip the low surrogate as well
      cu_idx = cu_idx + 2
    } else {
      cu_idx = cu_idx + 1
    }
    char_count = char_count + 1
  }
  cu_idx
}

///|
/// Convert UTF-16 code unit index to character index
pub fn utf16_cu_to_char_idx(text : String, utf16_idx : Int) -> Int {
  if utf16_idx <= 0 {
    return 0
  }
  let mut char_count = 0
  let mut i = 0
  while i < utf16_idx && i < text.length() {
    let ch_code = text[i]
    // Skip low surrogates (they are counted with their high surrogate)
    if ch_code < 0xDC00 || ch_code > 0xDFFF {
      char_count = char_count + 1
    }
    i = i + 1
  }
  char_count
}

///|
/// Convert character index to line index
pub fn char_to_line_idx(text : String, char_idx : Int) -> Int {
  if char_idx <= 0 {
    return 0
  }
  let target_utf16_idx = char_to_utf16_cu_idx(text, char_idx)
  let mut line_count = 0
  let mut i = 0
  while i < target_utf16_idx && i < text.length() {
    let ch_code = text[i]
    if ch_code == '\n'.to_int() {
      line_count = line_count + 1
    } else if ch_code == '\r'.to_int() {
      line_count = line_count + 1
      // Skip CRLF pairs
      if i + 1 < text.length() && text[i + 1] == '\n'.to_int() {
        i = i + 1
      }
    }
    i = i + 1
  }
  line_count
}

///|
/// Convert line index to character index
pub fn line_to_char_idx(text : String, line_idx : Int) -> Int {
  if line_idx <= 0 {
    return 0
  }
  let mut current_line = 0
  let mut i = 0
  while i < text.length() && current_line < line_idx {
    let ch_code = text[i]
    if ch_code == '\n'.to_int() {
      current_line = current_line + 1
      if current_line == line_idx {
        return utf16_cu_to_char_idx(text, i + 1)
      }
    } else if ch_code == '\r'.to_int() {
      current_line = current_line + 1
      if current_line == line_idx {
        // Handle CRLF
        if i + 1 < text.length() && text[i + 1] == '\n'.to_int() {
          return utf16_cu_to_char_idx(text, i + 2)
        } else {
          return utf16_cu_to_char_idx(text, i + 1)
        }
      }
      // Skip CRLF pairs when counting
      if i + 1 < text.length() && text[i + 1] == '\n'.to_int() {
        i = i + 1
      }
    }
    i = i + 1
  }

  // If we reach here, return the character count of the entire string
  count_chars(text)
}

///|
/// Get character at character index (not UTF-16 index)
pub fn char_at(text : String, char_idx : Int) -> Int {
  let utf16_idx = char_to_utf16_cu_idx(text, char_idx)
  if utf16_idx >= text.length() {
    abort("Character index out of bounds")
  }
  text[utf16_idx]
}

///|
/// Check if a character is a line break
pub fn is_line_break(ch_code : Int) -> Bool {
  ch_code == '\n'.to_int() || ch_code == '\r'.to_int()
}

///|
/// Get the length of a line starting at the given character index
pub fn line_length_from_char(text : String, start_char_idx : Int) -> Int {
  let start_utf16 = char_to_utf16_cu_idx(text, start_char_idx)
  let mut i = start_utf16
  let mut char_count = 0
  while i < text.length() {
    let ch_code = text[i]
    if is_line_break(ch_code) {
      break
    }

    // Count characters, not UTF-16 code units
    if ch_code < 0xDC00 || ch_code > 0xDFFF {
      char_count = char_count + 1
    }
    i = i + 1
  }
  char_count
}

///|
/// Check if string ends with a line break
pub fn ends_with_line_break(text : String) -> Bool {
  if text.length() == 0 {
    return false
  }
  let last_char_code = text[text.length() - 1]
  is_line_break(last_char_code)
}

///|
/// Find the start of the last line in the text
pub fn last_line_start_char_idx(text : String) -> Int {
  if text.length() == 0 {
    return 0
  }
  let mut i = text.length() - 1

  // Skip trailing line break if present
  if is_line_break(text[i]) {
    if i > 0 && text[i] == '\n'.to_int() && text[i - 1] == '\r'.to_int() {
      i = i - 1 // Skip CRLF
    }
    if i > 0 {
      i = i - 1
    }
  }

  // Find the previous line break
  while i > 0 {
    let ch_code = text[i]
    if is_line_break(ch_code) {
      return utf16_cu_to_char_idx(text, i + 1)
    }
    i = i - 1
  }
  0
}