///|
/// Incremental Source Map builder. Mappings must be added in generated order.
pub struct SourceMapBuilder {
  sources : Array[String]
  sources_content : Array[String?]
  names : Array[String]
  mappings : Array[Mapping]
  mut last_line : Int
  mut last_column : Int
}

///|
pub fn SourceMapBuilder::new() -> SourceMapBuilder {
  {
    sources: Array::new(),
    sources_content: Array::new(),
    names: Array::new(),
    mappings: Array::new(),
    last_line: 0,
    last_column: -1,
  }
}

///|
fn SourceMapBuilder::intern_source(
  self : SourceMapBuilder,
  source : String,
  content : String?,
) -> Int raise SourceMapError {
  for i in 0..
          raise SourceMapError::At(
            self.mappings.length(),
            "source content conflicts with an earlier mapping",
          )
        (None, Some(value)) => self.sources_content[i] = Some(value)
        _ => ()
      }
      return i
    }
  }
  self.sources.push(source)
  self.sources_content.push(content)
  self.sources.length() - 1
}

///|
fn intern(values : Array[String], value : String) -> Int {
  for i in 0.. Unit raise SourceMapError {
  self.check_order(generated_line, generated_column)
  self.mappings.push(Mapping::make(generated_line, generated_column))
  self.last_line = generated_line
  self.last_column = generated_column
}

///|
/// Add a segment linked to an original source position and optional symbol.
pub fn SourceMapBuilder::add_mapping(
  self : SourceMapBuilder,
  generated_line : Int,
  generated_column : Int,
  source : String,
  original_line : Int,
  original_column : Int,
  name? : String? = None,
  source_content? : String? = None,
) -> Unit raise SourceMapError {
  self.check_order(generated_line, generated_column)
  if original_line < 0 || original_column < 0 {
    raise SourceMapError::At(
      self.mappings.length(),
      "original positions must be non-negative",
    )
  }
  let source_index = self.intern_source(source, source_content)
  let name_index = match name {
    Some(value) => Some(intern(self.names, value))
    None => None
  }
  self.mappings.push(
    Mapping::make(
      generated_line,
      generated_column,
      source_index=Some(source_index),
      original_line=Some(original_line),
      original_column=Some(original_column),
      name_index~,
    ),
  )
  self.last_line = generated_line
  self.last_column = generated_column
}

///|
fn SourceMapBuilder::check_order(
  self : SourceMapBuilder,
  line : Int,
  column : Int,
) -> Unit raise SourceMapError {
  if line < 0 || column < 0 {
    raise SourceMapError::At(
      self.mappings.length(),
      "generated positions must be non-negative",
    )
  }
  if line < self.last_line ||
    (line == self.last_line && column < self.last_column) {
    raise SourceMapError::At(
      self.mappings.length(),
      "mappings must be added in generated order",
    )
  }
}

///|
/// Finish the map. The builder remains readable but should not be reused.
pub fn SourceMapBuilder::build(
  self : SourceMapBuilder,
  file? : String? = None,
  source_root? : String? = None,
) -> SourceMap raise SourceMapError {
  let has_source_content = self.sources_content.any(content => {
    content is Some(_)
  })
  let sources_content = if has_source_content {
    self.sources_content
  } else {
    []
  }
  SourceMap::make(
    self.sources,
    self.names,
    self.mappings,
    file~,
    source_root~,
    sources_content~,
  )
}