///|
/// A source span, mirroring the reference's `Wax_utils.Ast.location`
/// (`{ loc_start; loc_end }`).
pub(all) struct Location {
  start : Position
  end : Position
} derive(Eq, Compare, Hash, Debug)

///|
/// The span of a synthesized node, mirroring the reference's `dummy_loc`.
pub let dummy_loc : Location = { start: dummy_pos, end: dummy_pos }

///|
pub fn Location::is_dummy(self : Location) -> Bool {
  self.start.is_dummy()
}

///|
/// The smallest span covering both operands. Both must come from the same file:
/// a merge across files has no meaning, so it is a caller bug rather than a
/// case to handle, and `guard!` says so.
pub fn Location::merge(self : Location, other : Location) -> Location {
  guard! self.start.fname == other.start.fname
  let start = if self.start < other.start { self.start } else { other.start }
  let end = if self.end > other.end { self.end } else { other.end }
  { start, end }
}

///|
/// An empty span at the start of `self`, used for an insertion-point edit.
pub fn Location::collapse_start(self : Location) -> Location {
  { start: self.start, end: self.start }
}

///|
/// An empty span at the end of `self`, used for an insertion-point edit.
pub fn Location::collapse_end(self : Location) -> Location {
  { start: self.end, end: self.end }
}

///|
pub impl Show for Location with fn output(self, buf) {
  buf
  ..write_string(self.start.lnum.to_string())
  ..write_char(':')
  ..write_string(self.start.column1().to_string())
  ..write_char('-')
  ..write_string(self.end.lnum.to_string())
  ..write_char(':')
  .write_string(self.end.column1().to_string())
}

///|
pub impl ToJson for Location with fn to_json(self) {
  match show_loc.val {
    Hidden => Json::null()
    Json =>
      {
        "file": self.start.fname,
        "start": { "line": self.start.lnum, "column": self.start.column0() },
        "end": { "line": self.end.lnum, "column": self.end.column0() },
      }
    Str =>
      Json::string(
        "\{self.start.lnum}:\{self.start.column1()}-\{self.end.lnum}:\{self.end.column1()}",
      )
  }
}

///|
/// A value of type `D` carried together with an annotation of type `Info`.
///
/// This is the reference's `('desc, 'info) annotated`. The annotation is a type
/// parameter rather than a fixed `Location` because the type checker — which
/// will be ported later — re-annotates the tree with inferred types.
pub(all) struct Annotated[D, Info] {
  desc : D
  info : Info
} derive(Eq, Debug)

///|
/// Wrap a value with a dummy span, the counterpart of the reference's `no_loc`.
pub fn[D] no_loc(desc : D) -> Annotated[D, Location] {
  { desc, info: dummy_loc }
}