///|
/// A value and its complete literal span in the original UTF-8 source.
#derive(Eq, Debug)
pub struct LocatedString {
  value : String
  span : Span
}

///|
#derive(Eq, Debug)
pub struct Dependency {
  name : String
  version : String
  span : Span
}

///|
fn Document::span(self : Document, value : @moon_config.Ast) -> Span {
  let source = @utf8.decode_lossy(self.source)
  let loc = value.loc()
  {
    start: position_to_byte(source, loc.start),
    end: position_to_byte(source, loc.end),
  }
}

///|
/// Query a top-level string field, never a nested field with the same name.
pub fn Document::string_field(self : Document, key : String) -> LocatedString? {
  for (name, value) in fields(self.ast) {
    if name == key && value is @moon_config.Str(text, ..) {
      return Some({ value: text, span: self.span(value), })
    }
  }
  None
}

///|
pub fn Document::member_entries(self : Document) -> Array[LocatedString] {
  let result = []
  for (key, value) in fields(self.ast) {
    if key == "members" && value is @moon_config.Arr(items, ..) {
      for item in items {
        if item is @moon_config.Str(text, ..) {
          result.push({ value: text, span: self.span(item), })
        }
      }
    }
  }
  result
}

///|
/// A MoonBit private module requests restricted registry visibility.
pub fn Document::is_private(self : Document) -> Bool {
  for (key, value) in fields(self.ast) {
    if key == "private" && value is @moon_config.Bool(flag, ..) {
      return flag
    }
  }
  false
}

///|
pub fn Document::field_span(self : Document, key : String) -> Span? {
  for (name, value) in fields(self.ast) {
    if name == key {
      return Some(self.span(value))
    }
  }
  None
}

///|
/// Import literal spans include the name, version, and surrounding quotes.
pub fn Document::dependencies(self : Document) -> Array[Dependency] {
  let result = []
  for (key, value) in fields(self.ast) {
    if key == "deps" {
      for (name, item) in fields(value) {
        if item is @moon_config.Str(version, ..) {
          result.push({ name, version, span: self.span(item), })
        }
      }
    }
  }
  result
}

///|
fn validate_imports(
  input : String,
  source : String,
  path : String,
) -> Array[Diagnostic] {
  let (raw, _) = @moon_config.parse_moon_pkg(input, name=path)
  let diagnostics = []
  let seen : Map[String, Bool] = Map([])
  for (key, value) in fields(raw) {
    if key == "import" || key == "test-import" || key == "wbtest-import" {
      if value is @moon_config.Arr(items, ..) {
        for item in items {
          let message = match item {
            @moon_config.Str(spec, ..) if key == "import" => {
              let name = match spec.rev_find("@") {
                Some(i) if i > 0 => spec[:i].to_owned()
                _ => spec
              }
              if seen.contains(name) {
                "Duplicate dependency `\{name}`"
              } else {
                seen[name] = true
                ""
              }
            }
            _ =>
              "Unsupported module import: expected a plain module@version string"
          }
          if message != "" {
            let loc = item.loc()
            diagnostics.push({
              message,
              start: position_to_byte(source, loc.start),
              end: position_to_byte(source, loc.end),
            })
          }
        }
      }
    }
  }
  diagnostics
}