///| String-specific operations for Runs

///|
/// String implements Mergeable - strings always merge
pub impl Mergeable for String with fn can_merge(_a : String, _b : String) -> Bool {
  true
}

///|
/// Merge two strings by concatenation
pub impl Mergeable for String with fn merge(a : String, b : String) -> String {
  a + b
}

///|
/// String implements HasLength - length() is used by Spanning default
/// logical_length() → span() → HasLength::length(), all returning UTF-16 code unit count
pub impl HasLength for String with fn length(self : String) -> Int {
  self.length()
}

///|
/// String implements Spanning via defaults:
/// span() → HasLength::length(), logical_length() → span()
/// Both return UTF-16 code unit count.
pub impl Spanning for String with fn span(self : String) -> Int {
  HasLength::length(self)
}

///|
/// String helper that slices with bounds and surrogate pair validation.
///
/// MoonBit's `text[start:end]` no longer raises on invalid boundaries,
/// so we validate explicitly:
/// - Out of bounds → `SliceError::IndexOutOfBounds`
/// - Boundary inside a surrogate pair → `SliceError::InvalidIndex`
///
/// A UTF-16 surrogate pair is [high (0xD800–0xDBFF), low (0xDC00–0xDFFF)].
/// A boundary on a low surrogate means we'd split inside a pair.
/// A boundary on a high surrogate is valid (start of the character).
pub fn slice_string_view(
  text : String,
  start~ : Int,
  end~ : Int,
) -> Result[String, SliceError] {
  let len = text.length()
  if start < 0 || end > len || start > end {
    return Err(SliceError::IndexOutOfBounds)
  }
  if (
      start < len &&
      ({
        let cu = text.code_unit_at(start)
        cu >= 0xDC00 && cu <= 0xDFFF
      })
    ) ||
    (
      end < len &&
      ({
        let cu = text.code_unit_at(end)
        cu >= 0xDC00 && cu <= 0xDFFF
      })
    ) {
    return Err(SliceError::InvalidIndex)
  }
  Ok(text[start:end].to_owned())
}

///|
/// String implements Sliceable via substring with UTF-16 boundary validation.
pub impl Sliceable for String with fn slice(
  self : String,
  start~ : Int,
  end~ : Int,
) -> Result[String, RleError] {
  match slice_string_view(self, start~, end~) {
    Ok(value) => Ok(value)
    Err(err) => Err(RleError::InvalidSlice(reason=err))
  }
}

///|
/// Create from string (single run)
pub fn Runs::from_string(text : String) -> Runs[String] {
  if text.is_empty() {
    Runs::new()
  } else {
    Runs([text])
  }
}

///|
/// Concatenate all runs into single string
pub fn Runs::to_string(self : Runs[String]) -> String {
  self.0
  .fold(init=StringBuilder::new(), fn(sb, s) {
    sb.write_string(s)
    sb
  })
  .to_string()
}

///|
/// Iterate over codepoints
pub fn Runs::iter_chars(self : Runs[String]) -> Iter[Char] {
  let chars : Array[Char] = []
  for run in self.0 {
    for c in run {
      chars.push(c)
    }
  }
  chars.iter()
}

///|
/// Create Rle from string
pub fn Rle::from_string(text : String) -> Rle[String] {
  Rle::from_runs(Runs::from_string(text))
}

///|
/// Concatenate all runs into single string
pub fn Rle::to_string(self : Rle[String]) -> String {
  self.runs.to_string()
}

///|
/// Iterate over codepoints
pub fn Rle::iter_chars(self : Rle[String]) -> Iter[Char] {
  self.runs.iter_chars()
}