///|
fn usage() -> Unit {
  println("MoonIce 0.2.1 — Iceberg v2 snapshot inspector")
  println("moon run cmd/main --target js --   [options]")
  println("Actions: inspect, plan, scan, diff, check")
  println(
    "Options: --snapshot ID | --ref NAME, --filter JSON, --filter-file PATH, --limit N, --from ID",
  )
  println(
    "Pack local standard files: pack  --objects  --out ",
  )
}

///|
fn execute(args : Array[String]) -> Int raise {
  if args.length() < 2 || args[1] == "--help" || args[1] == "help" {
    usage()
    return 0
  }
  if args.length() < 3 {
    usage()
    return 2
  }
  let action = args[1]
  let input = args[2]
  let opts : Map[String, String] = Map([])
  let mut i = 3
  while i < args.length() {
    let name = args[i]
    if ![
        "--snapshot", "--ref", "--filter", "--filter-file", "--limit", "--from",
        "--objects", "--out",
      ].contains(name) ||
      i + 1 >= args.length() ||
      opts.contains(name) {
      println("Invalid, missing, or repeated option: \{name}")
      return 2
    }
    opts[name] = args[i + 1]
    i += 2
  }
  if action == "pack" {
    guard opts.get("--objects") is Some(mapping) &&
      opts.get("--out") is Some(output) else {
      usage()
      return 2
    }
    guard @json.parse(@fs.read_file_to_string(mapping)) is Object(objects) else {
      println("Object map must be a JSON object of URI -> local file path")
      return 2
    }
    if @fs.path_exists(output) {
      println("Refusing to overwrite existing output: \{output}")
      return 2
    }
    let files : Map[String, Json] = Map([])
    for uri, path in objects {
      guard path is String(local_path) else {
        println("Expected local path for \{uri}")
        return 2
      }
      files[uri] = Json::string(
        @base64.encode(@fs.read_file_to_bytes(local_path)),
      )
    }
    let bundle : Json = {
      "moonice_bundle_version": 1,
      "metadata": @fs.read_file_to_string(input),
      "files": Json::object(files),
    }
    ignore(@moonice.open_bundle(bundle.stringify()))
    @fs.write_string_to_file(output, bundle.stringify(indent=2))
    println("Packed \{files.length()} objects into \{output}")
    return 0
  }
  if !["inspect", "plan", "scan", "diff", "check"].contains(action) {
    usage()
    return 2
  }
  let options : Map[String, Json] = Map([("action", Json::string(action))])
  for name in ["snapshot", "ref", "from"] {
    if opts.get("--\{name}") is Some(value) {
      options[name] = Json::string(value)
    }
  }
  if opts.get("--limit") is Some(value) {
    options["limit"] = @json.parse(value)
  }
  if opts.contains("--filter") && opts.contains("--filter-file") {
    println("Choose --filter or --filter-file")
    return 2
  }
  if opts.get("--filter") is Some(value) {
    options["filter"] = @json.parse(value)
  }
  if opts.get("--filter-file") is Some(path) {
    options["filter"] = @json.parse(@fs.read_file_to_string(path))
  }
  let result = @json.parse(
    @moonice.run_request(
      @fs.read_file_to_string(input),
      Json::object(options).stringify(),
    ),
  )
  println(result.stringify(indent=2))
  if result is { "ok": False, .. } {
    return 1
  }
  if action == "check" &&
    result is { "result": { "findings": Array(findings), .. }, .. } &&
    findings.iter().any(f => f is { "severity": String("error"), .. }) {
    return 1
  }
  0
}

///|
// Node supplies both the runtime executable and script path. Keep the script
// path as argv[0], matching the native and Wasm command-line conventions.
#cfg(target="js")
fn cli_args() -> Array[String] {
  let args = @env.args()
  if args.is_empty() {
    []
  } else {
    args[1:].to_owned()
  }
}

///|
#cfg(not(target="js"))
fn cli_args() -> Array[String] {
  @env.args()
}

///|
fn main {
  let code = execute(cli_args()) catch {
    error => {
      println("MoonIce: \{error}")
      2
    }
  }
  if code != 0 {
    @sys.exit(code)
  }
}