///|
/// Result of reading a `moon.mod`: the metadata plus whatever `moon_config`
/// complained about, left for the caller to report.
pub(all) struct ParsedMoonMod {
  info : ModuleInfo
  /// Empty when the source is clean.
  errors : Array[String]
}

///|
/// First field named `key`. `moon_config` normalizes `preferred_target` to
/// `preferred-target` at the top level but keeps the underscore inside
/// `options(...)`, so both spellings are accepted.
fn find_field(
  fields : @vector.Vector[(String, @moon_config.Ast)],
  key : String,
) -> @moon_config.Ast? {
  let alt = key.replace_all(old="-", new="_")
  for (name, value) in fields {
    if name == key || name == alt {
      return Some(value)
    }
  }
  None
}

///|
fn field_str(
  fields : @vector.Vector[(String, @moon_config.Ast)],
  key : String,
) -> String {
  match find_field(fields, key) {
    Some(@moon_config.Str(value, ..)) => unescape(value)
    _ => ""
  }
}

///|
fn field_strs(
  fields : @vector.Vector[(String, @moon_config.Ast)],
  key : String,
) -> Array[String] {
  match find_field(fields, key) {
    Some(@moon_config.Str(value, ..)) => [unescape(value)]
    Some(@moon_config.Arr(items, ..)) =>
      items
      .to_array()
      .map(fn(item) {
        match (item : @moon_config.Ast) {
          @moon_config.Str(value, ..) => unescape(value)
          _ => ""
        }
      })
    _ => []
  }
}

///|
fn read_deps(
  fields : @vector.Vector[(String, @moon_config.Ast)],
) -> Array[Dependency] {
  match find_field(fields, "deps") {
    Some(@moon_config.Obj(deps, ..)) =>
      deps
      .to_array()
      .map(fn(dep) {
        let (name, value) = dep
        {
          name,
          version: match (value : @moon_config.Ast) {
            @moon_config.Str(version, ..) => unescape(version)
            _ => ""
          },
        }
      })
    _ => []
  }
}

///|
/// Parse `moon.mod` source into `ModuleInfo`.
///
/// The DSL is `moonbitlang/moon_config`'s job, including the diagnostics for
/// duplicate and unknown keys; nothing here re-implements it.
pub fn parse_moon_mod(
  name~ : String = "moon.mod",
  source : String,
) -> ParsedMoonMod {
  let (ast, reports) = @moon_config.parse_moon_mod(name~, source)
  let info = match (ast : @moon_config.Ast) {
    @moon_config.Obj(fields, ..) =>
      {
        name: field_str(fields, "name"),
        version: field_str(fields, "version"),
        description: field_str(fields, "description"),
        license: field_str(fields, "license"),
        repository: field_str(fields, "repository"),
        readme: field_str(fields, "readme"),
        keywords: field_strs(fields, "keywords"),
        preferred_target: field_str(fields, "preferred-target"),
        supported_targets: field_strs(fields, "supported-targets"),
        deps: read_deps(fields),
      }
    _ => ModuleInfo::empty()
  }
  { info, errors: reports.map(report => report.msg), }
}