///|
/// A half-open UTF-8 byte range, or the compiler's EOF location.
/// Fields are readable. Only validated constructors can create a span.
pub struct SrcSpan {
  start : ByteOffset
  end : ByteOffset
} derive(Eq, Debug, Hash)

///|
/// Create an ordered byte range. Raise `CoordinateError` for negative
/// offsets or an end before the start. Use `from_ints` to get `Option`.
///
/// ```mbt check
/// test {
///   let span = @span.SrcSpan(2, 5)
///   assert_eq(span.length().map(n => n.to_int()), Some(3))
/// }
/// ```
pub fn SrcSpan::SrcSpan(
  start : Int32,
  end : Int32,
) -> SrcSpan raise CoordinateError {
  let start_offset = ByteOffset(start)
  let end_offset = ByteOffset(end)
  guard SrcSpan::from_offsets(start_offset, end_offset) is Some(span) else {
    raise ReversedSpan(start~, end~)
  }
  span
}

///|
/// Create an ordered range from raw byte offsets.
/// Return `None` for negative offsets or an end before the start.
///
/// ```mbt check
/// test {
///   debug_inspect(@span.SrcSpan::from_ints(3, 2), content="None")
///   let span = @span.SrcSpan::from_ints(2, 5).unwrap()
///   debug_inspect(span.length().map(n => n.to_int()), content="Some(3)")
/// }
/// ```
#alias(new, deprecated="Use `SrcSpan::from_ints(...)` instead")
pub fn SrcSpan::from_ints(start : Int32, end : Int32) -> SrcSpan? {
  match (ByteOffset::from_int(start), ByteOffset::from_int(end)) {
    (Some(start), Some(end)) => SrcSpan::from_offsets(start, end)
    _ => None
  }
}

///|
/// Create an ordered range. Return `None` when the end precedes the start.
#alias(between, deprecated="Use `SrcSpan::from_offsets(...)` instead")
pub fn SrcSpan::from_offsets(start : ByteOffset, end : ByteOffset) -> SrcSpan? {
  if start <= end {
    Some({ start, end, })
  } else {
    None
  }
}

///|
/// Create the compiler's `start..0` location.
/// A positive start gives a reversed EOF sentinel with no byte length.
/// At zero, this is the empty range `0..0`, as in the compiler.
///
/// ```mbt check
/// test {
///   let location = @span.SrcSpan::eof(@span.ByteOffset::from_int(5).unwrap())
///   assert_eq(location.is_eof(), true)
///   assert_eq(location.length(), None)
/// }
/// ```
pub fn SrcSpan::eof(start : ByteOffset) -> SrcSpan {
  { start, end: ByteOffset::zero(), }
}

///|
/// Import compiler offsets. A reversed `start..0` is an EOF sentinel.
/// Return `None` for any other reversed range.
pub fn SrcSpan::from_compiler(start : ByteOffset, end : ByteOffset) -> SrcSpan? {
  if start > end && end == ByteOffset::zero() {
    Some(SrcSpan::eof(start))
  } else {
    SrcSpan::from_offsets(start, end)
  }
}

///|
/// Whether this is a reversed compiler EOF sentinel.
/// EOF at byte zero has the same representation as the empty range at zero.
pub fn SrcSpan::is_eof(self : SrcSpan) -> Bool {
  self.start > self.end
}

///|
/// Whether the byte offset lies in the half-open range. EOF contains nothing.
pub fn SrcSpan::contains(self : SrcSpan, byte : ByteOffset) -> Bool {
  !self.is_eof() && self.start <= byte && byte < self.end
}

///|
/// The range's byte length, or `None` for a compiler EOF location.
pub fn SrcSpan::length(self : SrcSpan) -> ByteLength? {
  if self.is_eof() {
    None
  } else {
    ByteLength::from_int(self.end.to_int() - self.start.to_int())
  }
}

///|
/// A one-based line and column. Columns count Unicode code points.
pub struct Position {
  line : LineNumber
  column : ColumnNumber
} derive(Eq, Debug, Hash)

///|
/// Create a one-based position from raw coordinates.
/// Raise `CoordinateError` for a line or column below one.
/// Use `from_ints` to get `Option`, or `at` with validated coordinates.
///
/// ```mbt check
/// test {
///   let position = @span.Position(2, 1)
///   assert_eq(position.line.to_int(), 2)
/// }
/// ```
pub fn Position::Position(
  line : Int32,
  column : Int32,
) -> Position raise CoordinateError {
  Position::at(LineNumber(line), ColumnNumber(column))
}

///|
/// Create a position from raw coordinates. Return `None` below one.
///
/// ```mbt check
/// test {
///   debug_inspect(@span.Position::from_ints(0, 1), content="None")
///   let position = @span.Position::from_ints(1, 2).unwrap()
///   assert_eq(position.column.to_int(), 2)
/// }
/// ```
#alias(new, deprecated="Use `Position::from_ints(...)` instead")
pub fn Position::from_ints(line : Int32, column : Int32) -> Position? {
  match (LineNumber::from_int(line), ColumnNumber::from_int(column)) {
    (Some(line), Some(column)) => Some(Position::at(line, column))
    _ => None
  }
}

///|
/// Create a position from validated coordinates.
///
/// ```mbt check
/// test {
///   let position = @span.Position::at(
///     @span.LineNumber::from_int(2).unwrap(),
///     @span.ColumnNumber::from_int(1).unwrap(),
///   )
///   assert_eq(position, @span.Position::from_ints(2, 1).unwrap())
/// }
/// ```
pub fn Position::at(line : LineNumber, column : ColumnNumber) -> Position {
  { line, column, }
}