///|
priv enum ConfigKind {
  Module
  Package
  Workspace
}

///|
fn config_name(kind : ConfigKind) -> String {
  match kind {
    Module => "moon.mod"
    Package => "moon.pkg"
    Workspace => "moon.work"
  }
}

///|
fn validate_config(
  ast : Ast,
  kind : ConfigKind,
  diagnostics : Diagnostics,
) -> Unit {
  let seen : Map[String, Bool] = Map([])
  let filename = config_name(kind)
  fn invalid(key : String, loc : Location) -> Unit {
    diagnostics.errors.push({
      loc,
      msg: "Invalid configuration `\{key}` in \{filename} file.",
    })
  }
  fn unique(key : String, loc : Location) -> Unit {
    let repeatable = (key == "rule" && !(kind is Workspace)) ||
      (key == "dev_build" && kind is Package)
    if seen.contains(key) && !repeatable {
      invalid(key, loc)
    }
    seen[key] = true
  }
  fn validate_value(key : String, value : Expr, loc : Location) -> Unit {
    unique(key, loc)
    match (kind, key, value) {
      (
        Module,
        "name"
        | "version"
        | "rule"
        | "readme"
        | "repository"
        | "license"
        | "keywords"
        | "description"
        | "source",
        _,
      ) => ()
      (Module, "private", Bool(_)) => ()
      (Module, "preferred_target" | "warnings" | "supported_targets", String(_)) =>
        ()
      (
        Package,
        "import"
        | "test-import"
        | "wbtest-import"
        | "dev_build"
        | "rule"
        | "formatter",
        _,
      ) => ()
      (Package, "warnings" | "supported_targets" | "regex_backend", String(_)) =>
        ()
      (Package, "proof_enabled" | "experimental", Bool(_)) => ()
      (Package, "max_concurrent_tests", Int(_)) => ()
      (Package, "pkgtype", value) => {
        let valid = match value {
          Map(fields~, ..) | Apply(args=fields, ..) =>
            find_field(fields, "kind")
            is Some(
              String(str="library" | "executable" | "foreign_library", ..)
            )
          _ => false
        }
        if !valid {
          diagnostics.errors.push({
            loc,
            msg: "Invalid `pkgtype` in moon.pkg file. Expected `pkgtype(kind: \"library\" | \"executable\" | \"foreign_library\")`.",
          })
        }
      }
      (Package, "virtual", value) => {
        let valid = match value {
          Map(fields~, ..) | Apply(args=fields, ..) =>
            if find_field(fields, "implement") is Some(implementation) {
              unique("implement", loc)
              fields.length() == 1 && implementation is String(_)
            } else if find_field(fields, "has_default") is Some(flag) {
              flag is Bool(_)
            } else {
              find_field(fields, "has-default") is Some(Bool(_))
            }
          _ => false
        }
        if !valid {
          invalid(key, loc)
        }
      }
      (Module | Package, "options", Apply(args~, ..)) =>
        for field in args {
          let field_loc = field.value.loc()
          let field_key = if kind is Package {
            match (field.key, field.value) {
              ("is-main" | "is_main" | "link", Bool(value=true, ..)) =>
                "pkgtype"
              ("proof-enabled", _) => "proof_enabled"
              ("max-concurrent-tests", _) => "max_concurrent_tests"
              ("regex-backend", _) => "regex_backend"
              ("virtual_pkg", _) => "virtual"
              _ => field.key
            }
          } else {
            field.key
          }
          unique(field_key, field_loc)
          if kind is Module &&
            field.key == "private" &&
            !(field.value is Bool(_)) {
            invalid(field.key, field_loc)
          }
          diagnose_option(kind, field, diagnostics.warnings)
        }
      (Workspace, "members" | "preferred_target", _) => ()
      _ => invalid(key, loc)
    }
  }
  for statement in ast.0 {
    match statement {
      Import(kind=import_kind, conditions~, loc~, ..) => {
        let key = import_kind_to_key(import_kind)
        if !(kind is Package) {
          unique(key, loc)
        }
        for condition in conditions {
          if !(kind is Package) {
            invalid("#cfg", condition.loc)
          } else if condition.parsed is None ||
            !condition.parsed.unwrap().is_valid() {
            diagnostics.errors.push({
              loc: condition.loc,
              msg: "Invalid `#cfg` in moon.pkg file. Expected one target condition, boolean, all(...), any(...), or not(...).",
            })
          }
        }
        if kind is Workspace || (kind is Module && !(import_kind is Normal)) {
          invalid(key, loc)
        }
      }
      Assign(field) => validate_value(field.key, field.value, field.value.loc())
      Expr(Apply(name~, args_loc~, ..) as value) =>
        validate_value(name, value, args_loc)
      Expr(value) => validate_value("", value, value.loc())
    }
  }
}

///|
fn diagnose_option(
  kind : ConfigKind,
  field : Field,
  warnings : Array[Report],
) -> Unit {
  let replacement = match (kind, field.key) {
    (Module, "private") => Some(("specify module privacy", "private = true"))
    (Module, "readme") => Some(("specify the README path", "readme = \"...\""))
    (Module, "repository") =>
      Some(("specify the repository URL", "repository = \"...\""))
    (Module, "license") => Some(("specify the license", "license = \"...\""))
    (Module, "keywords") => Some(("specify keywords", "keywords = [...]"))
    (Module, "description") =>
      Some(("specify the description", "description = \"...\""))
    (Module, "source") =>
      Some(("specify the source directory", "source = \"...\""))
    (Module, "preferred-target") =>
      Some(("specify the preferred target", "preferred_target = \"...\""))
    (Module | Package, "supported-targets") =>
      Some(("specify supported targets", "supported_targets = \"...\""))
    (Module | Package, "warn-list") =>
      Some(("specify warning options", "warnings = \"...\""))
    (Module | Package, "import") =>
      Some(("import packages", "import { \"path/to/pkg1\", \"path/to/pkg2\" }"))
    (Package, "formatter") =>
      Some(("configure formatter options", "formatter(ignore: [\"file\"])"))
    (Package, "test-import") =>
      Some(
        (
          "import packages for black-box test", "import { \"path/to/pkg\" } for \"test\"",
        ),
      )
    (Package, "wbtest-import") =>
      Some(
        (
          "import packages for white-box test", "import { \"path/to/pkg\" } for \"wbtest\"",
        ),
      )
    _ => None
  }
  if replacement is Some((purpose, new_config)) {
    warnings.push({
      loc: field.value.loc(),
      msg: "The syntax `\"\{field.key}\": ...` for \{purpose} is deprecated. Use `\{new_config}` in top-level instead.",
    })
  }
}