///|
fn diag_error(code : String, path : String, message : String) -> Diagnostic {
  { severity: "error", code, path, message }
}

///|
fn diag_warning(code : String, path : String, message : String) -> Diagnostic {
  { severity: "warning", code, path, message }
}

///|
fn is_ignored(map : SourceMap, index : Int) -> Bool {
  map.ignore_list.contains(index)
}

///|
pub fn SourceMap::validate(self : SourceMap) -> Array[Diagnostic] {
  let diagnostics : Array[Diagnostic] = []
  if self.version != 3 {
    diagnostics.push(
      diag_error(
        "version",
        "version",
        "Source Map v3 requires version=3, found \{self.version}",
      ),
    )
  }
  if self.sources.is_empty() &&
    self.mappings.any(segment => segment.is_mapped()) {
    diagnostics.push(
      diag_error(
        "sources.empty", "sources", "mapped segments require at least one source",
      ),
    )
  }
  if self.sources_content.length() > 0 &&
    self.sources_content.length() != self.sources.length() {
    diagnostics.push(
      diag_warning(
        "sourcesContent.length", "sourcesContent", "sourcesContent should be absent or have the same length as sources",
      ),
    )
  }
  for i, source in self.sources {
    if source.length() == 0 {
      diagnostics.push(
        diag_warning("source.empty", "sources[\{i}]", "source path is empty"),
      )
    }
  }
  for i, name in self.names {
    if name.length() == 0 {
      diagnostics.push(
        diag_warning("name.empty", "names[\{i}]", "name entry is empty"),
      )
    }
  }
  for i, ignored in self.ignore_list {
    if ignored < 0 || ignored >= self.sources.length() {
      diagnostics.push(
        diag_error(
          "ignoreList.index",
          "ignoreList[\{i}]",
          "ignoreList index \{ignored} is outside sources range",
        ),
      )
    }
  }
  let mut previous_line = -1
  let mut previous_column = -1
  for i, segment in self.mappings {
    let path = "mappings[\{i}]"
    if segment.generated_line < 0 {
      diagnostics.push(
        diag_error("generated.line", path, "generated line is negative"),
      )
    }
    if segment.generated_column < 0 {
      diagnostics.push(
        diag_error("generated.column", path, "generated column is negative"),
      )
    }
    if segment.generated_line < previous_line {
      diagnostics.push(
        diag_error("mapping.order", path, "mapping lines must be nondecreasing"),
      )
    } else if segment.generated_line == previous_line &&
      segment.generated_column < previous_column {
      diagnostics.push(
        diag_error(
          "mapping.order", path, "mapping columns must be nondecreasing within a generated line",
        ),
      )
    }
    previous_line = segment.generated_line
    previous_column = segment.generated_column
    match
      (segment.source_index, segment.original_line, segment.original_column) {
      (None, None, None) => ()
      (Some(source), Some(line), Some(column)) => {
        if source < 0 || source >= self.sources.length() {
          diagnostics.push(
            diag_error(
              "source.index",
              path,
              "source index \{source} is outside sources range",
            ),
          )
        }
        if line < 0 {
          diagnostics.push(
            diag_error("original.line", path, "original line is negative"),
          )
        }
        if column < 0 {
          diagnostics.push(
            diag_error("original.column", path, "original column is negative"),
          )
        }
      }
      _ =>
        diagnostics.push(
          diag_error(
            "segment.partial", path, "mapped segments must carry source, original line and original column together",
          ),
        )
    }
    if segment.name_index is Some(name_index) {
      if name_index < 0 || name_index >= self.names.length() {
        diagnostics.push(
          diag_error(
            "name.index",
            path,
            "name index \{name_index} is outside names range",
          ),
        )
      }
    }
  }
  diagnostics
}

///|
pub fn SourceMap::is_valid(self : SourceMap) -> Bool {
  !self.validate().any(d => d.severity == "error")
}

///|
pub fn SourceMap::warnings(self : SourceMap) -> Array[Diagnostic] {
  self.validate().filter(d => d.severity == "warning")
}

///|
pub fn SourceMap::errors(self : SourceMap) -> Array[Diagnostic] {
  self.validate().filter(d => d.severity == "error")
}

///|
pub fn SourceMap::is_source_ignored(
  self : SourceMap,
  source : StringView,
) -> Bool {
  let source = source.to_owned()
  for i, item in self.sources {
    if item == source {
      return is_ignored(self, i)
    }
  }
  false
}