///|
/// Execute the command-line protocol without touching process-global state.
/// This small pure boundary keeps the CLI testable and lets another host embed
/// the same commands without copying argument handling.
pub fn run_cli(args : Array[String]) -> (Bool, String) {
  let offset = cli_program_offset(args)
  if args.length() <= offset {
    return (true, cli_help())
  }
  let command = args[offset]
  if command == "help" || command == "--help" || command == "-h" {
    return (true, cli_help())
  }
  if command == "version" || command == "--version" {
    return (true, "moonbit-json-jcs " + library_version())
  }
  let json_mode = (command == "inspect" || command == "validate") &&
    args.length() > offset + 1 &&
    args[offset + 1] == "--json"
  let input_start = if json_mode { offset + 2 } else { offset + 1 }
  let input = join_cli_args(args, input_start)
  if input == "" {
    return (
      false, "missing JSON input; pass one JSON document after the command",
    )
  }
  match command {
    "canonicalize" => cli_canonicalize(input)
    "validate" => cli_report(input, json_mode)
    "inspect" => cli_report(input, json_mode)
    "digest" => cli_digest(input)
    "diff" => cli_diff(args, offset)
    "pretty" => cli_pretty(input)
    "tokens" => cli_tokens(input)
    "diagnose" => cli_diagnose(input)
    "profile" => cli_profile(input)
    "compare" => cli_compare(args, offset)
    _ => (false, "unknown command '" + command + "'; try 'help'")
  }
}

///|
/// `moon run` exposes the generated executable path as argv[0]. Native
/// launchers do the same, while library callers normally pass only commands.
fn cli_program_offset(args : Array[String]) -> Int {
  if args.length() == 0 {
    0
  } else if args[0].has_suffix(".wasm") ||
    args[0].has_suffix(".exe") ||
    args[0].has_suffix(".js") {
    1
  } else {
    0
  }
}

///|
pub fn cli_help() -> String {
  "moonbit-json-jcs - RFC 8785 JSON Canonicalization Scheme\n\n" +
  "Usage:\n" +
  "  moonbit-json-jcs canonicalize ''\n" +
  "  moonbit-json-jcs validate ''\n" +
  "  moonbit-json-jcs inspect ''\n" +
  "  moonbit-json-jcs digest ''\n" +
  "  moonbit-json-jcs diff '' ''\n\n" +
  "Commands:\n" +
  "  canonicalize  print deterministic RFC 8785 JSON\n" +
  "  validate      print validity and parser diagnostics\n" +
  "  inspect       print shape metrics and canonical digest\n" +
  "  digest        print SHA-256 of canonical UTF-8 bytes\n" +
  "  diff          compare two documents by JSON Pointer paths\n" +
  "  pretty        print an indented deterministic representation\n" +
  "  tokens        print source token spans\n" +
  "  diagnose      print semantic canonicalization issues\n" +
  "  profile       print detailed audit metrics as JSON\n" +
  "  compare       compare two documents and print a JSON report\n" +
  "  help          print this message\n"
}

///|
fn join_cli_args(args : Array[String], start : Int) -> String {
  let mut output = ""
  let mut index = start
  while index < args.length() {
    if output != "" {
      output = output + " "
    }
    output = output + args[index]
    index = index + 1
  }
  output
}

///|
fn cli_canonicalize(input : String) -> (Bool, String) {
  match parse(input) {
    Err(error) => (false, error.message())
    Ok(value) =>
      match canonicalize(value) {
        Ok(serialized) => (true, serialized)
        Err(error) => (false, error.message())
      }
  }
}

///|
fn cli_report(input : String, json_mode : Bool) -> (Bool, String) {
  let report = inspect_json(input)
  let output = if json_mode { report.to_json() } else { report.to_text() }
  (report.valid, output)
}

///|
fn cli_digest(input : String) -> (Bool, String) {
  match digest_json(input) {
    Ok(digest) => (true, digest)
    Err(error) => (false, error)
  }
}

///|
fn cli_diff(args : Array[String], offset : Int) -> (Bool, String) {
  if args.length() < offset + 3 {
    return (false, "diff requires two JSON documents")
  }
  match diff_documents(args[offset + 1], args[offset + 2]) {
    Err(error) => (false, error)
    Ok(differences) => {
      if differences.length() == 0 {
        return (true, "no differences")
      }
      let mut output = ""
      for difference in differences {
        if output != "" {
          output = output + "\n"
        }
        output = output + difference.to_text()
      }
      (true, output)
    }
  }
}

///|
fn cli_pretty(input : String) -> (Bool, String) {
  match pretty_document(input, 2) {
    Ok(output) => (true, output)
    Err(error) => (false, error)
  }
}

///|
fn cli_tokens(input : String) -> (Bool, String) {
  match tokenize(input) {
    Ok(tokens) => (true, token_summary(tokens))
    Err(error) => (false, error.message())
  }
}

///|
fn cli_diagnose(input : String) -> (Bool, String) {
  match diagnose_document(input) {
    Ok(issues) => (true, issues_text(issues))
    Err(error) => (false, error)
  }
}

///|
fn cli_profile(input : String) -> (Bool, String) {
  match profile_document(input) {
    Ok(profile) => (true, profile.to_json())
    Err(error) => (false, error)
  }
}

///|
fn cli_compare(args : Array[String], offset : Int) -> (Bool, String) {
  if args.length() < offset + 3 {
    return (false, "compare requires two JSON documents")
  }
  let report = compare_documents(args[offset + 1], args[offset + 2])
  (report.valid, report.to_json())
}