// The `moon.mod` and `moon.pkg` every generated project is written with, in the
// format `moon new` writes today. Both the scaffolds and the layered tree go
// through here, so the two cannot drift apart.

///|
/// The version of each library generated code imports.
///
/// These are the versions this repository's CI scaffolds a project against and
/// builds, so a generated project is promised to compile with them. A project that
/// wants a later release edits its own `moon.mod`.
pub let pins : Map[String, String] = {
  "moonbitstack/moonapi": "0.17.9",
  "moonbitstack/moonasgi": "0.10.6",
  "moonbitstack/mooncat": "0.19.5",
  "moonbitstack/moonmcp": "0.1.9",
  "moonbitstack/moongql": "0.10.9",
  "moonbitstack/moonrpc": "0.21.12",
  "moonbitstack/moonorm": "0.11.7",
  "moonbitstack/moondb": "0.2.6",
  "moonbitlang/async": "0.22.5",
}

///|
/// A `moon.mod` for module `name`, importing each of `deps` at its pin.
fn mod_file(name : String, deps : Array[String]) -> String {
  let out = StringBuilder()
  out.write_string("name = " + @text.quote(name) + "\n\nversion = \"0.1.0\"\n")
  if deps.length() > 0 {
    out.write_string("\nimport {\n")
    for dep in deps {
      guard pins.get(dep) is Some(version) else {
        abort(
          "moonctl generates an import of " + dep + " but has no pin for it",
        )
      }
      out.write_string("  " + @text.quote(dep + "@" + version) + ",\n")
    }
    out.write_string("}\n")
  }
  out.to_string()
}

///|
/// A `moon.mod` for module `name` importing `deps`, with the `moonx` rules `rules`
/// names — the build step that turns annotations into code.
fn mod_file_with_rules(
  name : String,
  deps : Array[String],
  rules : Array[(String, String)],
) -> String {
  let mut out = mod_file(name, deps)
  for r in rules {
    out = out + "\n" + rule_line(r.0, r.1)
  }
  out
}

///|
/// The `moonx` command that runs one of a library's generators, for the `rule(...)`
/// that wires it into `moon.mod`.
pub fn generator_command(library : String, cmd : String) -> String {
  "moonx " +
  library +
  "/cmd/" +
  cmd +
  "@" +
  pins.get(library).unwrap() +
  " -C \\\"$mod_dir\\\" \\\"$input\\\" -o \\\"$output\\\""
}

///|
/// The `rule(...)` entry that wires a `moonx` generator into `moon.mod`, on one line
/// when it fits in the width `moon fmt` breaks at and one argument per line when it
/// does not — the two shapes the formatter leaves it in, so a generated `moon.mod`
/// never changes on the first format.
pub fn rule_line(name : String, command : String) -> String {
  let one = "rule(name: " +
    @text.quote(name) +
    ", command: " +
    @text.quote(command) +
    ")"
  if one.length() <= 80 {
    one + "\n"
  } else {
    "rule(\n  name: " +
    @text.quote(name) +
    ",\n  command: " +
    @text.quote(command) +
    ",\n)\n"
  }
}

///|
/// A `moon.pkg` importing `entries`, each a `(path, alias)` pair where an empty
/// alias leaves the path's last segment as the name, and marked as the module's
/// native executable when `main_`.
fn pkg_file(entries : Array[(String, String)], main_~ : Bool = false) -> String {
  let out = StringBuilder()
  if entries.length() > 0 {
    out.write_string("import {\n")
    for entry in entries {
      out.write_string("  " + @text.quote(entry.0))
      if entry.1 != "" {
        out.write_string(" @" + entry.1)
      }
      out.write_string(",\n")
    }
    out.write_string("}\n")
  }
  if main_ {
    if entries.length() > 0 {
      out.write_char('\n')
    }
    out.write_string(
      "supported_targets = \"native\"\n\npkgtype(kind: \"executable\")\n",
    )
  }
  out.to_string()
}