///|
pub(all) struct Span {
  start : Int
  end : Int
} derive(Eq, Debug)

///|
pub fn Span::new(start : Int, end : Int) -> Span {
  if start <= end {
    { start, end }
  } else {
    { start: end, end: start }
  }
}

///|
pub fn Span::length(self : Span) -> Int {
  self.end - self.start
}

///|
pub fn Span::is_empty(self : Span) -> Bool {
  self.start == self.end
}

///|
pub(all) struct SourceFile {
  file_name : String
  text : String
}

///|
pub fn SourceFile::new(file_name : String, text : String) -> SourceFile {
  { file_name, text }
}

///|
pub fn SourceFile::name(self : SourceFile) -> String {
  self.file_name
}

///|
pub fn SourceFile::text(self : SourceFile) -> String {
  self.text
}

///|
fn line_col_from(
  chars : Array[Char],
  limit : Int,
  index : Int,
  line : Int,
  column : Int,
) -> (Int, Int) {
  if index >= limit {
    (line, column)
  } else if chars[index] == '\n' {
    line_col_from(chars, limit, index + 1, line + 1, 0)
  } else {
    line_col_from(chars, limit, index + 1, line, column + 1)
  }
}

///|
pub fn SourceFile::line_col(self : SourceFile, offset : Int) -> (Int, Int) {
  let chars = self.text.to_array()
  let limit = if offset < 0 {
    0
  } else if offset > chars.length() {
    chars.length()
  } else {
    offset
  }
  line_col_from(chars, limit, 0, 0, 0)
}

///|
pub fn SourceFile::slice(self : SourceFile, span : Span) -> String {
  let start = if span.start < 0 { 0 } else { span.start }
  let end = if span.end > self.text.length() {
    self.text.length()
  } else {
    span.end
  }
  if start >= end {
    ""
  } else {
    self.text.sub(start~, end~).to_owned()
  }
}