///|
/// CLI entry: apply a JSON patch (or merge patch, or compute a diff).
///
/// Usage:
///   moon run cmd/patch -- apply '' ''
///   moon run cmd/patch -- merge '' ''
///   moon run cmd/patch -- diff '' ''
///
/// Every document argument also accepts `@path` to read from a file, or `-`
/// to read from stdin (js backend only):
///   moon run cmd/patch -- apply @doc.json @patch.json
///   cat patch.json | moon run --target js cmd/patch -- apply doc.json -
///
/// Prints the resulting JSON, or a diagnostic, and exits non-zero on
/// failure.
fn main {
  let cmd = @argparse.Command::Command(
    "moonbit-jsonpatch",
    positionals=[
      @argparse.PositionArg::PositionArg(
        "command",
        about="apply | merge | diff",
      ),
      @argparse.PositionArg::PositionArg(
        "document",
        about="JSON text, @file, or - for stdin",
      ),
      @argparse.PositionArg::PositionArg(
        "patch",
        about="patch document (apply/merge), second document (diff); JSON text, @file, or -",
      ),
    ],
    options=[
      @argparse.OptionArg::OptionArg(
        "key",
        long="key",
        about="diff only: match array items by this member (e.g. id) instead of position",
      ),
    ],
    about="RFC 6902 JSON Patch and RFC 7386 JSON Merge Patch",
  )
  let matches = cmd.parse(argv=cli_args()) catch {
    _ => return // argparse has already printed its error/help
  }
  let command = match matches.values.get("command") {
    Some(arr) if arr.length() > 0 => arr[0]
    _ => return
  }
  fn arg_or_fail(name : String) -> String {
    match matches.values.get(name) {
      Some(arr) if arr.length() > 0 => arr[0]
      _ => abort("missing argument: \{name}")
    }
  }
  fn input_or_fail(name : String, what : String) -> String {
    match read_input(arg_or_fail(name), what) {
      Ok(text) => text
      Err(msg) => {
        println(msg)
        abort("input error")
      }
    }
  }
  let document = input_or_fail("document", "document")
  let patch = input_or_fail("patch", "patch")
  match command {
    "apply" =>
      match @lib.apply(document, patch) {
        Ok(out) => println(out)
        Err(@lib.BadDocumentJson(msg)) => {
          println("document parse error: \{msg}")
          abort("invalid document")
        }
        Err(@lib.BadPatchJson(msg)) => {
          println("patch parse error: \{msg}")
          abort("invalid patch")
        }
        Err(@lib.PatchFailed(e)) => {
          println(
            "patch failed at operation \{e.index} (\{e.op}, path \{e.path}): \{e.message}",
          )
          abort("patch failed")
        }
      }
    "merge" =>
      match @lib.merge(document, patch) {
        Ok(out) => println(out)
        Err(@lib.BadTargetJson(msg)) => {
          println("target parse error: \{msg}")
          abort("invalid target")
        }
        Err(@lib.BadPatchJson(msg)) => {
          println("patch parse error: \{msg}")
          abort("invalid patch")
        }
      }
    "diff" => {
      let keyed = match matches.values.get("key") {
        Some(arr) if arr.length() > 0 => Some(arr[0])
        _ => None
      }
      let result = match keyed {
        Some(key) => @lib.diff_text_keyed(document, patch, key)
        None => @lib.diff_text(document, patch)
      }
      match result {
        Ok(out) => println(out)
        Err(@lib.BadTargetJson(msg)) => {
          println("original parse error: \{msg}")
          abort("invalid original")
        }
        Err(@lib.BadPatchJson(msg)) => {
          println("modified parse error: \{msg}")
          abort("invalid modified")
        }
      }
    }
    _ => {
      println("unknown command: \{command} (expected apply, merge, or diff)")
      abort("bad command")
    }
  }
}